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