]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/filed/accurate.c
404756aa6c20e16e42b1880c2848877abc97e145
[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+1);
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
263    if (!jcr->accurate && !jcr->rerunning) {
264       return true;
265    }
266
267    strip_path(ff_pkt);
268  
269    if (S_ISDIR(ff_pkt->statp.st_mode)) {
270       fname = ff_pkt->link;
271    } else {
272       fname = ff_pkt->fname;
273    } 
274
275    if (!accurate_lookup(jcr, fname, &elt)) {
276       Dmsg1(dbglvl, "accurate %s (not found)\n", fname);
277       stat = true;
278       goto bail_out;
279    }
280
281    ff_pkt->delta_seq = elt.delta_seq;
282
283    if (elt.seen) { /* file has been seen ? */
284       Dmsg1(dbglvl, "accurate %s (already seen)\n", fname);
285       goto bail_out;
286    }
287
288    decode_stat(elt.lstat, &statc, sizeof(statc), &LinkFIc); /* decode catalog stat */
289
290    if (!jcr->rerunning && (jcr->getJobLevel() == L_FULL)) {
291       opts = ff_pkt->BaseJobOpts;
292    } else {
293       opts = ff_pkt->AccurateOpts;
294    }
295
296    /*
297     * Loop over options supplied by user and verify the
298     * fields he requests.
299     */
300    for (char *p=opts; !stat && *p; p++) {
301       char ed1[30], ed2[30];
302       switch (*p) {
303       case 'i':                /* compare INODEs */
304          if (statc.st_ino != ff_pkt->statp.st_ino) {
305             Dmsg3(dbglvl-1, "%s      st_ino   differ. Cat: %s File: %s\n",
306                   fname,
307                   edit_uint64((uint64_t)statc.st_ino, ed1),
308                   edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
309             stat = true;
310          }
311          break;
312       case 'p':                /* permissions bits */
313          /* TODO: If something change only in perm, user, group
314           * Backup only the attribute stream
315           */
316          if (statc.st_mode != ff_pkt->statp.st_mode) {
317             Dmsg3(dbglvl-1, "%s     st_mode  differ. Cat: %x File: %x\n",
318                   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             Dmsg3(dbglvl-1, "%s      st_nlink differ. Cat: %d File: %d\n",
326                   fname,
327                   (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
328             stat = true;
329          }
330          break;
331       case 'u':                /* user id */
332          if (statc.st_uid != ff_pkt->statp.st_uid) {
333             Dmsg3(dbglvl-1, "%s      st_uid   differ. Cat: %u File: %u\n",
334                   fname,
335                   (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
336             stat = true;
337          }
338          break;
339       case 'g':                /* group id */
340          if (statc.st_gid != ff_pkt->statp.st_gid) {
341             Dmsg3(dbglvl-1, "%s      st_gid   differ. Cat: %u File: %u\n",
342                   fname,
343                   (uint32_t)statc.st_gid, (uint32_t)ff_pkt->statp.st_gid);
344             stat = true;
345          }
346          break;
347       case 's':                /* size */
348          if (statc.st_size != ff_pkt->statp.st_size) {
349             Dmsg3(dbglvl-1, "%s      st_size  differ. Cat: %s File: %s\n",
350                   fname,
351                   edit_uint64((uint64_t)statc.st_size, ed1),
352                   edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
353             stat = true;
354          }
355          break;
356       case 'a':                /* access time */
357          if (statc.st_atime != ff_pkt->statp.st_atime) {
358             Dmsg1(dbglvl-1, "%s      st_atime differs\n", fname);
359             stat = true;
360          }
361          break;
362       case 'm':                 /* modification time */
363          if (statc.st_mtime != ff_pkt->statp.st_mtime) {
364             Dmsg1(dbglvl-1, "%s      st_mtime differs\n", fname);
365             stat = true;
366          }
367          break;
368       case 'c':                /* ctime */
369          if (statc.st_ctime != ff_pkt->statp.st_ctime) {
370             Dmsg1(dbglvl-1, "%s      st_ctime differs\n", fname);
371             stat = true;
372          }
373          break;
374       case 'd':                /* file size decrease */
375          if (statc.st_size > ff_pkt->statp.st_size) {
376             Dmsg3(dbglvl-1, "%s      st_size  decrease. Cat: %s File: %s\n",
377                   fname,
378                   edit_uint64((uint64_t)statc.st_size, ed1),
379                   edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
380             stat = true;
381          }
382          break;
383       case 'A':                 /* Always backup a file */
384          stat = true;
385          break;
386       /* TODO: cleanup and factorise this function with verify.c */
387       case '5':                /* compare MD5 */
388       case '1':                /* compare SHA1 */
389         /*
390           * The remainder of the function is all about getting the checksum.
391           * First we initialise, then we read files, other streams and Finder Info.
392           */
393          if (!stat && ff_pkt->type != FT_LNKSAVED && 
394              (S_ISREG(ff_pkt->statp.st_mode) && 
395               ff_pkt->flags & (FO_MD5|FO_SHA1|FO_SHA256|FO_SHA512))) 
396          {
397
398             if (!*elt.chksum && !jcr->rerunning) {
399                Jmsg(jcr, M_WARNING, 0, _("Cannot verify checksum for %s\n"),
400                     ff_pkt->fname);
401                stat = true;
402                break;
403             }
404
405             /*
406              * Create our digest context. If this fails, the digest will be set
407              * to NULL and not used.
408              */
409             if (ff_pkt->flags & FO_MD5) {
410                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_MD5);
411                digest_stream = STREAM_MD5_DIGEST;
412                
413             } else if (ff_pkt->flags & FO_SHA1) {
414                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA1);
415                digest_stream = STREAM_SHA1_DIGEST;
416                
417             } else if (ff_pkt->flags & FO_SHA256) {
418                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA256);
419                digest_stream = STREAM_SHA256_DIGEST;
420                
421             } else if (ff_pkt->flags & FO_SHA512) {
422                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA512);
423                digest_stream = STREAM_SHA512_DIGEST;
424             }
425             
426             /* Did digest initialization fail? */
427             if (digest_stream != STREAM_NONE && digest == NULL) {
428                Jmsg(jcr, M_WARNING, 0, _("%s digest initialization failed\n"),
429                     stream_to_ascii(digest_stream));
430             }
431
432             /* compute MD5 or SHA1 hash */
433             if (digest) {
434                char md[CRYPTO_DIGEST_MAX_SIZE];
435                uint32_t size;
436                
437                size = sizeof(md);
438                
439                if (digest_file(jcr, ff_pkt, digest) != 0) {
440                   jcr->JobErrors++;
441
442                } else if (crypto_digest_finalize(digest, (uint8_t *)md, &size)) {
443                   char *digest_buf;
444                   const char *digest_name;
445                   
446                   digest_buf = (char *)malloc(BASE64_SIZE(size));
447                   digest_name = crypto_digest_name(digest);
448                   
449                   bin_to_base64(digest_buf, BASE64_SIZE(size), md, size, true);
450
451                   if (strcmp(digest_buf, elt.chksum)) {
452                      Dmsg4(dbglvl,"%s      %s chksum  diff. Cat: %s File: %s\n",
453                            fname,
454                            digest_name,
455                            elt.chksum,
456                            digest_buf);
457                      stat = true;
458                   }
459                   
460                   free(digest_buf);
461                }
462                crypto_digest_free(digest);
463             }
464          }
465
466          break;
467       case ':':
468       case 'J':
469       case 'C':
470       default:
471          break;
472       }
473    }
474
475    /* In Incr/Diff accurate mode, we mark all files as seen
476     * When in Full+Base mode, we mark only if the file match exactly
477     */
478    if (jcr->getJobLevel() == L_FULL) {
479       if (!stat) {               
480          /* compute space saved with basefile */
481          jcr->base_size += ff_pkt->statp.st_size;
482          accurate_mark_file_as_seen(jcr, &elt);
483       }
484    } else {
485       accurate_mark_file_as_seen(jcr, &elt);
486    }
487
488 bail_out:
489    unstrip_path(ff_pkt);
490    return stat;
491 }
492
493 /* 
494  * TODO: use big buffer from htable
495  */
496 int accurate_cmd(JCR *jcr)
497 {
498    BSOCK *dir = jcr->dir_bsock;
499    int lstat_pos, chksum_pos;
500    int32_t nb;
501    uint16_t delta_seq;
502
503    if (job_canceled(jcr)) {
504       return true;
505    }
506    if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
507       dir->fsend(_("2991 Bad accurate command\n"));
508       return false;
509    }
510
511    jcr->accurate = true;
512
513    accurate_init(jcr, nb);
514
515    /*
516     * buffer = sizeof(CurFile) + dirmsg
517     * dirmsg = fname + \0 + lstat + \0 + checksum + \0 + delta_seq + \0
518     */
519    /* get current files */
520    while (dir->recv() >= 0) {
521       lstat_pos = strlen(dir->msg) + 1;
522       if (lstat_pos < dir->msglen) {
523          chksum_pos = lstat_pos + strlen(dir->msg + lstat_pos) + 1;
524
525          if (chksum_pos >= dir->msglen) {
526             chksum_pos = lstat_pos - 1;    /* tweak: no checksum, point to the last \0 */
527             delta_seq = 0;
528          } else {
529             delta_seq = str_to_int32(dir->msg + 
530                                      chksum_pos + 
531                                      strlen(dir->msg + chksum_pos) + 1);
532          }
533
534          accurate_add_file(jcr, dir->msglen, 
535                            dir->msg,               /* Path */
536                            dir->msg + lstat_pos,   /* LStat */
537                            dir->msg + chksum_pos,  /* CheckSum */
538                            delta_seq);             /* Delta Sequence */
539       }
540    }
541
542 #ifdef DEBUG
543    extern void *start_heap;
544
545    char b1[50], b2[50], b3[50], b4[50], b5[50];
546    Dmsg5(dbglvl," Heap: heap=%s smbytes=%s max_bytes=%s bufs=%s max_bufs=%s\n",
547          edit_uint64_with_commas((char *)sbrk(0)-(char *)start_heap, b1),
548          edit_uint64_with_commas(sm_bytes, b2),
549          edit_uint64_with_commas(sm_max_bytes, b3),
550          edit_uint64_with_commas(sm_buffers, b4),
551          edit_uint64_with_commas(sm_max_buffers, b5));
552 #endif
553
554    return true;
555 }