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