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