]> git.sur5r.net Git - bacula/bacula/blob - bacula/patches/testing/project-accurate-backup.patch
ebl add full backup initialization
[bacula/bacula] / bacula / patches / testing / project-accurate-backup.patch
1 Index: src/dird/backup.c
2 ===================================================================
3 --- src/dird/backup.c   (révision 6374)
4 +++ src/dird/backup.c   (copie de travail)
5 @@ -44,6 +44,7 @@
6  #include "bacula.h"
7  #include "dird.h"
8  #include "ua.h"
9 +#include "findlib/find.h"
10  
11  /* Commands sent to File daemon */
12  static char backupcmd[] = "backup\n";
13 @@ -97,6 +98,411 @@
14  }
15  
16  /*
17 + * We are called here for each record that matches the above
18 + *  SQL query -- that is for each file contained in the Catalog
19 + *  that was not marked earlier. This means that the file in
20 + *  question is a missing file (in the Catalog but not on Disk).
21 + */
22 +static int missing_handler(void *ctx, int num_fields, char **row)
23 +{
24 +   JCR *jcr = (JCR *)ctx;
25 +
26 +   if (job_canceled(jcr)) {
27 +      return 1;
28 +   }
29 +
30 +   /* TODO: return the list to the FD */
31 +   if (num_fields == 2) 
32 +      Qmsg(jcr, M_INFO, 0, "      %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
33 +   else
34 +      Qmsg(jcr, M_INFO, 0, "      %s\n", row[0]?row[0]:"");
35
36 +   return 0;
37 +}
38 +
39 +/* TODO: tweak verify code to use the same function */
40 +bool accurate_check_file(JCR *jcr, FILE_DBR *fdbr, char *attr, char *Opts_Digest, int *do_Digest)
41 +{
42 +   char *p;
43 +   int stat=false;
44 +   struct stat statf;                 /* file stat */
45 +   struct stat statc;                 /* catalog stat */
46 +
47 +   int32_t LinkFIf, LinkFIc;
48 +
49 +   decode_stat(attr, &statf, &LinkFIf);  /* decode file stat packet */
50 +   decode_stat(fdbr->LStat, &statc, &LinkFIc); /* decode catalog stat */
51 +   *do_Digest = CRYPTO_DIGEST_NONE;
52 +
53 +   for (p=Opts_Digest; *p; p++) {
54 +      char ed1[30], ed2[30];
55 +      switch (*p) {
56 +      case 'i':                /* compare INODEs */
57 +        if (statc.st_ino != statf.st_ino) {
58 +           Jmsg(jcr, M_INFO, 0, _("      st_ino   differ. Cat: %s File: %s\n"),
59 +                edit_uint64((uint64_t)statc.st_ino, ed1),
60 +                edit_uint64((uint64_t)statf.st_ino, ed2));
61 +           stat = true;
62 +        }
63 +        break;
64 +      case 'p':                /* permissions bits */
65 +        if (statc.st_mode != statf.st_mode) {
66 +           Jmsg(jcr, M_INFO, 0, _("      st_mode  differ. Cat: %x File: %x\n"),
67 +                (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
68 +           stat = true;
69 +        }
70 +        break;
71 +      case 'n':                /* number of links */
72 +        if (statc.st_nlink != statf.st_nlink) {
73 +           Jmsg(jcr, M_INFO, 0, _("      st_nlink differ. Cat: %d File: %d\n"),
74 +                (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
75 +           stat = true;
76 +        }
77 +        break;
78 +      case 'u':                /* user id */
79 +        if (statc.st_uid != statf.st_uid) {
80 +           Jmsg(jcr, M_INFO, 0, _("      st_uid   differ. Cat: %u File: %u\n"),
81 +                (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
82 +           stat = true;
83 +        }
84 +        break;
85 +      case 'g':                /* group id */
86 +        if (statc.st_gid != statf.st_gid) {
87 +           Jmsg(jcr, M_INFO, 0, _("      st_gid   differ. Cat: %u File: %u\n"),
88 +                (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
89 +           stat = true;
90 +        }
91 +        break;
92 +      case 's':                /* size */
93 +        if (statc.st_size != statf.st_size) {
94 +           Jmsg(jcr, M_INFO, 0, _("      st_size  differ. Cat: %s File: %s\n"),
95 +                edit_uint64((uint64_t)statc.st_size, ed1),
96 +                edit_uint64((uint64_t)statf.st_size, ed2));
97 +           stat = true;
98 +        }
99 +        break;
100 +      case 'a':                /* access time */
101 +        if (statc.st_atime != statf.st_atime) {
102 +           Jmsg(jcr, M_INFO, 0, _("      st_atime differs\n"));
103 +           stat = true;
104 +        }
105 +        break;
106 +      case 'm':
107 +        if (statc.st_mtime != statf.st_mtime) {
108 +           Jmsg(jcr, M_INFO, 0, _("      st_mtime differs\n"));
109 +           stat = true;
110 +        }
111 +        break;
112 +      case 'c':                /* ctime */
113 +        if (statc.st_ctime != statf.st_ctime) {
114 +           Jmsg(jcr, M_INFO, 0, _("      st_ctime differs\n"));
115 +           stat = true;
116 +        }
117 +        break;
118 +      case 'd':                /* file size decrease */
119 +        if (statc.st_size > statf.st_size) {
120 +           Jmsg(jcr, M_INFO, 0, _("      st_size  decrease. Cat: %s File: %s\n"),
121 +                edit_uint64((uint64_t)statc.st_size, ed1),
122 +                edit_uint64((uint64_t)statf.st_size, ed2));
123 +           stat = true;
124 +        }
125 +        break;
126 +      case '5':                /* compare MD5 */
127 +        Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
128 +        *do_Digest = CRYPTO_DIGEST_MD5;
129 +        break;
130 +      case '1':                 /* compare SHA1 */
131 +        *do_Digest = CRYPTO_DIGEST_SHA1;
132 +        break;
133 +      case ':':
134 +      case 'V':
135 +      default:
136 +        break;
137 +      }
138 +   }
139 +   return stat;
140 +}
141 +
142 +/*
143 + * This function is called at EOJ.
144 + *  For a Full backup, we remove old one, and we add all entries 
145 + *  For an Incremental, we add all entries (delete have been before)
146 + *  For a Differential, we add all entries (delete have been before)
147 + * 
148 + * TODO: 
149 + *      
150 + */
151 +bool accurate_update_current_files(JCR *jcr)
152 +{
153 +   JobId_t backupid;
154 +/*
155 +   if (jcr->accurate == false) {
156 +      return true;
157 +   }
158 +*/
159 +   backupid = db_accurate_find_backupid(jcr, jcr->db, &jcr->jr);
160 +
161 +   Dmsg1(1, "backupid = %i\n", backupid);
162 +
163 +   if (!backupid) {
164 +      return false;            /* something goes wrong */
165 +   }
166 +
167 +   if (jcr->JobLevel == L_FULL) {
168 +      db_accurate_cleanup_currentfile(jcr, jcr->db, backupid);
169 +   }
170 +
171 +   db_accurate_update_currentfile(jcr, jcr->db, jcr->JobId, 
172 +                                 jcr->JobLevel, backupid);
173 +   return true;
174 +}
175 +
176 +/*
177 + * We are called here for each record that matches the above
178 + *  SQL query -- that is for each file contained in the Catalog
179 + *  that was not marked earlier. This means that the file in
180 + *  question is a missing file (in the Catalog but not on Disk).
181 + */
182 +static int accurate_handler(void *ctx, int num_fields, char **row)
183 +{
184 +   JCR *jcr = (JCR *)ctx;
185 +
186 +   if (job_canceled(jcr)) {
187 +      return 1;
188 +   }
189 +   if (num_fields == 2) {      /* deleted files */
190 +      jcr->file_bsock->fsend("D %s%s", row[0]?row[0]:"", row[1]?row[1]:""); 
191 +   } else if (num_fields == 1) { /* files to backup */
192 +      jcr->file_bsock->fsend("S %s", row[0]?row[0]:""); 
193 +   }
194 +   return 0;
195 +}
196 +
197 +/*
198 + * Send deleted files and files to backup in accurate mode
199 + *
200 + */
201 +static int accurate_send_missing_and_deleted_files(JCR *jcr, JobId_t BackupId)
202 +{
203 +   char buf[MAXSTRING];
204 +   char ed1[50], ed2[50];
205 +
206 +   bsnprintf(buf, sizeof(buf),
207 +      "SELECT Path.Path,Filename.Name "
208 +        "FROM CurrentFile "
209 +             "JOIN File USING (FileId) "
210 +             "JOIN Path USING (PathId) "
211 +             "JOIN Filename USING (FilenameId) "
212 +      "WHERE CurrentFile.BackupId=%s "
213 +        "AND CurrentFile.MarkId!=%s ",
214 +            edit_uint64(BackupId, ed1), edit_uint64(jcr->JobId, ed2));
215 +   /* missing_handler is called for each file found */
216 +   Dmsg1(2, "display deleted files cmd=%s\n", buf);
217 +   db_sql_query(jcr->db, buf, accurate_handler, (void *)jcr);
218 +   jcr->file_bsock->signal(BNET_EOD);
219 +   
220 +   bsnprintf(buf, sizeof(buf),
221 +            "SELECT Name FROM ToBackup%s",
222 +            edit_uint64(jcr->JobId, ed2));
223 +   /* missing_handler is called for each file found */
224 +   Dmsg1(2, "display files to backup cmd=%s\n", buf);
225 +   db_sql_query(jcr->db, buf, accurate_handler, (void *)jcr);
226 +   jcr->file_bsock->signal(BNET_EOD);
227 +
228 +   return 1;
229 +}
230 +
231 +/*
232 + * Accurate backup mode
233 + * 1. Receive the list of all files including those backed up to the Dir
234 + * 2. Dir computes files and deleted files.
235 + * 3. Dir sends list of additional files (new files) to backup, and list of files
236 + *    deleted.
237 + *
238 + * Cleanup attributes (don't use atime, inode etc..)
239 + * Need to insert file and attributes to temp table ?
240 + * Batch compare files and attributes ?
241 + *
242 + * If file have file_index=0, they are discarded by FD
243 + *
244 + * TODO: send deleted list and new list to client
245 + *       tweak SD with file_index=-1
246 + */
247 +bool accurate_compute_files(JCR *jcr)
248 +{
249 +   BSOCK   *fd;
250 +   char buf[MAXSTRING];
251 +   int n, len;
252 +   FILE_DBR fdbr;
253 +   POOLMEM *fname = get_pool_memory(PM_MESSAGE);
254 +   int do_Digest = CRYPTO_DIGEST_NONE;
255 +   int32_t file_index = 0;
256 +   JobId_t JobId=0;            /* TODO: compute the job key in new table */
257 +   JobId_t backupid=0;
258 +
259 +   memset(&fdbr, 0, sizeof(FILE_DBR));
260 +   fd = jcr->file_bsock;
261 +   fdbr.JobId = JobId;         
262 +   jcr->FileIndex = 0;
263 +
264 +   if (/*jcr->accurate == false ||*/ jcr->JobLevel == L_FULL) {
265 +      return true;
266 +   }
267 +
268 +   backupid = db_accurate_find_backupid(jcr, jcr->db, &jcr->jr);
269 +   if (!backupid) {
270 +      Jmsg(jcr, M_ERROR, 0, _("Can't use Accurate mode ERR=Can't find BackupId\n"));
271 +      return false;
272 +   }
273 +   db_accurate_create_tobackup_table(jcr, jcr->db, jcr->JobId);
274 +   Dmsg0(1, "bdird: waiting to receive file attributes\n");
275 +   /*
276 +    * Get Attributes and Signature from File daemon
277 +    * We expect:
278 +    *   FileIndex
279 +    *   Stream
280 +    *   Options or Digest (MD5/SHA1)
281 +    *   Filename
282 +    *   Attributes
283 +    *   Link name  ???
284 +    */
285 +   while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
286 +      int stream;
287 +      char *attr, *p, *fn;
288 +      char Opts_Digest[MAXSTRING];        /* Verify Opts or MD5/SHA1 digest */
289 +
290 +      if (job_canceled(jcr)) {
291 +         goto bail_out2;
292 +      }
293 +      fname = check_pool_memory_size(fname, fd->msglen);
294 +      jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
295 +      Dmsg1(1, "Atts+Digest=%s\n", fd->msg);
296 +      if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
297 +            fname)) != 3) {
298 +         Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
299 +" mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
300 +         goto bail_out2;
301 +      }
302 +      /*
303 +       * We read the Options or Signature into fname
304 +       *  to prevent overrun, now copy it to proper location.
305 +       */
306 +      bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
307 +      p = fd->msg;
308 +      skip_nonspaces(&p);             /* skip FileIndex */
309 +      skip_spaces(&p);
310 +      skip_nonspaces(&p);             /* skip Stream */
311 +      skip_spaces(&p);
312 +      skip_nonspaces(&p);             /* skip Opts_Digest */
313 +      p++;                            /* skip space */
314 +      fn = fname;
315 +      while (*p != 0) {
316 +         *fn++ = *p++;                /* copy filename */
317 +      }
318 +      *fn = *p++;                     /* term filename and point to attribs */
319 +      attr = p;
320 +      /*
321 +       * Got attributes stream, decode it
322 +       */
323 +      if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
324 +        int changed=true;
325 +         Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
326 +         jcr->JobFiles++;
327 +         jcr->FileIndex = file_index;    /* remember attribute file_index */
328 +         do_Digest = CRYPTO_DIGEST_NONE;
329 +         pm_strcpy(jcr->fname, fname);  /* move filename into JCR */
330 +        fdbr.FileId = 0;
331 +
332 +         /*
333 +          * Find equivalent record in the database
334 +          */
335 +        
336 +        if (db_accurate_get_file_attributes_record(jcr, jcr->db, jcr->fname,
337 +                                                   backupid, &fdbr))
338 +        {
339 +           Dmsg2(1, "get_file ok fname=%s fileid=%i\n", jcr->fname, fdbr.FileId);
340 +           if (fdbr.MarkId != jcr->JobId) {           /* Already visited ? */
341 +              if (file_index == 0) { /* file not saved */
342 +                 changed = accurate_check_file(jcr, &fdbr, attr, Opts_Digest, &do_Digest);
343 +                 Dmsg1(1, "check_file changed=%i\n", changed);
344 +                 
345 +                 if (changed == true) {
346 +                    db_accurate_mark_file_for_backup(jcr, jcr->db, jcr->fname, jcr->JobId);
347 +                    db_accurate_delete_file_record(jcr, jcr->db, fdbr.FileId, backupid);
348 +                 } else {
349 +                    db_accurate_mark_file_record(jcr, jcr->db, backupid,
350 +                                                 fdbr.FileId, jcr->JobId);
351 +                 }
352 +              } else {         /* file_index != 0 file have be backuped */
353 +                 db_accurate_delete_file_record(jcr, jcr->db, fdbr.FileId, backupid);
354 +              }
355 +           }
356 +        } else if (file_index == 0) {
357 +              Dmsg1(1, "mark_for_backup fname=%s\n", jcr->fname);
358 +              db_accurate_mark_file_for_backup(jcr, jcr->db, jcr->fname, jcr->JobId);
359 +        }
360 +      
361 +      /*
362 +       * Got Digest Signature from Storage daemon
363 +       *  It came across in the Opts_Digest field.
364 +       */
365 +        /* not used */
366 +      } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
367 +         Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
368 +         /*
369 +          * When ever we get a digest it MUST have been
370 +          * preceded by an attributes record, which sets attr_file_index
371 +          */
372 +         if (jcr->FileIndex != (uint32_t)file_index) {
373 +            Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
374 +               file_index, jcr->FileIndex);
375 +            goto bail_out2;
376 +         }
377 +         if (do_Digest != CRYPTO_DIGEST_NONE) {
378 +            db_escape_string(jcr, jcr->db, buf, Opts_Digest, strlen(Opts_Digest));
379 +            if (strcmp(buf, fdbr.Digest) != 0) {
380 +               if (debug_level >= 10) {
381 +                  Jmsg(jcr, M_INFO, 0, _("      %d not same. File=%s Cat=%s\n"),
382 +                       stream, buf, fdbr.Digest);
383 +               } else {
384 +                  Jmsg(jcr, M_INFO, 0, _("      %d differs.\n"),
385 +                       stream);
386 +               }
387 +               //stat = JS_Differences;
388 +            }
389 +            do_Digest = CRYPTO_DIGEST_NONE;
390 +         }
391 +      }
392 +//      jcr->JobFiles = file_index;
393 +   }
394 +   if (is_bnet_error(fd)) {
395 +      berrno be;
396 +      Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
397 +                        n, be.bstrerror());
398 +      goto bail_out2;
399 +   }
400 +
401 +/*
402 +CREATE  VIEW cf AS SELECT path.path || filename.name as filename, 
403 +       jobid, currentfile.markid, backupid 
404 +  FROM File join currentfile using (fileid) join filename using (filenameid) join path using (pathid)
405 +*/
406 +
407 +   accurate_send_missing_and_deleted_files(jcr, backupid);
408 +
409 +   db_accurate_clean_deleted_files(jcr, jcr->db, jcr->JobId, backupid);
410 +
411 +   db_accurate_drop_tobackup_table(jcr, jcr->db, jcr->JobId);
412 +
413 +   free_pool_memory(fname);
414 +   return true;
415 +
416 +bail_out2:
417 +   db_accurate_drop_tobackup_table(jcr, jcr->db, jcr->JobId);
418 +   return false;
419 +}
420 +
421 +/*
422   * Do a backup of the specified FileSet
423   *
424   *  Returns:  false on failure
425 @@ -231,9 +637,18 @@
426        goto bail_out;
427     }
428  
429 +   /*
430 +    * If backup is in accurate mode, FD will send the list of
431 +    * all files. We have to store it, and compute witch files
432 +    * have been deleted and witch files have to be backuped.
433 +    */
434 +   accurate_compute_files(jcr);
435 +
436     /* Pickup Job termination data */
437     stat = wait_for_job_termination(jcr);
438     db_write_batch_file_records(jcr);    /* used by bulk batch file insert */
439 +   accurate_update_current_files(jcr);
440 +
441     if (stat == JS_Terminated) {
442        backup_cleanup(jcr, stat);
443        return true;
444 Index: src/dird/inc_conf.c
445 ===================================================================
446 --- src/dird/inc_conf.c (révision 6374)
447 +++ src/dird/inc_conf.c (copie de travail)
448 @@ -94,6 +94,7 @@
449   * Items that are valid in an Options resource
450   */
451  static RES_ITEM options_items[] = {
452 +   {"accurate",        store_opts,    {0},     0, 0, 0},
453     {"compression",     store_opts,    {0},     0, 0, 0},
454     {"signature",       store_opts,    {0},     0, 0, 0},
455     {"verify",          store_opts,    {0},     0, 0, 0},
456 @@ -153,7 +154,8 @@
457     INC_KW_NOATIME,
458     INC_KW_ENHANCEDWILD,
459     INC_KW_CHKCHANGES,
460 -   INC_KW_STRIPPATH
461 +   INC_KW_STRIPPATH,
462 +   INC_KW_ACCURATE
463  };
464  
465  /*
466 @@ -163,6 +165,7 @@
467   *   options given above.
468   */
469  static struct s_kw FS_option_kw[] = {
470 +   {"accurate",    INC_KW_ACCURATE},
471     {"compression", INC_KW_COMPRESSION},
472     {"signature",   INC_KW_DIGEST},
473     {"encryption",  INC_KW_ENCRYPTION},
474 @@ -251,6 +254,8 @@
475     {"no",       INC_KW_ENHANCEDWILD,  "0"},
476     {"yes",      INC_KW_CHKCHANGES,    "c"},
477     {"no",       INC_KW_CHKCHANGES,    "0"},
478 +   {"yes",      INC_KW_ACCURATE,      "C"},
479 +   {"no",       INC_KW_ACCURATE,      "0"},
480     {NULL,       0,                      0}
481  };
482  
483 Index: src/dird/dird_conf.c
484 ===================================================================
485 --- src/dird/dird_conf.c        (révision 6374)
486 +++ src/dird/dird_conf.c        (copie de travail)
487 @@ -319,6 +319,7 @@
488     {"selectionpattern", store_str, ITEM(res_job.selection_pattern), 0, 0, 0},
489     {"runscript", store_runscript, ITEM(res_job.RunScripts), 0, ITEM_NO_EQUALS, 0},
490     {"selectiontype", store_migtype, ITEM(res_job.selection_type), 0, 0, 0},
491 +   {"accuratebackup", store_bool, ITEM(res_job.accurate), 0,0,0},
492     {NULL, NULL, {0}, 0, 0, 0}
493  };
494  
495 @@ -618,6 +619,9 @@
496        if (res->res_job.spool_size) {
497           sendit(sock, _("     SpoolSize=%s\n"),        edit_uint64(res->res_job.spool_size, ed1));
498        }
499 +      if (res->res_job.JobType == JT_BACKUP) {
500 +        sendit(sock, _("     Accurate=%d\n"), res->res_job.accurate);
501 +      }
502        if (res->res_job.JobType == JT_MIGRATE) {
503           sendit(sock, _("     SelectionType=%d\n"), res->res_job.selection_type);
504        }
505 Index: src/dird/dird_conf.h
506 ===================================================================
507 --- src/dird/dird_conf.h        (révision 6374)
508 +++ src/dird/dird_conf.h        (copie de travail)
509 @@ -400,6 +400,7 @@
510     bool write_part_after_job;         /* Set to write part after job in SD */
511     bool enabled;                      /* Set if job enabled */
512     bool OptimizeJobScheduling;        /* Set if we should optimize Job scheduling */
513 +   bool accurate;                     /* Set if it is an accurate backup job */
514     
515     MSGS      *messages;               /* How and where to send messages */
516     SCHED     *schedule;               /* When -- Automatic schedule */
517 Index: src/filed/backup.c
518 ===================================================================
519 --- src/filed/backup.c  (révision 6374)
520 +++ src/filed/backup.c  (copie de travail)
521 @@ -50,6 +50,109 @@
522  static bool crypto_session_send(JCR *jcr, BSOCK *sd);
523  
524  /*
525 + * Called by save_file when accept/discard file for backup
526 + *
527 + * TODO: we could add MD5/SHAX digest, but we have to compute it
528 + * for all files.
529 + */
530 +static bool accurate_add_file(JCR *jcr, FF_PKT *ff_pkt, char *stats)
531 +{
532 +   char *a=stats;
533 +   char attribs[MAXSTRING];
534 +   uint32_t file_index=jcr->JobFiles;
535 +   BSOCK *dir = jcr->dir_bsock;
536 +   int stat;
537 +
538 +   if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
539 +      return true;
540 +   }
541 +
542 +   if (!stats) {               /* TODO: don't always compute attribute  */
543 +      file_index=0;
544 +      encode_stat(attribs, ff_pkt, 0);
545 +      a = attribs;
546 +   }
547 +
548 +   switch (ff_pkt->type) {
549 +   case FT_LNKSAVED:                  /* Hard linked, file already saved */
550 +   case FT_LNK:
551 +      stat = dir->fsend("%d %d %s %s%c%s%c%s%c", file_index,
552 +            STREAM_UNIX_ATTRIBUTES, ff_pkt->VerifyOpts, ff_pkt->fname,
553 +            0, a, 0, ff_pkt->link, 0);
554 +      break;
555 +   case FT_REGE:
556 +   case FT_REG:
557 +   case FT_SPEC:
558 +   case FT_RAW:
559 +   case FT_FIFO:
560 +   case FT_NOCHG:
561 +      stat = dir->fsend("%d %d %s %s%c%s%c%c", file_index,
562 +            STREAM_UNIX_ATTRIBUTES, ff_pkt->VerifyOpts, ff_pkt->fname,
563 +            0, a, 0, 0);
564 +
565 +      break;
566 +   case FT_REPARSE: 
567 +   case FT_DIREND:
568 +   case FT_NORECURSE:
569 +   case FT_DIRNOCHG:
570 +      stat = dir->fsend("%d %d %s %s%c%s%c%c", file_index,
571 +               STREAM_UNIX_ATTRIBUTES, ff_pkt->VerifyOpts, ff_pkt->link,
572 +               0, a, 0, 0);
573 +      break;
574 +   default:
575 +      Dmsg2(1, _("Fname=%s Type=%i\n"), ff_pkt->fname, ff_pkt->type);
576 +      return true;
577 +   }
578 +
579 +   if (!stat) {
580 +      Jmsg(jcr, M_FATAL, 0, _("Network error in send to Director: ERR=%s\n"), bnet_strerror(dir));
581 +      return 0;
582 +   }
583 +
584 +   return true;
585 +}
586 +
587 +/* build a fileset with new files from director */
588 +static bool accurate_get_new_and_deleted_file_list(JCR *jcr)
589 +{   
590 +   BSOCK *dir = jcr->dir_bsock;
591 +   if (jcr->accurate == false || job_canceled(jcr)) {
592 +      return true;
593 +   }
594 +
595 +   /* get deleted files */
596 +   while (dir->recv() >= 0) {
597 +      Dmsg1(1, "deleted = %s\n", dir->msg);
598 +   }
599 +   /* get missing files */
600 +   while (dir->recv() >= 0) {
601 +      Dmsg1(1, "missing = %s\n", dir->msg);
602 +   }
603 +   
604 +   return true;
605 +}
606 +
607 +/* send deleted file list to stored */
608 +static bool accurate_send_deleted_list(JCR *jcr)
609 +{
610 +   if (jcr->accurate == false || job_canceled(jcr)) {
611 +      return true;
612 +   }
613 +   return true;
614 +}
615 +
616 +static bool accurate_send_file_list(JCR *jcr)
617 +{
618 +   if (jcr->accurate == false || job_canceled(jcr)) {
619 +      return true;
620 +   }
621 +   Dmsg0(1, "Sending BNET_EOD\n");
622 +   jcr->dir_bsock->signal(BNET_EOD);            /* end of sending data */
623 +   return true;
624 +}
625 +
626 +
627 +/*
628   * Find all the requested files and send them
629   * to the Storage daemon.
630   *
631 @@ -66,6 +169,9 @@
632     BSOCK *sd;
633     bool ok = true;
634     // TODO landonf: Allow user to specify encryption algorithm
635 +   if (jcr->JobLevel != L_FULL) {
636 +      jcr->accurate=true;              /* TODO: remove that */
637 +   }
638  
639     sd = jcr->store_bsock;
640  
641 @@ -134,6 +240,20 @@
642        ok = false;                     /* error */
643        set_jcr_job_status(jcr, JS_ErrorTerminated);
644     }
645 +   Dmsg1(1, "jcr->accurate == %i\n", jcr->accurate);
646 +   /* start accurate stuffs */
647 +   if (jcr->accurate) {
648 +      /* TODO: test job_canceled() */
649 +      accurate_send_file_list(jcr);                 /* send all files to DIR */
650 +      accurate_get_new_and_deleted_file_list(jcr);  /* get a new incr fileset from DIR */
651 +//      set_find_options((FF_PKT *)jcr->ff, 0, 0);            /* we backup all that director wants */
652 +//      if (!find_files(jcr, (FF_PKT *)jcr->ff, save_file, (void *)jcr)) {
653 +//      ok = false;                     /* error */
654 +//      set_jcr_job_status(jcr, JS_ErrorTerminated);
655 +//      }
656 +//      accurate_send_file_list(jcr);                 /* send all new files to DIR */
657 +      accurate_send_deleted_list(jcr);              /* send deleted list to SD  */
658 +   }
659  
660     free_pool_memory(jcr->acl_text);
661  
662 @@ -355,9 +475,11 @@
663     case FT_DIRNOCHG:
664     case FT_NOCHG:
665        Jmsg(jcr, M_SKIPPED, 1, _("     Unchanged file skipped: %s\n"), ff_pkt->fname);
666 +      accurate_add_file(jcr, ff_pkt, NULL); /* list skipped files */
667        return 1;
668     case FT_ISARCH:
669        Jmsg(jcr, M_NOTSAVED, 0, _("     Archive file not saved: %s\n"), ff_pkt->fname);
670 +      accurate_add_file(jcr, ff_pkt, NULL); /* list skipped files */
671        return 1;
672     case FT_NOOPEN: {
673        berrno be;
674 @@ -1111,6 +1233,9 @@
675     }
676     unstrip_path(ff_pkt);
677  
678 +   /* list backuped files */
679 +   accurate_add_file(jcr, ff_pkt, attribs);
680 +
681     Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg);
682     if (!stat) {
683        Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
684 Index: src/filed/job.c
685 ===================================================================
686 --- src/filed/job.c     (révision 6374)
687 +++ src/filed/job.c     (copie de travail)
688 @@ -1087,6 +1087,9 @@
689        case 'c':
690           fo->flags |= FO_CHKCHANGES;
691           break;
692 +      case 'C':
693 +         fo->flags |= FO_ACCURATE;
694 +         break;
695        default:
696           Emsg1(M_ERROR, 0, _("Unknown include/exclude option: %c\n"), *p);
697           break;
698 Index: src/cats/sql_update.c
699 ===================================================================
700 --- src/cats/sql_update.c       (révision 6374)
701 +++ src/cats/sql_update.c       (copie de travail)
702 @@ -88,6 +88,102 @@
703     return stat;
704  }
705  
706 +int db_accurate_delete_file_record(JCR *jcr, B_DB *mdb, FileId_t FileId, JobId_t BackupId)
707 +{
708 +   int stat;
709 +   char ed1[50], ed2[50];
710 +   db_lock(mdb);
711 +   Mmsg(mdb->cmd, "DELETE FROM CurrentFile WHERE FileId=%s AND BackupId=%s",
712 +       edit_int64(FileId, ed1), edit_int64(BackupId, ed2));
713 +   stat = INSERT_DB(jcr, mdb, mdb->cmd);
714 +   db_unlock(mdb);
715 +   return stat;
716 +}
717 +
718 +int db_accurate_mark_file_for_backup(JCR *jcr, B_DB *mdb, char *fname, JobId_t JobId)
719 +{
720 +   int stat;
721 +   char ed1[50];
722 +   db_lock(mdb);
723 +   /* TODO: mdb->esc_xxx are already ok but it's more smart to recompute it */
724 +//   mdb->esc_name = check_pool_memory_size(mdb->esc_name, 2*len+2);
725 +//   mdb->esc_name = db_escape_string(jcr, mdb, mdb->esc_name, fname, len);
726 +   Mmsg(mdb->cmd, "INSERT INTO ToBackup%s (name) VALUES ('%s%s')", edit_int64(JobId, ed1), mdb->esc_path, mdb->esc_name);
727 +   stat = INSERT_DB(jcr, mdb, mdb->cmd);
728 +   db_unlock(mdb);
729 +   return stat;
730 +}
731 +
732 +int db_accurate_cleanup_currentfile(JCR *jcr, B_DB *mdb, JobId_t BackupId)
733 +{
734 +   int stat;
735 +   char ed1[50];
736 +   db_lock(mdb);
737 +   Mmsg(mdb->cmd, "DELETE FROM CurrentFile WHERE BackupId=%s", edit_int64(BackupId, ed1));
738 +   stat = QUERY_DB(jcr, mdb, mdb->cmd);
739 +   db_unlock(mdb);
740 +   return stat;
741 +}
742 +
743 +int db_accurate_update_currentfile(JCR *jcr, B_DB *mdb, JobId_t JobId, int JobLevel, JobId_t BackupId)
744 +{
745 +   int stat;
746 +   char ed1[50], ed2[50], ed3[50];
747 +   db_lock(mdb);
748 +   edit_int64(JobId, ed2);
749 +   Mmsg(mdb->cmd, 
750 +       "INSERT INTO CurrentFile (FileId, BackupId, FullMark, MarkId) "
751 +       " (SELECT FileId, %s, '%c', %s FROM File WHERE JobId=%s)", 
752 +       edit_int64(BackupId, ed1),
753 +       JobLevel, ed2, ed2);
754 +   stat = QUERY_DB(jcr, mdb, mdb->cmd);
755 +   db_unlock(mdb);
756 +   return stat; 
757 +}
758 +
759 +int db_accurate_create_tobackup_table(JCR *jcr, B_DB *mdb, JobId_t JobId)
760 +{
761 +   int stat;
762 +   char ed1[50];
763 +   db_lock(mdb);
764 +   Mmsg(mdb->cmd, "CREATE TABLE ToBackup%s (name text)", edit_int64(JobId, ed1));
765 +//   Mmsg(mdb->cmd, "CREATE TEMPORARY TABLE ToBackup%s (name text)", edit_int64(JobId, ed1));
766 +   stat = QUERY_DB(jcr, mdb, mdb->cmd);
767 +   db_unlock(mdb);
768 +   return stat;
769 +}
770 +
771 +int db_accurate_drop_tobackup_table(JCR *jcr, B_DB *mdb, JobId_t JobId)
772 +{
773 +   int stat=0;
774 +   char ed1[50];
775 +   db_lock(mdb);
776 +//   Mmsg(mdb->cmd, "DROP TABLE ToBackup%s", edit_int64(JobId, ed1));
777 +//   stat = QUERY_DB(jcr, mdb, mdb->cmd);
778 +   db_unlock(mdb);
779 +   return stat;
780 +}
781 +
782 +
783 +/* Mark the file record as being visited during database
784 + * accurate compare. Stuff JobId into the MarkId field
785 + */
786 +int db_accurate_mark_file_record(JCR *jcr, B_DB *mdb, JobId_t BackupId, FileId_t FileId, JobId_t JobId)
787 +{
788 +   int stat;
789 +   char ed1[50], ed2[50], ed3[50];
790 +
791 +   db_lock(mdb);
792 +   Mmsg(mdb->cmd, "UPDATE CurrentFile SET MarkId=%s WHERE FileId=%s AND BackupId=%s", 
793 +       edit_int64(JobId, ed1), edit_int64(FileId, ed2), edit_int64(BackupId, ed3));
794 +   stat = QUERY_DB(jcr, mdb, mdb->cmd);
795 +   if (!stat || sql_affected_rows(mdb) != 1) {
796 +      stat = 0;
797 +   }
798 +   db_unlock(mdb);
799 +   return stat;
800 +}
801 +
802  /*
803   * Update the Job record at start of Job
804   *
805 Index: src/cats/make_postgresql_tables.in
806 ===================================================================
807 --- src/cats/make_postgresql_tables.in  (révision 6374)
808 +++ src/cats/make_postgresql_tables.in  (copie de travail)
809 @@ -43,6 +43,59 @@
810  CREATE INDEX file_jobid_idx on file (jobid);
811  CREATE INDEX file_fp_idx on file (filenameid, pathid);
812  
813 +CREATE TABLE CurrentBackupId
814 +(
815 +     BackupId          serial     not null,
816 +     ClientId          integer    not null,
817 +     JobName           text       not null,
818 +     FileSetId         integer    not null,
819 +     primary key (BackupId)
820 +);
821 +
822 +-- Serait bien de prendre la meme table pour
823 +-- les File et le CurrentBackup...
824 +-- Mais y'a des problemes pour les prunes
825 +
826 +CREATE TABLE CurrentFile
827 +(
828 +     FileId           integer    not null,
829 +     BackupId         integer    not null,
830 +     FullMark         char(1)    default 0,
831 +     MarkId           integer    default 0,
832 +     primary key (FileId)
833 +);
834 +
835 +CREATE INDEX currentfile_fileid on CurrentFile (BackupId);
836 +
837 +-- CREATE TEMPORARY TABLE batch (fileindex int,
838 +--                               jobid int,
839 +--                               path varchar,
840 +--                               name varchar,
841 +--                               lstat varchar,
842 +--                               md5 varchar);
843 +-- 
844 +-- -- On batch insert dans la table temporaire
845 +
846 +-- il faut trouver les fichiers manquant
847 +-- INSERT des nouveaux, UPDATE des anciens, SELECT pour trouver les deletes
848 +
849 +
850 +-- il faut trouver les fichiers modifies
851 +-- Le champs LStat n'est plus le meme
852 +-- SELECT * 
853 +--   FROM CurrentBackup, 
854 +--        batch JOIN Path USING (Path) JOIN Filename USING (Name)
855 +--  WHERE Path.PathId = CurrentBackup.PathId
856 +--    AND Filename.FilenameId = CurrentBackup.FilenameId
857 +--    AND CurrentBackup.LStat != batch.LStat
858 +-- 
859 +-- il faut mettre a jour la liste des fichiers
860 +
861 +
862 +
863 +
864 +
865 +
866  --
867  -- Possibly add one or more of the following indexes
868  --  if your Verifies are too slow.
869 Index: src/cats/protos.h
870 ===================================================================
871 --- src/cats/protos.h   (révision 6374)
872 +++ src/cats/protos.h   (copie de travail)
873 @@ -78,14 +78,17 @@
874  /* sql_delete.c */
875  int db_delete_pool_record(JCR *jcr, B_DB *db, POOL_DBR *pool_dbr);
876  int db_delete_media_record(JCR *jcr, B_DB *mdb, MEDIA_DBR *mr);
877 +int db_accurate_clean_deleted_files(JCR *jcr, B_DB *mdb, JobId_t JobId, JobId_t BackupId);
878  
879  /* sql_find.c */
880  bool db_find_job_start_time(JCR *jcr, B_DB *mdb, JOB_DBR *jr, POOLMEM **stime);
881  bool db_find_last_jobid(JCR *jcr, B_DB *mdb, const char *Name, JOB_DBR *jr);
882 +JobId_t db_accurate_find_backupid(JCR *jcr, B_DB *mdb, JOB_DBR *jr);
883  int db_find_next_volume(JCR *jcr, B_DB *mdb, int index, bool InChanger, MEDIA_DBR *mr);
884  bool db_find_failed_job_since(JCR *jcr, B_DB *mdb, JOB_DBR *jr, POOLMEM *stime, int &JobLevel);
885  
886  /* sql_get.c */
887 +int db_accurate_get_file_attributes_record(JCR *jcr, B_DB *mdb, char *fname, JobId_t backupid, FILE_DBR *fdbr);
888  bool db_get_pool_record(JCR *jcr, B_DB *db, POOL_DBR *pdbr);
889  int db_get_client_record(JCR *jcr, B_DB *mdb, CLIENT_DBR *cr);
890  bool db_get_job_record(JCR *jcr, B_DB *mdb, JOB_DBR *jr);
891 @@ -129,6 +132,14 @@
892  int  db_update_counter_record(JCR *jcr, B_DB *mdb, COUNTER_DBR *cr);
893  int  db_add_digest_to_file_record(JCR *jcr, B_DB *mdb, FileId_t FileId, char *digest, int type);
894  int  db_mark_file_record(JCR *jcr, B_DB *mdb, FileId_t FileId, JobId_t JobId);
895 +int db_accurate_mark_file_for_backup(JCR *jcr, B_DB *mdb, char *fname, FileId_t JobId);
896 +int db_accurate_mark_file_record(JCR *jcr, B_DB *mdb, JobId_t BackupId, FileId_t FileId, JobId_t JobId);
897 +int db_accurate_drop_tobackup_table(JCR *jcr, B_DB *mdb, JobId_t JobId);
898 +int db_accurate_create_tobackup_table(JCR *jcr, B_DB *mdb, JobId_t JobId);
899 +int db_accurate_delete_file_record(JCR *jcr, B_DB *mdb, FileId_t FileId, JobId_t BackupId);
900  void db_make_inchanger_unique(JCR *jcr, B_DB *mdb, MEDIA_DBR *mr);
901 +int db_accurate_cleanup_currentfile(JCR *jcr, B_DB *mdb, JobId_t BackupId);
902 +int db_accurate_update_currentfile(JCR *jcr, B_DB *mdb, JobId_t JobId, int JobLevel, JobId_t BackupId);
903  
904 +
905  #endif /* __SQL_PROTOS_H */
906 Index: src/cats/sql_find.c
907 ===================================================================
908 --- src/cats/sql_find.c (révision 6374)
909 +++ src/cats/sql_find.c (copie de travail)
910 @@ -190,7 +190,60 @@
911     return true;
912  }
913  
914 +/*
915 + * Find BackupId of last job that ran.  E.g. for
916 + *
917 + * Returns: Last backuip
918 + *
919 + */
920 +JobId_t
921 +db_accurate_find_backupid(JCR *jcr, B_DB *mdb, JOB_DBR *jr)
922 +{
923 +   SQL_ROW row;
924 +   char ed1[50],ed2[50];
925 +   JobId_t backupid=0;
926  
927 +   /* Find backupid */
928 +   db_lock(mdb);
929 +   Dmsg2(100, "JobLevel=%d JobType=%d\n", jcr->JobLevel, jcr->JobType);
930 +   Mmsg(mdb->cmd,
931 +"SELECT BackupId FROM CurrentBackupId WHERE JobName='%s' AND "
932 +"ClientId=%s AND FileSetId=%s ORDER BY BackupId DESC LIMIT 1",
933 +       jr->Name, 
934 +       edit_int64(jr->ClientId, ed1),
935 +       edit_int64(jr->FileSetId, ed2));
936 +
937 +   Dmsg1(100, "Query: %s\n", mdb->cmd);
938 +   if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
939 +      db_unlock(mdb);
940 +      return 0;
941 +   }
942 +   if ((row = sql_fetch_row(mdb)) == NULL) {
943 +      sql_free_result(mdb);
944 +      if (jcr->JobLevel == L_FULL) {
945 +        Mmsg(mdb->cmd,
946 +             "INSERT INTO CurrentBackupId (JobName, ClientId, FileSetId) VALUES ('%s', %s, %s)",
947 +             jr->Name, ed1, ed2);
948 +        if (!INSERT_DB(jcr, mdb, mdb->cmd)) {
949 +           db_unlock(mdb);
950 +           return 0;
951 +        }
952 +        backupid = sql_insert_id(mdb, NT_("CurrentBackupId"));
953 +      } else {
954 +        Mmsg1(&mdb->errmsg, _("No Job found for: %s.\n"), mdb->cmd);
955 +        backupid = 0;
956 +      }
957 +   } else {
958 +      backupid = str_to_int64(row[0]);
959 +   }
960 +
961 +   sql_free_result(mdb);
962 +
963 +   db_unlock(mdb);
964 +   return backupid;
965 +}
966 +
967 +
968  /*
969   * Find JobId of last job that ran.  E.g. for
970   *   VERIFY_CATALOG we want the JobId of the last INIT.
971 Index: src/cats/sql_delete.c
972 ===================================================================
973 --- src/cats/sql_delete.c       (révision 6374)
974 +++ src/cats/sql_delete.c       (copie de travail)
975 @@ -236,5 +236,22 @@
976     return 1;
977  }
978  
979 +/*
980 + * Purge delete file from CurrentFile table. This table contains only
981 + * current files.
982 + */
983 +int db_accurate_clean_deleted_files(JCR *jcr, B_DB *mdb, JobId_t JobId, JobId_t BackupId)
984 +{
985 +   int stat;
986 +   char ed1[50], ed2[50];
987 +   db_lock(mdb);
988 +   Mmsg(mdb->cmd, "DELETE FROM CurrentFile WHERE MarkId!=%s AND BackupId=%s", 
989 +       edit_int64(JobId, ed1), edit_int64(BackupId, ed2));
990 +   stat = QUERY_DB(jcr, mdb, mdb->cmd);
991 +   db_unlock(mdb);
992 +   return stat;
993  
994 +}
995 +
996 +
997  #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL*/
998 Index: src/cats/sql_create.c
999 ===================================================================
1000 --- src/cats/sql_create.c       (révision 6374)
1001 +++ src/cats/sql_create.c       (copie de travail)
1002 @@ -829,6 +829,14 @@
1003     return true;
1004  }
1005  
1006 +bool db_accurate_insert(JCR *jcr, B_DB *mdb, bool saved, const char *fname, struct stat *stat)
1007 +{
1008 +   int len;
1009 +   split_path_and_file(jcr, mdb, fname);
1010 +   /* make like in Verify code */
1011 +   return true;
1012 +} 
1013 +
1014  /*
1015   * Create File record in B_DB
1016   *
1017 Index: src/cats/sql_get.c
1018 ===================================================================
1019 --- src/cats/sql_get.c  (révision 6374)
1020 +++ src/cats/sql_get.c  (copie de travail)
1021 @@ -66,6 +66,8 @@
1022   *
1023   *  Returns: 0 on failure
1024   *           1 on success with the File record in FILE_DBR
1025 + *
1026 + * TODO: optimize this with only one query
1027   */
1028  int db_get_file_attributes_record(JCR *jcr, B_DB *mdb, char *fname, JOB_DBR *jr, FILE_DBR *fdbr)
1029  {
1030 @@ -86,7 +88,73 @@
1031     return stat;
1032  }
1033  
1034 +/*
1035 + * Given a full filename (with path), look up the File record
1036 + * (with attributes) in the database.
1037 + *
1038 + *  Returns: 0 on failure
1039 + *           1 on success with the File record in FILE_DBR
1040 + */
1041 +int db_accurate_get_file_attributes_record(JCR *jcr, B_DB *mdb, char *fname, JobId_t backupid, FILE_DBR *fdbr)
1042 +{
1043 +   int stat=0;
1044 +   char ed1[50];
1045 +   SQL_ROW row;
1046  
1047 +   db_lock(mdb);
1048 +   split_path_and_file(jcr, mdb, fname);
1049 +
1050 +   mdb->esc_name = check_pool_memory_size(mdb->esc_name, 2*mdb->fnl+2);
1051 +   db_escape_string(jcr, mdb, mdb->esc_name, mdb->fname, mdb->fnl);
1052 +
1053 +   mdb->esc_path = check_pool_memory_size(mdb->esc_path, 2*mdb->pnl+2);
1054 +   db_escape_string(jcr, mdb, mdb->esc_path, mdb->path, mdb->pnl);
1055 +
1056 +   Mmsg(mdb->cmd,
1057 +"SELECT FileId, LStat, MD5, FilenameId, PathId, FileIndex, CurrentFile.MarkId, JobId "
1058 +  "FROM File JOIN CurrentFile USING (FileId) "
1059 +            "JOIN Filename USING (FilenameId) "
1060 +            "JOIN Path     USING (PathId) "
1061 + "WHERE Path.Path='%s' "
1062 +   "AND Filename.Name='%s' "
1063 +   "AND BackupId=%s ",
1064 +       mdb->esc_path,
1065 +       mdb->esc_name,
1066 +       edit_int64(backupid, ed1));
1067 +   
1068 +   Dmsg1(100,"get_file %s\n", mdb->cmd);
1069 +
1070 +   if (QUERY_DB(jcr, mdb, mdb->cmd)) {
1071 +      char ed1[30];
1072 +      mdb->num_rows = sql_num_rows(mdb);
1073 +      if (mdb->num_rows == 1) {
1074 +         if ((row = sql_fetch_row(mdb)) == NULL) {
1075 +            Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
1076 +         } else {
1077 +           fdbr->FileId     = str_to_int64(row[0]);
1078 +            bstrncpy(fdbr->LStat, row[1], sizeof(fdbr->LStat));
1079 +            bstrncpy(fdbr->Digest, row[2], sizeof(fdbr->Digest));
1080 +           fdbr->FilenameId = str_to_int64(row[3]);
1081 +           fdbr->PathId     = str_to_int64(row[4]);
1082 +           fdbr->FileIndex  = str_to_int64(row[5]);
1083 +           fdbr->MarkId     = str_to_int64(row[6]);
1084 +           fdbr->JobId      = str_to_int64(row[7]);
1085 +           stat=1;
1086 +        }
1087 +      } else if (mdb->num_rows > 1) {
1088 +        Mmsg2(mdb->errmsg, _("Get DB File record %s failed num=%i\n"),fname,mdb->num_rows);
1089 +         Jmsg(jcr, M_WARNING, 0, "%s", mdb->errmsg);
1090 +      }
1091 +      sql_free_result(mdb);
1092 +   } else {
1093 +      Mmsg(mdb->errmsg, _("File record: %s not found in Catalog for BackupId=%s.\n"), fname, ed1);
1094 +   }
1095 +
1096 +   db_unlock(mdb);
1097 +
1098 +   return stat;
1099 +}
1100 +
1101  /*
1102   * Get a File record
1103   * Returns: 0 on failure
1104 Index: src/jcr.h
1105 ===================================================================
1106 --- src/jcr.h   (révision 6374)
1107 +++ src/jcr.h   (copie de travail)
1108 @@ -208,6 +208,7 @@
1109     B_DB *db_batch;                    /* database pointer for batch insert */
1110     ATTR_DBR *ar;                      /* DB attribute record */
1111     guid_list *id_list;                /* User/group id to name list */
1112 +   bool accurate;                     /* true if job is accurate */
1113  
1114     void *plugin_ctx_list;             /* list of contexts for plugins */
1115     void *plugin_ctx;                  /* current plugin context */
1116 Index: src/findlib/find.h
1117 ===================================================================
1118 --- src/findlib/find.h  (révision 6374)
1119 +++ src/findlib/find.h  (copie de travail)
1120 @@ -108,6 +108,7 @@
1121  #define FO_ENHANCEDWILD (1<<23)       /* Enhanced wild card processing */
1122  #define FO_CHKCHANGES   (1<<24)       /* Check if file have been modified during backup */
1123  #define FO_STRIPPATH    (1<<25)       /* Check for stripping path */
1124 +#define FO_ACCURATE     (1<<26)       /* Accurate mode */
1125  
1126  struct s_included_file {
1127     struct s_included_file *next;