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->getJobLevel() != 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->getJobLevel() == L_FULL) {
187 ret = accurate_send_base_file_list(jcr);
189 ret = accurate_send_deleted_list(jcr);
193 if (jcr->getJobLevel() == L_FULL) {
194 Jmsg(jcr, M_INFO, 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;
249 if (!jcr->accurate) {
255 if (S_ISDIR(ff_pkt->statp.st_mode)) {
256 fname = ff_pkt->link;
258 fname = ff_pkt->fname;
261 if (!accurate_lookup(jcr, fname, &elt)) {
262 Dmsg1(dbglvl, "accurate %s (not found)\n", fname);
267 if (elt.seen) { /* file has been seen ? */
268 Dmsg1(dbglvl, "accurate %s (already seen)\n", fname);
272 decode_stat(elt.lstat, &statc, &LinkFIc); /* decode catalog stat */
274 if (jcr->getJobLevel() == L_FULL) {
275 opts = ff_pkt->BaseJobOpts;
277 opts = ff_pkt->AccurateOpts;
281 * Loop over options supplied by user and verify the
282 * fields he requests.
284 for (char *p=opts; !stat && *p; p++) {
285 char ed1[30], ed2[30];
287 case 'i': /* compare INODEs */
288 if (statc.st_ino != ff_pkt->statp.st_ino) {
289 Dmsg3(dbglvl-1, "%s st_ino differ. Cat: %s File: %s\n",
291 edit_uint64((uint64_t)statc.st_ino, ed1),
292 edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
296 case 'p': /* permissions bits */
297 /* TODO: If something change only in perm, user, group
298 * Backup only the attribute stream
300 if (statc.st_mode != ff_pkt->statp.st_mode) {
301 Dmsg3(dbglvl-1, "%s st_mode differ. Cat: %x File: %x\n",
303 (uint32_t)statc.st_mode, (uint32_t)ff_pkt->statp.st_mode);
307 case 'n': /* number of links */
308 if (statc.st_nlink != ff_pkt->statp.st_nlink) {
309 Dmsg3(dbglvl-1, "%s st_nlink differ. Cat: %d File: %d\n",
311 (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
315 case 'u': /* user id */
316 if (statc.st_uid != ff_pkt->statp.st_uid) {
317 Dmsg3(dbglvl-1, "%s st_uid differ. Cat: %u File: %u\n",
319 (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
323 case 'g': /* group id */
324 if (statc.st_gid != ff_pkt->statp.st_gid) {
325 Dmsg3(dbglvl-1, "%s st_gid differ. Cat: %u File: %u\n",
327 (uint32_t)statc.st_gid, (uint32_t)ff_pkt->statp.st_gid);
332 if (statc.st_size != ff_pkt->statp.st_size) {
333 Dmsg3(dbglvl-1, "%s st_size differ. Cat: %s File: %s\n",
335 edit_uint64((uint64_t)statc.st_size, ed1),
336 edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
340 case 'a': /* access time */
341 if (statc.st_atime != ff_pkt->statp.st_atime) {
342 Dmsg1(dbglvl-1, "%s st_atime differs\n", fname);
346 case 'm': /* modification time */
347 if (statc.st_mtime != ff_pkt->statp.st_mtime) {
348 Dmsg1(dbglvl-1, "%s st_mtime differs\n", fname);
352 case 'c': /* ctime */
353 if (statc.st_ctime != ff_pkt->statp.st_ctime) {
354 Dmsg1(dbglvl-1, "%s st_ctime differs\n", fname);
358 case 'd': /* file size decrease */
359 if (statc.st_size > ff_pkt->statp.st_size) {
360 Dmsg3(dbglvl-1, "%s st_size decrease. Cat: %s File: %s\n",
362 edit_uint64((uint64_t)statc.st_size, ed1),
363 edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
368 /* TODO: cleanup and factorise this function with verify.c */
369 case '5': /* compare MD5 */
370 case '1': /* compare SHA1 */
372 * The remainder of the function is all about getting the checksum.
373 * First we initialise, then we read files, other streams and Finder Info.
375 if (!stat && ff_pkt->type != FT_LNKSAVED &&
376 (S_ISREG(ff_pkt->statp.st_mode) &&
377 ff_pkt->flags & (FO_MD5|FO_SHA1|FO_SHA256|FO_SHA512)))
381 Jmsg(jcr, M_WARNING, 0, _("Can't verify checksum for %s\n"),
388 * Create our digest context. If this fails, the digest will be set
389 * to NULL and not used.
391 if (ff_pkt->flags & FO_MD5) {
392 digest = crypto_digest_new(jcr, CRYPTO_DIGEST_MD5);
393 digest_stream = STREAM_MD5_DIGEST;
395 } else if (ff_pkt->flags & FO_SHA1) {
396 digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA1);
397 digest_stream = STREAM_SHA1_DIGEST;
399 } else if (ff_pkt->flags & FO_SHA256) {
400 digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA256);
401 digest_stream = STREAM_SHA256_DIGEST;
403 } else if (ff_pkt->flags & FO_SHA512) {
404 digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA512);
405 digest_stream = STREAM_SHA512_DIGEST;
408 /* Did digest initialization fail? */
409 if (digest_stream != STREAM_NONE && digest == NULL) {
410 Jmsg(jcr, M_WARNING, 0, _("%s digest initialization failed\n"),
411 stream_to_ascii(digest_stream));
414 /* compute MD5 or SHA1 hash */
416 char md[CRYPTO_DIGEST_MAX_SIZE];
421 if (digest_file(jcr, ff_pkt, digest) != 0) {
424 } else if (crypto_digest_finalize(digest, (uint8_t *)md, &size)) {
426 const char *digest_name;
428 digest_buf = (char *)malloc(BASE64_SIZE(size));
429 digest_name = crypto_digest_name(digest);
431 bin_to_base64(digest_buf, BASE64_SIZE(size), md, size, true);
433 if (strcmp(digest_buf, elt.chksum)) {
434 Dmsg3(dbglvl-1, "%s chksum diff. Cat: %s File: %s\n",
443 crypto_digest_free(digest);
456 /* In Incr/Diff accurate mode, we mark all files as seen
457 * When in Full+Base mode, we mark only if the file match exactly
459 if (jcr->getJobLevel() == L_FULL) {
461 /* compute space saved with basefile */
462 jcr->base_size += ff_pkt->statp.st_size;
463 accurate_mark_file_as_seen(jcr, &elt);
466 accurate_mark_file_as_seen(jcr, &elt);
470 unstrip_path(ff_pkt);
475 * TODO: use big buffer from htable
477 int accurate_cmd(JCR *jcr)
479 BSOCK *dir = jcr->dir_bsock;
480 int lstat_pos, chksum_pos;
483 if (job_canceled(jcr)) {
486 if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
487 dir->fsend(_("2991 Bad accurate command\n"));
491 jcr->accurate = true;
493 accurate_init(jcr, nb);
496 * buffer = sizeof(CurFile) + dirmsg
497 * dirmsg = fname + \0 + lstat + \0 + checksum + \0
499 /* get current files */
500 while (dir->recv() >= 0) {
501 lstat_pos = strlen(dir->msg) + 1;
502 if (lstat_pos < dir->msglen) {
503 chksum_pos = lstat_pos + strlen(dir->msg + lstat_pos) + 1;
505 if (chksum_pos >= dir->msglen) {
506 chksum_pos = lstat_pos - 1; /* tweak: no checksum, point to the last \0 */
509 accurate_add_file(jcr, dir->msglen,
511 dir->msg + lstat_pos, /* LStat */
512 dir->msg + chksum_pos); /* CheckSum */
517 extern void *start_heap;
519 char b1[50], b2[50], b3[50], b4[50], b5[50];
520 Dmsg5(dbglvl," Heap: heap=%s smbytes=%s max_bytes=%s bufs=%s max_bufs=%s\n",
521 edit_uint64_with_commas((char *)sbrk(0)-(char *)start_heap, b1),
522 edit_uint64_with_commas(sm_bytes, b2),
523 edit_uint64_with_commas(sm_max_bytes, b3),
524 edit_uint64_with_commas(sm_buffers, b4),
525 edit_uint64_with_commas(sm_max_buffers, b5));