1 Index: src/dird/fd_cmds.c
2 ===================================================================
3 --- src/dird/fd_cmds.c (révision 6471)
4 +++ src/dird/fd_cmds.c (copie de travail)
6 static char filesetcmd[] = "fileset%s\n"; /* set full fileset */
7 static char jobcmd[] = "JobId=%s Job=%s SDid=%u SDtime=%u Authorization=%s\n";
8 /* Note, mtime_only is not used here -- implemented as file option */
9 -static char levelcmd[] = "level = %s%s mtime_only=%d\n";
10 +static char levelcmd[] = "level = %s%s%s mtime_only=%d\n";
11 static char runscript[] = "Run OnSuccess=%u OnFailure=%u AbortOnError=%u When=%u Command=%s\n";
12 static char runbeforenow[]= "RunBeforeNow\n";
17 stime = str_to_utime(jcr->stime);
18 - fd->fsend(levelcmd, NT_("since_utime "), edit_uint64(stime, ed1), 0);
19 + fd->fsend(levelcmd, "", NT_("since_utime "), edit_uint64(stime, ed1), 0);
20 while (bget_dirmsg(fd) >= 0) { /* allow him to poll us to sync clocks */
21 Jmsg(jcr, M_INFO, 0, "%s\n", fd->msg);
27 * Send level command to FD.
28 * Used for backup jobs and estimate command.
30 bool send_level_command(JCR *jcr)
32 BSOCK *fd = jcr->file_bsock;
33 + const char *accurate=jcr->job->accurate?"accurate_":"";
34 + const char *not_accurate="";
36 * Send Level command to File daemon
38 switch (jcr->JobLevel) {
40 - fd->fsend(levelcmd, "base", " ", 0);
41 + fd->fsend(levelcmd, not_accurate, "base", " ", 0);
43 /* L_NONE is the console, sending something off to the FD */
46 - fd->fsend(levelcmd, "full", " ", 0);
47 + fd->fsend(levelcmd, not_accurate, "full", " ", 0);
50 - fd->fsend(levelcmd, "differential", " ", 0);
51 + fd->fsend(levelcmd, accurate, "differential", " ", 0);
55 - fd->fsend(levelcmd, "incremental", " ", 0);
56 + fd->fsend(levelcmd, accurate, "incremental", " ", 0);
60 Index: src/dird/backup.c
61 ===================================================================
62 --- src/dird/backup.c (révision 6471)
63 +++ src/dird/backup.c (copie de travail)
68 + * Foreach files in currrent list, send "/path/fname\0LStat" to FD
70 +static int accurate_list_handler(void *ctx, int num_fields, char **row)
72 + JCR *jcr = (JCR *)ctx;
74 + if (job_canceled(jcr)) {
78 + if (row[2] > 0) { /* discard when file_index == 0 */
79 + jcr->file_bsock->fsend("%s%s%c%s", row[0], row[1], 0, row[4]);
85 + * Send current file list to FD
86 + * DIR -> FD : accurate files=xxxx
87 + * DIR -> FD : /path/to/file\0Lstat
88 + * DIR -> FD : /path/to/dir/\0Lstat
92 +bool send_accurate_current_files(JCR *jcr)
96 + if (jcr->accurate==false || job_canceled(jcr) || jcr->JobLevel==L_FULL) {
99 + POOLMEM *jobids = get_pool_memory(PM_FNAME);
100 + db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, jobids);
102 + if (*jobids == 0) {
103 + free_pool_memory(jobids);
104 + Jmsg(jcr, M_FATAL, 0, _("Cannot find previous jobids.\n"));
107 + Jmsg(jcr, M_INFO, 0, _("Sending Accurate information.\n"));
109 + /* to be able to allocate the right size for htable */
110 + POOLMEM *nb = get_pool_memory(PM_FNAME);
111 + Mmsg(buf, "SELECT sum(JobFiles) FROM Job WHERE JobId IN (%s)",jobids);
112 + db_sql_query(jcr->db, buf.c_str(), db_get_int_handler, nb);
113 + jcr->file_bsock->fsend("accurate files=%s\n", nb);
115 + db_get_file_list(jcr, jcr->db, jobids, accurate_list_handler, (void *)jcr);
117 + free_pool_memory(jobids);
118 + free_pool_memory(nb);
120 + jcr->file_bsock->signal(BNET_EOD);
121 + /* TODO: use response() ? */
127 * Do a backup of the specified FileSet
129 * Returns: false on failure
131 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
135 + * If backup is in accurate mode, we send the list of
138 + if (!send_accurate_current_files(jcr)) {
142 /* Send backup command */
143 fd->fsend(backupcmd);
144 if (!response(jcr, fd, OKbackup, "backup", DISPLAY_ERROR)) {
146 " Software Compression: %s\n"
150 " Volume name(s): %s\n"
151 " Volume Session Id: %d\n"
152 " Volume Session Time: %d\n"
154 edit_uint64_with_suffix(jcr->SDJobBytes, ec6),
157 - jcr->VSS?"yes":"no",
158 - jcr->Encrypt?"yes":"no",
159 + jcr->VSS?_("yes"):_("no"),
160 + jcr->Encrypt?_("yes"):_("no"),
161 + jcr->accurate?_("yes"):_("no"),
165 Index: src/dird/catreq.c
166 ===================================================================
167 --- src/dird/catreq.c (révision 6471)
168 +++ src/dird/catreq.c (copie de travail)
170 * Update File Attributes in the catalog with data
171 * sent by the Storage daemon. Note, we receive the whole
172 * attribute record, but we select out only the stat packet,
173 - * VolSessionId, VolSessionTime, FileIndex, and file name
174 - * to store in the catalog.
175 + * VolSessionId, VolSessionTime, FileIndex, file type, and
176 + * file name to store in the catalog.
178 void catalog_update(JCR *jcr, BSOCK *bs)
189 unser_uint32(data_len);
190 p += unser_length(p);
192 - Dmsg1(400, "UpdCat msg=%s\n", bs->msg);
193 - Dmsg5(400, "UpdCat VolSessId=%d VolSessT=%d FI=%d Strm=%d data_len=%d\n",
194 + Dmsg1(1, "UpdCat msg=%s\n", bs->msg);
195 + Dmsg5(1, "UpdCat VolSessId=%d VolSessT=%d FI=%d Strm=%d data_len=%d\n",
196 VolSessionId, VolSessionTime, FileIndex, Stream, data_len);
198 if (Stream == STREAM_UNIX_ATTRIBUTES || Stream == STREAM_UNIX_ATTRIBUTES_EX) {
200 p = jcr->attr - bs->msg + p; /* point p into jcr->attr */
201 skip_nonspaces(&p); /* skip FileIndex */
203 + filetype = str_to_int32(p); /* TODO: choose between unserialize and str_to_int32 */
204 skip_nonspaces(&p); /* skip FileType */
208 Dmsg1(400, "dird<stored: attr=%s\n", attr);
211 - ar->FileIndex = FileIndex;
212 + if (filetype == FT_DELETED) {
213 + ar->FileIndex = 0; /* special value */
215 + ar->FileIndex = FileIndex;
220 Index: src/dird/ua_restore.c
221 ===================================================================
222 --- src/dird/ua_restore.c (révision 6471)
223 +++ src/dird/ua_restore.c (copie de travail)
224 @@ -1005,7 +1005,6 @@
225 * For display purposes, the same JobId, with different volumes may
226 * appear more than once, however, we only insert it once.
230 tree.FileEstimate = 0;
231 if (get_next_jobid_from_list(&p, &JobId) > 0) {
232 @@ -1020,23 +1019,12 @@
233 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
236 - for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
239 - if (JobId == last_JobId) {
240 - continue; /* eliminate duplicate JobIds */
242 - last_JobId = JobId;
243 - ua->info_msg(_("\nBuilding directory tree for JobId %s ... "),
244 - edit_int64(JobId, ed1));
247 - * Find files for this JobId and insert them in the tree
249 - Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
250 - if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
251 - ua->error_msg("%s", db_strerror(ua->db));
253 + ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ... "),
256 + if (!db_get_file_list(ua->jcr, ua->db, rx->JobIds, insert_tree_handler, (void *)&tree)) {
257 + ua->error_msg("%s", db_strerror(ua->db));
259 if (tree.FileCount == 0) {
260 ua->send_msg(_("\nThere were no files inserted into the tree, so file selection\n"
261 @@ -1055,26 +1043,13 @@
267 - ua->info_msg(_("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
268 - edit_uint64_with_commas(tree.FileCount, ec1));
271 - ua->info_msg(_("\n1 Job, %s files inserted into the tree.\n"),
272 - edit_uint64_with_commas(tree.FileCount, ec1));
275 + ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
276 + edit_uint64_with_commas(tree.FileCount, ec1));
278 + ua->info_msg(_("\n%s files inserted into the tree.\n"),
279 + edit_uint64_with_commas(tree.FileCount, ec1));
283 - ua->info_msg(_("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
284 - items, edit_uint64_with_commas(tree.FileCount, ec1));
287 - ua->info_msg(_("\n%d Jobs, %s files inserted into the tree.\n"),
288 - items, edit_uint64_with_commas(tree.FileCount, ec1));
292 if (find_arg(ua, NT_("done")) < 0) {
293 /* Let the user interact in selecting which files to restore */
294 Index: src/dird/inc_conf.c
295 ===================================================================
296 --- src/dird/inc_conf.c (révision 6471)
297 +++ src/dird/inc_conf.c (copie de travail)
299 static RES_ITEM options_items[] = {
300 {"compression", store_opts, {0}, 0, 0, 0},
301 {"signature", store_opts, {0}, 0, 0, 0},
302 + {"accurate", store_opts, {0}, 0, 0, 0},
303 {"verify", store_opts, {0}, 0, 0, 0},
304 {"onefs", store_opts, {0}, 0, 0, 0},
305 {"recurse", store_opts, {0}, 0, 0, 0},
315 {"signature", INC_KW_DIGEST},
316 {"encryption", INC_KW_ENCRYPTION},
317 {"verify", INC_KW_VERIFY},
318 + {"accurate", INC_KW_ACCURATE},
319 {"onefs", INC_KW_ONEFS},
320 {"recurse", INC_KW_RECURSE},
321 {"sparse", INC_KW_SPARSE},
323 bstrncat(opts, lc->str, optlen);
324 bstrncat(opts, ":", optlen); /* terminate it */
325 Dmsg3(900, "Catopts=%s option=%s optlen=%d\n", opts, option,optlen);
326 + } else if (keyword == INC_KW_ACCURATE) { /* special case */
327 + /* ***FIXME**** ensure these are in permitted set */
328 + bstrncat(opts, "C", optlen); /* indicate Accurate */
329 + bstrncat(opts, lc->str, optlen);
330 + bstrncat(opts, ":", optlen); /* terminate it */
331 + Dmsg3(900, "Catopts=%s option=%s optlen=%d\n", opts, option,optlen);
332 } else if (keyword == INC_KW_STRIPPATH) { /* another special case */
333 if (!is_an_integer(lc->str)) {
334 scan_err1(lc, _("Expected a strip path positive integer, got:%s:"), lc->str);
335 Index: src/filed/backup.c
336 ===================================================================
337 --- src/filed/backup.c (révision 6471)
338 +++ src/filed/backup.c (copie de travail)
343 +#include "lib/htable.h"
345 /* Forward referenced functions */
346 int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level);
348 static void crypto_session_end(JCR *jcr);
349 static bool crypto_session_send(JCR *jcr, BSOCK *sd);
351 +typedef struct CurFile {
358 +#define accurate_mark_file_as_seen(elt) ((elt)->seen = 1)
359 +#define accurate_file_has_been_seen(elt) ((elt)->seen)
362 + * This function is called for each file seen in fileset.
363 + * We check in file_list hash if fname have been backuped
364 + * the last time. After we can compare Lstat field.
367 +/* TODO: tweak verify code to use the same function ?? */
368 +bool accurate_check_file(JCR *jcr, FF_PKT *ff_pkt)
372 + struct stat statc; /* catalog stat */
379 + if (*ff_pkt->VerifyOpts) { /* use mtime + ctime checks by default */
380 + Opts_Digest = ff_pkt->VerifyOpts;
382 + Opts_Digest = "cm";
385 + if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
389 + strip_path(ff_pkt);
391 + if (S_ISDIR(ff_pkt->statp.st_mode)) {
392 + fname = ff_pkt->link;
394 + fname = ff_pkt->fname;
397 + elt = (CurFile *) jcr->file_list->lookup(fname);
400 + Dmsg1(500, "accurate %s = yes (not found)\n", fname);
405 + if (accurate_file_has_been_seen(elt)) {
406 + Dmsg1(500, "accurate %s = no (already seen)\n", fname);
411 + decode_stat(elt->lstat, &statc, &LinkFIc); /* decode catalog stat */
412 +// *do_Digest = CRYPTO_DIGEST_NONE;
414 + for (p=Opts_Digest; *p; p++) {
415 + char ed1[30], ed2[30];
417 + case 'i': /* compare INODEs */
418 + if (statc.st_ino != ff_pkt->statp.st_ino) {
419 + Jmsg(jcr, M_SAVED, 0, _("%s st_ino differ. Cat: %s File: %s\n"), fname,
420 + edit_uint64((uint64_t)statc.st_ino, ed1),
421 + edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
425 + case 'p': /* permissions bits */
426 + if (statc.st_mode != ff_pkt->statp.st_mode) {
427 + Jmsg(jcr, M_SAVED, 0, _("%s st_mode differ. Cat: %x File: %x\n"), fname,
428 + (uint32_t)statc.st_mode, (uint32_t)ff_pkt->statp.st_mode);
432 +// case 'n': /* number of links */
433 +// if (statc.st_nlink != ff_pkt->statp.st_nlink) {
434 +// Jmsg(jcr, M_SAVED, 0, _("%s st_nlink differ. Cat: %d File: %d\n"), fname,
435 +// (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
439 + case 'u': /* user id */
440 + if (statc.st_uid != ff_pkt->statp.st_uid) {
441 + Jmsg(jcr, M_SAVED, 0, _("%s st_uid differ. Cat: %u File: %u\n"), fname,
442 + (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
446 + case 'g': /* group id */
447 + if (statc.st_gid != ff_pkt->statp.st_gid) {
448 + Jmsg(jcr, M_SAVED, 0, _("%s st_gid differ. Cat: %u File: %u\n"), fname,
449 + (uint32_t)statc.st_gid, (uint32_t)ff_pkt->statp.st_gid);
453 + case 's': /* size */
454 + if (statc.st_size != ff_pkt->statp.st_size) {
455 + Jmsg(jcr, M_SAVED, 0, _("%s st_size differ. Cat: %s File: %s\n"), fname,
456 + edit_uint64((uint64_t)statc.st_size, ed1),
457 + edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
461 +// case 'a': /* access time */
462 +// if (statc.st_atime != ff_pkt->statp.st_atime) {
463 +// Jmsg(jcr, M_SAVED, 0, _("%s st_atime differs\n"), fname);
468 + if (statc.st_mtime != ff_pkt->statp.st_mtime) {
469 + Jmsg(jcr, M_SAVED, 0, _("%s st_mtime differs\n"), fname);
473 + case 'c': /* ctime */
474 + if (statc.st_ctime != ff_pkt->statp.st_ctime) {
475 + Jmsg(jcr, M_SAVED, 0, _("%s st_ctime differs\n"), fname);
479 + case 'd': /* file size decrease */
480 + if (statc.st_size > ff_pkt->statp.st_size) {
481 + Jmsg(jcr, M_SAVED, 0, _("%s st_size decrease. Cat: %s File: %s\n"), fname,
482 + edit_uint64((uint64_t)statc.st_size, ed1),
483 + edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
487 + case '5': /* compare MD5 */
488 + Dmsg1(500, "set Do_MD5 for %s\n", ff_pkt->fname);
489 +// *do_Digest = CRYPTO_DIGEST_MD5;
491 + case '1': /* compare SHA1 */
492 +// *do_Digest = CRYPTO_DIGEST_SHA1;
500 + accurate_mark_file_as_seen(elt);
501 + Dmsg2(500, "accurate %s = %i\n", fname, stat);
504 + unstrip_path(ff_pkt);
509 + * This function doesn't work very well with smartalloc
510 + * TODO: use bigbuffer from htable
512 +int accurate_cmd(JCR *jcr)
514 + BSOCK *dir = jcr->dir_bsock;
519 + if (jcr->accurate==false || job_canceled(jcr) || jcr->JobLevel==L_FULL) {
523 + if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
524 + dir->fsend(_("2991 Bad accurate command\n"));
528 + jcr->file_list = (htable *)malloc(sizeof(htable));
529 + jcr->file_list->init(elt, &elt->link, nb);
532 + * buffer = sizeof(CurFile) + dirmsg
533 + * dirmsg = fname + lstat
535 + /* get current files */
536 + while (dir->recv() >= 0) {
537 + len = strlen(dir->msg);
538 + if ((len+1) < dir->msglen) {
539 +// elt = (CurFile *)malloc(sizeof(CurFile));
540 +// elt->fname = (char *) malloc(dir->msglen+1);
542 + /* we store CurFile, fname and lstat in the same chunk */
543 + elt = (CurFile *)malloc(sizeof(CurFile)+dir->msglen+1);
544 + elt->fname = (char *) elt+sizeof(CurFile);
545 + memcpy(elt->fname, dir->msg, dir->msglen);
546 + elt->fname[dir->msglen]='\0';
547 + elt->lstat = elt->fname + len + 1;
549 + jcr->file_list->insert(elt->fname, elt);
550 + Dmsg2(500, "add fname=%s lstat=%s\n", elt->fname, elt->lstat);
554 +// jcr->file_list->stats();
555 + /* TODO: send a EOM ?
556 + dir->fsend("2000 OK accurate\n");
561 +bool accurate_send_deleted_list(JCR *jcr)
566 + int stream = STREAM_UNIX_ATTRIBUTES;
568 + if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
572 + if (jcr->file_list == NULL) {
576 + ff_pkt = init_find_files();
577 + ff_pkt->type = FT_DELETED;
579 + foreach_htable (elt, jcr->file_list) {
580 + if (!accurate_file_has_been_seen(elt)) { /* already seen */
581 + Dmsg3(500, "deleted fname=%s lstat=%s seen=%i\n", elt->fname, elt->lstat, elt->seen);
582 + ff_pkt->fname = elt->fname;
583 + decode_stat(elt->lstat, &ff_pkt->statp, &ff_pkt->LinkFI); /* decode catalog stat */
584 + encode_and_send_attributes(jcr, ff_pkt, stream);
586 +// Free(elt->fname);
588 + term_find_files(ff_pkt);
590 + /* TODO: clean htable when this function is not reached ? */
591 + if (jcr->file_list) {
592 + jcr->file_list->destroy();
593 + free(jcr->file_list);
594 + jcr->file_list = NULL;
600 * Find all the requested files and send them
601 * to the Storage daemon.
605 jcr->compress_buf_size = jcr->buf_size + ((jcr->buf_size+999) / 1000) + 30;
606 jcr->compress_buf = get_memory(jcr->compress_buf_size);
610 z_stream *pZlibStream = (z_stream*)malloc(sizeof(z_stream));
612 @@ -121,10 +370,13 @@
616 - Dmsg1(300, "set_find_options ff=%p\n", jcr->ff);
617 set_find_options((FF_PKT *)jcr->ff, jcr->incremental, jcr->mtime);
618 - Dmsg0(300, "start find files\n");
620 + /* in accurate mode, we overwrite the find_one check function */
621 + if (jcr->accurate) {
622 + set_find_changed_function((FF_PKT *)jcr->ff, accurate_check_file);
625 start_heartbeat_monitor(jcr);
627 jcr->acl_text = get_pool_memory(PM_MESSAGE);
629 set_jcr_job_status(jcr, JS_ErrorTerminated);
632 + accurate_send_deleted_list(jcr); /* send deleted list to SD */
634 free_pool_memory(jcr->acl_text);
636 stop_heartbeat_monitor(jcr);
637 @@ -1066,8 +1320,8 @@
638 Jmsg0(jcr, M_FATAL, 0, _("Invalid file flags, no supported data stream type.\n"));
642 encode_stat(attribs, ff_pkt, data_stream);
644 /* Now possibly extend the attributes */
645 attr_stream = encode_attribsEx(jcr, attribsEx, ff_pkt);
647 @@ -1102,7 +1356,9 @@
648 * For a directory, link is the same as fname, but with trailing
649 * slash. For a linked file, link is the link.
651 - strip_path(ff_pkt);
652 + if (ff_pkt->type != FT_DELETED) { /* already stripped */
653 + strip_path(ff_pkt);
655 if (ff_pkt->type == FT_LNK || ff_pkt->type == FT_LNKSAVED) {
656 Dmsg2(300, "Link %s to %s\n", ff_pkt->fname, ff_pkt->link);
657 stat = sd->fsend("%ld %d %s%c%s%c%s%c%s%c", jcr->JobFiles,
658 @@ -1116,7 +1372,9 @@
659 stat = sd->fsend("%ld %d %s%c%s%c%c%s%c", jcr->JobFiles,
660 ff_pkt->type, ff_pkt->fname, 0, attribs, 0, 0, attribsEx, 0);
662 - unstrip_path(ff_pkt);
663 + if (ff_pkt->type != FT_DELETED) {
664 + unstrip_path(ff_pkt);
667 Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg);
669 Index: src/filed/job.c
670 ===================================================================
671 --- src/filed/job.c (révision 6471)
672 +++ src/filed/job.c (copie de travail)
674 /* Imported functions */
675 extern int status_cmd(JCR *jcr);
676 extern int qstatus_cmd(JCR *jcr);
677 +extern int accurate_cmd(JCR *jcr);
679 /* Forward referenced functions */
680 static int backup_cmd(JCR *jcr);
682 {"RunBeforeJob", runbefore_cmd, 0},
683 {"RunAfterJob", runafter_cmd, 0},
684 {"Run", runscript_cmd, 0},
685 + {"accurate", accurate_cmd, 0},
686 {NULL, NULL} /* list terminator */
689 @@ -1057,6 +1059,16 @@
691 fo->VerifyOpts[j] = 0;
693 + case 'C': /* accurate options */
694 + /* Copy Accurate Options */
695 + for (j=0; *p && *p != ':'; p++) {
696 + fo->AccurateOpts[j] = *p;
697 + if (j < (int)sizeof(fo->AccurateOpts) - 1) {
701 + fo->AccurateOpts[j] = 0;
703 case 'P': /* strip path */
706 @@ -1195,6 +1207,9 @@
708 level = get_memory(dir->msglen+1);
709 Dmsg1(110, "level_cmd: %s", dir->msg);
710 + if (strstr(dir->msg, "accurate")) {
711 + jcr->accurate = true;
713 if (sscanf(dir->msg, "level = %s ", level) != 1) {
716 @@ -1204,14 +1219,14 @@
717 /* Full backup requested? */
718 } else if (strcmp(level, "full") == 0) {
719 jcr->JobLevel = L_FULL;
720 - } else if (strcmp(level, "differential") == 0) {
721 + } else if (strstr(level, "differential")) {
722 jcr->JobLevel = L_DIFFERENTIAL;
725 - } else if (strcmp(level, "incremental") == 0) {
726 + } else if (strstr(level, "incremental")) {
727 jcr->JobLevel = L_INCREMENTAL;
732 * We get his UTC since time, then sync the clocks and correct it
733 * to agree with our clock.
734 Index: src/filed/restore.c
735 ===================================================================
736 --- src/filed/restore.c (révision 6471)
737 +++ src/filed/restore.c (copie de travail)
742 + /* TODO: manage deleted files */
743 + if (rctx.type == FT_DELETED) { /* deleted file */
748 * Unpack attributes and do sanity check them
750 Index: src/cats/protos.h
751 ===================================================================
752 --- src/cats/protos.h (révision 6471)
753 +++ src/cats/protos.h (copie de travail)
755 int db_get_client_record(JCR *jcr, B_DB *mdb, CLIENT_DBR *cdbr);
756 int db_get_counter_record(JCR *jcr, B_DB *mdb, COUNTER_DBR *cr);
757 bool db_get_query_dbids(JCR *jcr, B_DB *mdb, POOL_MEM &query, dbid_list &ids);
758 +bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids, DB_RESULT_HANDLER *result_handler, void *ctx);
759 +bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb, JOB_DBR *jr, POOLMEM *jobids);
760 +int db_get_int_handler(void *ctx, int num_fields, char **row);
764 Index: src/cats/sql_get.c
765 ===================================================================
766 --- src/cats/sql_get.c (révision 6471)
767 +++ src/cats/sql_get.c (copie de travail)
776 * Returns: false: on failure
777 @@ -1018,5 +1016,141 @@
782 + * Find the last "accurate" backup state (that can take deleted files in account)
783 + * 1) Get all files with jobid in list (F subquery)
784 + * 2) Take only the last version of each file (Temp subquery) => accurate list is ok
785 + * 3) Join the result to file table to get fileindex, jobid and lstat information
787 + * TODO: On postgresql, this is done with
788 +SELECT DISTINCT ON (PathId, FilenameId) FileIndex, Path, Name, LStat
789 + FROM File JOIN Filename USING (FilenameId) JOIN Path USING (PathId) WHERE JobId IN (40341)
790 + ORDER BY PathId, FilenameId, JobId DESC
792 +bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids,
793 + DB_RESULT_HANDLER *result_handler, void *ctx)
797 + Mmsg(mdb->errmsg, _("ERR=JobIds are empty\n"));
802 + POOL_MEM buf (PM_MESSAGE);
805 + "SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId, File.LStat "
807 + "SELECT max(FileId) as FileId, PathId, FilenameId "
808 + "FROM (SELECT FileId, PathId, FilenameId FROM File WHERE JobId IN (%s)) AS F "
809 + "GROUP BY PathId, FilenameId "
811 + "JOIN Filename ON (Filename.FilenameId = Temp.FilenameId) "
812 + "JOIN Path ON (Path.PathId = Temp.PathId) "
813 + "JOIN File ON (File.FileId = Temp.FileId) "
814 + "WHERE File.FileIndex > 0 ",
817 + return db_sql_query(mdb, buf.c_str(), result_handler, ctx);
821 +/* Full : do nothing
822 + * Differential : get the last full id
823 + * Incremental : get the last full + last diff + last incr(s) ids
825 + * TODO: look and merge from ua_restore.c
827 +bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb,
828 + JOB_DBR *jr, POOLMEM *jobids)
830 + char clientid[50], jobid[50], filesetid[50];
831 + char date[MAX_TIME_LENGTH];
833 + POOL_MEM query (PM_FNAME);
834 + bstrutime(date, sizeof(date), time(NULL) + 1);
837 + /* First, find the last good Full backup for this job/client/fileset */
839 +"CREATE TEMPORARY TABLE btemp3%s AS "
840 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
841 + "FROM Job JOIN FileSet USING (FileSetId) "
842 + "WHERE ClientId = %s "
843 + "AND Level='F' AND JobStatus='T' AND Type='B' "
844 + "AND StartTime<'%s' "
845 + "AND FileSet.FileSet=(SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
846 + "ORDER BY Job.JobTDate DESC LIMIT 1",
847 + edit_uint64(jcr->JobId, jobid),
848 + edit_uint64(jr->ClientId, clientid),
850 + edit_uint64(jr->FileSetId, filesetid));
852 + if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
856 + if (jr->JobLevel == L_INCREMENTAL) {
858 + /* Now, find the last differential backup after the last full */
860 +"INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
861 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
862 + "FROM Job JOIN FileSet USING (FileSetId) "
863 + "WHERE ClientId = %s "
864 + "AND Level='D' AND JobStatus='T' AND Type='B' "
865 + "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
866 + "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
867 + "ORDER BY Job.JobTDate DESC LIMIT 1 ",
873 + db_sql_query(mdb, query.c_str(), NULL, NULL);
875 + /* We just have to take all incremental after the last Full/Diff */
877 +"INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
878 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
879 + "FROM Job JOIN FileSet USING (FileSetId) "
880 + "WHERE ClientId = %s "
881 + "AND Level='I' AND JobStatus='T' AND Type='B' "
882 + "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
883 + "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
884 + "ORDER BY Job.JobTDate DESC ",
889 + db_sql_query(mdb, query.c_str(), NULL, NULL);
892 + /* build a jobid list ie: 1,2,3,4 */
893 + Mmsg(query, "SELECT JobId FROM btemp3%s", jobid);
894 + db_sql_query(mdb, query.c_str(), db_get_int_handler, jobids);
895 + Dmsg1(1, "db_accurate_get_jobids=%s\n", jobids);
897 + Mmsg(query, "DROP TABLE btemp3%s", jobid);
898 + db_sql_query(mdb, query.c_str(), NULL, NULL);
904 + * Use to build a string of int list from a query. "10,20,30"
906 +int db_get_int_handler(void *ctx, int num_fields, char **row)
908 + POOLMEM *ret = (POOLMEM *)ctx;
909 + if (num_fields == 1) {
911 + pm_strcat(ret, ",");
913 + pm_strcat(ret, row[0]);
918 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL || HAVE_DBI */
919 Index: src/baconfig.h
920 ===================================================================
921 --- src/baconfig.h (révision 6471)
922 +++ src/baconfig.h (copie de travail)
924 #define FT_INVALIDDT 20 /* Drive type not allowed for */
925 #define FT_REPARSE 21 /* Win NTFS reparse point */
926 #define FT_PLUGIN 22 /* Plugin generated filename */
927 +#define FT_DELETED 23 /* Deleted file entry */
929 /* Definitions for upper part of type word (see above). */
930 #define AR_DATA_STREAM (1<<16) /* Data stream id present */
931 Index: src/stored/bextract.c
932 ===================================================================
933 --- src/stored/bextract.c (révision 6471)
934 +++ src/stored/bextract.c (copie de travail)
937 build_attr_output_fnames(jcr, attr);
939 + if (attr->type == FT_DELETED) { /* TODO: choose the right fname/ofname */
940 + Jmsg(jcr, M_INFO, 0, _("%s was deleted.\n"), attr->fname);
946 stat = create_file(jcr, attr, &bfd, REPLACE_ALWAYS);
948 Index: src/stored/bscan.c
949 ===================================================================
950 --- src/stored/bscan.c (révision 6471)
951 +++ src/stored/bscan.c (copie de travail)
953 ar.ClientId = mjcr->ClientId;
954 ar.JobId = mjcr->JobId;
955 ar.Stream = rec->Stream;
956 - ar.FileIndex = rec->FileIndex;
957 + if (type == FT_DELETED) {
960 + ar.FileIndex = rec->FileIndex;
963 if (dcr->VolFirstIndex == 0) {
964 dcr->VolFirstIndex = rec->FileIndex;
966 ===================================================================
967 --- src/jcr.h (révision 6471)
968 +++ src/jcr.h (copie de travail)
971 /* Forward referenced structures */
978 CRYPTO_CTX crypto; /* Crypto ctx */
979 DIRRES* director; /* Director resource */
980 bool VSS; /* VSS used by FD */
981 + htable *file_list; /* Previous file list (accurate mode) */
982 #endif /* FILE_DAEMON */
985 Index: src/lib/Makefile.in
986 ===================================================================
987 --- src/lib/Makefile.in (révision 6471)
988 +++ src/lib/Makefile.in (copie de travail)
990 res.c rwlock.c scan.c serial.c sha1.c \
991 signal.c smartall.c rblist.c tls.c tree.c \
992 util.c var.c watchdog.c workq.c btimers.c \
993 - address_conf.c pythonlib.c breg.c
994 + address_conf.c pythonlib.c breg.c htable.c
997 LIBOBJS = attr.o base64.o berrno.o bsys.o bget_msg.o \
999 res.o rwlock.o scan.o serial.o sha1.o \
1000 signal.o smartall.o rblist.o tls.o tree.o \
1001 util.o var.o watchdog.o workq.o btimers.o \
1002 - address_conf.o pythonlib.o breg.o
1003 + address_conf.o pythonlib.o breg.o htable.o
1006 EXTRAOBJS = @OBJLIST@
1007 Index: src/lib/attr.c
1008 ===================================================================
1009 --- src/lib/attr.c (révision 6471)
1010 +++ src/lib/attr.c (copie de travail)
1011 @@ -242,6 +242,14 @@
1015 + if (attr->type == FT_DELETED) { /* TODO: change this to get last seen values */
1016 + bsnprintf(buf, sizeof(buf),
1017 + "---------- - - - - ---------- -------- %s\n", attr->ofname);
1018 + Dmsg1(20, "%s", buf);
1019 + Jmsg(jcr, M_RESTORED, 1, "%s", buf);
1023 if (!jcr->id_list) {
1024 jcr->id_list = new_guid_list();
1033 Dmsg1(20, "%s", buf);
1034 Index: src/findlib/create_file.c
1035 ===================================================================
1036 --- src/findlib/create_file.c (révision 6471)
1037 +++ src/findlib/create_file.c (copie de travail)
1043 + Qmsg2(jcr, M_INFO, 0, _("Original file %s have been deleted: type=%d\n"), attr->fname, attr->type);
1045 /* The following should not occur */
1048 Index: src/findlib/find.c
1049 ===================================================================
1050 --- src/findlib/find.c (révision 6471)
1051 +++ src/findlib/find.c (copie de travail)
1053 Dmsg0(100, "Leave set_find_options()\n");
1057 +set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff))
1059 + Dmsg0(1, "Enter set_find_changed_function()\n");
1060 + ff->check_fct = check_fct;
1064 * For VSS we need to know which windows drives
1065 * are used, because we create a snapshot of all used
1066 Index: src/findlib/find_one.c
1067 ===================================================================
1068 --- src/findlib/find_one.c (révision 6471)
1069 +++ src/findlib/find_one.c (copie de travail)
1070 @@ -258,6 +258,33 @@
1074 + * In incremental/diffential or accurate backup, we
1075 + * say if the current file has changed.
1077 +static bool check_changes(JCR *jcr, FF_PKT *ff_pkt)
1079 + /* in special mode (like accurate backup), user can
1080 + * choose his comparison function.
1082 + if (ff_pkt->check_fct) {
1083 + return ff_pkt->check_fct(jcr, ff_pkt);
1086 + /* in normal modes (incr/diff), we use this default
1089 + if (ff_pkt->incremental &&
1090 + (ff_pkt->statp.st_mtime < ff_pkt->save_time &&
1091 + ((ff_pkt->flags & FO_MTIMEONLY) ||
1092 + ff_pkt->statp.st_ctime < ff_pkt->save_time)))
1101 * Find a single file.
1102 * handle_file is the callback for handling the file.
1104 @@ -333,16 +360,13 @@
1105 * since our last "save_time", presumably the last Full save
1108 - if (ff_pkt->incremental && !S_ISDIR(ff_pkt->statp.st_mode)) {
1109 - Dmsg1(300, "Non-directory incremental: %s\n", ff_pkt->fname);
1110 - /* Not a directory */
1111 - if (ff_pkt->statp.st_mtime < ff_pkt->save_time
1112 - && ((ff_pkt->flags & FO_MTIMEONLY) ||
1113 - ff_pkt->statp.st_ctime < ff_pkt->save_time)) {
1114 - /* Incremental option, file not changed */
1115 - ff_pkt->type = FT_NOCHG;
1116 - return handle_file(jcr, ff_pkt, top_level);
1118 + if ( ff_pkt->incremental
1119 + && !S_ISDIR(ff_pkt->statp.st_mode)
1120 + && !check_changes(jcr, ff_pkt))
1122 + Dmsg1(500, "Non-directory incremental: %s\n", ff_pkt->fname);
1123 + ff_pkt->type = FT_NOCHG;
1124 + return handle_file(jcr, ff_pkt, top_level);
1127 #ifdef HAVE_DARWIN_OS
1128 @@ -502,15 +526,13 @@
1131 ff_pkt->link = link;
1132 - if (ff_pkt->incremental &&
1133 - (ff_pkt->statp.st_mtime < ff_pkt->save_time &&
1134 - ((ff_pkt->flags & FO_MTIMEONLY) ||
1135 - ff_pkt->statp.st_ctime < ff_pkt->save_time))) {
1136 + if (ff_pkt->incremental && !check_changes(jcr, ff_pkt)) {
1137 /* Incremental option, directory entry not changed */
1138 ff_pkt->type = FT_DIRNOCHG;
1140 ff_pkt->type = FT_DIRBEGIN;
1143 /* We have set st_rdev to 1 if it is a reparse point, otherwise 0 */
1144 if (have_win32_api() && ff_pkt->statp.st_rdev) {
1145 ff_pkt->type = FT_REPARSE;
1146 Index: src/findlib/find.h
1147 ===================================================================
1148 --- src/findlib/find.h (révision 6471)
1149 +++ src/findlib/find.h (copie de travail)
1151 int GZIP_level; /* GZIP level */
1152 int strip_path; /* strip path count */
1153 char VerifyOpts[MAX_FOPTS]; /* verify options */
1154 + char AccurateOpts[MAX_FOPTS]; /* accurate mode options */
1155 alist regex; /* regex string(s) */
1156 alist regexdir; /* regex string(s) for directories */
1157 alist regexfile; /* regex string(s) for files */
1159 findFILESET *fileset;
1160 int (*file_save)(JCR *, FF_PKT *, bool); /* User's callback */
1161 int (*plugin_save)(JCR *, FF_PKT *, bool); /* User's callback */
1162 + bool (*check_fct)(JCR *, FF_PKT *); /* optionnal user fct to check file changes */
1164 /* Values set by accept_file while processing Options */
1165 uint32_t flags; /* backup options */
1166 Index: src/findlib/protos.h
1167 ===================================================================
1168 --- src/findlib/protos.h (révision 6471)
1169 +++ src/findlib/protos.h (copie de travail)
1172 FF_PKT *init_find_files();
1173 void set_find_options(FF_PKT *ff, int incremental, time_t mtime);
1174 +void set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff));
1175 int find_files(JCR *jcr, FF_PKT *ff, int file_sub(JCR *, FF_PKT *ff_pkt, bool),
1176 int plugin_sub(JCR *, FF_PKT *ff_pkt, bool));
1177 int match_files(JCR *jcr, FF_PKT *ff, int sub(JCR *, FF_PKT *ff_pkt, bool));