]> git.sur5r.net Git - bacula/bacula/blob - bacula/patches/testing/project-accurate-backup.patch2
ebl Update strippath and cleanup
[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 @@ -415,6 +416,7 @@
189        p = jcr->attr - bs->msg + p;    /* point p into jcr->attr */
190        skip_nonspaces(&p);             /* skip FileIndex */
191        skip_spaces(&p);
192 +      filetype = str_to_int32(p);     /* TODO: choose between unserialize and str_to_int32 */
193        skip_nonspaces(&p);             /* skip FileType */
194        skip_spaces(&p);
195        fname = p;
196 @@ -425,7 +427,11 @@
197        Dmsg1(400, "dird<stored: attr=%s\n", attr);
198        ar->attr = attr;
199        ar->fname = fname;
200 -      ar->FileIndex = FileIndex;
201 +      if (filetype == FT_DELETED) {
202 +         ar->FileIndex = 0;     /* special value */
203 +      } else {
204 +         ar->FileIndex = FileIndex;
205 +      }
206        ar->Stream = Stream;
207        ar->link = NULL;
208        if (jcr->mig_jcr) {
209 Index: src/dird/ua_restore.c
210 ===================================================================
211 --- src/dird/ua_restore.c       (révision 6471)
212 +++ src/dird/ua_restore.c       (copie de travail)
213 @@ -1005,7 +1005,6 @@
214      * For display purposes, the same JobId, with different volumes may
215      * appear more than once, however, we only insert it once.
216      */
217 -   int items = 0;
218     p = rx->JobIds;
219     tree.FileEstimate = 0;
220     if (get_next_jobid_from_list(&p, &JobId) > 0) {
221 @@ -1020,23 +1019,12 @@
222           tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
223        }
224     }
225 -   for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
226 -      char ed1[50];
227  
228 -      if (JobId == last_JobId) {
229 -         continue;                    /* eliminate duplicate JobIds */
230 -      }
231 -      last_JobId = JobId;
232 -      ua->info_msg(_("\nBuilding directory tree for JobId %s ...  "), 
233 -         edit_int64(JobId, ed1));
234 -      items++;
235 -      /*
236 -       * Find files for this JobId and insert them in the tree
237 -       */
238 -      Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
239 -      if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
240 -         ua->error_msg("%s", db_strerror(ua->db));
241 -      }
242 +   ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ...  "),
243 +                rx->JobIds);
244 +
245 +   if (!db_get_file_list(ua->jcr, ua->db, rx->JobIds, insert_tree_handler, (void *)&tree)) {
246 +      ua->error_msg("%s", db_strerror(ua->db));
247     }
248     if (tree.FileCount == 0) {
249        ua->send_msg(_("\nThere were no files inserted into the tree, so file selection\n"
250 @@ -1055,26 +1043,13 @@
251        }
252     } else {
253        char ec1[50];
254 -      if (items==1) {
255 -         if (tree.all) {
256 -            ua->info_msg(_("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
257 -              edit_uint64_with_commas(tree.FileCount, ec1));
258 -         }
259 -         else {
260 -            ua->info_msg(_("\n1 Job, %s files inserted into the tree.\n"),
261 -              edit_uint64_with_commas(tree.FileCount, ec1));
262 -         }
263 +      if (tree.all) {
264 +         ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
265 +                      edit_uint64_with_commas(tree.FileCount, ec1));
266 +      } else {
267 +         ua->info_msg(_("\n%s files inserted into the tree.\n"),
268 +                      edit_uint64_with_commas(tree.FileCount, ec1));
269        }
270 -      else {
271 -         if (tree.all) {
272 -            ua->info_msg(_("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
273 -              items, edit_uint64_with_commas(tree.FileCount, ec1));
274 -         }
275 -         else {
276 -            ua->info_msg(_("\n%d Jobs, %s files inserted into the tree.\n"),
277 -              items, edit_uint64_with_commas(tree.FileCount, ec1));
278 -         }
279 -      }
280  
281        if (find_arg(ua, NT_("done")) < 0) {
282           /* Let the user interact in selecting which files to restore */
283 Index: src/dird/inc_conf.c
284 ===================================================================
285 --- src/dird/inc_conf.c (révision 6471)
286 +++ src/dird/inc_conf.c (copie de travail)
287 @@ -96,6 +96,7 @@
288  static RES_ITEM options_items[] = {
289     {"compression",     store_opts,    {0},     0, 0, 0},
290     {"signature",       store_opts,    {0},     0, 0, 0},
291 +   {"accurate",        store_opts,    {0},     0, 0, 0},
292     {"verify",          store_opts,    {0},     0, 0, 0},
293     {"onefs",           store_opts,    {0},     0, 0, 0},
294     {"recurse",         store_opts,    {0},     0, 0, 0},
295 @@ -137,6 +138,7 @@
296     INC_KW_DIGEST,
297     INC_KW_ENCRYPTION,
298     INC_KW_VERIFY,
299 +   INC_KW_ACCURATE,
300     INC_KW_ONEFS,
301     INC_KW_RECURSE,
302     INC_KW_SPARSE,
303 @@ -167,6 +169,7 @@
304     {"signature",   INC_KW_DIGEST},
305     {"encryption",  INC_KW_ENCRYPTION},
306     {"verify",      INC_KW_VERIFY},
307 +   {"accurate",    INC_KW_ACCURATE},
308     {"onefs",       INC_KW_ONEFS},
309     {"recurse",     INC_KW_RECURSE},
310     {"sparse",      INC_KW_SPARSE},
311 @@ -278,6 +281,12 @@
312        bstrncat(opts, lc->str, optlen);
313        bstrncat(opts, ":", optlen);         /* terminate it */
314        Dmsg3(900, "Catopts=%s option=%s optlen=%d\n", opts, option,optlen);
315 +   } else if (keyword == INC_KW_ACCURATE) { /* special case */
316 +      /* ***FIXME**** ensure these are in permitted set */
317 +      bstrncat(opts, "C", optlen);         /* indicate Accurate */
318 +      bstrncat(opts, lc->str, optlen);
319 +      bstrncat(opts, ":", optlen);         /* terminate it */
320 +      Dmsg3(900, "Catopts=%s option=%s optlen=%d\n", opts, option,optlen);
321     } else if (keyword == INC_KW_STRIPPATH) { /* another special case */
322        if (!is_an_integer(lc->str)) {
323           scan_err1(lc, _("Expected a strip path positive integer, got:%s:"), lc->str);
324 Index: src/filed/backup.c
325 ===================================================================
326 --- src/filed/backup.c  (révision 6484)
327 +++ src/filed/backup.c  (copie de travail)
328 @@ -37,6 +37,7 @@
329  
330  #include "bacula.h"
331  #include "filed.h"
332 +#include "lib/htable.h"
333  
334  /* Forward referenced functions */
335  int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level);
336 @@ -49,7 +50,255 @@
337  static void crypto_session_end(JCR *jcr);
338  static bool crypto_session_send(JCR *jcr, BSOCK *sd);
339  
340 +typedef struct CurFile {
341 +   hlink link;
342 +   char *fname;
343 +   char *lstat;
344 +   bool seen;
345 +} CurFile;
346 +
347 +#define accurate_mark_file_as_seen(elt) ((elt)->seen = 1)
348 +#define accurate_file_has_been_seen(elt) ((elt)->seen)
349 +
350  /*
351 + * This function is called for each file seen in fileset.
352 + * We check in file_list hash if fname have been backuped
353 + * the last time. After we can compare Lstat field. 
354 + * 
355 + */
356 +/* TODO: tweak verify code to use the same function ?? */
357 +bool accurate_check_file(JCR *jcr, FF_PKT *ff_pkt)
358 +{
359 +   char *p;
360 +   int stat=false;
361 +   struct stat statc;                 /* catalog stat */
362 +   char *Opts_Digest;
363 +   char *fname;
364 +   CurFile *elt;
365 +
366 +   int32_t LinkFIc;
367 +
368 +   if (*ff_pkt->VerifyOpts) {  /* use mtime + ctime checks by default */
369 +      Opts_Digest = ff_pkt->VerifyOpts;
370 +   } else {
371 +      Opts_Digest = "cm"; 
372 +   }
373 +
374 +   if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
375 +      return true;
376 +   }
377 +
378 +   strip_path(ff_pkt);
379
380 +   if (S_ISDIR(ff_pkt->statp.st_mode)) {
381 +      fname = ff_pkt->link;
382 +   } else {
383 +      fname = ff_pkt->fname;
384 +   } 
385 +
386 +   elt = (CurFile *) jcr->file_list->lookup(fname);
387 +
388 +   if (!elt) {
389 +      Dmsg1(500, "accurate %s = yes (not found)\n", fname);
390 +      stat=true;
391 +      goto bail_out;
392 +   }
393 +
394 +   if (accurate_file_has_been_seen(elt)) {
395 +      Dmsg1(500, "accurate %s = no (already seen)\n", fname);
396 +      stat=false;
397 +      goto bail_out;
398 +   }
399 +
400 +   decode_stat(elt->lstat, &statc, &LinkFIc); /* decode catalog stat */
401 +//   *do_Digest = CRYPTO_DIGEST_NONE;
402 +
403 +   for (p=Opts_Digest; *p; p++) {
404 +      char ed1[30], ed2[30];
405 +      switch (*p) {
406 +      case 'i':                /* compare INODEs */
407 +         if (statc.st_ino != ff_pkt->statp.st_ino) {
408 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_ino   differ. Cat: %s File: %s\n"), fname,
409 +                 edit_uint64((uint64_t)statc.st_ino, ed1),
410 +                 edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
411 +            stat = true;
412 +         }
413 +         break;
414 +      case 'p':                /* permissions bits */
415 +         if (statc.st_mode != ff_pkt->statp.st_mode) {
416 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_mode  differ. Cat: %x File: %x\n"), fname,
417 +                 (uint32_t)statc.st_mode, (uint32_t)ff_pkt->statp.st_mode);
418 +            stat = true;
419 +         }
420 +         break;
421 +//      case 'n':                /* number of links */
422 +//         if (statc.st_nlink != ff_pkt->statp.st_nlink) {
423 +//            Jmsg(jcr, M_SAVED, 0, _("%s      st_nlink differ. Cat: %d File: %d\n"), fname,
424 +//                 (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
425 +//            stat = true;
426 +//         }
427 +//         break;
428 +      case 'u':                /* user id */
429 +         if (statc.st_uid != ff_pkt->statp.st_uid) {
430 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_uid   differ. Cat: %u File: %u\n"), fname,
431 +                 (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
432 +            stat = true;
433 +         }
434 +         break;
435 +      case 'g':                /* group id */
436 +         if (statc.st_gid != ff_pkt->statp.st_gid) {
437 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_gid   differ. Cat: %u File: %u\n"), fname,
438 +                 (uint32_t)statc.st_gid, (uint32_t)ff_pkt->statp.st_gid);
439 +            stat = true;
440 +         }
441 +         break;
442 +      case 's':                /* size */
443 +         if (statc.st_size != ff_pkt->statp.st_size) {
444 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_size  differ. Cat: %s File: %s\n"), fname,
445 +                 edit_uint64((uint64_t)statc.st_size, ed1),
446 +                 edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
447 +            stat = true;
448 +         }
449 +         break;
450 +//      case 'a':                /* access time */
451 +//         if (statc.st_atime != ff_pkt->statp.st_atime) {
452 +//            Jmsg(jcr, M_SAVED, 0, _("%s      st_atime differs\n"), fname);
453 +//            stat = true;
454 +//         }
455 +//         break;
456 +      case 'm':
457 +         if (statc.st_mtime != ff_pkt->statp.st_mtime) {
458 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_mtime differs\n"), fname);
459 +            stat = true;
460 +         }
461 +         break;
462 +      case 'c':                /* ctime */
463 +         if (statc.st_ctime != ff_pkt->statp.st_ctime) {
464 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_ctime differs\n"), fname);
465 +            stat = true;
466 +         }
467 +         break;
468 +      case 'd':                /* file size decrease */
469 +         if (statc.st_size > ff_pkt->statp.st_size) {
470 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_size  decrease. Cat: %s File: %s\n"), fname,
471 +                 edit_uint64((uint64_t)statc.st_size, ed1),
472 +                 edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
473 +            stat = true;
474 +         }
475 +         break;
476 +      case '5':                /* compare MD5 */
477 +         Dmsg1(500, "set Do_MD5 for %s\n", ff_pkt->fname);
478 +//       *do_Digest = CRYPTO_DIGEST_MD5;
479 +         break;
480 +      case '1':                 /* compare SHA1 */
481 +//       *do_Digest = CRYPTO_DIGEST_SHA1;
482 +         break;
483 +      case ':':
484 +      case 'V':
485 +      default:
486 +         break;
487 +      }
488 +   }
489 +   accurate_mark_file_as_seen(elt);
490 +   Dmsg2(500, "accurate %s = %i\n", fname, stat);
491 +
492 +bail_out:
493 +   unstrip_path(ff_pkt);
494 +   return stat;
495 +}
496 +
497 +/* 
498 + * This function doesn't work very well with smartalloc
499 + * TODO: use bigbuffer from htable
500 + */
501 +int accurate_cmd(JCR *jcr)
502 +{
503 +   BSOCK *dir = jcr->dir_bsock;
504 +   int len;
505 +   uint64_t nb;
506 +   CurFile *elt=NULL;
507 +
508 +   if (jcr->accurate==false || job_canceled(jcr) || jcr->JobLevel==L_FULL) {
509 +      return true;
510 +   }
511 +
512 +   if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
513 +      dir->fsend(_("2991 Bad accurate command\n"));
514 +      return false;
515 +   }
516 +
517 +   jcr->file_list = (htable *)malloc(sizeof(htable));
518 +   jcr->file_list->init(elt, &elt->link, nb);
519 +
520 +   /*
521 +    * buffer = sizeof(CurFile) + dirmsg
522 +    * dirmsg = fname + lstat
523 +    */
524 +   /* get current files */
525 +   while (dir->recv() >= 0) {
526 +      len = strlen(dir->msg);
527 +      if ((len+1) < dir->msglen) {
528 +//       elt = (CurFile *)malloc(sizeof(CurFile));
529 +//       elt->fname  = (char *) malloc(dir->msglen+1);
530 +
531 +         /* we store CurFile, fname and lstat in the same chunk */
532 +         elt = (CurFile *)malloc(sizeof(CurFile)+dir->msglen+1);
533 +         elt->fname  = (char *) elt+sizeof(CurFile);
534 +         memcpy(elt->fname, dir->msg, dir->msglen);
535 +         elt->fname[dir->msglen]='\0';
536 +         elt->lstat = elt->fname + len + 1;
537 +        elt->seen=0;
538 +         jcr->file_list->insert(elt->fname, elt); 
539 +         Dmsg2(500, "add fname=%s lstat=%s\n", elt->fname, elt->lstat);
540 +      }
541 +   }
542 +
543 +//   jcr->file_list->stats();
544 +   /* TODO: send a EOM ?
545 +   dir->fsend("2000 OK accurate\n");
546 +    */
547 +   return true;
548 +}
549 +
550 +bool accurate_send_deleted_list(JCR *jcr)
551 +{
552 +   CurFile *elt;
553 +   FF_PKT *ff_pkt;
554 +
555 +   int stream = STREAM_UNIX_ATTRIBUTES;
556 +
557 +   if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
558 +      goto bail_out;
559 +   }
560 +
561 +   if (jcr->file_list == NULL) {
562 +      goto bail_out;
563 +   }
564 +
565 +   ff_pkt = init_find_files();
566 +   ff_pkt->type = FT_DELETED;
567 +
568 +   foreach_htable (elt, jcr->file_list) {
569 +      if (!accurate_file_has_been_seen(elt)) { /* already seen */
570 +         Dmsg3(500, "deleted fname=%s lstat=%s seen=%i\n", elt->fname, elt->lstat, elt->seen);
571 +         ff_pkt->fname = elt->fname;
572 +         decode_stat(elt->lstat, &ff_pkt->statp, &ff_pkt->LinkFI); /* decode catalog stat */
573 +         encode_and_send_attributes(jcr, ff_pkt, stream);
574 +      }
575 +//      Free(elt->fname);
576 +   }
577 +   term_find_files(ff_pkt);
578 +bail_out:
579 +   /* TODO: clean htable when this function is not reached ? */
580 +   if (jcr->file_list) {
581 +      jcr->file_list->destroy();
582 +      free(jcr->file_list);
583 +      jcr->file_list = NULL;
584 +   }
585 +   return true;
586 +}
587 +
588 +/*
589   * Find all the requested files and send them
590   * to the Storage daemon.
591   *
592 @@ -100,7 +349,7 @@
593      */
594     jcr->compress_buf_size = jcr->buf_size + ((jcr->buf_size+999) / 1000) + 30;
595     jcr->compress_buf = get_memory(jcr->compress_buf_size);
596 -
597 +   
598  #ifdef HAVE_LIBZ
599     z_stream *pZlibStream = (z_stream*)malloc(sizeof(z_stream));  
600     if (pZlibStream) {
601 @@ -121,10 +370,13 @@
602        return false;
603     }
604  
605 -   Dmsg1(300, "set_find_options ff=%p\n", jcr->ff);
606     set_find_options((FF_PKT *)jcr->ff, jcr->incremental, jcr->mtime);
607 -   Dmsg0(300, "start find files\n");
608  
609 +   /* in accurate mode, we overwrite the find_one check function */
610 +   if (jcr->accurate) {
611 +      set_find_changed_function((FF_PKT *)jcr->ff, accurate_check_file);
612 +   } 
613 +   
614     start_heartbeat_monitor(jcr);
615  
616     jcr->acl_text = get_pool_memory(PM_MESSAGE);
617 @@ -135,6 +387,8 @@
618        set_jcr_job_status(jcr, JS_ErrorTerminated);
619     }
620  
621 +   accurate_send_deleted_list(jcr);              /* send deleted list to SD  */
622 +
623     free_pool_memory(jcr->acl_text);
624  
625     stop_heartbeat_monitor(jcr);
626 @@ -1102,7 +1356,9 @@
627      * For a directory, link is the same as fname, but with trailing
628      * slash. For a linked file, link is the link.
629      */
630 -   strip_path(ff_pkt);
631 +   if (ff_pkt->type != FT_DELETED) { /* already stripped */
632 +      strip_path(ff_pkt);
633 +   }
634     if (ff_pkt->type == FT_LNK || ff_pkt->type == FT_LNKSAVED) {
635        Dmsg2(300, "Link %s to %s\n", ff_pkt->fname, ff_pkt->link);
636        stat = sd->fsend("%ld %d %s%c%s%c%s%c%s%c", jcr->JobFiles,
637 @@ -1116,7 +1372,9 @@
638        stat = sd->fsend("%ld %d %s%c%s%c%c%s%c", jcr->JobFiles,
639                 ff_pkt->type, ff_pkt->fname, 0, attribs, 0, 0, attribsEx, 0);
640     }
641 -   unstrip_path(ff_pkt);
642 +   if (ff_pkt->type != FT_DELETED) {
643 +      unstrip_path(ff_pkt);
644 +   }
645  
646     Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg);
647     if (!stat) {
648 Index: src/filed/job.c
649 ===================================================================
650 --- src/filed/job.c     (révision 6471)
651 +++ src/filed/job.c     (copie de travail)
652 @@ -49,6 +49,7 @@
653  /* Imported functions */
654  extern int status_cmd(JCR *jcr);
655  extern int qstatus_cmd(JCR *jcr);
656 +extern int accurate_cmd(JCR *jcr);
657  
658  /* Forward referenced functions */
659  static int backup_cmd(JCR *jcr);
660 @@ -106,6 +107,7 @@
661     {"RunBeforeJob", runbefore_cmd, 0},
662     {"RunAfterJob",  runafter_cmd,  0},
663     {"Run",          runscript_cmd, 0},
664 +   {"accurate",     accurate_cmd, 0},
665     {NULL,       NULL}                  /* list terminator */
666  };
667  
668 @@ -1057,6 +1059,16 @@
669           }
670           fo->VerifyOpts[j] = 0;
671           break;
672 +      case 'C':                  /* accurate options */
673 +         /* Copy Accurate Options */
674 +         for (j=0; *p && *p != ':'; p++) {
675 +            fo->AccurateOpts[j] = *p;
676 +            if (j < (int)sizeof(fo->AccurateOpts) - 1) {
677 +               j++;
678 +            }
679 +         }
680 +         fo->AccurateOpts[j] = 0;
681 +         break;
682        case 'P':                  /* strip path */
683           /* Get integer */
684           p++;                    /* skip P */
685 @@ -1195,6 +1207,9 @@
686  
687     level = get_memory(dir->msglen+1);
688     Dmsg1(110, "level_cmd: %s", dir->msg);
689 +   if (strstr(dir->msg, "accurate")) {
690 +      jcr->accurate = true;
691 +   }
692     if (sscanf(dir->msg, "level = %s ", level) != 1) {
693        goto bail_out;
694     }
695 @@ -1204,14 +1219,14 @@
696     /* Full backup requested? */
697     } else if (strcmp(level, "full") == 0) {
698        jcr->JobLevel = L_FULL;
699 -   } else if (strcmp(level, "differential") == 0) {
700 +   } else if (strstr(level, "differential")) {
701        jcr->JobLevel = L_DIFFERENTIAL;
702        free_memory(level);
703        return 1;
704 -   } else if (strcmp(level, "incremental") == 0) {
705 +   } else if (strstr(level, "incremental")) {
706        jcr->JobLevel = L_INCREMENTAL;
707        free_memory(level);
708 -      return 1;   
709 +      return 1;
710     /*
711      * We get his UTC since time, then sync the clocks and correct it
712      *   to agree with our clock.
713 Index: src/filed/restore.c
714 ===================================================================
715 --- src/filed/restore.c (révision 6471)
716 +++ src/filed/restore.c (copie de travail)
717 @@ -320,6 +320,11 @@
718              bclose(&rctx.bfd);
719           }
720  
721 +        /* TODO: manage deleted files */
722 +        if (rctx.type == FT_DELETED) { /* deleted file */
723 +           continue;
724 +        }
725 +
726           /*
727            * Unpack attributes and do sanity check them
728            */
729 Index: src/cats/protos.h
730 ===================================================================
731 --- src/cats/protos.h   (révision 6471)
732 +++ src/cats/protos.h   (copie de travail)
733 @@ -102,6 +102,9 @@
734  int db_get_client_record(JCR *jcr, B_DB *mdb, CLIENT_DBR *cdbr);
735  int db_get_counter_record(JCR *jcr, B_DB *mdb, COUNTER_DBR *cr);
736  bool db_get_query_dbids(JCR *jcr, B_DB *mdb, POOL_MEM &query, dbid_list &ids);
737 +bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids, DB_RESULT_HANDLER *result_handler, void *ctx);
738 +bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb, JOB_DBR *jr, POOLMEM *jobids);
739 +int db_get_int_handler(void *ctx, int num_fields, char **row);
740  
741  
742  /* sql_list.c */
743 Index: src/cats/sql_get.c
744 ===================================================================
745 --- src/cats/sql_get.c  (révision 6471)
746 +++ src/cats/sql_get.c  (copie de travail)
747 @@ -898,8 +898,6 @@
748     return ok;
749  }
750  
751 -
752 -
753  /* Get Media Record
754   *
755   * Returns: false: on failure
756 @@ -1018,5 +1016,141 @@
757     return ok;
758  }
759  
760 +/*
761 + * Find the last "accurate" backup state (that can take deleted files in account)
762 + * 1) Get all files with jobid in list (F subquery) 
763 + * 2) Take only the last version of each file (Temp subquery) => accurate list is ok
764 + * 3) Join the result to file table to get fileindex, jobid and lstat information
765 + *
766 + * TODO: On postgresql, this is done with
767 +SELECT DISTINCT ON (PathId, FilenameId) FileIndex, Path, Name, LStat
768 +  FROM File JOIN Filename USING (FilenameId) JOIN Path USING (PathId) WHERE JobId IN (40341)
769 + ORDER BY PathId, FilenameId, JobId DESC
770 + */
771 +bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids, 
772 +                      DB_RESULT_HANDLER *result_handler, void *ctx)
773 +{
774 +   if (!*jobids) {
775 +      db_lock(mdb);
776 +      Mmsg(mdb->errmsg, _("ERR=JobIds are empty\n"));
777 +      db_unlock(mdb);
778 +      return false;
779 +   }
780  
781 +   POOL_MEM buf (PM_MESSAGE);
782 +   
783 +   Mmsg(buf,
784 + "SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId, File.LStat "
785 + "FROM ( "
786 +  "SELECT max(FileId) as FileId, PathId, FilenameId "
787 +    "FROM (SELECT FileId, PathId, FilenameId FROM File WHERE JobId IN (%s)) AS F "
788 +   "GROUP BY PathId, FilenameId "
789 +  ") AS Temp "
790 + "JOIN Filename ON (Filename.FilenameId = Temp.FilenameId) "
791 + "JOIN Path ON (Path.PathId = Temp.PathId) "
792 + "JOIN File ON (File.FileId = Temp.FileId) "
793 + "WHERE File.FileIndex > 0 ",
794 +             jobids);
795 +
796 +   return db_sql_query(mdb, buf.c_str(), result_handler, ctx);
797 +}
798 +
799 +
800 +/* Full : do nothing
801 + * Differential : get the last full id
802 + * Incremental : get the last full + last diff + last incr(s) ids
803 + *
804 + * TODO: look and merge from ua_restore.c
805 + */
806 +bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb, 
807 +                            JOB_DBR *jr, POOLMEM *jobids)
808 +{
809 +   char clientid[50], jobid[50], filesetid[50];
810 +   char date[MAX_TIME_LENGTH];
811 +
812 +   POOL_MEM query (PM_FNAME);
813 +   bstrutime(date, sizeof(date),  time(NULL) + 1);
814 +   jobids[0]='\0';
815 +
816 +   /* First, find the last good Full backup for this job/client/fileset */
817 +   Mmsg(query, 
818 +"CREATE TEMPORARY TABLE btemp3%s AS "
819 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
820 +   "FROM Job JOIN FileSet USING (FileSetId) "
821 +  "WHERE ClientId = %s "
822 +    "AND Level='F' AND JobStatus='T' AND Type='B' "
823 +    "AND StartTime<'%s' "
824 +    "AND FileSet.FileSet=(SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
825 +  "ORDER BY Job.JobTDate DESC LIMIT 1",
826 +        edit_uint64(jcr->JobId, jobid),
827 +        edit_uint64(jr->ClientId, clientid),
828 +        date,
829 +        edit_uint64(jr->FileSetId, filesetid));
830 +
831 +   if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
832 +      return false;
833 +   }
834 +
835 +   if (jr->JobLevel == L_INCREMENTAL) {
836 +
837 +      /* Now, find the last differential backup after the last full */
838 +      Mmsg(query, 
839 +"INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
840 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
841 +   "FROM Job JOIN FileSet USING (FileSetId) "
842 +  "WHERE ClientId = %s "
843 +    "AND Level='D' AND JobStatus='T' AND Type='B' "
844 +    "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
845 +    "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
846 +  "ORDER BY Job.JobTDate DESC LIMIT 1 ",
847 +           jobid,
848 +           clientid,
849 +           jobid,
850 +           filesetid);
851 +
852 +      db_sql_query(mdb, query.c_str(), NULL, NULL);
853 +
854 +      /* We just have to take all incremental after the last Full/Diff */
855 +      Mmsg(query, 
856 +"INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
857 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
858 +   "FROM Job JOIN FileSet USING (FileSetId) "
859 +  "WHERE ClientId = %s "
860 +    "AND Level='I' AND JobStatus='T' AND Type='B' "
861 +    "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
862 +    "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
863 +  "ORDER BY Job.JobTDate DESC ",
864 +           jobid,
865 +           clientid,
866 +           jobid,
867 +           filesetid);
868 +      db_sql_query(mdb, query.c_str(), NULL, NULL);
869 +   }
870 +
871 +   /* build a jobid list ie: 1,2,3,4 */
872 +   Mmsg(query, "SELECT JobId FROM btemp3%s", jobid);
873 +   db_sql_query(mdb, query.c_str(), db_get_int_handler, jobids);
874 +   Dmsg1(1, "db_accurate_get_jobids=%s\n", jobids);
875 +
876 +   Mmsg(query, "DROP TABLE btemp3%s", jobid);
877 +   db_sql_query(mdb, query.c_str(), NULL, NULL);
878 +
879 +   return true;
880 +}
881 +
882 +/*
883 + * Use to build a string of int list from a query. "10,20,30"
884 + */
885 +int db_get_int_handler(void *ctx, int num_fields, char **row)
886 +{
887 +   POOLMEM *ret = (POOLMEM *)ctx;
888 +   if (num_fields == 1) {
889 +      if (ret[0]) {
890 +         pm_strcat(ret, ",");
891 +      }
892 +      pm_strcat(ret, row[0]);
893 +   }
894 +   return 0;
895 +}
896 +
897  #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL || HAVE_DBI */
898 Index: src/baconfig.h
899 ===================================================================
900 --- src/baconfig.h      (révision 6471)
901 +++ src/baconfig.h      (copie de travail)
902 @@ -277,6 +277,7 @@
903  #define FT_INVALIDDT 20               /* Drive type not allowed for */
904  #define FT_REPARSE   21               /* Win NTFS reparse point */
905  #define FT_PLUGIN    22               /* Plugin generated filename */
906 +#define FT_DELETED   23               /* Deleted file entry */
907  
908  /* Definitions for upper part of type word (see above). */
909  #define AR_DATA_STREAM (1<<16)        /* Data stream id present */
910 Index: src/stored/bextract.c
911 ===================================================================
912 --- src/stored/bextract.c       (révision 6471)
913 +++ src/stored/bextract.c       (copie de travail)
914 @@ -343,6 +343,12 @@
915  
916           build_attr_output_fnames(jcr, attr);
917  
918 +        if (attr->type == FT_DELETED) { /* TODO: choose the right fname/ofname */
919 +           Jmsg(jcr, M_INFO, 0, _("%s was deleted.\n"), attr->fname);
920 +           extract = false;
921 +           return true;
922 +        }
923 +
924           extract = false;
925           stat = create_file(jcr, attr, &bfd, REPLACE_ALWAYS);
926           switch (stat) {
927 Index: src/stored/bscan.c
928 ===================================================================
929 --- src/stored/bscan.c  (révision 6471)
930 +++ src/stored/bscan.c  (copie de travail)
931 @@ -845,7 +845,11 @@
932     ar.ClientId = mjcr->ClientId;
933     ar.JobId = mjcr->JobId;
934     ar.Stream = rec->Stream;
935 -   ar.FileIndex = rec->FileIndex;
936 +   if (type == FT_DELETED) {
937 +      ar.FileIndex = 0;
938 +   } else {
939 +      ar.FileIndex = rec->FileIndex;
940 +   }
941     ar.attr = ap;
942     if (dcr->VolFirstIndex == 0) {
943        dcr->VolFirstIndex = rec->FileIndex;
944 Index: src/jcr.h
945 ===================================================================
946 --- src/jcr.h   (révision 6471)
947 +++ src/jcr.h   (copie de travail)
948 @@ -119,6 +119,7 @@
949  
950  /* Forward referenced structures */
951  class JCR;
952 +class htable;
953  struct FF_PKT;
954  struct B_DB;
955  struct ATTR_DBR;
956 @@ -318,6 +319,7 @@
957     CRYPTO_CTX crypto;                 /* Crypto ctx */
958     DIRRES* director;                  /* Director resource */
959     bool VSS;                          /* VSS used by FD */
960 +   htable *file_list;                 /* Previous file list (accurate mode) */
961  #endif /* FILE_DAEMON */
962  
963  
964 Index: src/lib/Makefile.in
965 ===================================================================
966 --- src/lib/Makefile.in (révision 6471)
967 +++ src/lib/Makefile.in (copie de travail)
968 @@ -29,7 +29,7 @@
969           res.c rwlock.c scan.c serial.c sha1.c \
970           signal.c smartall.c rblist.c tls.c tree.c \
971           util.c var.c watchdog.c workq.c btimers.c \
972 -         address_conf.c pythonlib.c breg.c
973 +         address_conf.c pythonlib.c breg.c htable.c
974  
975  
976  LIBOBJS = attr.o base64.o berrno.o bsys.o bget_msg.o \
977 @@ -42,7 +42,7 @@
978           res.o rwlock.o scan.o serial.o sha1.o \
979           signal.o smartall.o rblist.o tls.o tree.o \
980           util.o var.o watchdog.o workq.o btimers.o \
981 -         address_conf.o pythonlib.o breg.o
982 +         address_conf.o pythonlib.o breg.o htable.o
983  
984  
985  EXTRAOBJS = @OBJLIST@
986 Index: src/lib/attr.c
987 ===================================================================
988 --- src/lib/attr.c      (révision 6471)
989 +++ src/lib/attr.c      (copie de travail)
990 @@ -242,6 +242,14 @@
991     char *p, *f;
992     guid_list *guid;
993  
994 +   if (attr->type == FT_DELETED) { /* TODO: change this to get last seen values */
995 +      bsnprintf(buf, sizeof(buf),
996 +               "----------   - -        -                - ---------- --------  %s\n", attr->ofname);
997 +      Dmsg1(20, "%s", buf);
998 +      Jmsg(jcr, M_RESTORED, 1, "%s", buf);
999 +      return;
1000 +   }
1001 +
1002     if (!jcr->id_list) {
1003        jcr->id_list = new_guid_list();
1004     }
1005 Index: src/findlib/create_file.c
1006 ===================================================================
1007 --- src/findlib/create_file.c   (révision 6471)
1008 +++ src/findlib/create_file.c   (copie de travail)
1009 @@ -389,6 +389,9 @@
1010           return CF_CREATED;
1011        }
1012  
1013 +   case FT_DELETED:
1014 +      Qmsg2(jcr, M_INFO, 0, _("Original file %s have been deleted: type=%d\n"), attr->fname, attr->type);
1015 +      break;
1016     /* The following should not occur */
1017     case FT_NOACCESS:
1018     case FT_NOFOLLOW:
1019 Index: src/findlib/find.c
1020 ===================================================================
1021 --- src/findlib/find.c  (révision 6471)
1022 +++ src/findlib/find.c  (copie de travail)
1023 @@ -96,6 +96,13 @@
1024    Dmsg0(100, "Leave set_find_options()\n");
1025  }
1026  
1027 +void
1028 +set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff))
1029 +{
1030 +   Dmsg0(1, "Enter set_find_changed_function()\n");
1031 +   ff->check_fct = check_fct;
1032 +}
1033 +
1034  /*
1035   * For VSS we need to know which windows drives
1036   * are used, because we create a snapshot of all used
1037 Index: src/findlib/find_one.c
1038 ===================================================================
1039 --- src/findlib/find_one.c      (révision 6471)
1040 +++ src/findlib/find_one.c      (copie de travail)
1041 @@ -258,6 +258,33 @@
1042  }
1043  
1044  /*
1045 + * In incremental/diffential or accurate backup, we
1046 + * say if the current file has changed.
1047 + */
1048 +static bool check_changes(JCR *jcr, FF_PKT *ff_pkt)
1049 +{
1050 +   /* in special mode (like accurate backup), user can 
1051 +    * choose his comparison function.
1052 +    */
1053 +   if (ff_pkt->check_fct) {
1054 +      return ff_pkt->check_fct(jcr, ff_pkt);
1055 +   }
1056 +
1057 +   /* in normal modes (incr/diff), we use this default
1058 +    * behaviour
1059 +    */
1060 +   if (ff_pkt->incremental &&
1061 +       (ff_pkt->statp.st_mtime < ff_pkt->save_time &&
1062 +       ((ff_pkt->flags & FO_MTIMEONLY) ||
1063 +        ff_pkt->statp.st_ctime < ff_pkt->save_time))) 
1064 +   {
1065 +      return false;
1066 +   } 
1067 +
1068 +   return true;
1069 +}
1070 +
1071 +/*
1072   * Find a single file.
1073   * handle_file is the callback for handling the file.
1074   * p is the filename
1075 @@ -327,22 +354,18 @@
1076        }
1077        ff_pkt->volhas_attrlist = volume_has_attrlist(fname);
1078     }
1079 -
1080     /*
1081      * If this is an Incremental backup, see if file was modified
1082      * since our last "save_time", presumably the last Full save
1083      * or Incremental.
1084      */
1085 -   if (ff_pkt->incremental && !S_ISDIR(ff_pkt->statp.st_mode)) {
1086 -      Dmsg1(300, "Non-directory incremental: %s\n", ff_pkt->fname);
1087 -      /* Not a directory */
1088 -      if (ff_pkt->statp.st_mtime < ff_pkt->save_time
1089 -          && ((ff_pkt->flags & FO_MTIMEONLY) ||
1090 -              ff_pkt->statp.st_ctime < ff_pkt->save_time)) {
1091 -         /* Incremental option, file not changed */
1092 -         ff_pkt->type = FT_NOCHG;
1093 -         return handle_file(jcr, ff_pkt, top_level);
1094 -      }
1095 +   if (   ff_pkt->incremental 
1096 +       && !S_ISDIR(ff_pkt->statp.st_mode) 
1097 +       && !check_changes(jcr, ff_pkt)) 
1098 +   {
1099 +      Dmsg1(500, "Non-directory incremental: %s\n", ff_pkt->fname);
1100 +      ff_pkt->type = FT_NOCHG;
1101 +      return handle_file(jcr, ff_pkt, top_level);
1102     }
1103  
1104  #ifdef HAVE_DARWIN_OS
1105 @@ -502,10 +525,7 @@
1106        link[len] = 0;
1107  
1108        ff_pkt->link = link;
1109 -      if (ff_pkt->incremental &&
1110 -          (ff_pkt->statp.st_mtime < ff_pkt->save_time &&
1111 -             ((ff_pkt->flags & FO_MTIMEONLY) ||
1112 -               ff_pkt->statp.st_ctime < ff_pkt->save_time))) {
1113 +      if (ff_pkt->incremental && !check_changes(jcr, ff_pkt)) {
1114           /* Incremental option, directory entry not changed */
1115           ff_pkt->type = FT_DIRNOCHG;
1116        } else {
1117 Index: src/findlib/find.h
1118 ===================================================================
1119 --- src/findlib/find.h  (révision 6471)
1120 +++ src/findlib/find.h  (copie de travail)
1121 @@ -146,6 +146,7 @@
1122     int GZIP_level;                    /* GZIP level */
1123     int strip_path;                    /* strip path count */
1124     char VerifyOpts[MAX_FOPTS];        /* verify options */
1125 +   char AccurateOpts[MAX_FOPTS];      /* accurate mode options */
1126     alist regex;                       /* regex string(s) */
1127     alist regexdir;                    /* regex string(s) for directories */
1128     alist regexfile;                   /* regex string(s) for files */
1129 @@ -215,6 +216,7 @@
1130     findFILESET *fileset;
1131     int (*file_save)(JCR *, FF_PKT *, bool); /* User's callback */
1132     int (*plugin_save)(JCR *, FF_PKT *, bool); /* User's callback */
1133 +   bool (*check_fct)(JCR *, FF_PKT *); /* optionnal user fct to check file changes */
1134  
1135     /* Values set by accept_file while processing Options */
1136     uint32_t flags;                    /* backup options */
1137 Index: src/findlib/protos.h
1138 ===================================================================
1139 --- src/findlib/protos.h        (révision 6471)
1140 +++ src/findlib/protos.h        (copie de travail)
1141 @@ -45,6 +45,7 @@
1142  /* From find.c */
1143  FF_PKT *init_find_files();
1144  void  set_find_options(FF_PKT *ff, int incremental, time_t mtime);
1145 +void set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff));
1146  int   find_files(JCR *jcr, FF_PKT *ff, int file_sub(JCR *, FF_PKT *ff_pkt, bool),
1147                   int plugin_sub(JCR *, FF_PKT *ff_pkt, bool));
1148  int   match_files(JCR *jcr, FF_PKT *ff, int sub(JCR *, FF_PKT *ff_pkt, bool));