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