]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/filed/accurate.c
Fix segfault in 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 some extra bytes 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
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
384       /* TODO: cleanup and factorise this function with verify.c */
385       case '5':                /* compare MD5 */
386       case '1':                /* compare SHA1 */
387         /*
388           * The remainder of the function is all about getting the checksum.
389           * First we initialise, then we read files, other streams and Finder Info.
390           */
391          if (!stat && ff_pkt->type != FT_LNKSAVED && 
392              (S_ISREG(ff_pkt->statp.st_mode) && 
393               ff_pkt->flags & (FO_MD5|FO_SHA1|FO_SHA256|FO_SHA512))) 
394          {
395
396             if (!*elt.chksum && !jcr->rerunning) {
397                Jmsg(jcr, M_WARNING, 0, _("Cannot verify checksum for %s\n"),
398                     ff_pkt->fname);
399                stat = true;
400                break;
401             }
402
403             /*
404              * Create our digest context. If this fails, the digest will be set
405              * to NULL and not used.
406              */
407             if (ff_pkt->flags & FO_MD5) {
408                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_MD5);
409                digest_stream = STREAM_MD5_DIGEST;
410                
411             } else if (ff_pkt->flags & FO_SHA1) {
412                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA1);
413                digest_stream = STREAM_SHA1_DIGEST;
414                
415             } else if (ff_pkt->flags & FO_SHA256) {
416                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA256);
417                digest_stream = STREAM_SHA256_DIGEST;
418                
419             } else if (ff_pkt->flags & FO_SHA512) {
420                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA512);
421                digest_stream = STREAM_SHA512_DIGEST;
422             }
423             
424             /* Did digest initialization fail? */
425             if (digest_stream != STREAM_NONE && digest == NULL) {
426                Jmsg(jcr, M_WARNING, 0, _("%s digest initialization failed\n"),
427                     stream_to_ascii(digest_stream));
428             }
429
430             /* compute MD5 or SHA1 hash */
431             if (digest) {
432                char md[CRYPTO_DIGEST_MAX_SIZE];
433                uint32_t size;
434                
435                size = sizeof(md);
436                
437                if (digest_file(jcr, ff_pkt, digest) != 0) {
438                   jcr->JobErrors++;
439
440                } else if (crypto_digest_finalize(digest, (uint8_t *)md, &size)) {
441                   char *digest_buf;
442                   const char *digest_name;
443                   
444                   digest_buf = (char *)malloc(BASE64_SIZE(size));
445                   digest_name = crypto_digest_name(digest);
446                   
447                   bin_to_base64(digest_buf, BASE64_SIZE(size), md, size, true);
448
449                   if (strcmp(digest_buf, elt.chksum)) {
450                      Dmsg4(dbglvl,"%s      %s chksum  diff. Cat: %s File: %s\n",
451                            fname,
452                            digest_name,
453                            elt.chksum,
454                            digest_buf);
455                      stat = true;
456                   }
457                   
458                   free(digest_buf);
459                }
460                crypto_digest_free(digest);
461             }
462          }
463
464          break;
465       case ':':
466       case 'J':
467       case 'C':
468       default:
469          break;
470       }
471    }
472
473    /* In Incr/Diff accurate mode, we mark all files as seen
474     * When in Full+Base mode, we mark only if the file match exactly
475     */
476    if (jcr->getJobLevel() == L_FULL) {
477       if (!stat) {               
478          /* compute space saved with basefile */
479          jcr->base_size += ff_pkt->statp.st_size;
480          accurate_mark_file_as_seen(jcr, &elt);
481       }
482    } else {
483       accurate_mark_file_as_seen(jcr, &elt);
484    }
485
486 bail_out:
487    unstrip_path(ff_pkt);
488    return stat;
489 }
490
491 /* 
492  * TODO: use big buffer from htable
493  */
494 int accurate_cmd(JCR *jcr)
495 {
496    BSOCK *dir = jcr->dir_bsock;
497    int lstat_pos, chksum_pos;
498    int32_t nb;
499    uint32_t delta_seq;
500
501    if (job_canceled(jcr)) {
502       return true;
503    }
504    if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
505       dir->fsend(_("2991 Bad accurate command\n"));
506       return false;
507    }
508
509    jcr->accurate = true;
510
511    accurate_init(jcr, nb);
512
513    /*
514     * buffer = sizeof(CurFile) + dirmsg
515     * dirmsg = fname + \0 + lstat + \0 + checksum + \0 + delta_seq + \0
516     */
517    /* get current files */
518    while (dir->recv() >= 0) {
519       lstat_pos = strlen(dir->msg) + 1;
520       if (lstat_pos < dir->msglen) {
521          chksum_pos = lstat_pos + strlen(dir->msg + lstat_pos) + 1;
522
523          if (chksum_pos >= dir->msglen) {
524             chksum_pos = lstat_pos - 1;    /* tweak: no checksum, point to the last \0 */
525             delta_seq = 0;
526          } else {
527             delta_seq = str_to_int32(dir->msg + 
528                                      chksum_pos + 
529                                      strlen(dir->msg + chksum_pos) + 1);
530          }
531
532          accurate_add_file(jcr, dir->msglen, 
533                            dir->msg,               /* Path */
534                            dir->msg + lstat_pos,   /* LStat */
535                            dir->msg + chksum_pos,  /* CheckSum */
536                            delta_seq);             /* Delta Sequence */
537       }
538    }
539
540 #ifdef DEBUG
541    extern void *start_heap;
542
543    char b1[50], b2[50], b3[50], b4[50], b5[50];
544    Dmsg5(dbglvl," Heap: heap=%s smbytes=%s max_bytes=%s bufs=%s max_bufs=%s\n",
545          edit_uint64_with_commas((char *)sbrk(0)-(char *)start_heap, b1),
546          edit_uint64_with_commas(sm_bytes, b2),
547          edit_uint64_with_commas(sm_max_bytes, b3),
548          edit_uint64_with_commas(sm_buffers, b4),
549          edit_uint64_with_commas(sm_max_buffers, b5));
550 #endif
551
552    return true;
553 }