]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/filed/accurate.c
Remove bad optimization from Accurate code
[bacula/bacula] / bacula / src / filed / accurate.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2011 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version three of the GNU Affero General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU Affero General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of Kern Sibbald.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *  Version $Id $
30  *
31  */
32
33 #include "bacula.h"
34 #include "filed.h"
35
36 static int dbglvl=100;
37
38 typedef struct PrivateCurFile {
39    hlink link;
40    char *fname;
41    char *lstat;
42    char *chksum;
43    int32_t delta_seq;
44    bool seen;
45 } CurFile;
46
47 bool accurate_mark_file_as_seen(JCR *jcr, char *fname)
48 {
49    if (!jcr->accurate || !jcr->file_list) {
50       return false;
51    }
52    /* TODO: just use elt->seen = 1 */
53    CurFile *temp = (CurFile *)jcr->file_list->lookup(fname);
54    if (temp) {
55       temp->seen = 1;              /* records are in memory */
56       Dmsg1(dbglvl, "marked <%s> as seen\n", fname);
57    } else {
58       Dmsg1(dbglvl, "<%s> not found to be marked as seen\n", fname);
59    }
60    return true;
61 }
62
63 static bool accurate_mark_file_as_seen(JCR *jcr, CurFile *elt)
64 {
65    /* TODO: just use elt->seen = 1 */
66    CurFile *temp = (CurFile *)jcr->file_list->lookup(elt->fname);
67    if (temp) {
68       temp->seen = 1;              /* records are in memory */
69    }
70    return true;
71 }
72
73 static bool accurate_lookup(JCR *jcr, char *fname, CurFile *ret)
74 {
75    bool found=false;
76    ret->seen = 0;
77
78    CurFile *temp = (CurFile *)jcr->file_list->lookup(fname);
79    if (temp) {
80       memcpy(ret, temp, sizeof(CurFile));
81       found=true;
82       Dmsg1(dbglvl, "lookup <%s> ok\n", fname);
83    }
84
85    return found;
86 }
87
88 static bool accurate_init(JCR *jcr, int nbfile)
89 {
90    CurFile *elt = NULL;
91    jcr->file_list = (htable *)malloc(sizeof(htable));
92    jcr->file_list->init(elt, &elt->link, nbfile);
93    return true;
94 }
95
96 static bool accurate_send_base_file_list(JCR *jcr)
97 {
98    CurFile *elt;
99    struct stat statc;
100    int32_t LinkFIc;
101    FF_PKT *ff_pkt;
102    int stream = STREAM_UNIX_ATTRIBUTES;
103
104    if (!jcr->accurate || jcr->getJobLevel() != L_FULL) {
105       return true;
106    }
107
108    if (jcr->file_list == NULL) {
109       return true;
110    }
111
112    ff_pkt = init_find_files();
113    ff_pkt->type = FT_BASE;
114
115    foreach_htable(elt, jcr->file_list) {
116       if (elt->seen) {
117          Dmsg2(dbglvl, "base file fname=%s seen=%i\n", elt->fname, elt->seen);
118          /* TODO: skip the decode and use directly the lstat field */
119          decode_stat(elt->lstat, &statc, sizeof(statc), &LinkFIc); /* decode catalog stat */  
120          ff_pkt->fname = elt->fname;
121          ff_pkt->statp = statc;
122          encode_and_send_attributes(jcr, ff_pkt, stream);
123 //       free(elt->fname);
124       }
125    }
126
127    term_find_files(ff_pkt);
128    return true;
129 }
130
131
132 /* This function is called at the end of backup
133  * We walk over all hash disk element, and we check
134  * for elt.seen.
135  */
136 static bool accurate_send_deleted_list(JCR *jcr)
137 {
138    CurFile *elt;
139    struct stat statc;
140    int32_t LinkFIc;
141    FF_PKT *ff_pkt;
142    int stream = STREAM_UNIX_ATTRIBUTES;
143
144    if (!jcr->accurate) {
145       return true;
146    }
147
148    if (jcr->file_list == NULL) {
149       return true;
150    }
151
152    ff_pkt = init_find_files();
153    ff_pkt->type = FT_DELETED;
154
155    foreach_htable(elt, jcr->file_list) {
156       if (elt->seen || plugin_check_file(jcr, elt->fname)) {
157          continue;
158       }
159       Dmsg2(dbglvl, "deleted fname=%s seen=%i\n", elt->fname, elt->seen);
160       /* TODO: skip the decode and use directly the lstat field */
161       decode_stat(elt->lstat, &statc, sizeof(statc), &LinkFIc); /* decode catalog stat */
162       ff_pkt->fname = elt->fname;
163       ff_pkt->statp.st_mtime = statc.st_mtime;
164       ff_pkt->statp.st_ctime = statc.st_ctime;
165       encode_and_send_attributes(jcr, ff_pkt, stream);
166 //    free(elt->fname);
167    }
168
169    term_find_files(ff_pkt);
170    return true;
171 }
172
173 void accurate_free(JCR *jcr)
174 {
175    if (jcr->file_list) {
176       jcr->file_list->destroy();
177       free(jcr->file_list);
178       jcr->file_list = NULL;
179    }
180 }
181
182 /* Send the deleted or the base file list and cleanup  */
183 bool accurate_finish(JCR *jcr)
184 {
185    bool ret = true;
186
187    if (jcr->is_canceled() || jcr->is_incomplete()) {
188       accurate_free(jcr);
189       return ret;
190    }
191    if (jcr->accurate) {
192       if (jcr->is_JobLevel(L_FULL)) {
193          if (!jcr->rerunning) {
194             ret = accurate_send_base_file_list(jcr);
195          }
196       } else {
197          ret = accurate_send_deleted_list(jcr);
198       }
199       accurate_free(jcr);
200       if (jcr->is_JobLevel(L_FULL)) {
201          Jmsg(jcr, M_INFO, 0, _("Space saved with Base jobs: %lld MB\n"), 
202               jcr->base_size/(1024*1024));
203       }
204    }
205    return ret;
206 }
207
208 static bool accurate_add_file(JCR *jcr, uint32_t len, 
209                               char *fname, char *lstat, char *chksum,
210                               int32_t delta)
211 {
212    bool ret = true;
213    CurFile *item;
214
215    /* we store CurFile, fname and ctime/mtime in the same chunk 
216     * we need one extra byte to handle an empty chksum
217     */
218    item = (CurFile *)jcr->file_list->hash_malloc(sizeof(CurFile)+len+3);
219    item->seen = 0;
220
221    /* TODO: see if we can optimize this part with memcpy instead of strcpy */
222    item->fname  = (char *)item+sizeof(CurFile);
223    strcpy(item->fname, fname);
224
225    item->lstat  = item->fname+strlen(item->fname)+1;
226    strcpy(item->lstat, lstat);
227
228    item->chksum = item->lstat+strlen(item->lstat)+1;
229    strcpy(item->chksum, chksum);
230
231    item->delta_seq = delta;
232
233    jcr->file_list->insert(item->fname, item); 
234
235    Dmsg4(dbglvl, "add fname=<%s> lstat=%s  delta_seq=%i chksum=%s\n", 
236          fname, lstat, delta, chksum);
237    return ret;
238 }
239
240 /*
241  * This function is called for each file seen in fileset.
242  * We check in file_list hash if fname have been backuped
243  * the last time. After we can compare Lstat field. 
244  * Full Lstat usage have been removed on 6612 
245  *
246  * Returns: true   if file has changed (must be backed up)
247  *          false  file not changed
248  */
249 bool accurate_check_file(JCR *jcr, FF_PKT *ff_pkt)
250 {
251    int digest_stream = STREAM_NONE;
252    DIGEST *digest = NULL;
253
254    struct stat statc;
255    int32_t LinkFIc;
256    bool stat = false;
257    char *opts;
258    char *fname;
259    CurFile elt;
260
261    ff_pkt->delta_seq = 0;
262    ff_pkt->accurate_found = false;
263
264    if (!jcr->accurate && !jcr->rerunning) {
265       return true;
266    }
267
268    strip_path(ff_pkt);
269  
270    if (S_ISDIR(ff_pkt->statp.st_mode)) {
271       fname = ff_pkt->link;
272    } else {
273       fname = ff_pkt->fname;
274    } 
275
276    if (!accurate_lookup(jcr, fname, &elt)) {
277       Dmsg1(dbglvl, "accurate %s (not found)\n", fname);
278       stat = true;
279       goto bail_out;
280    }
281
282    ff_pkt->accurate_found = true;
283    ff_pkt->delta_seq = elt.delta_seq;
284
285    decode_stat(elt.lstat, &statc, sizeof(statc), &LinkFIc); /* decode catalog stat */
286
287    if (!jcr->rerunning && (jcr->getJobLevel() == L_FULL)) {
288       opts = ff_pkt->BaseJobOpts;
289    } else {
290       opts = ff_pkt->AccurateOpts;
291    }
292
293    /*
294     * Loop over options supplied by user and verify the
295     * fields he requests.
296     */
297    for (char *p=opts; !stat && *p; p++) {
298       char ed1[30], ed2[30];
299       switch (*p) {
300       case 'i':                /* compare INODEs */
301          if (statc.st_ino != ff_pkt->statp.st_ino) {
302             Dmsg3(dbglvl-1, "%s      st_ino   differ. Cat: %s File: %s\n",
303                   fname,
304                   edit_uint64((uint64_t)statc.st_ino, ed1),
305                   edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
306             stat = true;
307          }
308          break;
309       case 'p':                /* permissions bits */
310          /* TODO: If something change only in perm, user, group
311           * Backup only the attribute stream
312           */
313          if (statc.st_mode != ff_pkt->statp.st_mode) {
314             Dmsg3(dbglvl-1, "%s     st_mode  differ. Cat: %x File: %x\n",
315                   fname,
316                   (uint32_t)statc.st_mode, (uint32_t)ff_pkt->statp.st_mode);
317             stat = true;
318          }
319          break;
320       case 'n':                /* number of links */
321          if (statc.st_nlink != ff_pkt->statp.st_nlink) {
322             Dmsg3(dbglvl-1, "%s      st_nlink differ. Cat: %d File: %d\n",
323                   fname,
324                   (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
325             stat = true;
326          }
327          break;
328       case 'u':                /* user id */
329          if (statc.st_uid != ff_pkt->statp.st_uid) {
330             Dmsg3(dbglvl-1, "%s      st_uid   differ. Cat: %u File: %u\n",
331                   fname,
332                   (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
333             stat = true;
334          }
335          break;
336       case 'g':                /* group id */
337          if (statc.st_gid != ff_pkt->statp.st_gid) {
338             Dmsg3(dbglvl-1, "%s      st_gid   differ. Cat: %u File: %u\n",
339                   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             Dmsg3(dbglvl-1, "%s      st_size  differ. Cat: %s File: %s\n",
347                   fname,
348                   edit_uint64((uint64_t)statc.st_size, ed1),
349                   edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
350             stat = true;
351          }
352          break;
353       case 'a':                /* access time */
354          if (statc.st_atime != ff_pkt->statp.st_atime) {
355             Dmsg1(dbglvl-1, "%s      st_atime differs\n", fname);
356             stat = true;
357          }
358          break;
359       case 'm':                 /* modification time */
360          if (statc.st_mtime != ff_pkt->statp.st_mtime) {
361             Dmsg1(dbglvl-1, "%s      st_mtime differs\n", fname);
362             stat = true;
363          }
364          break;
365       case 'c':                /* ctime */
366          if (statc.st_ctime != ff_pkt->statp.st_ctime) {
367             Dmsg1(dbglvl-1, "%s      st_ctime differs\n", fname);
368             stat = true;
369          }
370          break;
371       case 'd':                /* file size decrease */
372          if (statc.st_size > ff_pkt->statp.st_size) {
373             Dmsg3(dbglvl-1, "%s      st_size  decrease. Cat: %s File: %s\n",
374                   fname,
375                   edit_uint64((uint64_t)statc.st_size, ed1),
376                   edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
377             stat = true;
378          }
379          break;
380       case 'A':                 /* Always backup a file */
381          stat = true;
382          break;
383       /* TODO: cleanup and factorise this function with verify.c */
384       case '5':                /* compare MD5 */
385       case '1':                /* compare SHA1 */
386         /*
387           * The remainder of the function is all about getting the checksum.
388           * First we initialise, then we read files, other streams and Finder Info.
389           */
390          if (!stat && ff_pkt->type != FT_LNKSAVED && 
391              (S_ISREG(ff_pkt->statp.st_mode) && 
392               ff_pkt->flags & (FO_MD5|FO_SHA1|FO_SHA256|FO_SHA512))) 
393          {
394
395             if (!*elt.chksum && !jcr->rerunning) {
396                Jmsg(jcr, M_WARNING, 0, _("Cannot verify checksum for %s\n"),
397                     ff_pkt->fname);
398                stat = true;
399                break;
400             }
401
402             /*
403              * Create our digest context. If this fails, the digest will be set
404              * to NULL and not used.
405              */
406             if (ff_pkt->flags & FO_MD5) {
407                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_MD5);
408                digest_stream = STREAM_MD5_DIGEST;
409                
410             } else if (ff_pkt->flags & FO_SHA1) {
411                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA1);
412                digest_stream = STREAM_SHA1_DIGEST;
413                
414             } else if (ff_pkt->flags & FO_SHA256) {
415                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA256);
416                digest_stream = STREAM_SHA256_DIGEST;
417                
418             } else if (ff_pkt->flags & FO_SHA512) {
419                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA512);
420                digest_stream = STREAM_SHA512_DIGEST;
421             }
422             
423             /* Did digest initialization fail? */
424             if (digest_stream != STREAM_NONE && digest == NULL) {
425                Jmsg(jcr, M_WARNING, 0, _("%s digest initialization failed\n"),
426                     stream_to_ascii(digest_stream));
427             }
428
429             /* compute MD5 or SHA1 hash */
430             if (digest) {
431                char md[CRYPTO_DIGEST_MAX_SIZE];
432                uint32_t size;
433                
434                size = sizeof(md);
435                
436                if (digest_file(jcr, ff_pkt, digest) != 0) {
437                   jcr->JobErrors++;
438
439                } else if (crypto_digest_finalize(digest, (uint8_t *)md, &size)) {
440                   char *digest_buf;
441                   const char *digest_name;
442                   
443                   digest_buf = (char *)malloc(BASE64_SIZE(size));
444                   digest_name = crypto_digest_name(digest);
445                   
446                   bin_to_base64(digest_buf, BASE64_SIZE(size), md, size, true);
447
448                   if (strcmp(digest_buf, elt.chksum)) {
449                      Dmsg4(dbglvl,"%s      %s chksum  diff. Cat: %s File: %s\n",
450                            fname,
451                            digest_name,
452                            elt.chksum,
453                            digest_buf);
454                      stat = true;
455                   }
456                   
457                   free(digest_buf);
458                }
459                crypto_digest_free(digest);
460             }
461          }
462
463          break;
464       case ':':
465       case 'J':
466       case 'C':
467       default:
468          break;
469       }
470    }
471
472    /* In Incr/Diff accurate mode, we mark all files as seen
473     * When in Full+Base mode, we mark only if the file match exactly
474     */
475    if (jcr->getJobLevel() == L_FULL) {
476       if (!stat) {               
477          /* compute space saved with basefile */
478          jcr->base_size += ff_pkt->statp.st_size;
479          accurate_mark_file_as_seen(jcr, &elt);
480       }
481    } else {
482       accurate_mark_file_as_seen(jcr, &elt);
483    }
484
485 bail_out:
486    unstrip_path(ff_pkt);
487    return stat;
488 }
489
490 /* 
491  * TODO: use big buffer from htable
492  */
493 int accurate_cmd(JCR *jcr)
494 {
495    BSOCK *dir = jcr->dir_bsock;
496    int lstat_pos, chksum_pos;
497    int32_t nb;
498    uint16_t delta_seq;
499
500    if (job_canceled(jcr)) {
501       return true;
502    }
503    if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
504       dir->fsend(_("2991 Bad accurate command\n"));
505       return false;
506    }
507
508    jcr->accurate = true;
509
510    accurate_init(jcr, nb);
511
512    /*
513     * buffer = sizeof(CurFile) + dirmsg
514     * dirmsg = fname + \0 + lstat + \0 + checksum + \0 + delta_seq + \0
515     */
516    /* get current files */
517    while (dir->recv() >= 0) {
518       lstat_pos = strlen(dir->msg) + 1;
519       if (lstat_pos < dir->msglen) {
520          chksum_pos = lstat_pos + strlen(dir->msg + lstat_pos) + 1;
521
522          if (chksum_pos >= dir->msglen) {
523             chksum_pos = lstat_pos - 1;    /* tweak: no checksum, point to the last \0 */
524             delta_seq = 0;
525          } else {
526             delta_seq = str_to_int32(dir->msg + 
527                                      chksum_pos + 
528                                      strlen(dir->msg + chksum_pos) + 1);
529          }
530
531          accurate_add_file(jcr, dir->msglen, 
532                            dir->msg,               /* Path */
533                            dir->msg + lstat_pos,   /* LStat */
534                            dir->msg + chksum_pos,  /* CheckSum */
535                            delta_seq);             /* Delta Sequence */
536       }
537    }
538
539 #ifdef DEBUG
540    extern void *start_heap;
541
542    char b1[50], b2[50], b3[50], b4[50], b5[50];
543    Dmsg5(dbglvl," Heap: heap=%s smbytes=%s max_bytes=%s bufs=%s max_bufs=%s\n",
544          edit_uint64_with_commas((char *)sbrk(0)-(char *)start_heap, b1),
545          edit_uint64_with_commas(sm_bytes, b2),
546          edit_uint64_with_commas(sm_max_bytes, b3),
547          edit_uint64_with_commas(sm_buffers, b4),
548          edit_uint64_with_commas(sm_max_buffers, b5));
549 #endif
550
551    return true;
552 }