]> git.sur5r.net Git - bacula/bacula/blob - bacula/patches/testing/project-accurate-backup.patch2
ebl Use old LStat fields for deleted files (more simple)
[bacula/bacula] / bacula / patches / testing / project-accurate-backup.patch2
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)
5 @@ -50,7 +50,7 @@
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";
13  
14 @@ -226,13 +226,12 @@
15     char ed1[50];
16  
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);
22     }
23  }
24  
25 -
26  /*
27   * Send level command to FD.
28   * Used for backup jobs and estimate command.
29 @@ -240,24 +239,26 @@
30  bool send_level_command(JCR *jcr)
31  {
32     BSOCK   *fd = jcr->file_bsock;
33 +   const char *accurate=jcr->job->accurate?"accurate_":"";
34 +   const char *not_accurate="";
35     /*
36      * Send Level command to File daemon
37      */
38     switch (jcr->JobLevel) {
39     case L_BASE:
40 -      fd->fsend(levelcmd, "base", " ", 0);
41 +      fd->fsend(levelcmd, not_accurate, "base", " ", 0);
42        break;
43     /* L_NONE is the console, sending something off to the FD */
44     case L_NONE:
45     case L_FULL:
46 -      fd->fsend(levelcmd, "full", " ", 0);
47 +      fd->fsend(levelcmd, not_accurate, "full", " ", 0);
48        break;
49     case L_DIFFERENTIAL:
50 -      fd->fsend(levelcmd, "differential", " ", 0);
51 +      fd->fsend(levelcmd, accurate, "differential", " ", 0);
52        send_since_time(jcr);
53        break;
54     case L_INCREMENTAL:
55 -      fd->fsend(levelcmd, "incremental", " ", 0);
56 +      fd->fsend(levelcmd, accurate, "incremental", " ", 0);
57        send_since_time(jcr);
58        break;
59     case L_SINCE:
60 Index: src/dird/backup.c
61 ===================================================================
62 --- src/dird/backup.c   (révision 6471)
63 +++ src/dird/backup.c   (copie de travail)
64 @@ -97,6 +97,65 @@
65  }
66  
67  /*
68 + * Foreach files in currrent list, send "/path/fname\0LStat" to FD
69 + */
70 +static int accurate_list_handler(void *ctx, int num_fields, char **row)
71 +{
72 +   JCR *jcr = (JCR *)ctx;
73 +
74 +   if (job_canceled(jcr)) {
75 +      return 1;
76 +   }
77 +   
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]); 
80 +   }
81 +   return 0;
82 +}
83 +
84 +/*
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
89 + *    ...
90 + *    DIR -> FD : EOD
91 + */
92 +bool send_accurate_current_files(JCR *jcr)
93 +{
94 +   POOL_MEM buf;
95 +
96 +   if (jcr->accurate==false || job_canceled(jcr) || jcr->JobLevel==L_FULL) {
97 +      return true;
98 +   }
99 +   POOLMEM *jobids = get_pool_memory(PM_FNAME);
100 +   db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, jobids);
101 +
102 +   if (*jobids == 0) {
103 +      free_pool_memory(jobids);
104 +      Jmsg(jcr, M_FATAL, 0, _("Cannot find previous jobids.\n"));
105 +      return false;
106 +   }
107 +   Jmsg(jcr, M_INFO, 0, _("Sending Accurate information.\n"));
108 +
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); 
114 +
115 +   db_get_file_list(jcr, jcr->db, jobids, accurate_list_handler, (void *)jcr);
116 +
117 +   free_pool_memory(jobids);
118 +   free_pool_memory(nb);
119 +
120 +   jcr->file_bsock->signal(BNET_EOD);
121 +   /* TODO: use response() ? */
122 +
123 +   return true;
124 +}
125 +
126 +/*
127   * Do a backup of the specified FileSet
128   *
129   *  Returns:  false on failure
130 @@ -225,6 +284,14 @@
131        Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
132     }
133  
134 +   /*
135 +    * If backup is in accurate mode, we send the list of
136 +    * all files to FD.
137 +    */
138 +   if (!send_accurate_current_files(jcr)) {
139 +      goto bail_out;
140 +   }
141 +
142     /* Send backup command */
143     fd->fsend(backupcmd);
144     if (!response(jcr, fd, OKbackup, "backup", DISPLAY_ERROR)) {
145 @@ -475,6 +542,7 @@
146  "  Software Compression:   %s\n"
147  "  VSS:                    %s\n"
148  "  Encryption:             %s\n"
149 +"  Accurate:               %s\n"
150  "  Volume name(s):         %s\n"
151  "  Volume Session Id:      %d\n"
152  "  Volume Session Time:    %d\n"
153 @@ -506,8 +574,9 @@
154          edit_uint64_with_suffix(jcr->SDJobBytes, ec6),
155          kbps,
156          compress,
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"),
162          jcr->VolumeName,
163          jcr->VolSessionId,
164          jcr->VolSessionTime,
165 Index: src/dird/catreq.c
166 ===================================================================
167 --- src/dird/catreq.c   (révision 6471)
168 +++ src/dird/catreq.c   (copie de travail)
169 @@ -346,8 +346,8 @@
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.
177   */
178  void catalog_update(JCR *jcr, BSOCK *bs)
179  {
180 @@ -357,6 +357,7 @@
181     uint32_t FileIndex;
182     uint32_t data_len;
183     char *p;
184 +   int filetype;
185     int len;
186     char *fname, *attr;
187     ATTR_DBR *ar = NULL;
188 @@ -398,8 +399,8 @@
189     unser_uint32(data_len);
190     p += unser_length(p);
191  
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);
197  
198     if (Stream == STREAM_UNIX_ATTRIBUTES || Stream == STREAM_UNIX_ATTRIBUTES_EX) {
199 @@ -415,6 +416,7 @@
200        p = jcr->attr - bs->msg + p;    /* point p into jcr->attr */
201        skip_nonspaces(&p);             /* skip FileIndex */
202        skip_spaces(&p);
203 +      filetype = str_to_int32(p);     /* TODO: choose between unserialize and str_to_int32 */
204        skip_nonspaces(&p);             /* skip FileType */
205        skip_spaces(&p);
206        fname = p;
207 @@ -425,7 +427,11 @@
208        Dmsg1(400, "dird<stored: attr=%s\n", attr);
209        ar->attr = attr;
210        ar->fname = fname;
211 -      ar->FileIndex = FileIndex;
212 +      if (filetype == FT_DELETED) {
213 +         ar->FileIndex = 0;     /* special value */
214 +      } else {
215 +         ar->FileIndex = FileIndex;
216 +      }
217        ar->Stream = Stream;
218        ar->link = NULL;
219        if (jcr->mig_jcr) {
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.
227      */
228 -   int items = 0;
229     p = rx->JobIds;
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 */
234        }
235     }
236 -   for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
237 -      char ed1[50];
238  
239 -      if (JobId == last_JobId) {
240 -         continue;                    /* eliminate duplicate JobIds */
241 -      }
242 -      last_JobId = JobId;
243 -      ua->info_msg(_("\nBuilding directory tree for JobId %s ...  "), 
244 -         edit_int64(JobId, ed1));
245 -      items++;
246 -      /*
247 -       * Find files for this JobId and insert them in the tree
248 -       */
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));
252 -      }
253 +   ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ...  "),
254 +                rx->JobIds);
255 +
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));
258     }
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 @@
262        }
263     } else {
264        char ec1[50];
265 -      if (items==1) {
266 -         if (tree.all) {
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));
269 -         }
270 -         else {
271 -            ua->info_msg(_("\n1 Job, %s files inserted into the tree.\n"),
272 -              edit_uint64_with_commas(tree.FileCount, ec1));
273 -         }
274 +      if (tree.all) {
275 +         ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
276 +                      edit_uint64_with_commas(tree.FileCount, ec1));
277 +      } else {
278 +         ua->info_msg(_("\n%s files inserted into the tree.\n"),
279 +                      edit_uint64_with_commas(tree.FileCount, ec1));
280        }
281 -      else {
282 -         if (tree.all) {
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));
285 -         }
286 -         else {
287 -            ua->info_msg(_("\n%d Jobs, %s files inserted into the tree.\n"),
288 -              items, edit_uint64_with_commas(tree.FileCount, ec1));
289 -         }
290 -      }
291  
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)
298 @@ -96,6 +96,7 @@
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},
306 @@ -137,6 +138,7 @@
307     INC_KW_DIGEST,
308     INC_KW_ENCRYPTION,
309     INC_KW_VERIFY,
310 +   INC_KW_ACCURATE,
311     INC_KW_ONEFS,
312     INC_KW_RECURSE,
313     INC_KW_SPARSE,
314 @@ -167,6 +169,7 @@
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},
322 @@ -278,6 +281,12 @@
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)
339 @@ -37,6 +37,7 @@
340  
341  #include "bacula.h"
342  #include "filed.h"
343 +#include "lib/htable.h"
344  
345  /* Forward referenced functions */
346  int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level);
347 @@ -49,7 +50,255 @@
348  static void crypto_session_end(JCR *jcr);
349  static bool crypto_session_send(JCR *jcr, BSOCK *sd);
350  
351 +typedef struct CurFile {
352 +   hlink link;
353 +   char *fname;
354 +   char *lstat;
355 +   bool seen;
356 +} CurFile;
357 +
358 +#define accurate_mark_file_as_seen(elt) ((elt)->seen = 1)
359 +#define accurate_file_has_been_seen(elt) ((elt)->seen)
360 +
361  /*
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. 
365 + * 
366 + */
367 +/* TODO: tweak verify code to use the same function ?? */
368 +bool accurate_check_file(JCR *jcr, FF_PKT *ff_pkt)
369 +{
370 +   char *p;
371 +   int stat=false;
372 +   struct stat statc;                 /* catalog stat */
373 +   char *Opts_Digest;
374 +   char *fname;
375 +   CurFile *elt;
376 +
377 +   int32_t LinkFIc;
378 +
379 +   if (*ff_pkt->VerifyOpts) {  /* use mtime + ctime checks by default */
380 +      Opts_Digest = ff_pkt->VerifyOpts;
381 +   } else {
382 +      Opts_Digest = "cm"; 
383 +   }
384 +
385 +   if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
386 +      return true;
387 +   }
388 +
389 +   strip_path(ff_pkt);
390 +   
391 +   if (S_ISDIR(ff_pkt->statp.st_mode)) {
392 +      fname = ff_pkt->link;
393 +   } else {
394 +      fname = ff_pkt->fname;
395 +   } 
396 +
397 +   elt = (CurFile *) jcr->file_list->lookup(fname);
398 +
399 +   if (!elt) {
400 +      Dmsg1(500, "accurate %s = yes (not found)\n", fname);
401 +      stat=true;
402 +      goto bail_out;
403 +   }
404 +
405 +   if (accurate_file_has_been_seen(elt)) {
406 +      Dmsg1(500, "accurate %s = no (already seen)\n", fname);
407 +      stat=false;
408 +      goto bail_out;
409 +   }
410 +
411 +   decode_stat(elt->lstat, &statc, &LinkFIc); /* decode catalog stat */
412 +//   *do_Digest = CRYPTO_DIGEST_NONE;
413 +
414 +   for (p=Opts_Digest; *p; p++) {
415 +      char ed1[30], ed2[30];
416 +      switch (*p) {
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));
422 +            stat = true;
423 +         }
424 +         break;
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);
429 +            stat = true;
430 +         }
431 +         break;
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);
436 +//            stat = true;
437 +//         }
438 +//         break;
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);
443 +            stat = true;
444 +         }
445 +         break;
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);
450 +            stat = true;
451 +         }
452 +         break;
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));
458 +            stat = true;
459 +         }
460 +         break;
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);
464 +//            stat = true;
465 +//         }
466 +//         break;
467 +      case 'm':
468 +         if (statc.st_mtime != ff_pkt->statp.st_mtime) {
469 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_mtime differs\n"), fname);
470 +            stat = true;
471 +         }
472 +         break;
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);
476 +            stat = true;
477 +         }
478 +         break;
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));
484 +            stat = true;
485 +         }
486 +         break;
487 +      case '5':                /* compare MD5 */
488 +         Dmsg1(500, "set Do_MD5 for %s\n", ff_pkt->fname);
489 +//       *do_Digest = CRYPTO_DIGEST_MD5;
490 +         break;
491 +      case '1':                 /* compare SHA1 */
492 +//       *do_Digest = CRYPTO_DIGEST_SHA1;
493 +         break;
494 +      case ':':
495 +      case 'V':
496 +      default:
497 +         break;
498 +      }
499 +   }
500 +   accurate_mark_file_as_seen(elt);
501 +   Dmsg2(500, "accurate %s = %i\n", fname, stat);
502 +
503 +bail_out:
504 +   unstrip_path(ff_pkt);
505 +   return stat;
506 +}
507 +
508 +/* 
509 + * This function doesn't work very well with smartalloc
510 + * TODO: use bigbuffer from htable
511 + */
512 +int accurate_cmd(JCR *jcr)
513 +{
514 +   BSOCK *dir = jcr->dir_bsock;
515 +   int len;
516 +   uint64_t nb;
517 +   CurFile *elt=NULL;
518 +
519 +   if (jcr->accurate==false || job_canceled(jcr) || jcr->JobLevel==L_FULL) {
520 +      return true;
521 +   }
522 +
523 +   if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
524 +      dir->fsend(_("2991 Bad accurate command\n"));
525 +      return false;
526 +   }
527 +
528 +   jcr->file_list = (htable *)malloc(sizeof(htable));
529 +   jcr->file_list->init(elt, &elt->link, nb);
530 +
531 +   /*
532 +    * buffer = sizeof(CurFile) + dirmsg
533 +    * dirmsg = fname + lstat
534 +    */
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);
541 +
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;
548 +        elt->seen=0;
549 +         jcr->file_list->insert(elt->fname, elt); 
550 +         Dmsg2(500, "add fname=%s lstat=%s\n", elt->fname, elt->lstat);
551 +      }
552 +   }
553 +
554 +//   jcr->file_list->stats();
555 +   /* TODO: send a EOM ?
556 +   dir->fsend("2000 OK accurate\n");
557 +    */
558 +   return true;
559 +}
560 +
561 +bool accurate_send_deleted_list(JCR *jcr)
562 +{
563 +   CurFile *elt;
564 +   FF_PKT *ff_pkt;
565 +
566 +   int stream = STREAM_UNIX_ATTRIBUTES;
567 +
568 +   if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
569 +      goto bail_out;
570 +   }
571 +
572 +   if (jcr->file_list == NULL) {
573 +      goto bail_out;
574 +   }
575 +
576 +   ff_pkt = init_find_files();
577 +   ff_pkt->type = FT_DELETED;
578 +
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);
585 +      }
586 +//      Free(elt->fname);
587 +   }
588 +   term_find_files(ff_pkt);
589 +bail_out:
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;
595 +   }
596 +   return true;
597 +}
598 +
599 +/*
600   * Find all the requested files and send them
601   * to the Storage daemon.
602   *
603 @@ -100,7 +349,7 @@
604      */
605     jcr->compress_buf_size = jcr->buf_size + ((jcr->buf_size+999) / 1000) + 30;
606     jcr->compress_buf = get_memory(jcr->compress_buf_size);
607 -
608 +   
609  #ifdef HAVE_LIBZ
610     z_stream *pZlibStream = (z_stream*)malloc(sizeof(z_stream));  
611     if (pZlibStream) {
612 @@ -121,10 +370,13 @@
613        return false;
614     }
615  
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");
619  
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);
623 +   } 
624 +   
625     start_heartbeat_monitor(jcr);
626  
627     jcr->acl_text = get_pool_memory(PM_MESSAGE);
628 @@ -135,6 +387,8 @@
629        set_jcr_job_status(jcr, JS_ErrorTerminated);
630     }
631  
632 +   accurate_send_deleted_list(jcr);              /* send deleted list to SD  */
633 +
634     free_pool_memory(jcr->acl_text);
635  
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"));
639        return false;
640     }
641 +
642     encode_stat(attribs, ff_pkt, data_stream);
643 -
644     /* Now possibly extend the attributes */
645     attr_stream = encode_attribsEx(jcr, attribsEx, ff_pkt);
646  
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.
650      */
651 -   strip_path(ff_pkt);
652 +   if (ff_pkt->type != FT_DELETED) { /* already stripped */
653 +      strip_path(ff_pkt);
654 +   }
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);
661     }
662 -   unstrip_path(ff_pkt);
663 +   if (ff_pkt->type != FT_DELETED) {
664 +      unstrip_path(ff_pkt);
665 +   }
666  
667     Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg);
668     if (!stat) {
669 Index: src/filed/job.c
670 ===================================================================
671 --- src/filed/job.c     (révision 6471)
672 +++ src/filed/job.c     (copie de travail)
673 @@ -49,6 +49,7 @@
674  /* Imported functions */
675  extern int status_cmd(JCR *jcr);
676  extern int qstatus_cmd(JCR *jcr);
677 +extern int accurate_cmd(JCR *jcr);
678  
679  /* Forward referenced functions */
680  static int backup_cmd(JCR *jcr);
681 @@ -106,6 +107,7 @@
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 */
687  };
688  
689 @@ -1057,6 +1059,16 @@
690           }
691           fo->VerifyOpts[j] = 0;
692           break;
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) {
698 +               j++;
699 +            }
700 +         }
701 +         fo->AccurateOpts[j] = 0;
702 +         break;
703        case 'P':                  /* strip path */
704           /* Get integer */
705           p++;                    /* skip P */
706 @@ -1195,6 +1207,9 @@
707  
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;
712 +   }
713     if (sscanf(dir->msg, "level = %s ", level) != 1) {
714        goto bail_out;
715     }
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;
723        free_memory(level);
724        return 1;
725 -   } else if (strcmp(level, "incremental") == 0) {
726 +   } else if (strstr(level, "incremental")) {
727        jcr->JobLevel = L_INCREMENTAL;
728        free_memory(level);
729 -      return 1;   
730 +      return 1;
731     /*
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)
738 @@ -320,6 +320,11 @@
739              bclose(&rctx.bfd);
740           }
741  
742 +        /* TODO: manage deleted files */
743 +        if (rctx.type == FT_DELETED) { /* deleted file */
744 +           continue;
745 +        }
746 +
747           /*
748            * Unpack attributes and do sanity check them
749            */
750 Index: src/cats/protos.h
751 ===================================================================
752 --- src/cats/protos.h   (révision 6471)
753 +++ src/cats/protos.h   (copie de travail)
754 @@ -102,6 +102,9 @@
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);
761  
762  
763  /* sql_list.c */
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)
768 @@ -898,8 +898,6 @@
769     return ok;
770  }
771  
772 -
773 -
774  /* Get Media Record
775   *
776   * Returns: false: on failure
777 @@ -1018,5 +1016,141 @@
778     return ok;
779  }
780  
781 +/*
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
786 + *
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
791 + */
792 +bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids, 
793 +                      DB_RESULT_HANDLER *result_handler, void *ctx)
794 +{
795 +   if (!*jobids) {
796 +      db_lock(mdb);
797 +      Mmsg(mdb->errmsg, _("ERR=JobIds are empty\n"));
798 +      db_unlock(mdb);
799 +      return false;
800 +   }
801  
802 +   POOL_MEM buf (PM_MESSAGE);
803 +   
804 +   Mmsg(buf,
805 + "SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId, File.LStat "
806 + "FROM ( "
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 "
810 +  ") AS Temp "
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 ",
815 +             jobids);
816 +
817 +   return db_sql_query(mdb, buf.c_str(), result_handler, ctx);
818 +}
819 +
820 +
821 +/* Full : do nothing
822 + * Differential : get the last full id
823 + * Incremental : get the last full + last diff + last incr(s) ids
824 + *
825 + * TODO: look and merge from ua_restore.c
826 + */
827 +bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb, 
828 +                            JOB_DBR *jr, POOLMEM *jobids)
829 +{
830 +   char clientid[50], jobid[50], filesetid[50];
831 +   char date[MAX_TIME_LENGTH];
832 +
833 +   POOL_MEM query (PM_FNAME);
834 +   bstrutime(date, sizeof(date),  time(NULL) + 1);
835 +   jobids[0]='\0';
836 +
837 +   /* First, find the last good Full backup for this job/client/fileset */
838 +   Mmsg(query, 
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),
849 +        date,
850 +        edit_uint64(jr->FileSetId, filesetid));
851 +
852 +   if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
853 +      return false;
854 +   }
855 +
856 +   if (jr->JobLevel == L_INCREMENTAL) {
857 +
858 +      /* Now, find the last differential backup after the last full */
859 +      Mmsg(query, 
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 ",
868 +           jobid,
869 +           clientid,
870 +           jobid,
871 +           filesetid);
872 +
873 +      db_sql_query(mdb, query.c_str(), NULL, NULL);
874 +
875 +      /* We just have to take all incremental after the last Full/Diff */
876 +      Mmsg(query, 
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 ",
885 +           jobid,
886 +           clientid,
887 +           jobid,
888 +           filesetid);
889 +      db_sql_query(mdb, query.c_str(), NULL, NULL);
890 +   }
891 +
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);
896 +
897 +   Mmsg(query, "DROP TABLE btemp3%s", jobid);
898 +   db_sql_query(mdb, query.c_str(), NULL, NULL);
899 +
900 +   return true;
901 +}
902 +
903 +/*
904 + * Use to build a string of int list from a query. "10,20,30"
905 + */
906 +int db_get_int_handler(void *ctx, int num_fields, char **row)
907 +{
908 +   POOLMEM *ret = (POOLMEM *)ctx;
909 +   if (num_fields == 1) {
910 +      if (ret[0]) {
911 +         pm_strcat(ret, ",");
912 +      }
913 +      pm_strcat(ret, row[0]);
914 +   }
915 +   return 0;
916 +}
917 +
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)
923 @@ -277,6 +277,7 @@
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 */
928  
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)
935 @@ -343,6 +343,12 @@
936  
937           build_attr_output_fnames(jcr, attr);
938  
939 +        if (attr->type == FT_DELETED) { /* TODO: choose the right fname/ofname */
940 +           Jmsg(jcr, M_INFO, 0, _("%s was deleted.\n"), attr->fname);
941 +           extract = false;
942 +           return true;
943 +        }
944 +
945           extract = false;
946           stat = create_file(jcr, attr, &bfd, REPLACE_ALWAYS);
947           switch (stat) {
948 Index: src/stored/bscan.c
949 ===================================================================
950 --- src/stored/bscan.c  (révision 6471)
951 +++ src/stored/bscan.c  (copie de travail)
952 @@ -845,7 +845,11 @@
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) {
958 +      ar.FileIndex = 0;
959 +   } else {
960 +      ar.FileIndex = rec->FileIndex;
961 +   }
962     ar.attr = ap;
963     if (dcr->VolFirstIndex == 0) {
964        dcr->VolFirstIndex = rec->FileIndex;
965 Index: src/jcr.h
966 ===================================================================
967 --- src/jcr.h   (révision 6471)
968 +++ src/jcr.h   (copie de travail)
969 @@ -119,6 +119,7 @@
970  
971  /* Forward referenced structures */
972  class JCR;
973 +class htable;
974  struct FF_PKT;
975  struct B_DB;
976  struct ATTR_DBR;
977 @@ -318,6 +319,7 @@
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 */
983  
984  
985 Index: src/lib/Makefile.in
986 ===================================================================
987 --- src/lib/Makefile.in (révision 6471)
988 +++ src/lib/Makefile.in (copie de travail)
989 @@ -29,7 +29,7 @@
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
995  
996  
997  LIBOBJS = attr.o base64.o berrno.o bsys.o bget_msg.o \
998 @@ -42,7 +42,7 @@
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
1004  
1005  
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 @@
1012     char *p, *f;
1013     guid_list *guid;
1014  
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);
1020 +      return;
1021 +   }
1022 +
1023     if (!jcr->id_list) {
1024        jcr->id_list = new_guid_list();
1025     }
1026 @@ -268,6 +276,7 @@
1027           *p++ = *f++;
1028        }
1029     }
1030 +
1031     *p++ = '\n';
1032     *p = 0;
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)
1038 @@ -389,6 +389,9 @@
1039           return CF_CREATED;
1040        }
1041  
1042 +   case FT_DELETED:
1043 +      Qmsg2(jcr, M_INFO, 0, _("Original file %s have been deleted: type=%d\n"), attr->fname, attr->type);
1044 +      break;
1045     /* The following should not occur */
1046     case FT_NOACCESS:
1047     case FT_NOFOLLOW:
1048 Index: src/findlib/find.c
1049 ===================================================================
1050 --- src/findlib/find.c  (révision 6471)
1051 +++ src/findlib/find.c  (copie de travail)
1052 @@ -96,6 +96,13 @@
1053    Dmsg0(100, "Leave set_find_options()\n");
1054  }
1055  
1056 +void
1057 +set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff))
1058 +{
1059 +   Dmsg0(1, "Enter set_find_changed_function()\n");
1060 +   ff->check_fct = check_fct;
1061 +}
1062 +
1063  /*
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 @@
1071  }
1072  
1073  /*
1074 + * In incremental/diffential or accurate backup, we
1075 + * say if the current file has changed.
1076 + */
1077 +static bool check_changes(JCR *jcr, FF_PKT *ff_pkt)
1078 +{
1079 +   /* in special mode (like accurate backup), user can 
1080 +    * choose his comparison function.
1081 +    */
1082 +   if (ff_pkt->check_fct) {
1083 +      return ff_pkt->check_fct(jcr, ff_pkt);
1084 +   }
1085 +
1086 +   /* in normal modes (incr/diff), we use this default
1087 +    * behaviour
1088 +    */
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))) 
1093 +   {
1094 +      return false;
1095 +   } 
1096 +
1097 +   return true;
1098 +}
1099 +
1100 +/*
1101   * Find a single file.
1102   * handle_file is the callback for handling the file.
1103   * p is the filename
1104 @@ -333,16 +360,13 @@
1105      * since our last "save_time", presumably the last Full save
1106      * or Incremental.
1107      */
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);
1117 -      }
1118 +   if (   ff_pkt->incremental 
1119 +       && !S_ISDIR(ff_pkt->statp.st_mode) 
1120 +       && !check_changes(jcr, ff_pkt)) 
1121 +   {
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);
1125     }
1126  
1127  #ifdef HAVE_DARWIN_OS
1128 @@ -502,15 +526,13 @@
1129        link[len] = 0;
1130  
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;
1139        } else {
1140           ff_pkt->type = FT_DIRBEGIN;
1141        }
1142 +
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)
1150 @@ -146,6 +146,7 @@
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 */
1158 @@ -215,6 +216,7 @@
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 */
1163  
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)
1170 @@ -45,6 +45,7 @@
1171  /* From find.c */
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));