]> git.sur5r.net Git - bacula/bacula/blob - bacula/patches/testing/project-accurate-backup.patch2
4a32725b997db2abdfcaf088e0203e6260281340
[bacula/bacula] / bacula / patches / testing / project-accurate-backup.patch2
1 Index: src/dird/fd_cmds.c
2 ===================================================================
3 --- src/dird/fd_cmds.c  (revision 6450)
4 +++ src/dird/fd_cmds.c  (working copy)
5 @@ -50,7 +50,7 @@
6  static char filesetcmd[]  = "fileset%s\n"; /* set full fileset */
7  static char jobcmd[]      = "JobId=%s Job=%s SDid=%u SDtime=%u Authorization=%s\n";
8  /* Note, mtime_only is not used here -- implemented as file option */
9 -static char levelcmd[]    = "level = %s%s mtime_only=%d\n";
10 +static char levelcmd[]    = "level = %s%s%s mtime_only=%d\n";
11  static char runscript[]   = "Run OnSuccess=%u OnFailure=%u AbortOnError=%u When=%u Command=%s\n";
12  static char runbeforenow[]= "RunBeforeNow\n";
13  
14 @@ -226,7 +226,7 @@
15     char ed1[50];
16  
17     stime = str_to_utime(jcr->stime);
18 -   fd->fsend(levelcmd, NT_("since_utime "), edit_uint64(stime, ed1), 0);
19 +   fd->fsend(levelcmd, "", NT_("since_utime "), edit_uint64(stime, ed1), 0);
20     while (bget_dirmsg(fd) >= 0) {  /* allow him to poll us to sync clocks */
21        Jmsg(jcr, M_INFO, 0, "%s\n", fd->msg);
22     }
23 @@ -240,24 +240,25 @@
24  bool send_level_command(JCR *jcr)
25  {
26     BSOCK   *fd = jcr->file_bsock;
27 +   const char *accurate=jcr->job->accurate?"accurate_":"";
28     /*
29      * Send Level command to File daemon
30      */
31     switch (jcr->JobLevel) {
32     case L_BASE:
33 -      fd->fsend(levelcmd, "base", " ", 0);
34 +      fd->fsend(levelcmd, "", "base", " ", 0);
35        break;
36     /* L_NONE is the console, sending something off to the FD */
37     case L_NONE:
38     case L_FULL:
39 -      fd->fsend(levelcmd, "full", " ", 0);
40 +      fd->fsend(levelcmd, "", "full", " ", 0);
41        break;
42     case L_DIFFERENTIAL:
43 -      fd->fsend(levelcmd, "differential", " ", 0);
44 +      fd->fsend(levelcmd, accurate, "differential", " ", 0);
45        send_since_time(jcr);
46        break;
47     case L_INCREMENTAL:
48 -      fd->fsend(levelcmd, "incremental", " ", 0);
49 +      fd->fsend(levelcmd, accurate, "incremental", " ", 0);
50        send_since_time(jcr);
51        break;
52     case L_SINCE:
53 Index: src/dird/backup.c
54 ===================================================================
55 --- src/dird/backup.c   (revision 6450)
56 +++ src/dird/backup.c   (working copy)
57 @@ -44,6 +44,7 @@
58  #include "bacula.h"
59  #include "dird.h"
60  #include "ua.h"
61 +#include "findlib/find.h"
62  
63  /* Commands sent to File daemon */
64  static char backupcmd[] = "backup\n";
65 @@ -96,7 +97,177 @@
66     return true;
67  }
68  
69 +static int get_int_handler(void *ctx, int num_fields, char **row)
70 +{
71 +   POOLMEM *ret = (POOLMEM *)ctx;
72 +   if (num_fields == 1) {
73 +      if (ret[0] != 0) {
74 +        pm_strcat(ret, ",");
75 +      }
76 +      pm_strcat(ret, row[0]);
77 +   }
78 +   return 0;
79 +}
80 +
81 +static int accurate_list_handler(void *ctx, int num_fields, char **row)
82 +{
83 +   JCR *jcr = (JCR *)ctx;
84 +
85 +   if (job_canceled(jcr)) {
86 +      return 1;
87 +   }
88 +   
89 +   if (row[0] > 0) {           /* discard when file_index == 0 */
90 +      jcr->file_bsock->fsend("%s%s%c%s", row[1], row[2], 0, row[3]); 
91 +   }
92 +   return 0;
93 +}
94 +
95 +/* Full : do nothing
96 + * Differential : get the last full id
97 + * Incremental : get the last full + last diff + last incr(s) ids
98 + *
99 + * TODO: look and merge from ua_restore.c
100 + */
101 +bool db_accurate_get_jobids(JCR *jcr, POOLMEM *jobids)
102 +{
103 +   char clientid[50], jobid[50], filesetid[50];
104 +   char date[MAX_TIME_LENGTH];
105 +
106 +   JOB_DBR *jr = &jcr->jr; 
107 +   POOLMEM *query = get_pool_memory(PM_FNAME);
108 +   bstrutime(date, sizeof(date),  time(NULL) + 1);
109 +
110 +   Mmsg(query, 
111 +"CREATE TEMPORARY TABLE btemp3%s AS ( "
112 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
113 +   "FROM Job JOIN FileSet USING (FileSetId) "
114 +  "WHERE ClientId = %s "
115 +    "AND Level='F' AND JobStatus='T' AND Type='B' "
116 +    "AND StartTime<'%s' "
117 +    "AND FileSet.FileSet=(SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
118 +  "ORDER BY Job.JobTDate DESC LIMIT 1) ",
119 +       edit_uint64(jcr->JobId, jobid),
120 +       edit_uint64(jr->ClientId, clientid),
121 +       date,
122 +       edit_uint64(jr->FileSetId, filesetid));
123 +   db_sql_query(jcr->db, query, NULL, NULL);
124 +
125 +   if (jcr->JobLevel == L_INCREMENTAL) {
126 +
127 +      Mmsg(query, 
128 +"INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
129 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
130 +   "FROM Job JOIN FileSet USING (FileSetId) "
131 +  "WHERE ClientId = %s "
132 +    "AND Level='D' AND JobStatus='T' AND Type='B' "
133 +    "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
134 +    "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
135 +  "ORDER BY Job.JobTDate DESC LIMIT 1 ",
136 +          jobid,
137 +          clientid,
138 +          jobid,
139 +          filesetid);
140 +      db_sql_query(jcr->db, query, NULL, NULL);
141 +
142 +      Mmsg(query, 
143 +"INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
144 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
145 +   "FROM Job JOIN FileSet USING (FileSetId) "
146 +  "WHERE ClientId = %s "
147 +    "AND Level='I' AND JobStatus='T' AND Type='B' "
148 +    "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
149 +    "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
150 +  "ORDER BY Job.JobTDate DESC ",
151 +          jobid,
152 +          clientid,
153 +          jobid,
154 +          filesetid);
155 +      db_sql_query(jcr->db, query, NULL, NULL);
156 +   }
157 +
158 +   jobids[0]='\0';
159 +   Mmsg(query, "SELECT JobId FROM btemp3%s", jobid);
160 +   db_sql_query(jcr->db, query, get_int_handler, jobids);
161 +   Dmsg1(1, "db_accurate_get_jobids=%s\n", jobids);
162 +
163 +   Mmsg(query, "DROP TABLE btemp3%s", jobid);
164 +   db_sql_query(jcr->db, query, NULL, NULL);
165 +   free_pool_memory(query);
166 +
167 +   return 1;
168 +}
169 +
170 +bool send_accurate_current_files(JCR *jcr)
171 +{
172 +   char buf[MAXSTRING];
173 +   char ed1[50];
174 +
175 +   if (jcr->accurate == false || job_canceled(jcr) || jcr->JobLevel == L_FULL) {
176 +      return true;
177 +   }
178 +   POOLMEM *jobids = get_pool_memory(PM_FNAME);
179 +   db_accurate_get_jobids(jcr, jobids);
180 +
181 +   if (*jobids == 0) {
182 +      free_pool_memory(jobids);
183 +      Jmsg(jcr, M_ERROR_TERM, 0, _("Cannot find previous jobids.\n"));
184 +      return true;
185 +   }
186 +
187 +   bsnprintf(buf, sizeof(buf),
188 +             "CREATE TEMPORARY TABLE btemp2%s AS ( "
189 +             "SELECT max(FileId) as FileId, PathId, FilenameId "
190 +             "FROM (SELECT FileId, PathId, FilenameId "
191 +                      "FROM File WHERE JobId IN (%s)) AS F "
192 +             "GROUP BY PathId, FilenameId ) ",
193 +             edit_uint64(jcr->JobId, ed1),
194 +             jobids);
195 +   db_sql_query(jcr->db, buf, NULL, NULL);
196 +
197 +   /* to be able to allocate the right size for htable */
198 +   POOLMEM *nb = get_pool_memory(PM_FNAME);
199 +   bsnprintf(buf, sizeof(buf), "SELECT count(1) FROM btemp2%s",ed1);
200 +   db_sql_query(jcr->db, buf, get_int_handler, nb);
201 +   jcr->file_bsock->fsend("accurate files=%s\n", nb); 
202 +
203 +   bsnprintf(buf, sizeof(buf),
204 +            "SELECT File.FileIndex, Path.Path, Filename.Name, File.LStat "
205 +              "FROM btemp2%s JOIN Path USING (PathId) " 
206 +                             "JOIN Filename USING (FilenameId) "
207 +                            "JOIN File USING (FileId) "
208 +             "WHERE File.FileIndex > 0",
209 +            ed1, jobids);
210 +   db_sql_query(jcr->db, buf, accurate_list_handler, (void *)jcr);
211 +
212 +   bsnprintf(buf, sizeof(buf), "DROP TABLE btemp2%s", ed1);
213 +   free_pool_memory(jobids);
214 +   free_pool_memory(nb);
215 +
216  /*
217 + CREATE TEMPORARY TABLE btemp2 AS (
218 +  SELECT max(FileId) as FileId, PathId, FilenameId 
219 +    FROM (SELECT FileId, PathId, FilenameId FROM File WHERE JobId IN (39867,40341)) AS F
220 +   GROUP BY PathId, FilenameId )
221 +
222 +  SELECT File.FileIndex, Path.Path, Filename.Name, File.LStat
223 +    FROM btemp2 JOIN Path USING (PathId) JOIN Filename USING (FilenameId)
224 +                JOIN File USING (FileId)
225 +   WHERE File.FileIndex > 0
226 +
227 + DROP TABLE btemp2
228 +*/
229 +/*
230 +SELECT DISTINCT ON (PathId, FilenameId) FileIndex, Path, Name, LStat
231 +  FROM File JOIN Filename USING (FilenameId) JOIN Path USING (PathId) WHERE JobId IN (40341)
232 + ORDER BY PathId, FilenameId, JobId DESC
233 +*/
234 +
235 +   jcr->file_bsock->signal(BNET_EOD);
236 +   return true;
237 +}
238 +
239 +/*
240   * Do a backup of the specified FileSet
241   *
242   *  Returns:  false on failure
243 @@ -225,6 +396,14 @@
244        Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
245     }
246  
247 +   /*
248 +    * If backup is in accurate mode, FD will send the list of
249 +    * all files.
250 +    */
251 +   if (!send_accurate_current_files(jcr)) {
252 +      goto bail_out;
253 +   }
254 +
255     /* Send backup command */
256     fd->fsend(backupcmd);
257     if (!response(jcr, fd, OKbackup, "backup", DISPLAY_ERROR)) {
258 @@ -234,6 +413,7 @@
259     /* Pickup Job termination data */
260     stat = wait_for_job_termination(jcr);
261     db_write_batch_file_records(jcr);    /* used by bulk batch file insert */
262 +
263     if (stat == JS_Terminated) {
264        backup_cleanup(jcr, stat);
265        return true;
266 Index: src/dird/inc_conf.c
267 ===================================================================
268 --- src/dird/inc_conf.c (revision 6450)
269 +++ src/dird/inc_conf.c (working copy)
270 @@ -94,6 +94,7 @@
271   * Items that are valid in an Options resource
272   */
273  static RES_ITEM options_items[] = {
274 +   {"accurate",        store_opts,    {0},     0, 0, 0},
275     {"compression",     store_opts,    {0},     0, 0, 0},
276     {"signature",       store_opts,    {0},     0, 0, 0},
277     {"verify",          store_opts,    {0},     0, 0, 0},
278 @@ -153,7 +154,8 @@
279     INC_KW_NOATIME,
280     INC_KW_ENHANCEDWILD,
281     INC_KW_CHKCHANGES,
282 -   INC_KW_STRIPPATH
283 +   INC_KW_STRIPPATH,
284 +   INC_KW_ACCURATE
285  };
286  
287  /*
288 @@ -163,6 +165,7 @@
289   *   options given above.
290   */
291  static struct s_kw FS_option_kw[] = {
292 +   {"accurate",    INC_KW_ACCURATE},
293     {"compression", INC_KW_COMPRESSION},
294     {"signature",   INC_KW_DIGEST},
295     {"encryption",  INC_KW_ENCRYPTION},
296 @@ -251,6 +254,8 @@
297     {"no",       INC_KW_ENHANCEDWILD,  "0"},
298     {"yes",      INC_KW_CHKCHANGES,    "c"},
299     {"no",       INC_KW_CHKCHANGES,    "0"},
300 +   {"yes",      INC_KW_ACCURATE,      "C"},
301 +   {"no",       INC_KW_ACCURATE,      "0"},
302     {NULL,       0,                      0}
303  };
304  
305 Index: src/filed/backup.c
306 ===================================================================
307 --- src/filed/backup.c  (revision 6450)
308 +++ src/filed/backup.c  (working copy)
309 @@ -37,6 +37,7 @@
310  
311  #include "bacula.h"
312  #include "filed.h"
313 +#include "lib/htable.h"
314  
315  /* Forward referenced functions */
316  int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level);
317 @@ -48,8 +49,223 @@
318  static bool crypto_session_start(JCR *jcr);
319  static void crypto_session_end(JCR *jcr);
320  static bool crypto_session_send(JCR *jcr, BSOCK *sd);
321 +static bool encode_and_send_deleted_file(JCR *jcr, char *fname);
322  
323 +typedef struct CurFile {
324 +   char *fname;
325 +   char *lstat;
326 +   hlink link;
327 +} CurFile;
328 +
329  /*
330 + * This function is called for each file seen in fileset.
331 + * 
332 + * If the file is skipped (saved=false), we will check if this
333 + * file have been backuped before. If not, we decide to backup it.
334 + *
335 + * If the file have saved=true, we mark it as seen
336 + * 
337 + */
338 +/* TODO: tweak verify code to use the same function */
339 +bool accurate_check_file(JCR *jcr, FF_PKT *ff_pkt, bool saved)
340 +{
341 +   char *p;
342 +   int stat=false;
343 +   struct stat statc;                 /* catalog stat */
344 +   char *Opts_Digest = ff_pkt->VerifyOpts;
345 +   char *fname = ff_pkt->fname;
346 +   CurFile *elt;
347 +
348 +   int32_t LinkFIc;
349 +
350 +   if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
351 +      return true;
352 +   }
353 +
354 +   if (ff_pkt->type == FT_DIREND || ff_pkt->type == FT_REPARSE || ff_pkt->type == FT_DIRNOCHG) {
355 +      fname = ff_pkt->link;
356 +   } 
357 +
358 +   elt = (CurFile *) jcr->file_list->lookup(fname);
359 +
360 +   if (!elt) {
361 +      // TODO: we must backup it !
362 +      Dmsg1(1, "accurate %s = yes (not found)\n", fname);
363 +      return true;
364 +   }
365 +
366 +   if (saved || *elt->lstat == '\0') {
367 +      Dmsg1(1, "accurate %s = no (already seen)\n", fname);
368 +      *elt->lstat = '\0';
369 +      return false;
370 +   }
371 +
372 +   decode_stat(elt->lstat, &statc, &LinkFIc); /* decode catalog stat */
373 +//   *do_Digest = CRYPTO_DIGEST_NONE;
374 +
375 +   for (p=Opts_Digest; *p; p++) {
376 +      char ed1[30], ed2[30];
377 +      switch (*p) {
378 +      case 'i':                /* compare INODEs */
379 +        if (statc.st_ino != ff_pkt->statp.st_ino) {
380 +           Jmsg(jcr, M_INFO, 0, _("      st_ino   differ. Cat: %s File: %s\n"),
381 +                edit_uint64((uint64_t)statc.st_ino, ed1),
382 +                edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
383 +           stat = true;
384 +        }
385 +        break;
386 +      case 'p':                /* permissions bits */
387 +        if (statc.st_mode != ff_pkt->statp.st_mode) {
388 +           Jmsg(jcr, M_INFO, 0, _("      st_mode  differ. Cat: %x File: %x\n"),
389 +                (uint32_t)statc.st_mode, (uint32_t)ff_pkt->statp.st_mode);
390 +           stat = true;
391 +        }
392 +        break;
393 +      case 'n':                /* number of links */
394 +        if (statc.st_nlink != ff_pkt->statp.st_nlink) {
395 +           Jmsg(jcr, M_INFO, 0, _("      st_nlink differ. Cat: %d File: %d\n"),
396 +                (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
397 +           stat = true;
398 +        }
399 +        break;
400 +      case 'u':                /* user id */
401 +        if (statc.st_uid != ff_pkt->statp.st_uid) {
402 +           Jmsg(jcr, M_INFO, 0, _("      st_uid   differ. Cat: %u File: %u\n"),
403 +                (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
404 +           stat = true;
405 +        }
406 +        break;
407 +      case 'g':                /* group id */
408 +        if (statc.st_gid != ff_pkt->statp.st_gid) {
409 +           Jmsg(jcr, M_INFO, 0, _("      st_gid   differ. Cat: %u File: %u\n"),
410 +                (uint32_t)statc.st_gid, (uint32_t)ff_pkt->statp.st_gid);
411 +           stat = true;
412 +        }
413 +        break;
414 +      case 's':                /* size */
415 +        if (statc.st_size != ff_pkt->statp.st_size) {
416 +           Jmsg(jcr, M_INFO, 0, _("      st_size  differ. Cat: %s File: %s\n"),
417 +                edit_uint64((uint64_t)statc.st_size, ed1),
418 +                edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
419 +           stat = true;
420 +        }
421 +        break;
422 +      case 'a':                /* access time */
423 +        if (statc.st_atime != ff_pkt->statp.st_atime) {
424 +           Jmsg(jcr, M_INFO, 0, _("      st_atime differs\n"));
425 +           stat = true;
426 +        }
427 +        break;
428 +      case 'm':
429 +        if (statc.st_mtime != ff_pkt->statp.st_mtime) {
430 +           Jmsg(jcr, M_INFO, 0, _("      st_mtime differs\n"));
431 +           stat = true;
432 +        }
433 +        break;
434 +      case 'c':                /* ctime */
435 +        if (statc.st_ctime != ff_pkt->statp.st_ctime) {
436 +           Jmsg(jcr, M_INFO, 0, _("      st_ctime differs\n"));
437 +           stat = true;
438 +        }
439 +        break;
440 +      case 'd':                /* file size decrease */
441 +        if (statc.st_size > ff_pkt->statp.st_size) {
442 +           Jmsg(jcr, M_INFO, 0, _("      st_size  decrease. Cat: %s File: %s\n"),
443 +                edit_uint64((uint64_t)statc.st_size, ed1),
444 +                edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
445 +           stat = true;
446 +        }
447 +        break;
448 +      case '5':                /* compare MD5 */
449 +        Dmsg1(500, "set Do_MD5 for %s\n", ff_pkt->fname);
450 +//      *do_Digest = CRYPTO_DIGEST_MD5;
451 +        break;
452 +      case '1':                 /* compare SHA1 */
453 +//      *do_Digest = CRYPTO_DIGEST_SHA1;
454 +        break;
455 +      case ':':
456 +      case 'V':
457 +      default:
458 +        break;
459 +      }
460 +   }
461 +   *elt->lstat = '\0';         /* mark it as seen */
462 +   Dmsg2(1, "accurate %s = %i\n", fname, stat);
463 +   return stat;
464 +}
465 +
466 +/* 
467 + * This function doesn't work very well with smartalloc
468 + */
469 +int accurate_get_current_file_list_cmd(JCR *jcr)
470 +{
471 +   BSOCK *dir = jcr->dir_bsock;
472 +   int len;
473 +   uint64_t nb;
474 +   CurFile *elt=NULL;
475 +
476 +   if (jcr->accurate == false || job_canceled(jcr) || jcr->JobLevel == L_FULL) {
477 +      return true;
478 +   }
479 +
480 +   if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
481 +      dir->fsend(_("2991 Bad accurate command\n"));
482 +      return false;
483 +   }
484 +   
485 +   jcr->file_list = (htable *)malloc(sizeof(htable));
486 +   jcr->file_list->init(elt, &elt->link, nb);
487 +
488 +   /* get current files */
489 +   while (dir->recv() >= 0) {
490 +      len = strlen(dir->msg);
491 +      if ((len+1) < dir->msglen) {
492 +//      elt = (CurFile *)malloc(sizeof(CurFile));
493 +//      elt->fname  = (char *) malloc(dir->msglen+1);
494 +
495 +        /* we store CurFile, fname and lstat in the same chunk */
496 +        elt = (CurFile *)malloc(sizeof(CurFile)+dir->msglen+1);
497 +        elt->fname  = (char *) elt+sizeof(CurFile);
498 +        memcpy(elt->fname, dir->msg, dir->msglen);
499 +        elt->fname[dir->msglen]='\0';
500 +        elt->lstat = elt->fname + len + 1;
501 +        jcr->file_list->insert(elt->fname, elt); 
502 +        Dmsg2(1, "add fname=%s lstat=%s\n", elt->fname, elt->lstat);
503 +      }
504 +   }
505 +  
506 +//   jcr->file_list->stats();
507 +   /* TODO: send a EOM ?
508 +   dir->fsend("2000 OK accurate\n");
509 +    */
510 +   return true;
511 +}
512 +
513 +bool accurate_send_deleted_list(JCR *jcr)
514 +{
515 +   if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
516 +      return true;
517 +   }
518 +
519 +   if (jcr->file_list == NULL) { /* TODO: bug ? */
520 +      return true;
521 +   }
522 +
523 +   CurFile *elt;
524 +   foreach_htable (elt, jcr->file_list) {
525 +      if (*elt->lstat != '\0') {
526 +        Dmsg2(1, "deleted fname=%s lstat=%s\n", elt->fname, elt->lstat);
527 +        encode_and_send_deleted_file(jcr, elt->fname);
528 +      }
529 +//      free(elt->fname);
530 +   }
531 +   jcr->file_list->destroy();  /* TODO: clean htable when this function is not reached ? */
532 +   free(jcr->file_list);
533 +   jcr->file_list = NULL;
534 +   return true;
535 +}
536 +
537 +/*
538   * Find all the requested files and send them
539   * to the Storage daemon.
540   *
541 @@ -66,7 +282,6 @@
542     BSOCK *sd;
543     bool ok = true;
544     // TODO landonf: Allow user to specify encryption algorithm
545 -
546     sd = jcr->store_bsock;
547  
548     set_jcr_job_status(jcr, JS_Running);
549 @@ -100,7 +315,7 @@
550      */
551     jcr->compress_buf_size = jcr->buf_size + ((jcr->buf_size+999) / 1000) + 30;
552     jcr->compress_buf = get_memory(jcr->compress_buf_size);
553 -
554 +   
555  #ifdef HAVE_LIBZ
556     z_stream *pZlibStream = (z_stream*)malloc(sizeof(z_stream));  
557     if (pZlibStream) {
558 @@ -134,7 +349,10 @@
559        ok = false;                     /* error */
560        set_jcr_job_status(jcr, JS_ErrorTerminated);
561     }
562 +   Dmsg1(1, "jcr->accurate == %i\n", jcr->accurate);
563  
564 +   accurate_send_deleted_list(jcr);              /* send deleted list to SD  */
565 +
566     free_pool_memory(jcr->acl_text);
567  
568     stop_heartbeat_monitor(jcr);
569 @@ -354,9 +572,21 @@
570     }
571     case FT_DIRNOCHG:
572     case FT_NOCHG:
573 +      /* TODO: in accurate mode, we have to change NOCHG attribute to FT_REG... */
574 +//      if (!accurate_check_file(jcr, ff_pkt, false)) {
575 +//      Jmsg(jcr, M_SKIPPED, 1, _("     Unchanged file skipped: %s\n"), ff_pkt->fname);
576 +//      return 1;
577 +//      }
578 +      accurate_check_file(jcr, ff_pkt, false);
579        Jmsg(jcr, M_SKIPPED, 1, _("     Unchanged file skipped: %s\n"), ff_pkt->fname);
580        return 1;
581     case FT_ISARCH:
582 +      /* TODO: in accurate mode, we have to change NOCHG attribute to FT_REG... */
583 +//      if (!accurate_check_file(jcr, ff_pkt, false)) { 
584 +//      Jmsg(jcr, M_NOTSAVED, 0, _("     Archive file not saved: %s\n"), ff_pkt->fname);
585 +//      return 1;
586 +//      }
587 +      accurate_check_file(jcr, ff_pkt, false);
588        Jmsg(jcr, M_NOTSAVED, 0, _("     Archive file not saved: %s\n"), ff_pkt->fname);
589        return 1;
590     case FT_NOOPEN: {
591 @@ -1118,6 +1348,9 @@
592     }
593     unstrip_path(ff_pkt);
594  
595 +   /* list backuped files */
596 +   accurate_check_file(jcr, ff_pkt, true);
597 +
598     Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg);
599     if (!stat) {
600        Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
601 @@ -1128,6 +1361,58 @@
602     return true;
603  }
604  
605 +static bool encode_and_send_deleted_file(JCR *jcr, char *fname) 
606 +{
607 +   BSOCK *sd = jcr->store_bsock;
608 +   char *attribs;
609 +   char *attribsEx;
610 +   int stat;
611 +#ifdef FD_NO_SEND_TEST
612 +   return true;
613 +#endif
614 +
615 +   attribs = " ";
616 +   attribsEx = " ";
617 +
618 +   /*
619 +    * Send Attributes header to Storage daemon
620 +    *    <file-index> <stream> <info>
621 +    */
622 +   if (!sd->fsend("%ld %d 0", 0, STREAM_UNIX_ATTRIBUTES)) {
623 +      Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
624 +            sd->bstrerror());
625 +      return false;
626 +   }
627 +   Dmsg1(300, ">stored: attrhdr %s\n", sd->msg);
628 +
629 +   /*
630 +    * Send file attributes to Storage daemon
631 +    *   File_index
632 +    *   File type
633 +    *   Filename (full path)
634 +    *   Encoded attributes
635 +    *   Link name (if type==FT_LNK or FT_LNKSAVED)
636 +    *   Encoded extended-attributes (for Win32)
637 +    *
638 +    * For a directory, link is the same as fname, but with trailing
639 +    * slash. For a linked file, link is the link.
640 +    */
641 +   stat = sd->fsend("%ld %d %s%c%s%c%s%c%s%c", 
642 +                   0 /* FileIndex */,
643 +                   FT_NOSTAT /* FileType */,
644 +                   fname /* FileName */, 
645 +                   0, attribs, 0, 0, 0, attribsEx, 0);
646 +
647 +   Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg);
648 +   if (!stat) {
649 +      Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
650 +            sd->bstrerror());
651 +      return false;
652 +   }
653 +   sd->signal(BNET_EOD);            /* indicate end of attributes data */
654 +   return true;
655 +}
656 +
657  /* 
658   * Do in place strip of path
659   */
660 Index: src/filed/job.c
661 ===================================================================
662 --- src/filed/job.c     (revision 6450)
663 +++ src/filed/job.c     (working copy)
664 @@ -49,6 +49,7 @@
665  /* Imported functions */
666  extern int status_cmd(JCR *jcr);
667  extern int qstatus_cmd(JCR *jcr);
668 +extern int accurate_get_current_file_list_cmd(JCR *jcr);
669  
670  /* Forward referenced functions */
671  static int backup_cmd(JCR *jcr);
672 @@ -106,6 +107,7 @@
673     {"RunBeforeJob", runbefore_cmd, 0},
674     {"RunAfterJob",  runafter_cmd,  0},
675     {"Run",          runscript_cmd, 0},
676 +   {"accurate",     accurate_get_current_file_list_cmd, 0},
677     {NULL,       NULL}                  /* list terminator */
678  };
679  
680 @@ -1087,6 +1089,9 @@
681        case 'c':
682           fo->flags |= FO_CHKCHANGES;
683           break;
684 +      case 'C':
685 +         fo->flags |= FO_ACCURATE;
686 +         break;
687        default:
688           Emsg1(M_ERROR, 0, _("Unknown include/exclude option: %c\n"), *p);
689           break;
690 @@ -1195,6 +1200,9 @@
691  
692     level = get_memory(dir->msglen+1);
693     Dmsg1(110, "level_cmd: %s", dir->msg);
694 +   if (strstr(dir->msg, "accurate")) {
695 +      jcr->accurate = true;
696 +   }
697     if (sscanf(dir->msg, "level = %s ", level) != 1) {
698        goto bail_out;
699     }
700 @@ -1204,14 +1212,14 @@
701     /* Full backup requested? */
702     } else if (strcmp(level, "full") == 0) {
703        jcr->JobLevel = L_FULL;
704 -   } else if (strcmp(level, "differential") == 0) {
705 +   } else if (strstr(level, "differential")) {
706        jcr->JobLevel = L_DIFFERENTIAL;
707        free_memory(level);
708        return 1;
709 -   } else if (strcmp(level, "incremental") == 0) {
710 +   } else if (strstr(level, "incremental")) {
711        jcr->JobLevel = L_INCREMENTAL;
712        free_memory(level);
713 -      return 1;   
714 +      return 1;
715     /*
716      * We get his UTC since time, then sync the clocks and correct it
717      *   to agree with our clock.
718 Index: src/filed/restore.c
719 ===================================================================
720 --- src/filed/restore.c (revision 6450)
721 +++ src/filed/restore.c (working copy)
722 @@ -320,6 +320,11 @@
723              bclose(&rctx.bfd);
724           }
725  
726 +        /* TODO: manage deleted files */
727 +        if (file_index == 0) { /* deleted file */
728 +           continue;
729 +        }
730 +
731           /*
732            * Unpack attributes and do sanity check them
733            */
734 Index: src/stored/bextract.c
735 ===================================================================
736 --- src/stored/bextract.c       (revision 6450)
737 +++ src/stored/bextract.c       (working copy)
738 @@ -324,6 +324,14 @@
739           Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
740        }
741  
742 +      /* handle deleted file 
743 +       */
744 +      if (rec->FileIndex == 0) {
745 +        /* if file is included, remove it ? */
746 +        Jmsg(jcr, M_INFO, 0, _("fname=%s is marked as deleted.\n"), attr->fname);
747 +        break;
748 +      }
749 +
750        if (attr->file_index != rec->FileIndex) {
751           Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
752              rec->FileIndex, attr->file_index);
753 Index: src/stored/bscan.c
754 ===================================================================
755 --- src/stored/bscan.c  (revision 6450)
756 +++ src/stored/bscan.c  (working copy)
757 @@ -648,6 +648,15 @@
758     case STREAM_UNIX_ATTRIBUTES:
759     case STREAM_UNIX_ATTRIBUTES_EX:
760  
761 +      /* handle deleted file 
762 +       */
763 +      if (rec->FileIndex == 0) {
764 +        create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
765 +                                      FT_NOSTAT, "", rec);
766 +        free_jcr(mjcr);
767 +        break;
768 +      }
769 +
770        if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
771           Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
772        }
773 Index: src/stored/append.c
774 ===================================================================
775 --- src/stored/append.c (revision 6450)
776 +++ src/stored/append.c (working copy)
777 @@ -146,7 +146,7 @@
778  
779        /* Read Stream header from the File daemon.
780         *  The stream header consists of the following:
781 -       *    file_index (sequential Bacula file index, base 1)
782 +       *    file_index (sequential Bacula file index, base 1, 0 for deleted files)
783         *    stream     (Bacula number to distinguish parts of data)
784         *    info       (Info for Storage daemon -- compressed, encryped, ...)
785         *       info is not currently used, so is read, but ignored!
786 @@ -185,16 +185,18 @@
787  
788        Dmsg2(890, "<filed: Header FilInx=%d stream=%d\n", file_index, stream);
789  
790 -      if (!(file_index > 0 && (file_index == last_file_index ||
791 -          file_index == last_file_index + 1))) {
792 -         Jmsg0(jcr, M_FATAL, 0, _("File index from FD not positive or sequential\n"));
793 -         ok = false;
794 -         break;
795 +      if (file_index != 0) {   /* TODO: handle file_index == 0 */
796 +        if (!(file_index > 0 && (file_index == last_file_index ||
797 +                                 file_index == last_file_index + 1))) {
798 +           Jmsg0(jcr, M_FATAL, 0, _("File index from FD not positive or sequential\n"));
799 +           ok = false;
800 +           break;
801 +        }
802 +        if (file_index != last_file_index) {
803 +           jcr->JobFiles = file_index;
804 +           last_file_index = file_index;
805 +        }
806        }
807 -      if (file_index != last_file_index) {
808 -         jcr->JobFiles = file_index;
809 -         last_file_index = file_index;
810 -      }
811  
812        /* Read data stream from the File daemon.
813         *  The data stream is just raw bytes
814 @@ -212,25 +214,26 @@
815              stream_to_ascii(buf1, rec.Stream,rec.FileIndex),
816              rec.data_len);
817  
818 -         while (!write_record_to_block(dcr->block, &rec)) {
819 -            Dmsg2(850, "!write_record_to_block data_len=%d rem=%d\n", rec.data_len,
820 -                       rec.remainder);
821 -            if (!write_block_to_device(dcr)) {
822 -               Dmsg2(90, "Got write_block_to_dev error on device %s. %s\n",
823 -                  dev->print_name(), dev->bstrerror());
824 -               ok = false;
825 -               break;
826 -            }
827 -         }
828 -         if (!ok) {
829 -            Dmsg0(400, "Not OK\n");
830 -            break;
831 -         }
832 -         jcr->JobBytes += rec.data_len;   /* increment bytes this job */
833 -         Dmsg4(850, "write_record FI=%s SessId=%d Strm=%s len=%d\n",
834 -            FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId,
835 -            stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len);
836 +        while (!write_record_to_block(dcr->block, &rec)) {
837 +           Dmsg2(850, "!write_record_to_block data_len=%d rem=%d\n", rec.data_len,
838 +                 rec.remainder);
839 +           if (!write_block_to_device(dcr)) {
840 +              Dmsg2(90, "Got write_block_to_dev error on device %s. %s\n",
841 +                    dev->print_name(), dev->bstrerror());
842 +              ok = false;
843 +              break;
844 +           }
845  
846 +           if (!ok) {
847 +              Dmsg0(400, "Not OK\n");
848 +              break;
849 +           }
850 +           jcr->JobBytes += rec.data_len;   /* increment bytes this job */
851 +           Dmsg4(850, "write_record FI=%s SessId=%d Strm=%s len=%d\n",
852 +                 FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId,
853 +                 stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len);
854 +        }
855 +
856           /* Send attributes and digest to Director for Catalog */
857           if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX ||
858               crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
859 Index: src/jcr.h
860 ===================================================================
861 --- src/jcr.h   (revision 6450)
862 +++ src/jcr.h   (working copy)
863 @@ -119,6 +119,7 @@
864  
865  /* Forward referenced structures */
866  class JCR;
867 +class htable;
868  struct FF_PKT;
869  struct B_DB;
870  struct ATTR_DBR;
871 @@ -318,6 +319,7 @@
872     CRYPTO_CTX crypto;                 /* Crypto ctx */
873     DIRRES* director;                  /* Director resource */
874     bool VSS;                          /* VSS used by FD */
875 +   htable *file_list;                 /* Previous file list (accurate mode) */
876  #endif /* FILE_DAEMON */
877  
878  
879 Index: src/lib/Makefile.in
880 ===================================================================
881 --- src/lib/Makefile.in (revision 6450)
882 +++ src/lib/Makefile.in (working copy)
883 @@ -29,7 +29,7 @@
884           res.c rwlock.c scan.c serial.c sha1.c \
885           signal.c smartall.c rblist.c tls.c tree.c \
886           util.c var.c watchdog.c workq.c btimers.c \
887 -         address_conf.c pythonlib.c breg.c
888 +         address_conf.c pythonlib.c breg.c htable.c
889  
890  
891  LIBOBJS = attr.o base64.o berrno.o bsys.o bget_msg.o \
892 @@ -42,7 +42,7 @@
893           res.o rwlock.o scan.o serial.o sha1.o \
894           signal.o smartall.o rblist.o tls.o tree.o \
895           util.o var.o watchdog.o workq.o btimers.o \
896 -         address_conf.o pythonlib.o breg.o
897 +         address_conf.o pythonlib.o breg.o htable.o
898  
899  
900  EXTRAOBJS = @OBJLIST@
901 Index: src/lib/htable.c
902 ===================================================================
903 --- src/lib/htable.c    (revision 6450)
904 +++ src/lib/htable.c    (working copy)
905 @@ -193,7 +193,6 @@
906     if (lookup(key)) {
907        return false;                   /* already exists */
908     }
909 -   sm_check(__FILE__, __LINE__, false);
910     ASSERT(index < buckets);
911     Dmsg2(100, "Insert: hash=0x%x index=%d\n", (unsigned)hash, index);
912     hp = (hlink *)(((char *)item)+loffset);
913 @@ -210,7 +209,6 @@
914        Dmsg2(100, "num_items=%d max_items=%d\n", num_items, max_items);
915        grow_table();
916     }
917 -   sm_check(__FILE__, __LINE__, false);
918     Dmsg3(100, "Leave insert index=%d num_items=%d key=%s\n", index, num_items, key);
919     return true;
920  }
921 @@ -275,12 +273,13 @@
922  {
923     void *ni;
924     void *li = first();
925 -   do {
926 -      ni = next();
927 -      free(li);
928 -      li = ni;
929 -   } while (ni);
930 -
931 +   if (li) {
932 +      do {
933 +        ni = next();
934 +        free(li);
935 +        li = ni;
936 +      } while (ni);
937 +   }
938     free(table);
939     table = NULL;
940     Dmsg0(100, "Done destroy.\n");
941 @@ -295,7 +294,7 @@
942     hlink link;
943  };
944  
945 -#define NITEMS 10000
946 +#define NITEMS 1000000
947  
948  int main()
949  {
950 @@ -311,7 +310,7 @@
951     Dmsg1(000, "Inserting %d items\n", NITEMS);
952     for (int i=0; i<NITEMS; i++) {
953        sprintf(mkey, "This is htable item %d", i);
954 -      jcr = (MYJCR *)malloc(sizeof(MYJCR));
955 +      jcr = (MYJCR *)malloc(sizeof(MYJCR)+strlen(mkey)+1);
956        Dmsg2(100, "link=0x%x jcr=0x%x\n", (unsigned)&jcr->link, (unsigned)jcr);
957        jcr->key = bstrdup(mkey);
958  
959 Index: src/findlib/find.h
960 ===================================================================
961 --- src/findlib/find.h  (revision 6450)
962 +++ src/findlib/find.h  (working copy)
963 @@ -108,6 +108,7 @@
964  #define FO_ENHANCEDWILD (1<<23)       /* Enhanced wild card processing */
965  #define FO_CHKCHANGES   (1<<24)       /* Check if file have been modified during backup */
966  #define FO_STRIPPATH    (1<<25)       /* Check for stripping path */
967 +#define FO_ACCURATE     (1<<26)       /* Accurate mode */
968  
969  struct s_included_file {
970     struct s_included_file *next;