1 Index: src/dird/fd_cmds.c
2 ===================================================================
3 --- src/dird/fd_cmds.c (révision 6467)
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 6467)
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/ua_restore.c
166 ===================================================================
167 --- src/dird/ua_restore.c (révision 6467)
168 +++ src/dird/ua_restore.c (copie de travail)
169 @@ -1005,7 +1005,6 @@
170 * For display purposes, the same JobId, with different volumes may
171 * appear more than once, however, we only insert it once.
175 tree.FileEstimate = 0;
176 if (get_next_jobid_from_list(&p, &JobId) > 0) {
177 @@ -1020,23 +1019,12 @@
178 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
181 - for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
184 - if (JobId == last_JobId) {
185 - continue; /* eliminate duplicate JobIds */
187 - last_JobId = JobId;
188 - ua->info_msg(_("\nBuilding directory tree for JobId %s ... "),
189 - edit_int64(JobId, ed1));
192 - * Find files for this JobId and insert them in the tree
194 - Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
195 - if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
196 - ua->error_msg("%s", db_strerror(ua->db));
198 + ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ... "),
201 + if (!db_get_file_list(ua->jcr, ua->db, rx->JobIds, insert_tree_handler, (void *)&tree)) {
202 + ua->error_msg("%s", db_strerror(ua->db));
204 if (tree.FileCount == 0) {
205 ua->send_msg(_("\nThere were no files inserted into the tree, so file selection\n"
206 @@ -1055,26 +1043,13 @@
212 - ua->info_msg(_("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
213 - edit_uint64_with_commas(tree.FileCount, ec1));
216 - ua->info_msg(_("\n1 Job, %s files inserted into the tree.\n"),
217 - edit_uint64_with_commas(tree.FileCount, ec1));
220 + ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
221 + edit_uint64_with_commas(tree.FileCount, ec1));
223 + ua->info_msg(_("\n%s files inserted into the tree.\n"),
224 + edit_uint64_with_commas(tree.FileCount, ec1));
228 - ua->info_msg(_("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
229 - items, edit_uint64_with_commas(tree.FileCount, ec1));
232 - ua->info_msg(_("\n%d Jobs, %s files inserted into the tree.\n"),
233 - items, edit_uint64_with_commas(tree.FileCount, ec1));
237 if (find_arg(ua, NT_("done")) < 0) {
238 /* Let the user interact in selecting which files to restore */
239 Index: src/filed/backup.c
240 ===================================================================
241 --- src/filed/backup.c (révision 6467)
242 +++ src/filed/backup.c (copie de travail)
247 +#include "lib/htable.h"
249 /* Forward referenced functions */
250 int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level);
252 static bool crypto_session_start(JCR *jcr);
253 static void crypto_session_end(JCR *jcr);
254 static bool crypto_session_send(JCR *jcr, BSOCK *sd);
255 +static bool encode_and_send_deleted_file(JCR *jcr, char *fname);
257 +typedef struct CurFile {
263 +#define accurate_mark_file_as_seen(elt) ((elt)->lstat[0] = 0)
264 +#define accurate_file_has_been_seen(elt) ((elt)->lstat[0] == 0)
267 + * This function is called for each file seen in fileset.
268 + * We check in file_list hash if fname have been backuped
269 + * the last time. After we can compare Lstat field. When
270 + * a file have been seen, we put '\0' in LStat field.
273 +/* TODO: tweak verify code to use the same function ?? */
274 +bool accurate_check_file(JCR *jcr, FF_PKT *ff_pkt)
278 + struct stat statc; /* catalog stat */
279 + char *Opts_Digest = ff_pkt->VerifyOpts;
280 + char *fname = ff_pkt->fname;
285 + if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
289 + if (S_ISDIR(ff_pkt->statp.st_mode)) {
290 + fname = ff_pkt->link;
293 + elt = (CurFile *) jcr->file_list->lookup(fname);
296 + Dmsg1(500, "accurate %s = yes (not found)\n", fname);
300 + if (accurate_file_has_been_seen(elt)) {
301 + Dmsg1(500, "accurate %s = no (already seen)\n", fname);
305 + decode_stat(elt->lstat, &statc, &LinkFIc); /* decode catalog stat */
306 +// *do_Digest = CRYPTO_DIGEST_NONE;
308 + for (p=Opts_Digest; *p; p++) {
309 + char ed1[30], ed2[30];
311 + case 'i': /* compare INODEs */
312 + if (statc.st_ino != ff_pkt->statp.st_ino) {
313 + Jmsg(jcr, M_SAVED, 0, _("%s st_ino differ. Cat: %s File: %s\n"), fname,
314 + edit_uint64((uint64_t)statc.st_ino, ed1),
315 + edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
319 + case 'p': /* permissions bits */
320 + if (statc.st_mode != ff_pkt->statp.st_mode) {
321 + Jmsg(jcr, M_SAVED, 0, _("%s st_mode differ. Cat: %x File: %x\n"), fname,
322 + (uint32_t)statc.st_mode, (uint32_t)ff_pkt->statp.st_mode);
326 +// case 'n': /* number of links */
327 +// if (statc.st_nlink != ff_pkt->statp.st_nlink) {
328 +// Jmsg(jcr, M_SAVED, 0, _("%s st_nlink differ. Cat: %d File: %d\n"), fname,
329 +// (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
333 + case 'u': /* user id */
334 + if (statc.st_uid != ff_pkt->statp.st_uid) {
335 + Jmsg(jcr, M_SAVED, 0, _("%s st_uid differ. Cat: %u File: %u\n"), fname,
336 + (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
340 + case 'g': /* group id */
341 + if (statc.st_gid != ff_pkt->statp.st_gid) {
342 + Jmsg(jcr, M_SAVED, 0, _("%s st_gid differ. Cat: %u File: %u\n"), fname,
343 + (uint32_t)statc.st_gid, (uint32_t)ff_pkt->statp.st_gid);
347 + case 's': /* size */
348 + if (statc.st_size != ff_pkt->statp.st_size) {
349 + Jmsg(jcr, M_SAVED, 0, _("%s st_size differ. Cat: %s File: %s\n"), fname,
350 + edit_uint64((uint64_t)statc.st_size, ed1),
351 + edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
355 +// case 'a': /* access time */
356 +// if (statc.st_atime != ff_pkt->statp.st_atime) {
357 +// Jmsg(jcr, M_SAVED, 0, _("%s st_atime differs\n"), fname);
362 + if (statc.st_mtime != ff_pkt->statp.st_mtime) {
363 + Jmsg(jcr, M_SAVED, 0, _("%s st_mtime differs\n"), fname);
367 + case 'c': /* ctime */
368 + if (statc.st_ctime != ff_pkt->statp.st_ctime) {
369 + Jmsg(jcr, M_SAVED, 0, _("%s st_ctime differs\n"), fname);
373 + case 'd': /* file size decrease */
374 + if (statc.st_size > ff_pkt->statp.st_size) {
375 + Jmsg(jcr, M_SAVED, 0, _("%s st_size decrease. Cat: %s File: %s\n"), fname,
376 + edit_uint64((uint64_t)statc.st_size, ed1),
377 + edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
381 + case '5': /* compare MD5 */
382 + Dmsg1(500, "set Do_MD5 for %s\n", ff_pkt->fname);
383 +// *do_Digest = CRYPTO_DIGEST_MD5;
385 + case '1': /* compare SHA1 */
386 +// *do_Digest = CRYPTO_DIGEST_SHA1;
394 + accurate_mark_file_as_seen(elt);
395 + Dmsg2(500, "accurate %s = %i\n", fname, stat);
400 + * This function doesn't work very well with smartalloc
401 + * TODO: use bigbuffer from htable
403 +int accurate_cmd(JCR *jcr)
405 + BSOCK *dir = jcr->dir_bsock;
410 + if (jcr->accurate==false || job_canceled(jcr) || jcr->JobLevel==L_FULL) {
414 + if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
415 + dir->fsend(_("2991 Bad accurate command\n"));
419 + jcr->file_list = (htable *)malloc(sizeof(htable));
420 + jcr->file_list->init(elt, &elt->link, nb);
423 + * buffer = sizeof(CurFile) + dirmsg
424 + * dirmsg = fname + lstat
426 + /* get current files */
427 + while (dir->recv() >= 0) {
428 + len = strlen(dir->msg);
429 + if ((len+1) < dir->msglen) {
430 +// elt = (CurFile *)malloc(sizeof(CurFile));
431 +// elt->fname = (char *) malloc(dir->msglen+1);
433 + /* we store CurFile, fname and lstat in the same chunk */
434 + elt = (CurFile *)malloc(sizeof(CurFile)+dir->msglen+1);
435 + elt->fname = (char *) elt+sizeof(CurFile);
436 + memcpy(elt->fname, dir->msg, dir->msglen);
437 + elt->fname[dir->msglen]='\0';
438 + elt->lstat = elt->fname + len + 1;
439 + jcr->file_list->insert(elt->fname, elt);
440 + Dmsg2(500, "add fname=%s lstat=%s\n", elt->fname, elt->lstat);
444 +// jcr->file_list->stats();
445 + /* TODO: send a EOM ?
446 + dir->fsend("2000 OK accurate\n");
451 +bool accurate_send_deleted_list(JCR *jcr)
453 + if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
457 + if (jcr->file_list == NULL) {
462 + foreach_htable (elt, jcr->file_list) {
463 + if (accurate_file_has_been_seen(elt)) { /* already seen */
464 + Dmsg2(500, "deleted fname=%s lstat=%s\n", elt->fname, elt->lstat);
465 + encode_and_send_deleted_file(jcr, elt->fname);
467 +// free(elt->fname);
470 + /* TODO: clean htable when this function is not reached ? */
471 + if (jcr->file_list) {
472 + jcr->file_list->destroy();
473 + free(jcr->file_list);
474 + jcr->file_list = NULL;
480 * Find all the requested files and send them
481 * to the Storage daemon.
485 jcr->compress_buf_size = jcr->buf_size + ((jcr->buf_size+999) / 1000) + 30;
486 jcr->compress_buf = get_memory(jcr->compress_buf_size);
490 z_stream *pZlibStream = (z_stream*)malloc(sizeof(z_stream));
492 @@ -121,10 +345,13 @@
496 - Dmsg1(300, "set_find_options ff=%p\n", jcr->ff);
497 set_find_options((FF_PKT *)jcr->ff, jcr->incremental, jcr->mtime);
498 - Dmsg0(300, "start find files\n");
500 + /* in accurate mode, we overwrite the find_one check function */
501 + if (jcr->accurate) {
502 + set_find_changed_function((FF_PKT *)jcr->ff, accurate_check_file);
505 start_heartbeat_monitor(jcr);
507 jcr->acl_text = get_pool_memory(PM_MESSAGE);
509 set_jcr_job_status(jcr, JS_ErrorTerminated);
512 + accurate_send_deleted_list(jcr); /* send deleted list to SD */
514 free_pool_memory(jcr->acl_text);
516 stop_heartbeat_monitor(jcr);
517 @@ -1128,6 +1357,57 @@
521 +static bool encode_and_send_deleted_file(JCR *jcr, char *fname)
523 + BSOCK *sd = jcr->store_bsock;
527 +#ifdef FD_NO_SEND_TEST
535 + * Send Attributes header to Storage daemon
536 + * <file-index> <stream> <info>
538 + if (!sd->fsend("%ld %d 0", 0, STREAM_UNIX_ATTRIBUTES)) {
539 + Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
543 + Dmsg1(300, ">stored: attrhdr %s\n", sd->msg);
546 + * Send file attributes to Storage daemon
549 + * Filename (full path)
550 + * Encoded attributes
551 + * Link name (if type==FT_LNK or FT_LNKSAVED)
552 + * Encoded extended-attributes (for Win32)
554 + * For a directory, link is the same as fname, but with trailing
555 + * slash. For a linked file, link is the link.
557 + stat = sd->fsend("0 %d %s%c%s%c%s%c%s%c",
558 + FT_NOSTAT /* FileType */,
559 + fname /* FileName */,
560 + 0, attribs, 0, 0, 0, attribsEx, 0);
562 + Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg);
564 + Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
568 + sd->signal(BNET_EOD); /* indicate end of attributes data */
573 * Do in place strip of path
575 Index: src/filed/job.c
576 ===================================================================
577 --- src/filed/job.c (révision 6467)
578 +++ src/filed/job.c (copie de travail)
580 /* Imported functions */
581 extern int status_cmd(JCR *jcr);
582 extern int qstatus_cmd(JCR *jcr);
583 +extern int accurate_cmd(JCR *jcr);
585 /* Forward referenced functions */
586 static int backup_cmd(JCR *jcr);
588 {"RunBeforeJob", runbefore_cmd, 0},
589 {"RunAfterJob", runafter_cmd, 0},
590 {"Run", runscript_cmd, 0},
591 + {"accurate", accurate_cmd, 0},
592 {NULL, NULL} /* list terminator */
595 @@ -1195,6 +1197,9 @@
597 level = get_memory(dir->msglen+1);
598 Dmsg1(110, "level_cmd: %s", dir->msg);
599 + if (strstr(dir->msg, "accurate")) {
600 + jcr->accurate = true;
602 if (sscanf(dir->msg, "level = %s ", level) != 1) {
605 @@ -1204,14 +1209,14 @@
606 /* Full backup requested? */
607 } else if (strcmp(level, "full") == 0) {
608 jcr->JobLevel = L_FULL;
609 - } else if (strcmp(level, "differential") == 0) {
610 + } else if (strstr(level, "differential")) {
611 jcr->JobLevel = L_DIFFERENTIAL;
614 - } else if (strcmp(level, "incremental") == 0) {
615 + } else if (strstr(level, "incremental")) {
616 jcr->JobLevel = L_INCREMENTAL;
621 * We get his UTC since time, then sync the clocks and correct it
622 * to agree with our clock.
623 Index: src/filed/restore.c
624 ===================================================================
625 --- src/filed/restore.c (révision 6467)
626 +++ src/filed/restore.c (copie de travail)
631 + /* TODO: manage deleted files */
632 + if (file_index == 0) { /* deleted file */
637 * Unpack attributes and do sanity check them
639 Index: src/cats/protos.h
640 ===================================================================
641 --- src/cats/protos.h (révision 6467)
642 +++ src/cats/protos.h (copie de travail)
644 int db_get_client_record(JCR *jcr, B_DB *mdb, CLIENT_DBR *cdbr);
645 int db_get_counter_record(JCR *jcr, B_DB *mdb, COUNTER_DBR *cr);
646 bool db_get_query_dbids(JCR *jcr, B_DB *mdb, POOL_MEM &query, dbid_list &ids);
647 +bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids, DB_RESULT_HANDLER *result_handler, void *ctx);
648 +bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb, JOB_DBR *jr, POOLMEM *jobids);
649 +int db_get_int_handler(void *ctx, int num_fields, char **row);
653 Index: src/cats/sql_get.c
654 ===================================================================
655 --- src/cats/sql_get.c (révision 6467)
656 +++ src/cats/sql_get.c (copie de travail)
665 * Returns: false: on failure
666 @@ -1018,5 +1016,141 @@
671 + * Find the last "accurate" backup state (that can take deleted files in account)
672 + * 1) Get all files with jobid in list (F subquery)
673 + * 2) Take only the last version of each file (Temp subquery) => accurate list is ok
674 + * 3) Join the result to file table to get fileindex, jobid and lstat information
676 + * TODO: On postgresql, this is done with
677 +SELECT DISTINCT ON (PathId, FilenameId) FileIndex, Path, Name, LStat
678 + FROM File JOIN Filename USING (FilenameId) JOIN Path USING (PathId) WHERE JobId IN (40341)
679 + ORDER BY PathId, FilenameId, JobId DESC
681 +bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids,
682 + DB_RESULT_HANDLER *result_handler, void *ctx)
686 + Mmsg(mdb->errmsg, _("ERR=JobIds are empty\n"));
691 + POOL_MEM buf (PM_MESSAGE);
694 + "SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId, File.LStat "
696 + "SELECT max(FileId) as FileId, PathId, FilenameId "
697 + "FROM (SELECT FileId, PathId, FilenameId FROM File WHERE JobId IN (%s)) AS F "
698 + "GROUP BY PathId, FilenameId "
700 + "JOIN Filename ON (Filename.FilenameId = Temp.FilenameId) "
701 + "JOIN Path ON (Path.PathId = Temp.PathId) "
702 + "JOIN File ON (File.FileId = Temp.FileId) "
703 + "WHERE File.FileIndex > 0 ",
706 + return db_sql_query(mdb, buf.c_str(), result_handler, ctx);
710 +/* Full : do nothing
711 + * Differential : get the last full id
712 + * Incremental : get the last full + last diff + last incr(s) ids
714 + * TODO: look and merge from ua_restore.c
716 +bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb,
717 + JOB_DBR *jr, POOLMEM *jobids)
719 + char clientid[50], jobid[50], filesetid[50];
720 + char date[MAX_TIME_LENGTH];
722 + POOL_MEM query (PM_FNAME);
723 + bstrutime(date, sizeof(date), time(NULL) + 1);
726 + /* First, find the last good Full backup for this job/client/fileset */
728 +"CREATE TEMPORARY TABLE btemp3%s AS "
729 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
730 + "FROM Job JOIN FileSet USING (FileSetId) "
731 + "WHERE ClientId = %s "
732 + "AND Level='F' AND JobStatus='T' AND Type='B' "
733 + "AND StartTime<'%s' "
734 + "AND FileSet.FileSet=(SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
735 + "ORDER BY Job.JobTDate DESC LIMIT 1",
736 + edit_uint64(jcr->JobId, jobid),
737 + edit_uint64(jr->ClientId, clientid),
739 + edit_uint64(jr->FileSetId, filesetid));
741 + if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
745 + if (jr->JobLevel == L_INCREMENTAL) {
747 + /* Now, find the last differential backup after the last full */
749 +"INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
750 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
751 + "FROM Job JOIN FileSet USING (FileSetId) "
752 + "WHERE ClientId = %s "
753 + "AND Level='D' AND JobStatus='T' AND Type='B' "
754 + "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
755 + "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
756 + "ORDER BY Job.JobTDate DESC LIMIT 1 ",
762 + db_sql_query(mdb, query.c_str(), NULL, NULL);
764 + /* We just have to take all incremental after the last Full/Diff */
766 +"INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
767 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
768 + "FROM Job JOIN FileSet USING (FileSetId) "
769 + "WHERE ClientId = %s "
770 + "AND Level='I' AND JobStatus='T' AND Type='B' "
771 + "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
772 + "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
773 + "ORDER BY Job.JobTDate DESC ",
778 + db_sql_query(mdb, query.c_str(), NULL, NULL);
781 + /* build a jobid list ie: 1,2,3,4 */
782 + Mmsg(query, "SELECT JobId FROM btemp3%s", jobid);
783 + db_sql_query(mdb, query.c_str(), db_get_int_handler, jobids);
784 + Dmsg1(1, "db_accurate_get_jobids=%s\n", jobids);
786 + Mmsg(query, "DROP TABLE btemp3%s", jobid);
787 + db_sql_query(mdb, query.c_str(), NULL, NULL);
793 + * Use to build a string of int list from a query. "10,20,30"
795 +int db_get_int_handler(void *ctx, int num_fields, char **row)
797 + POOLMEM *ret = (POOLMEM *)ctx;
798 + if (num_fields == 1) {
800 + pm_strcat(ret, ",");
802 + pm_strcat(ret, row[0]);
807 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL || HAVE_DBI */
808 Index: src/stored/bextract.c
809 ===================================================================
810 --- src/stored/bextract.c (révision 6467)
811 +++ src/stored/bextract.c (copie de travail)
813 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
816 + /* handle deleted file
818 + if (rec->FileIndex == 0) {
819 + /* if file is included, remove it ? */
820 + Jmsg(jcr, M_INFO, 0, _("fname=%s is marked as deleted.\n"), attr->fname);
824 if (attr->file_index != rec->FileIndex) {
825 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
826 rec->FileIndex, attr->file_index);
827 Index: src/stored/bscan.c
828 ===================================================================
829 --- src/stored/bscan.c (révision 6467)
830 +++ src/stored/bscan.c (copie de travail)
832 case STREAM_UNIX_ATTRIBUTES:
833 case STREAM_UNIX_ATTRIBUTES_EX:
835 + /* handle deleted file
837 + if (rec->FileIndex == 0) {
838 + create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
839 + FT_NOSTAT, "", rec);
844 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
845 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
847 Index: src/stored/append.c
848 ===================================================================
849 --- src/stored/append.c (révision 6467)
850 +++ src/stored/append.c (copie de travail)
853 /* Read Stream header from the File daemon.
854 * The stream header consists of the following:
855 - * file_index (sequential Bacula file index, base 1)
856 + * file_index (sequential Bacula file index, base 1,
857 + * 0 for deleted files)
858 * stream (Bacula number to distinguish parts of data)
859 * info (Info for Storage daemon -- compressed, encryped, ...)
860 * info is not currently used, so is read, but ignored!
861 @@ -185,16 +186,21 @@
863 Dmsg2(890, "<filed: Header FilInx=%d stream=%d\n", file_index, stream);
865 - if (!(file_index > 0 && (file_index == last_file_index ||
866 - file_index == last_file_index + 1))) {
867 - Jmsg0(jcr, M_FATAL, 0, _("File index from FD not positive or sequential\n"));
871 + * In accurate mode, files with file_index == 0 are marked as deleted
874 + if (!(file_index > 0 && (file_index == last_file_index ||
875 + file_index == last_file_index + 1))) {
876 + Jmsg0(jcr, M_FATAL, 0, _("File index from FD not positive or sequential\n"));
880 + if (file_index != last_file_index) {
881 + jcr->JobFiles = file_index;
882 + last_file_index = file_index;
885 - if (file_index != last_file_index) {
886 - jcr->JobFiles = file_index;
887 - last_file_index = file_index;
890 /* Read data stream from the File daemon.
891 * The data stream is just raw bytes
892 @@ -214,22 +220,23 @@
894 while (!write_record_to_block(dcr->block, &rec)) {
895 Dmsg2(850, "!write_record_to_block data_len=%d rem=%d\n", rec.data_len,
898 if (!write_block_to_device(dcr)) {
899 Dmsg2(90, "Got write_block_to_dev error on device %s. %s\n",
900 - dev->print_name(), dev->bstrerror());
901 + dev->print_name(), dev->bstrerror());
907 + Dmsg0(400, "Not OK\n");
910 + jcr->JobBytes += rec.data_len; /* increment bytes this job */
911 + Dmsg4(850, "write_record FI=%s SessId=%d Strm=%s len=%d\n",
912 + FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId,
913 + stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len);
916 - Dmsg0(400, "Not OK\n");
919 - jcr->JobBytes += rec.data_len; /* increment bytes this job */
920 - Dmsg4(850, "write_record FI=%s SessId=%d Strm=%s len=%d\n",
921 - FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId,
922 - stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len);
924 /* Send attributes and digest to Director for Catalog */
925 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX ||
927 ===================================================================
928 --- src/jcr.h (révision 6467)
929 +++ src/jcr.h (copie de travail)
932 /* Forward referenced structures */
939 CRYPTO_CTX crypto; /* Crypto ctx */
940 DIRRES* director; /* Director resource */
941 bool VSS; /* VSS used by FD */
942 + htable *file_list; /* Previous file list (accurate mode) */
943 #endif /* FILE_DAEMON */
946 Index: src/lib/Makefile.in
947 ===================================================================
948 --- src/lib/Makefile.in (révision 6467)
949 +++ src/lib/Makefile.in (copie de travail)
951 res.c rwlock.c scan.c serial.c sha1.c \
952 signal.c smartall.c rblist.c tls.c tree.c \
953 util.c var.c watchdog.c workq.c btimers.c \
954 - address_conf.c pythonlib.c breg.c
955 + address_conf.c pythonlib.c breg.c htable.c
958 LIBOBJS = attr.o base64.o berrno.o bsys.o bget_msg.o \
960 res.o rwlock.o scan.o serial.o sha1.o \
961 signal.o smartall.o rblist.o tls.o tree.o \
962 util.o var.o watchdog.o workq.o btimers.o \
963 - address_conf.o pythonlib.o breg.o
964 + address_conf.o pythonlib.o breg.o htable.o
967 EXTRAOBJS = @OBJLIST@
968 Index: src/findlib/find.c
969 ===================================================================
970 --- src/findlib/find.c (révision 6467)
971 +++ src/findlib/find.c (copie de travail)
973 Dmsg0(100, "Leave set_find_options()\n");
977 +set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff))
979 + Dmsg0(100, "Enter set_find_changed_function()\n");
980 + ff->check_fct = check_fct;
984 * For VSS we need to know which windows drives
985 * are used, because we create a snapshot of all used
986 Index: src/findlib/find_one.c
987 ===================================================================
988 --- src/findlib/find_one.c (révision 6467)
989 +++ src/findlib/find_one.c (copie de travail)
994 + * In incremental/diffential or accurate backup, we
995 + * say if the current file has changed.
997 +static bool check_changes(JCR *jcr, FF_PKT *ff_pkt)
999 + /* in special mode (like accurate backup), user can
1000 + * choose his comparison function.
1002 + if (ff_pkt->check_fct) {
1003 + return ff_pkt->check_fct(jcr, ff_pkt);
1006 + /* in normal modes (incr/diff), we use this default
1009 + if (ff_pkt->incremental &&
1010 + (ff_pkt->statp.st_mtime < ff_pkt->save_time &&
1011 + ((ff_pkt->flags & FO_MTIMEONLY) ||
1012 + ff_pkt->statp.st_ctime < ff_pkt->save_time)))
1021 * Find a single file.
1022 * handle_file is the callback for handling the file.
1024 @@ -333,16 +360,13 @@
1025 * since our last "save_time", presumably the last Full save
1028 - if (ff_pkt->incremental && !S_ISDIR(ff_pkt->statp.st_mode)) {
1029 + if ( ff_pkt->incremental
1030 + && !S_ISDIR(ff_pkt->statp.st_mode)
1031 + && !check_changes(jcr, ff_pkt))
1033 Dmsg1(300, "Non-directory incremental: %s\n", ff_pkt->fname);
1034 - /* Not a directory */
1035 - if (ff_pkt->statp.st_mtime < ff_pkt->save_time
1036 - && ((ff_pkt->flags & FO_MTIMEONLY) ||
1037 - ff_pkt->statp.st_ctime < ff_pkt->save_time)) {
1038 - /* Incremental option, file not changed */
1039 - ff_pkt->type = FT_NOCHG;
1040 - return handle_file(jcr, ff_pkt, top_level);
1042 + ff_pkt->type = FT_NOCHG;
1043 + return handle_file(jcr, ff_pkt, top_level);
1046 #ifdef HAVE_DARWIN_OS
1047 @@ -502,15 +526,13 @@
1050 ff_pkt->link = link;
1051 - if (ff_pkt->incremental &&
1052 - (ff_pkt->statp.st_mtime < ff_pkt->save_time &&
1053 - ((ff_pkt->flags & FO_MTIMEONLY) ||
1054 - ff_pkt->statp.st_ctime < ff_pkt->save_time))) {
1055 + if (ff_pkt->incremental && !check_changes(jcr, ff_pkt)) {
1056 /* Incremental option, directory entry not changed */
1057 ff_pkt->type = FT_DIRNOCHG;
1059 ff_pkt->type = FT_DIRBEGIN;
1062 /* We have set st_rdev to 1 if it is a reparse point, otherwise 0 */
1063 if (have_win32_api() && ff_pkt->statp.st_rdev) {
1064 ff_pkt->type = FT_REPARSE;
1065 Index: src/findlib/find.h
1066 ===================================================================
1067 --- src/findlib/find.h (révision 6467)
1068 +++ src/findlib/find.h (copie de travail)
1070 findFILESET *fileset;
1071 int (*file_save)(JCR *, FF_PKT *, bool); /* User's callback */
1072 int (*plugin_save)(JCR *, FF_PKT *, bool); /* User's callback */
1073 + bool (*check_fct)(JCR *, FF_PKT *); /* optionnal user fct to check file changes */
1075 /* Values set by accept_file while processing Options */
1076 uint32_t flags; /* backup options */
1077 Index: src/findlib/protos.h
1078 ===================================================================
1079 --- src/findlib/protos.h (révision 6467)
1080 +++ src/findlib/protos.h (copie de travail)
1083 FF_PKT *init_find_files();
1084 void set_find_options(FF_PKT *ff, int incremental, time_t mtime);
1085 +void set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff));
1086 int find_files(JCR *jcr, FF_PKT *ff, int file_sub(JCR *, FF_PKT *ff_pkt, bool),
1087 int plugin_sub(JCR *, FF_PKT *ff_pkt, bool));
1088 int match_files(JCR *jcr, FF_PKT *ff, int sub(JCR *, FF_PKT *ff_pkt, bool));