Imported Upstream version 3.3.3
[debian/amanda] / client-src / calcsize.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998 University of Maryland at College Park
4  * Copyright (c) 2007-2012 Zmanda, Inc.  All Rights Reserved.
5  * All Rights Reserved.
6  *
7  * Permission to use, copy, modify, distribute, and sell this software and its
8  * documentation for any purpose is hereby granted without fee, provided that
9  * the above copyright notice appear in all copies and that both that
10  * copyright notice and this permission notice appear in supporting
11  * documentation, and that the name of U.M. not be used in advertising or
12  * publicity pertaining to distribution of the software without specific,
13  * written prior permission.  U.M. makes no representations about the
14  * suitability of this software for any purpose.  It is provided "as is"
15  * without express or implied warranty.
16  *
17  * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
19  * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
21  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
22  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23  *
24  * Authors: the Amanda Development Team.  Its members are listed in a
25  * file named AUTHORS, in the root directory of this distribution.
26  */
27 /* 
28  * $Id: calcsize.c,v 1.44 2006/07/25 18:27:56 martinea Exp $
29  *
30  * traverse directory tree to get backup size estimates
31  *
32  * argv[0] is the calcsize program name
33  * argv[1] is the config name or NOCONFIG
34  */
35 #include "amanda.h"
36 #include "match.h"
37 #include "conffile.h"
38 #include "fsusage.h"
39 #include "am_sl.h"
40 #include "util.h"
41
42 #define ROUND(n,x)      ((x) + (n) - 1 - (((x) + (n) - 1) % (n)))
43
44 /*
45 static off_t
46 round_function(n, x)
47     off_t       n,
48     off_t       x)
49 {
50   unsigned long remainder = x % n;
51   if (remainder)
52     x += n - remainder;
53   return x;
54 }
55 */
56
57 #define ST_BLOCKS(s)                                                           \
58             (((((off_t)(s).st_blocks * (off_t)512) <= (s).st_size)) ?          \
59               ((off_t)(s).st_blocks + (off_t)1) :                              \
60               ((s).st_size / (off_t)512 +                                      \
61                 (off_t)((((s).st_size % (off_t)512) != (off_t)0) ?             \
62                 (off_t)1 : (off_t)0)))
63
64 #define FILETYPES       (S_IFREG|S_IFLNK|S_IFDIR)
65
66 typedef struct name_s {
67     struct name_s *next;
68     char *str;
69 } Name;
70
71 Name *name_stack;
72
73 #define MAXDUMPS 10
74
75 struct {
76     int max_inode;
77     int total_dirs;
78     int total_files;
79     off_t total_size;
80     off_t total_size_name;
81 } dumpstats[MAXDUMPS];
82
83 time_t dumpdate[MAXDUMPS];
84 int  dumplevel[MAXDUMPS];
85 int ndumps;
86
87 void (*add_file_name)(int, char *);
88 void (*add_file)(int, struct stat *);
89 off_t (*final_size)(int, char *);
90
91
92 int main(int, char **);
93 void traverse_dirs(char *, char *);
94
95
96 void add_file_name_dump(int, char *);
97 void add_file_dump(int, struct stat *);
98 off_t final_size_dump(int, char *);
99
100 void add_file_name_star(int, char *);
101 void add_file_star(int, struct stat *);
102 off_t final_size_star(int, char *);
103
104 void add_file_name_gnutar(int, char *);
105 void add_file_gnutar(int, struct stat *);
106 off_t final_size_gnutar(int, char *);
107
108 void add_file_name_unknown(int, char *);
109 void add_file_unknown(int, struct stat *);
110 off_t final_size_unknown(int, char *);
111
112 am_sl_t *calc_load_file(char *filename);
113 int calc_check_exclude(char *filename);
114
115 int use_star_excl = 0;
116 int use_gtar_excl = 0;
117 am_sl_t *include_sl=NULL, *exclude_sl=NULL;
118
119 int
120 main(
121     int         argc,
122     char **     argv)
123 {
124 #ifdef TEST
125 /* standalone test to ckeck wether the calculated file size is ok */
126     struct stat finfo;
127     int i;
128     off_t dump_total = (off_t)0;
129     off_t gtar_total = (off_t)0;
130     char *d;
131     int l, w;
132
133     /*
134      * Configure program for internationalization:
135      *   1) Only set the message locale for now.
136      *   2) Set textdomain for all amanda related programs to "amanda"
137      *      We don't want to be forced to support dozens of message catalogs.
138      */  
139     setlocale(LC_MESSAGES, "C");
140     textdomain("amanda"); 
141
142     safe_fd(-1, 0);
143
144     set_pname("calcsize");
145
146     dbopen(NULL);
147     config_init(CONFIG_INIT_CLIENT, NULL);
148
149     /* Don't die when child closes pipe */
150     signal(SIGPIPE, SIG_IGN);
151
152     if (argc < 2) {
153         g_fprintf(stderr,_("Usage: %s file[s]\n"),argv[0]);
154         return 1;
155     }
156     for(i=1; i<argc; i++) {
157         if(lstat(argv[i], &finfo) == -1) {
158             g_fprintf(stderr, "%s: %s\n", argv[i], strerror(errno));
159             continue;
160         }
161         g_printf("%s: st_size=%lu", argv[i],(unsigned long)finfo.st_size);
162         g_printf(": blocks=%llu\n", ST_BLOCKS(finfo));
163         dump_total += (ST_BLOCKS(finfo) + (off_t)1) / (off_t)2 + (off_t)1;
164         gtar_total += ROUND(4,(ST_BLOCKS(finfo) + (off_t)1));
165     }
166     g_printf("           gtar           dump\n");
167     g_printf("total      %-9lu         %-9lu\n",gtar_total,dump_total);
168     return 0;
169 #else
170     int i;
171     char *dirname=NULL;
172     char *amname=NULL, *qamname=NULL;
173     char *filename=NULL, *qfilename = NULL;
174
175     if (argc > 1 && argv && argv[1] && g_str_equal(argv[1], "--version")) {
176         printf("calcsize-%s\n", VERSION);
177         return (0);
178     }
179
180     safe_fd(-1, 0);
181     safe_cd();
182
183     set_pname("calcsize");
184
185     dbopen(DBG_SUBDIR_CLIENT);
186     config_init(CONFIG_INIT_CLIENT, NULL);
187     dbprintf(_("version %s\n"), VERSION);
188
189     /* drop root privileges; we'll regain them for the required operations */
190 #ifdef WANT_SETUID_CLIENT
191     check_running_as(RUNNING_AS_CLIENT_LOGIN | RUNNING_AS_UID_ONLY);
192     if (!set_root_privs(0)) {
193         error(_("calcsize must be run setuid root"));
194     }
195 #else
196     check_running_as(RUNNING_AS_CLIENT_LOGIN);
197 #endif
198
199     argc--, argv++;     /* skip program name */
200
201     /* need at least program, amname, and directory name */
202
203     if(argc < 4) {
204         error(_("Usage: %s config [DUMP|STAR|GNUTAR] name dir [-X exclude-file] [-I include-file] [level date]*"),
205               get_pname());
206         /*NOTREACHED*/
207     }
208
209     dbprintf(_("config: %s\n"), *argv);
210     if (strcmp(*argv, "NOCONFIG") != 0) {
211         dbrename(*argv, DBG_SUBDIR_CLIENT);
212     }
213     argc--;
214     argv++;
215
216     /* parse backup program name */
217
218     if(strcmp(*argv, "DUMP") == 0) {
219 #if !defined(DUMP) && !defined(XFSDUMP)
220         error("dump not available on this system");
221         /*NOTREACHED*/
222 #else
223         add_file_name = add_file_name_dump;
224         add_file = add_file_dump;
225         final_size = final_size_dump;
226 #endif
227     }
228     else if(strcmp(*argv, "GNUTAR") == 0) {
229 #ifndef GNUTAR
230         error("gnutar not available on this system");
231         /*NOTREACHED*/
232 #else
233         add_file_name = add_file_name_gnutar;
234         add_file = add_file_gnutar;
235         final_size = final_size_gnutar;
236         use_gtar_excl++;
237 #endif
238     }
239     else {
240         add_file_name = add_file_name_unknown;
241         add_file = add_file_unknown;
242         final_size = final_size_unknown;
243     }
244     argc--, argv++;
245
246     /* the amanda name can be different from the directory name */
247
248     if (argc > 0) {
249         amname = *argv;
250         qamname = quote_string(amname);
251         argc--, argv++;
252     } else {
253         error("missing <name>");
254         /*NOTREACHED*/
255     }
256
257     /* the toplevel directory name to search from */
258     if (argc > 0) {
259         dirname = *argv;
260         argc--, argv++;
261     } else {
262         error("missing <dir>");
263         /*NOTREACHED*/
264     }
265
266     if ((argc > 1) && strcmp(*argv,"-X") == 0) {
267         argv++;
268
269         if (!(use_gtar_excl || use_star_excl)) {
270           error("exclusion specification not supported");
271           /*NOTREACHED*/
272         }
273         
274         filename = stralloc(*argv);
275         qfilename = quote_string(filename);
276         if (access(filename, R_OK) != 0) {
277             g_fprintf(stderr,"Cannot open exclude file %s\n", qfilename);
278             use_gtar_excl = use_star_excl = 0;
279         } else {
280             exclude_sl = calc_load_file(filename);
281             if (!exclude_sl) {
282                 g_fprintf(stderr,"Cannot open exclude file %s: %s\n", qfilename,
283                         strerror(errno));
284                 use_gtar_excl = use_star_excl = 0;
285             }
286         }
287         amfree(qfilename);
288         amfree(filename);
289         argc -= 2;
290         argv++;
291     } else {
292         use_gtar_excl = use_star_excl = 0;
293     }
294
295     if ((argc > 1) && strcmp(*argv,"-I") == 0) {
296         argv++;
297         
298         filename = stralloc(*argv);
299         qfilename = quote_string(filename);
300         if (access(filename, R_OK) != 0) {
301             g_fprintf(stderr,"Cannot open include file %s\n", qfilename);
302             use_gtar_excl = use_star_excl = 0;
303         } else {
304             include_sl = calc_load_file(filename);
305             if (!include_sl) {
306                 g_fprintf(stderr,"Cannot open include file %s: %s\n", qfilename,
307                         strerror(errno));
308                 use_gtar_excl = use_star_excl = 0;
309             }
310         }
311         amfree(qfilename);
312         amfree(filename);
313         argc -= 2;
314         argv++;
315     }
316
317     /* the dump levels to calculate sizes for */
318
319     ndumps = 0;
320     while(argc >= 2) {
321         if(ndumps < MAXDUMPS) {
322             dumplevel[ndumps] = atoi(argv[0]);
323             dumpdate [ndumps] = (time_t) atol(argv[1]);
324             ndumps++;
325             argc -= 2, argv += 2;
326         }
327     }
328
329     if(argc) {
330         error("leftover arg \"%s\", expected <level> and <date>", *argv);
331         /*NOTREACHED*/
332     }
333
334     if(is_empty_sl(include_sl)) {
335         traverse_dirs(dirname,".");
336     }
337     else {
338         sle_t *an_include = include_sl->first;
339         while(an_include != NULL) {
340 /*
341             char *adirname = stralloc2(dirname, an_include->name+1);
342             traverse_dirs(adirname);
343             amfree(adirname);
344 */
345             traverse_dirs(dirname, an_include->name);
346             an_include = an_include->next;
347         }
348     }
349     for(i = 0; i < ndumps; i++) {
350
351         amflock(1, "size");
352
353         dbprintf("calcsize: %s %d SIZE %lld\n",
354                qamname, dumplevel[i],
355                (long long)final_size(i, dirname));
356         g_fprintf(stderr, "%s %d SIZE %lld\n",
357                qamname, dumplevel[i],
358                (long long)final_size(i, dirname));
359         fflush(stderr);
360
361         amfunlock(1, "size");
362     }
363     amfree(qamname);
364
365     return 0;
366 #endif
367 }
368
369 /*
370  * =========================================================================
371  */
372
373 #if !defined(HAVE_BASENAME) && defined(BUILTIN_EXCLUDE_SUPPORT)
374
375 static char *
376 basename(
377     char *      file)
378 {
379     char *cp;
380
381     if ( (cp = strrchr(file,'/')) )
382         return cp+1;
383     return file;
384 }
385 #endif
386
387 void push_name(char *str);
388 char *pop_name(void);
389
390 void
391 traverse_dirs(
392     char *      parent_dir,
393     char *      include)
394 {
395     DIR *d;
396     struct dirent *f;
397     struct stat finfo;
398     char *dirname, *newname = NULL;
399     char *newbase = NULL;
400     dev_t parent_dev = (dev_t)0;
401     int i;
402     size_t l;
403     size_t parent_len;
404     int has_exclude;
405     char *aparent;
406
407     if(parent_dir == NULL || include == NULL)
408         return;
409
410     has_exclude = !is_empty_sl(exclude_sl) && (use_gtar_excl || use_star_excl);
411     aparent = vstralloc(parent_dir, "/", include, NULL);
412
413     /* We (may) need root privs for the *stat() calls here. */
414     set_root_privs(1);
415     if(stat(parent_dir, &finfo) != -1)
416         parent_dev = finfo.st_dev;
417
418     parent_len = strlen(parent_dir);
419
420     push_name(aparent);
421
422     for(; (dirname = pop_name()) != NULL; free(dirname)) {
423         if(has_exclude && calc_check_exclude(dirname+parent_len+1)) {
424             continue;
425         }
426         if((d = opendir(dirname)) == NULL) {
427             perror(dirname);
428             continue;
429         }
430
431         l = strlen(dirname);
432         if(l > 0 && dirname[l - 1] != '/') {
433             newbase = newstralloc2(newbase, dirname, "/");
434         } else {
435             newbase = newstralloc(newbase, dirname);
436         }
437
438         while((f = readdir(d)) != NULL) {
439             int is_symlink = 0;
440             int is_dir;
441             int is_file;
442             if(is_dot_or_dotdot(f->d_name)) {
443                 continue;
444             }
445
446             newname = newstralloc2(newname, newbase, f->d_name);
447             if(lstat(newname, &finfo) == -1) {
448                 g_fprintf(stderr, "%s/%s: %s\n",
449                         dirname, f->d_name, strerror(errno));
450                 continue;
451             }
452
453             if(finfo.st_dev != parent_dev)
454                 continue;
455
456 #ifdef S_IFLNK
457             is_symlink = ((finfo.st_mode & S_IFMT) == S_IFLNK);
458 #endif
459             is_dir = ((finfo.st_mode & S_IFMT) == S_IFDIR);
460             is_file = ((finfo.st_mode & S_IFMT) == S_IFREG);
461
462             if (!(is_file || is_dir || is_symlink)) {
463                 continue;
464             }
465
466             {
467                 int is_excluded = -1;
468                 for(i = 0; i < ndumps; i++) {
469                     add_file_name(i, newname);
470                     if(is_file && (time_t)finfo.st_ctime >= dumpdate[i]) {
471
472                         if(has_exclude) {
473                             if(is_excluded == -1)
474                                 is_excluded =
475                                        calc_check_exclude(newname+parent_len+1);
476                             if(is_excluded == 1) {
477                                 i = ndumps;
478                                 continue;
479                             }
480                         }
481                         add_file(i, &finfo);
482                     }
483                 }
484                 if(is_dir) {
485                     if(has_exclude && calc_check_exclude(newname+parent_len+1))
486                         continue;
487                     push_name(newname);
488                 }
489             }
490         }
491
492 #ifdef CLOSEDIR_VOID
493         closedir(d);
494 #else
495         if(closedir(d) == -1)
496             perror(dirname);
497 #endif
498     }
499
500     /* drop root privs -- we're done with the permission-sensitive calls */
501     set_root_privs(0);
502
503     amfree(newbase);
504     amfree(newname);
505     amfree(aparent);
506 }
507
508 void
509 push_name(
510     char *      str)
511 {
512     Name *newp;
513
514     newp = alloc(SIZEOF(*newp));
515     newp->str = stralloc(str);
516
517     newp->next = name_stack;
518     name_stack = newp;
519 }
520
521 char *
522 pop_name(void)
523 {
524     Name *newp = name_stack;
525     char *str;
526
527     if(!newp) return NULL;
528
529     name_stack = newp->next;
530     str = newp->str;
531     amfree(newp);
532     return str;
533 }
534
535
536 /*
537  * =========================================================================
538  * Backup size calculations for DUMP program
539  *
540  * Given the system-dependent nature of dump, it's impossible to pin this
541  * down accurately.  Luckily, that's not necessary.
542  *
543  * Dump rounds each file up to TP_BSIZE bytes, which is 1k in the BSD dump,
544  * others are unknown.  In addition, dump stores three bitmaps at the
545  * beginning of the dump: a used inode map, a dumped dir map, and a dumped
546  * inode map.  These are sized by the number of inodes in the filesystem.
547  *
548  * We don't take into account the complexities of BSD dump's indirect block
549  * requirements for files with holes, nor the dumping of directories that
550  * are not themselves modified.
551  */
552 void
553 add_file_name_dump(
554     int         level,
555     char *      name)
556 {
557     (void)level;        /* Quiet unused parameter warning */
558     (void)name;         /* Quiet unused parameter warning */
559
560     return;
561 }
562
563 void
564 add_file_dump(
565     int                 level,
566     struct stat *       sp)
567 {
568     /* keep the size in kbytes, rounded up, plus a 1k header block */
569     if((sp->st_mode & S_IFMT) == S_IFREG || (sp->st_mode & S_IFMT) == S_IFDIR)
570         dumpstats[level].total_size +=
571                         (ST_BLOCKS(*sp) + (off_t)1) / (off_t)2 + (off_t)1;
572 }
573
574 off_t
575 final_size_dump(
576     int         level,
577     char *      topdir)
578 {
579     struct fs_usage fsusage;
580     off_t mapsize;
581     char *s;
582
583     /* calculate the map sizes */
584
585     s = stralloc2(topdir, "/.");
586     if(get_fs_usage(s, NULL, &fsusage) == -1) {
587         error("statfs %s: %s", s, strerror(errno));
588         /*NOTREACHED*/
589     }
590     amfree(s);
591
592     mapsize = (fsusage.fsu_files + (off_t)7) / (off_t)8;    /* in bytes */
593     mapsize = (mapsize + (off_t)1023) / (off_t)1024;  /* in kbytes */
594
595     /* the dump contains three maps plus the files */
596
597     return (mapsize * (off_t)3) + dumpstats[level].total_size;
598 }
599
600 /*
601  * =========================================================================
602  * Backup size calculations for GNUTAR program
603  *
604  * Gnutar's basic blocksize is 512 bytes.  Each file is rounded up to that
605  * size, plus one header block.  Gnutar stores directories' file lists in
606  * incremental dumps - we'll pick up size of the modified dirs here.  These
607  * will be larger than a simple filelist of their contents, but that's ok.
608  *
609  * As with DUMP, we only need a reasonable estimate, not an exact figure.
610  */
611 void
612 add_file_name_gnutar(
613     int         level,
614     char *      name)
615 {
616     (void)name; /* Quiet unused parameter warning */
617
618 /*  dumpstats[level].total_size_name += strlen(name) + 64;*/
619     dumpstats[level].total_size += (off_t)1;
620 }
621
622 void
623 add_file_gnutar(
624     int                 level,
625     struct stat *       sp)
626 {
627     /* the header takes one additional block */
628     dumpstats[level].total_size += ST_BLOCKS(*sp);
629 }
630
631 off_t
632 final_size_gnutar(
633     int         level,
634     char *      topdir)
635 {
636     (void)topdir;       /* Quiet unused parameter warning */
637
638     /* divide by two to get kbytes, rounded up */
639     /* + 4 blocks for security */
640     return (dumpstats[level].total_size + (off_t)5 +
641                 (dumpstats[level].total_size_name/(off_t)512)) / (off_t)2;
642 }
643
644 /*
645  * =========================================================================
646  * Backup size calculations for unknown backup programs.
647  *
648  * Here we'll just add up the file sizes and output that.
649  */
650
651 void
652 add_file_name_unknown(
653     int         level,
654     char *      name)
655 {
656     (void)level;        /* Quiet unused parameter warning */
657     (void)name;         /* Quiet unused parameter warning */
658
659     return;
660 }
661
662 void
663 add_file_unknown(
664     int                 level,
665     struct stat *       sp)
666 {
667     /* just add up the block counts */
668     if((sp->st_mode & S_IFMT) == S_IFREG || (sp->st_mode & S_IFMT) == S_IFDIR)
669         dumpstats[level].total_size += ST_BLOCKS(*sp);
670 }
671
672 off_t
673 final_size_unknown(
674     int         level,
675     char *      topdir)
676 {
677     (void)topdir;       /* Quiet unused parameter warning */
678
679     /* divide by two to get kbytes, rounded up */
680     return (dumpstats[level].total_size + (off_t)1) / (off_t)2;
681 }
682
683 /*
684  * =========================================================================
685  */
686 am_sl_t *
687 calc_load_file(
688     char *      filename)
689 {
690     char pattern[1025];
691
692     am_sl_t *sl_list;
693
694     FILE *file = fopen(filename, "r");
695
696     if (!file) {
697         return NULL;
698     }
699
700     sl_list = new_sl();
701
702     while(fgets(pattern, 1025, file)) {
703         if(strlen(pattern)>0 && pattern[strlen(pattern)-1] == '\n')
704             pattern[strlen(pattern)-1] = '\0';
705         sl_list = append_sl(sl_list, pattern);
706     }  
707     fclose(file);
708
709     return sl_list;
710 }
711
712 int
713 calc_check_exclude(
714     char *      filename)
715 {
716     sle_t *an_exclude;
717     if(is_empty_sl(exclude_sl)) return 0;
718
719     an_exclude=exclude_sl->first;
720     while(an_exclude != NULL) {
721         if(match_tar(an_exclude->name, filename)) {
722             return 1;
723         }
724         an_exclude=an_exclude->next;
725     }
726     return 0;
727 }