]> git.sur5r.net Git - bacula/bacula/blob - bacula/patches/testing/project-accurate-backup.patch2
ebl Replace mtime+ctime checks by check_accurate during backup
[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 6443)
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,7 +226,7 @@
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 @@ -240,24 +240,25 @@
24  bool send_level_command(JCR *jcr)
25  {
26     BSOCK   *fd = jcr->file_bsock;
27 +   const char *accurate=jcr->job->accurate?"accurate_":"";
28     /*
29      * Send Level command to File daemon
30      */
31     switch (jcr->JobLevel) {
32     case L_BASE:
33 -      fd->fsend(levelcmd, "base", " ", 0);
34 +      fd->fsend(levelcmd, "", "base", " ", 0);
35        break;
36     /* L_NONE is the console, sending something off to the FD */
37     case L_NONE:
38     case L_FULL:
39 -      fd->fsend(levelcmd, "full", " ", 0);
40 +      fd->fsend(levelcmd, "", "full", " ", 0);
41        break;
42     case L_DIFFERENTIAL:
43 -      fd->fsend(levelcmd, "differential", " ", 0);
44 +      fd->fsend(levelcmd, accurate, "differential", " ", 0);
45        send_since_time(jcr);
46        break;
47     case L_INCREMENTAL:
48 -      fd->fsend(levelcmd, "incremental", " ", 0);
49 +      fd->fsend(levelcmd, accurate, "incremental", " ", 0);
50        send_since_time(jcr);
51        break;
52     case L_SINCE:
53 Index: src/dird/backup.c
54 ===================================================================
55 --- src/dird/backup.c   (révision 6443)
56 +++ src/dird/backup.c   (copie de travail)
57 @@ -96,6 +96,54 @@
58     return true;
59  }
60  
61 +static int accurate_list_handler(void *ctx, int num_fields, char **row)
62 +{
63 +   JCR *jcr = (JCR *)ctx;
64 +
65 +   if (job_canceled(jcr)) {
66 +      return 1;
67 +   }
68 +   
69 +   if (row[2] > 0) {            /* discard when file_index == 0 */
70 +      jcr->file_bsock->fsend("%s%s%c%s", row[0], row[1], 0, row[4]); 
71 +   }
72 +   return 0;
73 +}
74 +
75 +bool send_accurate_current_files(JCR *jcr)
76 +{
77 +   char buf[MAXSTRING];
78 +
79 +   if (jcr->accurate == false || job_canceled(jcr) || jcr->JobLevel == L_FULL) {
80 +      return true;
81 +   }
82 +   POOLMEM *jobids = get_pool_memory(PM_FNAME);
83 +   db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, jobids);
84 +
85 +   if (*jobids == 0) {
86 +      free_pool_memory(jobids);
87 +      Jmsg(jcr, M_ERROR_TERM, 0, _("Cannot find previous jobids.\n"));
88 +      return true;
89 +   }
90 +
91 +   /* to be able to allocate the right size for htable */
92 +   POOLMEM *nb = get_pool_memory(PM_FNAME);
93 +   bsnprintf(buf, sizeof(buf), 
94 +             "SELECT sum(JobFiles) FROM Job WHERE JobId IN (%s)",jobids);
95 +   db_sql_query(jcr->db, buf, db_get_int_handler, nb);
96 +   jcr->file_bsock->fsend("accurate files=%s\n", nb); 
97 +
98 +   db_get_file_list(jcr, jcr->db, jobids, accurate_list_handler, (void *)jcr);
99 +
100 +   free_pool_memory(jobids);
101 +   free_pool_memory(nb);
102 +
103 +   jcr->file_bsock->signal(BNET_EOD);
104 +   /* TODO: use response() ? */
105 +
106 +   return true;
107 +}
108 +
109  /*
110   * Do a backup of the specified FileSet
111   *
112 @@ -225,6 +273,14 @@
113        Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
114     }
115  
116 +   /*
117 +    * If backup is in accurate mode, FD will send the list of
118 +    * all files.
119 +    */
120 +   if (!send_accurate_current_files(jcr)) {
121 +      goto bail_out;
122 +   }
123 +
124     /* Send backup command */
125     fd->fsend(backupcmd);
126     if (!response(jcr, fd, OKbackup, "backup", DISPLAY_ERROR)) {
127 @@ -234,6 +290,7 @@
128     /* Pickup Job termination data */
129     stat = wait_for_job_termination(jcr);
130     db_write_batch_file_records(jcr);    /* used by bulk batch file insert */
131 +
132     if (stat == JS_Terminated) {
133        backup_cleanup(jcr, stat);
134        return true;
135 @@ -475,6 +532,7 @@
136  "  Software Compression:   %s\n"
137  "  VSS:                    %s\n"
138  "  Encryption:             %s\n"
139 +"  Accurate:               %s\n"
140  "  Volume name(s):         %s\n"
141  "  Volume Session Id:      %d\n"
142  "  Volume Session Time:    %d\n"
143 @@ -506,8 +564,9 @@
144          edit_uint64_with_suffix(jcr->SDJobBytes, ec6),
145          kbps,
146          compress,
147 -        jcr->VSS?"yes":"no",
148 -        jcr->Encrypt?"yes":"no",
149 +        jcr->VSS?_("yes"):_("no"),
150 +        jcr->Encrypt?_("yes"):_("no"),
151 +        jcr->accurate?_("yes"):_("no"),
152          jcr->VolumeName,
153          jcr->VolSessionId,
154          jcr->VolSessionTime,
155 Index: src/dird/ua_restore.c
156 ===================================================================
157 --- src/dird/ua_restore.c       (révision 6443)
158 +++ src/dird/ua_restore.c       (copie de travail)
159 @@ -1006,7 +1006,6 @@
160      * For display purposes, the same JobId, with different volumes may
161      * appear more than once, however, we only insert it once.
162      */
163 -   int items = 0;
164     p = rx->JobIds;
165     tree.FileEstimate = 0;
166     if (get_next_jobid_from_list(&p, &JobId) > 0) {
167 @@ -1021,24 +1020,14 @@
168           tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
169        }
170     }
171 -   for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
172 -      char ed1[50];
173  
174 -      if (JobId == last_JobId) {
175 -         continue;                    /* eliminate duplicate JobIds */
176 -      }
177 -      last_JobId = JobId;
178 -      ua->info_msg(_("\nBuilding directory tree for JobId %s ...  "), 
179 -         edit_int64(JobId, ed1));
180 -      items++;
181 -      /*
182 -       * Find files for this JobId and insert them in the tree
183 -       */
184 -      Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
185 -      if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
186 -         ua->error_msg("%s", db_strerror(ua->db));
187 -      }
188 +   ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ...  "),
189 +                rx->JobIds);
190 +
191 +   if (!db_get_file_list(ua->jcr, ua->db, rx->JobIds, insert_tree_handler, (void *)&tree)) {
192 +      ua->error_msg("%s", db_strerror(ua->db));
193     }
194 +
195     if (tree.FileCount == 0) {
196        ua->send_msg(_("\nThere were no files inserted into the tree, so file selection\n"
197           "is not possible.Most likely your retention policy pruned the files\n"));
198 @@ -1056,26 +1045,13 @@
199        }
200     } else {
201        char ec1[50];
202 -      if (items==1) {
203 -         if (tree.all) {
204 -            ua->info_msg(_("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
205 -              edit_uint64_with_commas(tree.FileCount, ec1));
206 -         }
207 -         else {
208 -            ua->info_msg(_("\n1 Job, %s files inserted into the tree.\n"),
209 -              edit_uint64_with_commas(tree.FileCount, ec1));
210 -         }
211 +      if (tree.all) {
212 +         ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
213 +                      edit_uint64_with_commas(tree.FileCount, ec1));
214 +      } else {
215 +         ua->info_msg(_("\n%s files inserted into the tree.\n"),
216 +                      edit_uint64_with_commas(tree.FileCount, ec1));
217        }
218 -      else {
219 -         if (tree.all) {
220 -            ua->info_msg(_("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
221 -              items, edit_uint64_with_commas(tree.FileCount, ec1));
222 -         }
223 -         else {
224 -            ua->info_msg(_("\n%d Jobs, %s files inserted into the tree.\n"),
225 -              items, edit_uint64_with_commas(tree.FileCount, ec1));
226 -         }
227 -      }
228  
229        if (find_arg(ua, NT_("done")) < 0) {
230           /* Let the user interact in selecting which files to restore */
231 Index: src/dird/inc_conf.c
232 ===================================================================
233 --- src/dird/inc_conf.c (révision 6443)
234 +++ src/dird/inc_conf.c (copie de travail)
235 @@ -94,6 +94,7 @@
236   * Items that are valid in an Options resource
237   */
238  static RES_ITEM options_items[] = {
239 +   {"accurate",        store_opts,    {0},     0, 0, 0},
240     {"compression",     store_opts,    {0},     0, 0, 0},
241     {"signature",       store_opts,    {0},     0, 0, 0},
242     {"verify",          store_opts,    {0},     0, 0, 0},
243 @@ -153,7 +154,8 @@
244     INC_KW_NOATIME,
245     INC_KW_ENHANCEDWILD,
246     INC_KW_CHKCHANGES,
247 -   INC_KW_STRIPPATH
248 +   INC_KW_STRIPPATH,
249 +   INC_KW_ACCURATE
250  };
251  
252  /*
253 @@ -163,6 +165,7 @@
254   *   options given above.
255   */
256  static struct s_kw FS_option_kw[] = {
257 +   {"accurate",    INC_KW_ACCURATE},
258     {"compression", INC_KW_COMPRESSION},
259     {"signature",   INC_KW_DIGEST},
260     {"encryption",  INC_KW_ENCRYPTION},
261 @@ -251,6 +254,8 @@
262     {"no",       INC_KW_ENHANCEDWILD,  "0"},
263     {"yes",      INC_KW_CHKCHANGES,    "c"},
264     {"no",       INC_KW_CHKCHANGES,    "0"},
265 +   {"yes",      INC_KW_ACCURATE,      "C"},
266 +   {"no",       INC_KW_ACCURATE,      "0"},
267     {NULL,       0,                      0}
268  };
269  
270 Index: src/filed/backup.c
271 ===================================================================
272 --- src/filed/backup.c  (révision 6443)
273 +++ src/filed/backup.c  (copie de travail)
274 @@ -37,6 +37,7 @@
275  
276  #include "bacula.h"
277  #include "filed.h"
278 +#include "lib/htable.h"
279  
280  /* Forward referenced functions */
281  int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level);
282 @@ -48,8 +49,225 @@
283  static bool crypto_session_start(JCR *jcr);
284  static void crypto_session_end(JCR *jcr);
285  static bool crypto_session_send(JCR *jcr, BSOCK *sd);
286 +static bool encode_and_send_deleted_file(JCR *jcr, char *fname);
287  
288 +typedef struct CurFile {
289 +   char *fname;
290 +   char *lstat;
291 +   hlink link;
292 +} CurFile;
293 +
294  /*
295 + * This function is called for each file seen in fileset.
296 + * 
297 + * If the file is skipped (saved=false), we will check if this
298 + * file have been backuped before. If not, we decide to backup it.
299 + *
300 + * If the file have saved=true, we mark it as seen
301 + * 
302 + */
303 +/* TODO: tweak verify code to use the same function */
304 +bool accurate_check_file(JCR *jcr, FF_PKT *ff_pkt)
305 +{
306 +   char *p;
307 +   int stat=false;
308 +   struct stat statc;                 /* catalog stat */
309 +   char *Opts_Digest = ff_pkt->VerifyOpts;
310 +   char *fname = ff_pkt->fname;
311 +   CurFile *elt;
312 +
313 +   int32_t LinkFIc;
314 +
315 +   if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
316 +      return true;
317 +   }
318 +
319 +   if (ff_pkt->type == FT_DIREND || ff_pkt->type == FT_REPARSE || ff_pkt->type == FT_DIRNOCHG) {
320 +      fname = ff_pkt->link;
321 +   } 
322 +
323 +   elt = (CurFile *) jcr->file_list->lookup(fname);
324 +
325 +   if (!elt) {
326 +      // TODO: we must backup it !
327 +      Dmsg1(1, "accurate %s = yes (not found)\n", fname);
328 +      return true;
329 +   }
330 +
331 +   if (*elt->lstat == '\0') {
332 +      Dmsg1(1, "accurate %s = no (already seen)\n", fname);
333 +      *elt->lstat = '\0';
334 +      return false;
335 +   }
336 +
337 +   decode_stat(elt->lstat, &statc, &LinkFIc); /* decode catalog stat */
338 +//   *do_Digest = CRYPTO_DIGEST_NONE;
339 +
340 +   for (p=Opts_Digest; *p; p++) {
341 +      char ed1[30], ed2[30];
342 +      switch (*p) {
343 +      case 'i':                /* compare INODEs */
344 +         if (statc.st_ino != ff_pkt->statp.st_ino) {
345 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_ino   differ. Cat: %s File: %s\n"), fname,
346 +                 edit_uint64((uint64_t)statc.st_ino, ed1),
347 +                 edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
348 +            stat = true;
349 +         }
350 +         break;
351 +      case 'p':                /* permissions bits */
352 +         if (statc.st_mode != ff_pkt->statp.st_mode) {
353 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_mode  differ. Cat: %x File: %x\n"), fname,
354 +                 (uint32_t)statc.st_mode, (uint32_t)ff_pkt->statp.st_mode);
355 +            stat = true;
356 +         }
357 +         break;
358 +//      case 'n':                /* number of links */
359 +//         if (statc.st_nlink != ff_pkt->statp.st_nlink) {
360 +//            Jmsg(jcr, M_SAVED, 0, _("%s      st_nlink differ. Cat: %d File: %d\n"), fname,
361 +//                 (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
362 +//            stat = true;
363 +//         }
364 +//         break;
365 +      case 'u':                /* user id */
366 +         if (statc.st_uid != ff_pkt->statp.st_uid) {
367 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_uid   differ. Cat: %u File: %u\n"), fname,
368 +                 (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
369 +            stat = true;
370 +         }
371 +         break;
372 +      case 'g':                /* group id */
373 +         if (statc.st_gid != ff_pkt->statp.st_gid) {
374 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_gid   differ. Cat: %u File: %u\n"), fname,
375 +                 (uint32_t)statc.st_gid, (uint32_t)ff_pkt->statp.st_gid);
376 +            stat = true;
377 +         }
378 +         break;
379 +      case 's':                /* size */
380 +         if (statc.st_size != ff_pkt->statp.st_size) {
381 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_size  differ. Cat: %s File: %s\n"), fname,
382 +                 edit_uint64((uint64_t)statc.st_size, ed1),
383 +                 edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
384 +            stat = true;
385 +         }
386 +         break;
387 +//      case 'a':                /* access time */
388 +//         if (statc.st_atime != ff_pkt->statp.st_atime) {
389 +//            Jmsg(jcr, M_SAVED, 0, _("%s      st_atime differs\n"), fname);
390 +//            stat = true;
391 +//         }
392 +//         break;
393 +      case 'm':
394 +         if (statc.st_mtime != ff_pkt->statp.st_mtime) {
395 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_mtime differs\n"), fname);
396 +            stat = true;
397 +         }
398 +         break;
399 +      case 'c':                /* ctime */
400 +         if (statc.st_ctime != ff_pkt->statp.st_ctime) {
401 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_ctime differs\n"), fname);
402 +            stat = true;
403 +         }
404 +         break;
405 +      case 'd':                /* file size decrease */
406 +         if (statc.st_size > ff_pkt->statp.st_size) {
407 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_size  decrease. Cat: %s File: %s\n"), fname,
408 +                 edit_uint64((uint64_t)statc.st_size, ed1),
409 +                 edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
410 +            stat = true;
411 +         }
412 +         break;
413 +      case '5':                /* compare MD5 */
414 +         Dmsg1(500, "set Do_MD5 for %s\n", ff_pkt->fname);
415 +//       *do_Digest = CRYPTO_DIGEST_MD5;
416 +         break;
417 +      case '1':                 /* compare SHA1 */
418 +//       *do_Digest = CRYPTO_DIGEST_SHA1;
419 +         break;
420 +      case ':':
421 +      case 'V':
422 +      default:
423 +         break;
424 +      }
425 +   }
426 +   *elt->lstat = '\0';          /* mark it as seen */
427 +   Dmsg2(1, "accurate %s = %i\n", fname, stat);
428 +   return stat;
429 +}
430 +
431 +/* 
432 + * This function doesn't work very well with smartalloc
433 + */
434 +int accurate_get_current_file_list_cmd(JCR *jcr)
435 +{
436 +   BSOCK *dir = jcr->dir_bsock;
437 +   int len;
438 +   uint64_t nb;
439 +   CurFile *elt=NULL;
440 +
441 +   if (jcr->accurate == false || job_canceled(jcr) || jcr->JobLevel == L_FULL) {
442 +      return true;
443 +   }
444 +
445 +   if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
446 +      dir->fsend(_("2991 Bad accurate command\n"));
447 +      return false;
448 +   }
449 +
450 +   Jmsg(jcr, M_INFO, 0, _("Getting Accurate informations.\n"));
451 +   
452 +   jcr->file_list = (htable *)malloc(sizeof(htable));
453 +   jcr->file_list->init(elt, &elt->link, nb);
454 +
455 +   /* get current files */
456 +   while (dir->recv() >= 0) {
457 +      len = strlen(dir->msg);
458 +      if ((len+1) < dir->msglen) {
459 +//       elt = (CurFile *)malloc(sizeof(CurFile));
460 +//       elt->fname  = (char *) malloc(dir->msglen+1);
461 +
462 +         /* we store CurFile, fname and lstat in the same chunk */
463 +         elt = (CurFile *)malloc(sizeof(CurFile)+dir->msglen+1);
464 +         elt->fname  = (char *) elt+sizeof(CurFile);
465 +         memcpy(elt->fname, dir->msg, dir->msglen);
466 +         elt->fname[dir->msglen]='\0';
467 +         elt->lstat = elt->fname + len + 1;
468 +         jcr->file_list->insert(elt->fname, elt); 
469 +         Dmsg2(5, "add fname=%s lstat=%s\n", elt->fname, elt->lstat);
470 +      }
471 +   }
472 +
473 +//   jcr->file_list->stats();
474 +   /* TODO: send a EOM ?
475 +   dir->fsend("2000 OK accurate\n");
476 +    */
477 +   return true;
478 +}
479 +
480 +bool accurate_send_deleted_list(JCR *jcr)
481 +{
482 +   if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
483 +      return true;
484 +   }
485 +
486 +   if (jcr->file_list == NULL) { /* TODO: bug ? */
487 +      return true;
488 +   }
489 +
490 +   CurFile *elt;
491 +   foreach_htable (elt, jcr->file_list) {
492 +      if (*elt->lstat != '\0') {
493 +         Dmsg2(1, "deleted fname=%s lstat=%s\n", elt->fname, elt->lstat);
494 +         encode_and_send_deleted_file(jcr, elt->fname);
495 +      }
496 +//      free(elt->fname);
497 +   }
498 +   jcr->file_list->destroy();   /* TODO: clean htable when this function is not reached ? */
499 +   free(jcr->file_list);
500 +   jcr->file_list = NULL;
501 +   return true;
502 +}
503 +
504 +/*
505   * Find all the requested files and send them
506   * to the Storage daemon.
507   *
508 @@ -66,7 +284,6 @@
509     BSOCK *sd;
510     bool ok = true;
511     // TODO landonf: Allow user to specify encryption algorithm
512 -
513     sd = jcr->store_bsock;
514  
515     set_jcr_job_status(jcr, JS_Running);
516 @@ -100,7 +317,7 @@
517      */
518     jcr->compress_buf_size = jcr->buf_size + ((jcr->buf_size+999) / 1000) + 30;
519     jcr->compress_buf = get_memory(jcr->compress_buf_size);
520 -
521 +   
522  #ifdef HAVE_LIBZ
523     z_stream *pZlibStream = (z_stream*)malloc(sizeof(z_stream));  
524     if (pZlibStream) {
525 @@ -121,10 +338,13 @@
526        return false;
527     }
528  
529 -   Dmsg1(300, "set_find_options ff=%p\n", jcr->ff);
530     set_find_options((FF_PKT *)jcr->ff, jcr->incremental, jcr->mtime);
531 -   Dmsg0(300, "start find files\n");
532  
533 +   /* in accurate mode, we overwrite the find_one check function */
534 +   if (jcr->accurate) {
535 +      set_find_changed_function((FF_PKT *)jcr->ff, accurate_check_file);
536 +   } 
537 +   
538     start_heartbeat_monitor(jcr);
539  
540     jcr->acl_text = get_pool_memory(PM_MESSAGE);
541 @@ -134,7 +354,10 @@
542        ok = false;                     /* error */
543        set_jcr_job_status(jcr, JS_ErrorTerminated);
544     }
545 +   Dmsg1(1, "jcr->accurate == %i\n", jcr->accurate);
546  
547 +   accurate_send_deleted_list(jcr);              /* send deleted list to SD  */
548 +
549     free_pool_memory(jcr->acl_text);
550  
551     stop_heartbeat_monitor(jcr);
552 @@ -1128,6 +1351,58 @@
553     return true;
554  }
555  
556 +static bool encode_and_send_deleted_file(JCR *jcr, char *fname) 
557 +{
558 +   BSOCK *sd = jcr->store_bsock;
559 +   char *attribs;
560 +   char *attribsEx;
561 +   int stat;
562 +#ifdef FD_NO_SEND_TEST
563 +   return true;
564 +#endif
565 +
566 +   attribs = " ";
567 +   attribsEx = " ";
568 +
569 +   /*
570 +    * Send Attributes header to Storage daemon
571 +    *    <file-index> <stream> <info>
572 +    */
573 +   if (!sd->fsend("%ld %d 0", 0, STREAM_UNIX_ATTRIBUTES)) {
574 +      Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
575 +            sd->bstrerror());
576 +      return false;
577 +   }
578 +   Dmsg1(300, ">stored: attrhdr %s\n", sd->msg);
579 +
580 +   /*
581 +    * Send file attributes to Storage daemon
582 +    *   File_index
583 +    *   File type
584 +    *   Filename (full path)
585 +    *   Encoded attributes
586 +    *   Link name (if type==FT_LNK or FT_LNKSAVED)
587 +    *   Encoded extended-attributes (for Win32)
588 +    *
589 +    * For a directory, link is the same as fname, but with trailing
590 +    * slash. For a linked file, link is the link.
591 +    */
592 +   stat = sd->fsend("%ld %d %s%c%s%c%s%c%s%c", 
593 +                    0 /* FileIndex */,
594 +                    FT_NOSTAT /* FileType */,
595 +                    fname /* FileName */, 
596 +                    0, attribs, 0, 0, 0, attribsEx, 0);
597 +
598 +   Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg);
599 +   if (!stat) {
600 +      Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
601 +            sd->bstrerror());
602 +      return false;
603 +   }
604 +   sd->signal(BNET_EOD);            /* indicate end of attributes data */
605 +   return true;
606 +}
607 +
608  /* 
609   * Do in place strip of path
610   */
611 Index: src/filed/job.c
612 ===================================================================
613 --- src/filed/job.c     (révision 6443)
614 +++ src/filed/job.c     (copie de travail)
615 @@ -49,6 +49,7 @@
616  /* Imported functions */
617  extern int status_cmd(JCR *jcr);
618  extern int qstatus_cmd(JCR *jcr);
619 +extern int accurate_get_current_file_list_cmd(JCR *jcr);
620  
621  /* Forward referenced functions */
622  static int backup_cmd(JCR *jcr);
623 @@ -106,6 +107,7 @@
624     {"RunBeforeJob", runbefore_cmd, 0},
625     {"RunAfterJob",  runafter_cmd,  0},
626     {"Run",          runscript_cmd, 0},
627 +   {"accurate",     accurate_get_current_file_list_cmd, 0},
628     {NULL,       NULL}                  /* list terminator */
629  };
630  
631 @@ -1087,6 +1089,9 @@
632        case 'c':
633           fo->flags |= FO_CHKCHANGES;
634           break;
635 +      case 'C':
636 +         fo->flags |= FO_ACCURATE;
637 +         break;
638        default:
639           Emsg1(M_ERROR, 0, _("Unknown include/exclude option: %c\n"), *p);
640           break;
641 @@ -1195,6 +1200,9 @@
642  
643     level = get_memory(dir->msglen+1);
644     Dmsg1(110, "level_cmd: %s", dir->msg);
645 +   if (strstr(dir->msg, "accurate")) {
646 +      jcr->accurate = true;
647 +   }
648     if (sscanf(dir->msg, "level = %s ", level) != 1) {
649        goto bail_out;
650     }
651 @@ -1204,14 +1212,14 @@
652     /* Full backup requested? */
653     } else if (strcmp(level, "full") == 0) {
654        jcr->JobLevel = L_FULL;
655 -   } else if (strcmp(level, "differential") == 0) {
656 +   } else if (strstr(level, "differential")) {
657        jcr->JobLevel = L_DIFFERENTIAL;
658        free_memory(level);
659        return 1;
660 -   } else if (strcmp(level, "incremental") == 0) {
661 +   } else if (strstr(level, "incremental")) {
662        jcr->JobLevel = L_INCREMENTAL;
663        free_memory(level);
664 -      return 1;   
665 +      return 1;
666     /*
667      * We get his UTC since time, then sync the clocks and correct it
668      *   to agree with our clock.
669 Index: src/filed/restore.c
670 ===================================================================
671 --- src/filed/restore.c (révision 6443)
672 +++ src/filed/restore.c (copie de travail)
673 @@ -320,6 +320,11 @@
674              bclose(&rctx.bfd);
675           }
676  
677 +        /* TODO: manage deleted files */
678 +        if (file_index == 0) { /* deleted file */
679 +           continue;
680 +        }
681 +
682           /*
683            * Unpack attributes and do sanity check them
684            */
685 Index: src/cats/protos.h
686 ===================================================================
687 --- src/cats/protos.h   (révision 6443)
688 +++ src/cats/protos.h   (copie de travail)
689 @@ -102,6 +102,9 @@
690  int db_get_client_record(JCR *jcr, B_DB *mdb, CLIENT_DBR *cdbr);
691  int db_get_counter_record(JCR *jcr, B_DB *mdb, COUNTER_DBR *cr);
692  bool db_get_query_dbids(JCR *jcr, B_DB *mdb, POOL_MEM &query, dbid_list &ids);
693 +bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids, DB_RESULT_HANDLER *result_handler, void *ctx);
694 +bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb, JOB_DBR *jr, POOLMEM *jobids);
695 +int db_get_int_handler(void *ctx, int num_fields, char **row);
696  
697  
698  /* sql_list.c */
699 Index: src/cats/sql_get.c
700 ===================================================================
701 --- src/cats/sql_get.c  (révision 6443)
702 +++ src/cats/sql_get.c  (copie de travail)
703 @@ -898,8 +898,6 @@
704     return ok;
705  }
706  
707 -
708 -
709  /* Get Media Record
710   *
711   * Returns: false: on failure
712 @@ -1018,5 +1016,126 @@
713     return ok;
714  }
715  
716 +bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids, 
717 +                      DB_RESULT_HANDLER *result_handler, void *ctx)
718 +{
719 +   if (*jobids == 0) {
720 +      db_lock(mdb);
721 +      Mmsg(mdb->errmsg, _("ERR=JobIds are empty\n"));
722 +      db_unlock(mdb);
723 +      return false;
724 +   }
725  
726 +   POOL_MEM buf (PM_MESSAGE);
727 +   
728 +   Mmsg(buf,
729 + "SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId, File.LStat "
730 + "FROM ( "
731 +  "SELECT max(FileId) as FileId, PathId, FilenameId "
732 +    "FROM (SELECT FileId, PathId, FilenameId FROM File WHERE JobId IN (%s)) AS F "
733 +   "GROUP BY PathId, FilenameId "
734 +  ") AS Temp "
735 + "JOIN Filename ON (Filename.FilenameId = Temp.FilenameId) "
736 + "JOIN Path ON (Path.PathId = Temp.PathId) "
737 + "JOIN File ON (File.FileId = Temp.FileId) "
738 + "WHERE File.FileIndex > 0 ",
739 +             jobids);
740 +
741 +   return db_sql_query(mdb, buf.c_str(), result_handler, ctx);
742 +}
743 +
744 +
745 +/* Full : do nothing
746 + * Differential : get the last full id
747 + * Incremental : get the last full + last diff + last incr(s) ids
748 + *
749 + * TODO: look and merge from ua_restore.c
750 + */
751 +bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb, 
752 +                            JOB_DBR *jr, POOLMEM *jobids)
753 +{
754 +   char clientid[50], jobid[50], filesetid[50];
755 +   char date[MAX_TIME_LENGTH];
756 +
757 +   POOL_MEM query (PM_FNAME);
758 +   bstrutime(date, sizeof(date),  time(NULL) + 1);
759 +   jobids[0]='\0';
760 +
761 +   Mmsg(query, 
762 +"CREATE TEMPORARY TABLE btemp3%s AS ( "
763 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
764 +   "FROM Job JOIN FileSet USING (FileSetId) "
765 +  "WHERE ClientId = %s "
766 +    "AND Level='F' AND JobStatus='T' AND Type='B' "
767 +    "AND StartTime<'%s' "
768 +    "AND FileSet.FileSet=(SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
769 +  "ORDER BY Job.JobTDate DESC LIMIT 1) ",
770 +        edit_uint64(jcr->JobId, jobid),
771 +        edit_uint64(jr->ClientId, clientid),
772 +        date,
773 +        edit_uint64(jr->FileSetId, filesetid));
774 +
775 +   if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
776 +      return false;
777 +   }
778 +
779 +   if (jr->JobLevel == L_INCREMENTAL) {
780 +
781 +      Mmsg(query, 
782 +"INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
783 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
784 +   "FROM Job JOIN FileSet USING (FileSetId) "
785 +  "WHERE ClientId = %s "
786 +    "AND Level='D' AND JobStatus='T' AND Type='B' "
787 +    "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
788 +    "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
789 +  "ORDER BY Job.JobTDate DESC LIMIT 1 ",
790 +           jobid,
791 +           clientid,
792 +           jobid,
793 +           filesetid);
794 +
795 +      db_sql_query(mdb, query.c_str(), NULL, NULL);
796 +
797 +      Mmsg(query, 
798 +"INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
799 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
800 +   "FROM Job JOIN FileSet USING (FileSetId) "
801 +  "WHERE ClientId = %s "
802 +    "AND Level='I' AND JobStatus='T' AND Type='B' "
803 +    "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
804 +    "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
805 +  "ORDER BY Job.JobTDate DESC ",
806 +           jobid,
807 +           clientid,
808 +           jobid,
809 +           filesetid);
810 +      db_sql_query(mdb, query.c_str(), NULL, NULL);
811 +   }
812 +
813 +   Mmsg(query, "SELECT JobId FROM btemp3%s", jobid);
814 +   db_sql_query(mdb, query.c_str(), db_get_int_handler, jobids);
815 +   Dmsg1(1, "db_accurate_get_jobids=%s\n", jobids);
816 +
817 +   Mmsg(query, "DROP TABLE btemp3%s", jobid);
818 +   db_sql_query(mdb, query.c_str(), NULL, NULL);
819 +
820 +   return true;
821 +}
822 +
823 +/*
824 + * Use to build a string of int list from a query. "10,20,30"
825 + */
826 +int db_get_int_handler(void *ctx, int num_fields, char **row)
827 +{
828 +   POOLMEM *ret = (POOLMEM *)ctx;
829 +   if (num_fields == 1) {
830 +      if (ret[0] != 0) {
831 +         pm_strcat(ret, ",");
832 +      }
833 +      pm_strcat(ret, row[0]);
834 +   }
835 +   return 0;
836 +}
837 +
838  #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL*/
839 Index: src/stored/bextract.c
840 ===================================================================
841 --- src/stored/bextract.c       (révision 6443)
842 +++ src/stored/bextract.c       (copie de travail)
843 @@ -324,6 +324,14 @@
844           Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
845        }
846  
847 +      /* handle deleted file 
848 +       */
849 +      if (rec->FileIndex == 0) {
850 +         /* if file is included, remove it ? */
851 +         Jmsg(jcr, M_INFO, 0, _("fname=%s is marked as deleted.\n"), attr->fname);
852 +         break;
853 +      }
854 +
855        if (attr->file_index != rec->FileIndex) {
856           Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
857              rec->FileIndex, attr->file_index);
858 Index: src/stored/bscan.c
859 ===================================================================
860 --- src/stored/bscan.c  (révision 6443)
861 +++ src/stored/bscan.c  (copie de travail)
862 @@ -648,6 +648,15 @@
863     case STREAM_UNIX_ATTRIBUTES:
864     case STREAM_UNIX_ATTRIBUTES_EX:
865  
866 +      /* handle deleted file 
867 +       */
868 +      if (rec->FileIndex == 0) {
869 +         create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
870 +                                       FT_NOSTAT, "", rec);
871 +         free_jcr(mjcr);
872 +         break;
873 +      }
874 +
875        if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
876           Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
877        }
878 Index: src/stored/append.c
879 ===================================================================
880 --- src/stored/append.c (révision 6443)
881 +++ src/stored/append.c (copie de travail)
882 @@ -146,7 +146,7 @@
883  
884        /* Read Stream header from the File daemon.
885         *  The stream header consists of the following:
886 -       *    file_index (sequential Bacula file index, base 1)
887 +       *    file_index (sequential Bacula file index, base 1, 0 for deleted files)
888         *    stream     (Bacula number to distinguish parts of data)
889         *    info       (Info for Storage daemon -- compressed, encryped, ...)
890         *       info is not currently used, so is read, but ignored!
891 @@ -185,16 +185,18 @@
892  
893        Dmsg2(890, "<filed: Header FilInx=%d stream=%d\n", file_index, stream);
894  
895 -      if (!(file_index > 0 && (file_index == last_file_index ||
896 -          file_index == last_file_index + 1))) {
897 -         Jmsg0(jcr, M_FATAL, 0, _("File index from FD not positive or sequential\n"));
898 -         ok = false;
899 -         break;
900 +      if (file_index != 0) {    /* TODO: handle file_index == 0 */
901 +         if (!(file_index > 0 && (file_index == last_file_index ||
902 +                                  file_index == last_file_index + 1))) {
903 +            Jmsg0(jcr, M_FATAL, 0, _("File index from FD not positive or sequential\n"));
904 +            ok = false;
905 +            break;
906 +         }
907 +         if (file_index != last_file_index) {
908 +            jcr->JobFiles = file_index;
909 +            last_file_index = file_index;
910 +         }
911        }
912 -      if (file_index != last_file_index) {
913 -         jcr->JobFiles = file_index;
914 -         last_file_index = file_index;
915 -      }
916  
917        /* Read data stream from the File daemon.
918         *  The data stream is just raw bytes
919 @@ -214,22 +216,23 @@
920  
921           while (!write_record_to_block(dcr->block, &rec)) {
922              Dmsg2(850, "!write_record_to_block data_len=%d rem=%d\n", rec.data_len,
923 -                       rec.remainder);
924 +                  rec.remainder);
925              if (!write_block_to_device(dcr)) {
926                 Dmsg2(90, "Got write_block_to_dev error on device %s. %s\n",
927 -                  dev->print_name(), dev->bstrerror());
928 +                     dev->print_name(), dev->bstrerror());
929                 ok = false;
930                 break;
931              }
932 +
933 +            if (!ok) {
934 +               Dmsg0(400, "Not OK\n");
935 +               break;
936 +            }
937 +            jcr->JobBytes += rec.data_len;   /* increment bytes this job */
938 +            Dmsg4(850, "write_record FI=%s SessId=%d Strm=%s len=%d\n",
939 +                  FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId,
940 +                  stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len);
941           }
942 -         if (!ok) {
943 -            Dmsg0(400, "Not OK\n");
944 -            break;
945 -         }
946 -         jcr->JobBytes += rec.data_len;   /* increment bytes this job */
947 -         Dmsg4(850, "write_record FI=%s SessId=%d Strm=%s len=%d\n",
948 -            FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId,
949 -            stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len);
950  
951           /* Send attributes and digest to Director for Catalog */
952           if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX ||
953 Index: src/jcr.h
954 ===================================================================
955 --- src/jcr.h   (révision 6443)
956 +++ src/jcr.h   (copie de travail)
957 @@ -119,6 +119,7 @@
958  
959  /* Forward referenced structures */
960  class JCR;
961 +class htable;
962  struct FF_PKT;
963  struct B_DB;
964  struct ATTR_DBR;
965 @@ -318,6 +319,7 @@
966     CRYPTO_CTX crypto;                 /* Crypto ctx */
967     DIRRES* director;                  /* Director resource */
968     bool VSS;                          /* VSS used by FD */
969 +   htable *file_list;                 /* Previous file list (accurate mode) */
970  #endif /* FILE_DAEMON */
971  
972  
973 Index: src/lib/Makefile.in
974 ===================================================================
975 --- src/lib/Makefile.in (révision 6443)
976 +++ src/lib/Makefile.in (copie de travail)
977 @@ -29,7 +29,7 @@
978           res.c rwlock.c scan.c serial.c sha1.c \
979           signal.c smartall.c rblist.c tls.c tree.c \
980           util.c var.c watchdog.c workq.c btimers.c \
981 -         address_conf.c pythonlib.c breg.c
982 +         address_conf.c pythonlib.c breg.c htable.c
983  
984  
985  LIBOBJS = attr.o base64.o berrno.o bsys.o bget_msg.o \
986 @@ -42,7 +42,7 @@
987           res.o rwlock.o scan.o serial.o sha1.o \
988           signal.o smartall.o rblist.o tls.o tree.o \
989           util.o var.o watchdog.o workq.o btimers.o \
990 -         address_conf.o pythonlib.o breg.o
991 +         address_conf.o pythonlib.o breg.o htable.o
992  
993  
994  EXTRAOBJS = @OBJLIST@
995 Index: src/findlib/find.c
996 ===================================================================
997 --- src/findlib/find.c  (révision 6443)
998 +++ src/findlib/find.c  (copie de travail)
999 @@ -96,6 +96,13 @@
1000    Dmsg0(100, "Leave set_find_options()\n");
1001  }
1002  
1003 +void
1004 +set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff))
1005 +{
1006 +   Dmsg0(100, "Enter set_find_changed_function()\n");
1007 +   ff->check_fct = check_fct;
1008 +}
1009 +
1010  /*
1011   * For VSS we need to know which windows drives
1012   * are used, because we create a snapshot of all used
1013 Index: src/findlib/find_one.c
1014 ===================================================================
1015 --- src/findlib/find_one.c      (révision 6443)
1016 +++ src/findlib/find_one.c      (copie de travail)
1017 @@ -258,6 +258,33 @@
1018  }
1019  
1020  /*
1021 + * In incremental/diffential or accurate backup, we
1022 + * say if the current file has changed.
1023 + */
1024 +static bool check_changes(JCR *jcr, FF_PKT *ff_pkt)
1025 +{
1026 +   /* in special mode (like accurate backup), user can 
1027 +    * choose his comparison function.
1028 +    */
1029 +   if (ff_pkt->check_fct) {
1030 +      return ff_pkt->check_fct(jcr, ff_pkt);
1031 +   }
1032 +
1033 +   /* in normal modes (incr/diff), we use this default
1034 +    * behaviour
1035 +    */
1036 +   if (ff_pkt->incremental &&
1037 +       (ff_pkt->statp.st_mtime < ff_pkt->save_time &&
1038 +       ((ff_pkt->flags & FO_MTIMEONLY) ||
1039 +        ff_pkt->statp.st_ctime < ff_pkt->save_time))) 
1040 +   {
1041 +      return true;
1042 +   } else {
1043 +      return false;
1044 +   }
1045 +}
1046 +
1047 +/*
1048   * Find a single file.
1049   * handle_file is the callback for handling the file.
1050   * p is the filename
1051 @@ -333,16 +360,10 @@
1052      * since our last "save_time", presumably the last Full save
1053      * or Incremental.
1054      */
1055 -   if (ff_pkt->incremental && !S_ISDIR(ff_pkt->statp.st_mode)) {
1056 +   if (!S_ISDIR(ff_pkt->statp.st_mode) && !check_changes(jcr, ff_pkt)) {
1057        Dmsg1(300, "Non-directory incremental: %s\n", ff_pkt->fname);
1058 -      /* Not a directory */
1059 -      if (ff_pkt->statp.st_mtime < ff_pkt->save_time
1060 -          && ((ff_pkt->flags & FO_MTIMEONLY) ||
1061 -              ff_pkt->statp.st_ctime < ff_pkt->save_time)) {
1062 -         /* Incremental option, file not changed */
1063 -         ff_pkt->type = FT_NOCHG;
1064 -         return handle_file(jcr, ff_pkt, top_level);
1065 -      }
1066 +      ff_pkt->type = FT_NOCHG;
1067 +      return handle_file(jcr, ff_pkt, top_level);
1068     }
1069  
1070  #ifdef HAVE_DARWIN_OS
1071 @@ -502,15 +523,13 @@
1072        link[len] = 0;
1073  
1074        ff_pkt->link = link;
1075 -      if (ff_pkt->incremental &&
1076 -          (ff_pkt->statp.st_mtime < ff_pkt->save_time &&
1077 -             ((ff_pkt->flags & FO_MTIMEONLY) ||
1078 -               ff_pkt->statp.st_ctime < ff_pkt->save_time))) {
1079 +      if (check_changes(jcr, ff_pkt)) {
1080           /* Incremental option, directory entry not changed */
1081 -         ff_pkt->type = FT_DIRNOCHG;
1082 +         ff_pkt->type = FT_DIRNOCHG;    
1083        } else {
1084           ff_pkt->type = FT_DIRBEGIN;
1085        }
1086 +
1087        /* We have set st_rdev to 1 if it is a reparse point, otherwise 0 */
1088        if (have_win32_api() && ff_pkt->statp.st_rdev) {
1089           ff_pkt->type = FT_REPARSE;
1090 Index: src/findlib/find.h
1091 ===================================================================
1092 --- src/findlib/find.h  (révision 6443)
1093 +++ src/findlib/find.h  (copie de travail)
1094 @@ -108,6 +108,7 @@
1095  #define FO_ENHANCEDWILD (1<<23)       /* Enhanced wild card processing */
1096  #define FO_CHKCHANGES   (1<<24)       /* Check if file have been modified during backup */
1097  #define FO_STRIPPATH    (1<<25)       /* Check for stripping path */
1098 +#define FO_ACCURATE     (1<<26)       /* Accurate mode */
1099  
1100  struct s_included_file {
1101     struct s_included_file *next;
1102 @@ -215,6 +216,7 @@
1103     findFILESET *fileset;
1104     int (*file_save)(JCR *, FF_PKT *, bool); /* User's callback */
1105     int (*plugin_save)(JCR *, FF_PKT *, bool); /* User's callback */
1106 +   bool (*check_fct)(JCR *, FF_PKT *); /* optionnal user fct to check file changes */
1107  
1108     /* Values set by accept_file while processing Options */
1109     uint32_t flags;                    /* backup options */
1110 Index: src/findlib/protos.h
1111 ===================================================================
1112 --- src/findlib/protos.h        (révision 6443)
1113 +++ src/findlib/protos.h        (copie de travail)
1114 @@ -45,6 +45,7 @@
1115  /* From find.c */
1116  FF_PKT *init_find_files();
1117  void  set_find_options(FF_PKT *ff, int incremental, time_t mtime);
1118 +void set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff));
1119  int   find_files(JCR *jcr, FF_PKT *ff, int file_sub(JCR *, FF_PKT *ff_pkt, bool),
1120                   int plugin_sub(JCR *, FF_PKT *ff_pkt, bool));
1121  int   match_files(JCR *jcr, FF_PKT *ff, int sub(JCR *, FF_PKT *ff_pkt, bool));