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