2 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3 * Copyright (c) 1991-1998 University of Maryland at College Park
4 * Copyright (c) 2007-2012 Zmanda, Inc. All Rights Reserved.
7 * Permission to use, copy, modify, distribute, and sell this software and its
8 * documentation for any purpose is hereby granted without fee, provided that
9 * the above copyright notice appear in all copies and that both that
10 * copyright notice and this permission notice appear in supporting
11 * documentation, and that the name of U.M. not be used in advertising or
12 * publicity pertaining to distribution of the software without specific,
13 * written prior permission. U.M. makes no representations about the
14 * suitability of this software for any purpose. It is provided "as is"
15 * without express or implied warranty.
17 * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
19 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
21 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
22 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * Authors: the Amanda Development Team. Its members are listed in a
25 * file named AUTHORS, in the root directory of this distribution.
28 * $Id: calcsize.c,v 1.44 2006/07/25 18:27:56 martinea Exp $
30 * traverse directory tree to get backup size estimates
32 * argv[0] is the calcsize program name
33 * argv[1] is the config name or NOCONFIG
42 #define ROUND(n,x) ((x) + (n) - 1 - (((x) + (n) - 1) % (n)))
50 unsigned long remainder = x % n;
57 #define ST_BLOCKS(s) \
58 (((((off_t)(s).st_blocks * (off_t)512) <= (s).st_size)) ? \
59 ((off_t)(s).st_blocks + (off_t)1) : \
60 ((s).st_size / (off_t)512 + \
61 (off_t)((((s).st_size % (off_t)512) != (off_t)0) ? \
62 (off_t)1 : (off_t)0)))
64 #define FILETYPES (S_IFREG|S_IFLNK|S_IFDIR)
66 typedef struct name_s {
80 off_t total_size_name;
81 } dumpstats[MAXDUMPS];
83 time_t dumpdate[MAXDUMPS];
84 int dumplevel[MAXDUMPS];
87 void (*add_file_name)(int, char *);
88 void (*add_file)(int, struct stat *);
89 off_t (*final_size)(int, char *);
92 int main(int, char **);
93 void traverse_dirs(char *, char *);
96 void add_file_name_dump(int, char *);
97 void add_file_dump(int, struct stat *);
98 off_t final_size_dump(int, char *);
100 void add_file_name_star(int, char *);
101 void add_file_star(int, struct stat *);
102 off_t final_size_star(int, char *);
104 void add_file_name_gnutar(int, char *);
105 void add_file_gnutar(int, struct stat *);
106 off_t final_size_gnutar(int, char *);
108 void add_file_name_unknown(int, char *);
109 void add_file_unknown(int, struct stat *);
110 off_t final_size_unknown(int, char *);
112 am_sl_t *calc_load_file(char *filename);
113 int calc_check_exclude(char *filename);
115 int use_star_excl = 0;
116 int use_gtar_excl = 0;
117 am_sl_t *include_sl=NULL, *exclude_sl=NULL;
125 /* standalone test to ckeck wether the calculated file size is ok */
128 off_t dump_total = (off_t)0;
129 off_t gtar_total = (off_t)0;
134 * Configure program for internationalization:
135 * 1) Only set the message locale for now.
136 * 2) Set textdomain for all amanda related programs to "amanda"
137 * We don't want to be forced to support dozens of message catalogs.
139 setlocale(LC_MESSAGES, "C");
140 textdomain("amanda");
144 set_pname("calcsize");
147 config_init(CONFIG_INIT_CLIENT, NULL);
149 /* Don't die when child closes pipe */
150 signal(SIGPIPE, SIG_IGN);
153 g_fprintf(stderr,_("Usage: %s file[s]\n"),argv[0]);
156 for(i=1; i<argc; i++) {
157 if(lstat(argv[i], &finfo) == -1) {
158 g_fprintf(stderr, "%s: %s\n", argv[i], strerror(errno));
161 g_printf("%s: st_size=%lu", argv[i],(unsigned long)finfo.st_size);
162 g_printf(": blocks=%llu\n", ST_BLOCKS(finfo));
163 dump_total += (ST_BLOCKS(finfo) + (off_t)1) / (off_t)2 + (off_t)1;
164 gtar_total += ROUND(4,(ST_BLOCKS(finfo) + (off_t)1));
166 g_printf(" gtar dump\n");
167 g_printf("total %-9lu %-9lu\n",gtar_total,dump_total);
172 char *amname=NULL, *qamname=NULL;
173 char *filename=NULL, *qfilename = NULL;
175 if (argc > 1 && argv && argv[1] && g_str_equal(argv[1], "--version")) {
176 printf("calcsize-%s\n", VERSION);
183 set_pname("calcsize");
185 dbopen(DBG_SUBDIR_CLIENT);
186 config_init(CONFIG_INIT_CLIENT, NULL);
187 dbprintf(_("version %s\n"), VERSION);
189 /* drop root privileges; we'll regain them for the required operations */
190 #ifdef WANT_SETUID_CLIENT
191 check_running_as(RUNNING_AS_CLIENT_LOGIN | RUNNING_AS_UID_ONLY);
192 if (!set_root_privs(0)) {
193 error(_("calcsize must be run setuid root"));
196 check_running_as(RUNNING_AS_CLIENT_LOGIN);
199 argc--, argv++; /* skip program name */
201 /* need at least program, amname, and directory name */
204 error(_("Usage: %s config [DUMP|STAR|GNUTAR] name dir [-X exclude-file] [-I include-file] [level date]*"),
209 dbprintf(_("config: %s\n"), *argv);
210 if (strcmp(*argv, "NOCONFIG") != 0) {
211 dbrename(*argv, DBG_SUBDIR_CLIENT);
216 /* parse backup program name */
218 if(strcmp(*argv, "DUMP") == 0) {
219 #if !defined(DUMP) && !defined(XFSDUMP)
220 error("dump not available on this system");
223 add_file_name = add_file_name_dump;
224 add_file = add_file_dump;
225 final_size = final_size_dump;
228 else if(strcmp(*argv, "GNUTAR") == 0) {
230 error("gnutar not available on this system");
233 add_file_name = add_file_name_gnutar;
234 add_file = add_file_gnutar;
235 final_size = final_size_gnutar;
240 add_file_name = add_file_name_unknown;
241 add_file = add_file_unknown;
242 final_size = final_size_unknown;
246 /* the amanda name can be different from the directory name */
250 qamname = quote_string(amname);
253 error("missing <name>");
257 /* the toplevel directory name to search from */
262 error("missing <dir>");
266 if ((argc > 1) && strcmp(*argv,"-X") == 0) {
269 if (!(use_gtar_excl || use_star_excl)) {
270 error("exclusion specification not supported");
274 filename = stralloc(*argv);
275 qfilename = quote_string(filename);
276 if (access(filename, R_OK) != 0) {
277 g_fprintf(stderr,"Cannot open exclude file %s\n", qfilename);
278 use_gtar_excl = use_star_excl = 0;
280 exclude_sl = calc_load_file(filename);
282 g_fprintf(stderr,"Cannot open exclude file %s: %s\n", qfilename,
284 use_gtar_excl = use_star_excl = 0;
292 use_gtar_excl = use_star_excl = 0;
295 if ((argc > 1) && strcmp(*argv,"-I") == 0) {
298 filename = stralloc(*argv);
299 qfilename = quote_string(filename);
300 if (access(filename, R_OK) != 0) {
301 g_fprintf(stderr,"Cannot open include file %s\n", qfilename);
302 use_gtar_excl = use_star_excl = 0;
304 include_sl = calc_load_file(filename);
306 g_fprintf(stderr,"Cannot open include file %s: %s\n", qfilename,
308 use_gtar_excl = use_star_excl = 0;
317 /* the dump levels to calculate sizes for */
321 if(ndumps < MAXDUMPS) {
322 dumplevel[ndumps] = atoi(argv[0]);
323 dumpdate [ndumps] = (time_t) atol(argv[1]);
325 argc -= 2, argv += 2;
330 error("leftover arg \"%s\", expected <level> and <date>", *argv);
334 if(is_empty_sl(include_sl)) {
335 traverse_dirs(dirname,".");
338 sle_t *an_include = include_sl->first;
339 while(an_include != NULL) {
341 char *adirname = stralloc2(dirname, an_include->name+1);
342 traverse_dirs(adirname);
345 traverse_dirs(dirname, an_include->name);
346 an_include = an_include->next;
349 for(i = 0; i < ndumps; i++) {
353 dbprintf("calcsize: %s %d SIZE %lld\n",
354 qamname, dumplevel[i],
355 (long long)final_size(i, dirname));
356 g_fprintf(stderr, "%s %d SIZE %lld\n",
357 qamname, dumplevel[i],
358 (long long)final_size(i, dirname));
361 amfunlock(1, "size");
370 * =========================================================================
373 #if !defined(HAVE_BASENAME) && defined(BUILTIN_EXCLUDE_SUPPORT)
381 if ( (cp = strrchr(file,'/')) )
387 void push_name(char *str);
388 char *pop_name(void);
398 char *dirname, *newname = NULL;
399 char *newbase = NULL;
400 dev_t parent_dev = (dev_t)0;
407 if(parent_dir == NULL || include == NULL)
410 has_exclude = !is_empty_sl(exclude_sl) && (use_gtar_excl || use_star_excl);
411 aparent = vstralloc(parent_dir, "/", include, NULL);
413 /* We (may) need root privs for the *stat() calls here. */
415 if(stat(parent_dir, &finfo) != -1)
416 parent_dev = finfo.st_dev;
418 parent_len = strlen(parent_dir);
422 for(; (dirname = pop_name()) != NULL; free(dirname)) {
423 if(has_exclude && calc_check_exclude(dirname+parent_len+1)) {
426 if((d = opendir(dirname)) == NULL) {
432 if(l > 0 && dirname[l - 1] != '/') {
433 newbase = newstralloc2(newbase, dirname, "/");
435 newbase = newstralloc(newbase, dirname);
438 while((f = readdir(d)) != NULL) {
442 if(is_dot_or_dotdot(f->d_name)) {
446 newname = newstralloc2(newname, newbase, f->d_name);
447 if(lstat(newname, &finfo) == -1) {
448 g_fprintf(stderr, "%s/%s: %s\n",
449 dirname, f->d_name, strerror(errno));
453 if(finfo.st_dev != parent_dev)
457 is_symlink = ((finfo.st_mode & S_IFMT) == S_IFLNK);
459 is_dir = ((finfo.st_mode & S_IFMT) == S_IFDIR);
460 is_file = ((finfo.st_mode & S_IFMT) == S_IFREG);
462 if (!(is_file || is_dir || is_symlink)) {
467 int is_excluded = -1;
468 for(i = 0; i < ndumps; i++) {
469 add_file_name(i, newname);
470 if(is_file && (time_t)finfo.st_ctime >= dumpdate[i]) {
473 if(is_excluded == -1)
475 calc_check_exclude(newname+parent_len+1);
476 if(is_excluded == 1) {
485 if(has_exclude && calc_check_exclude(newname+parent_len+1))
495 if(closedir(d) == -1)
500 /* drop root privs -- we're done with the permission-sensitive calls */
514 newp = alloc(SIZEOF(*newp));
515 newp->str = stralloc(str);
517 newp->next = name_stack;
524 Name *newp = name_stack;
527 if(!newp) return NULL;
529 name_stack = newp->next;
537 * =========================================================================
538 * Backup size calculations for DUMP program
540 * Given the system-dependent nature of dump, it's impossible to pin this
541 * down accurately. Luckily, that's not necessary.
543 * Dump rounds each file up to TP_BSIZE bytes, which is 1k in the BSD dump,
544 * others are unknown. In addition, dump stores three bitmaps at the
545 * beginning of the dump: a used inode map, a dumped dir map, and a dumped
546 * inode map. These are sized by the number of inodes in the filesystem.
548 * We don't take into account the complexities of BSD dump's indirect block
549 * requirements for files with holes, nor the dumping of directories that
550 * are not themselves modified.
557 (void)level; /* Quiet unused parameter warning */
558 (void)name; /* Quiet unused parameter warning */
568 /* keep the size in kbytes, rounded up, plus a 1k header block */
569 if((sp->st_mode & S_IFMT) == S_IFREG || (sp->st_mode & S_IFMT) == S_IFDIR)
570 dumpstats[level].total_size +=
571 (ST_BLOCKS(*sp) + (off_t)1) / (off_t)2 + (off_t)1;
579 struct fs_usage fsusage;
583 /* calculate the map sizes */
585 s = stralloc2(topdir, "/.");
586 if(get_fs_usage(s, NULL, &fsusage) == -1) {
587 error("statfs %s: %s", s, strerror(errno));
592 mapsize = (fsusage.fsu_files + (off_t)7) / (off_t)8; /* in bytes */
593 mapsize = (mapsize + (off_t)1023) / (off_t)1024; /* in kbytes */
595 /* the dump contains three maps plus the files */
597 return (mapsize * (off_t)3) + dumpstats[level].total_size;
601 * =========================================================================
602 * Backup size calculations for GNUTAR program
604 * Gnutar's basic blocksize is 512 bytes. Each file is rounded up to that
605 * size, plus one header block. Gnutar stores directories' file lists in
606 * incremental dumps - we'll pick up size of the modified dirs here. These
607 * will be larger than a simple filelist of their contents, but that's ok.
609 * As with DUMP, we only need a reasonable estimate, not an exact figure.
612 add_file_name_gnutar(
616 (void)name; /* Quiet unused parameter warning */
618 /* dumpstats[level].total_size_name += strlen(name) + 64;*/
619 dumpstats[level].total_size += (off_t)1;
627 /* the header takes one additional block */
628 dumpstats[level].total_size += ST_BLOCKS(*sp);
636 (void)topdir; /* Quiet unused parameter warning */
638 /* divide by two to get kbytes, rounded up */
639 /* + 4 blocks for security */
640 return (dumpstats[level].total_size + (off_t)5 +
641 (dumpstats[level].total_size_name/(off_t)512)) / (off_t)2;
645 * =========================================================================
646 * Backup size calculations for unknown backup programs.
648 * Here we'll just add up the file sizes and output that.
652 add_file_name_unknown(
656 (void)level; /* Quiet unused parameter warning */
657 (void)name; /* Quiet unused parameter warning */
667 /* just add up the block counts */
668 if((sp->st_mode & S_IFMT) == S_IFREG || (sp->st_mode & S_IFMT) == S_IFDIR)
669 dumpstats[level].total_size += ST_BLOCKS(*sp);
677 (void)topdir; /* Quiet unused parameter warning */
679 /* divide by two to get kbytes, rounded up */
680 return (dumpstats[level].total_size + (off_t)1) / (off_t)2;
684 * =========================================================================
694 FILE *file = fopen(filename, "r");
702 while(fgets(pattern, 1025, file)) {
703 if(strlen(pattern)>0 && pattern[strlen(pattern)-1] == '\n')
704 pattern[strlen(pattern)-1] = '\0';
705 sl_list = append_sl(sl_list, pattern);
717 if(is_empty_sl(exclude_sl)) return 0;
719 an_exclude=exclude_sl->first;
720 while(an_exclude != NULL) {
721 if(match_tar(an_exclude->name, filename)) {
724 an_exclude=an_exclude->next;