2 Bacula® - The Network Backup Solution
4 Copyright (C) 2000-2009 Free Software Foundation Europe e.V.
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 two of the GNU General Public
10 License as published by the Free Software Foundation and included
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.
18 You should have received a copy of the GNU 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
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.
36 static int dbglvl=100;
38 typedef struct PrivateCurFile {
46 bool accurate_mark_file_as_seen(JCR *jcr, char *fname)
48 if (!jcr->accurate || !jcr->file_list) {
51 /* TODO: just use elt->seen = 1 */
52 CurFile *temp = (CurFile *)jcr->file_list->lookup(fname);
54 temp->seen = 1; /* records are in memory */
55 Dmsg1(dbglvl, "marked <%s> as seen\n", fname);
57 Dmsg1(dbglvl, "<%s> not found to be marked as seen\n", fname);
62 static bool accurate_mark_file_as_seen(JCR *jcr, CurFile *elt)
64 /* TODO: just use elt->seen = 1 */
65 CurFile *temp = (CurFile *)jcr->file_list->lookup(elt->fname);
67 temp->seen = 1; /* records are in memory */
72 static bool accurate_lookup(JCR *jcr, char *fname, CurFile *ret)
77 CurFile *temp = (CurFile *)jcr->file_list->lookup(fname);
79 memcpy(ret, temp, sizeof(CurFile));
81 Dmsg1(dbglvl, "lookup <%s> ok\n", fname);
87 static bool accurate_init(JCR *jcr, int nbfile)
90 jcr->file_list = (htable *)malloc(sizeof(htable));
91 jcr->file_list->init(elt, &elt->link, nbfile);
95 static bool accurate_send_base_file_list(JCR *jcr)
101 int stream = STREAM_UNIX_ATTRIBUTES;
103 if (!jcr->accurate || jcr->get_JobLevel() != L_FULL) {
107 if (jcr->file_list == NULL) {
111 ff_pkt = init_find_files();
112 ff_pkt->type = FT_BASE;
114 foreach_htable(elt, jcr->file_list) {
116 Dmsg2(dbglvl, "base file fname=%s seen=%i\n", elt->fname, elt->seen);
117 /* TODO: skip the decode and use directly the lstat field */
118 decode_stat(elt->lstat, &statc, &LinkFIc); /* decode catalog stat */
119 ff_pkt->fname = elt->fname;
120 ff_pkt->statp = statc;
121 encode_and_send_attributes(jcr, ff_pkt, stream);
126 term_find_files(ff_pkt);
131 /* This function is called at the end of backup
132 * We walk over all hash disk element, and we check
135 static bool accurate_send_deleted_list(JCR *jcr)
141 int stream = STREAM_UNIX_ATTRIBUTES;
143 if (!jcr->accurate) {
147 if (jcr->file_list == NULL) {
151 ff_pkt = init_find_files();
152 ff_pkt->type = FT_DELETED;
154 foreach_htable(elt, jcr->file_list) {
155 if (elt->seen || plugin_check_file(jcr, elt->fname)) {
158 Dmsg2(dbglvl, "deleted fname=%s seen=%i\n", elt->fname, elt->seen);
159 /* TODO: skip the decode and use directly the lstat field */
160 decode_stat(elt->lstat, &statc, &LinkFIc); /* decode catalog stat */
161 ff_pkt->fname = elt->fname;
162 ff_pkt->statp.st_mtime = statc.st_mtime;
163 ff_pkt->statp.st_ctime = statc.st_ctime;
164 encode_and_send_attributes(jcr, ff_pkt, stream);
168 term_find_files(ff_pkt);
172 void accurate_free(JCR *jcr)
174 if (jcr->file_list) {
175 jcr->file_list->destroy();
176 free(jcr->file_list);
177 jcr->file_list = NULL;
181 /* Send the deleted or the base file list and cleanup */
182 bool accurate_finish(JCR *jcr)
186 if (jcr->get_JobLevel() == L_FULL) {
187 ret = accurate_send_base_file_list(jcr);
189 ret = accurate_send_deleted_list(jcr);
193 if (jcr->get_JobLevel() == L_FULL) {
194 Dmsg1(0, "Space saved with Base jobs: %lld MB\n",
195 jcr->base_size/(1024*1024));
201 static bool accurate_add_file(JCR *jcr, uint32_t len,
202 char *fname, char *lstat, char *chksum)
209 /* we store CurFile, fname and ctime/mtime in the same chunk */
210 item = (CurFile *)jcr->file_list->hash_malloc(sizeof(CurFile)+len+3);
211 memcpy(item, &elt, sizeof(CurFile));
213 item->fname = (char *)item+sizeof(CurFile);
214 strcpy(item->fname, fname);
216 item->lstat = item->fname+strlen(item->fname)+1;
217 strcpy(item->lstat, lstat);
219 item->chksum = item->lstat+strlen(item->lstat)+1;
220 strcpy(item->chksum, chksum);
222 jcr->file_list->insert(item->fname, item);
224 Dmsg3(dbglvl, "add fname=<%s> lstat=%s chksum=%s\n", fname, lstat, chksum);
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
234 * Returns: true if file has changed (must be backed up)
235 * false file not changed
237 bool accurate_check_file(JCR *jcr, FF_PKT *ff_pkt)
239 int digest_stream = STREAM_NONE;
240 DIGEST *digest = NULL;
248 if (!jcr->accurate) {
254 if (S_ISDIR(ff_pkt->statp.st_mode)) {
255 fname = ff_pkt->link;
257 fname = ff_pkt->fname;
260 if (!accurate_lookup(jcr, fname, &elt)) {
261 Dmsg1(dbglvl, "accurate %s (not found)\n", fname);
266 if (elt.seen) { /* file has been seen ? */
267 Dmsg1(dbglvl, "accurate %s (already seen)\n", fname);
271 decode_stat(elt.lstat, &statc, &LinkFIc); /* decode catalog stat */
274 * Loop over options supplied by user and verify the
275 * fields he requests.
277 for (char *p=ff_pkt->AccurateOpts; !stat && *p; p++) {
278 char ed1[30], ed2[30];
280 case 'i': /* compare INODEs */
281 if (statc.st_ino != ff_pkt->statp.st_ino) {
282 Dmsg3(dbglvl-1, "%s st_ino differ. Cat: %s File: %s\n",
284 edit_uint64((uint64_t)statc.st_ino, ed1),
285 edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
289 case 'p': /* permissions bits */
290 if (statc.st_mode != ff_pkt->statp.st_mode) {
291 Dmsg3(dbglvl-1, "%s st_mode differ. Cat: %x File: %x\n",
293 (uint32_t)statc.st_mode, (uint32_t)ff_pkt->statp.st_mode);
297 case 'n': /* number of links */
298 if (statc.st_nlink != ff_pkt->statp.st_nlink) {
299 Dmsg3(dbglvl-1, "%s st_nlink differ. Cat: %d File: %d\n",
301 (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
305 case 'u': /* user id */
306 if (statc.st_uid != ff_pkt->statp.st_uid) {
307 Dmsg3(dbglvl-1, "%s st_uid differ. Cat: %u File: %u\n",
309 (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
313 case 'g': /* group id */
314 if (statc.st_gid != ff_pkt->statp.st_gid) {
315 Dmsg3(dbglvl-1, "%s st_gid differ. Cat: %u File: %u\n",
317 (uint32_t)statc.st_gid, (uint32_t)ff_pkt->statp.st_gid);
322 if (statc.st_size != ff_pkt->statp.st_size) {
323 Dmsg3(dbglvl-1, "%s st_size differ. Cat: %s File: %s\n",
325 edit_uint64((uint64_t)statc.st_size, ed1),
326 edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
330 case 'a': /* access time */
331 if (statc.st_atime != ff_pkt->statp.st_atime) {
332 Dmsg1(dbglvl-1, "%s st_atime differs\n", fname);
336 case 'm': /* modification time */
337 if (statc.st_mtime != ff_pkt->statp.st_mtime) {
338 Dmsg1(dbglvl-1, "%s st_mtime differs\n", fname);
342 case 'c': /* ctime */
343 if (statc.st_ctime != ff_pkt->statp.st_ctime) {
344 Dmsg1(dbglvl-1, "%s st_ctime differs\n", fname);
348 case 'd': /* file size decrease */
349 if (statc.st_size > ff_pkt->statp.st_size) {
350 Dmsg3(dbglvl-1, "%s st_size decrease. Cat: %s File: %s\n",
352 edit_uint64((uint64_t)statc.st_size, ed1),
353 edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
358 /* TODO: cleanup and factorise this function with verify.c */
359 case '5': /* compare MD5 */
360 case '1': /* compare SHA1 */
362 * The remainder of the function is all about getting the checksum.
363 * First we initialise, then we read files, other streams and Finder Info.
365 if (!stat && *elt.chksum && ff_pkt->type != FT_LNKSAVED &&
366 (S_ISREG(ff_pkt->statp.st_mode) &&
367 ff_pkt->flags & (FO_MD5|FO_SHA1|FO_SHA256|FO_SHA512)))
370 * Create our digest context. If this fails, the digest will be set to NULL
373 if (ff_pkt->flags & FO_MD5) {
374 digest = crypto_digest_new(jcr, CRYPTO_DIGEST_MD5);
375 digest_stream = STREAM_MD5_DIGEST;
377 } else if (ff_pkt->flags & FO_SHA1) {
378 digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA1);
379 digest_stream = STREAM_SHA1_DIGEST;
381 } else if (ff_pkt->flags & FO_SHA256) {
382 digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA256);
383 digest_stream = STREAM_SHA256_DIGEST;
385 } else if (ff_pkt->flags & FO_SHA512) {
386 digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA512);
387 digest_stream = STREAM_SHA512_DIGEST;
390 /* Did digest initialization fail? */
391 if (digest_stream != STREAM_NONE && digest == NULL) {
392 Jmsg(jcr, M_WARNING, 0, _("%s digest initialization failed\n"),
393 stream_to_ascii(digest_stream));
396 /* compute MD5 or SHA1 hash */
398 char md[CRYPTO_DIGEST_MAX_SIZE];
403 if (digest_file(jcr, ff_pkt, digest) != 0) {
406 } else if (crypto_digest_finalize(digest, (uint8_t *)md, &size)) {
408 const char *digest_name;
410 digest_buf = (char *)malloc(BASE64_SIZE(size));
411 digest_name = crypto_digest_name(digest);
413 bin_to_base64(digest_buf, BASE64_SIZE(size), md, size, true);
415 if (strcmp(digest_buf, elt.chksum)) {
416 Dmsg3(dbglvl-1, "%s chksum diff. Cat: %s File: %s\n",
425 crypto_digest_free(digest);
437 /* In Incr/Diff accurate mode, we mark all files as seen
438 * When in Full+Base mode, we mark only if the file match exactly
440 if (jcr->get_JobLevel() == L_FULL) {
442 /* compute space saved with basefile */
443 jcr->base_size += ff_pkt->statp.st_size;
444 accurate_mark_file_as_seen(jcr, &elt);
447 accurate_mark_file_as_seen(jcr, &elt);
451 unstrip_path(ff_pkt);
456 * TODO: use big buffer from htable
458 int accurate_cmd(JCR *jcr)
460 BSOCK *dir = jcr->dir_bsock;
461 int lstat_pos, chksum_pos;
464 if (job_canceled(jcr)) {
467 if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
468 dir->fsend(_("2991 Bad accurate command\n"));
472 jcr->accurate = true;
474 accurate_init(jcr, nb);
477 * buffer = sizeof(CurFile) + dirmsg
478 * dirmsg = fname + \0 + lstat + \0 + checksum + \0
480 /* get current files */
481 while (dir->recv() >= 0) {
482 lstat_pos = strlen(dir->msg) + 1;
483 if (lstat_pos < dir->msglen) {
484 chksum_pos = lstat_pos + strlen(dir->msg + lstat_pos) + 1;
486 Dmsg3(dbglvl, "len=%i lstat_pos=%i chksum_pos=%i\n",
487 (uint32_t) dir->msglen,
488 (uint32_t) lstat_pos, (uint32_t) chksum_pos);
490 if (chksum_pos >= dir->msglen) {
491 chksum_pos = lstat_pos - 1; /* no checksum, point to the last \0 */
494 accurate_add_file(jcr, dir->msglen,
496 dir->msg + lstat_pos, /* LStat */
497 dir->msg + chksum_pos); /* CheckSum */
502 extern void *start_heap;
504 char b1[50], b2[50], b3[50], b4[50], b5[50];
505 Dmsg5(dbglvl," Heap: heap=%s smbytes=%s max_bytes=%s bufs=%s max_bufs=%s\n",
506 edit_uint64_with_commas((char *)sbrk(0)-(char *)start_heap, b1),
507 edit_uint64_with_commas(sm_bytes, b2),
508 edit_uint64_with_commas(sm_max_bytes, b3),
509 edit_uint64_with_commas(sm_buffers, b4),
510 edit_uint64_with_commas(sm_max_buffers, b5));