Index: src/dird/fd_cmds.c
===================================================================
---- src/dird/fd_cmds.c (révision 6443)
+--- src/dird/fd_cmds.c (révision 6467)
+++ src/dird/fd_cmds.c (copie de travail)
@@ -50,7 +50,7 @@
static char filesetcmd[] = "fileset%s\n"; /* set full fileset */
static char runscript[] = "Run OnSuccess=%u OnFailure=%u AbortOnError=%u When=%u Command=%s\n";
static char runbeforenow[]= "RunBeforeNow\n";
-@@ -226,7 +226,7 @@
+@@ -226,13 +226,12 @@
char ed1[50];
stime = str_to_utime(jcr->stime);
while (bget_dirmsg(fd) >= 0) { /* allow him to poll us to sync clocks */
Jmsg(jcr, M_INFO, 0, "%s\n", fd->msg);
}
-@@ -240,24 +240,25 @@
+ }
+
+-
+ /*
+ * Send level command to FD.
+ * Used for backup jobs and estimate command.
+@@ -240,24 +239,26 @@
bool send_level_command(JCR *jcr)
{
BSOCK *fd = jcr->file_bsock;
+ const char *accurate=jcr->job->accurate?"accurate_":"";
++ const char *not_accurate="";
/*
* Send Level command to File daemon
*/
switch (jcr->JobLevel) {
case L_BASE:
- fd->fsend(levelcmd, "base", " ", 0);
-+ fd->fsend(levelcmd, "", "base", " ", 0);
++ fd->fsend(levelcmd, not_accurate, "base", " ", 0);
break;
/* L_NONE is the console, sending something off to the FD */
case L_NONE:
case L_FULL:
- fd->fsend(levelcmd, "full", " ", 0);
-+ fd->fsend(levelcmd, "", "full", " ", 0);
++ fd->fsend(levelcmd, not_accurate, "full", " ", 0);
break;
case L_DIFFERENTIAL:
- fd->fsend(levelcmd, "differential", " ", 0);
case L_SINCE:
Index: src/dird/backup.c
===================================================================
---- src/dird/backup.c (révision 6443)
+--- src/dird/backup.c (révision 6467)
+++ src/dird/backup.c (copie de travail)
-@@ -44,6 +44,7 @@
- #include "bacula.h"
- #include "dird.h"
- #include "ua.h"
-+#include "findlib/find.h"
-
- /* Commands sent to File daemon */
- static char backupcmd[] = "backup\n";
-@@ -96,7 +97,93 @@
- return true;
+@@ -97,6 +97,65 @@
}
+ /*
++ * Foreach files in currrent list, send "/path/fname\0LStat" to FD
++ */
+static int accurate_list_handler(void *ctx, int num_fields, char **row)
+{
+ JCR *jcr = (JCR *)ctx;
+ return 1;
+ }
+
-+ if (row[0] > 0) { /* discard when file_index == 0 */
-+ jcr->file_bsock->fsend("%s%s%c%s", row[1], row[2], 0, row[3]);
++ if (row[2] > 0) { /* discard when file_index == 0 */
++ jcr->file_bsock->fsend("%s%s%c%s", row[0], row[1], 0, row[4]);
+ }
+ return 0;
+}
+
-+/* Full : do nothing
-+ * Differential : get the last full id
-+ * Incremental : get the last full + last diff + last incr(s) ids
-+ *
-+ * TODO: look and merge from ua_restore.c
++/*
++ * Send current file list to FD
++ * DIR -> FD : accurate files=xxxx
++ * DIR -> FD : /path/to/file\0Lstat
++ * DIR -> FD : /path/to/dir/\0Lstat
++ * ...
++ * DIR -> FD : EOD
+ */
-+bool db_accurate_get_jobids(JCR *jcr, POOLMEM *jobids)
-+{
-+ pm_strcpy(jobids, "42");
-+ return 1;
-+}
-+
+bool send_accurate_current_files(JCR *jcr)
+{
-+ char buf[MAXSTRING];
-+ char ed1[50], ed2[50];
++ POOL_MEM buf;
+
-+ if (jcr->accurate == false || job_canceled(jcr) || jcr->JobLevel == L_FULL) {
++ if (jcr->accurate==false || job_canceled(jcr) || jcr->JobLevel==L_FULL) {
+ return true;
+ }
-+
+ POOLMEM *jobids = get_pool_memory(PM_FNAME);
-+ db_accurate_get_jobids(jcr, jobids);
-+
-+ bsnprintf(buf, sizeof(buf),
-+ "CREATE TEMPORARY TABLE btemp2%s AS ( "
-+ "SELECT max(FileId) as FileId, PathId, FilenameId "
-+ "FROM (SELECT FileId, PathId, FilenameId FROM File WHERE JobId IN (%s)) AS F "
-+ "GROUP BY PathId, FilenameId ) ",
-+ edit_uint64(jcr->JobId, ed1),
-+ jobids);
-+ db_sql_query(jcr->db, buf, NULL, NULL);
-+
-+ bsnprintf(buf, sizeof(buf), "SELECT count(1) FROM btemp2%s",ed1);
-+ db_sql_query(jcr->db, buf, NULL, NULL); // TODO: compter le nombre de rows
-+
-+ jcr->file_bsock->fsend("accurate files=%s\n", edit_uint64(337969*2, ed2));
-+
-+ bsnprintf(buf, sizeof(buf),
-+ "SELECT File.FileIndex, Path.Path, Filename.Name, File.LStat "
-+ "FROM btemp2%s JOIN Path USING (PathId) JOIN Filename USING (FilenameId) "
-+ "JOIN File USING (FileId) "
-+ "WHERE File.FileIndex > 0",
-+ ed1, jobids);
-+ db_sql_query(jcr->db, buf, accurate_list_handler, (void *)jcr);
-+
-+ bsnprintf(buf, sizeof(buf), "DROP TABLE btemp2%s", ed1);
-+ free_pool_memory(jobids);
++ db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, jobids);
+
- /*
-+ CREATE TEMPORARY TABLE btemp2 AS (
-+ SELECT max(FileId) as FileId, PathId, FilenameId
-+ FROM (SELECT FileId, PathId, FilenameId FROM File WHERE JobId IN (39867,40341)) AS F
-+ GROUP BY PathId, FilenameId )
-+
-+ SELECT File.FileIndex, Path.Path, Filename.Name, File.LStat
-+ FROM btemp2 JOIN Path USING (PathId) JOIN Filename USING (FilenameId)
-+ JOIN File USING (FileId)
-+ WHERE File.FileIndex > 0
-+
-+ DROP TABLE btemp2
-+*/
-+/*
-+SELECT DISTINCT ON (PathId, FilenameId) FileIndex, Path, Name, LStat
-+ FROM File JOIN Filename USING (FilenameId) JOIN Path USING (PathId) WHERE JobId IN (40341)
-+ ORDER BY PathId, FilenameId, JobId DESC
-+*/
++ if (*jobids == 0) {
++ free_pool_memory(jobids);
++ Jmsg(jcr, M_FATAL, 0, _("Cannot find previous jobids.\n"));
++ return false;
++ }
++ Jmsg(jcr, M_INFO, 0, _("Sending Accurate information.\n"));
++
++ /* to be able to allocate the right size for htable */
++ POOLMEM *nb = get_pool_memory(PM_FNAME);
++ Mmsg(buf, "SELECT sum(JobFiles) FROM Job WHERE JobId IN (%s)",jobids);
++ db_sql_query(jcr->db, buf.c_str(), db_get_int_handler, nb);
++ jcr->file_bsock->fsend("accurate files=%s\n", nb);
++
++ db_get_file_list(jcr, jcr->db, jobids, accurate_list_handler, (void *)jcr);
++
++ free_pool_memory(jobids);
++ free_pool_memory(nb);
+
+ jcr->file_bsock->signal(BNET_EOD);
++ /* TODO: use response() ? */
++
+ return true;
+}
+
* Do a backup of the specified FileSet
*
* Returns: false on failure
-@@ -225,6 +312,14 @@
+@@ -225,6 +284,14 @@
Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
}
+ /*
-+ * If backup is in accurate mode, FD will send the list of
-+ * all files.
++ * If backup is in accurate mode, we send the list of
++ * all files to FD.
+ */
+ if (!send_accurate_current_files(jcr)) {
+ goto bail_out;
/* Send backup command */
fd->fsend(backupcmd);
if (!response(jcr, fd, OKbackup, "backup", DISPLAY_ERROR)) {
-@@ -234,6 +329,7 @@
- /* Pickup Job termination data */
- stat = wait_for_job_termination(jcr);
- db_write_batch_file_records(jcr); /* used by bulk batch file insert */
-+
- if (stat == JS_Terminated) {
- backup_cleanup(jcr, stat);
- return true;
-Index: src/dird/inc_conf.c
+@@ -475,6 +542,7 @@
+ " Software Compression: %s\n"
+ " VSS: %s\n"
+ " Encryption: %s\n"
++" Accurate: %s\n"
+ " Volume name(s): %s\n"
+ " Volume Session Id: %d\n"
+ " Volume Session Time: %d\n"
+@@ -506,8 +574,9 @@
+ edit_uint64_with_suffix(jcr->SDJobBytes, ec6),
+ kbps,
+ compress,
+- jcr->VSS?"yes":"no",
+- jcr->Encrypt?"yes":"no",
++ jcr->VSS?_("yes"):_("no"),
++ jcr->Encrypt?_("yes"):_("no"),
++ jcr->accurate?_("yes"):_("no"),
+ jcr->VolumeName,
+ jcr->VolSessionId,
+ jcr->VolSessionTime,
+Index: src/dird/ua_restore.c
===================================================================
---- src/dird/inc_conf.c (révision 6443)
-+++ src/dird/inc_conf.c (copie de travail)
-@@ -94,6 +94,7 @@
- * Items that are valid in an Options resource
- */
- static RES_ITEM options_items[] = {
-+ {"accurate", store_opts, {0}, 0, 0, 0},
- {"compression", store_opts, {0}, 0, 0, 0},
- {"signature", store_opts, {0}, 0, 0, 0},
- {"verify", store_opts, {0}, 0, 0, 0},
-@@ -153,7 +154,8 @@
- INC_KW_NOATIME,
- INC_KW_ENHANCEDWILD,
- INC_KW_CHKCHANGES,
-- INC_KW_STRIPPATH
-+ INC_KW_STRIPPATH,
-+ INC_KW_ACCURATE
- };
+--- src/dird/ua_restore.c (révision 6467)
++++ src/dird/ua_restore.c (copie de travail)
+@@ -1005,7 +1005,6 @@
+ * For display purposes, the same JobId, with different volumes may
+ * appear more than once, however, we only insert it once.
+ */
+- int items = 0;
+ p = rx->JobIds;
+ tree.FileEstimate = 0;
+ if (get_next_jobid_from_list(&p, &JobId) > 0) {
+@@ -1020,23 +1019,12 @@
+ tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
+ }
+ }
+- for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
+- char ed1[50];
- /*
-@@ -163,6 +165,7 @@
- * options given above.
- */
- static struct s_kw FS_option_kw[] = {
-+ {"accurate", INC_KW_ACCURATE},
- {"compression", INC_KW_COMPRESSION},
- {"signature", INC_KW_DIGEST},
- {"encryption", INC_KW_ENCRYPTION},
-@@ -251,6 +254,8 @@
- {"no", INC_KW_ENHANCEDWILD, "0"},
- {"yes", INC_KW_CHKCHANGES, "c"},
- {"no", INC_KW_CHKCHANGES, "0"},
-+ {"yes", INC_KW_ACCURATE, "C"},
-+ {"no", INC_KW_ACCURATE, "0"},
- {NULL, 0, 0}
- };
+- if (JobId == last_JobId) {
+- continue; /* eliminate duplicate JobIds */
+- }
+- last_JobId = JobId;
+- ua->info_msg(_("\nBuilding directory tree for JobId %s ... "),
+- edit_int64(JobId, ed1));
+- items++;
+- /*
+- * Find files for this JobId and insert them in the tree
+- */
+- Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
+- if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
+- ua->error_msg("%s", db_strerror(ua->db));
+- }
++ ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ... "),
++ rx->JobIds);
++
++ if (!db_get_file_list(ua->jcr, ua->db, rx->JobIds, insert_tree_handler, (void *)&tree)) {
++ ua->error_msg("%s", db_strerror(ua->db));
+ }
+ if (tree.FileCount == 0) {
+ ua->send_msg(_("\nThere were no files inserted into the tree, so file selection\n"
+@@ -1055,26 +1043,13 @@
+ }
+ } else {
+ char ec1[50];
+- if (items==1) {
+- if (tree.all) {
+- ua->info_msg(_("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
+- edit_uint64_with_commas(tree.FileCount, ec1));
+- }
+- else {
+- ua->info_msg(_("\n1 Job, %s files inserted into the tree.\n"),
+- edit_uint64_with_commas(tree.FileCount, ec1));
+- }
++ if (tree.all) {
++ ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
++ edit_uint64_with_commas(tree.FileCount, ec1));
++ } else {
++ ua->info_msg(_("\n%s files inserted into the tree.\n"),
++ edit_uint64_with_commas(tree.FileCount, ec1));
+ }
+- else {
+- if (tree.all) {
+- ua->info_msg(_("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
+- items, edit_uint64_with_commas(tree.FileCount, ec1));
+- }
+- else {
+- ua->info_msg(_("\n%d Jobs, %s files inserted into the tree.\n"),
+- items, edit_uint64_with_commas(tree.FileCount, ec1));
+- }
+- }
+ if (find_arg(ua, NT_("done")) < 0) {
+ /* Let the user interact in selecting which files to restore */
Index: src/filed/backup.c
===================================================================
---- src/filed/backup.c (révision 6443)
+--- src/filed/backup.c (révision 6467)
+++ src/filed/backup.c (copie de travail)
-@@ -48,8 +48,220 @@
+@@ -37,6 +37,7 @@
+
+ #include "bacula.h"
+ #include "filed.h"
++#include "lib/htable.h"
+
+ /* Forward referenced functions */
+ int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level);
+@@ -48,8 +49,231 @@
static bool crypto_session_start(JCR *jcr);
static void crypto_session_end(JCR *jcr);
static bool crypto_session_send(JCR *jcr, BSOCK *sd);
+static bool encode_and_send_deleted_file(JCR *jcr, char *fname);
-+#include "lib/htable.c"
+typedef struct CurFile {
+ char *fname;
+ char *lstat;
+ hlink link;
+} CurFile;
++
++#define accurate_mark_file_as_seen(elt) ((elt)->lstat[0] = 0)
++#define accurate_file_has_been_seen(elt) ((elt)->lstat[0] == 0)
+
/*
+ * This function is called for each file seen in fileset.
-+ *
-+ * If the file is skipped (saved=false), we will check if this
-+ * file have been backuped before. If not, we decide to backup it.
-+ *
-+ * If the file have saved=true, we mark it as seen
++ * We check in file_list hash if fname have been backuped
++ * the last time. After we can compare Lstat field. When
++ * a file have been seen, we put '\0' in LStat field.
+ *
+ */
-+/* TODO: tweak verify code to use the same function */
-+bool accurate_check_file(JCR *jcr, FF_PKT *ff_pkt, bool saved)
++/* TODO: tweak verify code to use the same function ?? */
++bool accurate_check_file(JCR *jcr, FF_PKT *ff_pkt)
+{
+ char *p;
+ int stat=false;
+ return true;
+ }
+
-+ if (ff_pkt->type == FT_DIREND || ff_pkt->type == FT_REPARSE || ff_pkt->type == FT_DIRNOCHG) {
++ if (S_ISDIR(ff_pkt->statp.st_mode)) {
+ fname = ff_pkt->link;
+ }
+
+ elt = (CurFile *) jcr->file_list->lookup(fname);
+
+ if (!elt) {
-+ // TODO: we must backup it !
-+ Dmsg1(1, "accurate %s = yes (not found)\n", fname);
++ Dmsg1(500, "accurate %s = yes (not found)\n", fname);
+ return true;
+ }
+
-+ if (saved || *elt->lstat == '\0') {
-+ Dmsg1(1, "accurate %s = no (already seen)\n", fname);
-+ *elt->lstat = '\0';
++ if (accurate_file_has_been_seen(elt)) {
++ Dmsg1(500, "accurate %s = no (already seen)\n", fname);
+ return false;
+ }
+
+ char ed1[30], ed2[30];
+ switch (*p) {
+ case 'i': /* compare INODEs */
-+ if (statc.st_ino != ff_pkt->statp.st_ino) {
-+ Jmsg(jcr, M_INFO, 0, _(" st_ino differ. Cat: %s File: %s\n"),
-+ edit_uint64((uint64_t)statc.st_ino, ed1),
-+ edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
-+ stat = true;
-+ }
-+ break;
++ if (statc.st_ino != ff_pkt->statp.st_ino) {
++ Jmsg(jcr, M_SAVED, 0, _("%s st_ino differ. Cat: %s File: %s\n"), fname,
++ edit_uint64((uint64_t)statc.st_ino, ed1),
++ edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
++ stat = true;
++ }
++ break;
+ case 'p': /* permissions bits */
-+ if (statc.st_mode != ff_pkt->statp.st_mode) {
-+ Jmsg(jcr, M_INFO, 0, _(" st_mode differ. Cat: %x File: %x\n"),
-+ (uint32_t)statc.st_mode, (uint32_t)ff_pkt->statp.st_mode);
-+ stat = true;
-+ }
-+ break;
-+ case 'n': /* number of links */
-+ if (statc.st_nlink != ff_pkt->statp.st_nlink) {
-+ Jmsg(jcr, M_INFO, 0, _(" st_nlink differ. Cat: %d File: %d\n"),
-+ (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
-+ stat = true;
-+ }
-+ break;
++ if (statc.st_mode != ff_pkt->statp.st_mode) {
++ Jmsg(jcr, M_SAVED, 0, _("%s st_mode differ. Cat: %x File: %x\n"), fname,
++ (uint32_t)statc.st_mode, (uint32_t)ff_pkt->statp.st_mode);
++ stat = true;
++ }
++ break;
++// case 'n': /* number of links */
++// if (statc.st_nlink != ff_pkt->statp.st_nlink) {
++// Jmsg(jcr, M_SAVED, 0, _("%s st_nlink differ. Cat: %d File: %d\n"), fname,
++// (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
++// stat = true;
++// }
++// break;
+ case 'u': /* user id */
-+ if (statc.st_uid != ff_pkt->statp.st_uid) {
-+ Jmsg(jcr, M_INFO, 0, _(" st_uid differ. Cat: %u File: %u\n"),
-+ (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
-+ stat = true;
-+ }
-+ break;
++ if (statc.st_uid != ff_pkt->statp.st_uid) {
++ Jmsg(jcr, M_SAVED, 0, _("%s st_uid differ. Cat: %u File: %u\n"), fname,
++ (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
++ stat = true;
++ }
++ break;
+ case 'g': /* group id */
-+ if (statc.st_gid != ff_pkt->statp.st_gid) {
-+ Jmsg(jcr, M_INFO, 0, _(" st_gid differ. Cat: %u File: %u\n"),
-+ (uint32_t)statc.st_gid, (uint32_t)ff_pkt->statp.st_gid);
-+ stat = true;
-+ }
-+ break;
++ if (statc.st_gid != ff_pkt->statp.st_gid) {
++ Jmsg(jcr, M_SAVED, 0, _("%s st_gid differ. Cat: %u File: %u\n"), fname,
++ (uint32_t)statc.st_gid, (uint32_t)ff_pkt->statp.st_gid);
++ stat = true;
++ }
++ break;
+ case 's': /* size */
-+ if (statc.st_size != ff_pkt->statp.st_size) {
-+ Jmsg(jcr, M_INFO, 0, _(" st_size differ. Cat: %s File: %s\n"),
-+ edit_uint64((uint64_t)statc.st_size, ed1),
-+ edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
-+ stat = true;
-+ }
-+ break;
-+ case 'a': /* access time */
-+ if (statc.st_atime != ff_pkt->statp.st_atime) {
-+ Jmsg(jcr, M_INFO, 0, _(" st_atime differs\n"));
-+ stat = true;
-+ }
-+ break;
++ if (statc.st_size != ff_pkt->statp.st_size) {
++ Jmsg(jcr, M_SAVED, 0, _("%s st_size differ. Cat: %s File: %s\n"), fname,
++ edit_uint64((uint64_t)statc.st_size, ed1),
++ edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
++ stat = true;
++ }
++ break;
++// case 'a': /* access time */
++// if (statc.st_atime != ff_pkt->statp.st_atime) {
++// Jmsg(jcr, M_SAVED, 0, _("%s st_atime differs\n"), fname);
++// stat = true;
++// }
++// break;
+ case 'm':
-+ if (statc.st_mtime != ff_pkt->statp.st_mtime) {
-+ Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n"));
-+ stat = true;
-+ }
-+ break;
++ if (statc.st_mtime != ff_pkt->statp.st_mtime) {
++ Jmsg(jcr, M_SAVED, 0, _("%s st_mtime differs\n"), fname);
++ stat = true;
++ }
++ break;
+ case 'c': /* ctime */
-+ if (statc.st_ctime != ff_pkt->statp.st_ctime) {
-+ Jmsg(jcr, M_INFO, 0, _(" st_ctime differs\n"));
-+ stat = true;
-+ }
-+ break;
++ if (statc.st_ctime != ff_pkt->statp.st_ctime) {
++ Jmsg(jcr, M_SAVED, 0, _("%s st_ctime differs\n"), fname);
++ stat = true;
++ }
++ break;
+ case 'd': /* file size decrease */
-+ if (statc.st_size > ff_pkt->statp.st_size) {
-+ Jmsg(jcr, M_INFO, 0, _(" st_size decrease. Cat: %s File: %s\n"),
-+ edit_uint64((uint64_t)statc.st_size, ed1),
-+ edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
-+ stat = true;
-+ }
-+ break;
++ if (statc.st_size > ff_pkt->statp.st_size) {
++ Jmsg(jcr, M_SAVED, 0, _("%s st_size decrease. Cat: %s File: %s\n"), fname,
++ edit_uint64((uint64_t)statc.st_size, ed1),
++ edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
++ stat = true;
++ }
++ break;
+ case '5': /* compare MD5 */
-+ Dmsg1(500, "set Do_MD5 for %s\n", ff_pkt->fname);
-+// *do_Digest = CRYPTO_DIGEST_MD5;
-+ break;
++ Dmsg1(500, "set Do_MD5 for %s\n", ff_pkt->fname);
++// *do_Digest = CRYPTO_DIGEST_MD5;
++ break;
+ case '1': /* compare SHA1 */
-+// *do_Digest = CRYPTO_DIGEST_SHA1;
-+ break;
++// *do_Digest = CRYPTO_DIGEST_SHA1;
++ break;
+ case ':':
+ case 'V':
+ default:
-+ break;
++ break;
+ }
+ }
-+ *elt->lstat = '\0'; /* mark it as seen */
-+ Dmsg2(1, "accurate %s = %i\n", fname, stat);
++ accurate_mark_file_as_seen(elt);
++ Dmsg2(500, "accurate %s = %i\n", fname, stat);
+ return stat;
+}
+
-+static int deb=0;
-+int accurate_get_current_file_list_cmd(JCR *jcr)
++/*
++ * This function doesn't work very well with smartalloc
++ * TODO: use bigbuffer from htable
++ */
++int accurate_cmd(JCR *jcr)
+{
+ BSOCK *dir = jcr->dir_bsock;
+ int len;
+ uint64_t nb;
+ CurFile *elt=NULL;
+
-+ if (jcr->accurate == false || job_canceled(jcr) || jcr->JobLevel == L_FULL) {
++ if (jcr->accurate==false || job_canceled(jcr) || jcr->JobLevel==L_FULL) {
+ return true;
+ }
+
+ dir->fsend(_("2991 Bad accurate command\n"));
+ return false;
+ }
-+
++
+ jcr->file_list = (htable *)malloc(sizeof(htable));
+ jcr->file_list->init(elt, &elt->link, nb);
+
++ /*
++ * buffer = sizeof(CurFile) + dirmsg
++ * dirmsg = fname + lstat
++ */
+ /* get current files */
+ while (dir->recv() >= 0) {
+ len = strlen(dir->msg);
+ if ((len+1) < dir->msglen) {
-+ elt = (CurFile *)malloc(sizeof(CurFile));
-+ elt->fname = (char *) malloc(dir->msglen+1);
-+ memcpy(elt->fname, dir->msg, dir->msglen);
-+ elt->fname[dir->msglen]='\0';
-+ elt->lstat = elt->fname + len + 1;
-+ if ((deb % 1000) == 1) {
-+ Dmsg1(1, "deb=%i\n", deb);
-+ }
-+ if ((deb % 5000) == 1) {
-+ jcr->file_list->stats();
-+ }
-+ Dmsg2(100, "hash[%s]=%s\n", elt->fname, elt->lstat);
-+ jcr->file_list->insert(elt->fname, elt);
-+ deb++;
++// elt = (CurFile *)malloc(sizeof(CurFile));
++// elt->fname = (char *) malloc(dir->msglen+1);
++
++ /* we store CurFile, fname and lstat in the same chunk */
++ elt = (CurFile *)malloc(sizeof(CurFile)+dir->msglen+1);
++ elt->fname = (char *) elt+sizeof(CurFile);
++ memcpy(elt->fname, dir->msg, dir->msglen);
++ elt->fname[dir->msglen]='\0';
++ elt->lstat = elt->fname + len + 1;
++ jcr->file_list->insert(elt->fname, elt);
++ Dmsg2(500, "add fname=%s lstat=%s\n", elt->fname, elt->lstat);
+ }
+ }
-+
-+ jcr->file_list->stats();
-+// dir->fsend("2000 OK accurate\n");
+
++// jcr->file_list->stats();
++ /* TODO: send a EOM ?
++ dir->fsend("2000 OK accurate\n");
++ */
+ return true;
+}
+
+bool accurate_send_deleted_list(JCR *jcr)
+{
+ if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
-+ return true;
++ goto bailout;
++ }
++
++ if (jcr->file_list == NULL) {
++ goto bailout;
+ }
+
+ CurFile *elt;
+ foreach_htable (elt, jcr->file_list) {
-+ Dmsg3(100, "elt = 0x%x fname=%s lstat=%s\n", elt, elt->fname, elt->lstat);
-+ if (*elt->lstat != '\0') {
-+ encode_and_send_deleted_file(jcr, elt->fname);
++ if (accurate_file_has_been_seen(elt)) { /* already seen */
++ Dmsg2(500, "deleted fname=%s lstat=%s\n", elt->fname, elt->lstat);
++ encode_and_send_deleted_file(jcr, elt->fname);
+ }
-+ free(elt->fname);
++// free(elt->fname);
++ }
++bailout:
++ /* TODO: clean htable when this function is not reached ? */
++ if (jcr->file_list) {
++ jcr->file_list->destroy();
++ free(jcr->file_list);
++ jcr->file_list = NULL;
+ }
-+ jcr->file_list->destroy(); /* TODO: clean htable when this function is not reached ? */
-+ free(jcr->file_list);
-+ jcr->file_list = NULL;
+ return true;
+}
+
* Find all the requested files and send them
* to the Storage daemon.
*
-@@ -66,7 +278,6 @@
- BSOCK *sd;
- bool ok = true;
- // TODO landonf: Allow user to specify encryption algorithm
+@@ -100,7 +324,7 @@
+ */
+ jcr->compress_buf_size = jcr->buf_size + ((jcr->buf_size+999) / 1000) + 30;
+ jcr->compress_buf = get_memory(jcr->compress_buf_size);
-
- sd = jcr->store_bsock;
++
+ #ifdef HAVE_LIBZ
+ z_stream *pZlibStream = (z_stream*)malloc(sizeof(z_stream));
+ if (pZlibStream) {
+@@ -121,10 +345,13 @@
+ return false;
+ }
- set_jcr_job_status(jcr, JS_Running);
-@@ -134,7 +345,10 @@
- ok = false; /* error */
+- Dmsg1(300, "set_find_options ff=%p\n", jcr->ff);
+ set_find_options((FF_PKT *)jcr->ff, jcr->incremental, jcr->mtime);
+- Dmsg0(300, "start find files\n");
+
++ /* in accurate mode, we overwrite the find_one check function */
++ if (jcr->accurate) {
++ set_find_changed_function((FF_PKT *)jcr->ff, accurate_check_file);
++ }
++
+ start_heartbeat_monitor(jcr);
+
+ jcr->acl_text = get_pool_memory(PM_MESSAGE);
+@@ -135,6 +362,8 @@
set_jcr_job_status(jcr, JS_ErrorTerminated);
}
-+ Dmsg1(1, "jcr->accurate == %i\n", jcr->accurate);
+ accurate_send_deleted_list(jcr); /* send deleted list to SD */
+
free_pool_memory(jcr->acl_text);
stop_heartbeat_monitor(jcr);
-@@ -354,9 +568,19 @@
- }
- case FT_DIRNOCHG:
- case FT_NOCHG:
-+ /* TODO: in accurate mode, we have to change NOCHG attribute to FT_REG... */
-+// if (!accurate_check_file(jcr, ff_pkt, false)) {
-+// Jmsg(jcr, M_SKIPPED, 1, _(" Unchanged file skipped: %s\n"), ff_pkt->fname);
-+// return 1;
-+// }
- Jmsg(jcr, M_SKIPPED, 1, _(" Unchanged file skipped: %s\n"), ff_pkt->fname);
- return 1;
- case FT_ISARCH:
-+ /* TODO: in accurate mode, we have to change NOCHG attribute to FT_REG... */
-+// if (!accurate_check_file(jcr, ff_pkt, false)) {
-+// Jmsg(jcr, M_NOTSAVED, 0, _(" Archive file not saved: %s\n"), ff_pkt->fname);
-+// return 1;
-+// }
- Jmsg(jcr, M_NOTSAVED, 0, _(" Archive file not saved: %s\n"), ff_pkt->fname);
- return 1;
- case FT_NOOPEN: {
-@@ -1118,6 +1342,9 @@
- }
- unstrip_path(ff_pkt);
-
-+ /* list backuped files */
-+ accurate_check_file(jcr, ff_pkt, true);
-+
- Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg);
- if (!stat) {
- Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
-@@ -1128,6 +1355,58 @@
+@@ -1128,6 +1357,57 @@
return true;
}
+ * For a directory, link is the same as fname, but with trailing
+ * slash. For a linked file, link is the link.
+ */
-+ stat = sd->fsend("%ld %d %s%c%s%c%s%c%s%c",
-+ 0 /* FileIndex */,
-+ FT_NOSTAT /* FileType */,
-+ fname /* FileName */,
-+ 0, attribs, 0, 0, 0, attribsEx, 0);
++ stat = sd->fsend("0 %d %s%c%s%c%s%c%s%c",
++ FT_NOSTAT /* FileType */,
++ fname /* FileName */,
++ 0, attribs, 0, 0, 0, attribsEx, 0);
+
+ Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg);
+ if (!stat) {
*/
Index: src/filed/job.c
===================================================================
---- src/filed/job.c (révision 6443)
+--- src/filed/job.c (révision 6467)
+++ src/filed/job.c (copie de travail)
@@ -49,6 +49,7 @@
/* Imported functions */
extern int status_cmd(JCR *jcr);
extern int qstatus_cmd(JCR *jcr);
-+extern int accurate_get_current_file_list_cmd(JCR *jcr);
++extern int accurate_cmd(JCR *jcr);
/* Forward referenced functions */
static int backup_cmd(JCR *jcr);
{"RunBeforeJob", runbefore_cmd, 0},
{"RunAfterJob", runafter_cmd, 0},
{"Run", runscript_cmd, 0},
-+ {"accurate", accurate_get_current_file_list_cmd, 0},
++ {"accurate", accurate_cmd, 0},
{NULL, NULL} /* list terminator */
};
-@@ -1087,6 +1089,9 @@
- case 'c':
- fo->flags |= FO_CHKCHANGES;
- break;
-+ case 'C':
-+ fo->flags |= FO_ACCURATE;
-+ break;
- default:
- Emsg1(M_ERROR, 0, _("Unknown include/exclude option: %c\n"), *p);
- break;
-@@ -1195,6 +1200,9 @@
+@@ -1195,6 +1197,9 @@
level = get_memory(dir->msglen+1);
Dmsg1(110, "level_cmd: %s", dir->msg);
if (sscanf(dir->msg, "level = %s ", level) != 1) {
goto bail_out;
}
-@@ -1204,14 +1212,14 @@
+@@ -1204,14 +1209,14 @@
/* Full backup requested? */
} else if (strcmp(level, "full") == 0) {
jcr->JobLevel = L_FULL;
* to agree with our clock.
Index: src/filed/restore.c
===================================================================
---- src/filed/restore.c (révision 6443)
+--- src/filed/restore.c (révision 6467)
+++ src/filed/restore.c (copie de travail)
@@ -320,6 +320,11 @@
bclose(&rctx.bfd);
/*
* Unpack attributes and do sanity check them
*/
+Index: src/cats/protos.h
+===================================================================
+--- src/cats/protos.h (révision 6467)
++++ src/cats/protos.h (copie de travail)
+@@ -102,6 +102,9 @@
+ int db_get_client_record(JCR *jcr, B_DB *mdb, CLIENT_DBR *cdbr);
+ int db_get_counter_record(JCR *jcr, B_DB *mdb, COUNTER_DBR *cr);
+ bool db_get_query_dbids(JCR *jcr, B_DB *mdb, POOL_MEM &query, dbid_list &ids);
++bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids, DB_RESULT_HANDLER *result_handler, void *ctx);
++bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb, JOB_DBR *jr, POOLMEM *jobids);
++int db_get_int_handler(void *ctx, int num_fields, char **row);
+
+
+ /* sql_list.c */
+Index: src/cats/sql_get.c
+===================================================================
+--- src/cats/sql_get.c (révision 6467)
++++ src/cats/sql_get.c (copie de travail)
+@@ -898,8 +898,6 @@
+ return ok;
+ }
+
+-
+-
+ /* Get Media Record
+ *
+ * Returns: false: on failure
+@@ -1018,5 +1016,141 @@
+ return ok;
+ }
+
++/*
++ * Find the last "accurate" backup state (that can take deleted files in account)
++ * 1) Get all files with jobid in list (F subquery)
++ * 2) Take only the last version of each file (Temp subquery) => accurate list is ok
++ * 3) Join the result to file table to get fileindex, jobid and lstat information
++ *
++ * TODO: On postgresql, this is done with
++SELECT DISTINCT ON (PathId, FilenameId) FileIndex, Path, Name, LStat
++ FROM File JOIN Filename USING (FilenameId) JOIN Path USING (PathId) WHERE JobId IN (40341)
++ ORDER BY PathId, FilenameId, JobId DESC
++ */
++bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids,
++ DB_RESULT_HANDLER *result_handler, void *ctx)
++{
++ if (!*jobids) {
++ db_lock(mdb);
++ Mmsg(mdb->errmsg, _("ERR=JobIds are empty\n"));
++ db_unlock(mdb);
++ return false;
++ }
+
++ POOL_MEM buf (PM_MESSAGE);
++
++ Mmsg(buf,
++ "SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId, File.LStat "
++ "FROM ( "
++ "SELECT max(FileId) as FileId, PathId, FilenameId "
++ "FROM (SELECT FileId, PathId, FilenameId FROM File WHERE JobId IN (%s)) AS F "
++ "GROUP BY PathId, FilenameId "
++ ") AS Temp "
++ "JOIN Filename ON (Filename.FilenameId = Temp.FilenameId) "
++ "JOIN Path ON (Path.PathId = Temp.PathId) "
++ "JOIN File ON (File.FileId = Temp.FileId) "
++ "WHERE File.FileIndex > 0 ",
++ jobids);
++
++ return db_sql_query(mdb, buf.c_str(), result_handler, ctx);
++}
++
++
++/* Full : do nothing
++ * Differential : get the last full id
++ * Incremental : get the last full + last diff + last incr(s) ids
++ *
++ * TODO: look and merge from ua_restore.c
++ */
++bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb,
++ JOB_DBR *jr, POOLMEM *jobids)
++{
++ char clientid[50], jobid[50], filesetid[50];
++ char date[MAX_TIME_LENGTH];
++
++ POOL_MEM query (PM_FNAME);
++ bstrutime(date, sizeof(date), time(NULL) + 1);
++ jobids[0]='\0';
++
++ /* First, find the last good Full backup for this job/client/fileset */
++ Mmsg(query,
++"CREATE TEMPORARY TABLE btemp3%s AS "
++ "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
++ "FROM Job JOIN FileSet USING (FileSetId) "
++ "WHERE ClientId = %s "
++ "AND Level='F' AND JobStatus='T' AND Type='B' "
++ "AND StartTime<'%s' "
++ "AND FileSet.FileSet=(SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
++ "ORDER BY Job.JobTDate DESC LIMIT 1",
++ edit_uint64(jcr->JobId, jobid),
++ edit_uint64(jr->ClientId, clientid),
++ date,
++ edit_uint64(jr->FileSetId, filesetid));
++
++ if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
++ return false;
++ }
++
++ if (jr->JobLevel == L_INCREMENTAL) {
++
++ /* Now, find the last differential backup after the last full */
++ Mmsg(query,
++"INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
++ "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
++ "FROM Job JOIN FileSet USING (FileSetId) "
++ "WHERE ClientId = %s "
++ "AND Level='D' AND JobStatus='T' AND Type='B' "
++ "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
++ "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
++ "ORDER BY Job.JobTDate DESC LIMIT 1 ",
++ jobid,
++ clientid,
++ jobid,
++ filesetid);
++
++ db_sql_query(mdb, query.c_str(), NULL, NULL);
++
++ /* We just have to take all incremental after the last Full/Diff */
++ Mmsg(query,
++"INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
++ "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
++ "FROM Job JOIN FileSet USING (FileSetId) "
++ "WHERE ClientId = %s "
++ "AND Level='I' AND JobStatus='T' AND Type='B' "
++ "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
++ "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
++ "ORDER BY Job.JobTDate DESC ",
++ jobid,
++ clientid,
++ jobid,
++ filesetid);
++ db_sql_query(mdb, query.c_str(), NULL, NULL);
++ }
++
++ /* build a jobid list ie: 1,2,3,4 */
++ Mmsg(query, "SELECT JobId FROM btemp3%s", jobid);
++ db_sql_query(mdb, query.c_str(), db_get_int_handler, jobids);
++ Dmsg1(1, "db_accurate_get_jobids=%s\n", jobids);
++
++ Mmsg(query, "DROP TABLE btemp3%s", jobid);
++ db_sql_query(mdb, query.c_str(), NULL, NULL);
++
++ return true;
++}
++
++/*
++ * Use to build a string of int list from a query. "10,20,30"
++ */
++int db_get_int_handler(void *ctx, int num_fields, char **row)
++{
++ POOLMEM *ret = (POOLMEM *)ctx;
++ if (num_fields == 1) {
++ if (ret[0]) {
++ pm_strcat(ret, ",");
++ }
++ pm_strcat(ret, row[0]);
++ }
++ return 0;
++}
++
+ #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL || HAVE_DBI */
Index: src/stored/bextract.c
===================================================================
---- src/stored/bextract.c (révision 6443)
+--- src/stored/bextract.c (révision 6467)
+++ src/stored/bextract.c (copie de travail)
@@ -324,6 +324,14 @@
Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
+ /* handle deleted file
+ */
+ if (rec->FileIndex == 0) {
-+ /* if file is included, remove it ? */
-+ Jmsg(jcr, M_INFO, 0, _("fname=%s is marked as deleted.\n", attr->fname));
-+ break;
++ /* if file is included, remove it ? */
++ Jmsg(jcr, M_INFO, 0, _("fname=%s is marked as deleted.\n"), attr->fname);
++ break;
+ }
+
if (attr->file_index != rec->FileIndex) {
rec->FileIndex, attr->file_index);
Index: src/stored/bscan.c
===================================================================
---- src/stored/bscan.c (révision 6443)
+--- src/stored/bscan.c (révision 6467)
+++ src/stored/bscan.c (copie de travail)
-@@ -648,6 +648,15 @@
+@@ -660,6 +660,15 @@
case STREAM_UNIX_ATTRIBUTES:
case STREAM_UNIX_ATTRIBUTES_EX:
+ /* handle deleted file
+ */
+ if (rec->FileIndex == 0) {
-+ create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
-+ FT_NOSTAT, "", rec);
-+ free_jcr(mjcr);
-+ break;
++ create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
++ FT_NOSTAT, "", rec);
++ free_jcr(mjcr);
++ break;
+ }
+
if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
}
Index: src/stored/append.c
===================================================================
---- src/stored/append.c (révision 6443)
+--- src/stored/append.c (révision 6467)
+++ src/stored/append.c (copie de travail)
-@@ -146,7 +146,7 @@
+@@ -146,7 +146,8 @@
/* Read Stream header from the File daemon.
* The stream header consists of the following:
- * file_index (sequential Bacula file index, base 1)
-+ * file_index (sequential Bacula file index, base 1, 0 for deleted files)
++ * file_index (sequential Bacula file index, base 1,
++ * 0 for deleted files)
* stream (Bacula number to distinguish parts of data)
* info (Info for Storage daemon -- compressed, encryped, ...)
* info is not currently used, so is read, but ignored!
-@@ -185,16 +185,18 @@
+@@ -185,16 +186,21 @@
Dmsg2(890, "<filed: Header FilInx=%d stream=%d\n", file_index, stream);
- Jmsg0(jcr, M_FATAL, 0, _("File index from FD not positive or sequential\n"));
- ok = false;
- break;
-+ if (file_index != 0) { /* TODO: handle file_index == 0 */
-+ if (!(file_index > 0 && (file_index == last_file_index ||
-+ file_index == last_file_index + 1))) {
-+ Jmsg0(jcr, M_FATAL, 0, _("File index from FD not positive or sequential\n"));
-+ ok = false;
-+ break;
-+ }
-+ if (file_index != last_file_index) {
-+ jcr->JobFiles = file_index;
-+ last_file_index = file_index;
-+ }
++ /*
++ * In accurate mode, files with file_index == 0 are marked as deleted
++ */
++ if (!file_index) {
++ if (!(file_index > 0 && (file_index == last_file_index ||
++ file_index == last_file_index + 1))) {
++ Jmsg0(jcr, M_FATAL, 0, _("File index from FD not positive or sequential\n"));
++ ok = false;
++ break;
++ }
++ if (file_index != last_file_index) {
++ jcr->JobFiles = file_index;
++ last_file_index = file_index;
++ }
}
- if (file_index != last_file_index) {
- jcr->JobFiles = file_index;
/* Read data stream from the File daemon.
* The data stream is just raw bytes
-@@ -212,25 +214,26 @@
- stream_to_ascii(buf1, rec.Stream,rec.FileIndex),
- rec.data_len);
+@@ -214,22 +220,23 @@
-- while (!write_record_to_block(dcr->block, &rec)) {
-- Dmsg2(850, "!write_record_to_block data_len=%d rem=%d\n", rec.data_len,
+ while (!write_record_to_block(dcr->block, &rec)) {
+ Dmsg2(850, "!write_record_to_block data_len=%d rem=%d\n", rec.data_len,
- rec.remainder);
-- if (!write_block_to_device(dcr)) {
-- Dmsg2(90, "Got write_block_to_dev error on device %s. %s\n",
++ rec.remainder);
+ if (!write_block_to_device(dcr)) {
+ Dmsg2(90, "Got write_block_to_dev error on device %s. %s\n",
- dev->print_name(), dev->bstrerror());
-- ok = false;
-- break;
-- }
-- }
++ dev->print_name(), dev->bstrerror());
+ ok = false;
+ break;
+ }
++
++ if (!ok) {
++ Dmsg0(400, "Not OK\n");
++ break;
++ }
++ jcr->JobBytes += rec.data_len; /* increment bytes this job */
++ Dmsg4(850, "write_record FI=%s SessId=%d Strm=%s len=%d\n",
++ FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId,
++ stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len);
+ }
- if (!ok) {
- Dmsg0(400, "Not OK\n");
- break;
- Dmsg4(850, "write_record FI=%s SessId=%d Strm=%s len=%d\n",
- FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId,
- stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len);
-+ while (!write_record_to_block(dcr->block, &rec)) {
-+ Dmsg2(850, "!write_record_to_block data_len=%d rem=%d\n", rec.data_len,
-+ rec.remainder);
-+ if (!write_block_to_device(dcr)) {
-+ Dmsg2(90, "Got write_block_to_dev error on device %s. %s\n",
-+ dev->print_name(), dev->bstrerror());
-+ ok = false;
-+ break;
-+ }
-
-+ if (!ok) {
-+ Dmsg0(400, "Not OK\n");
-+ break;
-+ }
-+ jcr->JobBytes += rec.data_len; /* increment bytes this job */
-+ Dmsg4(850, "write_record FI=%s SessId=%d Strm=%s len=%d\n",
-+ FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId,
-+ stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len);
-+ }
-+
+
/* Send attributes and digest to Director for Catalog */
if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX ||
- crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
Index: src/jcr.h
===================================================================
---- src/jcr.h (révision 6443)
+--- src/jcr.h (révision 6467)
+++ src/jcr.h (copie de travail)
@@ -119,6 +119,7 @@
#endif /* FILE_DAEMON */
+Index: src/lib/Makefile.in
+===================================================================
+--- src/lib/Makefile.in (révision 6467)
++++ src/lib/Makefile.in (copie de travail)
+@@ -29,7 +29,7 @@
+ res.c rwlock.c scan.c serial.c sha1.c \
+ signal.c smartall.c rblist.c tls.c tree.c \
+ util.c var.c watchdog.c workq.c btimers.c \
+- address_conf.c pythonlib.c breg.c
++ address_conf.c pythonlib.c breg.c htable.c
+
+
+ LIBOBJS = attr.o base64.o berrno.o bsys.o bget_msg.o \
+@@ -42,7 +42,7 @@
+ res.o rwlock.o scan.o serial.o sha1.o \
+ signal.o smartall.o rblist.o tls.o tree.o \
+ util.o var.o watchdog.o workq.o btimers.o \
+- address_conf.o pythonlib.o breg.o
++ address_conf.o pythonlib.o breg.o htable.o
+
+
+ EXTRAOBJS = @OBJLIST@
+Index: src/findlib/find.c
+===================================================================
+--- src/findlib/find.c (révision 6467)
++++ src/findlib/find.c (copie de travail)
+@@ -96,6 +96,13 @@
+ Dmsg0(100, "Leave set_find_options()\n");
+ }
+
++void
++set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff))
++{
++ Dmsg0(100, "Enter set_find_changed_function()\n");
++ ff->check_fct = check_fct;
++}
++
+ /*
+ * For VSS we need to know which windows drives
+ * are used, because we create a snapshot of all used
+Index: src/findlib/find_one.c
+===================================================================
+--- src/findlib/find_one.c (révision 6467)
++++ src/findlib/find_one.c (copie de travail)
+@@ -258,6 +258,33 @@
+ }
+
+ /*
++ * In incremental/diffential or accurate backup, we
++ * say if the current file has changed.
++ */
++static bool check_changes(JCR *jcr, FF_PKT *ff_pkt)
++{
++ /* in special mode (like accurate backup), user can
++ * choose his comparison function.
++ */
++ if (ff_pkt->check_fct) {
++ return ff_pkt->check_fct(jcr, ff_pkt);
++ }
++
++ /* in normal modes (incr/diff), we use this default
++ * behaviour
++ */
++ if (ff_pkt->incremental &&
++ (ff_pkt->statp.st_mtime < ff_pkt->save_time &&
++ ((ff_pkt->flags & FO_MTIMEONLY) ||
++ ff_pkt->statp.st_ctime < ff_pkt->save_time)))
++ {
++ return true;
++ } else {
++ return false;
++ }
++}
++
++/*
+ * Find a single file.
+ * handle_file is the callback for handling the file.
+ * p is the filename
+@@ -333,16 +360,13 @@
+ * since our last "save_time", presumably the last Full save
+ * or Incremental.
+ */
+- if (ff_pkt->incremental && !S_ISDIR(ff_pkt->statp.st_mode)) {
++ if ( ff_pkt->incremental
++ && !S_ISDIR(ff_pkt->statp.st_mode)
++ && !check_changes(jcr, ff_pkt))
++ {
+ Dmsg1(300, "Non-directory incremental: %s\n", ff_pkt->fname);
+- /* Not a directory */
+- if (ff_pkt->statp.st_mtime < ff_pkt->save_time
+- && ((ff_pkt->flags & FO_MTIMEONLY) ||
+- ff_pkt->statp.st_ctime < ff_pkt->save_time)) {
+- /* Incremental option, file not changed */
+- ff_pkt->type = FT_NOCHG;
+- return handle_file(jcr, ff_pkt, top_level);
+- }
++ ff_pkt->type = FT_NOCHG;
++ return handle_file(jcr, ff_pkt, top_level);
+ }
+
+ #ifdef HAVE_DARWIN_OS
+@@ -502,15 +526,13 @@
+ link[len] = 0;
+
+ ff_pkt->link = link;
+- if (ff_pkt->incremental &&
+- (ff_pkt->statp.st_mtime < ff_pkt->save_time &&
+- ((ff_pkt->flags & FO_MTIMEONLY) ||
+- ff_pkt->statp.st_ctime < ff_pkt->save_time))) {
++ if (ff_pkt->incremental && !check_changes(jcr, ff_pkt)) {
+ /* Incremental option, directory entry not changed */
+ ff_pkt->type = FT_DIRNOCHG;
+ } else {
+ ff_pkt->type = FT_DIRBEGIN;
+ }
++
+ /* We have set st_rdev to 1 if it is a reparse point, otherwise 0 */
+ if (have_win32_api() && ff_pkt->statp.st_rdev) {
+ ff_pkt->type = FT_REPARSE;
Index: src/findlib/find.h
===================================================================
---- src/findlib/find.h (révision 6443)
+--- src/findlib/find.h (révision 6467)
+++ src/findlib/find.h (copie de travail)
-@@ -108,6 +108,7 @@
- #define FO_ENHANCEDWILD (1<<23) /* Enhanced wild card processing */
- #define FO_CHKCHANGES (1<<24) /* Check if file have been modified during backup */
- #define FO_STRIPPATH (1<<25) /* Check for stripping path */
-+#define FO_ACCURATE (1<<26) /* Accurate mode */
-
- struct s_included_file {
- struct s_included_file *next;
+@@ -215,6 +215,7 @@
+ findFILESET *fileset;
+ int (*file_save)(JCR *, FF_PKT *, bool); /* User's callback */
+ int (*plugin_save)(JCR *, FF_PKT *, bool); /* User's callback */
++ bool (*check_fct)(JCR *, FF_PKT *); /* optionnal user fct to check file changes */
+
+ /* Values set by accept_file while processing Options */
+ uint32_t flags; /* backup options */
+Index: src/findlib/protos.h
+===================================================================
+--- src/findlib/protos.h (révision 6467)
++++ src/findlib/protos.h (copie de travail)
+@@ -45,6 +45,7 @@
+ /* From find.c */
+ FF_PKT *init_find_files();
+ void set_find_options(FF_PKT *ff, int incremental, time_t mtime);
++void set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff));
+ int find_files(JCR *jcr, FF_PKT *ff, int file_sub(JCR *, FF_PKT *ff_pkt, bool),
+ int plugin_sub(JCR *, FF_PKT *ff_pkt, bool));
+ int match_files(JCR *jcr, FF_PKT *ff, int sub(JCR *, FF_PKT *ff_pkt, bool));