]> git.sur5r.net Git - bacula/bacula/blob - bacula/patches/testing/project-accurate-backup.patch
ebl accurate project update
[bacula/bacula] / bacula / patches / testing / project-accurate-backup.patch
1 Index: src/dird/backup.c
2 ===================================================================
3 --- src/dird/backup.c   (revision 6372)
4 +++ src/dird/backup.c   (working copy)
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,300 @@
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 +   Qmsg(jcr, M_INFO, 0, "      %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
32 +   return 0;
33 +}
34 +
35 +/*
36 + * Accurate backup mode
37 + * 1. Receive the list of all files including those backed up to the Dir
38 + * 2. Dir computes files and deleted files.
39 + * 3. Dir sends list of additional files (new files) to backup, and list of files
40 + *    deleted.
41 + *
42 + * Cleanup attributes (don't use atime, inode etc..)
43 + * Need to insert file and attributes to temp table
44 + * Batch compare files and attributes 
45 + *
46 + *
47 + */
48 +bool accurate_compute_files(JCR *jcr)
49 +{
50 +   BSOCK   *fd;
51 +   int n, len;
52 +   FILE_DBR fdbr;
53 +   struct stat statf;                 /* file stat */
54 +   struct stat statc;                 /* catalog stat */
55 +   int stat = JS_Terminated;
56 +   char buf[MAXSTRING];
57 +   char ed1[50], ed2[50];
58 +   POOLMEM *fname = get_pool_memory(PM_MESSAGE);
59 +   int do_Digest = CRYPTO_DIGEST_NONE;
60 +   int32_t file_index = 0;
61 +   JobId_t JobId=0;            /* TODO: compute the job key in new table */
62 +   JobId_t backupid=0;
63 +
64 +   memset(&fdbr, 0, sizeof(FILE_DBR));
65 +   fd = jcr->file_bsock;
66 +   fdbr.JobId = JobId;         
67 +   jcr->FileIndex = 0;
68 +
69 +   if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
70 +      return true;
71 +   }
72 +
73 +   backupid = db_find_backupid(jcr, jcr->db, &jcr->jr);
74 +   if (!backupid) {
75 +      Jmsg(jcr, M_ERROR, 0, _("Can't use Accurate mode ERR=Can't find BackupId\n"));
76 +      return false;
77 +   }
78 +
79 +   Dmsg0(20, "bdird: waiting to receive file attributes\n");
80 +   /*
81 +    * Get Attributes and Signature from File daemon
82 +    * We expect:
83 +    *   FileIndex
84 +    *   Stream
85 +    *   Options or Digest (MD5/SHA1)
86 +    *   Filename
87 +    *   Attributes
88 +    *   Link name  ???
89 +    */
90 +   while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
91 +      int stream;
92 +      char *attr, *p, *fn;
93 +      char Opts_Digest[MAXSTRING];        /* Verify Opts or MD5/SHA1 digest */
94 +
95 +      if (job_canceled(jcr)) {
96 +         return false;
97 +      }
98 +      fname = check_pool_memory_size(fname, fd->msglen);
99 +      jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
100 +      Dmsg1(20, "Atts+Digest=%s\n", fd->msg);
101 +      if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
102 +            fname)) != 3) {
103 +         Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
104 +" mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
105 +         return false;
106 +      }
107 +      /*
108 +       * We read the Options or Signature into fname
109 +       *  to prevent overrun, now copy it to proper location.
110 +       */
111 +      bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
112 +      p = fd->msg;
113 +      skip_nonspaces(&p);             /* skip FileIndex */
114 +      skip_spaces(&p);
115 +      skip_nonspaces(&p);             /* skip Stream */
116 +      skip_spaces(&p);
117 +      skip_nonspaces(&p);             /* skip Opts_Digest */
118 +      p++;                            /* skip space */
119 +      fn = fname;
120 +      while (*p != 0) {
121 +         *fn++ = *p++;                /* copy filename */
122 +      }
123 +      *fn = *p++;                     /* term filename and point to attribs */
124 +      attr = p;
125 +      /*
126 +       * Got attributes stream, decode it
127 +       */
128 +      if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
129 +         int32_t LinkFIf, LinkFIc;
130 +         Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
131 +         jcr->JobFiles++;
132 +         jcr->FileIndex = file_index;    /* remember attribute file_index */
133 +         decode_stat(attr, &statf, &LinkFIf);  /* decode file stat packet */
134 +         do_Digest = CRYPTO_DIGEST_NONE;
135 +         pm_strcpy(jcr->fname, fname);  /* move filename into JCR */
136 +
137 +         Dmsg3(040, "dird<filed: stream=%d %s %s\n", stream, jcr->fname, attr);
138 +
139 +         /*
140 +          * Find equivalent record in the database
141 +          */
142 +         fdbr.FileId = 0;
143 +         if (!db_accurate_get_file_attributes_record(jcr, jcr->db, jcr->fname, backupid, &fdbr)) {
144 +            Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
145 +            Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
146 +            continue;
147 +         } else {
148 +            /*
149 +             * mark file record as visited by stuffing the
150 +             * current JobId, which is unique, into the MarkId field.
151 +             */
152 +            db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
153 +         }
154 +
155 +         Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
156 +            file_index, Opts_Digest);
157 +         decode_stat(fdbr.LStat, &statc, &LinkFIc); /* decode catalog stat */
158 +
159 +        // TODO: for each JS_Differences, send it to FD for backup
160 +         /*
161 +          * Loop over options supplied by user and verify the
162 +          * fields he requests.
163 +          */
164 +         for (p=Opts_Digest; *p; p++) {
165 +            char ed1[30], ed2[30];
166 +            switch (*p) {
167 +            case 'i':                /* compare INODEs */
168 +               if (statc.st_ino != statf.st_ino) {
169 +                  Jmsg(jcr, M_INFO, 0, _("      st_ino   differ. Cat: %s File: %s\n"),
170 +                     edit_uint64((uint64_t)statc.st_ino, ed1),
171 +                     edit_uint64((uint64_t)statf.st_ino, ed2));
172 +                  stat = JS_Differences;
173 +               }
174 +               break;
175 +            case 'p':                /* permissions bits */
176 +               if (statc.st_mode != statf.st_mode) {
177 +                  Jmsg(jcr, M_INFO, 0, _("      st_mode  differ. Cat: %x File: %x\n"),
178 +                     (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
179 +                  stat = JS_Differences;
180 +               }
181 +               break;
182 +            case 'n':                /* number of links */
183 +               if (statc.st_nlink != statf.st_nlink) {
184 +                  Jmsg(jcr, M_INFO, 0, _("      st_nlink differ. Cat: %d File: %d\n"),
185 +                     (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
186 +                  stat = JS_Differences;
187 +               }
188 +               break;
189 +            case 'u':                /* user id */
190 +               if (statc.st_uid != statf.st_uid) {
191 +                  Jmsg(jcr, M_INFO, 0, _("      st_uid   differ. Cat: %u File: %u\n"),
192 +                     (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
193 +                  stat = JS_Differences;
194 +               }
195 +               break;
196 +            case 'g':                /* group id */
197 +               if (statc.st_gid != statf.st_gid) {
198 +                  Jmsg(jcr, M_INFO, 0, _("      st_gid   differ. Cat: %u File: %u\n"),
199 +                     (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
200 +                  stat = JS_Differences;
201 +               }
202 +               break;
203 +            case 's':                /* size */
204 +               if (statc.st_size != statf.st_size) {
205 +                  Jmsg(jcr, M_INFO, 0, _("      st_size  differ. Cat: %s File: %s\n"),
206 +                     edit_uint64((uint64_t)statc.st_size, ed1),
207 +                     edit_uint64((uint64_t)statf.st_size, ed2));
208 +                  stat = JS_Differences;
209 +               }
210 +               break;
211 +            case 'a':                /* access time */
212 +               if (statc.st_atime != statf.st_atime) {
213 +                  Jmsg(jcr, M_INFO, 0, _("      st_atime differs\n"));
214 +                  stat = JS_Differences;
215 +               }
216 +               break;
217 +            case 'm':
218 +               if (statc.st_mtime != statf.st_mtime) {
219 +                  Jmsg(jcr, M_INFO, 0, _("      st_mtime differs\n"));
220 +                  stat = JS_Differences;
221 +               }
222 +               break;
223 +            case 'c':                /* ctime */
224 +               if (statc.st_ctime != statf.st_ctime) {
225 +                  Jmsg(jcr, M_INFO, 0, _("      st_ctime differs\n"));
226 +                  stat = JS_Differences;
227 +               }
228 +               break;
229 +            case 'd':                /* file size decrease */
230 +               if (statc.st_size > statf.st_size) {
231 +                  Jmsg(jcr, M_INFO, 0, _("      st_size  decrease. Cat: %s File: %s\n"),
232 +                     edit_uint64((uint64_t)statc.st_size, ed1),
233 +                     edit_uint64((uint64_t)statf.st_size, ed2));
234 +                  stat = JS_Differences;
235 +               }
236 +               break;
237 +            case '5':                /* compare MD5 */
238 +               Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
239 +               do_Digest = CRYPTO_DIGEST_MD5;
240 +               break;
241 +            case '1':                 /* compare SHA1 */
242 +               do_Digest = CRYPTO_DIGEST_SHA1;
243 +               break;
244 +            case ':':
245 +            case 'V':
246 +            default:
247 +               break;
248 +            }
249 +         }
250 +      /*
251 +       * Got Digest Signature from Storage daemon
252 +       *  It came across in the Opts_Digest field.
253 +       */
254 +      } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
255 +         Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
256 +         /*
257 +          * When ever we get a digest it MUST have been
258 +          * preceded by an attributes record, which sets attr_file_index
259 +          */
260 +         if (jcr->FileIndex != (uint32_t)file_index) {
261 +            Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
262 +               file_index, jcr->FileIndex);
263 +            return false;
264 +         }
265 +         if (do_Digest != CRYPTO_DIGEST_NONE) {
266 +            db_escape_string(jcr, jcr->db, buf, Opts_Digest, strlen(Opts_Digest));
267 +            if (strcmp(buf, fdbr.Digest) != 0) {
268 +               if (debug_level >= 10) {
269 +                  Jmsg(jcr, M_INFO, 0, _("      %d not same. File=%s Cat=%s\n"),
270 +                       stream, buf, fdbr.Digest);
271 +               } else {
272 +                  Jmsg(jcr, M_INFO, 0, _("      %d differs.\n"),
273 +                       stream);
274 +               }
275 +               stat = JS_Differences;
276 +            }
277 +            do_Digest = CRYPTO_DIGEST_NONE;
278 +         }
279 +      }
280 +//      jcr->JobFiles = file_index;
281 +   }
282 +   if (is_bnet_error(fd)) {
283 +      berrno be;
284 +      Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
285 +                        n, be.bstrerror());
286 +      return false;
287 +   }
288 +
289 +   /* Now find all the files that are missing -- i.e. all files in
290 +    *  the database where the MarkId != current JobId
291 +    */
292 +
293 +   bsnprintf(buf, sizeof(buf),
294 +      "SELECT Path.Path,Filename.Name "
295 +        "FROM CurrentBackup "
296 +             "JOIN File USING (FileId) "
297 +             "JOIN Path USING (PathId) "
298 +             "JOIN Filename USING (FilenameId) "
299 +      "WHERE CurrentBackup.BackupId=%s "
300 +        "AND File.MarkId!=%d ",
301 +            edit_uint64(backupid, ed1), edit_uint64(jcr->JobId, ed2));
302 +   /* missing_handler is called for each file found */
303 +   db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
304 +
305 +   free_pool_memory(fname);
306 +
307 +   return true;
308 +}
309 +
310 +/*
311   * Do a backup of the specified FileSet
312   *
313   *  Returns:  false on failure
314 @@ -231,6 +526,13 @@
315        goto bail_out;
316     }
317  
318 +   /*
319 +    * If backup is in accurate mode, FD will send the list of
320 +    * all files. We have to store it, and compute witch files
321 +    * have been deleted and witch files have to be backuped.
322 +    */
323 +   accurate_compute_files(jcr);
324 +
325     /* Pickup Job termination data */
326     stat = wait_for_job_termination(jcr);
327     db_write_batch_file_records(jcr);    /* used by bulk batch file insert */
328 Index: src/dird/inc_conf.c
329 ===================================================================
330 --- src/dird/inc_conf.c (revision 6372)
331 +++ src/dird/inc_conf.c (working copy)
332 @@ -94,6 +94,7 @@
333   * Items that are valid in an Options resource
334   */
335  static RES_ITEM options_items[] = {
336 +   {"accurate",        store_opts,    {0},     0, 0, 0},
337     {"compression",     store_opts,    {0},     0, 0, 0},
338     {"signature",       store_opts,    {0},     0, 0, 0},
339     {"verify",          store_opts,    {0},     0, 0, 0},
340 @@ -153,7 +154,8 @@
341     INC_KW_NOATIME,
342     INC_KW_ENHANCEDWILD,
343     INC_KW_CHKCHANGES,
344 -   INC_KW_STRIPPATH
345 +   INC_KW_STRIPPATH,
346 +   INC_KW_ACCURATE
347  };
348  
349  /*
350 @@ -163,6 +165,7 @@
351   *   options given above.
352   */
353  static struct s_kw FS_option_kw[] = {
354 +   {"accurate",    INC_KW_ACCURATE},
355     {"compression", INC_KW_COMPRESSION},
356     {"signature",   INC_KW_DIGEST},
357     {"encryption",  INC_KW_ENCRYPTION},
358 @@ -251,6 +254,8 @@
359     {"no",       INC_KW_ENHANCEDWILD,  "0"},
360     {"yes",      INC_KW_CHKCHANGES,    "c"},
361     {"no",       INC_KW_CHKCHANGES,    "0"},
362 +   {"yes",      INC_KW_ACCURATE,      "C"},
363 +   {"no",       INC_KW_ACCURATE,      "0"},
364     {NULL,       0,                      0}
365  };
366  
367 Index: src/dird/dird_conf.c
368 ===================================================================
369 --- src/dird/dird_conf.c        (revision 6372)
370 +++ src/dird/dird_conf.c        (working copy)
371 @@ -319,6 +319,7 @@
372     {"selectionpattern", store_str, ITEM(res_job.selection_pattern), 0, 0, 0},
373     {"runscript", store_runscript, ITEM(res_job.RunScripts), 0, ITEM_NO_EQUALS, 0},
374     {"selectiontype", store_migtype, ITEM(res_job.selection_type), 0, 0, 0},
375 +   {"accuratebackup", store_bool, ITEM(res_job.accurate), 0,0,0},
376     {NULL, NULL, {0}, 0, 0, 0}
377  };
378  
379 @@ -618,6 +619,9 @@
380        if (res->res_job.spool_size) {
381           sendit(sock, _("     SpoolSize=%s\n"),        edit_uint64(res->res_job.spool_size, ed1));
382        }
383 +      if (res->res_job.JobType == JT_BACKUP) {
384 +        sendit(sock, _("     Accurate=%d\n"), res->res_job.accurate);
385 +      }
386        if (res->res_job.JobType == JT_MIGRATE) {
387           sendit(sock, _("     SelectionType=%d\n"), res->res_job.selection_type);
388        }
389 Index: src/dird/dird_conf.h
390 ===================================================================
391 --- src/dird/dird_conf.h        (revision 6372)
392 +++ src/dird/dird_conf.h        (working copy)
393 @@ -400,6 +400,7 @@
394     bool write_part_after_job;         /* Set to write part after job in SD */
395     bool enabled;                      /* Set if job enabled */
396     bool OptimizeJobScheduling;        /* Set if we should optimize Job scheduling */
397 +   bool accurate;                     /* Set if it is an accurate backup job */
398     
399     MSGS      *messages;               /* How and where to send messages */
400     SCHED     *schedule;               /* When -- Automatic schedule */
401 Index: src/filed/backup.c
402 ===================================================================
403 --- src/filed/backup.c  (revision 6372)
404 +++ src/filed/backup.c  (working copy)
405 @@ -50,6 +50,81 @@
406  static bool crypto_session_send(JCR *jcr, BSOCK *sd);
407  
408  /*
409 + * Called by save_file when accept/discard file for backup
410 + * TODO: we could add MD5/SHAX digest, but we have to compute it
411 + * for all files.
412 + */
413 +static bool accurate_add_file(JCR *jcr, FF_PKT *ff_pkt, char *stats)
414 +{
415 +   char *a=stats;
416 +   char attribs[MAXSTRING];
417 +   uint32_t file_index=jcr->JobFiles;
418 +   BSOCK *dir = jcr->dir_bsock;
419 +   int stat;
420 +
421 +   if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
422 +      return true;
423 +   }
424 +
425 +   if (!stats) {
426 +      file_index=0;
427 +      encode_stat(attribs, ff_pkt, 0);
428 +      a = attribs;
429 +   }
430 +
431 +   if (ff_pkt->type == FT_LNK || ff_pkt->type == FT_LNKSAVED) {
432 +      stat = dir->fsend("%d %d %s %s%c%s%c%s%c", file_index,
433 +            STREAM_UNIX_ATTRIBUTES, ff_pkt->VerifyOpts, ff_pkt->fname,
434 +            0, a, 0, ff_pkt->link, 0);
435 +   } else if (ff_pkt->type == FT_DIREND || ff_pkt->type == FT_REPARSE) {
436 +         /* Here link is the canonical filename (i.e. with trailing slash) */
437 +      stat = dir->fsend("%d %d %s %s%c%s%c%c", file_index,
438 +               STREAM_UNIX_ATTRIBUTES, ff_pkt->VerifyOpts, ff_pkt->link,
439 +               0, a, 0, 0);
440 +   } else {
441 +      stat = dir->fsend("%d %d %s %s%c%s%c%c", file_index,
442 +            STREAM_UNIX_ATTRIBUTES, ff_pkt->VerifyOpts, ff_pkt->fname,
443 +            0, a, 0, 0);
444 +   }
445 +
446 +   if (!stat) {
447 +      Jmsg(jcr, M_FATAL, 0, _("Network error in send to Director: ERR=%s\n"), bnet_strerror(dir));
448 +      return 0;
449 +   }
450 +
451 +   return true;
452 +}
453 +
454 +/* build a fileset with new files from director */
455 +static bool accurate_get_new_and_deleted_file_list(JCR *jcr)
456 +{   
457 +   if (jcr->accurate == false || job_canceled(jcr)) {
458 +      return true;
459 +   }
460 +   return true;
461 +}
462 +
463 +/* send deleted file list to stored */
464 +static bool accurate_send_deleted_list(JCR *jcr)
465 +{
466 +   if (jcr->accurate == false || job_canceled(jcr)) {
467 +      return true;
468 +   }
469 +   return true;
470 +}
471 +
472 +static bool accurate_send_file_list(JCR *jcr)
473 +{
474 +   if (jcr->accurate == false || job_canceled(jcr)) {
475 +      return true;
476 +   }
477 +   Dmsg0(1, "Sending BNET_EOD\n");
478 +   jcr->dir_bsock->signal(BNET_EOD);            /* end of sending data */
479 +   return true;
480 +}
481 +
482 +
483 +/*
484   * Find all the requested files and send them
485   * to the Storage daemon.
486   *
487 @@ -66,6 +141,7 @@
488     BSOCK *sd;
489     bool ok = true;
490     // TODO landonf: Allow user to specify encryption algorithm
491 +   jcr->accurate=true;         /* TODO: remove that */
492  
493     sd = jcr->store_bsock;
494  
495 @@ -135,6 +211,20 @@
496        set_jcr_job_status(jcr, JS_ErrorTerminated);
497     }
498  
499 +   /* start accurate stuffs */
500 +   if (jcr->accurate) {
501 +      /* TODO: test job_canceled() */
502 +      accurate_send_file_list(jcr);                 /* send all files to DIR */
503 +      accurate_get_new_and_deleted_file_list(jcr);  /* get a new incr fileset from DIR */
504 +//      set_find_options((FF_PKT *)jcr->ff, 0, 0);            /* we backup all that director wants */
505 +//      if (!find_files(jcr, (FF_PKT *)jcr->ff, save_file, (void *)jcr)) {
506 +//      ok = false;                     /* error */
507 +//      set_jcr_job_status(jcr, JS_ErrorTerminated);
508 +//      }
509 +//      accurate_send_file_list(jcr);                 /* send all new files to DIR */
510 +      accurate_send_deleted_list(jcr);              /* send deleted list to SD  */
511 +   }
512 +
513     free_pool_memory(jcr->acl_text);
514  
515     stop_heartbeat_monitor(jcr);
516 @@ -355,9 +445,11 @@
517     case FT_DIRNOCHG:
518     case FT_NOCHG:
519        Jmsg(jcr, M_SKIPPED, 1, _("     Unchanged file skipped: %s\n"), ff_pkt->fname);
520 +      accurate_add_file(jcr, ff_pkt, NULL); /* list skipped files */
521        return 1;
522     case FT_ISARCH:
523        Jmsg(jcr, M_NOTSAVED, 0, _("     Archive file not saved: %s\n"), ff_pkt->fname);
524 +      accurate_add_file(jcr, ff_pkt, NULL); /* list skipped files */
525        return 1;
526     case FT_NOOPEN: {
527        berrno be;
528 @@ -1111,6 +1203,9 @@
529     }
530     unstrip_path(ff_pkt);
531  
532 +   /* list backuped files */
533 +   accurate_add_file(jcr, ff_pkt, attribs);
534 +
535     Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg);
536     if (!stat) {
537        Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
538 Index: src/filed/job.c
539 ===================================================================
540 --- src/filed/job.c     (revision 6372)
541 +++ src/filed/job.c     (working copy)
542 @@ -1087,6 +1087,9 @@
543        case 'c':
544           fo->flags |= FO_CHKCHANGES;
545           break;
546 +      case 'C':
547 +         fo->flags |= FO_ACCURATE;
548 +         break;
549        default:
550           Emsg1(M_ERROR, 0, _("Unknown include/exclude option: %c\n"), *p);
551           break;
552 Index: src/cats/make_postgresql_tables.in
553 ===================================================================
554 --- src/cats/make_postgresql_tables.in  (revision 6372)
555 +++ src/cats/make_postgresql_tables.in  (working copy)
556 @@ -43,6 +43,58 @@
557  CREATE INDEX file_jobid_idx on file (jobid);
558  CREATE INDEX file_fp_idx on file (filenameid, pathid);
559  
560 +CREATE TABLE CurrentBackupId
561 +(
562 +     BackupId          serial     not null,
563 +     ClientId          integer    not null,
564 +     JobName           text       not null,
565 +     FileSetId         integer    not null,
566 +     primary key (BackupId)
567 +);
568 +
569 +-- Serait bien de prendre la meme table pour
570 +-- les File et le CurrentBackup...
571 +-- Mais y'a des problemes pour les prunes
572 +
573 +CREATE TABLE CurrentBackup
574 +(
575 +     FileId           integer    not null,
576 +     BackupId         integer    not null,
577 +     FullMark         char(1)    default 0,
578 +     primary key (FileId)
579 +);
580 +
581 +CREATE INDEX currentbackup_fileid on CurrentBackup (BackupId);
582 +
583 +-- CREATE TEMPORARY TABLE batch (fileindex int,
584 +--                               jobid int,
585 +--                               path varchar,
586 +--                               name varchar,
587 +--                               lstat varchar,
588 +--                               md5 varchar);
589 +-- 
590 +-- -- On batch insert dans la table temporaire
591 +
592 +-- il faut trouver les fichiers manquant
593 +-- INSERT des nouveaux, UPDATE des anciens, SELECT pour trouver les deletes
594 +
595 +
596 +-- il faut trouver les fichiers modifies
597 +-- Le champs LStat n'est plus le meme
598 +-- SELECT * 
599 +--   FROM CurrentBackup, 
600 +--        batch JOIN Path USING (Path) JOIN Filename USING (Name)
601 +--  WHERE Path.PathId = CurrentBackup.PathId
602 +--    AND Filename.FilenameId = CurrentBackup.FilenameId
603 +--    AND CurrentBackup.LStat != batch.LStat
604 +-- 
605 +-- il faut mettre a jour la liste des fichiers
606 +
607 +
608 +
609 +
610 +
611 +
612  --
613  -- Possibly add one or more of the following indexes
614  --  if your Verifies are too slow.
615 Index: src/cats/protos.h
616 ===================================================================
617 --- src/cats/protos.h   (revision 6372)
618 +++ src/cats/protos.h   (working copy)
619 @@ -82,10 +82,12 @@
620  /* sql_find.c */
621  bool db_find_job_start_time(JCR *jcr, B_DB *mdb, JOB_DBR *jr, POOLMEM **stime);
622  bool db_find_last_jobid(JCR *jcr, B_DB *mdb, const char *Name, JOB_DBR *jr);
623 +JobId_t db_find_backupid(JCR *jcr, B_DB *mdb, JOB_DBR *jr);
624  int db_find_next_volume(JCR *jcr, B_DB *mdb, int index, bool InChanger, MEDIA_DBR *mr);
625  bool db_find_failed_job_since(JCR *jcr, B_DB *mdb, JOB_DBR *jr, POOLMEM *stime, int &JobLevel);
626  
627  /* sql_get.c */
628 +int db_accurate_get_file_attributes_record(JCR *jcr, B_DB *mdb, char *fname, JobId_t backupid, FILE_DBR *fdbr);
629  bool db_get_pool_record(JCR *jcr, B_DB *db, POOL_DBR *pdbr);
630  int db_get_client_record(JCR *jcr, B_DB *mdb, CLIENT_DBR *cr);
631  bool db_get_job_record(JCR *jcr, B_DB *mdb, JOB_DBR *jr);
632 Index: src/cats/sql_find.c
633 ===================================================================
634 --- src/cats/sql_find.c (revision 6372)
635 +++ src/cats/sql_find.c (working copy)
636 @@ -190,7 +190,55 @@
637     return true;
638  }
639  
640 +/*
641 + * Find BackupId of last job that ran.  E.g. for
642 + *
643 + * Returns: Last backuip
644 + *
645 + */
646 +JobId_t
647 +db_find_backupid(JCR *jcr, B_DB *mdb, JOB_DBR *jr)
648 +{
649 +   SQL_ROW row;
650 +   char ed1[50],ed2[50];
651 +   JobId_t backupid=0;
652  
653 +   /* Find backupid */
654 +   db_lock(mdb);
655 +   Dmsg2(100, "JobLevel=%d JobType=%d\n", jcr->JobLevel, jcr->JobType);
656 +   Mmsg(mdb->cmd,
657 +"SELECT BackupId FROM CurrentBackupId WHERE JobName='%s' AND "
658 +"ClientId=%s AND FileSetId=%s ORDER BY BackupId DESC LIMIT 1",
659 +       jr->Name, 
660 +       edit_int64(jr->ClientId, ed1),
661 +       edit_int64(jr->FileSetId, ed2));
662 +
663 +   Dmsg1(100, "Query: %s\n", mdb->cmd);
664 +   if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
665 +      db_unlock(mdb);
666 +      return 0;
667 +   }
668 +   if ((row = sql_fetch_row(mdb)) == NULL) {
669 +      Mmsg1(&mdb->errmsg, _("No Job found for: %s.\n"), mdb->cmd);
670 +      sql_free_result(mdb);
671 +      db_unlock(mdb);
672 +      return 0;
673 +   }
674 +
675 +   backupid = str_to_int64(row[0]);
676 +   sql_free_result(mdb);
677 +
678 +   if (backupid <= 0) {
679 +      Mmsg1(&mdb->errmsg, _("No Job found for: %s\n"), mdb->cmd);
680 +      db_unlock(mdb);
681 +      return 0;
682 +   }
683 +
684 +   db_unlock(mdb);
685 +   return backupid;
686 +}
687 +
688 +
689  /*
690   * Find JobId of last job that ran.  E.g. for
691   *   VERIFY_CATALOG we want the JobId of the last INIT.
692 Index: src/cats/sql_create.c
693 ===================================================================
694 --- src/cats/sql_create.c       (revision 6372)
695 +++ src/cats/sql_create.c       (working copy)
696 @@ -829,6 +829,14 @@
697     return true;
698  }
699  
700 +bool db_accurate_insert(JCR *jcr, B_DB *mdb, bool saved, const char *fname, struct stat *stat)
701 +{
702 +   int len;
703 +   split_path_and_file(jcr, mdb, fname);
704 +   /* make like in Verify code */
705 +   return true;
706 +} 
707 +
708  /*
709   * Create File record in B_DB
710   *
711 Index: src/cats/sql_get.c
712 ===================================================================
713 --- src/cats/sql_get.c  (revision 6372)
714 +++ src/cats/sql_get.c  (working copy)
715 @@ -66,6 +66,8 @@
716   *
717   *  Returns: 0 on failure
718   *           1 on success with the File record in FILE_DBR
719 + *
720 + * TODO: optimize this with only one query
721   */
722  int db_get_file_attributes_record(JCR *jcr, B_DB *mdb, char *fname, JOB_DBR *jr, FILE_DBR *fdbr)
723  {
724 @@ -86,7 +88,70 @@
725     return stat;
726  }
727  
728 +/*
729 + * Given a full filename (with path), look up the File record
730 + * (with attributes) in the database.
731 + *
732 + *  Returns: 0 on failure
733 + *           1 on success with the File record in FILE_DBR
734 + */
735 +int db_accurate_get_file_attributes_record(JCR *jcr, B_DB *mdb, char *fname, JobId_t backupid, FILE_DBR *fdbr)
736 +{
737 +   int stat;
738 +   char ed1[50];
739 +   SQL_ROW row;
740  
741 +   db_lock(mdb);
742 +   split_path_and_file(jcr, mdb, fname);
743 +
744 +   mdb->esc_name = check_pool_memory_size(mdb->esc_name, 2*mdb->fnl+2);
745 +   db_escape_string(jcr, mdb, mdb->esc_name, mdb->fname, mdb->fnl);
746 +
747 +   mdb->esc_path = check_pool_memory_size(mdb->esc_path, 2*mdb->pnl+2);
748 +   db_escape_string(jcr, mdb, mdb->esc_path, mdb->path, mdb->pnl);
749 +
750 +   Mmsg(mdb->cmd,
751 +"SELECT FileId, LStat, MD5, FilenameId, PathId, FileIndex, MarkId, JobId "
752 +  "FROM File JOIN CurrentBackup USING (FileId) "
753 +            "JOIN Filename USING (FilenameId) "
754 +            "JOIN Path     USING (PathId) "
755 + "WHERE Path.Path='%s' "
756 +   "AND Filename.Name='%s' "
757 +   "AND BackupId=%s ",
758 +       mdb->esc_path,
759 +       mdb->esc_name,
760 +       edit_int64(backupid, ed1));
761 +
762 +   if (QUERY_DB(jcr, mdb, mdb->cmd)) {
763 +      char ed1[30];
764 +      mdb->num_rows = sql_num_rows(mdb);
765 +      if (mdb->num_rows == 1) {
766 +         if ((row = sql_fetch_row(mdb)) == NULL) {
767 +            Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
768 +         } else {
769 +           fdbr->FileId     = str_to_int64(row[0]);
770 +            bstrncpy(fdbr->LStat, row[1], sizeof(fdbr->LStat));
771 +            bstrncpy(fdbr->Digest, row[2], sizeof(fdbr->Digest));
772 +           fdbr->FilenameId = str_to_int64(row[3]);
773 +           fdbr->PathId     = str_to_int64(row[4]);
774 +           fdbr->FileIndex  = str_to_int64(row[5]);
775 +           fdbr->MarkId     = str_to_int64(row[6]);
776 +           fdbr->JobId      = str_to_int64(row[7]);
777 +        }
778 +      } else {
779 +        Mmsg1(mdb->errmsg, _("Get DB File record %s failed\n"),fname);
780 +         Jmsg(jcr, M_WARNING, 0, "%s", mdb->errmsg);
781 +      }
782 +      sql_free_result(mdb);
783 +   } else {
784 +      Mmsg(mdb->errmsg, _("File record: %s not found in Catalog for BackupId=%s.\n"), fname, ed1);
785 +   }
786 +
787 +   db_unlock(mdb);
788 +
789 +   return stat;
790 +}
791 +
792  /*
793   * Get a File record
794   * Returns: 0 on failure
795 Index: src/jcr.h
796 ===================================================================
797 --- src/jcr.h   (revision 6372)
798 +++ src/jcr.h   (working copy)
799 @@ -208,6 +208,7 @@
800     B_DB *db_batch;                    /* database pointer for batch insert */
801     ATTR_DBR *ar;                      /* DB attribute record */
802     guid_list *id_list;                /* User/group id to name list */
803 +   bool accurate;                     /* true if job is accurate */
804  
805     void *plugin_ctx_list;             /* list of contexts for plugins */
806     void *plugin_ctx;                  /* current plugin context */
807 Index: src/findlib/find.h
808 ===================================================================
809 --- src/findlib/find.h  (revision 6372)
810 +++ src/findlib/find.h  (working copy)
811 @@ -108,6 +108,7 @@
812  #define FO_ENHANCEDWILD (1<<23)       /* Enhanced wild card processing */
813  #define FO_CHKCHANGES   (1<<24)       /* Check if file have been modified during backup */
814  #define FO_STRIPPATH    (1<<25)       /* Check for stripping path */
815 +#define FO_ACCURATE     (1<<26)       /* Accurate mode */
816  
817  struct s_included_file {
818     struct s_included_file *next;