]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/filed/accurate.c
Merge branch 'Branch-5.2' of bsweb:bacula into Branch-5.2
[bacula/bacula] / bacula / src / filed / accurate.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2011 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version three of the GNU Affero General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU Affero General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of Kern Sibbald.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *  Version $Id $
30  *
31  */
32
33 #include "bacula.h"
34 #include "filed.h"
35
36 static int dbglvl=100;
37
38 typedef struct PrivateCurFile {
39    hlink link;
40    char *fname;
41    char *lstat;
42    char *chksum;
43    int32_t delta_seq;
44    bool seen;
45 } CurFile;
46
47 bool accurate_mark_file_as_seen(JCR *jcr, char *fname)
48 {
49    if (!jcr->accurate || !jcr->file_list) {
50       return false;
51    }
52    /* TODO: just use elt->seen = 1 */
53    CurFile *temp = (CurFile *)jcr->file_list->lookup(fname);
54    if (temp) {
55       temp->seen = 1;              /* records are in memory */
56       Dmsg1(dbglvl, "marked <%s> as seen\n", fname);
57    } else {
58       Dmsg1(dbglvl, "<%s> not found to be marked as seen\n", fname);
59    }
60    return true;
61 }
62
63 static bool accurate_mark_file_as_seen(JCR *jcr, CurFile *elt)
64 {
65    /* TODO: just use elt->seen = 1 */
66    CurFile *temp = (CurFile *)jcr->file_list->lookup(elt->fname);
67    if (temp) {
68       temp->seen = 1;              /* records are in memory */
69    }
70    return true;
71 }
72
73 static bool accurate_lookup(JCR *jcr, char *fname, CurFile *ret)
74 {
75    bool found=false;
76    ret->seen = 0;
77
78    CurFile *temp = (CurFile *)jcr->file_list->lookup(fname);
79    if (temp) {
80       memcpy(ret, temp, sizeof(CurFile));
81       found=true;
82       Dmsg1(dbglvl, "lookup <%s> ok\n", fname);
83    }
84
85    return found;
86 }
87
88 static bool accurate_init(JCR *jcr, int nbfile)
89 {
90    CurFile *elt = NULL;
91    jcr->file_list = (htable *)malloc(sizeof(htable));
92    jcr->file_list->init(elt, &elt->link, nbfile);
93    return true;
94 }
95
96 static bool accurate_send_base_file_list(JCR *jcr)
97 {
98    CurFile *elt;
99    struct stat statc;
100    int32_t LinkFIc;
101    FF_PKT *ff_pkt;
102    int stream = STREAM_UNIX_ATTRIBUTES;
103
104    if (!jcr->accurate || jcr->getJobLevel() != L_FULL) {
105       return true;
106    }
107
108    if (jcr->file_list == NULL) {
109       return true;
110    }
111
112    ff_pkt = init_find_files();
113    ff_pkt->type = FT_BASE;
114
115    foreach_htable(elt, jcr->file_list) {
116       if (elt->seen) {
117          Dmsg2(dbglvl, "base file fname=%s seen=%i\n", elt->fname, elt->seen);
118          /* TODO: skip the decode and use directly the lstat field */
119          decode_stat(elt->lstat, &statc, sizeof(statc), &LinkFIc); /* decode catalog stat */  
120          ff_pkt->fname = elt->fname;
121          ff_pkt->statp = statc;
122          encode_and_send_attributes(jcr, ff_pkt, stream);
123 //       free(elt->fname);
124       }
125    }
126
127    term_find_files(ff_pkt);
128    return true;
129 }
130
131
132 /* This function is called at the end of backup
133  * We walk over all hash disk element, and we check
134  * for elt.seen.
135  */
136 static bool accurate_send_deleted_list(JCR *jcr)
137 {
138    CurFile *elt;
139    struct stat statc;
140    int32_t LinkFIc;
141    FF_PKT *ff_pkt;
142    int stream = STREAM_UNIX_ATTRIBUTES;
143
144    if (!jcr->accurate) {
145       return true;
146    }
147
148    if (jcr->file_list == NULL) {
149       return true;
150    }
151
152    ff_pkt = init_find_files();
153    ff_pkt->type = FT_DELETED;
154
155    foreach_htable(elt, jcr->file_list) {
156       if (elt->seen || plugin_check_file(jcr, elt->fname)) {
157          continue;
158       }
159       Dmsg2(dbglvl, "deleted fname=%s seen=%i\n", elt->fname, elt->seen);
160       /* TODO: skip the decode and use directly the lstat field */
161       decode_stat(elt->lstat, &statc, sizeof(statc), &LinkFIc); /* decode catalog stat */
162       ff_pkt->fname = elt->fname;
163       ff_pkt->statp.st_mtime = statc.st_mtime;
164       ff_pkt->statp.st_ctime = statc.st_ctime;
165       encode_and_send_attributes(jcr, ff_pkt, stream);
166 //    free(elt->fname);
167    }
168
169    term_find_files(ff_pkt);
170    return true;
171 }
172
173 void accurate_free(JCR *jcr)
174 {
175    if (jcr->file_list) {
176       jcr->file_list->destroy();
177       free(jcr->file_list);
178       jcr->file_list = NULL;
179    }
180 }
181
182 /* Send the deleted or the base file list and cleanup  */
183 bool accurate_finish(JCR *jcr)
184 {
185    bool ret = true;
186
187    if (jcr->is_canceled() || jcr->is_incomplete()) {
188       accurate_free(jcr);
189       return ret;
190    }
191    if (jcr->accurate) {
192       if (jcr->is_JobLevel(L_FULL)) {
193          if (!jcr->rerunning) {
194             ret = accurate_send_base_file_list(jcr);
195          }
196       } else {
197          ret = accurate_send_deleted_list(jcr);
198       }
199       accurate_free(jcr);
200       if (jcr->is_JobLevel(L_FULL)) {
201          Jmsg(jcr, M_INFO, 0, _("Space saved with Base jobs: %lld MB\n"), 
202               jcr->base_size/(1024*1024));
203       }
204    }
205    return ret;
206 }
207
208 static bool accurate_add_file(JCR *jcr, uint32_t len, 
209                               char *fname, char *lstat, char *chksum,
210                               int32_t delta)
211 {
212    bool ret = true;
213    CurFile *item;
214
215    /* we store CurFile, fname and ctime/mtime in the same chunk 
216     * we need one extra byte to handle an empty chksum
217     */
218    item = (CurFile *)jcr->file_list->hash_malloc(sizeof(CurFile)+len+3);
219    item->seen = 0;
220
221    /* TODO: see if we can optimize this part with memcpy instead of strcpy */
222    item->fname  = (char *)item+sizeof(CurFile);
223    strcpy(item->fname, fname);
224
225    item->lstat  = item->fname+strlen(item->fname)+1;
226    strcpy(item->lstat, lstat);
227
228    item->chksum = item->lstat+strlen(item->lstat)+1;
229    strcpy(item->chksum, chksum);
230
231    item->delta_seq = delta;
232
233    jcr->file_list->insert(item->fname, item); 
234
235    Dmsg4(dbglvl, "add fname=<%s> lstat=%s  delta_seq=%i chksum=%s\n", 
236          fname, lstat, delta, chksum);
237    return ret;
238 }
239
240 /*
241  * This function is called for each file seen in fileset.
242  * We check in file_list hash if fname have been backuped
243  * the last time. After we can compare Lstat field. 
244  * Full Lstat usage have been removed on 6612 
245  *
246  * Returns: true   if file has changed (must be backed up)
247  *          false  file not changed
248  */
249 bool accurate_check_file(JCR *jcr, FF_PKT *ff_pkt)
250 {
251    int digest_stream = STREAM_NONE;
252    DIGEST *digest = NULL;
253
254    struct stat statc;
255    int32_t LinkFIc;
256    bool stat = false;
257    char *opts;
258    char *fname;
259    CurFile elt;
260
261    ff_pkt->delta_seq = 0;
262    ff_pkt->accurate_found = false;
263
264    if (!jcr->accurate && !jcr->rerunning) {
265       return true;
266    }
267
268    strip_path(ff_pkt);
269  
270    if (S_ISDIR(ff_pkt->statp.st_mode)) {
271       fname = ff_pkt->link;
272    } else {
273       fname = ff_pkt->fname;
274    } 
275
276    if (!accurate_lookup(jcr, fname, &elt)) {
277       Dmsg1(dbglvl, "accurate %s (not found)\n", fname);
278       stat = true;
279       goto bail_out;
280    }
281
282    ff_pkt->accurate_found = true;
283    ff_pkt->delta_seq = elt.delta_seq;
284
285    if (elt.seen) { /* file has been seen ? */
286       Dmsg1(dbglvl, "accurate %s (already seen)\n", fname);
287       goto bail_out;
288    }
289
290    decode_stat(elt.lstat, &statc, sizeof(statc), &LinkFIc); /* decode catalog stat */
291
292    if (!jcr->rerunning && (jcr->getJobLevel() == L_FULL)) {
293       opts = ff_pkt->BaseJobOpts;
294    } else {
295       opts = ff_pkt->AccurateOpts;
296    }
297
298    /*
299     * Loop over options supplied by user and verify the
300     * fields he requests.
301     */
302    for (char *p=opts; !stat && *p; p++) {
303       char ed1[30], ed2[30];
304       switch (*p) {
305       case 'i':                /* compare INODEs */
306          if (statc.st_ino != ff_pkt->statp.st_ino) {
307             Dmsg3(dbglvl-1, "%s      st_ino   differ. Cat: %s File: %s\n",
308                   fname,
309                   edit_uint64((uint64_t)statc.st_ino, ed1),
310                   edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
311             stat = true;
312          }
313          break;
314       case 'p':                /* permissions bits */
315          /* TODO: If something change only in perm, user, group
316           * Backup only the attribute stream
317           */
318          if (statc.st_mode != ff_pkt->statp.st_mode) {
319             Dmsg3(dbglvl-1, "%s     st_mode  differ. Cat: %x File: %x\n",
320                   fname,
321                   (uint32_t)statc.st_mode, (uint32_t)ff_pkt->statp.st_mode);
322             stat = true;
323          }
324          break;
325       case 'n':                /* number of links */
326          if (statc.st_nlink != ff_pkt->statp.st_nlink) {
327             Dmsg3(dbglvl-1, "%s      st_nlink differ. Cat: %d File: %d\n",
328                   fname,
329                   (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
330             stat = true;
331          }
332          break;
333       case 'u':                /* user id */
334          if (statc.st_uid != ff_pkt->statp.st_uid) {
335             Dmsg3(dbglvl-1, "%s      st_uid   differ. Cat: %u File: %u\n",
336                   fname,
337                   (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
338             stat = true;
339          }
340          break;
341       case 'g':                /* group id */
342          if (statc.st_gid != ff_pkt->statp.st_gid) {
343             Dmsg3(dbglvl-1, "%s      st_gid   differ. Cat: %u File: %u\n",
344                   fname,
345                   (uint32_t)statc.st_gid, (uint32_t)ff_pkt->statp.st_gid);
346             stat = true;
347          }
348          break;
349       case 's':                /* size */
350          if (statc.st_size != ff_pkt->statp.st_size) {
351             Dmsg3(dbglvl-1, "%s      st_size  differ. Cat: %s File: %s\n",
352                   fname,
353                   edit_uint64((uint64_t)statc.st_size, ed1),
354                   edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
355             stat = true;
356          }
357          break;
358       case 'a':                /* access time */
359          if (statc.st_atime != ff_pkt->statp.st_atime) {
360             Dmsg1(dbglvl-1, "%s      st_atime differs\n", fname);
361             stat = true;
362          }
363          break;
364       case 'm':                 /* modification time */
365          if (statc.st_mtime != ff_pkt->statp.st_mtime) {
366             Dmsg1(dbglvl-1, "%s      st_mtime differs\n", fname);
367             stat = true;
368          }
369          break;
370       case 'c':                /* ctime */
371          if (statc.st_ctime != ff_pkt->statp.st_ctime) {
372             Dmsg1(dbglvl-1, "%s      st_ctime differs\n", fname);
373             stat = true;
374          }
375          break;
376       case 'd':                /* file size decrease */
377          if (statc.st_size > ff_pkt->statp.st_size) {
378             Dmsg3(dbglvl-1, "%s      st_size  decrease. Cat: %s File: %s\n",
379                   fname,
380                   edit_uint64((uint64_t)statc.st_size, ed1),
381                   edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
382             stat = true;
383          }
384          break;
385       case 'A':                 /* Always backup a file */
386          stat = true;
387          break;
388       /* TODO: cleanup and factorise this function with verify.c */
389       case '5':                /* compare MD5 */
390       case '1':                /* compare SHA1 */
391         /*
392           * The remainder of the function is all about getting the checksum.
393           * First we initialise, then we read files, other streams and Finder Info.
394           */
395          if (!stat && ff_pkt->type != FT_LNKSAVED && 
396              (S_ISREG(ff_pkt->statp.st_mode) && 
397               ff_pkt->flags & (FO_MD5|FO_SHA1|FO_SHA256|FO_SHA512))) 
398          {
399
400             if (!*elt.chksum && !jcr->rerunning) {
401                Jmsg(jcr, M_WARNING, 0, _("Cannot verify checksum for %s\n"),
402                     ff_pkt->fname);
403                stat = true;
404                break;
405             }
406
407             /*
408              * Create our digest context. If this fails, the digest will be set
409              * to NULL and not used.
410              */
411             if (ff_pkt->flags & FO_MD5) {
412                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_MD5);
413                digest_stream = STREAM_MD5_DIGEST;
414                
415             } else if (ff_pkt->flags & FO_SHA1) {
416                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA1);
417                digest_stream = STREAM_SHA1_DIGEST;
418                
419             } else if (ff_pkt->flags & FO_SHA256) {
420                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA256);
421                digest_stream = STREAM_SHA256_DIGEST;
422                
423             } else if (ff_pkt->flags & FO_SHA512) {
424                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA512);
425                digest_stream = STREAM_SHA512_DIGEST;
426             }
427             
428             /* Did digest initialization fail? */
429             if (digest_stream != STREAM_NONE && digest == NULL) {
430                Jmsg(jcr, M_WARNING, 0, _("%s digest initialization failed\n"),
431                     stream_to_ascii(digest_stream));
432             }
433
434             /* compute MD5 or SHA1 hash */
435             if (digest) {
436                char md[CRYPTO_DIGEST_MAX_SIZE];
437                uint32_t size;
438                
439                size = sizeof(md);
440                
441                if (digest_file(jcr, ff_pkt, digest) != 0) {
442                   jcr->JobErrors++;
443
444                } else if (crypto_digest_finalize(digest, (uint8_t *)md, &size)) {
445                   char *digest_buf;
446                   const char *digest_name;
447                   
448                   digest_buf = (char *)malloc(BASE64_SIZE(size));
449                   digest_name = crypto_digest_name(digest);
450                   
451                   bin_to_base64(digest_buf, BASE64_SIZE(size), md, size, true);
452
453                   if (strcmp(digest_buf, elt.chksum)) {
454                      Dmsg4(dbglvl,"%s      %s chksum  diff. Cat: %s File: %s\n",
455                            fname,
456                            digest_name,
457                            elt.chksum,
458                            digest_buf);
459                      stat = true;
460                   }
461                   
462                   free(digest_buf);
463                }
464                crypto_digest_free(digest);
465             }
466          }
467
468          break;
469       case ':':
470       case 'J':
471       case 'C':
472       default:
473          break;
474       }
475    }
476
477    /* In Incr/Diff accurate mode, we mark all files as seen
478     * When in Full+Base mode, we mark only if the file match exactly
479     */
480    if (jcr->getJobLevel() == L_FULL) {
481       if (!stat) {               
482          /* compute space saved with basefile */
483          jcr->base_size += ff_pkt->statp.st_size;
484          accurate_mark_file_as_seen(jcr, &elt);
485       }
486    } else {
487       accurate_mark_file_as_seen(jcr, &elt);
488    }
489
490 bail_out:
491    unstrip_path(ff_pkt);
492    return stat;
493 }
494
495 /* 
496  * TODO: use big buffer from htable
497  */
498 int accurate_cmd(JCR *jcr)
499 {
500    BSOCK *dir = jcr->dir_bsock;
501    int lstat_pos, chksum_pos;
502    int32_t nb;
503    uint16_t delta_seq;
504
505    if (job_canceled(jcr)) {
506       return true;
507    }
508    if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
509       dir->fsend(_("2991 Bad accurate command\n"));
510       return false;
511    }
512
513    jcr->accurate = true;
514
515    accurate_init(jcr, nb);
516
517    /*
518     * buffer = sizeof(CurFile) + dirmsg
519     * dirmsg = fname + \0 + lstat + \0 + checksum + \0 + delta_seq + \0
520     */
521    /* get current files */
522    while (dir->recv() >= 0) {
523       lstat_pos = strlen(dir->msg) + 1;
524       if (lstat_pos < dir->msglen) {
525          chksum_pos = lstat_pos + strlen(dir->msg + lstat_pos) + 1;
526
527          if (chksum_pos >= dir->msglen) {
528             chksum_pos = lstat_pos - 1;    /* tweak: no checksum, point to the last \0 */
529             delta_seq = 0;
530          } else {
531             delta_seq = str_to_int32(dir->msg + 
532                                      chksum_pos + 
533                                      strlen(dir->msg + chksum_pos) + 1);
534          }
535
536          accurate_add_file(jcr, dir->msglen, 
537                            dir->msg,               /* Path */
538                            dir->msg + lstat_pos,   /* LStat */
539                            dir->msg + chksum_pos,  /* CheckSum */
540                            delta_seq);             /* Delta Sequence */
541       }
542    }
543
544 #ifdef DEBUG
545    extern void *start_heap;
546
547    char b1[50], b2[50], b3[50], b4[50], b5[50];
548    Dmsg5(dbglvl," Heap: heap=%s smbytes=%s max_bytes=%s bufs=%s max_bufs=%s\n",
549          edit_uint64_with_commas((char *)sbrk(0)-(char *)start_heap, b1),
550          edit_uint64_with_commas(sm_bytes, b2),
551          edit_uint64_with_commas(sm_max_bytes, b3),
552          edit_uint64_with_commas(sm_buffers, b4),
553          edit_uint64_with_commas(sm_max_buffers, b5));
554 #endif
555
556    return true;
557 }