2 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3 * Copyright (c) 1991-1998 University of Maryland at College Park
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.
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.
23 * Authors: the Amanda Development Team. Its members are listed in a
24 * file named AUTHORS, in the root directory of this distribution.
27 * $Id: calcsize.c,v 1.24.2.3.6.1.2.3 2005/02/09 17:56:52 martinea Exp $
29 * traverse directory tree to get backup size estimates
35 #define ROUND(n,x) ((x) + (n) - 1 - (((x) + (n) - 1) % (n)))
38 static unsigned long round_function(n, x)
41 unsigned long remainder = x % n;
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)))
50 #define FILETYPES (S_IFREG|S_IFLNK|S_IFDIR)
52 typedef struct name_s {
67 } dumpstats[MAXDUMPS];
69 time_t dumpdate[MAXDUMPS];
70 int dumplevel[MAXDUMPS];
73 void (*add_file_name) P((int, char *));
74 void (*add_file) P((int, struct stat *));
75 long (*final_size) P((int, char *));
78 int main P((int, char **));
79 void traverse_dirs P((char *, char *));
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 *));
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 *));
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 *));
94 sl_t *calc_load_file P((char *filename));
95 int calc_check_exclude P((char *filename));
97 int use_gtar_excl = 0;
98 sl_t *include_sl=NULL, *exclude_sl=NULL;
105 /* standalone test to ckeck wether the calculated file size is ok */
108 unsigned long dump_total=0, gtar_total=0;
113 for(fd = 3; fd < FD_SETSIZE; fd++) {
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).
123 set_pname("calcsize");
126 fprintf(stderr,"Usage: %s file[s]\n",argv[0]);
129 for(i=1; i<argc; i++) {
130 if(lstat(argv[i], &finfo) == -1) {
131 fprintf(stderr, "%s: %s\n", argv[i], strerror(errno));
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));
139 printf(" gtar dump\n");
140 printf("total %-9lu %-9lu\n",gtar_total,dump_total);
144 char *dirname=NULL, *amname=NULL, *filename=NULL;
146 unsigned long malloc_hist_1, malloc_size_1;
147 unsigned long malloc_hist_2, malloc_size_2;
149 for(fd = 3; fd < FD_SETSIZE; fd++) {
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).
159 set_pname("calcsize");
163 malloc_size_1 = malloc_inuse(&malloc_hist_1);
166 erroutput_type = (ERR_INTERACTIVE|ERR_SYSLOG);
169 argc--, argv++; /* skip program name */
171 /* need at least program, amname, and directory name */
174 error("Usage: %s [DUMP|GNUTAR%s] name dir [-X exclude-file] [-I include-file] [level date]*",
179 /* parse backup program name */
181 if(strcmp(*argv, "DUMP") == 0) {
182 #if !defined(DUMP) && !defined(XFSDUMP)
183 error("dump not available on this system");
186 add_file_name = add_file_name_dump;
187 add_file = add_file_dump;
188 final_size = final_size_dump;
191 else if(strcmp(*argv, "GNUTAR") == 0) {
193 error("gnutar not available on this system");
196 add_file_name = add_file_name_gnutar;
197 add_file = add_file_gnutar;
198 final_size = final_size_gnutar;
203 add_file_name = add_file_name_unknown;
204 add_file = add_file_unknown;
205 final_size = final_size_unknown;
209 /* the amanda name can be different from the directory name */
215 error("missing <name>");
217 /* the toplevel directory name to search from */
222 error("missing <dir>");
224 if ((argc > 1) && strcmp(*argv,"-X") == 0) {
227 if (!use_gtar_excl) {
228 error("exclusion specification not supported");
232 filename = stralloc(*argv);
233 if (access(filename, R_OK) != 0) {
234 fprintf(stderr,"Cannot open exclude file %s\n",filename);
237 exclude_sl = calc_load_file(filename);
245 if ((argc > 1) && strcmp(*argv,"-I") == 0) {
248 filename = stralloc(*argv);
249 if (access(filename, R_OK) != 0) {
250 fprintf(stderr,"Cannot open include file %s\n",filename);
253 include_sl = calc_load_file(filename);
260 /* the dump levels to calculate sizes for */
264 if(ndumps < MAXDUMPS) {
265 dumplevel[ndumps] = atoi(argv[0]);
266 dumpdate [ndumps] = (time_t) atol(argv[1]);
268 argc -= 2, argv += 2;
273 error("leftover arg \"%s\", expected <level> and <date>", *argv);
275 if(is_empty_sl(include_sl)) {
276 traverse_dirs(dirname,".");
279 sle_t *an_include = include_sl->first;
280 while(an_include != NULL) {
282 char *adirname = stralloc2(dirname, an_include->name+1);
283 traverse_dirs(adirname);
286 traverse_dirs(dirname, an_include->name);
287 an_include = an_include->next;
290 for(i = 0; i < ndumps; i++) {
294 lseek(1, (off_t)0, SEEK_END);
296 fprintf(stderr, "%s %d SIZE %ld\n",
297 amname, dumplevel[i], final_size(i, dirname));
300 amfunlock(1, "size");
303 malloc_size_2 = malloc_inuse(&malloc_hist_2);
305 if(malloc_size_1 != malloc_size_2) {
306 malloc_list(fileno(stderr), malloc_hist_1, malloc_hist_2);
314 * =========================================================================
317 #ifndef HAVE_BASENAME
323 if ( (cp = strrchr(file,'/')) )
329 void push_name P((char *str));
330 char *pop_name P((void));
332 void traverse_dirs(parent_dir, include)
339 char *dirname, *newname = NULL;
340 char *newbase = NULL;
341 dev_t parent_dev = 0;
345 int has_exclude = !is_empty_sl(exclude_sl) && use_gtar_excl;
347 char *aparent = vstralloc(parent_dir, "/", include, NULL);
349 if(parent_dir && stat(parent_dir, &finfo) != -1)
350 parent_dev = finfo.st_dev;
352 parent_len = strlen(parent_dir);
356 for(dirname = pop_name(); dirname; free(dirname), dirname = pop_name()) {
357 if(has_exclude && calc_check_exclude(dirname+parent_len+1)) {
360 if((d = opendir(dirname)) == NULL) {
366 if(l > 0 && dirname[l - 1] != '/') {
367 newbase = newstralloc2(newbase, dirname, "/");
369 newbase = newstralloc(newbase, dirname);
372 while((f = readdir(d)) != NULL) {
376 if(is_dot_or_dotdot(f->d_name)) {
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));
387 if(finfo.st_dev != parent_dev) {
392 is_symlink = ((finfo.st_mode & S_IFMT) == S_IFLNK);
394 is_dir = ((finfo.st_mode & S_IFMT) == S_IFDIR);
395 is_file = ((finfo.st_mode & S_IFMT) == S_IFREG);
397 if (!(is_file || is_dir || is_symlink)) {
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]) {
408 if(is_excluded == -1)
410 calc_check_exclude(newname+parent_len+1);
411 if(is_excluded == 1) {
420 if(has_exclude && calc_check_exclude(newname+parent_len+1))
430 if(closedir(d) == -1)
444 newp = alloc(sizeof(*newp));
445 newp->str = stralloc(str);
447 newp->next = name_stack;
453 Name *newp = name_stack;
456 if(!newp) return NULL;
458 name_stack = newp->next;
466 * =========================================================================
467 * Backup size calculations for DUMP program
469 * Given the system-dependent nature of dump, it's impossible to pin this
470 * down accurately. Luckily, that's not necessary.
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.
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.
481 void add_file_name_dump(level, name)
488 void add_file_dump(level, sp)
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;
497 long final_size_dump(level, topdir)
501 generic_fs_stats_t stats;
505 /* calculate the map sizes */
507 s = stralloc2(topdir, "/.");
508 if(get_fs_stats(s, &stats) == -1) {
509 error("statfs %s: %s", s, strerror(errno));
513 mapsize = (stats.files + 7) / 8; /* in bytes */
514 mapsize = (mapsize + 1023) / 1024; /* in kbytes */
516 /* the dump contains three maps plus the files */
518 return 3*mapsize + dumpstats[level].total_size;
522 * =========================================================================
523 * Backup size calculations for GNUTAR program
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.
530 * As with DUMP, we only need a reasonable estimate, not an exact figure.
532 void add_file_name_gnutar(level, name)
536 /* dumpstats[level].total_size_name += strlen(name) + 64;*/
537 dumpstats[level].total_size += 1;
540 void add_file_gnutar(level, sp)
544 /* the header takes one additional block */
545 dumpstats[level].total_size += ST_BLOCKS(*sp);
548 long final_size_gnutar(level, topdir)
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;
558 * =========================================================================
559 * Backup size calculations for unknown backup programs.
561 * Here we'll just add up the file sizes and output that.
564 void add_file_name_unknown(level, name)
571 void add_file_unknown(level, sp)
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);
580 long final_size_unknown(level, topdir)
584 /* divide by two to get kbytes, rounded up */
585 return (dumpstats[level].total_size + 1) / 2;
589 * =========================================================================
591 sl_t *calc_load_file(filename)
596 sl_t *sl_list = new_sl();
598 FILE *file = fopen(filename, "r");
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);
610 int calc_check_exclude(filename)
614 if(is_empty_sl(exclude_sl)) return 0;
616 an_exclude=exclude_sl->first;
617 while(an_exclude != NULL) {
618 if(match_tar(an_exclude->name, filename)) {
621 an_exclude=an_exclude->next;