2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2015 Kern Sibbald
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.
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.
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
17 Bacula(R) is a registered trademark of Kern Sibbald.
24 static int dbglvl=100;
26 typedef struct PrivateCurFile {
35 bool accurate_mark_file_as_seen(JCR *jcr, char *fname)
37 if (!jcr->accurate || !jcr->file_list) {
40 /* TODO: just use elt->seen = 1 */
41 CurFile *temp = (CurFile *)jcr->file_list->lookup(fname);
43 temp->seen = 1; /* records are in memory */
44 Dmsg1(dbglvl, "marked <%s> as seen\n", fname);
46 Dmsg1(dbglvl, "<%s> not found to be marked as seen\n", fname);
51 static bool accurate_mark_file_as_seen(JCR *jcr, CurFile *elt)
53 /* TODO: just use elt->seen = 1 */
54 CurFile *temp = (CurFile *)jcr->file_list->lookup(elt->fname);
56 temp->seen = 1; /* records are in memory */
61 static bool accurate_lookup(JCR *jcr, char *fname, CurFile *ret)
66 CurFile *temp = (CurFile *)jcr->file_list->lookup(fname);
68 memcpy(ret, temp, sizeof(CurFile));
70 Dmsg1(dbglvl, "lookup <%s> ok\n", fname);
76 static bool accurate_init(JCR *jcr, int nbfile)
79 jcr->file_list = (htable *)malloc(sizeof(htable));
80 jcr->file_list->init(elt, &elt->link, nbfile);
84 static bool accurate_send_base_file_list(JCR *jcr)
91 memset(&bctx, 0, sizeof(bctx));
93 bctx.data_stream = STREAM_UNIX_ATTRIBUTES;
95 if (!jcr->accurate || jcr->getJobLevel() != L_FULL) {
99 if (jcr->file_list == NULL) {
103 bctx.ff_pkt = init_find_files();
104 bctx.ff_pkt->type = FT_BASE;
106 foreach_htable(elt, jcr->file_list) {
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);
118 term_find_files(bctx.ff_pkt);
123 /* This function is called at the end of backup
124 * We walk over all hash disk element, and we check
127 static bool accurate_send_deleted_list(JCR *jcr)
134 memset(&bctx, 0, sizeof(bctx));
136 bctx.data_stream = STREAM_UNIX_ATTRIBUTES;
138 if (!jcr->accurate) {
142 if (jcr->file_list == NULL) {
146 bctx.ff_pkt = init_find_files();
147 bctx.ff_pkt->type = FT_DELETED;
149 foreach_htable(elt, jcr->file_list) {
150 if (elt->seen || plugin_check_file(jcr, elt->fname)) {
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);
163 term_find_files(bctx.ff_pkt);
167 void accurate_free(JCR *jcr)
169 if (jcr->file_list) {
170 jcr->file_list->destroy();
171 free(jcr->file_list);
172 jcr->file_list = NULL;
176 /* Send the deleted or the base file list and cleanup */
177 bool accurate_finish(JCR *jcr)
181 if (jcr->is_canceled() || jcr->is_incomplete()) {
186 if (jcr->is_JobLevel(L_FULL)) {
187 if (!jcr->rerunning) {
188 ret = accurate_send_base_file_list(jcr);
191 ret = accurate_send_deleted_list(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));
202 static bool accurate_add_file(JCR *jcr, uint32_t len,
203 char *fname, char *lstat, char *chksum,
209 /* we store CurFile, fname and ctime/mtime in the same chunk
210 * we need one extra byte to handle an empty chksum
212 item = (CurFile *)jcr->file_list->hash_malloc(sizeof(CurFile)+len+3);
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);
219 item->lstat = item->fname+strlen(item->fname)+1;
220 strcpy(item->lstat, lstat);
222 item->chksum = item->lstat+strlen(item->lstat)+1;
223 strcpy(item->chksum, chksum);
225 item->delta_seq = delta;
227 jcr->file_list->insert(item->fname, item);
229 Dmsg4(dbglvl, "add fname=<%s> lstat=%s delta_seq=%i chksum=%s\n",
230 fname, lstat, delta, chksum);
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
240 * Returns: true if file has changed (must be backed up)
241 * false file not changed
243 bool accurate_check_file(JCR *jcr, FF_PKT *ff_pkt)
245 int digest_stream = STREAM_NONE;
246 DIGEST *digest = NULL;
255 ff_pkt->delta_seq = 0;
256 ff_pkt->accurate_found = false;
258 if (!jcr->accurate && !jcr->rerunning) {
262 if (!jcr->file_list) {
263 return true; /* Not initialized properly */
268 if (S_ISDIR(ff_pkt->statp.st_mode)) {
269 fname = ff_pkt->link;
271 fname = ff_pkt->fname;
274 if (!accurate_lookup(jcr, fname, &elt)) {
275 Dmsg1(dbglvl, "accurate %s (not found)\n", fname);
277 unstrip_path(ff_pkt);
281 unstrip_path(ff_pkt); /* Get full path back */
282 ff_pkt->accurate_found = true;
283 ff_pkt->delta_seq = elt.delta_seq;
285 decode_stat(elt.lstat, &statc, sizeof(statc), &LinkFIc); /* decode catalog stat */
287 if (!jcr->rerunning && (jcr->getJobLevel() == L_FULL)) {
288 opts = ff_pkt->BaseJobOpts;
290 opts = ff_pkt->AccurateOpts;
294 * Loop over options supplied by user and verify the
295 * fields he requests.
297 for (char *p=opts; !stat && *p; p++) {
298 char ed1[30], ed2[30];
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",
304 edit_uint64((uint64_t)statc.st_ino, ed1),
305 edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
309 case 'p': /* permissions bits */
310 /* TODO: If something change only in perm, user, group
311 * Backup only the attribute stream
313 if (statc.st_mode != ff_pkt->statp.st_mode) {
314 Dmsg3(dbglvl-1, "%s st_mode differ. Cat: %x File: %x\n",
316 (uint32_t)statc.st_mode, (uint32_t)ff_pkt->statp.st_mode);
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",
324 (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
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",
332 (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
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",
340 (uint32_t)statc.st_gid, (uint32_t)ff_pkt->statp.st_gid);
345 if (statc.st_size != ff_pkt->statp.st_size) {
346 Dmsg3(dbglvl-1, "%s st_size differ. Cat: %s File: %s\n",
348 edit_uint64((uint64_t)statc.st_size, ed1),
349 edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
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);
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);
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)))
371 Dmsg1(dbglvl-1, "%s mtime/ctime more recent than save_time\n", fname);
375 case 'c': /* ctime */
376 if (statc.st_ctime != ff_pkt->statp.st_ctime) {
377 Dmsg1(dbglvl-1, "%s st_ctime differs\n", fname);
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",
385 edit_uint64((uint64_t)statc.st_size, ed1),
386 edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
390 case 'A': /* Always backup a file */
393 /* TODO: cleanup and factorise this function with verify.c */
394 case '5': /* compare MD5 */
395 case '1': /* compare SHA1 */
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.
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)))
405 if (!*elt.chksum && !jcr->rerunning) {
406 Jmsg(jcr, M_WARNING, 0, _("Cannot verify checksum for %s\n"),
413 * Create our digest context. If this fails, the digest will be set
414 * to NULL and not used.
416 if (ff_pkt->flags & FO_MD5) {
417 digest = crypto_digest_new(jcr, CRYPTO_DIGEST_MD5);
418 digest_stream = STREAM_MD5_DIGEST;
420 } else if (ff_pkt->flags & FO_SHA1) {
421 digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA1);
422 digest_stream = STREAM_SHA1_DIGEST;
424 } else if (ff_pkt->flags & FO_SHA256) {
425 digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA256);
426 digest_stream = STREAM_SHA256_DIGEST;
428 } else if (ff_pkt->flags & FO_SHA512) {
429 digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA512);
430 digest_stream = STREAM_SHA512_DIGEST;
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));
439 /* compute MD5 or SHA1 hash */
441 char md[CRYPTO_DIGEST_MAX_SIZE];
446 if (digest_file(jcr, ff_pkt, digest) != 0) {
449 } else if (crypto_digest_finalize(digest, (uint8_t *)md, &size)) {
451 const char *digest_name;
453 digest_buf = (char *)malloc(BASE64_SIZE(size));
454 digest_name = crypto_digest_name(digest);
456 bin_to_base64(digest_buf, BASE64_SIZE(size), md, size, true);
458 if (strcmp(digest_buf, elt.chksum)) {
459 Dmsg4(dbglvl,"%s %s chksum diff. Cat: %s File: %s\n",
469 crypto_digest_free(digest);
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
485 if (jcr->getJobLevel() == L_FULL) {
487 /* compute space saved with basefile */
488 jcr->base_size += ff_pkt->statp.st_size;
489 accurate_mark_file_as_seen(jcr, &elt);
492 accurate_mark_file_as_seen(jcr, &elt);
500 * TODO: use big buffer from htable
502 int accurate_cmd(JCR *jcr)
504 BSOCK *dir = jcr->dir_bsock;
505 int lstat_pos, chksum_pos;
509 if (job_canceled(jcr)) {
512 if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
513 dir->fsend(_("2991 Bad accurate command\n"));
517 jcr->accurate = true;
519 accurate_init(jcr, nb);
522 * buffer = sizeof(CurFile) + dirmsg
523 * dirmsg = fname + \0 + lstat + \0 + checksum + \0 + delta_seq + \0
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;
531 if (chksum_pos >= dir->msglen) {
532 chksum_pos = lstat_pos - 1; /* tweak: no checksum, point to the last \0 */
535 delta_seq = str_to_int32(dir->msg +
537 strlen(dir->msg + chksum_pos) + 1);
540 accurate_add_file(jcr, dir->msglen,
542 dir->msg + lstat_pos, /* LStat */
543 dir->msg + chksum_pos, /* CheckSum */
544 delta_seq); /* Delta Sequence */
549 extern void *start_heap;
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));