]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/filed/accurate.c
tweak comments
[bacula/bacula] / bacula / src / filed / accurate.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2009 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 two of the GNU 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 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    bool seen;
44 } CurFile;
45
46 bool accurate_mark_file_as_seen(JCR *jcr, char *fname)
47 {
48    if (!jcr->accurate || !jcr->file_list) {
49       return false;
50    }
51    /* TODO: just use elt->seen = 1 */
52    CurFile *temp = (CurFile *)jcr->file_list->lookup(fname);
53    if (temp) {
54       temp->seen = 1;              /* records are in memory */
55       Dmsg1(dbglvl, "marked <%s> as seen\n", fname);
56    } else {
57       Dmsg1(dbglvl, "<%s> not found to be marked as seen\n", fname);
58    }
59    return true;
60 }
61
62 static bool accurate_mark_file_as_seen(JCR *jcr, CurFile *elt)
63 {
64    /* TODO: just use elt->seen = 1 */
65    CurFile *temp = (CurFile *)jcr->file_list->lookup(elt->fname);
66    if (temp) {
67       temp->seen = 1;              /* records are in memory */
68    }
69    return true;
70 }
71
72 static bool accurate_lookup(JCR *jcr, char *fname, CurFile *ret)
73 {
74    bool found=false;
75    ret->seen = 0;
76
77    CurFile *temp = (CurFile *)jcr->file_list->lookup(fname);
78    if (temp) {
79       memcpy(ret, temp, sizeof(CurFile));
80       found=true;
81       Dmsg1(dbglvl, "lookup <%s> ok\n", fname);
82    }
83
84    return found;
85 }
86
87 static bool accurate_init(JCR *jcr, int nbfile)
88 {
89    CurFile *elt = NULL;
90    jcr->file_list = (htable *)malloc(sizeof(htable));
91    jcr->file_list->init(elt, &elt->link, nbfile);
92    return true;
93 }
94
95 static bool accurate_send_base_file_list(JCR *jcr)
96 {
97    CurFile *elt;
98    struct stat statc;
99    int32_t LinkFIc;
100    FF_PKT *ff_pkt;
101    int stream = STREAM_UNIX_ATTRIBUTES;
102
103    if (!jcr->accurate || jcr->get_JobLevel() != L_FULL) {
104       return true;
105    }
106
107    if (jcr->file_list == NULL) {
108       return true;
109    }
110
111    ff_pkt = init_find_files();
112    ff_pkt->type = FT_BASE;
113
114    foreach_htable(elt, jcr->file_list) {
115       if (elt->seen) {
116          Dmsg2(dbglvl, "base file fname=%s seen=%i\n", elt->fname, elt->seen);
117          /* TODO: skip the decode and use directly the lstat field */
118          decode_stat(elt->lstat, &statc, &LinkFIc); /* decode catalog stat */  
119          ff_pkt->fname = elt->fname;
120          ff_pkt->statp = statc;
121          encode_and_send_attributes(jcr, ff_pkt, stream);
122 //       free(elt->fname);
123       }
124    }
125
126    term_find_files(ff_pkt);
127    return true;
128 }
129
130
131 /* This function is called at the end of backup
132  * We walk over all hash disk element, and we check
133  * for elt.seen.
134  */
135 static bool accurate_send_deleted_list(JCR *jcr)
136 {
137    CurFile *elt;
138    struct stat statc;
139    int32_t LinkFIc;
140    FF_PKT *ff_pkt;
141    int stream = STREAM_UNIX_ATTRIBUTES;
142
143    if (!jcr->accurate) {
144       return true;
145    }
146
147    if (jcr->file_list == NULL) {
148       return true;
149    }
150
151    ff_pkt = init_find_files();
152    ff_pkt->type = FT_DELETED;
153
154    foreach_htable(elt, jcr->file_list) {
155       if (elt->seen || plugin_check_file(jcr, elt->fname)) {
156          continue;
157       }
158       Dmsg2(dbglvl, "deleted fname=%s seen=%i\n", elt->fname, elt->seen);
159       /* TODO: skip the decode and use directly the lstat field */
160       decode_stat(elt->lstat, &statc, &LinkFIc); /* decode catalog stat */
161       ff_pkt->fname = elt->fname;
162       ff_pkt->statp.st_mtime = statc.st_mtime;
163       ff_pkt->statp.st_ctime = statc.st_ctime;
164       encode_and_send_attributes(jcr, ff_pkt, stream);
165 //    free(elt->fname);
166    }
167
168    term_find_files(ff_pkt);
169    return true;
170 }
171
172 void accurate_free(JCR *jcr)
173 {
174    if (jcr->file_list) {
175       jcr->file_list->destroy();
176       free(jcr->file_list);
177       jcr->file_list = NULL;
178    }
179 }
180
181 /* Send the deleted or the base file list and cleanup  */
182 bool accurate_finish(JCR *jcr)
183 {
184    bool ret=true;
185    if (jcr->accurate) {
186       if (jcr->get_JobLevel() == L_FULL) {
187          ret = accurate_send_base_file_list(jcr);
188       } else {
189          ret = accurate_send_deleted_list(jcr);
190       }
191       
192       accurate_free(jcr);
193       if (jcr->get_JobLevel() == L_FULL) {
194          Dmsg1(0, "Space saved with Base jobs: %lld MB\n", 
195                jcr->base_size/(1024*1024));
196       }
197    }
198    return ret;
199 }
200
201 static bool accurate_add_file(JCR *jcr, uint32_t len, 
202                               char *fname, char *lstat, char *chksum)
203 {
204    bool ret = true;
205    CurFile elt;
206    elt.seen = 0;
207
208    CurFile *item;
209    /* we store CurFile, fname and ctime/mtime in the same chunk */
210    item = (CurFile *)jcr->file_list->hash_malloc(sizeof(CurFile)+len+3);
211    memcpy(item, &elt, sizeof(CurFile));
212
213    item->fname  = (char *)item+sizeof(CurFile);
214    strcpy(item->fname, fname);
215
216    item->lstat  = item->fname+strlen(item->fname)+1;
217    strcpy(item->lstat, lstat);
218
219    item->chksum = item->lstat+strlen(item->lstat)+1;
220    strcpy(item->chksum, chksum);
221
222    jcr->file_list->insert(item->fname, item); 
223
224    Dmsg3(dbglvl, "add fname=<%s> lstat=%s chksum=%s\n", fname, lstat, chksum);
225    return ret;
226 }
227
228 /*
229  * This function is called for each file seen in fileset.
230  * We check in file_list hash if fname have been backuped
231  * the last time. After we can compare Lstat field. 
232  * Full Lstat usage have been removed on 6612 
233  *
234  * Returns: true   if file has changed (must be backed up)
235  *          false  file not changed
236  */
237 bool accurate_check_file(JCR *jcr, FF_PKT *ff_pkt)
238 {
239    int digest_stream = STREAM_NONE;
240    DIGEST *digest = NULL;
241
242    struct stat statc;
243    int32_t LinkFIc;
244    bool stat = false;
245    char *fname;
246    CurFile elt;
247
248    if (!jcr->accurate) {
249       return true;
250    }
251
252    strip_path(ff_pkt);
253  
254    if (S_ISDIR(ff_pkt->statp.st_mode)) {
255       fname = ff_pkt->link;
256    } else {
257       fname = ff_pkt->fname;
258    } 
259
260    if (!accurate_lookup(jcr, fname, &elt)) {
261       Dmsg1(dbglvl, "accurate %s (not found)\n", fname);
262       stat = true;
263       goto bail_out;
264    }
265
266    if (elt.seen) { /* file has been seen ? */
267       Dmsg1(dbglvl, "accurate %s (already seen)\n", fname);
268       goto bail_out;
269    }
270
271    decode_stat(elt.lstat, &statc, &LinkFIc); /* decode catalog stat */
272
273    /*
274     * Loop over options supplied by user and verify the
275     * fields he requests.
276     */
277    for (char *p=ff_pkt->AccurateOpts; !stat && *p; p++) {
278       char ed1[30], ed2[30];
279       switch (*p) {
280       case 'i':                /* compare INODEs */
281          if (statc.st_ino != ff_pkt->statp.st_ino) {
282             Dmsg3(dbglvl-1, "%s      st_ino   differ. Cat: %s File: %s\n",
283                   fname,
284                   edit_uint64((uint64_t)statc.st_ino, ed1),
285                   edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
286             stat = true;
287          }
288          break;
289       case 'p':                /* permissions bits */
290          if (statc.st_mode != ff_pkt->statp.st_mode) {
291             Dmsg3(dbglvl-1, "%s     st_mode  differ. Cat: %x File: %x\n",
292                   fname,
293                   (uint32_t)statc.st_mode, (uint32_t)ff_pkt->statp.st_mode);
294             stat = true;
295          }
296          break;
297       case 'n':                /* number of links */
298          if (statc.st_nlink != ff_pkt->statp.st_nlink) {
299             Dmsg3(dbglvl-1, "%s      st_nlink differ. Cat: %d File: %d\n",
300                   fname,
301                   (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
302             stat = true;
303          }
304          break;
305       case 'u':                /* user id */
306          if (statc.st_uid != ff_pkt->statp.st_uid) {
307             Dmsg3(dbglvl-1, "%s      st_uid   differ. Cat: %u File: %u\n",
308                   fname,
309                   (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
310             stat = true;
311          }
312          break;
313       case 'g':                /* group id */
314          if (statc.st_gid != ff_pkt->statp.st_gid) {
315             Dmsg3(dbglvl-1, "%s      st_gid   differ. Cat: %u File: %u\n",
316                   fname,
317                   (uint32_t)statc.st_gid, (uint32_t)ff_pkt->statp.st_gid);
318             stat = true;
319          }
320          break;
321       case 's':                /* size */
322          if (statc.st_size != ff_pkt->statp.st_size) {
323             Dmsg3(dbglvl-1, "%s      st_size  differ. Cat: %s File: %s\n",
324                   fname,
325                   edit_uint64((uint64_t)statc.st_size, ed1),
326                   edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
327             stat = true;
328          }
329          break;
330       case 'a':                /* access time */
331          if (statc.st_atime != ff_pkt->statp.st_atime) {
332             Dmsg1(dbglvl-1, "%s      st_atime differs\n", fname);
333             stat = true;
334          }
335          break;
336       case 'm':                 /* modification time */
337          if (statc.st_mtime != ff_pkt->statp.st_mtime) {
338             Dmsg1(dbglvl-1, "%s      st_mtime differs\n", fname);
339             stat = true;
340          }
341          break;
342       case 'c':                /* ctime */
343          if (statc.st_ctime != ff_pkt->statp.st_ctime) {
344             Dmsg1(dbglvl-1, "%s      st_ctime differs\n", fname);
345             stat = true;
346          }
347          break;
348       case 'd':                /* file size decrease */
349          if (statc.st_size > ff_pkt->statp.st_size) {
350             Dmsg3(dbglvl-1, "%s      st_size  decrease. Cat: %s File: %s\n",
351                   fname,
352                   edit_uint64((uint64_t)statc.st_size, ed1),
353                   edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
354             stat = true;
355          }
356          break;
357
358       /* TODO: cleanup and factorise this function with verify.c */
359       case '5':                /* compare MD5 */
360       case '1':                 /* compare SHA1 */
361         /*
362           * The remainder of the function is all about getting the checksum.
363           * First we initialise, then we read files, other streams and Finder Info.
364           */
365          if (!stat && *elt.chksum && ff_pkt->type != FT_LNKSAVED && 
366              (S_ISREG(ff_pkt->statp.st_mode) && 
367               ff_pkt->flags & (FO_MD5|FO_SHA1|FO_SHA256|FO_SHA512))) 
368          {
369             /*
370              * Create our digest context. If this fails, the digest will be set to NULL
371              * and not used.
372              */
373             if (ff_pkt->flags & FO_MD5) {
374                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_MD5);
375                digest_stream = STREAM_MD5_DIGEST;
376                
377             } else if (ff_pkt->flags & FO_SHA1) {
378                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA1);
379                digest_stream = STREAM_SHA1_DIGEST;
380                
381             } else if (ff_pkt->flags & FO_SHA256) {
382                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA256);
383                digest_stream = STREAM_SHA256_DIGEST;
384                
385             } else if (ff_pkt->flags & FO_SHA512) {
386                digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA512);
387                digest_stream = STREAM_SHA512_DIGEST;
388             }
389             
390             /* Did digest initialization fail? */
391             if (digest_stream != STREAM_NONE && digest == NULL) {
392                Jmsg(jcr, M_WARNING, 0, _("%s digest initialization failed\n"),
393                     stream_to_ascii(digest_stream));
394             }
395
396             /* compute MD5 or SHA1 hash */
397             if (digest) {
398                char md[CRYPTO_DIGEST_MAX_SIZE];
399                uint32_t size;
400                
401                size = sizeof(md);
402                
403                if (digest_file(jcr, ff_pkt, digest) != 0) {
404                   jcr->JobErrors++;
405
406                } else if (crypto_digest_finalize(digest, (uint8_t *)md, &size)) {
407                   char *digest_buf;
408                   const char *digest_name;
409                   
410                   digest_buf = (char *)malloc(BASE64_SIZE(size));
411                   digest_name = crypto_digest_name(digest);
412                   
413                   bin_to_base64(digest_buf, BASE64_SIZE(size), md, size, true);
414
415                   if (strcmp(digest_buf, elt.chksum)) {
416                      Dmsg3(dbglvl-1, "%s      chksum  diff. Cat: %s File: %s\n",
417                            fname,
418                            elt.chksum,
419                            digest_buf);
420                      stat = true;
421                   }
422                   
423                   free(digest_buf);
424                }
425                crypto_digest_free(digest);
426             }
427          }
428
429          break;
430       case ':':
431       case 'C':
432       default:
433          break;
434       }
435    }
436
437    /* In Incr/Diff accurate mode, we mark all files as seen
438     * When in Full+Base mode, we mark only if the file match exactly
439     */
440    if (jcr->get_JobLevel() == L_FULL) {
441       if (!stat) {               
442          /* compute space saved with basefile */
443          jcr->base_size += ff_pkt->statp.st_size;
444          accurate_mark_file_as_seen(jcr, &elt);
445       }
446    } else {
447       accurate_mark_file_as_seen(jcr, &elt);
448    }
449
450 bail_out:
451    unstrip_path(ff_pkt);
452    return stat;
453 }
454
455 /* 
456  * TODO: use big buffer from htable
457  */
458 int accurate_cmd(JCR *jcr)
459 {
460    BSOCK *dir = jcr->dir_bsock;
461    int lstat_pos, chksum_pos;
462    int32_t nb;
463
464    if (job_canceled(jcr)) {
465       return true;
466    }
467    if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
468       dir->fsend(_("2991 Bad accurate command\n"));
469       return false;
470    }
471
472    jcr->accurate = true;
473
474    accurate_init(jcr, nb);
475
476    /*
477     * buffer = sizeof(CurFile) + dirmsg
478     * dirmsg = fname + \0 + lstat + \0 + checksum + \0
479     */
480    /* get current files */
481    while (dir->recv() >= 0) {
482       lstat_pos = strlen(dir->msg) + 1;
483       if (lstat_pos < dir->msglen) {
484          chksum_pos = lstat_pos + strlen(dir->msg + lstat_pos) + 1;
485
486          if (chksum_pos >= dir->msglen) {
487             chksum_pos = lstat_pos - 1;    /* tweak: no checksum, point to the last \0 */
488          } 
489
490          accurate_add_file(jcr, dir->msglen, 
491                            dir->msg,               /* Path */
492                            dir->msg + lstat_pos,   /* LStat */
493                            dir->msg + chksum_pos); /* CheckSum */
494       }
495    }
496
497 #ifdef DEBUG
498    extern void *start_heap;
499
500    char b1[50], b2[50], b3[50], b4[50], b5[50];
501    Dmsg5(dbglvl," Heap: heap=%s smbytes=%s max_bytes=%s bufs=%s max_bufs=%s\n",
502          edit_uint64_with_commas((char *)sbrk(0)-(char *)start_heap, b1),
503          edit_uint64_with_commas(sm_bytes, b2),
504          edit_uint64_with_commas(sm_max_bytes, b3),
505          edit_uint64_with_commas(sm_buffers, b4),
506          edit_uint64_with_commas(sm_max_buffers, b5));
507 #endif
508
509    return true;
510 }