]> git.sur5r.net Git - bacula/bacula/blob - bacula/patches/testing/project-accurate-backup.patch2
ebl cleanup
[bacula/bacula] / bacula / patches / testing / project-accurate-backup.patch2
1 Index: src/dird/fd_cmds.c
2 ===================================================================
3 --- src/dird/fd_cmds.c  (révision 6467)
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 @@ -226,13 +226,12 @@
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  }
24  
25 -
26  /*
27   * Send level command to FD.
28   * Used for backup jobs and estimate command.
29 @@ -240,24 +239,26 @@
30  bool send_level_command(JCR *jcr)
31  {
32     BSOCK   *fd = jcr->file_bsock;
33 +   const char *accurate=jcr->job->accurate?"accurate_":"";
34 +   const char *not_accurate="";
35     /*
36      * Send Level command to File daemon
37      */
38     switch (jcr->JobLevel) {
39     case L_BASE:
40 -      fd->fsend(levelcmd, "base", " ", 0);
41 +      fd->fsend(levelcmd, not_accurate, "base", " ", 0);
42        break;
43     /* L_NONE is the console, sending something off to the FD */
44     case L_NONE:
45     case L_FULL:
46 -      fd->fsend(levelcmd, "full", " ", 0);
47 +      fd->fsend(levelcmd, not_accurate, "full", " ", 0);
48        break;
49     case L_DIFFERENTIAL:
50 -      fd->fsend(levelcmd, "differential", " ", 0);
51 +      fd->fsend(levelcmd, accurate, "differential", " ", 0);
52        send_since_time(jcr);
53        break;
54     case L_INCREMENTAL:
55 -      fd->fsend(levelcmd, "incremental", " ", 0);
56 +      fd->fsend(levelcmd, accurate, "incremental", " ", 0);
57        send_since_time(jcr);
58        break;
59     case L_SINCE:
60 Index: src/dird/backup.c
61 ===================================================================
62 --- src/dird/backup.c   (révision 6467)
63 +++ src/dird/backup.c   (copie de travail)
64 @@ -97,6 +97,65 @@
65  }
66  
67  /*
68 + * Foreach files in currrent list, send "/path/fname\0LStat" to FD
69 + */
70 +static int accurate_list_handler(void *ctx, int num_fields, char **row)
71 +{
72 +   JCR *jcr = (JCR *)ctx;
73 +
74 +   if (job_canceled(jcr)) {
75 +      return 1;
76 +   }
77 +   
78 +   if (row[2] > 0) {            /* discard when file_index == 0 */
79 +      jcr->file_bsock->fsend("%s%s%c%s", row[0], row[1], 0, row[4]); 
80 +   }
81 +   return 0;
82 +}
83 +
84 +/*
85 + * Send current file list to FD
86 + *    DIR -> FD : accurate files=xxxx
87 + *    DIR -> FD : /path/to/file\0Lstat
88 + *    DIR -> FD : /path/to/dir/\0Lstat
89 + *    ...
90 + *    DIR -> FD : EOD
91 + */
92 +bool send_accurate_current_files(JCR *jcr)
93 +{
94 +   POOL_MEM buf;
95 +
96 +   if (jcr->accurate==false || job_canceled(jcr) || jcr->JobLevel==L_FULL) {
97 +      return true;
98 +   }
99 +   POOLMEM *jobids = get_pool_memory(PM_FNAME);
100 +   db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, jobids);
101 +
102 +   if (*jobids == 0) {
103 +      free_pool_memory(jobids);
104 +      Jmsg(jcr, M_FATAL, 0, _("Cannot find previous jobids.\n"));
105 +      return false;
106 +   }
107 +   Jmsg(jcr, M_INFO, 0, _("Sending Accurate informations.\n"));
108 +
109 +   /* to be able to allocate the right size for htable */
110 +   POOLMEM *nb = get_pool_memory(PM_FNAME);
111 +   Mmsg(buf, "SELECT sum(JobFiles) FROM Job WHERE JobId IN (%s)",jobids);
112 +   db_sql_query(jcr->db, buf.c_str(), db_get_int_handler, nb);
113 +   jcr->file_bsock->fsend("accurate files=%s\n", nb); 
114 +
115 +   db_get_file_list(jcr, jcr->db, jobids, accurate_list_handler, (void *)jcr);
116 +
117 +   free_pool_memory(jobids);
118 +   free_pool_memory(nb);
119 +
120 +   jcr->file_bsock->signal(BNET_EOD);
121 +   /* TODO: use response() ? */
122 +
123 +   return true;
124 +}
125 +
126 +/*
127   * Do a backup of the specified FileSet
128   *
129   *  Returns:  false on failure
130 @@ -225,6 +284,14 @@
131        Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
132     }
133  
134 +   /*
135 +    * If backup is in accurate mode, we send the list of
136 +    * all files to FD.
137 +    */
138 +   if (!send_accurate_current_files(jcr)) {
139 +      goto bail_out;
140 +   }
141 +
142     /* Send backup command */
143     fd->fsend(backupcmd);
144     if (!response(jcr, fd, OKbackup, "backup", DISPLAY_ERROR)) {
145 @@ -475,6 +542,7 @@
146  "  Software Compression:   %s\n"
147  "  VSS:                    %s\n"
148  "  Encryption:             %s\n"
149 +"  Accurate:               %s\n"
150  "  Volume name(s):         %s\n"
151  "  Volume Session Id:      %d\n"
152  "  Volume Session Time:    %d\n"
153 @@ -506,8 +574,9 @@
154          edit_uint64_with_suffix(jcr->SDJobBytes, ec6),
155          kbps,
156          compress,
157 -        jcr->VSS?"yes":"no",
158 -        jcr->Encrypt?"yes":"no",
159 +        jcr->VSS?_("yes"):_("no"),
160 +        jcr->Encrypt?_("yes"):_("no"),
161 +        jcr->accurate?_("yes"):_("no"),
162          jcr->VolumeName,
163          jcr->VolSessionId,
164          jcr->VolSessionTime,
165 Index: src/dird/ua_restore.c
166 ===================================================================
167 --- src/dird/ua_restore.c       (révision 6467)
168 +++ src/dird/ua_restore.c       (copie de travail)
169 @@ -1005,7 +1005,6 @@
170      * For display purposes, the same JobId, with different volumes may
171      * appear more than once, however, we only insert it once.
172      */
173 -   int items = 0;
174     p = rx->JobIds;
175     tree.FileEstimate = 0;
176     if (get_next_jobid_from_list(&p, &JobId) > 0) {
177 @@ -1020,23 +1019,12 @@
178           tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
179        }
180     }
181 -   for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
182 -      char ed1[50];
183  
184 -      if (JobId == last_JobId) {
185 -         continue;                    /* eliminate duplicate JobIds */
186 -      }
187 -      last_JobId = JobId;
188 -      ua->info_msg(_("\nBuilding directory tree for JobId %s ...  "), 
189 -         edit_int64(JobId, ed1));
190 -      items++;
191 -      /*
192 -       * Find files for this JobId and insert them in the tree
193 -       */
194 -      Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
195 -      if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
196 -         ua->error_msg("%s", db_strerror(ua->db));
197 -      }
198 +   ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ...  "),
199 +                rx->JobIds);
200 +
201 +   if (!db_get_file_list(ua->jcr, ua->db, rx->JobIds, insert_tree_handler, (void *)&tree)) {
202 +      ua->error_msg("%s", db_strerror(ua->db));
203     }
204     if (tree.FileCount == 0) {
205        ua->send_msg(_("\nThere were no files inserted into the tree, so file selection\n"
206 @@ -1055,26 +1043,13 @@
207        }
208     } else {
209        char ec1[50];
210 -      if (items==1) {
211 -         if (tree.all) {
212 -            ua->info_msg(_("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
213 -              edit_uint64_with_commas(tree.FileCount, ec1));
214 -         }
215 -         else {
216 -            ua->info_msg(_("\n1 Job, %s files inserted into the tree.\n"),
217 -              edit_uint64_with_commas(tree.FileCount, ec1));
218 -         }
219 +      if (tree.all) {
220 +         ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
221 +                      edit_uint64_with_commas(tree.FileCount, ec1));
222 +      } else {
223 +         ua->info_msg(_("\n%s files inserted into the tree.\n"),
224 +                      edit_uint64_with_commas(tree.FileCount, ec1));
225        }
226 -      else {
227 -         if (tree.all) {
228 -            ua->info_msg(_("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
229 -              items, edit_uint64_with_commas(tree.FileCount, ec1));
230 -         }
231 -         else {
232 -            ua->info_msg(_("\n%d Jobs, %s files inserted into the tree.\n"),
233 -              items, edit_uint64_with_commas(tree.FileCount, ec1));
234 -         }
235 -      }
236  
237        if (find_arg(ua, NT_("done")) < 0) {
238           /* Let the user interact in selecting which files to restore */
239 Index: src/filed/backup.c
240 ===================================================================
241 --- src/filed/backup.c  (révision 6467)
242 +++ src/filed/backup.c  (copie de travail)
243 @@ -37,6 +37,7 @@
244  
245  #include "bacula.h"
246  #include "filed.h"
247 +#include "lib/htable.h"
248  
249  /* Forward referenced functions */
250  int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level);
251 @@ -48,8 +49,224 @@
252  static bool crypto_session_start(JCR *jcr);
253  static void crypto_session_end(JCR *jcr);
254  static bool crypto_session_send(JCR *jcr, BSOCK *sd);
255 +static bool encode_and_send_deleted_file(JCR *jcr, char *fname);
256  
257 +typedef struct CurFile {
258 +   char *fname;
259 +   char *lstat;
260 +   hlink link;
261 +} CurFile;
262 +
263  /*
264 + * This function is called for each file seen in fileset.
265 + * We check in file_list hash if fname have been backuped
266 + * the last time. After we can compare Lstat field. When
267 + * a file have been seen, we put '\0' in LStat field.
268 + * 
269 + */
270 +/* TODO: tweak verify code to use the same function ?? */
271 +bool accurate_check_file(JCR *jcr, FF_PKT *ff_pkt)
272 +{
273 +   char *p;
274 +   int stat=false;
275 +   struct stat statc;                 /* catalog stat */
276 +   char *Opts_Digest = ff_pkt->VerifyOpts;
277 +   char *fname = ff_pkt->fname;
278 +   CurFile *elt;
279 +
280 +   int32_t LinkFIc;
281 +
282 +   if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
283 +      return true;
284 +   }
285 +
286 +   if (S_ISDIR(ff_pkt->statp.st_mode)) {
287 +      fname = ff_pkt->link;
288 +   } 
289 +
290 +   elt = (CurFile *) jcr->file_list->lookup(fname);
291 +
292 +   if (!elt) {
293 +      Dmsg1(500, "accurate %s = yes (not found)\n", fname);
294 +      return true;
295 +   }
296 +
297 +   if (*elt->lstat == '\0') {
298 +      Dmsg1(500, "accurate %s = no (already seen)\n", fname);
299 +      return false;
300 +   }
301 +
302 +   decode_stat(elt->lstat, &statc, &LinkFIc); /* decode catalog stat */
303 +//   *do_Digest = CRYPTO_DIGEST_NONE;
304 +
305 +   for (p=Opts_Digest; *p; p++) {
306 +      char ed1[30], ed2[30];
307 +      switch (*p) {
308 +      case 'i':                /* compare INODEs */
309 +         if (statc.st_ino != ff_pkt->statp.st_ino) {
310 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_ino   differ. Cat: %s File: %s\n"), fname,
311 +                 edit_uint64((uint64_t)statc.st_ino, ed1),
312 +                 edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
313 +            stat = true;
314 +         }
315 +         break;
316 +      case 'p':                /* permissions bits */
317 +         if (statc.st_mode != ff_pkt->statp.st_mode) {
318 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_mode  differ. Cat: %x File: %x\n"), fname,
319 +                 (uint32_t)statc.st_mode, (uint32_t)ff_pkt->statp.st_mode);
320 +            stat = true;
321 +         }
322 +         break;
323 +//      case 'n':                /* number of links */
324 +//         if (statc.st_nlink != ff_pkt->statp.st_nlink) {
325 +//            Jmsg(jcr, M_SAVED, 0, _("%s      st_nlink differ. Cat: %d File: %d\n"), fname,
326 +//                 (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
327 +//            stat = true;
328 +//         }
329 +//         break;
330 +      case 'u':                /* user id */
331 +         if (statc.st_uid != ff_pkt->statp.st_uid) {
332 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_uid   differ. Cat: %u File: %u\n"), fname,
333 +                 (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
334 +            stat = true;
335 +         }
336 +         break;
337 +      case 'g':                /* group id */
338 +         if (statc.st_gid != ff_pkt->statp.st_gid) {
339 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_gid   differ. Cat: %u File: %u\n"), fname,
340 +                 (uint32_t)statc.st_gid, (uint32_t)ff_pkt->statp.st_gid);
341 +            stat = true;
342 +         }
343 +         break;
344 +      case 's':                /* size */
345 +         if (statc.st_size != ff_pkt->statp.st_size) {
346 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_size  differ. Cat: %s File: %s\n"), fname,
347 +                 edit_uint64((uint64_t)statc.st_size, ed1),
348 +                 edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
349 +            stat = true;
350 +         }
351 +         break;
352 +//      case 'a':                /* access time */
353 +//         if (statc.st_atime != ff_pkt->statp.st_atime) {
354 +//            Jmsg(jcr, M_SAVED, 0, _("%s      st_atime differs\n"), fname);
355 +//            stat = true;
356 +//         }
357 +//         break;
358 +      case 'm':
359 +         if (statc.st_mtime != ff_pkt->statp.st_mtime) {
360 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_mtime differs\n"), fname);
361 +            stat = true;
362 +         }
363 +         break;
364 +      case 'c':                /* ctime */
365 +         if (statc.st_ctime != ff_pkt->statp.st_ctime) {
366 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_ctime differs\n"), fname);
367 +            stat = true;
368 +         }
369 +         break;
370 +      case 'd':                /* file size decrease */
371 +         if (statc.st_size > ff_pkt->statp.st_size) {
372 +            Jmsg(jcr, M_SAVED, 0, _("%s      st_size  decrease. Cat: %s File: %s\n"), fname,
373 +                 edit_uint64((uint64_t)statc.st_size, ed1),
374 +                 edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
375 +            stat = true;
376 +         }
377 +         break;
378 +      case '5':                /* compare MD5 */
379 +         Dmsg1(500, "set Do_MD5 for %s\n", ff_pkt->fname);
380 +//       *do_Digest = CRYPTO_DIGEST_MD5;
381 +         break;
382 +      case '1':                 /* compare SHA1 */
383 +//       *do_Digest = CRYPTO_DIGEST_SHA1;
384 +         break;
385 +      case ':':
386 +      case 'V':
387 +      default:
388 +         break;
389 +      }
390 +   }
391 +   *elt->lstat = '\0';          /* mark it as seen */
392 +   Dmsg2(500, "accurate %s = %i\n", fname, stat);
393 +   return stat;
394 +}
395 +
396 +/* 
397 + * This function doesn't work very well with smartalloc
398 + * TODO: use bigbuffer from htable
399 + */
400 +int accurate_cmd(JCR *jcr)
401 +{
402 +   BSOCK *dir = jcr->dir_bsock;
403 +   int len;
404 +   uint64_t nb;
405 +   CurFile *elt=NULL;
406 +
407 +   if (jcr->accurate==false || job_canceled(jcr) || jcr->JobLevel==L_FULL) {
408 +      return true;
409 +   }
410 +
411 +   if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
412 +      dir->fsend(_("2991 Bad accurate command\n"));
413 +      return false;
414 +   }
415 +
416 +   jcr->file_list = (htable *)malloc(sizeof(htable));
417 +   jcr->file_list->init(elt, &elt->link, nb);
418 +
419 +   /*
420 +    * buffer = sizeof(CurFile) + dirmsg
421 +    * dirmsg = fname + lstat
422 +    */
423 +   /* get current files */
424 +   while (dir->recv() >= 0) {
425 +      len = strlen(dir->msg);
426 +      if ((len+1) < dir->msglen) {
427 +//       elt = (CurFile *)malloc(sizeof(CurFile));
428 +//       elt->fname  = (char *) malloc(dir->msglen+1);
429 +
430 +         /* we store CurFile, fname and lstat in the same chunk */
431 +         elt = (CurFile *)malloc(sizeof(CurFile)+dir->msglen+1);
432 +         elt->fname  = (char *) elt+sizeof(CurFile);
433 +         memcpy(elt->fname, dir->msg, dir->msglen);
434 +         elt->fname[dir->msglen]='\0';
435 +         elt->lstat = elt->fname + len + 1;
436 +         jcr->file_list->insert(elt->fname, elt); 
437 +         Dmsg2(500, "add fname=%s lstat=%s\n", elt->fname, elt->lstat);
438 +      }
439 +   }
440 +
441 +//   jcr->file_list->stats();
442 +   /* TODO: send a EOM ?
443 +   dir->fsend("2000 OK accurate\n");
444 +    */
445 +   return true;
446 +}
447 +
448 +bool accurate_send_deleted_list(JCR *jcr)
449 +{
450 +   if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
451 +      return true;
452 +   }
453 +
454 +   if (jcr->file_list == NULL) {
455 +      return true;
456 +   }
457 +
458 +   CurFile *elt;
459 +   foreach_htable (elt, jcr->file_list) {
460 +      if (*elt->lstat != '\0') { /* already seen */
461 +         Dmsg2(500, "deleted fname=%s lstat=%s\n", elt->fname, elt->lstat);
462 +         encode_and_send_deleted_file(jcr, elt->fname);
463 +      }
464 +//      free(elt->fname);
465 +   }
466 +   jcr->file_list->destroy(); /* TODO: clean htable when this function is not reached ? */
467 +   free(jcr->file_list);
468 +   jcr->file_list = NULL;
469 +   return true;
470 +}
471 +
472 +/*
473   * Find all the requested files and send them
474   * to the Storage daemon.
475   *
476 @@ -100,7 +317,7 @@
477      */
478     jcr->compress_buf_size = jcr->buf_size + ((jcr->buf_size+999) / 1000) + 30;
479     jcr->compress_buf = get_memory(jcr->compress_buf_size);
480 -
481 +   
482  #ifdef HAVE_LIBZ
483     z_stream *pZlibStream = (z_stream*)malloc(sizeof(z_stream));  
484     if (pZlibStream) {
485 @@ -121,10 +338,13 @@
486        return false;
487     }
488  
489 -   Dmsg1(300, "set_find_options ff=%p\n", jcr->ff);
490     set_find_options((FF_PKT *)jcr->ff, jcr->incremental, jcr->mtime);
491 -   Dmsg0(300, "start find files\n");
492  
493 +   /* in accurate mode, we overwrite the find_one check function */
494 +   if (jcr->accurate) {
495 +      set_find_changed_function((FF_PKT *)jcr->ff, accurate_check_file);
496 +   } 
497 +   
498     start_heartbeat_monitor(jcr);
499  
500     jcr->acl_text = get_pool_memory(PM_MESSAGE);
501 @@ -135,6 +355,8 @@
502        set_jcr_job_status(jcr, JS_ErrorTerminated);
503     }
504  
505 +   accurate_send_deleted_list(jcr);              /* send deleted list to SD  */
506 +
507     free_pool_memory(jcr->acl_text);
508  
509     stop_heartbeat_monitor(jcr);
510 @@ -1128,6 +1350,58 @@
511     return true;
512  }
513  
514 +static bool encode_and_send_deleted_file(JCR *jcr, char *fname) 
515 +{
516 +   BSOCK *sd = jcr->store_bsock;
517 +   char *attribs;
518 +   char *attribsEx;
519 +   int stat;
520 +#ifdef FD_NO_SEND_TEST
521 +   return true;
522 +#endif
523 +
524 +   attribs = " ";
525 +   attribsEx = " ";
526 +
527 +   /*
528 +    * Send Attributes header to Storage daemon
529 +    *    <file-index> <stream> <info>
530 +    */
531 +   if (!sd->fsend("%ld %d 0", 0, STREAM_UNIX_ATTRIBUTES)) {
532 +      Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
533 +            sd->bstrerror());
534 +      return false;
535 +   }
536 +   Dmsg1(300, ">stored: attrhdr %s\n", sd->msg);
537 +
538 +   /*
539 +    * Send file attributes to Storage daemon
540 +    *   File_index
541 +    *   File type
542 +    *   Filename (full path)
543 +    *   Encoded attributes
544 +    *   Link name (if type==FT_LNK or FT_LNKSAVED)
545 +    *   Encoded extended-attributes (for Win32)
546 +    *
547 +    * For a directory, link is the same as fname, but with trailing
548 +    * slash. For a linked file, link is the link.
549 +    */
550 +   stat = sd->fsend("%ld %d %s%c%s%c%s%c%s%c", 
551 +                    0 /* FileIndex */,
552 +                    FT_NOSTAT /* FileType */,
553 +                    fname /* FileName */, 
554 +                    0, attribs, 0, 0, 0, attribsEx, 0);
555 +
556 +   Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg);
557 +   if (!stat) {
558 +      Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
559 +            sd->bstrerror());
560 +      return false;
561 +   }
562 +   sd->signal(BNET_EOD);            /* indicate end of attributes data */
563 +   return true;
564 +}
565 +
566  /* 
567   * Do in place strip of path
568   */
569 Index: src/filed/job.c
570 ===================================================================
571 --- src/filed/job.c     (révision 6467)
572 +++ src/filed/job.c     (copie de travail)
573 @@ -49,6 +49,7 @@
574  /* Imported functions */
575  extern int status_cmd(JCR *jcr);
576  extern int qstatus_cmd(JCR *jcr);
577 +extern int accurate_cmd(JCR *jcr);
578  
579  /* Forward referenced functions */
580  static int backup_cmd(JCR *jcr);
581 @@ -106,6 +107,7 @@
582     {"RunBeforeJob", runbefore_cmd, 0},
583     {"RunAfterJob",  runafter_cmd,  0},
584     {"Run",          runscript_cmd, 0},
585 +   {"accurate",     accurate_cmd, 0},
586     {NULL,       NULL}                  /* list terminator */
587  };
588  
589 @@ -1195,6 +1197,9 @@
590  
591     level = get_memory(dir->msglen+1);
592     Dmsg1(110, "level_cmd: %s", dir->msg);
593 +   if (strstr(dir->msg, "accurate")) {
594 +      jcr->accurate = true;
595 +   }
596     if (sscanf(dir->msg, "level = %s ", level) != 1) {
597        goto bail_out;
598     }
599 @@ -1204,14 +1209,14 @@
600     /* Full backup requested? */
601     } else if (strcmp(level, "full") == 0) {
602        jcr->JobLevel = L_FULL;
603 -   } else if (strcmp(level, "differential") == 0) {
604 +   } else if (strstr(level, "differential")) {
605        jcr->JobLevel = L_DIFFERENTIAL;
606        free_memory(level);
607        return 1;
608 -   } else if (strcmp(level, "incremental") == 0) {
609 +   } else if (strstr(level, "incremental")) {
610        jcr->JobLevel = L_INCREMENTAL;
611        free_memory(level);
612 -      return 1;   
613 +      return 1;
614     /*
615      * We get his UTC since time, then sync the clocks and correct it
616      *   to agree with our clock.
617 Index: src/filed/restore.c
618 ===================================================================
619 --- src/filed/restore.c (révision 6467)
620 +++ src/filed/restore.c (copie de travail)
621 @@ -320,6 +320,11 @@
622              bclose(&rctx.bfd);
623           }
624  
625 +        /* TODO: manage deleted files */
626 +        if (file_index == 0) { /* deleted file */
627 +           continue;
628 +        }
629 +
630           /*
631            * Unpack attributes and do sanity check them
632            */
633 Index: src/cats/protos.h
634 ===================================================================
635 --- src/cats/protos.h   (révision 6467)
636 +++ src/cats/protos.h   (copie de travail)
637 @@ -102,6 +102,9 @@
638  int db_get_client_record(JCR *jcr, B_DB *mdb, CLIENT_DBR *cdbr);
639  int db_get_counter_record(JCR *jcr, B_DB *mdb, COUNTER_DBR *cr);
640  bool db_get_query_dbids(JCR *jcr, B_DB *mdb, POOL_MEM &query, dbid_list &ids);
641 +bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids, DB_RESULT_HANDLER *result_handler, void *ctx);
642 +bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb, JOB_DBR *jr, POOLMEM *jobids);
643 +int db_get_int_handler(void *ctx, int num_fields, char **row);
644  
645  
646  /* sql_list.c */
647 Index: src/cats/sql_get.c
648 ===================================================================
649 --- src/cats/sql_get.c  (révision 6467)
650 +++ src/cats/sql_get.c  (copie de travail)
651 @@ -898,8 +898,6 @@
652     return ok;
653  }
654  
655 -
656 -
657  /* Get Media Record
658   *
659   * Returns: false: on failure
660 @@ -1018,5 +1016,126 @@
661     return ok;
662  }
663  
664 +bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids, 
665 +                      DB_RESULT_HANDLER *result_handler, void *ctx)
666 +{
667 +   if (*jobids == 0) {
668 +      db_lock(mdb);
669 +      Mmsg(mdb->errmsg, _("ERR=JobIds are empty\n"));
670 +      db_unlock(mdb);
671 +      return false;
672 +   }
673  
674 +   POOL_MEM buf (PM_MESSAGE);
675 +   
676 +   Mmsg(buf,
677 + "SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId, File.LStat "
678 + "FROM ( "
679 +  "SELECT max(FileId) as FileId, PathId, FilenameId "
680 +    "FROM (SELECT FileId, PathId, FilenameId FROM File WHERE JobId IN (%s)) AS F "
681 +   "GROUP BY PathId, FilenameId "
682 +  ") AS Temp "
683 + "JOIN Filename ON (Filename.FilenameId = Temp.FilenameId) "
684 + "JOIN Path ON (Path.PathId = Temp.PathId) "
685 + "JOIN File ON (File.FileId = Temp.FileId) "
686 + "WHERE File.FileIndex > 0 ",
687 +             jobids);
688 +
689 +   return db_sql_query(mdb, buf.c_str(), result_handler, ctx);
690 +}
691 +
692 +
693 +/* Full : do nothing
694 + * Differential : get the last full id
695 + * Incremental : get the last full + last diff + last incr(s) ids
696 + *
697 + * TODO: look and merge from ua_restore.c
698 + */
699 +bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb, 
700 +                            JOB_DBR *jr, POOLMEM *jobids)
701 +{
702 +   char clientid[50], jobid[50], filesetid[50];
703 +   char date[MAX_TIME_LENGTH];
704 +
705 +   POOL_MEM query (PM_FNAME);
706 +   bstrutime(date, sizeof(date),  time(NULL) + 1);
707 +   jobids[0]='\0';
708 +
709 +   Mmsg(query, 
710 +"CREATE TEMPORARY TABLE btemp3%s AS "
711 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
712 +   "FROM Job JOIN FileSet USING (FileSetId) "
713 +  "WHERE ClientId = %s "
714 +    "AND Level='F' AND JobStatus='T' AND Type='B' "
715 +    "AND StartTime<'%s' "
716 +    "AND FileSet.FileSet=(SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
717 +  "ORDER BY Job.JobTDate DESC LIMIT 1",
718 +        edit_uint64(jcr->JobId, jobid),
719 +        edit_uint64(jr->ClientId, clientid),
720 +        date,
721 +        edit_uint64(jr->FileSetId, filesetid));
722 +
723 +   if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
724 +      return false;
725 +   }
726 +
727 +   if (jr->JobLevel == L_INCREMENTAL) {
728 +
729 +      Mmsg(query, 
730 +"INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
731 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
732 +   "FROM Job JOIN FileSet USING (FileSetId) "
733 +  "WHERE ClientId = %s "
734 +    "AND Level='D' AND JobStatus='T' AND Type='B' "
735 +    "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
736 +    "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
737 +  "ORDER BY Job.JobTDate DESC LIMIT 1 ",
738 +           jobid,
739 +           clientid,
740 +           jobid,
741 +           filesetid);
742 +
743 +      db_sql_query(mdb, query.c_str(), NULL, NULL);
744 +
745 +      Mmsg(query, 
746 +"INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
747 + "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
748 +   "FROM Job JOIN FileSet USING (FileSetId) "
749 +  "WHERE ClientId = %s "
750 +    "AND Level='I' AND JobStatus='T' AND Type='B' "
751 +    "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
752 +    "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
753 +  "ORDER BY Job.JobTDate DESC ",
754 +           jobid,
755 +           clientid,
756 +           jobid,
757 +           filesetid);
758 +      db_sql_query(mdb, query.c_str(), NULL, NULL);
759 +   }
760 +
761 +   Mmsg(query, "SELECT JobId FROM btemp3%s", jobid);
762 +   db_sql_query(mdb, query.c_str(), db_get_int_handler, jobids);
763 +   Dmsg1(1, "db_accurate_get_jobids=%s\n", jobids);
764 +
765 +   Mmsg(query, "DROP TABLE btemp3%s", jobid);
766 +   db_sql_query(mdb, query.c_str(), NULL, NULL);
767 +
768 +   return true;
769 +}
770 +
771 +/*
772 + * Use to build a string of int list from a query. "10,20,30"
773 + */
774 +int db_get_int_handler(void *ctx, int num_fields, char **row)
775 +{
776 +   POOLMEM *ret = (POOLMEM *)ctx;
777 +   if (num_fields == 1) {
778 +      if (ret[0] != 0) {
779 +         pm_strcat(ret, ",");
780 +      }
781 +      pm_strcat(ret, row[0]);
782 +   }
783 +   return 0;
784 +}
785 +
786  #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL || HAVE_DBI */
787 Index: src/stored/bextract.c
788 ===================================================================
789 --- src/stored/bextract.c       (révision 6467)
790 +++ src/stored/bextract.c       (copie de travail)
791 @@ -324,6 +324,14 @@
792           Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
793        }
794  
795 +      /* handle deleted file 
796 +       */
797 +      if (rec->FileIndex == 0) {
798 +         /* if file is included, remove it ? */
799 +         Jmsg(jcr, M_INFO, 0, _("fname=%s is marked as deleted.\n"), attr->fname);
800 +         break;
801 +      }
802 +
803        if (attr->file_index != rec->FileIndex) {
804           Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
805              rec->FileIndex, attr->file_index);
806 Index: src/stored/bscan.c
807 ===================================================================
808 --- src/stored/bscan.c  (révision 6467)
809 +++ src/stored/bscan.c  (copie de travail)
810 @@ -660,6 +660,15 @@
811     case STREAM_UNIX_ATTRIBUTES:
812     case STREAM_UNIX_ATTRIBUTES_EX:
813  
814 +      /* handle deleted file 
815 +       */
816 +      if (rec->FileIndex == 0) {
817 +         create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
818 +                                       FT_NOSTAT, "", rec);
819 +         free_jcr(mjcr);
820 +         break;
821 +      }
822 +
823        if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
824           Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
825        }
826 Index: src/stored/append.c
827 ===================================================================
828 --- src/stored/append.c (révision 6467)
829 +++ src/stored/append.c (copie de travail)
830 @@ -146,7 +146,7 @@
831  
832        /* Read Stream header from the File daemon.
833         *  The stream header consists of the following:
834 -       *    file_index (sequential Bacula file index, base 1)
835 +       *    file_index (sequential Bacula file index, base 1, 0 for deleted files)
836         *    stream     (Bacula number to distinguish parts of data)
837         *    info       (Info for Storage daemon -- compressed, encryped, ...)
838         *       info is not currently used, so is read, but ignored!
839 @@ -185,16 +185,18 @@
840  
841        Dmsg2(890, "<filed: Header FilInx=%d stream=%d\n", file_index, stream);
842  
843 -      if (!(file_index > 0 && (file_index == last_file_index ||
844 -          file_index == last_file_index + 1))) {
845 -         Jmsg0(jcr, M_FATAL, 0, _("File index from FD not positive or sequential\n"));
846 -         ok = false;
847 -         break;
848 +      if (file_index != 0) {    /* TODO: handle file_index == 0 */
849 +         if (!(file_index > 0 && (file_index == last_file_index ||
850 +                                  file_index == last_file_index + 1))) {
851 +            Jmsg0(jcr, M_FATAL, 0, _("File index from FD not positive or sequential\n"));
852 +            ok = false;
853 +            break;
854 +         }
855 +         if (file_index != last_file_index) {
856 +            jcr->JobFiles = file_index;
857 +            last_file_index = file_index;
858 +         }
859        }
860 -      if (file_index != last_file_index) {
861 -         jcr->JobFiles = file_index;
862 -         last_file_index = file_index;
863 -      }
864  
865        /* Read data stream from the File daemon.
866         *  The data stream is just raw bytes
867 @@ -214,22 +216,23 @@
868  
869           while (!write_record_to_block(dcr->block, &rec)) {
870              Dmsg2(850, "!write_record_to_block data_len=%d rem=%d\n", rec.data_len,
871 -                       rec.remainder);
872 +                  rec.remainder);
873              if (!write_block_to_device(dcr)) {
874                 Dmsg2(90, "Got write_block_to_dev error on device %s. %s\n",
875 -                  dev->print_name(), dev->bstrerror());
876 +                     dev->print_name(), dev->bstrerror());
877                 ok = false;
878                 break;
879              }
880 +
881 +            if (!ok) {
882 +               Dmsg0(400, "Not OK\n");
883 +               break;
884 +            }
885 +            jcr->JobBytes += rec.data_len;   /* increment bytes this job */
886 +            Dmsg4(850, "write_record FI=%s SessId=%d Strm=%s len=%d\n",
887 +                  FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId,
888 +                  stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len);
889           }
890 -         if (!ok) {
891 -            Dmsg0(400, "Not OK\n");
892 -            break;
893 -         }
894 -         jcr->JobBytes += rec.data_len;   /* increment bytes this job */
895 -         Dmsg4(850, "write_record FI=%s SessId=%d Strm=%s len=%d\n",
896 -            FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId,
897 -            stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len);
898  
899           /* Send attributes and digest to Director for Catalog */
900           if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX ||
901 Index: src/jcr.h
902 ===================================================================
903 --- src/jcr.h   (révision 6467)
904 +++ src/jcr.h   (copie de travail)
905 @@ -119,6 +119,7 @@
906  
907  /* Forward referenced structures */
908  class JCR;
909 +class htable;
910  struct FF_PKT;
911  struct B_DB;
912  struct ATTR_DBR;
913 @@ -318,6 +319,7 @@
914     CRYPTO_CTX crypto;                 /* Crypto ctx */
915     DIRRES* director;                  /* Director resource */
916     bool VSS;                          /* VSS used by FD */
917 +   htable *file_list;                 /* Previous file list (accurate mode) */
918  #endif /* FILE_DAEMON */
919  
920  
921 Index: src/lib/Makefile.in
922 ===================================================================
923 --- src/lib/Makefile.in (révision 6467)
924 +++ src/lib/Makefile.in (copie de travail)
925 @@ -29,7 +29,7 @@
926           res.c rwlock.c scan.c serial.c sha1.c \
927           signal.c smartall.c rblist.c tls.c tree.c \
928           util.c var.c watchdog.c workq.c btimers.c \
929 -         address_conf.c pythonlib.c breg.c
930 +         address_conf.c pythonlib.c breg.c htable.c
931  
932  
933  LIBOBJS = attr.o base64.o berrno.o bsys.o bget_msg.o \
934 @@ -42,7 +42,7 @@
935           res.o rwlock.o scan.o serial.o sha1.o \
936           signal.o smartall.o rblist.o tls.o tree.o \
937           util.o var.o watchdog.o workq.o btimers.o \
938 -         address_conf.o pythonlib.o breg.o
939 +         address_conf.o pythonlib.o breg.o htable.o
940  
941  
942  EXTRAOBJS = @OBJLIST@
943 Index: src/findlib/find.c
944 ===================================================================
945 --- src/findlib/find.c  (révision 6467)
946 +++ src/findlib/find.c  (copie de travail)
947 @@ -96,6 +96,13 @@
948    Dmsg0(100, "Leave set_find_options()\n");
949  }
950  
951 +void
952 +set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff))
953 +{
954 +   Dmsg0(100, "Enter set_find_changed_function()\n");
955 +   ff->check_fct = check_fct;
956 +}
957 +
958  /*
959   * For VSS we need to know which windows drives
960   * are used, because we create a snapshot of all used
961 Index: src/findlib/find_one.c
962 ===================================================================
963 --- src/findlib/find_one.c      (révision 6467)
964 +++ src/findlib/find_one.c      (copie de travail)
965 @@ -258,6 +258,33 @@
966  }
967  
968  /*
969 + * In incremental/diffential or accurate backup, we
970 + * say if the current file has changed.
971 + */
972 +static bool check_changes(JCR *jcr, FF_PKT *ff_pkt)
973 +{
974 +   /* in special mode (like accurate backup), user can 
975 +    * choose his comparison function.
976 +    */
977 +   if (ff_pkt->check_fct) {
978 +      return ff_pkt->check_fct(jcr, ff_pkt);
979 +   }
980 +
981 +   /* in normal modes (incr/diff), we use this default
982 +    * behaviour
983 +    */
984 +   if (ff_pkt->incremental &&
985 +       (ff_pkt->statp.st_mtime < ff_pkt->save_time &&
986 +       ((ff_pkt->flags & FO_MTIMEONLY) ||
987 +        ff_pkt->statp.st_ctime < ff_pkt->save_time))) 
988 +   {
989 +      return true;
990 +   } else {
991 +      return false;
992 +   }
993 +}
994 +
995 +/*
996   * Find a single file.
997   * handle_file is the callback for handling the file.
998   * p is the filename
999 @@ -333,16 +360,13 @@
1000      * since our last "save_time", presumably the last Full save
1001      * or Incremental.
1002      */
1003 -   if (ff_pkt->incremental && !S_ISDIR(ff_pkt->statp.st_mode)) {
1004 +   if (   ff_pkt->incremental 
1005 +       && !S_ISDIR(ff_pkt->statp.st_mode) 
1006 +       && !check_changes(jcr, ff_pkt)) 
1007 +   {
1008        Dmsg1(300, "Non-directory incremental: %s\n", ff_pkt->fname);
1009 -      /* Not a directory */
1010 -      if (ff_pkt->statp.st_mtime < ff_pkt->save_time
1011 -          && ((ff_pkt->flags & FO_MTIMEONLY) ||
1012 -              ff_pkt->statp.st_ctime < ff_pkt->save_time)) {
1013 -         /* Incremental option, file not changed */
1014 -         ff_pkt->type = FT_NOCHG;
1015 -         return handle_file(jcr, ff_pkt, top_level);
1016 -      }
1017 +      ff_pkt->type = FT_NOCHG;
1018 +      return handle_file(jcr, ff_pkt, top_level);
1019     }
1020  
1021  #ifdef HAVE_DARWIN_OS
1022 @@ -502,15 +526,13 @@
1023        link[len] = 0;
1024  
1025        ff_pkt->link = link;
1026 -      if (ff_pkt->incremental &&
1027 -          (ff_pkt->statp.st_mtime < ff_pkt->save_time &&
1028 -             ((ff_pkt->flags & FO_MTIMEONLY) ||
1029 -               ff_pkt->statp.st_ctime < ff_pkt->save_time))) {
1030 +      if (ff_pkt->incremental && !check_changes(jcr, ff_pkt)) {
1031           /* Incremental option, directory entry not changed */
1032           ff_pkt->type = FT_DIRNOCHG;
1033        } else {
1034           ff_pkt->type = FT_DIRBEGIN;
1035        }
1036 +
1037        /* We have set st_rdev to 1 if it is a reparse point, otherwise 0 */
1038        if (have_win32_api() && ff_pkt->statp.st_rdev) {
1039           ff_pkt->type = FT_REPARSE;
1040 Index: src/findlib/find.h
1041 ===================================================================
1042 --- src/findlib/find.h  (révision 6467)
1043 +++ src/findlib/find.h  (copie de travail)
1044 @@ -215,6 +215,7 @@
1045     findFILESET *fileset;
1046     int (*file_save)(JCR *, FF_PKT *, bool); /* User's callback */
1047     int (*plugin_save)(JCR *, FF_PKT *, bool); /* User's callback */
1048 +   bool (*check_fct)(JCR *, FF_PKT *); /* optionnal user fct to check file changes */
1049  
1050     /* Values set by accept_file while processing Options */
1051     uint32_t flags;                    /* backup options */
1052 Index: src/findlib/protos.h
1053 ===================================================================
1054 --- src/findlib/protos.h        (révision 6467)
1055 +++ src/findlib/protos.h        (copie de travail)
1056 @@ -45,6 +45,7 @@
1057  /* From find.c */
1058  FF_PKT *init_find_files();
1059  void  set_find_options(FF_PKT *ff, int incremental, time_t mtime);
1060 +void set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff));
1061  int   find_files(JCR *jcr, FF_PKT *ff, int file_sub(JCR *, FF_PKT *ff_pkt, bool),
1062                   int plugin_sub(JCR *, FF_PKT *ff_pkt, bool));
1063  int   match_files(JCR *jcr, FF_PKT *ff, int sub(JCR *, FF_PKT *ff_pkt, bool));