]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/filed/accurate.c
Add delta sequence to batch mode, accurate query and file daemon ff_pkt
[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 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, &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, &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    if (jcr->accurate) {
187       if (jcr->getJobLevel() == L_FULL) {
188          ret = accurate_send_base_file_list(jcr);
189       } else {
190          ret = accurate_send_deleted_list(jcr);
191       }
192       
193       accurate_free(jcr);
194       if (jcr->getJobLevel() == L_FULL) {
195          Jmsg(jcr, M_INFO, 0, _("Space saved with Base jobs: %lld MB\n"), 
196               jcr->base_size/(1024*1024));
197       }
198    }
199    return ret;
200 }
201
202 static bool accurate_add_file(JCR *jcr, uint32_t len, 
203                               char *fname, char *lstat, char *chksum,
204                               int32_t delta)
205 {
206    bool ret = true;
207    CurFile elt;
208    elt.seen = 0;
209
210    CurFile *item;
211    /* we store CurFile, fname and ctime/mtime in the same chunk */
212    item = (CurFile *)jcr->file_list->hash_malloc(sizeof(CurFile)+len+3);
213    memcpy(item, &elt, sizeof(CurFile));
214
215    item->fname  = (char *)item+sizeof(CurFile);
216    strcpy(item->fname, fname);
217
218    item->lstat  = item->fname+strlen(item->fname)+1;
219    strcpy(item->lstat, lstat);
220
221    item->chksum = item->lstat+strlen(item->lstat)+1;
222    strcpy(item->chksum, chksum);
223
224    item->delta_seq = delta;
225
226    jcr->file_list->insert(item->fname, item); 
227
228    Dmsg4(dbglvl, "add fname=<%s> lstat=%s  delta_seq=%i chksum=%s\n", 
229          fname, lstat, delta, chksum);
230    return ret;
231 }
232
233 /*
234  * This function is called for each file seen in fileset.
235  * We check in file_list hash if fname have been backuped
236  * the last time. After we can compare Lstat field. 
237  * Full Lstat usage have been removed on 6612 
238  *
239  * Returns: true   if file has changed (must be backed up)
240  *          false  file not changed
241  */
242 bool accurate_check_file(JCR *jcr, FF_PKT *ff_pkt)
243 {
244    int digest_stream = STREAM_NONE;
245    DIGEST *digest = NULL;
246
247    struct stat statc;
248    int32_t LinkFIc;
249    bool stat = false;
250    char *opts;
251    char *fname;
252    CurFile elt;
253
254    ff_pkt->delta_seq = 0;
255
256    if (!jcr->accurate) {
257       return true;
258    }
259
260    strip_path(ff_pkt);
261  
262    if (S_ISDIR(ff_pkt->statp.st_mode)) {
263       fname = ff_pkt->link;
264    } else {
265       fname = ff_pkt->fname;
266    } 
267
268    if (!accurate_lookup(jcr, fname, &elt)) {
269       Dmsg1(dbglvl, "accurate %s (not found)\n", fname);
270       stat = true;
271       goto bail_out;
272    }
273
274    ff_pkt->delta_seq = elt.delta_seq;
275    Dmsg1(10, "accurate delta_seq=%i\n", ff_pkt->delta_seq);
276
277    if (elt.seen) { /* file has been seen ? */
278       Dmsg1(dbglvl, "accurate %s (already seen)\n", fname);
279       goto bail_out;
280    }
281
282    decode_stat(elt.lstat, &statc, &LinkFIc); /* decode catalog stat */
283
284    if (jcr->getJobLevel() == L_FULL) {
285       opts = ff_pkt->BaseJobOpts;
286    } else {
287       opts = ff_pkt->AccurateOpts;
288    }
289
290    /*
291     * Loop over options supplied by user and verify the
292     * fields he requests.
293     */
294    for (char *p=opts; !stat && *p; p++) {
295       char ed1[30], ed2[30];
296       switch (*p) {
297       case 'i':                /* compare INODEs */
298          if (statc.st_ino != ff_pkt->statp.st_ino) {
299             Dmsg3(dbglvl-1, "%s      st_ino   differ. Cat: %s File: %s\n",
300                   fname,
301                   edit_uint64((uint64_t)statc.st_ino, ed1),
302                   edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
303             stat = true;
304          }
305          break;
306       case 'p':                /* permissions bits */
307          /* TODO: If something change only in perm, user, group
308           * Backup only the attribute stream
309           */
310          if (statc.st_mode != ff_pkt->statp.st_mode) {
311             Dmsg3(dbglvl-1, "%s     st_mode  differ. Cat: %x File: %x\n",
312                   fname,
313                   (uint32_t)statc.st_mode, (uint32_t)ff_pkt->statp.st_mode);
314             stat = true;
315          }
316          break;
317       case 'n':                /* number of links */
318          if (statc.st_nlink != ff_pkt->statp.st_nlink) {
319             Dmsg3(dbglvl-1, "%s      st_nlink differ. Cat: %d File: %d\n",
320                   fname,
321                   (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
322             stat = true;
323          }
324          break;
325       case 'u':                /* user id */
326          if (statc.st_uid != ff_pkt->statp.st_uid) {
327             Dmsg3(dbglvl-1, "%s      st_uid   differ. Cat: %u File: %u\n",
328                   fname,
329                   (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
330             stat = true;
331          }
332          break;
333       case 'g':                /* group id */
334          if (statc.st_gid != ff_pkt->statp.st_gid) {
335             Dmsg3(dbglvl-1, "%s      st_gid   differ. Cat: %u File: %u\n",
336                   fname,
337                   (uint32_t)statc.st_gid, (uint32_t)ff_pkt->statp.st_gid);
338             stat = true;
339          }
340          break;
341       case 's':                /* size */
342          if (statc.st_size != ff_pkt->statp.st_size) {
343             Dmsg3(dbglvl-1, "%s      st_size  differ. Cat: %s File: %s\n",
344                   fname,
345                   edit_uint64((uint64_t)statc.st_size, ed1),
346                   edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
347             stat = true;
348          }
349          break;
350       case 'a':                /* access time */
351          if (statc.st_atime != ff_pkt->statp.st_atime) {
352             Dmsg1(dbglvl-1, "%s      st_atime differs\n", fname);
353             stat = true;
354          }
355          break;
356       case 'm':                 /* modification time */
357          if (statc.st_mtime != ff_pkt->statp.st_mtime) {
358             Dmsg1(dbglvl-1, "%s      st_mtime differs\n", fname);
359             stat = true;
360          }
361          break;
362       case 'c':                /* ctime */
363          if (statc.st_ctime != ff_pkt->statp.st_ctime) {
364             Dmsg1(dbglvl-1, "%s      st_ctime differs\n", fname);
365             stat = true;
366          }
367          break;
368       case 'd':                /* file size decrease */
369          if (statc.st_size > ff_pkt->statp.st_size) {
370             Dmsg3(dbglvl-1, "%s      st_size  decrease. Cat: %s File: %s\n",
371                   fname,
372                   edit_uint64((uint64_t)statc.st_size, ed1),
373                   edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
374             stat = true;
375          }
376          break;
377
378       /* TODO: cleanup and factorise this function with verify.c */
379       case '5':                /* compare MD5 */
380       case '1':                 /* compare SHA1 */
381         /*
382           * The remainder of the function is all about getting the checksum.
383           * First we initialise, then we read files, other streams and Finder Info.
384           */
385          if (!stat && ff_pkt->type != FT_LNKSAVED && 
386              (S_ISREG(ff_pkt->statp.st_mode) && 
387               ff_pkt->flags & (FO_MD5|FO_SHA1|FO_SHA256|FO_SHA512))) 
388          {
389
390             if (!*elt.chksum) {
391                Jmsg(jcr, M_WARNING, 0, _("Can't verify checksum for %s\n"),
392                     ff_pkt->fname);
393                stat = true;
394                break;
395             }
396
397             /*
398              * Create our digest context. If this fails, the digest will be set
399              * to NULL and not used.
400              */
401             if (ff_pkt->flags & FO_MD5) {
402                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_MD5);
403                digest_stream = STREAM_MD5_DIGEST;
404                
405             } else if (ff_pkt->flags & FO_SHA1) {
406                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA1);
407                digest_stream = STREAM_SHA1_DIGEST;
408                
409             } else if (ff_pkt->flags & FO_SHA256) {
410                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA256);
411                digest_stream = STREAM_SHA256_DIGEST;
412                
413             } else if (ff_pkt->flags & FO_SHA512) {
414                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA512);
415                digest_stream = STREAM_SHA512_DIGEST;
416             }
417             
418             /* Did digest initialization fail? */
419             if (digest_stream != STREAM_NONE && digest == NULL) {
420                Jmsg(jcr, M_WARNING, 0, _("%s digest initialization failed\n"),
421                     stream_to_ascii(digest_stream));
422             }
423
424             /* compute MD5 or SHA1 hash */
425             if (digest) {
426                char md[CRYPTO_DIGEST_MAX_SIZE];
427                uint32_t size;
428                
429                size = sizeof(md);
430                
431                if (digest_file(jcr, ff_pkt, digest) != 0) {
432                   jcr->JobErrors++;
433
434                } else if (crypto_digest_finalize(digest, (uint8_t *)md, &size)) {
435                   char *digest_buf;
436                   const char *digest_name;
437                   
438                   digest_buf = (char *)malloc(BASE64_SIZE(size));
439                   digest_name = crypto_digest_name(digest);
440                   
441                   bin_to_base64(digest_buf, BASE64_SIZE(size), md, size, true);
442
443                   if (strcmp(digest_buf, elt.chksum)) {
444                      Dmsg3(dbglvl-1, "%s      chksum  diff. Cat: %s File: %s\n",
445                            fname,
446                            elt.chksum,
447                            digest_buf);
448                      stat = true;
449                   }
450                   
451                   free(digest_buf);
452                }
453                crypto_digest_free(digest);
454             }
455          }
456
457          break;
458       case ':':
459       case 'J':
460       case 'C':
461       default:
462          break;
463       }
464    }
465
466    /* In Incr/Diff accurate mode, we mark all files as seen
467     * When in Full+Base mode, we mark only if the file match exactly
468     */
469    if (jcr->getJobLevel() == L_FULL) {
470       if (!stat) {               
471          /* compute space saved with basefile */
472          jcr->base_size += ff_pkt->statp.st_size;
473          accurate_mark_file_as_seen(jcr, &elt);
474       }
475    } else {
476       accurate_mark_file_as_seen(jcr, &elt);
477    }
478
479 bail_out:
480    unstrip_path(ff_pkt);
481    return stat;
482 }
483
484 /* 
485  * TODO: use big buffer from htable
486  */
487 int accurate_cmd(JCR *jcr)
488 {
489    BSOCK *dir = jcr->dir_bsock;
490    int lstat_pos, chksum_pos;
491    int32_t nb;
492    uint16_t delta_seq;
493
494    if (job_canceled(jcr)) {
495       return true;
496    }
497    if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
498       dir->fsend(_("2991 Bad accurate command\n"));
499       return false;
500    }
501
502    jcr->accurate = true;
503
504    accurate_init(jcr, nb);
505
506    /*
507     * buffer = sizeof(CurFile) + dirmsg
508     * dirmsg = fname + \0 + lstat + \0 + checksum + \0 + delta_seq + \0
509     */
510    /* get current files */
511    while (dir->recv() >= 0) {
512       lstat_pos = strlen(dir->msg) + 1;
513       if (lstat_pos < dir->msglen) {
514          chksum_pos = lstat_pos + strlen(dir->msg + lstat_pos) + 1;
515
516          if (chksum_pos >= dir->msglen) {
517             chksum_pos = lstat_pos - 1;    /* tweak: no checksum, point to the last \0 */
518             delta_seq = 0;
519          } else {
520             delta_seq = str_to_int32(dir->msg + 
521                                      chksum_pos + 
522                                      strlen(dir->msg + chksum_pos) + 1);
523          }
524
525          accurate_add_file(jcr, dir->msglen, 
526                            dir->msg,               /* Path */
527                            dir->msg + lstat_pos,   /* LStat */
528                            dir->msg + chksum_pos,  /* CheckSum */
529                            delta_seq);             /* Delta Sequence */
530       }
531    }
532
533 #ifdef DEBUG
534    extern void *start_heap;
535
536    char b1[50], b2[50], b3[50], b4[50], b5[50];
537    Dmsg5(dbglvl," Heap: heap=%s smbytes=%s max_bytes=%s bufs=%s max_bufs=%s\n",
538          edit_uint64_with_commas((char *)sbrk(0)-(char *)start_heap, b1),
539          edit_uint64_with_commas(sm_bytes, b2),
540          edit_uint64_with_commas(sm_max_bytes, b3),
541          edit_uint64_with_commas(sm_buffers, b4),
542          edit_uint64_with_commas(sm_max_buffers, b5));
543 #endif
544
545    return true;
546 }