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