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