]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/filed/accurate.c
backport code from master
[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    if (!jcr->file_list) {
269       return true;              /* Not initialized properly */
270    }
271
272    strip_path(ff_pkt);
273  
274    if (S_ISDIR(ff_pkt->statp.st_mode)) {
275       fname = ff_pkt->link;
276    } else {
277       fname = ff_pkt->fname;
278    } 
279
280    if (!accurate_lookup(jcr, fname, &elt)) {
281       Dmsg1(dbglvl, "accurate %s (not found)\n", fname);
282       stat = true;
283       goto bail_out;
284    }
285
286    ff_pkt->accurate_found = true;
287    ff_pkt->delta_seq = elt.delta_seq;
288
289    decode_stat(elt.lstat, &statc, sizeof(statc), &LinkFIc); /* decode catalog stat */
290
291    if (!jcr->rerunning && (jcr->getJobLevel() == L_FULL)) {
292       opts = ff_pkt->BaseJobOpts;
293    } else {
294       opts = ff_pkt->AccurateOpts;
295    }
296
297    /*
298     * Loop over options supplied by user and verify the
299     * fields he requests.
300     */
301    for (char *p=opts; !stat && *p; p++) {
302       char ed1[30], ed2[30];
303       switch (*p) {
304       case 'i':                /* compare INODEs */
305          if (statc.st_ino != ff_pkt->statp.st_ino) {
306             Dmsg3(dbglvl-1, "%s      st_ino   differ. Cat: %s File: %s\n",
307                   fname,
308                   edit_uint64((uint64_t)statc.st_ino, ed1),
309                   edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
310             stat = true;
311          }
312          break;
313       case 'p':                /* permissions bits */
314          /* TODO: If something change only in perm, user, group
315           * Backup only the attribute stream
316           */
317          if (statc.st_mode != ff_pkt->statp.st_mode) {
318             Dmsg3(dbglvl-1, "%s     st_mode  differ. Cat: %x File: %x\n",
319                   fname,
320                   (uint32_t)statc.st_mode, (uint32_t)ff_pkt->statp.st_mode);
321             stat = true;
322          }
323          break;
324       case 'n':                /* number of links */
325          if (statc.st_nlink != ff_pkt->statp.st_nlink) {
326             Dmsg3(dbglvl-1, "%s      st_nlink differ. Cat: %d File: %d\n",
327                   fname,
328                   (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
329             stat = true;
330          }
331          break;
332       case 'u':                /* user id */
333          if (statc.st_uid != ff_pkt->statp.st_uid) {
334             Dmsg3(dbglvl-1, "%s      st_uid   differ. Cat: %u File: %u\n",
335                   fname,
336                   (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
337             stat = true;
338          }
339          break;
340       case 'g':                /* group id */
341          if (statc.st_gid != ff_pkt->statp.st_gid) {
342             Dmsg3(dbglvl-1, "%s      st_gid   differ. Cat: %u File: %u\n",
343                   fname,
344                   (uint32_t)statc.st_gid, (uint32_t)ff_pkt->statp.st_gid);
345             stat = true;
346          }
347          break;
348       case 's':                /* size */
349          if (statc.st_size != ff_pkt->statp.st_size) {
350             Dmsg3(dbglvl-1, "%s      st_size  differ. Cat: %s File: %s\n",
351                   fname,
352                   edit_uint64((uint64_t)statc.st_size, ed1),
353                   edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
354             stat = true;
355          }
356          break;
357       case 'a':                /* access time */
358          if (statc.st_atime != ff_pkt->statp.st_atime) {
359             Dmsg1(dbglvl-1, "%s      st_atime differs\n", fname);
360             stat = true;
361          }
362          break;
363       case 'm':                 /* modification time */
364          if (statc.st_mtime != ff_pkt->statp.st_mtime) {
365             Dmsg1(dbglvl-1, "%s      st_mtime differs\n", fname);
366             stat = true;
367          }
368          break;
369       case 'c':                /* ctime */
370          if (statc.st_ctime != ff_pkt->statp.st_ctime) {
371             Dmsg1(dbglvl-1, "%s      st_ctime differs\n", fname);
372             stat = true;
373          }
374          break;
375       case 'd':                /* file size decrease */
376          if (statc.st_size > ff_pkt->statp.st_size) {
377             Dmsg3(dbglvl-1, "%s      st_size  decrease. Cat: %s File: %s\n",
378                   fname,
379                   edit_uint64((uint64_t)statc.st_size, ed1),
380                   edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
381             stat = true;
382          }
383          break;
384       case 'A':                 /* Always backup a file */
385          stat = true;
386          break;
387       /* TODO: cleanup and factorise this function with verify.c */
388       case '5':                /* compare MD5 */
389       case '1':                /* compare SHA1 */
390         /*
391           * The remainder of the function is all about getting the checksum.
392           * First we initialise, then we read files, other streams and Finder Info.
393           */
394          if (!stat && ff_pkt->type != FT_LNKSAVED && 
395              (S_ISREG(ff_pkt->statp.st_mode) && 
396               ff_pkt->flags & (FO_MD5|FO_SHA1|FO_SHA256|FO_SHA512))) 
397          {
398
399             if (!*elt.chksum && !jcr->rerunning) {
400                Jmsg(jcr, M_WARNING, 0, _("Cannot verify checksum for %s\n"),
401                     ff_pkt->fname);
402                stat = true;
403                break;
404             }
405
406             /*
407              * Create our digest context. If this fails, the digest will be set
408              * to NULL and not used.
409              */
410             if (ff_pkt->flags & FO_MD5) {
411                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_MD5);
412                digest_stream = STREAM_MD5_DIGEST;
413                
414             } else if (ff_pkt->flags & FO_SHA1) {
415                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA1);
416                digest_stream = STREAM_SHA1_DIGEST;
417                
418             } else if (ff_pkt->flags & FO_SHA256) {
419                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA256);
420                digest_stream = STREAM_SHA256_DIGEST;
421                
422             } else if (ff_pkt->flags & FO_SHA512) {
423                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA512);
424                digest_stream = STREAM_SHA512_DIGEST;
425             }
426             
427             /* Did digest initialization fail? */
428             if (digest_stream != STREAM_NONE && digest == NULL) {
429                Jmsg(jcr, M_WARNING, 0, _("%s digest initialization failed\n"),
430                     stream_to_ascii(digest_stream));
431             }
432
433             /* compute MD5 or SHA1 hash */
434             if (digest) {
435                char md[CRYPTO_DIGEST_MAX_SIZE];
436                uint32_t size;
437                
438                size = sizeof(md);
439                
440                if (digest_file(jcr, ff_pkt, digest) != 0) {
441                   jcr->JobErrors++;
442
443                } else if (crypto_digest_finalize(digest, (uint8_t *)md, &size)) {
444                   char *digest_buf;
445                   const char *digest_name;
446                   
447                   digest_buf = (char *)malloc(BASE64_SIZE(size));
448                   digest_name = crypto_digest_name(digest);
449                   
450                   bin_to_base64(digest_buf, BASE64_SIZE(size), md, size, true);
451
452                   if (strcmp(digest_buf, elt.chksum)) {
453                      Dmsg4(dbglvl,"%s      %s chksum  diff. Cat: %s File: %s\n",
454                            fname,
455                            digest_name,
456                            elt.chksum,
457                            digest_buf);
458                      stat = true;
459                   }
460                   
461                   free(digest_buf);
462                }
463                crypto_digest_free(digest);
464             }
465          }
466
467          break;
468       case ':':
469       case 'J':
470       case 'C':
471       default:
472          break;
473       }
474    }
475
476    /* In Incr/Diff accurate mode, we mark all files as seen
477     * When in Full+Base mode, we mark only if the file match exactly
478     */
479    if (jcr->getJobLevel() == L_FULL) {
480       if (!stat) {               
481          /* compute space saved with basefile */
482          jcr->base_size += ff_pkt->statp.st_size;
483          accurate_mark_file_as_seen(jcr, &elt);
484       }
485    } else {
486       accurate_mark_file_as_seen(jcr, &elt);
487    }
488
489 bail_out:
490    unstrip_path(ff_pkt);
491    return stat;
492 }
493
494 /* 
495  * TODO: use big buffer from htable
496  */
497 int accurate_cmd(JCR *jcr)
498 {
499    BSOCK *dir = jcr->dir_bsock;
500    int lstat_pos, chksum_pos;
501    int32_t nb;
502    uint16_t delta_seq;
503
504    if (job_canceled(jcr)) {
505       return true;
506    }
507    if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
508       dir->fsend(_("2991 Bad accurate command\n"));
509       return false;
510    }
511
512    jcr->accurate = true;
513
514    accurate_init(jcr, nb);
515
516    /*
517     * buffer = sizeof(CurFile) + dirmsg
518     * dirmsg = fname + \0 + lstat + \0 + checksum + \0 + delta_seq + \0
519     */
520    /* get current files */
521    while (dir->recv() >= 0) {
522       lstat_pos = strlen(dir->msg) + 1;
523       if (lstat_pos < dir->msglen) {
524          chksum_pos = lstat_pos + strlen(dir->msg + lstat_pos) + 1;
525
526          if (chksum_pos >= dir->msglen) {
527             chksum_pos = lstat_pos - 1;    /* tweak: no checksum, point to the last \0 */
528             delta_seq = 0;
529          } else {
530             delta_seq = str_to_int32(dir->msg + 
531                                      chksum_pos + 
532                                      strlen(dir->msg + chksum_pos) + 1);
533          }
534
535          accurate_add_file(jcr, dir->msglen, 
536                            dir->msg,               /* Path */
537                            dir->msg + lstat_pos,   /* LStat */
538                            dir->msg + chksum_pos,  /* CheckSum */
539                            delta_seq);             /* Delta Sequence */
540       }
541    }
542
543 #ifdef DEBUG
544    extern void *start_heap;
545
546    char b1[50], b2[50], b3[50], b4[50], b5[50];
547    Dmsg5(dbglvl," Heap: heap=%s smbytes=%s max_bytes=%s bufs=%s max_bufs=%s\n",
548          edit_uint64_with_commas((char *)sbrk(0)-(char *)start_heap, b1),
549          edit_uint64_with_commas(sm_bytes, b2),
550          edit_uint64_with_commas(sm_max_bytes, b3),
551          edit_uint64_with_commas(sm_buffers, b4),
552          edit_uint64_with_commas(sm_max_buffers, b5));
553 #endif
554
555    return true;
556 }