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