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