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