]> git.sur5r.net Git - bacula/bacula/blob - bacula/patches/testing/project-accurate-backup.patch
ebl Use old LStat fields for deleted files (more simple)
[bacula/bacula] / bacula / patches / testing / project-accurate-backup.patch
1 Index: src/dird/fd_cmds.c
2 ===================================================================
3 --- src/dird/fd_cmds.c  (révision 6372)
4 +++ src/dird/fd_cmds.c  (copie de travail)
5 @@ -50,7 +50,7 @@
6  static char filesetcmd[]  = "fileset%s\n"; /* set full fileset */
7  static char jobcmd[]      = "JobId=%s Job=%s SDid=%u SDtime=%u Authorization=%s\n";
8  /* Note, mtime_only is not used here -- implemented as file option */
9 -static char levelcmd[]    = "level = %s%s mtime_only=%d\n";
10 +static char levelcmd[]    = "level = %s%s%s mtime_only=%d\n";
11  static char runscript[]   = "Run OnSuccess=%u OnFailure=%u AbortOnError=%u When=%u Command=%s\n";
12  static char runbeforenow[]= "RunBeforeNow\n";
13  
14 @@ -189,6 +189,12 @@
15           bsnprintf(since, since_len, _(" (upgraded from %s)"),
16              level_to_str(jcr->JobLevel));
17           jcr->JobLevel = jcr->jr.JobLevel = L_FULL;
18 +      /* look if we found the last accurate backup */
19 +      } else if (jcr->accurate && !db_accurate_find_backupid(jcr, jcr->db, &jcr->jr)) {
20 +         Jmsg(jcr, M_INFO, 0, _("No prior or suitable Full accurate backup found in catalog. Doing FULL backup.\n"));
21 +         bsnprintf(since, since_len, _(" (upgraded from %s)"),
22 +                  level_to_str(jcr->JobLevel));
23 +         jcr->JobLevel = jcr->jr.JobLevel = L_FULL;
24        } else {
25           if (jcr->job->rerun_failed_levels) {
26              if (db_find_failed_job_since(jcr, jcr->db, &jcr->jr, jcr->stime, JobLevel)) {
27 @@ -217,7 +223,7 @@
28     char ed1[50];
29  
30     stime = str_to_utime(jcr->stime);
31 -   fd->fsend(levelcmd, NT_("since_utime "), edit_uint64(stime, ed1), 0);
32 +   fd->fsend(levelcmd, "", NT_("since_utime "), edit_uint64(stime, ed1), 0);
33     while (bget_dirmsg(fd) >= 0) {  /* allow him to poll us to sync clocks */
34        Jmsg(jcr, M_INFO, 0, "%s\n", fd->msg);
35     }
36 @@ -231,24 +237,25 @@
37  bool send_level_command(JCR *jcr)
38  {
39     BSOCK   *fd = jcr->file_bsock;
40 +   const char *accurate=jcr->job->accurate?"accurate_":"";
41     /*
42      * Send Level command to File daemon
43      */
44     switch (jcr->JobLevel) {
45     case L_BASE:
46 -      fd->fsend(levelcmd, "base", " ", 0);
47 +      fd->fsend(levelcmd, "", "base", " ", 0);
48        break;
49     /* L_NONE is the console, sending something off to the FD */
50     case L_NONE:
51     case L_FULL:
52 -      fd->fsend(levelcmd, "full", " ", 0);
53 +      fd->fsend(levelcmd, "", "full", " ", 0);
54        break;
55     case L_DIFFERENTIAL:
56 -      fd->fsend(levelcmd, "differential", " ", 0);
57 +      fd->fsend(levelcmd, accurate, "differential", " ", 0);
58        send_since_time(jcr);
59        break;
60     case L_INCREMENTAL:
61 -      fd->fsend(levelcmd, "incremental", " ", 0);
62 +      fd->fsend(levelcmd, accurate, "incremental", " ", 0);
63        send_since_time(jcr);
64        break;
65     case L_SINCE:
66 Index: src/dird/backup.c
67 ===================================================================
68 --- src/dird/backup.c   (révision 6372)
69 +++ src/dird/backup.c   (copie de travail)
70 @@ -44,6 +44,7 @@
71  #include "bacula.h"
72  #include "dird.h"
73  #include "ua.h"
74 +#include "findlib/find.h"
75  
76  /* Commands sent to File daemon */
77  static char backupcmd[] = "backup\n";
78 @@ -96,7 +97,450 @@
79     return true;
80  }
81  
82 +static int accurate_list_handler(void *ctx, int num_fields, char **row)
83 +{
84 +   JCR *jcr = (JCR *)ctx;
85 +
86 +   if (job_canceled(jcr)) {
87 +      return 1;
88 +   }
89 +   
90 +   if (row[0] > 0) {
91 +      jcr->file_bsock->fsend("%s%s%c%s", row[1], row[2], 0, row[3]); 
92 +   }
93 +   return 0;
94 +}
95 +
96 +bool db_get_jobids(JCR *jcr)
97 +{
98 +   
99 +}
100 +
101 +bool accurate_send_current_files(JCR *jcr)
102 +{
103 +   char buf[MAXSTRING];
104 +   char ed1[50], ed2[50];
105  /*
106 + CREATE TEMPORARY TABLE btemp2 AS (
107 +  SELECT max(FileId) as FileId, PathId, FilenameId 
108 +    FROM (SELECT FileId, PathId, FilenameId FROM File WHERE JobId IN (39867,40341)) AS F
109 +   GROUP BY PathId, FilenameId )
110 +
111 +  SELECT File.FileIndex, Path.Path, Filename.Name, File.LStat
112 +    FROM btemp2 JOIN Path USING (PathId) JOIN Filename USING (FilenameId)
113 +                JOIN File USING (FileId)
114 +   WHERE File.FileIndex > 0
115 +
116 + DROP TABLE btemp2
117 +
118 +SELECT DISTINCT ON (PathId, FilenameId) FileIndex, Path, Name, LStat
119 +  FROM File JOIN Filename USING (FilenameId) JOIN Path USING (PathId) WHERE JobId IN (40341)
120 + ORDER BY PathId, FilenameId, JobId DESC
121 +*/
122 +   bsnprintf(buf, sizeof(buf),
123 +            "SELECT DISTINCT ON (PathId, FilenameId) FileIndex, Path, Name, LStat "
124 +             "FROM File JOIN Filename USING (FilenameId) JOIN Path USING (PathId) WHERE JobId IN (%s)"
125 +              "ORDER BY PathId, FilenameId, JobId DESC",
126 +            "10,20,30");       /* jobid */
127 +
128 +   Dmsg1(2, "display current files cmd=%s\n", buf);
129 +   db_sql_query(jcr->db, buf, accurate_list_handler, (void *)jcr);
130 +   jcr->file_bsock->signal(BNET_EOD);
131 +   return true;
132 +}
133 +
134 +/*
135 + * We are called here for each record that matches the above
136 + *  SQL query -- that is for each file contained in the Catalog
137 + *  that was not marked earlier. This means that the file in
138 + *  question is a missing file (in the Catalog but not on Disk).
139 + */
140 +/* TODO: tweak verify code to use the same function */
141 +bool accurate_check_file(JCR *jcr, FILE_DBR *fdbr, char *attr, char *Opts_Digest, int *do_Digest)
142 +{
143 +   char *p;
144 +   int stat=false;
145 +   struct stat statf;                 /* file stat */
146 +   struct stat statc;                 /* catalog stat */
147 +
148 +   int32_t LinkFIf, LinkFIc;
149 +
150 +   decode_stat(attr, &statf, &LinkFIf);  /* decode file stat packet */
151 +   decode_stat(fdbr->LStat, &statc, &LinkFIc); /* decode catalog stat */
152 +   *do_Digest = CRYPTO_DIGEST_NONE;
153 +
154 +   for (p=Opts_Digest; *p; p++) {
155 +      char ed1[30], ed2[30];
156 +      switch (*p) {
157 +      case 'i':                /* compare INODEs */
158 +        if (statc.st_ino != statf.st_ino) {
159 +           Jmsg(jcr, M_INFO, 0, _("      st_ino   differ. Cat: %s File: %s\n"),
160 +                edit_uint64((uint64_t)statc.st_ino, ed1),
161 +                edit_uint64((uint64_t)statf.st_ino, ed2));
162 +           stat = true;
163 +        }
164 +        break;
165 +      case 'p':                /* permissions bits */
166 +        if (statc.st_mode != statf.st_mode) {
167 +           Jmsg(jcr, M_INFO, 0, _("      st_mode  differ. Cat: %x File: %x\n"),
168 +                (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
169 +           stat = true;
170 +        }
171 +        break;
172 +      case 'n':                /* number of links */
173 +        if (statc.st_nlink != statf.st_nlink) {
174 +           Jmsg(jcr, M_INFO, 0, _("      st_nlink differ. Cat: %d File: %d\n"),
175 +                (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
176 +           stat = true;
177 +        }
178 +        break;
179 +      case 'u':                /* user id */
180 +        if (statc.st_uid != statf.st_uid) {
181 +           Jmsg(jcr, M_INFO, 0, _("      st_uid   differ. Cat: %u File: %u\n"),
182 +                (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
183 +           stat = true;
184 +        }
185 +        break;
186 +      case 'g':                /* group id */
187 +        if (statc.st_gid != statf.st_gid) {
188 +           Jmsg(jcr, M_INFO, 0, _("      st_gid   differ. Cat: %u File: %u\n"),
189 +                (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
190 +           stat = true;
191 +        }
192 +        break;
193 +      case 's':                /* size */
194 +        if (statc.st_size != statf.st_size) {
195 +           Jmsg(jcr, M_INFO, 0, _("      st_size  differ. Cat: %s File: %s\n"),
196 +                edit_uint64((uint64_t)statc.st_size, ed1),
197 +                edit_uint64((uint64_t)statf.st_size, ed2));
198 +           stat = true;
199 +        }
200 +        break;
201 +      case 'a':                /* access time */
202 +        if (statc.st_atime != statf.st_atime) {
203 +           Jmsg(jcr, M_INFO, 0, _("      st_atime differs\n"));
204 +           stat = true;
205 +        }
206 +        break;
207 +      case 'm':
208 +        if (statc.st_mtime != statf.st_mtime) {
209 +           Jmsg(jcr, M_INFO, 0, _("      st_mtime differs\n"));
210 +           stat = true;
211 +        }
212 +        break;
213 +      case 'c':                /* ctime */
214 +        if (statc.st_ctime != statf.st_ctime) {
215 +           Jmsg(jcr, M_INFO, 0, _("      st_ctime differs\n"));
216 +           stat = true;
217 +        }
218 +        break;
219 +      case 'd':                /* file size decrease */
220 +        if (statc.st_size > statf.st_size) {
221 +           Jmsg(jcr, M_INFO, 0, _("      st_size  decrease. Cat: %s File: %s\n"),
222 +                edit_uint64((uint64_t)statc.st_size, ed1),
223 +                edit_uint64((uint64_t)statf.st_size, ed2));
224 +           stat = true;
225 +        }
226 +        break;
227 +      case '5':                /* compare MD5 */
228 +        Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
229 +        *do_Digest = CRYPTO_DIGEST_MD5;
230 +        break;
231 +      case '1':                 /* compare SHA1 */
232 +        *do_Digest = CRYPTO_DIGEST_SHA1;
233 +        break;
234 +      case ':':
235 +      case 'V':
236 +      default:
237 +        break;
238 +      }
239 +   }
240 +   return stat;
241 +}
242 +
243 +/*
244 + * This function is called at EOJ.
245 + *  For a Full backup, we remove old one, and we add all entries 
246 + *  For an Incremental, we add all entries (delete have been before)
247 + *  For a Differential, we add all entries (delete have been before)
248 + * 
249 + * TODO: 
250 + *      
251 + */
252 +bool accurate_update_current_files(JCR *jcr)
253 +{
254 +   JobId_t backupid;
255 +
256 +   if (jcr->accurate == false) {
257 +      return true;
258 +   }
259 +
260 +   backupid = db_accurate_find_backupid(jcr, jcr->db, &jcr->jr);
261 +
262 +   Dmsg1(1, "backupid = %i\n", backupid);
263 +
264 +   if (!backupid) {
265 +      return false;            /* something goes wrong */
266 +   }
267 +
268 +   if (jcr->JobLevel == L_FULL) {
269 +      db_accurate_cleanup_currentfile(jcr, jcr->db, backupid);
270 +   }
271 +
272 +   db_accurate_update_currentfile(jcr, jcr->db, jcr->JobId, 
273 +                                 jcr->JobLevel, backupid);
274 +   return true;
275 +}
276 +
277 +/*
278 + * We are called here for each record that matches the above
279 + *  SQL query -- that is for each file contained in the Catalog
280 + *  that was not marked earlier. This means that the file in
281 + *  question is a missing file (in the Catalog but not on Disk).
282 + */
283 +static int accurate_handler(void *ctx, int num_fields, char **row)
284 +{
285 +   JCR *jcr = (JCR *)ctx;
286 +
287 +   if (job_canceled(jcr)) {
288 +      return 1;
289 +   }
290 +   if (num_fields == 2) {      /* deleted files */
291 +      jcr->file_bsock->fsend("%s%s", row[0]?row[0]:"", row[1]?row[1]:""); 
292 +   } else if (num_fields == 1) { /* files to backup */
293 +      jcr->file_bsock->fsend("%s", row[0]?row[0]:""); 
294 +   }
295 +   return 0;
296 +}
297 +
298 +/*
299 + * Send deleted files and files to backup in accurate mode
300 + *
301 + */
302 +static int accurate_send_missing_and_deleted_files(JCR *jcr, JobId_t BackupId)
303 +{
304 +   char buf[MAXSTRING];
305 +   char ed1[50], ed2[50];
306 +
307 +   bsnprintf(buf, sizeof(buf),
308 +            "SELECT Name FROM ToBackup%s",
309 +            edit_uint64(jcr->JobId, ed2));
310 +
311 +   /* missing_handler is called for each file found */
312 +   Dmsg1(2, "display files to backup cmd=%s\n", buf);
313 +   db_sql_query(jcr->db, buf, accurate_handler, (void *)jcr);
314 +   jcr->file_bsock->signal(BNET_EOD);
315 +
316 +   bsnprintf(buf, sizeof(buf),
317 +      "SELECT Path.Path,Filename.Name "
318 +        "FROM CurrentFile "
319 +             "JOIN File USING (FileId) "
320 +             "JOIN Path USING (PathId) "
321 +             "JOIN Filename USING (FilenameId) "
322 +      "WHERE CurrentFile.BackupId=%s "
323 +        "AND CurrentFile.MarkId!=%s ",
324 +            edit_uint64(BackupId, ed1), edit_uint64(jcr->JobId, ed2));
325 +   /* missing_handler is called for each file found */
326 +   Dmsg1(2, "display deleted files cmd=%s\n", buf);
327 +   db_sql_query(jcr->db, buf, accurate_handler, (void *)jcr);
328 +   jcr->file_bsock->signal(BNET_EOD);
329 +   
330 +   return 1;
331 +}
332 +
333 +/*
334 + * Accurate backup mode
335 + * 1. Receive the list of all files including those backed up to the Dir
336 + * 2. Dir computes files and deleted files.
337 + * 3. Dir sends list of additional files (new files) to backup,
338 + *    and list of files deleted.
339 + *
340 + * Cleanup attributes (don't use atime, inode etc..)
341 + * Need to insert file and attributes to temp table ?
342 + * Batch compare files and attributes ?
343 + *
344 + * If file have file_index=0, they are discarded by FD
345 + *
346 + * TODO: send deleted list and new list to client
347 + *       tweak SD with file_index=-1
348 + */
349 +bool accurate_compute_files(JCR *jcr)
350 +{
351 +   BSOCK   *fd;
352 +   char buf[MAXSTRING];
353 +   int n, len;
354 +   FILE_DBR fdbr;
355 +   POOLMEM *fname = get_pool_memory(PM_MESSAGE);
356 +   int do_Digest = CRYPTO_DIGEST_NONE;
357 +   int32_t file_index = 0;
358 +   JobId_t JobId=0;            /* TODO: compute the job key in new table */
359 +   JobId_t backupid=0;
360 +
361 +   memset(&fdbr, 0, sizeof(FILE_DBR));
362 +   fd = jcr->file_bsock;
363 +   fdbr.JobId = JobId;         
364 +   jcr->FileIndex = 0;
365 +
366 +   if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
367 +      return true;
368 +   }
369 +
370 +   backupid = db_accurate_find_backupid(jcr, jcr->db, &jcr->jr);
371 +   if (!backupid) {
372 +      Jmsg(jcr, M_FATAL, 0, _("Can't use Accurate mode ERR=Can't find BackupId\n"));
373 +      return false;
374 +   }
375 +   db_accurate_create_tobackup_table(jcr, jcr->db, jcr->JobId);
376 +   Dmsg0(1, "bdird: waiting to receive file attributes\n");
377 +   /*
378 +    * Get Attributes and Signature from File daemon
379 +    * We expect:
380 +    *   FileIndex
381 +    *   Stream
382 +    *   Options or Digest (MD5/SHA1)
383 +    *   Filename
384 +    *   Attributes
385 +    *   Link name  ???
386 +    */
387 +   while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
388 +      int stream;
389 +      char *attr, *p, *fn;
390 +      char Opts_Digest[MAXSTRING];        /* Verify Opts or MD5/SHA1 digest */
391 +
392 +      if (job_canceled(jcr)) {
393 +         goto bail_out2;
394 +      }
395 +      fname = check_pool_memory_size(fname, fd->msglen);
396 +      jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
397 +      Dmsg1(1, "Atts+Digest=%s\n", fd->msg);
398 +      if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
399 +            fname)) != 3) {
400 +         Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
401 +" mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
402 +         goto bail_out2;
403 +      }
404 +      /*
405 +       * We read the Options or Signature into fname
406 +       *  to prevent overrun, now copy it to proper location.
407 +       */
408 +      bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
409 +      p = fd->msg;
410 +      skip_nonspaces(&p);             /* skip FileIndex */
411 +      skip_spaces(&p);
412 +      skip_nonspaces(&p);             /* skip Stream */
413 +      skip_spaces(&p);
414 +      skip_nonspaces(&p);             /* skip Opts_Digest */
415 +      p++;                            /* skip space */
416 +      fn = fname;
417 +      while (*p != 0) {
418 +         *fn++ = *p++;                /* copy filename */
419 +      }
420 +      *fn = *p++;                     /* term filename and point to attribs */
421 +      attr = p;
422 +      /*
423 +       * Got attributes stream, decode it
424 +       */
425 +      if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
426 +        int changed=true;
427 +         Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
428 +         jcr->JobFiles++;
429 +         jcr->FileIndex = file_index;    /* remember attribute file_index */
430 +         do_Digest = CRYPTO_DIGEST_NONE;
431 +         pm_strcpy(jcr->fname, fname);  /* move filename into JCR */
432 +        fdbr.FileId = 0;
433 +
434 +         /*
435 +          * Find equivalent record in the database
436 +          */
437 +        
438 +        if (db_accurate_get_file_attributes_record(jcr, jcr->db, jcr->fname,
439 +                                                   backupid, &fdbr))
440 +        {
441 +           Dmsg2(1, "get_file ok fname=%s fileid=%i\n", jcr->fname, fdbr.FileId);
442 +           if (fdbr.MarkId != jcr->JobId) {           /* Already visited ? */
443 +              if (file_index == 0) { /* file not saved */
444 +                 changed = accurate_check_file(jcr, &fdbr, attr, Opts_Digest, &do_Digest);
445 +                 Dmsg1(1, "check_file changed=%i\n", changed);
446 +                 
447 +                 if (changed == true) {
448 +                    db_accurate_mark_file_for_backup(jcr, jcr->db, jcr->fname, jcr->JobId);
449 +                    db_accurate_delete_file_record(jcr, jcr->db, fdbr.FileId, backupid);
450 +                 } else {
451 +                    db_accurate_mark_file_record(jcr, jcr->db, backupid,
452 +                                                 fdbr.FileId, jcr->JobId);
453 +                 }
454 +              } else {         /* file_index != 0 file have be backuped */
455 +                 db_accurate_delete_file_record(jcr, jcr->db, fdbr.FileId, backupid);
456 +              }
457 +           } else {
458 +              Dmsg2(1, "already saved fname=%s fileid=%i\n", jcr->fname, fdbr.FileId);
459 +           }
460 +        } else if (file_index == 0) {
461 +              Dmsg1(1, "mark_for_backup fname=%s\n", jcr->fname);
462 +              db_accurate_mark_file_for_backup(jcr, jcr->db, jcr->fname, jcr->JobId);
463 +        }
464 +      
465 +      /*
466 +       * Got Digest Signature from Storage daemon
467 +       *  It came across in the Opts_Digest field.
468 +       */
469 +        /* not used */
470 +      } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
471 +         Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
472 +         /*
473 +          * When ever we get a digest it MUST have been
474 +          * preceded by an attributes record, which sets attr_file_index
475 +          */
476 +         if (jcr->FileIndex != (uint32_t)file_index) {
477 +            Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
478 +               file_index, jcr->FileIndex);
479 +            goto bail_out2;
480 +         }
481 +         if (do_Digest != CRYPTO_DIGEST_NONE) {
482 +            db_escape_string(jcr, jcr->db, buf, Opts_Digest, strlen(Opts_Digest));
483 +            if (strcmp(buf, fdbr.Digest) != 0) {
484 +               if (debug_level >= 10) {
485 +                  Jmsg(jcr, M_INFO, 0, _("      %d not same. File=%s Cat=%s\n"),
486 +                       stream, buf, fdbr.Digest);
487 +               } else {
488 +                  Jmsg(jcr, M_INFO, 0, _("      %d differs.\n"),
489 +                       stream);
490 +               }
491 +               //stat = JS_Differences;
492 +            }
493 +            do_Digest = CRYPTO_DIGEST_NONE;
494 +         }
495 +      }
496 +//      jcr->JobFiles = file_index;
497 +   }
498 +   if (is_bnet_error(fd)) {
499 +      berrno be;
500 +      Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
501 +                        n, be.bstrerror());
502 +      goto bail_out2;
503 +   }
504 +
505 +/*
506 +CREATE  VIEW cf AS SELECT path.path || filename.name as filename, 
507 +       jobid, currentfile.markid, backupid 
508 +  FROM File join currentfile using (fileid) join filename using (filenameid) join path using (pathid)
509 +*/
510 +
511 +   accurate_send_missing_and_deleted_files(jcr, backupid);
512 +
513 +   db_accurate_clean_deleted_files(jcr, jcr->db, jcr->JobId, backupid);
514 +
515 +   db_accurate_drop_tobackup_table(jcr, jcr->db, jcr->JobId);
516 +
517 +   free_pool_memory(fname);
518 +   return true;
519 +
520 +bail_out2:
521 +   db_accurate_drop_tobackup_table(jcr, jcr->db, jcr->JobId);
522 +   return false;
523 +}
524 +
525 +/*
526   * Do a backup of the specified FileSet
527   *
528   *  Returns:  false on failure
529 @@ -231,9 +675,18 @@
530        goto bail_out;
531     }
532  
533 +   /*
534 +    * If backup is in accurate mode, FD will send the list of
535 +    * all files. We have to store it, and compute witch files
536 +    * have been deleted and witch files have to be backuped.
537 +    */
538 +   accurate_compute_files(jcr);
539 +
540     /* Pickup Job termination data */
541     stat = wait_for_job_termination(jcr);
542     db_write_batch_file_records(jcr);    /* used by bulk batch file insert */
543 +   accurate_update_current_files(jcr);
544 +
545     if (stat == JS_Terminated) {
546        backup_cleanup(jcr, stat);
547        return true;
548 Index: src/dird/inc_conf.c
549 ===================================================================
550 --- src/dird/inc_conf.c (révision 6372)
551 +++ src/dird/inc_conf.c (copie de travail)
552 @@ -94,6 +94,7 @@
553   * Items that are valid in an Options resource
554   */
555  static RES_ITEM options_items[] = {
556 +   {"accurate",        store_opts,    {0},     0, 0, 0},
557     {"compression",     store_opts,    {0},     0, 0, 0},
558     {"signature",       store_opts,    {0},     0, 0, 0},
559     {"verify",          store_opts,    {0},     0, 0, 0},
560 @@ -153,7 +154,8 @@
561     INC_KW_NOATIME,
562     INC_KW_ENHANCEDWILD,
563     INC_KW_CHKCHANGES,
564 -   INC_KW_STRIPPATH
565 +   INC_KW_STRIPPATH,
566 +   INC_KW_ACCURATE
567  };
568  
569  /*
570 @@ -163,6 +165,7 @@
571   *   options given above.
572   */
573  static struct s_kw FS_option_kw[] = {
574 +   {"accurate",    INC_KW_ACCURATE},
575     {"compression", INC_KW_COMPRESSION},
576     {"signature",   INC_KW_DIGEST},
577     {"encryption",  INC_KW_ENCRYPTION},
578 @@ -251,6 +254,8 @@
579     {"no",       INC_KW_ENHANCEDWILD,  "0"},
580     {"yes",      INC_KW_CHKCHANGES,    "c"},
581     {"no",       INC_KW_CHKCHANGES,    "0"},
582 +   {"yes",      INC_KW_ACCURATE,      "C"},
583 +   {"no",       INC_KW_ACCURATE,      "0"},
584     {NULL,       0,                      0}
585  };
586  
587 Index: src/filed/backup.c
588 ===================================================================
589 --- src/filed/backup.c  (révision 6372)
590 +++ src/filed/backup.c  (copie de travail)
591 @@ -48,8 +48,114 @@
592  static bool crypto_session_start(JCR *jcr);
593  static void crypto_session_end(JCR *jcr);
594  static bool crypto_session_send(JCR *jcr, BSOCK *sd);
595 +static bool encode_and_send_deleted_file(JCR *jcr, char *fname);
596  
597  /*
598 + * Called by save_file when accept/discard file for backup
599 + *
600 + * TODO: we could add MD5/SHAX digest, but we have to compute it
601 + * for all files.
602 + */
603 +static bool accurate_add_file(JCR *jcr, FF_PKT *ff_pkt, char *stats)
604 +{
605 +   char *a=stats;
606 +   char attribs[MAXSTRING];
607 +   uint32_t file_index=jcr->JobFiles;
608 +   BSOCK *dir = jcr->dir_bsock;
609 +   int stat;
610 +
611 +   if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
612 +      return true;
613 +   }
614 +
615 +   if (!stats) {               /* TODO: don't always compute attribute  */
616 +      file_index=0;
617 +      encode_stat(attribs, ff_pkt, 0);
618 +      a = attribs;
619 +   }
620 +
621 +   switch (ff_pkt->type) {
622 +   case FT_LNKSAVED:                  /* Hard linked, file already saved */
623 +   case FT_LNK:
624 +      stat = dir->fsend("%d %d %s %s%c%s%c%s%c", file_index,
625 +            STREAM_UNIX_ATTRIBUTES, ff_pkt->VerifyOpts, ff_pkt->fname,
626 +            0, a, 0, ff_pkt->link, 0);
627 +      break;
628 +   case FT_REGE:
629 +   case FT_REG:
630 +   case FT_SPEC:
631 +   case FT_RAW:
632 +   case FT_FIFO:
633 +   case FT_NOCHG:
634 +      stat = dir->fsend("%d %d %s %s%c%s%c%c", file_index,
635 +            STREAM_UNIX_ATTRIBUTES, ff_pkt->VerifyOpts, ff_pkt->fname,
636 +            0, a, 0, 0);
637 +
638 +      break;
639 +   case FT_REPARSE: 
640 +   case FT_DIREND:
641 +   case FT_NORECURSE:
642 +   case FT_DIRNOCHG:
643 +      stat = dir->fsend("%d %d %s %s%c%s%c%c", file_index,
644 +               STREAM_UNIX_ATTRIBUTES, ff_pkt->VerifyOpts, ff_pkt->link,
645 +               0, a, 0, 0);
646 +      break;
647 +   default:
648 +      Dmsg2(1, _("Fname=%s Type=%i\n"), ff_pkt->fname, ff_pkt->type);
649 +      return true;
650 +   }
651 +
652 +   if (!stat) {
653 +      Jmsg(jcr, M_FATAL, 0, _("Network error in send to Director: ERR=%s\n"), bnet_strerror(dir));
654 +      return 0;
655 +   }
656 +
657 +   return true;
658 +}
659 +
660 +/* build a fileset with new files from director */
661 +static bool accurate_get_new_and_deleted_file_list(JCR *jcr)
662 +{   
663 +   BSOCK *dir = jcr->dir_bsock;
664 +   if (jcr->accurate == false || job_canceled(jcr)) {
665 +      return true;
666 +   }
667 +
668 +   /* get missing files */
669 +   while (dir->recv() >= 0) {
670 +      Dmsg1(1, "missing = %s\n", dir->msg);
671 +   }
672 +
673 +   /* get deleted files */
674 +   while (dir->recv() >= 0) {
675 +      Dmsg1(1, "deleted = %s\n", dir->msg);
676 +      encode_and_send_deleted_file(jcr, dir->msg); 
677 +   }
678 +  
679 +   return true;
680 +}
681 +
682 +/* send deleted file list to stored */
683 +static bool accurate_send_deleted_list(JCR *jcr)
684 +{
685 +   if (jcr->accurate == false || job_canceled(jcr)) {
686 +      return true;
687 +   }
688 +   return true;
689 +}
690 +
691 +static bool accurate_send_file_list(JCR *jcr)
692 +{
693 +   if (jcr->accurate == false || job_canceled(jcr)) {
694 +      return true;
695 +   }
696 +   Dmsg0(1, "Sending BNET_EOD\n");
697 +   jcr->dir_bsock->signal(BNET_EOD);            /* end of sending data */
698 +   return true;
699 +}
700 +
701 +
702 +/*
703   * Find all the requested files and send them
704   * to the Storage daemon.
705   *
706 @@ -66,7 +172,6 @@
707     BSOCK *sd;
708     bool ok = true;
709     // TODO landonf: Allow user to specify encryption algorithm
710 -
711     sd = jcr->store_bsock;
712  
713     set_jcr_job_status(jcr, JS_Running);
714 @@ -134,6 +239,20 @@
715        ok = false;                     /* error */
716        set_jcr_job_status(jcr, JS_ErrorTerminated);
717     }
718 +   Dmsg1(1, "jcr->accurate == %i\n", jcr->accurate);
719 +   /* start accurate stuffs */
720 +   if (jcr->accurate) {
721 +      /* TODO: test job_canceled() */
722 +      accurate_send_file_list(jcr);                 /* send all files to DIR */
723 +      accurate_get_new_and_deleted_file_list(jcr);  /* get a new incr fileset from DIR */
724 +//      set_find_options((FF_PKT *)jcr->ff, 0, 0);            /* we backup all that director wants */
725 +//      if (!find_files(jcr, (FF_PKT *)jcr->ff, save_file)) {
726 +//      ok = false;                     /* error */
727 +//      set_jcr_job_status(jcr, JS_ErrorTerminated);
728 +//      }
729 +//      accurate_send_file_list(jcr);                 /* send all new files to DIR */
730 +      accurate_send_deleted_list(jcr);              /* send deleted list to SD  */
731 +   }
732  
733     free_pool_memory(jcr->acl_text);
734  
735 @@ -355,9 +474,11 @@
736     case FT_DIRNOCHG:
737     case FT_NOCHG:
738        Jmsg(jcr, M_SKIPPED, 1, _("     Unchanged file skipped: %s\n"), ff_pkt->fname);
739 +      accurate_add_file(jcr, ff_pkt, NULL); /* list skipped files */
740        return 1;
741     case FT_ISARCH:
742        Jmsg(jcr, M_NOTSAVED, 0, _("     Archive file not saved: %s\n"), ff_pkt->fname);
743 +      accurate_add_file(jcr, ff_pkt, NULL); /* list skipped files */
744        return 1;
745     case FT_NOOPEN: {
746        berrno be;
747 @@ -1111,6 +1232,9 @@
748     }
749     unstrip_path(ff_pkt);
750  
751 +   /* list backuped files */
752 +   accurate_add_file(jcr, ff_pkt, attribs);
753 +
754     Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg);
755     if (!stat) {
756        Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
757 @@ -1121,6 +1245,58 @@
758     return true;
759  }
760  
761 +static bool encode_and_send_deleted_file(JCR *jcr, char *fname) 
762 +{
763 +   BSOCK *sd = jcr->store_bsock;
764 +   char *attribs;
765 +   char *attribsEx;
766 +   int stat;
767 +#ifdef FD_NO_SEND_TEST
768 +   return true;
769 +#endif
770 +
771 +   attribs = " ";
772 +   attribsEx = " ";
773 +
774 +   /*
775 +    * Send Attributes header to Storage daemon
776 +    *    <file-index> <stream> <info>
777 +    */
778 +   if (!sd->fsend("%ld %d 0", 0, STREAM_UNIX_ATTRIBUTES)) {
779 +      Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
780 +            sd->bstrerror());
781 +      return false;
782 +   }
783 +   Dmsg1(300, ">stored: attrhdr %s\n", sd->msg);
784 +
785 +   /*
786 +    * Send file attributes to Storage daemon
787 +    *   File_index
788 +    *   File type
789 +    *   Filename (full path)
790 +    *   Encoded attributes
791 +    *   Link name (if type==FT_LNK or FT_LNKSAVED)
792 +    *   Encoded extended-attributes (for Win32)
793 +    *
794 +    * For a directory, link is the same as fname, but with trailing
795 +    * slash. For a linked file, link is the link.
796 +    */
797 +   stat = sd->fsend("%ld %d %s%c%s%c%s%c%s%c", 
798 +                   0 /* FileIndex */,
799 +                   FT_NOSTAT /* FileType */,
800 +                   fname /* FileName */, 
801 +                   0, attribs, 0, 0, 0, attribsEx, 0);
802 +
803 +   Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg);
804 +   if (!stat) {
805 +      Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
806 +            sd->bstrerror());
807 +      return false;
808 +   }
809 +   sd->signal(BNET_EOD);            /* indicate end of attributes data */
810 +   return true;
811 +}
812 +
813  /* 
814   * Do in place strip of path
815   */
816 Index: src/filed/job.c
817 ===================================================================
818 --- src/filed/job.c     (révision 6372)
819 +++ src/filed/job.c     (copie de travail)
820 @@ -1087,6 +1087,9 @@
821        case 'c':
822           fo->flags |= FO_CHKCHANGES;
823           break;
824 +      case 'C':
825 +         fo->flags |= FO_ACCURATE;
826 +         break;
827        default:
828           Emsg1(M_ERROR, 0, _("Unknown include/exclude option: %c\n"), *p);
829           break;
830 @@ -1195,6 +1198,9 @@
831  
832     level = get_memory(dir->msglen+1);
833     Dmsg1(110, "level_cmd: %s", dir->msg);
834 +   if (strstr(dir->msg, "accurate")) {
835 +      jcr->accurate = true;
836 +   }
837     if (sscanf(dir->msg, "level = %s ", level) != 1) {
838        goto bail_out;
839     }
840 @@ -1204,14 +1210,14 @@
841     /* Full backup requested? */
842     } else if (strcmp(level, "full") == 0) {
843        jcr->JobLevel = L_FULL;
844 -   } else if (strcmp(level, "differential") == 0) {
845 +   } else if (strstr(level, "differential")) {
846        jcr->JobLevel = L_DIFFERENTIAL;
847        free_memory(level);
848        return 1;
849 -   } else if (strcmp(level, "incremental") == 0) {
850 +   } else if (strstr(level, "incremental")) {
851        jcr->JobLevel = L_INCREMENTAL;
852        free_memory(level);
853 -      return 1;   
854 +      return 1;
855     /*
856      * We get his UTC since time, then sync the clocks and correct it
857      *   to agree with our clock.
858 Index: src/cats/sql_update.c
859 ===================================================================
860 --- src/cats/sql_update.c       (révision 6372)
861 +++ src/cats/sql_update.c       (copie de travail)
862 @@ -88,6 +88,102 @@
863     return stat;
864  }
865  
866 +int db_accurate_delete_file_record(JCR *jcr, B_DB *mdb, FileId_t FileId, JobId_t BackupId)
867 +{
868 +   int stat;
869 +   char ed1[50], ed2[50];
870 +   db_lock(mdb);
871 +   Mmsg(mdb->cmd, "DELETE FROM CurrentFile WHERE FileId=%s AND BackupId=%s",
872 +       edit_int64(FileId, ed1), edit_int64(BackupId, ed2));
873 +   stat = INSERT_DB(jcr, mdb, mdb->cmd);
874 +   db_unlock(mdb);
875 +   return stat;
876 +}
877 +
878 +int db_accurate_mark_file_for_backup(JCR *jcr, B_DB *mdb, char *fname, JobId_t JobId)
879 +{
880 +   int stat;
881 +   char ed1[50];
882 +   db_lock(mdb);
883 +   /* TODO: mdb->esc_xxx are already ok but it's more smart to recompute it */
884 +//   mdb->esc_name = check_pool_memory_size(mdb->esc_name, 2*len+2);
885 +//   mdb->esc_name = db_escape_string(jcr, mdb, mdb->esc_name, fname, len);
886 +   Mmsg(mdb->cmd, "INSERT INTO ToBackup%s (name) VALUES ('%s%s')", edit_int64(JobId, ed1), mdb->esc_path, mdb->esc_name);
887 +   stat = INSERT_DB(jcr, mdb, mdb->cmd);
888 +   db_unlock(mdb);
889 +   return stat;
890 +}
891 +
892 +int db_accurate_cleanup_currentfile(JCR *jcr, B_DB *mdb, JobId_t BackupId)
893 +{
894 +   int stat;
895 +   char ed1[50];
896 +   db_lock(mdb);
897 +   Mmsg(mdb->cmd, "DELETE FROM CurrentFile WHERE BackupId=%s", edit_int64(BackupId, ed1));
898 +   stat = QUERY_DB(jcr, mdb, mdb->cmd);
899 +   db_unlock(mdb);
900 +   return stat;
901 +}
902 +
903 +int db_accurate_update_currentfile(JCR *jcr, B_DB *mdb, JobId_t JobId, int JobLevel, JobId_t BackupId)
904 +{
905 +   int stat;
906 +   char ed1[50], ed2[50], ed3[50];
907 +   db_lock(mdb);
908 +   edit_int64(JobId, ed2);
909 +   Mmsg(mdb->cmd, 
910 +       "INSERT INTO CurrentFile (FileId, BackupId, FullMark, MarkId) "
911 +       " (SELECT FileId, %s, '%c', %s FROM File WHERE JobId=%s)", 
912 +       edit_int64(BackupId, ed1),
913 +       JobLevel, ed2, ed2);
914 +   stat = QUERY_DB(jcr, mdb, mdb->cmd);
915 +   db_unlock(mdb);
916 +   return stat; 
917 +}
918 +
919 +int db_accurate_create_tobackup_table(JCR *jcr, B_DB *mdb, JobId_t JobId)
920 +{
921 +   int stat;
922 +   char ed1[50];
923 +   db_lock(mdb);
924 +   Mmsg(mdb->cmd, "CREATE TABLE ToBackup%s (name text)", edit_int64(JobId, ed1));
925 +//   Mmsg(mdb->cmd, "CREATE TEMPORARY TABLE ToBackup%s (name text)", edit_int64(JobId, ed1));
926 +   stat = QUERY_DB(jcr, mdb, mdb->cmd);
927 +   db_unlock(mdb);
928 +   return stat;
929 +}
930 +
931 +int db_accurate_drop_tobackup_table(JCR *jcr, B_DB *mdb, JobId_t JobId)
932 +{
933 +   int stat=0;
934 +   char ed1[50];
935 +   db_lock(mdb);
936 +//   Mmsg(mdb->cmd, "DROP TABLE ToBackup%s", edit_int64(JobId, ed1));
937 +//   stat = QUERY_DB(jcr, mdb, mdb->cmd);
938 +   db_unlock(mdb);
939 +   return stat;
940 +}
941 +
942 +
943 +/* Mark the file record as being visited during database
944 + * accurate compare. Stuff JobId into the MarkId field
945 + */
946 +int db_accurate_mark_file_record(JCR *jcr, B_DB *mdb, JobId_t BackupId, FileId_t FileId, JobId_t JobId)
947 +{
948 +   int stat;
949 +   char ed1[50], ed2[50], ed3[50];
950 +
951 +   db_lock(mdb);
952 +   Mmsg(mdb->cmd, "UPDATE CurrentFile SET MarkId=%s WHERE FileId=%s AND BackupId=%s", 
953 +       edit_int64(JobId, ed1), edit_int64(FileId, ed2), edit_int64(BackupId, ed3));
954 +   stat = QUERY_DB(jcr, mdb, mdb->cmd);
955 +   if (!stat || sql_affected_rows(mdb) != 1) {
956 +      stat = 0;
957 +   }
958 +   db_unlock(mdb);
959 +   return stat;
960 +}
961 +
962  /*
963   * Update the Job record at start of Job
964   *
965 Index: src/cats/drop_postgresql_tables.in
966 ===================================================================
967 --- src/cats/drop_postgresql_tables.in  (révision 6372)
968 +++ src/cats/drop_postgresql_tables.in  (copie de travail)
969 @@ -5,7 +5,7 @@
970  bindir=@SQL_BINDIR@
971  db_name=@db_name@
972  
973 -$bindir/psql -f - -d ${db_name} $* <<END-OF-DATA
974 +$bindir/psql -f - -U regress -d ${db_name} $* <<END-OF-DATA
975  drop table unsavedfiles;
976  drop table basefiles;
977  drop table jobmedia;
978 Index: src/cats/make_postgresql_tables.in
979 ===================================================================
980 --- src/cats/make_postgresql_tables.in  (révision 6372)
981 +++ src/cats/make_postgresql_tables.in  (copie de travail)
982 @@ -5,7 +5,7 @@
983  bindir=@SQL_BINDIR@
984  db_name=@db_name@
985  
986 -$bindir/psql -f - -d ${db_name} $* <<END-OF-DATA
987 +$bindir/psql -f - -U regress -d ${db_name} $* <<END-OF-DATA
988  
989  CREATE TABLE filename
990  (
991 @@ -43,6 +43,59 @@
992  CREATE INDEX file_jobid_idx on file (jobid);
993  CREATE INDEX file_fp_idx on file (filenameid, pathid);
994  
995 +CREATE TABLE CurrentBackupId
996 +(
997 +     BackupId          serial     not null,
998 +     ClientId          integer    not null,
999 +     JobName           text       not null,
1000 +     FileSetId         integer    not null,
1001 +     primary key (BackupId)
1002 +);
1003 +
1004 +-- Serait bien de prendre la meme table pour
1005 +-- les File et le CurrentBackup...
1006 +-- Mais y'a des problemes pour les prunes
1007 +
1008 +CREATE TABLE CurrentFile
1009 +(
1010 +     FileId           integer    not null,
1011 +     BackupId         integer    not null,
1012 +     FullMark         char(1)    default 0,
1013 +     MarkId           integer    default 0,
1014 +     primary key (FileId)
1015 +);
1016 +
1017 +CREATE INDEX currentfile_fileid on CurrentFile (BackupId);
1018 +
1019 +-- CREATE TEMPORARY TABLE batch (fileindex int,
1020 +--                               jobid int,
1021 +--                               path varchar,
1022 +--                               name varchar,
1023 +--                               lstat varchar,
1024 +--                               md5 varchar);
1025 +-- 
1026 +-- -- On batch insert dans la table temporaire
1027 +
1028 +-- il faut trouver les fichiers manquant
1029 +-- INSERT des nouveaux, UPDATE des anciens, SELECT pour trouver les deletes
1030 +
1031 +
1032 +-- il faut trouver les fichiers modifies
1033 +-- Le champs LStat n'est plus le meme
1034 +-- SELECT * 
1035 +--   FROM CurrentBackup, 
1036 +--        batch JOIN Path USING (Path) JOIN Filename USING (Name)
1037 +--  WHERE Path.PathId = CurrentBackup.PathId
1038 +--    AND Filename.FilenameId = CurrentBackup.FilenameId
1039 +--    AND CurrentBackup.LStat != batch.LStat
1040 +-- 
1041 +-- il faut mettre a jour la liste des fichiers
1042 +
1043 +
1044 +
1045 +
1046 +
1047 +
1048  --
1049  -- Possibly add one or more of the following indexes
1050  --  if your Verifies are too slow.
1051 Index: src/cats/protos.h
1052 ===================================================================
1053 --- src/cats/protos.h   (révision 6372)
1054 +++ src/cats/protos.h   (copie de travail)
1055 @@ -78,14 +78,17 @@
1056  /* sql_delete.c */
1057  int db_delete_pool_record(JCR *jcr, B_DB *db, POOL_DBR *pool_dbr);
1058  int db_delete_media_record(JCR *jcr, B_DB *mdb, MEDIA_DBR *mr);
1059 +int db_accurate_clean_deleted_files(JCR *jcr, B_DB *mdb, JobId_t JobId, JobId_t BackupId);
1060  
1061  /* sql_find.c */
1062  bool db_find_job_start_time(JCR *jcr, B_DB *mdb, JOB_DBR *jr, POOLMEM **stime);
1063  bool db_find_last_jobid(JCR *jcr, B_DB *mdb, const char *Name, JOB_DBR *jr);
1064 +JobId_t db_accurate_find_backupid(JCR *jcr, B_DB *mdb, JOB_DBR *jr);
1065  int db_find_next_volume(JCR *jcr, B_DB *mdb, int index, bool InChanger, MEDIA_DBR *mr);
1066  bool db_find_failed_job_since(JCR *jcr, B_DB *mdb, JOB_DBR *jr, POOLMEM *stime, int &JobLevel);
1067  
1068  /* sql_get.c */
1069 +int db_accurate_get_file_attributes_record(JCR *jcr, B_DB *mdb, char *fname, JobId_t backupid, FILE_DBR *fdbr);
1070  bool db_get_pool_record(JCR *jcr, B_DB *db, POOL_DBR *pdbr);
1071  int db_get_client_record(JCR *jcr, B_DB *mdb, CLIENT_DBR *cr);
1072  bool db_get_job_record(JCR *jcr, B_DB *mdb, JOB_DBR *jr);
1073 @@ -129,6 +132,14 @@
1074  int  db_update_counter_record(JCR *jcr, B_DB *mdb, COUNTER_DBR *cr);
1075  int  db_add_digest_to_file_record(JCR *jcr, B_DB *mdb, FileId_t FileId, char *digest, int type);
1076  int  db_mark_file_record(JCR *jcr, B_DB *mdb, FileId_t FileId, JobId_t JobId);
1077 +int db_accurate_mark_file_for_backup(JCR *jcr, B_DB *mdb, char *fname, FileId_t JobId);
1078 +int db_accurate_mark_file_record(JCR *jcr, B_DB *mdb, JobId_t BackupId, FileId_t FileId, JobId_t JobId);
1079 +int db_accurate_drop_tobackup_table(JCR *jcr, B_DB *mdb, JobId_t JobId);
1080 +int db_accurate_create_tobackup_table(JCR *jcr, B_DB *mdb, JobId_t JobId);
1081 +int db_accurate_delete_file_record(JCR *jcr, B_DB *mdb, FileId_t FileId, JobId_t BackupId);
1082  void db_make_inchanger_unique(JCR *jcr, B_DB *mdb, MEDIA_DBR *mr);
1083 +int db_accurate_cleanup_currentfile(JCR *jcr, B_DB *mdb, JobId_t BackupId);
1084 +int db_accurate_update_currentfile(JCR *jcr, B_DB *mdb, JobId_t JobId, int JobLevel, JobId_t BackupId);
1085  
1086 +
1087  #endif /* __SQL_PROTOS_H */
1088 Index: src/cats/sql_find.c
1089 ===================================================================
1090 --- src/cats/sql_find.c (révision 6372)
1091 +++ src/cats/sql_find.c (copie de travail)
1092 @@ -190,7 +190,64 @@
1093     return true;
1094  }
1095  
1096 +/*
1097 + * Find BackupId of last job that ran.  E.g. for
1098 + *
1099 + * Returns: Last backuip
1100 + *
1101 + */
1102 +JobId_t
1103 +db_accurate_find_backupid(JCR *jcr, B_DB *mdb, JOB_DBR *jr)
1104 +{
1105 +   SQL_ROW row;
1106 +   char ed1[50],ed2[50];
1107 +   JobId_t backupid=0;
1108  
1109 +   if (jcr->accurate == false) {
1110 +      return 0;
1111 +   }
1112 +
1113 +   /* Find backupid */
1114 +   db_lock(mdb);
1115 +   Dmsg2(100, "JobLevel=%d JobType=%d\n", jcr->JobLevel, jcr->JobType);
1116 +   Mmsg(mdb->cmd,
1117 +"SELECT BackupId FROM CurrentBackupId WHERE JobName='%s' AND "
1118 +"ClientId=%s AND FileSetId=%s ORDER BY BackupId DESC LIMIT 1",
1119 +       jr->Name, 
1120 +       edit_int64(jr->ClientId, ed1),
1121 +       edit_int64(jr->FileSetId, ed2));
1122 +
1123 +   Dmsg1(100, "Query: %s\n", mdb->cmd);
1124 +   if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
1125 +      db_unlock(mdb);
1126 +      return 0;
1127 +   }
1128 +   if ((row = sql_fetch_row(mdb)) == NULL) {
1129 +      sql_free_result(mdb);
1130 +      if (jcr->JobLevel == L_FULL) {
1131 +        Mmsg(mdb->cmd,
1132 +             "INSERT INTO CurrentBackupId (JobName, ClientId, FileSetId) VALUES ('%s', %s, %s)",
1133 +             jr->Name, ed1, ed2);
1134 +        if (!INSERT_DB(jcr, mdb, mdb->cmd)) {
1135 +           db_unlock(mdb);
1136 +           return 0;
1137 +        }
1138 +        backupid = sql_insert_id(mdb, NT_("CurrentBackupId"));
1139 +      } else {
1140 +        Mmsg1(&mdb->errmsg, _("No Job found for: %s.\n"), mdb->cmd);
1141 +        backupid = 0;
1142 +      }
1143 +   } else {
1144 +      backupid = str_to_int64(row[0]);
1145 +   }
1146 +
1147 +   sql_free_result(mdb);
1148 +
1149 +   db_unlock(mdb);
1150 +   return backupid;
1151 +}
1152 +
1153 +
1154  /*
1155   * Find JobId of last job that ran.  E.g. for
1156   *   VERIFY_CATALOG we want the JobId of the last INIT.
1157 Index: src/cats/sql_delete.c
1158 ===================================================================
1159 --- src/cats/sql_delete.c       (révision 6372)
1160 +++ src/cats/sql_delete.c       (copie de travail)
1161 @@ -236,5 +236,22 @@
1162     return 1;
1163  }
1164  
1165 +/*
1166 + * Purge delete file from CurrentFile table. This table contains only
1167 + * current files.
1168 + */
1169 +int db_accurate_clean_deleted_files(JCR *jcr, B_DB *mdb, JobId_t JobId, JobId_t BackupId)
1170 +{
1171 +   int stat;
1172 +   char ed1[50], ed2[50];
1173 +   db_lock(mdb);
1174 +   Mmsg(mdb->cmd, "DELETE FROM CurrentFile WHERE MarkId!=%s AND BackupId=%s", 
1175 +       edit_int64(JobId, ed1), edit_int64(BackupId, ed2));
1176 +   stat = QUERY_DB(jcr, mdb, mdb->cmd);
1177 +   db_unlock(mdb);
1178 +   return stat;
1179  
1180 +}
1181 +
1182 +
1183  #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL*/
1184 Index: src/cats/sql_create.c
1185 ===================================================================
1186 --- src/cats/sql_create.c       (révision 6372)
1187 +++ src/cats/sql_create.c       (copie de travail)
1188 @@ -829,6 +829,14 @@
1189     return true;
1190  }
1191  
1192 +bool db_accurate_insert(JCR *jcr, B_DB *mdb, bool saved, const char *fname, struct stat *stat)
1193 +{
1194 +   int len;
1195 +   split_path_and_file(jcr, mdb, fname);
1196 +   /* make like in Verify code */
1197 +   return true;
1198 +} 
1199 +
1200  /*
1201   * Create File record in B_DB
1202   *
1203 Index: src/cats/sql_get.c
1204 ===================================================================
1205 --- src/cats/sql_get.c  (révision 6372)
1206 +++ src/cats/sql_get.c  (copie de travail)
1207 @@ -66,6 +66,8 @@
1208   *
1209   *  Returns: 0 on failure
1210   *           1 on success with the File record in FILE_DBR
1211 + *
1212 + * TODO: optimize this with only one query
1213   */
1214  int db_get_file_attributes_record(JCR *jcr, B_DB *mdb, char *fname, JOB_DBR *jr, FILE_DBR *fdbr)
1215  {
1216 @@ -86,7 +88,73 @@
1217     return stat;
1218  }
1219  
1220 +/*
1221 + * Given a full filename (with path), look up the File record
1222 + * (with attributes) in the database.
1223 + *
1224 + *  Returns: 0 on failure
1225 + *           1 on success with the File record in FILE_DBR
1226 + */
1227 +int db_accurate_get_file_attributes_record(JCR *jcr, B_DB *mdb, char *fname, 
1228 +                                          JobId_t backupid, FILE_DBR *fdbr)
1229 +{
1230 +   int stat=0;
1231 +   char ed1[50];
1232 +   SQL_ROW row;
1233  
1234 +   db_lock(mdb);
1235 +   split_path_and_file(jcr, mdb, fname);
1236 +
1237 +   mdb->esc_name = check_pool_memory_size(mdb->esc_name, 2*mdb->fnl+2);
1238 +   db_escape_string(jcr, mdb, mdb->esc_name, mdb->fname, mdb->fnl);
1239 +
1240 +   mdb->esc_path = check_pool_memory_size(mdb->esc_path, 2*mdb->pnl+2);
1241 +   db_escape_string(jcr, mdb, mdb->esc_path, mdb->path, mdb->pnl);
1242 +
1243 +   /* A file could be present more than one time in the same backup, so we use LIMIT 1 */
1244 +   Mmsg(mdb->cmd,
1245 +"SELECT FileId, LStat, MD5, FilenameId, PathId, FileIndex, CurrentFile.MarkId, JobId "
1246 +  "FROM File JOIN CurrentFile USING (FileId) "
1247 +            "JOIN Filename USING (FilenameId) "
1248 +            "JOIN Path     USING (PathId) "
1249 + "WHERE Path.Path='%s' "
1250 +   "AND Filename.Name='%s' "
1251 +   "AND BackupId=%s "
1252 +   "ORDER BY FileId DESC LIMIT 1",
1253 +       mdb->esc_path,
1254 +       mdb->esc_name,
1255 +       edit_int64(backupid, ed1));
1256 +   
1257 +   Dmsg1(2,"get_file %s\n", mdb->cmd);
1258 +
1259 +   if (QUERY_DB(jcr, mdb, mdb->cmd)) {
1260 +      char ed1[30];
1261 +      mdb->num_rows = sql_num_rows(mdb);
1262 +      if (mdb->num_rows == 1) {
1263 +         if ((row = sql_fetch_row(mdb)) == NULL) {
1264 +            Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
1265 +         } else {
1266 +           fdbr->FileId     = str_to_int64(row[0]);
1267 +            bstrncpy(fdbr->LStat, row[1], sizeof(fdbr->LStat));
1268 +            bstrncpy(fdbr->Digest, row[2], sizeof(fdbr->Digest));
1269 +           fdbr->FilenameId = str_to_int64(row[3]);
1270 +           fdbr->PathId     = str_to_int64(row[4]);
1271 +           fdbr->FileIndex  = str_to_int64(row[5]);
1272 +           fdbr->MarkId     = str_to_int64(row[6]);
1273 +           fdbr->JobId      = str_to_int64(row[7]);
1274 +           stat=1;
1275 +        }
1276 +      } 
1277 +      sql_free_result(mdb);
1278 +   } else {
1279 +      Mmsg(mdb->errmsg, _("File record: %s not found in Catalog for BackupId=%s.\n"), fname, ed1);
1280 +   }
1281 +
1282 +   db_unlock(mdb);
1283 +
1284 +   return stat;
1285 +}
1286 +
1287  /*
1288   * Get a File record
1289   * Returns: 0 on failure
1290 Index: src/stored/append.c
1291 ===================================================================
1292 --- src/stored/append.c (révision 6372)
1293 +++ src/stored/append.c (copie de travail)
1294 @@ -185,16 +185,18 @@
1295  
1296        Dmsg2(890, "<filed: Header FilInx=%d stream=%d\n", file_index, stream);
1297  
1298 -      if (!(file_index > 0 && (file_index == last_file_index ||
1299 -          file_index == last_file_index + 1))) {
1300 -         Jmsg0(jcr, M_FATAL, 0, _("File index from FD not positive or sequential\n"));
1301 -         ok = false;
1302 -         break;
1303 +      if (file_index != 0) {   /* TODO: handle file_index == 0 */
1304 +        if (!(file_index > 0 && (file_index == last_file_index ||
1305 +                                 file_index == last_file_index + 1))) {
1306 +           Jmsg0(jcr, M_FATAL, 0, _("File index from FD not positive or sequential\n"));
1307 +           ok = false;
1308 +           break;
1309 +        }
1310 +        if (file_index != last_file_index) {
1311 +           jcr->JobFiles = file_index;
1312 +           last_file_index = file_index;
1313 +        }
1314        }
1315 -      if (file_index != last_file_index) {
1316 -         jcr->JobFiles = file_index;
1317 -         last_file_index = file_index;
1318 -      }
1319  
1320        /* Read data stream from the File daemon.
1321         *  The data stream is just raw bytes
1322 @@ -212,24 +214,26 @@
1323              stream_to_ascii(buf1, rec.Stream,rec.FileIndex),
1324              rec.data_len);
1325  
1326 -         while (!write_record_to_block(dcr->block, &rec)) {
1327 -            Dmsg2(850, "!write_record_to_block data_len=%d rem=%d\n", rec.data_len,
1328 -                       rec.remainder);
1329 -            if (!write_block_to_device(dcr)) {
1330 -               Dmsg2(90, "Got write_block_to_dev error on device %s. %s\n",
1331 -                  dev->print_name(), dev->bstrerror());
1332 -               ok = false;
1333 -               break;
1334 -            }
1335 -         }
1336 -         if (!ok) {
1337 -            Dmsg0(400, "Not OK\n");
1338 -            break;
1339 -         }
1340 -         jcr->JobBytes += rec.data_len;   /* increment bytes this job */
1341 -         Dmsg4(850, "write_record FI=%s SessId=%d Strm=%s len=%d\n",
1342 -            FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId,
1343 -            stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len);
1344 +        if (file_index != 0) {
1345 +           while (!write_record_to_block(dcr->block, &rec)) {
1346 +              Dmsg2(850, "!write_record_to_block data_len=%d rem=%d\n", rec.data_len,
1347 +                    rec.remainder);
1348 +              if (!write_block_to_device(dcr)) {
1349 +                 Dmsg2(90, "Got write_block_to_dev error on device %s. %s\n",
1350 +                       dev->print_name(), dev->bstrerror());
1351 +                 ok = false;
1352 +                 break;
1353 +              }
1354 +           }
1355 +           if (!ok) {
1356 +              Dmsg0(400, "Not OK\n");
1357 +              break;
1358 +           }
1359 +           jcr->JobBytes += rec.data_len;   /* increment bytes this job */
1360 +           Dmsg4(850, "write_record FI=%s SessId=%d Strm=%s len=%d\n",
1361 +                 FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId,
1362 +                 stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len);
1363 +        }
1364  
1365           /* Send attributes and digest to Director for Catalog */
1366           if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX ||
1367 Index: src/findlib/find.h
1368 ===================================================================
1369 --- src/findlib/find.h  (révision 6372)
1370 +++ src/findlib/find.h  (copie de travail)
1371 @@ -108,6 +108,7 @@
1372  #define FO_ENHANCEDWILD (1<<23)       /* Enhanced wild card processing */
1373  #define FO_CHKCHANGES   (1<<24)       /* Check if file have been modified during backup */
1374  #define FO_STRIPPATH    (1<<25)       /* Check for stripping path */
1375 +#define FO_ACCURATE     (1<<26)       /* Accurate mode */
1376  
1377  struct s_included_file {
1378     struct s_included_file *next;
1379 Index: patches/testing/project-accurate-backup.patch
1380 ===================================================================
1381 --- patches/testing/project-accurate-backup.patch       (révision 6410)
1382 +++ patches/testing/project-accurate-backup.patch       (copie de travail)
1383 @@ -75,7 +75,7 @@
1384   
1385   /* Commands sent to File daemon */
1386   static char backupcmd[] = "backup\n";
1387 -@@ -96,7 +97,443 @@
1388 +@@ -96,7 +97,450 @@
1389      return true;
1390   }
1391   
1392 @@ -93,6 +93,11 @@
1393  +   return 0;
1394  +}
1395  +
1396 ++bool db_get_jobids(JCR *jcr)
1397 ++{
1398 ++   
1399 ++}
1400 ++
1401  +bool accurate_send_current_files(JCR *jcr)
1402  +{
1403  +   char buf[MAXSTRING];
1404 @@ -108,6 +113,7 @@
1405  +                JOIN File USING (FileId)
1406  +   WHERE File.FileIndex > 0
1407  +
1408 ++ DROP TABLE btemp2
1409  +
1410  +SELECT DISTINCT ON (PathId, FilenameId) FileIndex, Path, Name, LStat
1411  +  FROM File JOIN Filename USING (FilenameId) JOIN Path USING (PathId) WHERE JobId IN (40341)
1412 @@ -299,6 +305,15 @@
1413  +   char ed1[50], ed2[50];
1414  +
1415  +   bsnprintf(buf, sizeof(buf),
1416 ++           "SELECT Name FROM ToBackup%s",
1417 ++           edit_uint64(jcr->JobId, ed2));
1418 ++
1419 ++   /* missing_handler is called for each file found */
1420 ++   Dmsg1(2, "display files to backup cmd=%s\n", buf);
1421 ++   db_sql_query(jcr->db, buf, accurate_handler, (void *)jcr);
1422 ++   jcr->file_bsock->signal(BNET_EOD);
1423 ++
1424 ++   bsnprintf(buf, sizeof(buf),
1425  +      "SELECT Path.Path,Filename.Name "
1426  +        "FROM CurrentFile "
1427  +             "JOIN File USING (FileId) "
1428 @@ -312,14 +327,6 @@
1429  +   db_sql_query(jcr->db, buf, accurate_handler, (void *)jcr);
1430  +   jcr->file_bsock->signal(BNET_EOD);
1431  +   
1432 -+   bsnprintf(buf, sizeof(buf),
1433 -+           "SELECT Name FROM ToBackup%s",
1434 -+           edit_uint64(jcr->JobId, ed2));
1435 -+   /* missing_handler is called for each file found */
1436 -+   Dmsg1(2, "display files to backup cmd=%s\n", buf);
1437 -+   db_sql_query(jcr->db, buf, accurate_handler, (void *)jcr);
1438 -+   jcr->file_bsock->signal(BNET_EOD);
1439 -+
1440  +   return 1;
1441  +}
1442  +
1443 @@ -519,7 +526,7 @@
1444    * Do a backup of the specified FileSet
1445    *
1446    *  Returns:  false on failure
1447 -@@ -231,9 +668,18 @@
1448 +@@ -231,9 +675,18 @@
1449         goto bail_out;
1450      }
1451   
1452 @@ -538,18 +545,6 @@
1453      if (stat == JS_Terminated) {
1454         backup_cleanup(jcr, stat);
1455         return true;
1456 -Index: src/dird/job.c
1457 -===================================================================
1458 ---- src/dird/job.c     (révision 6372)
1459 -+++ src/dird/job.c     (copie de travail)
1460 -@@ -979,6 +979,7 @@
1461 -    jcr->spool_data = job->spool_data;
1462 -    jcr->spool_size = job->spool_size;
1463 -    jcr->write_part_after_job = job->write_part_after_job;
1464 -+   jcr->accurate = job->accurate;
1465 -    if (jcr->RestoreBootstrap) {
1466 -       free(jcr->RestoreBootstrap);
1467 -       jcr->RestoreBootstrap = NULL;
1468  Index: src/dird/inc_conf.c
1469  ===================================================================
1470  --- src/dird/inc_conf.c        (révision 6372)
1471 @@ -589,49 +584,15 @@
1472      {NULL,       0,                      0}
1473   };
1474   
1475 -Index: src/dird/dird_conf.c
1476 -===================================================================
1477 ---- src/dird/dird_conf.c       (révision 6372)
1478 -+++ src/dird/dird_conf.c       (copie de travail)
1479 -@@ -319,6 +319,7 @@
1480 -    {"selectionpattern", store_str, ITEM(res_job.selection_pattern), 0, 0, 0},
1481 -    {"runscript", store_runscript, ITEM(res_job.RunScripts), 0, ITEM_NO_EQUALS, 0},
1482 -    {"selectiontype", store_migtype, ITEM(res_job.selection_type), 0, 0, 0},
1483 -+   {"accurate", store_bool, ITEM(res_job.accurate), 0,0,0},
1484 -    {NULL, NULL, {0}, 0, 0, 0}
1485 - };
1486
1487 -@@ -618,6 +619,9 @@
1488 -       if (res->res_job.spool_size) {
1489 -          sendit(sock, _("     SpoolSize=%s\n"),        edit_uint64(res->res_job.spool_size, ed1));
1490 -       }
1491 -+      if (res->res_job.JobType == JT_BACKUP) {
1492 -+       sendit(sock, _("     Accurate=%d\n"), res->res_job.accurate);
1493 -+      }
1494 -       if (res->res_job.JobType == JT_MIGRATE) {
1495 -          sendit(sock, _("     SelectionType=%d\n"), res->res_job.selection_type);
1496 -       }
1497 -Index: src/dird/dird_conf.h
1498 -===================================================================
1499 ---- src/dird/dird_conf.h       (révision 6372)
1500 -+++ src/dird/dird_conf.h       (copie de travail)
1501 -@@ -400,6 +400,7 @@
1502 -    bool write_part_after_job;         /* Set to write part after job in SD */
1503 -    bool enabled;                      /* Set if job enabled */
1504 -    bool OptimizeJobScheduling;        /* Set if we should optimize Job scheduling */
1505 -+   bool accurate;                     /* Set if it is an accurate backup job */
1506 -    
1507 -    MSGS      *messages;               /* How and where to send messages */
1508 -    SCHED     *schedule;               /* When -- Automatic schedule */
1509  Index: src/filed/backup.c
1510  ===================================================================
1511  --- src/filed/backup.c (révision 6372)
1512  +++ src/filed/backup.c (copie de travail)
1513 -@@ -48,8 +48,113 @@
1514 +@@ -48,8 +48,114 @@
1515   static bool crypto_session_start(JCR *jcr);
1516   static void crypto_session_end(JCR *jcr);
1517   static bool crypto_session_send(JCR *jcr, BSOCK *sd);
1518 -+static bool encode_and_send_deleted_files(JCR *jcr, char *fname);
1519 ++static bool encode_and_send_deleted_file(JCR *jcr, char *fname);
1520   
1521   /*
1522  + * Called by save_file when accept/discard file for backup
1523 @@ -704,16 +665,17 @@
1524  +      return true;
1525  +   }
1526  +
1527 -+   /* get deleted files */
1528 -+   while (dir->recv() >= 0) {
1529 -+      Dmsg1(1, "deleted = %s\n", dir->msg);
1530 -+      encode_and_send_deleted_files(jcr, dir->msg); 
1531 -+   }
1532  +   /* get missing files */
1533  +   while (dir->recv() >= 0) {
1534  +      Dmsg1(1, "missing = %s\n", dir->msg);
1535  +   }
1536 -+   
1537 ++
1538 ++   /* get deleted files */
1539 ++   while (dir->recv() >= 0) {
1540 ++      Dmsg1(1, "deleted = %s\n", dir->msg);
1541 ++      encode_and_send_deleted_file(jcr, dir->msg); 
1542 ++   }
1543 ++  
1544  +   return true;
1545  +}
1546  +
1547 @@ -741,7 +703,7 @@
1548    * Find all the requested files and send them
1549    * to the Storage daemon.
1550    *
1551 -@@ -66,7 +171,6 @@
1552 +@@ -66,7 +172,6 @@
1553      BSOCK *sd;
1554      bool ok = true;
1555      // TODO landonf: Allow user to specify encryption algorithm
1556 @@ -749,7 +711,7 @@
1557      sd = jcr->store_bsock;
1558   
1559      set_jcr_job_status(jcr, JS_Running);
1560 -@@ -134,6 +238,20 @@
1561 +@@ -134,6 +239,20 @@
1562         ok = false;                     /* error */
1563         set_jcr_job_status(jcr, JS_ErrorTerminated);
1564      }
1565 @@ -770,7 +732,7 @@
1566   
1567      free_pool_memory(jcr->acl_text);
1568   
1569 -@@ -355,9 +473,11 @@
1570 +@@ -355,9 +474,11 @@
1571      case FT_DIRNOCHG:
1572      case FT_NOCHG:
1573         Jmsg(jcr, M_SKIPPED, 1, _("     Unchanged file skipped: %s\n"), ff_pkt->fname);
1574 @@ -782,7 +744,7 @@
1575         return 1;
1576      case FT_NOOPEN: {
1577         berrno be;
1578 -@@ -1111,6 +1231,9 @@
1579 +@@ -1111,6 +1232,9 @@
1580      }
1581      unstrip_path(ff_pkt);
1582   
1583 @@ -792,11 +754,11 @@
1584      Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg);
1585      if (!stat) {
1586         Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
1587 -@@ -1121,6 +1244,58 @@
1588 +@@ -1121,6 +1245,58 @@
1589      return true;
1590   }
1591   
1592 -+static bool encode_and_send_deleted_files(JCR *jcr, char *fname) 
1593 ++static bool encode_and_send_deleted_file(JCR *jcr, char *fname) 
1594  +{
1595  +   BSOCK *sd = jcr->store_bsock;
1596  +   char *attribs;
1597 @@ -1402,18 +1364,6 @@
1598   
1599            /* Send attributes and digest to Director for Catalog */
1600            if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX ||
1601 -Index: src/jcr.h
1602 -===================================================================
1603 ---- src/jcr.h  (révision 6372)
1604 -+++ src/jcr.h  (copie de travail)
1605 -@@ -208,6 +208,7 @@
1606 -    B_DB *db_batch;                    /* database pointer for batch insert */
1607 -    ATTR_DBR *ar;                      /* DB attribute record */
1608 -    guid_list *id_list;                /* User/group id to name list */
1609 -+   bool accurate;                     /* true if job is accurate */
1610
1611 -    void *plugin_ctx_list;             /* list of contexts for plugins */
1612 -    void *plugin_ctx;                  /* current plugin context */
1613  Index: src/findlib/find.h
1614  ===================================================================
1615  --- src/findlib/find.h (révision 6372)