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