1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998 University of Maryland at College Park
4  * Copyright (c) 2007-2013 Zmanda, Inc.  All Rights Reserved.
5  * All Rights Reserved.
6  *
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.
16  *
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.
23  *
24  * Author: James da Silva, Systems Design and Analysis Group
25  *			   Computer Science Department
26  *			   University of Maryland at College Park
27  */
28 /*
29  * $Id: amadmin.c,v 1.124 2006/07/26 15:17:37 martinea Exp $
30  *
31  * controlling process for the Amanda backup system
32  */
33 #include "amanda.h"
34 #include "cmdline.h"
35 #include "conffile.h"
36 #include "diskfile.h"
37 #include "tapefile.h"
38 #include "infofile.h"
39 #include "logfile.h"
40 #include "version.h"
41 #include "holding.h"
42 #include "find.h"
43 #include "util.h"
44 #include "timestamp.h"
45 #include "server_util.h"
46 #include <getopt.h>
47 
48 disklist_t diskq;
49 
50 int main(int argc, char **argv);
51 void usage(void);
52 static void estimate(int argc, char **argv);
53 static void estimate_one(disk_t *dp);
54 void force(int argc, char **argv);
55 void force_one(disk_t *dp);
56 void force_level_1(int argc, char **argv);
57 void force_level_1_one(disk_t *dp);
58 void unforce(int argc, char **argv);
59 void unforce_one(disk_t *dp);
60 void force_bump(int argc, char **argv);
61 void force_bump_one(disk_t *dp);
62 void force_no_bump(int argc, char **argv);
63 void force_no_bump_one(disk_t *dp);
64 void unforce_bump(int argc, char **argv);
65 void unforce_bump_one(disk_t *dp);
66 void reuse(int argc, char **argv);
67 void noreuse(int argc, char **argv);
68 void info(int argc, char **argv);
69 void info_one(disk_t *dp);
70 void due(int argc, char **argv);
71 void due_one(disk_t *dp);
72 void find(int argc, char **argv);
73 void holding(int argc, char **argv);
74 void delete(int argc, char **argv);
75 void delete_one(disk_t *dp);
76 void balance(int argc, char **argv);
77 void tape(int argc, char **argv);
78 void bumpsize(int argc, char **argv);
79 void diskloop(int argc, char **argv, char *cmdname, void (*func)(disk_t *dp));
80 char *seqdatestr(int seq);
81 static int next_level0(disk_t *dp, info_t *info);
82 int bump_thresh(int level);
83 void export_db(int argc, char **argv);
84 void import_db(int argc, char **argv);
85 void hosts(int argc, char **argv);
86 void dles(int argc, char **argv);
87 void disklist(int argc, char **argv);
88 void disklist_one(disk_t *dp);
89 void show_version(int argc, char **argv);
90 static void show_config(int argc, char **argv);
91 
92 static char *conf_tapelist = NULL;
93 static char *displayunit;
94 static long int unitdivisor;
95 static gboolean print_default = 1;
96 static gboolean print_source = 0;
97 static int opt_days = -1;
98 static char *opt_sort = NULL;
99 static gboolean exact_match = FALSE;
100 static gboolean opt_long = 0;
101 static gboolean opt_outdated = 0;
102 
103 static const struct {
104     const char *name;
105     void (*fn)(int, char **);
106     const char *usage;
107 } cmdtab[] = {
108     { "version", show_version,
109 	T_("\t\t\t\t\t# Show version info.") },
110     { "config", show_config,
111 	T_("\t\t\t\t\t# Show configuration.") },
112     { "estimate", estimate,
113 	T_(" [<hostname> [<disks>]* ]*\t# Print server estimate.") },
114     { "force", force,
115 	T_(" [<hostname> [<disks>]* ]+\t\t# Force level 0 at next run.") },
116     { "force-level-1", force_level_1,
117 	T_(" [<hostname> [<disks>]* ]+\t\t# Force level 1 at next run.") },
118     { "unforce", unforce,
119 	T_(" [<hostname> [<disks>]* ]+\t# Clear force command.") },
120     { "force-bump", force_bump,
121 	T_(" [<hostname> [<disks>]* ]+\t# Force bump at next run.") },
122     { "force-no-bump", force_no_bump,
123 	T_(" [<hostname> [<disks>]* ]+\t# Force no-bump at next run.") },
124     { "unforce-bump", unforce_bump,
125 	T_(" [<hostname> [<disks>]* ]+\t# Clear bump command.") },
126     { "disklist", disklist,
127 	T_(" [<hostname> [<disks>]* ]*\t# Debug disklist entries.") },
128     { "hosts", hosts,
129 	T_("\t\t\t\t\t# Show all distinct hosts in disklist.") },
130     { "dles", dles,
131 	T_("\t\t\t\t\t# Show all dles in disklist, one per line.") },
132     { "reuse", reuse,
133 	T_(" <tapelabel> ...\t\t # re-use this tape.") },
134     { "no-reuse", noreuse,
135 	T_(" <tapelabel> ...\t # never re-use this tape.") },
136     { "find", find,
137 	T_(" [<hostname> [<disks>]* ]*\t # Show which tapes these dumps are on.") },
138     { "holding", holding,
139 	T_(" {list [ -l ] |delete} [ <hostname> [ <disk> [ <datestamp> [ .. ] ] ] ]+\t # Show or delete holding disk contents.") },
140     { "delete", delete,
141 	T_(" [<hostname> [<disks>]* ]+ # Delete from database.") },
142     { "info", info,
143 	T_(" [<hostname> [<disks>]* ]*\t # Show current info records.") },
144     { "due", due,
145 	T_(" [<hostname> [<disks>]* ]*\t # Show due date.") },
146     { "balance", balance,
147 	T_(" [--days <num>]\t\t # Show nightly dump size balance.") },
148     { "tape", tape,
149 	T_(" [--days <num>]\t\t # Show which tape is due next.") },
150     { "bumpsize", bumpsize,
151 	T_("\t\t\t # Show current bump thresholds.") },
152     { "export", export_db,
153 	T_(" [<hostname> [<disks>]* ]* # Export curinfo database to stdout.") },
154     { "import", import_db,
155 	T_("\t\t\t\t # Import curinfo database from stdin.") },
156 };
157 #define	NCMDS	(int)(sizeof(cmdtab) / sizeof(cmdtab[0]))
158 
159 static struct option long_options[] = {
160     {"version"       , 0, NULL,  1},
161     {"no-default"    , 0, NULL,  2},
162     {"print-source"  , 0, NULL,  3},
163     {"days"          , 1, NULL,  4},
164     {"sort"          , 1, NULL,  5},
165     {"exact-match"   , 0, NULL,  6},
166     {NULL, 0, NULL, 0}
167 };
168 
169 int
main(int argc,char ** argv)170 main(
171     int		argc,
172     char **	argv)
173 {
174     int i;
175     char *conf_diskfile;
176     char *conf_infofile;
177     config_overrides_t *cfg_ovr = NULL;
178 
179     /*
180      * Configure program for internationalization:
181      *   1) Only set the message locale for now.
182      *   2) Set textdomain for all amanda related programs to "amanda"
183      *      We don't want to be forced to support dozens of message catalogs.
184      */
185     setlocale(LC_MESSAGES, "C");
186     textdomain("amanda");
187 
188     safe_fd(-1, 0);
189     safe_cd();
190 
191     set_pname("amadmin");
192 
193     /* Don't die when child closes pipe */
194     signal(SIGPIPE, SIG_IGN);
195 
196     dbopen(DBG_SUBDIR_SERVER);
197 
198     add_amanda_log_handler(amanda_log_stderr);
199 
200     cfg_ovr = extract_commandline_config_overrides(&argc, &argv);
201 
202     while (1) {
203 	int option_index = 0;
204         int c;
205         c = getopt_long(argc, argv, "ld", long_options, &option_index);
206 
207 	if (c == -1) {
208 	    break;
209 	}
210 
211 	switch(c) {
212 	case 1: printf("amadmin-%s\n", VERSION);
213 		return 0;
214 	case 2: print_default = 0;
215 		break;
216 	case 3: print_source = 1;
217 		break;
218 	case 4: opt_days = atoi(optarg);
219 		break;
220 	case 5: opt_sort = g_strdup(optarg);
221 		break;
222 	case 6: exact_match = TRUE;
223 		break;
224 	case 'l': opt_long = TRUE;
225 		break;
226 	case 'd': opt_outdated = TRUE;
227 		break;
228 	default: usage();
229 	}
230     }
231     argc -= optind-1, argv += optind-1;
232 
233     if(argc < 3) usage();
234 
235     set_config_overrides(cfg_ovr);
236 
237     if(strcmp(argv[2],"version") == 0) {
238 	config_init(0, NULL);
239 	show_version(argc, argv);
240 	goto done;
241     }
242 
243     config_init(CONFIG_INIT_EXPLICIT_NAME, argv[1]);
244 
245     conf_diskfile = config_dir_relative(getconf_str(CNF_DISKFILE));
246     read_diskfile(conf_diskfile, &diskq);
247     amfree(conf_diskfile);
248 
249     if (config_errors(NULL) >= CFGERR_WARNINGS) {
250 	config_print_errors();
251 	if (config_errors(NULL) >= CFGERR_ERRORS) {
252 	    g_critical(_("errors processing config file"));
253 	}
254     }
255 
256     dbrename(get_config_name(), DBG_SUBDIR_SERVER);
257 
258     check_running_as(RUNNING_AS_DUMPUSER);
259 
260     conf_tapelist = config_dir_relative(getconf_str(CNF_TAPELIST));
261     if(read_tapelist(conf_tapelist)) {
262 	error(_("could not load tapelist \"%s\""), conf_tapelist);
263 	/*NOTREACHED*/
264     }
265     /* conf_tapelist is not freed yet -- it may be used to write the
266      * tapelist later. */
267 
268     conf_infofile = config_dir_relative(getconf_str(CNF_INFOFILE));
269     if(open_infofile(conf_infofile)) {
270 	error(_("could not open info db \"%s\""), conf_infofile);
271 	/*NOTREACHED*/
272     }
273     amfree(conf_infofile);
274 
275     displayunit = getconf_str(CNF_DISPLAYUNIT);
276     unitdivisor = getconf_unit_divisor();
277 
278     for (i = 0; i < NCMDS; i++)
279 	if (strcmp(argv[2], cmdtab[i].name) == 0) {
280 	    (*cmdtab[i].fn)(argc, argv);
281 	    break;
282 	}
283     if (i == NCMDS) {
284 	g_fprintf(stderr, _("%s: unknown command \"%s\"\n"), argv[0], argv[2]);
285 	usage();
286     }
287 
288     close_infofile();
289     clear_tapelist();
290     amfree(conf_tapelist);
291 
292 done:
293 
294     unload_disklist();
295     diskq.head = NULL;
296     diskq.tail = NULL;
297     dbclose();
298     return 0;
299 }
300 
301 
302 void
usage(void)303 usage(void)
304 {
305     int i;
306 
307     g_fprintf(stderr, _("\nUsage: %s [--version] [--exact-match] [--no-default] [--print-source] [-o configoption]*\n               <conf> <command> {<args>} ...\n"),
308 	    get_pname());
309     g_fprintf(stderr, _("    Valid <command>s are:\n"));
310     for (i = 0; i < NCMDS; i++)
311 	g_fprintf(stderr, "\t%s%s\n", cmdtab[i].name, _(cmdtab[i].usage));
312     exit(1);
313 }
314 
315 
316 /* ----------------------------------------------- */
317 
318 #define SECS_PER_DAY (24*60*60)
319 time_t today;
320 
321 char *
seqdatestr(int seq)322 seqdatestr(
323     int		seq)
324 {
325     static char str[16];
326     static char *dow[7] = {
327 			T_("Sun"),
328 			T_("Mon"),
329 			T_("Tue"),
330 			T_("Wed"),
331 			T_("Thu"),
332 			T_("Fri"),
333 			T_("Sat")
334 		};
335     time_t t = today + seq*SECS_PER_DAY;
336     struct tm *tm;
337 
338     tm = localtime(&t);
339 
340     if (tm)
341 	g_snprintf(str, SIZEOF(str),
342 		 "%2d/%02d %3s", tm->tm_mon+1, tm->tm_mday, _(dow[tm->tm_wday]));
343     else
344 	strcpy(str, _("BAD DATE"));
345 
346     return str;
347 }
348 
349 #undef days_diff
350 #define days_diff(a, b)        (int)(((b) - (a) + SECS_PER_DAY) / SECS_PER_DAY)
351 
352 /* when is next level 0 due? 0 = tonight, 1 = tommorrow, etc*/
353 static int
next_level0(disk_t * dp,info_t * info)354 next_level0(
355     disk_t *	dp,
356     info_t *	info)
357 {
358     if(dp->strategy == DS_NOFULL)
359 	return 1;	/* fake it */
360     if(info->inf[0].date < (time_t)0)
361 	return 0;	/* new disk */
362     else
363 	return dp->dumpcycle - days_diff(info->inf[0].date, today);
364 }
365 
366 /* ----------------------------------------------- */
367 
368 void
diskloop(int argc,char ** argv,char * cmdname,void (* func)(disk_t * dp))369 diskloop(
370     int		argc,
371     char **	argv,
372     char *	cmdname,
373     void	(*func)(disk_t *dp))
374 {
375     disk_t *dp;
376     int count = 0;
377     char *errstr;
378 
379     if(argc < 4) {
380 	g_fprintf(stderr,_("%s: expecting \"%s [<hostname> [<disks>]* ]+\"\n"),
381 		get_pname(), cmdname);
382 	usage();
383     }
384 
385     errstr = match_disklist(&diskq, exact_match, argc-3, argv+3);
386     if (errstr) {
387 	g_printf("%s", errstr);
388 	amfree(errstr);
389     }
390 
391     for(dp = diskq.head; dp != NULL; dp = dp->next) {
392 	if(dp->todo) {
393 	    count++;
394 	    func(dp);
395 	}
396     }
397     if(count==0) {
398 	g_fprintf(stderr,_("%s: no disk matched\n"),get_pname());
399     }
400 }
401 
402 /* ----------------------------------------------- */
403 
404 
405 static void
estimate_one(disk_t * dp)406 estimate_one(
407     disk_t *	dp)
408 {
409     char   *hostname = dp->host->hostname;
410     char   *diskname = dp->name;
411     char   *qhost = quote_string(hostname);
412     char   *qdisk = quote_string(diskname);
413     info_t  info;
414     int     stats;
415     gint64  size;
416 
417     get_info(hostname, diskname, &info);
418 
419     size = internal_server_estimate(dp, &info, 0, &stats);
420     if (stats) {
421 	printf("%s %s %d %jd\n", qhost, qdisk, 0, (intmax_t)size);
422     }
423 
424     if (info.last_level > 0) {
425 	size = internal_server_estimate(dp, &info, info.last_level, &stats);
426 	if (stats) {
427 	    printf("%s %s %d %jd\n", qhost, qdisk, info.last_level,
428 		   (intmax_t)size);
429 	}
430     }
431 
432     if (info.last_level > -1) {
433 	size = internal_server_estimate(dp, &info, info.last_level+1, &stats);
434 	if (stats) {
435 	    printf("%s %s %d %jd\n", qhost, qdisk, info.last_level+1,
436 		   (intmax_t)size);
437 	}
438     }
439 
440     amfree(qhost);
441     amfree(qdisk);
442 }
443 
444 
445 static void
estimate(int argc,char ** argv)446 estimate(
447     int		argc,
448     char **	argv)
449 {
450     disk_t *dp;
451 
452     if(argc >= 4)
453 	diskloop(argc, argv, "estimate", estimate_one);
454     else
455 	for(dp = diskq.head; dp != NULL; dp = dp->next)
456 	    estimate_one(dp);
457 }
458 
459 
460 /* ----------------------------------------------- */
461 
462 
463 void
force_one(disk_t * dp)464 force_one(
465     disk_t *	dp)
466 {
467     char *hostname = dp->host->hostname;
468     char *diskname = dp->name;
469     info_t info;
470 
471     get_info(hostname, diskname, &info);
472     SET(info.command, FORCE_FULL);
473     if (ISSET(info.command, FORCE_LEVEL_1)) {
474 	CLR(info.command, FORCE_LEVEL_1);
475 	g_printf(_("%s: WARNING: %s:%s FORCE-LEVEL-1 command was cleared.\n"),
476 	       get_pname(), hostname, diskname);
477     }
478     if (ISSET(info.command, FORCE_BUMP)) {
479 	CLR(info.command, FORCE_BUMP);
480 	g_printf(_("%s: WARNING: %s:%s FORCE-BUMP command was cleared.\n"),
481 	       get_pname(), hostname, diskname);
482     }
483     if(put_info(hostname, diskname, &info) == 0) {
484 	if (dp->strategy == DS_INCRONLY) {
485 	    g_printf(_("%s: %s:%s, full dump done offline, next dump will be at level 1.\n"),
486 		     get_pname(), hostname, diskname);
487 	} else {
488 	    g_printf(_("%s: %s:%s is set to a forced level 0 at next run.\n"),
489 		     get_pname(), hostname, diskname);
490 	}
491     } else {
492 	g_fprintf(stderr, _("%s: %s:%s could not be forced.\n"),
493 		get_pname(), hostname, diskname);
494     }
495 }
496 
497 
498 void
force(int argc,char ** argv)499 force(
500     int		argc,
501     char **	argv)
502 {
503     diskloop(argc, argv, "force", force_one);
504 }
505 
506 
507 /* ----------------------------------------------- */
508 
509 
510 void
force_level_1_one(disk_t * dp)511 force_level_1_one(
512     disk_t *	dp)
513 {
514     char *hostname = dp->host->hostname;
515     char *diskname = dp->name;
516     info_t info;
517 
518     get_info(hostname, diskname, &info);
519     SET(info.command, FORCE_LEVEL_1);
520     if (ISSET(info.command, FORCE_FULL)) {
521 	CLR(info.command, FORCE_FULL);
522 	g_printf(_("%s: WARNING: %s:%s FORCE command was cleared.\n"),
523 	       get_pname(), hostname, diskname);
524     }
525     if (ISSET(info.command, FORCE_BUMP)) {
526 	CLR(info.command, FORCE_BUMP);
527 	g_printf(_("%s: WARNING: %s:%s FORCE-BUMP command was cleared.\n"),
528 	       get_pname(), hostname, diskname);
529     }
530     if(put_info(hostname, diskname, &info) == 0) {
531 	g_printf(_("%s: %s:%s is set to a forced level 1 at next run.\n"),
532 		 get_pname(), hostname, diskname);
533     } else {
534 	g_fprintf(stderr, _("%s: %s:%s could not be forced.\n"),
535 		get_pname(), hostname, diskname);
536     }
537 }
538 
539 
540 void
force_level_1(int argc,char ** argv)541 force_level_1(
542     int		argc,
543     char **	argv)
544 {
545     diskloop(argc, argv, "force", force_level_1_one);
546 }
547 
548 
549 /* ----------------------------------------------- */
550 
551 
552 void
unforce_one(disk_t * dp)553 unforce_one(
554     disk_t *	dp)
555 {
556     char *hostname = dp->host->hostname;
557     char *diskname = dp->name;
558     info_t info;
559 
560     get_info(hostname, diskname, &info);
561     if (ISSET(info.command, FORCE_FULL)) {
562 	CLR(info.command, FORCE_FULL);
563 	if(put_info(hostname, diskname, &info) == 0){
564 	    g_printf(_("%s: force command for %s:%s cleared.\n"),
565 		   get_pname(), hostname, diskname);
566 	} else {
567 	    g_fprintf(stderr,
568 		    _("%s: force command for %s:%s could not be cleared.\n"),
569 		    get_pname(), hostname, diskname);
570 	}
571     } else if (ISSET(info.command, FORCE_LEVEL_1)) {
572 	CLR(info.command, FORCE_LEVEL_1);
573 	if(put_info(hostname, diskname, &info) == 0){
574 	    g_printf(_("%s: force-level-1 command for %s:%s cleared.\n"),
575 		   get_pname(), hostname, diskname);
576 	} else {
577 	    g_fprintf(stderr,
578 		    _("%s: force-level-1 command for %s:%s could not be cleared.\n"),
579 		    get_pname(), hostname, diskname);
580 	}
581     }
582     else {
583 	g_printf(_("%s: no force command outstanding for %s:%s, unchanged.\n"),
584 	       get_pname(), hostname, diskname);
585     }
586 }
587 
588 void
unforce(int argc,char ** argv)589 unforce(
590     int		argc,
591     char **	argv)
592 {
593     diskloop(argc, argv, "unforce", unforce_one);
594 }
595 
596 
597 /* ----------------------------------------------- */
598 
599 
600 void
force_bump_one(disk_t * dp)601 force_bump_one(
602     disk_t *	dp)
603 {
604     char *hostname = dp->host->hostname;
605     char *diskname = dp->name;
606     info_t info;
607 
608     get_info(hostname, diskname, &info);
609     SET(info.command, FORCE_BUMP);
610     if (ISSET(info.command, FORCE_NO_BUMP)) {
611 	CLR(info.command, FORCE_NO_BUMP);
612 	g_printf(_("%s: WARNING: %s:%s FORCE-NO-BUMP command was cleared.\n"),
613 	       get_pname(), hostname, diskname);
614     }
615     if (ISSET(info.command, FORCE_FULL)) {
616 	CLR(info.command, FORCE_FULL);
617 	g_printf(_("%s: WARNING: %s:%s FORCE command was cleared.\n"),
618 	       get_pname(), hostname, diskname);
619     }
620     if (ISSET(info.command, FORCE_LEVEL_1)) {
621 	CLR(info.command, FORCE_LEVEL_1);
622 	g_printf(_("%s: WARNING: %s:%s FORCE-LEVEL-1 command was cleared.\n"),
623 	       get_pname(), hostname, diskname);
624     }
625     if(put_info(hostname, diskname, &info) == 0) {
626 	g_printf(_("%s: %s:%s is set to bump at next run.\n"),
627 	       get_pname(), hostname, diskname);
628     } else {
629 	g_fprintf(stderr, _("%s: %s:%s could not be forced to bump.\n"),
630 		get_pname(), hostname, diskname);
631     }
632 }
633 
634 
635 void
force_bump(int argc,char ** argv)636 force_bump(
637     int		argc,
638     char **	argv)
639 {
640     diskloop(argc, argv, "force-bump", force_bump_one);
641 }
642 
643 
644 /* ----------------------------------------------- */
645 
646 
647 void
force_no_bump_one(disk_t * dp)648 force_no_bump_one(
649     disk_t *	dp)
650 {
651     char *hostname = dp->host->hostname;
652     char *diskname = dp->name;
653     info_t info;
654 
655     get_info(hostname, diskname, &info);
656     SET(info.command, FORCE_NO_BUMP);
657     if (ISSET(info.command, FORCE_BUMP)) {
658 	CLR(info.command, FORCE_BUMP);
659 	g_printf(_("%s: WARNING: %s:%s FORCE-BUMP command was cleared.\n"),
660 	       get_pname(), hostname, diskname);
661     }
662     if(put_info(hostname, diskname, &info) == 0) {
663 	g_printf(_("%s: %s:%s is set to not bump at next run.\n"),
664 	       get_pname(), hostname, diskname);
665     } else {
666 	g_fprintf(stderr, _("%s: %s:%s could not be force to not bump.\n"),
667 		get_pname(), hostname, diskname);
668     }
669 }
670 
671 
672 void
force_no_bump(int argc,char ** argv)673 force_no_bump(
674     int		argc,
675     char **	argv)
676 {
677     diskloop(argc, argv, "force-no-bump", force_no_bump_one);
678 }
679 
680 
681 /* ----------------------------------------------- */
682 
683 
684 void
unforce_bump_one(disk_t * dp)685 unforce_bump_one(
686     disk_t *	dp)
687 {
688     char *hostname = dp->host->hostname;
689     char *diskname = dp->name;
690     info_t info;
691 
692     get_info(hostname, diskname, &info);
693     if (ISSET(info.command, FORCE_BUMP|FORCE_NO_BUMP)) {
694 	CLR(info.command, FORCE_BUMP|FORCE_NO_BUMP);
695 	if(put_info(hostname, diskname, &info) == 0) {
696 	    g_printf(_("%s: bump command for %s:%s cleared.\n"),
697 		   get_pname(), hostname, diskname);
698 	} else {
699 	    g_fprintf(stderr, _("%s: %s:%s bump command could not be cleared.\n"),
700 		    get_pname(), hostname, diskname);
701 	}
702     }
703     else {
704 	g_printf(_("%s: no bump command outstanding for %s:%s, unchanged.\n"),
705 	       get_pname(), hostname, diskname);
706     }
707 }
708 
709 
710 void
unforce_bump(int argc,char ** argv)711 unforce_bump(
712     int		argc,
713     char **	argv)
714 {
715     diskloop(argc, argv, "unforce-bump", unforce_bump_one);
716 }
717 
718 
719 /* ----------------------------------------------- */
720 
721 void
reuse(int argc,char ** argv)722 reuse(
723     int		argc,
724     char **	argv)
725 {
726     tape_t *tp;
727     int count;
728 
729     if(argc < 4) {
730 	g_fprintf(stderr,_("%s: expecting \"reuse <tapelabel> ...\"\n"),
731 		get_pname());
732 	usage();
733     }
734 
735     for(count=3; count< argc; count++) {
736 	tp = lookup_tapelabel(argv[count]);
737 	if ( tp == NULL) {
738 	    g_fprintf(stderr, _("reuse: tape label %s not found in tapelist.\n"),
739 		argv[count]);
740 	    continue;
741 	}
742 	if( tp->reuse == 0 ) {
743 	    tp->reuse = 1;
744 	    g_printf(_("%s: marking tape %s as reusable.\n"),
745 		   get_pname(), argv[count]);
746 	} else {
747 	    g_fprintf(stderr, _("%s: tape %s already reusable.\n"),
748 		    get_pname(), argv[count]);
749 	}
750     }
751 
752     if(write_tapelist(conf_tapelist)) {
753 	error(_("could not write tapelist \"%s\""), conf_tapelist);
754 	/*NOTREACHED*/
755     }
756 }
757 
758 void
noreuse(int argc,char ** argv)759 noreuse(
760     int		argc,
761     char **	argv)
762 {
763     tape_t *tp;
764     int count;
765 
766     if(argc < 4) {
767 	g_fprintf(stderr,_("%s: expecting \"no-reuse <tapelabel> ...\"\n"),
768 		get_pname());
769 	usage();
770     }
771 
772     for(count=3; count< argc; count++) {
773 	tp = lookup_tapelabel(argv[count]);
774 	if ( tp == NULL) {
775 	    g_fprintf(stderr, _("no-reuse: tape label %s not found in tapelist.\n"),
776 		argv[count]);
777 	    continue;
778 	}
779 	if( tp->reuse == 1 ) {
780 	    tp->reuse = 0;
781 	    g_printf(_("%s: marking tape %s as not reusable.\n"),
782 		   get_pname(), argv[count]);
783 	} else {
784 	    g_fprintf(stderr, _("%s: tape %s already not reusable.\n"),
785 		    get_pname(), argv[count]);
786 	}
787     }
788 
789     if(write_tapelist(conf_tapelist)) {
790 	error(_("could not write tapelist \"%s\""), conf_tapelist);
791 	/*NOTREACHED*/
792     }
793 }
794 
795 
796 /* ----------------------------------------------- */
797 
798 static int deleted;
799 
800 void
delete_one(disk_t * dp)801 delete_one(
802     disk_t *	dp)
803 {
804     char *hostname = dp->host->hostname;
805     char *diskname = dp->name;
806     info_t info;
807 
808     if(get_info(hostname, diskname, &info)) {
809 	g_printf(_("%s: %s:%s NOT currently in database.\n"),
810 	       get_pname(), hostname, diskname);
811 	return;
812     }
813 
814     deleted++;
815     if(del_info(hostname, diskname)) {
816 	error(_("couldn't delete %s:%s from database: %s"),
817 	      hostname, diskname, strerror(errno));
818         /*NOTREACHED*/
819     } else {
820 	g_printf(_("%s: %s:%s deleted from curinfo database.\n"),
821 	       get_pname(), hostname, diskname);
822     }
823 }
824 
825 void
delete(int argc,char ** argv)826 delete(
827     int		argc,
828     char **	argv)
829 {
830     deleted = 0;
831     diskloop(argc, argv, "delete", delete_one);
832 
833    if(deleted)
834 	g_printf(
835 	 _("%s: NOTE: you'll have to remove these from the disklist yourself.\n"),
836 	 get_pname());
837 }
838 
839 /* ----------------------------------------------- */
840 
841 void
info_one(disk_t * dp)842 info_one(
843     disk_t *	dp)
844 {
845     info_t info;
846     int lev;
847     struct tm *tm;
848     stats_t *sp;
849 
850     get_info(dp->host->hostname, dp->name, &info);
851 
852     g_printf(_("\nCurrent info for %s %s:\n"), dp->host->hostname, dp->name);
853     if (ISSET(info.command, FORCE_FULL))
854 	g_printf(_("  (Forcing to level 0 dump at next run)\n"));
855     if (ISSET(info.command, FORCE_BUMP))
856 	g_printf(_("  (Forcing bump at next run)\n"));
857     if (ISSET(info.command, FORCE_NO_BUMP))
858 	g_printf(_("  (Forcing no-bump at next run)\n"));
859     g_printf(_("  Stats: dump rates (kps), Full:  %5.1lf, %5.1lf, %5.1lf\n"),
860 	   info.full.rate[0], info.full.rate[1], info.full.rate[2]);
861     g_printf(_("                    Incremental:  %5.1lf, %5.1lf, %5.1lf\n"),
862 	   info.incr.rate[0], info.incr.rate[1], info.incr.rate[2]);
863     g_printf(_("          compressed size, Full: %5.1lf%%,%5.1lf%%,%5.1lf%%\n"),
864 	   info.full.comp[0]*100, info.full.comp[1]*100, info.full.comp[2]*100);
865     g_printf(_("                    Incremental: %5.1lf%%,%5.1lf%%,%5.1lf%%\n"),
866 	   info.incr.comp[0]*100, info.incr.comp[1]*100, info.incr.comp[2]*100);
867 
868     g_printf(_("  Dumps: lev datestmp  tape             file   origK   compK secs\n"));
869     for(lev = 0, sp = &info.inf[0]; lev < 9; lev++, sp++) {
870 	if(sp->date < (time_t)0 && sp->label[0] == '\0') continue;
871 	tm = localtime(&sp->date);
872 	if (tm) {
873 	    g_printf(_("          %d  %04d%02d%02d  %-15s  %lld %lld %lld %jd\n"),
874 		   lev, tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
875 		   sp->label,
876 		   (long long)sp->filenum,
877 		   (long long)sp->size,
878 		   (long long)sp->csize,
879 		   (intmax_t)sp->secs);
880 	} else {
881 	    g_printf(_("          %d  BAD-DATE  %-15s  %lld %lld %lld %jd\n"),
882 		   lev,
883 		   sp->label,
884 		   (long long)sp->filenum,
885 		   (long long)sp->size,
886 		   (long long)sp->csize,
887 		   (intmax_t)sp->secs);
888 	}
889     }
890 }
891 
892 
893 void
info(int argc,char ** argv)894 info(
895     int		argc,
896     char **	argv)
897 {
898     disk_t *dp;
899 
900     if(argc >= 4)
901 	diskloop(argc, argv, "info", info_one);
902     else
903 	for(dp = diskq.head; dp != NULL; dp = dp->next)
904 	    info_one(dp);
905 }
906 
907 /* ----------------------------------------------- */
908 
909 void
due_one(disk_t * dp)910 due_one(
911     disk_t *	dp)
912 {
913     am_host_t *hp;
914     int days;
915     info_t info;
916 
917     hp = dp->host;
918     if(get_info(hp->hostname, dp->name, &info)) {
919 	g_printf(_("new disk %s:%s ignored.\n"), hp->hostname, dp->name);
920     }
921     else {
922 	days = next_level0(dp, &info);
923 	if(days < 0) {
924 	    g_printf(_("Overdue %2d day%s %s:%s\n"),
925 		   -days, (-days == 1) ? ": " : "s:",
926 		   hp->hostname, dp->name);
927 	}
928 	else if(days == 0) {
929 	    g_printf(_("Due today: %s:%s\n"), hp->hostname, dp->name);
930 	}
931 	else {
932 	    g_printf(_("Due in %2d day%s %s:%s\n"), days,
933 		   (days == 1) ? ": " : "s:",
934 		   hp->hostname, dp->name);
935 	}
936     }
937 }
938 
939 void
due(int argc,char ** argv)940 due(
941     int		argc,
942     char **	argv)
943 {
944     disk_t *dp;
945 
946     time(&today);
947     if(argc >= 4)
948 	diskloop(argc, argv, "due", due_one);
949     else
950 	for(dp = diskq.head; dp != NULL; dp = dp->next)
951 	    due_one(dp);
952 }
953 
954 /* ----------------------------------------------- */
955 
956 void
tape(int argc G_GNUC_UNUSED,char ** argv G_GNUC_UNUSED)957 tape(
958     int		argc G_GNUC_UNUSED,
959     char **	argv G_GNUC_UNUSED)
960 {
961     int     nb_days = 1;
962     int     runtapes;
963     tape_t *tp;
964     int     i, j;
965     int     skip;
966     int     nb_new_tape;
967 
968     nb_days = opt_days;
969     if (opt_days == 0 || opt_days == -1) {
970        nb_days = 1;
971     }
972     if (nb_days < 1) {
973 	g_printf(_("days must be an integer bigger than 0\n"));
974 	return;
975      }
976     if (nb_days > 10000)
977 	nb_days = 10000;
978 
979     if(argc > 4 && strcmp(argv[3],"--days") == 0) {
980 	nb_days = atoi(argv[4]);
981 	if(nb_days < 1) {
982 	    g_printf(_("days must be an integer bigger than 0\n"));
983 	    return;
984 	}
985 	if (nb_days > 10000)
986 	    nb_days = 10000;
987     }
988 
989     runtapes = getconf_int(CNF_RUNTAPES);
990     tp = lookup_last_reusable_tape(0);
991     skip = 0;
992 
993     for ( j=0 ; j < nb_days ; j++ ) {
994 	nb_new_tape=0;
995 	for ( i=0 ; i < runtapes ; i++ ) {
996 	    if(i==0)
997 		g_fprintf(stdout, _("The next Amanda run should go onto "));
998 	    if(tp != NULL) {
999 		if (nb_new_tape > 0) {
1000 		    if (nb_new_tape == 1)
1001 			g_fprintf(stdout, _("1 new tape.\n"));
1002 		    else
1003 			g_fprintf(stdout, _("%d new tapes.\n"), nb_new_tape);
1004 		    g_fprintf(stdout, "                                   ");
1005 		    nb_new_tape = 0;
1006 		}
1007 		g_fprintf(stdout, _("tape %s or a new tape.\n"), tp->label);
1008 		if (i < runtapes-1)
1009 		    g_fprintf(stdout, "                                   ");
1010 	    } else {
1011 		nb_new_tape++;
1012 	    }
1013 	    skip++;
1014 
1015 	    tp = lookup_last_reusable_tape(skip);
1016 	}
1017 	if (nb_new_tape > 0) {
1018 	    if (nb_new_tape == 1)
1019 		g_fprintf(stdout, _("1 new tape.\n"));
1020 	    else
1021 		g_fprintf(stdout, _("%d new tapes.\n"), nb_new_tape);
1022 	}
1023     }
1024 
1025     print_new_tapes(stdout, nb_days * runtapes);
1026 }
1027 
1028 /* ----------------------------------------------- */
1029 
1030 void
balance(int argc G_GNUC_UNUSED,char ** argv G_GNUC_UNUSED)1031 balance(
1032     int		argc G_GNUC_UNUSED,
1033     char **	argv G_GNUC_UNUSED)
1034 {
1035     disk_t *dp;
1036     struct balance_stats {
1037 	int disks;
1038 	off_t origsize, outsize;
1039     } *sp;
1040     int conf_runspercycle, conf_dumpcycle;
1041     int seq, runs_per_cycle, overdue, max_overdue;
1042     int later, total, balance, distinct;
1043     double fseq, disk_dumpcycle;
1044     info_t info;
1045     off_t total_balanced, balanced;
1046     int empty_day;
1047 
1048     time(&today);
1049     conf_dumpcycle = getconf_int(CNF_DUMPCYCLE);
1050     conf_runspercycle = getconf_int(CNF_RUNSPERCYCLE);
1051     later = conf_dumpcycle;
1052     overdue = 0;
1053     max_overdue = 0;
1054 
1055     if (opt_days > 0) {
1056 	later = opt_days;
1057     } else if (opt_days == 0) {
1058 	later = conf_dumpcycle;
1059     }
1060     if(later > 10000) later = 10000;
1061 
1062     if(conf_runspercycle == 0) {
1063 	runs_per_cycle = conf_dumpcycle;
1064     } else if(conf_runspercycle == -1 ) {
1065 	runs_per_cycle = guess_runs_from_tapelist();
1066     } else
1067 	runs_per_cycle = conf_runspercycle;
1068 
1069     if (runs_per_cycle <= 0) {
1070 	runs_per_cycle = 1;
1071     }
1072 
1073     total = later + 1;
1074     balance = later + 2;
1075     distinct = later + 3;
1076 
1077     sp = (struct balance_stats *)
1078 	alloc(SIZEOF(struct balance_stats) * (distinct+1));
1079 
1080     for(seq=0; seq <= distinct; seq++) {
1081 	sp[seq].disks = 0;
1082 	sp[seq].origsize = sp[seq].outsize = (off_t)0;
1083     }
1084 
1085     for(dp = diskq.head; dp != NULL; dp = dp->next) {
1086 	if(get_info(dp->host->hostname, dp->name, &info)) {
1087 	    g_printf(_("new disk %s:%s ignored.\n"), dp->host->hostname, dp->name);
1088 	    continue;
1089 	}
1090 	if (dp->strategy == DS_NOFULL) {
1091 	    continue;
1092 	}
1093 	sp[distinct].disks++;
1094 	sp[distinct].origsize += info.inf[0].size/(off_t)unitdivisor;
1095 	sp[distinct].outsize += info.inf[0].csize/(off_t)unitdivisor;
1096 
1097 	sp[balance].disks++;
1098 	if(dp->dumpcycle == 0) {
1099 	    sp[balance].origsize += (info.inf[0].size/(off_t)unitdivisor) * (off_t)runs_per_cycle;
1100 	    sp[balance].outsize += (info.inf[0].csize/(off_t)unitdivisor) * (off_t)runs_per_cycle;
1101 	}
1102 	else {
1103 	    sp[balance].origsize += (info.inf[0].size/(off_t)unitdivisor) *
1104 				    (off_t)(conf_dumpcycle / dp->dumpcycle);
1105 	    sp[balance].outsize += (info.inf[0].csize/(off_t)unitdivisor) *
1106 				   (off_t)(conf_dumpcycle / dp->dumpcycle);
1107 	}
1108 
1109 	disk_dumpcycle = (double)dp->dumpcycle;
1110 	if(dp->dumpcycle <= 0)
1111 	    disk_dumpcycle = ((double)conf_dumpcycle) / ((double)runs_per_cycle);
1112 
1113 	seq = next_level0(dp, &info);
1114 	fseq = seq + 0.0001;
1115 	do {
1116 	    if(seq < 0) {
1117 		overdue++;
1118 		if (-seq > max_overdue)
1119 		    max_overdue = -seq;
1120 		seq = 0;
1121 		fseq = seq + 0.0001;
1122 	    }
1123 	    if(seq > later) {
1124 	       	seq = later;
1125 	    }
1126 
1127 	    sp[seq].disks++;
1128 	    sp[seq].origsize += info.inf[0].size/(off_t)unitdivisor;
1129 	    sp[seq].outsize += info.inf[0].csize/(off_t)unitdivisor;
1130 
1131 	    if(seq < later) {
1132 		sp[total].disks++;
1133 		sp[total].origsize += info.inf[0].size/(off_t)unitdivisor;
1134 		sp[total].outsize += info.inf[0].csize/(off_t)unitdivisor;
1135 	    }
1136 
1137 	    /* See, if there's another run in this dumpcycle */
1138 	    fseq += disk_dumpcycle;
1139 	    seq = (int)fseq;
1140 	} while (seq < later);
1141     }
1142 
1143     if(sp[total].outsize == (off_t)0 && sp[later].outsize == (off_t)0) {
1144 	g_printf(_("\nNo data to report on yet.\n"));
1145 	amfree(sp);
1146 	return;
1147     }
1148 
1149     balanced = sp[balance].outsize / (off_t)runs_per_cycle;
1150     if(conf_dumpcycle == later) {
1151 	total_balanced = sp[total].outsize / (off_t)runs_per_cycle;
1152     }
1153     else {
1154 	total_balanced = (((sp[total].outsize/(off_t)1024) * (off_t)conf_dumpcycle)
1155 			    / (off_t)(runs_per_cycle * later)) * (off_t)1024;
1156     }
1157 
1158     empty_day = 0;
1159     g_printf(_("\n due-date  #fs    orig %cB     out %cB   balance\n"),
1160 	   displayunit[0], displayunit[0]);
1161     g_printf("----------------------------------------------\n");
1162     for(seq = 0; seq < later; seq++) {
1163 	if(sp[seq].disks == 0 &&
1164 	   ((seq > 0 && sp[seq-1].disks == 0) ||
1165 	    ((seq < later-1) && sp[seq+1].disks == 0))) {
1166 	    empty_day++;
1167 	}
1168 	else {
1169 	    if(empty_day > 0) {
1170 		g_printf("\n");
1171 		empty_day = 0;
1172 	    }
1173 	    g_printf(_("%-9.9s  %3d %10lld %10lld "),
1174 		   seqdatestr(seq), sp[seq].disks,
1175 		   (long long)sp[seq].origsize,
1176 		   (long long)sp[seq].outsize);
1177 	    if(!sp[seq].outsize) g_printf("     --- \n");
1178 	    else g_printf(_("%+8.1lf%%\n"),
1179 			(((double)sp[seq].outsize - (double)balanced) * 100.0 /
1180 			(double)balanced));
1181 	}
1182     }
1183 
1184     if(sp[later].disks != 0) {
1185 	g_printf(_("later      %3d %10lld %10lld "),
1186 	       sp[later].disks,
1187 	       (long long)sp[later].origsize,
1188 	       (long long)sp[later].outsize);
1189 	if(!sp[later].outsize) g_printf("     --- \n");
1190 	else g_printf(_("%+8.1lf%%\n"),
1191 		    (((double)sp[later].outsize - (double)balanced) * 100.0 /
1192 		    (double)balanced));
1193     }
1194     g_printf("----------------------------------------------\n");
1195     g_printf(_("TOTAL      %3d %10lld %10lld %9lld\n"),
1196 	   sp[total].disks,
1197 	   (long long)sp[total].origsize,
1198 	   (long long)sp[total].outsize,
1199 	   (long long)total_balanced);
1200     if (sp[balance].origsize != sp[total].origsize ||
1201         sp[balance].outsize != sp[total].outsize ||
1202 	balanced != total_balanced) {
1203 	g_printf(_("BALANCED       %10lld %10lld %9lld\n"),
1204 	       (long long)sp[balance].origsize,
1205 	       (long long)sp[balance].outsize,
1206 	       (long long)balanced);
1207     }
1208     if (sp[distinct].disks != sp[total].disks) {
1209 	g_printf(_("DISTINCT   %3d %10lld %10lld\n"),
1210 	       sp[distinct].disks,
1211 	       (long long)sp[distinct].origsize,
1212 	       (long long)sp[distinct].outsize);
1213     }
1214     g_printf(plural(_("  (estimated %d run per dumpcycle)\n"),
1215 		  _("  (estimated %d runs per dumpcycle)\n"),
1216 		  runs_per_cycle),
1217 	   runs_per_cycle);
1218     if (overdue) {
1219 	g_printf(plural(_(" (%d filesystem overdue."),
1220 		      _(" (%d filesystems overdue."), overdue),
1221 	       overdue);
1222 	g_printf(plural(_(" The most being overdue %d day.)\n"),
1223 	              _(" The most being overdue %d days.)\n"), max_overdue),
1224 	       max_overdue);
1225     }
1226     amfree(sp);
1227 }
1228 
1229 
1230 /* ----------------------------------------------- */
1231 
1232 void
find(int argc,char ** argv)1233 find(
1234     int		argc,
1235     char **	argv)
1236 {
1237     int start_argc;
1238     char *sort_order = NULL;
1239     find_result_t *output_find;
1240     char *errstr;
1241     char **output_find_log;
1242     char **name;
1243 
1244     if(argc < 3) {
1245 	g_fprintf(stderr,
1246 		_("%s: expecting \"find [--sort <hkdlpbfw>] [hostname [<disk>]]*\"\n"),
1247 		get_pname());
1248 	usage();
1249     }
1250 
1251 
1252     sort_order = newstralloc(sort_order, DEFAULT_SORT_ORDER);
1253     if (opt_sort) {
1254 	size_t i, valid_sort=1;
1255 
1256 	for(i = strlen(opt_sort); i > 0; i--) {
1257 	    switch (opt_sort[i - 1]) {
1258 	    case 'h':
1259 	    case 'H':
1260 	    case 'k':
1261 	    case 'K':
1262 	    case 'd':
1263 	    case 'D':
1264 	    case 'f':
1265 	    case 'F':
1266 	    case 'l':
1267 	    case 'L':
1268 	    case 'p':
1269 	    case 'P':
1270 	    case 'b':
1271 	    case 'B':
1272 	    case 'w':
1273 	    case 'W':
1274 		    break;
1275 	    default: valid_sort=0;
1276 	    }
1277 	}
1278 	if(valid_sort) {
1279 	    sort_order = newstralloc(sort_order, opt_sort);
1280 	} else {
1281 	    g_printf(_("Invalid sort order: %s\n"), opt_sort);
1282 	    g_printf(_("Use default sort order: %s\n"), sort_order);
1283 	}
1284     }
1285     start_argc=4;
1286     errstr = match_disklist(&diskq, exact_match, argc-(start_argc-1),
1287 						 argv+(start_argc-1));
1288 
1289     /* check all log file exists */
1290     output_find_log = find_log();
1291     for (name = output_find_log; *name != NULL; name++) {
1292         amfree(*name);
1293     }
1294     amfree(output_find_log);
1295 
1296     output_find = find_dump(&diskq); /* Add deleted dump to diskq */
1297     if(argc-(start_argc-1) > 0) {
1298 	find_result_t *afind = NULL;
1299 	find_result_t *afind_next = NULL;
1300 	find_result_t *new_output_find = NULL;
1301 	disk_t *dp;
1302 
1303 	amfree(errstr);
1304 	errstr = match_disklist(&diskq, exact_match, argc-(start_argc-1),
1305 						     argv+(start_argc-1));
1306 	if (errstr) {
1307 	    g_printf("%s", errstr);
1308 	    amfree(errstr);
1309 	}
1310 	for (afind = output_find; afind; afind = afind_next) {
1311 	    afind_next = afind->next;
1312 	    dp = lookup_disk(afind->hostname, afind->diskname);
1313 	    if (dp->todo) {
1314 		afind->next = new_output_find;
1315 		new_output_find = afind;
1316 	    } else {
1317 		amfree(afind);
1318 	    }
1319 	}
1320 	output_find = new_output_find;
1321     } else if (errstr) {
1322 	g_printf("%s", errstr);
1323 	amfree(errstr);
1324     }
1325 
1326     sort_find_result(sort_order, &output_find);
1327     print_find_result(output_find);
1328     free_find_result(&output_find);
1329 
1330     amfree(sort_order);
1331 }
1332 
1333 
1334 /* ------------------------ */
1335 
1336 static GSList *
get_file_list(int argc,char ** argv,int allow_empty,gboolean exact_match)1337 get_file_list(
1338     int argc,
1339     char **argv,
1340     int allow_empty,
1341     gboolean exact_match)
1342 {
1343     GSList * file_list = NULL;
1344     GSList * dumplist;
1345     int flags;
1346 
1347     flags = CMDLINE_PARSE_DATESTAMP;
1348     if (allow_empty) flags |= CMDLINE_EMPTY_TO_WILDCARD;
1349     if (exact_match) flags |= CMDLINE_EXACT_MATCH;
1350     dumplist = cmdline_parse_dumpspecs(argc, argv, flags);
1351 
1352     file_list = cmdline_match_holding(dumplist);
1353     dumpspec_list_free(dumplist);
1354 
1355     return file_list;
1356 }
1357 
1358 /* Given a file header, find the history element in curinfo most likely
1359  * corresponding to that dump (this is not an exact science).
1360  *
1361  * @param info: the info_t element for this DLE
1362  * @param file: the header of the file
1363  * @returns: index of the matching history element, or -1 if not found
1364  */
1365 static int
holding_file_find_history(info_t * info,dumpfile_t * file)1366 holding_file_find_history(
1367     info_t *info,
1368     dumpfile_t *file)
1369 {
1370     int matching_hist_idx = -1;
1371     int nhist;
1372     int i;
1373 
1374     /* Begin by trying to find the history element matching this dump.
1375      * The datestamp on the dump is for the entire run of amdump, while the
1376      * 'date' in the history element of 'info' is the time the dump itself
1377      * began.  A matching history element, then, is the earliest element
1378      * with a 'date' equal to or later than the date of the dumpfile.
1379      *
1380      * We compare using formatted datestamps; even using seconds since epoch,
1381      * we would still face timezone issues, and have to do a reverse (timezone
1382      * to gmt) translation.
1383      */
1384 
1385     /* get to the end of the history list and search backward */
1386     for (nhist = 0; info->history[nhist].level > -1; nhist++) /* empty loop */;
1387     for (i = nhist-1; i > -1; i--) {
1388         char *info_datestamp = get_timestamp_from_time(info->history[i].date);
1389         int order = strcmp(file->datestamp, info_datestamp);
1390         amfree(info_datestamp);
1391 
1392         if (order <= 0) {
1393             /* only a match if the levels are equal */
1394             if (info->history[i].level == file->dumplevel) {
1395                 matching_hist_idx = i;
1396             }
1397             break;
1398         }
1399     }
1400 
1401     return matching_hist_idx;
1402 }
1403 
1404 /* A holding file is 'outdated' if a subsequent dump of the same DLE was made
1405  * at the same level or a lower leve; for example, a level 2 dump is outdated if
1406  * there is a subsequent level 2, or a subsequent level 0.
1407  *
1408  * @param file: the header of the file
1409  * @returns: true if the file is outdated
1410  */
1411 static int
holding_file_is_outdated(dumpfile_t * file)1412 holding_file_is_outdated(
1413     dumpfile_t *file)
1414 {
1415     info_t info;
1416     int matching_hist_idx;
1417 
1418     if (get_info(file->name, file->disk, &info) == -1) {
1419 	return 0; /* assume it's not outdated */
1420     }
1421 
1422     /* if the last level is less than the level of this dump, then
1423      * it's outdated */
1424     if (info.last_level < file->dumplevel)
1425 	return 1;
1426 
1427     /* otherwise, we need to see if this dump is the last at its level */
1428     matching_hist_idx = holding_file_find_history(&info, file);
1429     if (matching_hist_idx == -1) {
1430         return 0; /* assume it's not outdated */
1431     }
1432 
1433     /* compare the date of the history element with the most recent date
1434      * for this level.  If they match, then this is the last dump at this
1435      * level, and we checked above for more recent lower-level dumps, so
1436      * the dump is not outdated. */
1437     if (info.history[matching_hist_idx].date ==
1438 	info.inf[info.history[matching_hist_idx].level].date) {
1439 	return 0;
1440     } else {
1441 	return 1;
1442     }
1443 }
1444 
1445 static int
remove_holding_file_from_catalog(char * filename)1446 remove_holding_file_from_catalog(
1447     char *filename)
1448 {
1449     static int warnings_printed; /* only print once per invocation */
1450     dumpfile_t file;
1451     info_t info;
1452     int matching_hist_idx = -1;
1453     history_t matching_hist; /* will be a copy */
1454     int i;
1455 
1456     if (!holding_file_get_dumpfile(filename, &file)) {
1457         g_printf(_("Could not read holding file %s\n"), filename);
1458         return 0;
1459     }
1460 
1461     if (get_info(file.name, file.disk, &info) == -1) {
1462 	g_printf(_("WARNING: No curinfo record for %s:%s\n"), file.name, file.disk);
1463 	dumpfile_free_data(&file);
1464 	return 1; /* not an error */
1465     }
1466 
1467     matching_hist_idx = holding_file_find_history(&info, &file);
1468 
1469     if (matching_hist_idx == -1) {
1470         g_printf(_("WARNING: No dump matching %s found in curinfo.\n"), filename);
1471 	dumpfile_free_data(&file);
1472 	return 1; /* not an error */
1473     }
1474 
1475     /* make a copy */
1476     matching_hist = info.history[matching_hist_idx];
1477 
1478     /* Remove the history element itself before doing the stats */
1479     for (i = matching_hist_idx; i < NB_HISTORY; i++) {
1480         info.history[i] = info.history[i+1];
1481     }
1482     info.history[NB_HISTORY].level = -1;
1483 
1484     /* Remove stats for that history element, if necessary.  Doing so
1485      * will result in an inconsistent set of backups, so we warn the
1486      * user and adjust last_level to make the next dump get us a
1487      * consistent picture. */
1488     if (matching_hist.date == info.inf[matching_hist.level].date) {
1489         /* search for an earlier dump at this level */
1490         for (i = matching_hist_idx; info.history[i].level > -1; i++) {
1491             if (info.history[i].level == matching_hist.level)
1492                 break;
1493         }
1494 
1495         if (info.history[i].level < 0) {
1496             /* not found => zero it out */
1497             info.inf[matching_hist.level].date = (time_t)-1; /* flag as not set */
1498             info.inf[matching_hist.level].label[0] = '\0';
1499         } else {
1500             /* found => reconstruct stats as best we can */
1501             info.inf[matching_hist.level].size = info.history[i].size;
1502             info.inf[matching_hist.level].csize = info.history[i].csize;
1503             info.inf[matching_hist.level].secs = info.history[i].secs;
1504             info.inf[matching_hist.level].date = info.history[i].date;
1505             info.inf[matching_hist.level].filenum = 0; /* we don't know */
1506             info.inf[matching_hist.level].label[0] = '\0'; /* we don't know */
1507         }
1508 
1509         /* set last_level to the level we just deleted, and set command
1510          * appropriately to make sure planner does a new dump at this level
1511          * or lower */
1512         info.last_level = matching_hist.level;
1513         if (info.last_level == 0) {
1514             g_printf(_("WARNING: Deleting the most recent full dump; forcing a full dump at next run.\n"));
1515             SET(info.command, FORCE_FULL);
1516         } else {
1517             g_printf(_("WARNING: Deleting the most recent level %d dump; forcing a level %d dump or \nWARNING: lower at next run.\n"),
1518                 info.last_level, info.last_level);
1519             SET(info.command, FORCE_NO_BUMP);
1520         }
1521 
1522         /* Search for and display any subsequent runs that depended on this one */
1523         warnings_printed = 0;
1524         for (i = matching_hist_idx-1; i >= 0; i--) {
1525             char *datestamp;
1526             if (info.history[i].level <= matching_hist.level) break;
1527 
1528             datestamp = get_timestamp_from_time(info.history[i].date);
1529             g_printf(_("WARNING: Level %d dump made %s can no longer be accurately restored.\n"),
1530                 info.history[i].level, datestamp);
1531             amfree(datestamp);
1532 
1533             warnings_printed = 1;
1534         }
1535         if (warnings_printed)
1536             g_printf(_("WARNING: (note, dates shown above are for dumps, and may be later than the\nWARNING: corresponding run date)\n"));
1537     }
1538 
1539     /* recalculate consecutive_runs based on the history: find the first run
1540      * at this level, and then count the consecutive runs at that level. This
1541      * number may be zero (if we just deleted the last run at this level) */
1542     info.consecutive_runs = 0;
1543     for (i = 0; info.history[i].level >= 0; i++) {
1544         if (info.history[i].level == info.last_level) break;
1545     }
1546     while (info.history[i+info.consecutive_runs].level == info.last_level)
1547         info.consecutive_runs++;
1548 
1549     /* this function doesn't touch the performance stats */
1550 
1551     /* write out the changes */
1552     if (put_info(file.name, file.disk, &info) == -1) {
1553 	g_printf(_("Could not write curinfo record for %s:%s\n"), file.name, file.disk);
1554 	dumpfile_free_data(&file);
1555 	return 0;
1556     }
1557 
1558     dumpfile_free_data(&file);
1559     return 1;
1560 }
1561 
1562 void
holding(int argc,char ** argv)1563 holding(
1564     int		argc,
1565     char **	argv)
1566 {
1567     GSList *file_list;
1568     GSList *li;
1569     enum { HOLDING_USAGE, HOLDING_LIST, HOLDING_DELETE } action = HOLDING_USAGE;
1570     int long_list = 0;
1571     int outdated_list = 0;
1572     dumpfile_t file;
1573 
1574     if (argc < 4)
1575         action = HOLDING_USAGE;
1576     else if (strcmp(argv[3], "list") == 0 && argc >= 4)
1577         action = HOLDING_LIST;
1578     else if (strcmp(argv[3], "delete") == 0 && argc > 4)
1579         action = HOLDING_DELETE;
1580 
1581     switch (action) {
1582         case HOLDING_USAGE:
1583             g_fprintf(stderr,
1584                     _("%s: expecting \"holding list [-l] [-d]\" or \"holding delete <host> [ .. ]\"\n"),
1585                     get_pname());
1586             usage();
1587             return;
1588 
1589         case HOLDING_LIST:
1590 	    long_list = opt_long;
1591 	    outdated_list = opt_outdated;
1592             argc -= 4; argv += 4;
1593 
1594 	    /* header */
1595             if (long_list) {
1596                 g_printf("%-10s %-2s %-4s %s\n",
1597 		    _("size (kB)"), _("lv"), _("outd"), _("dump specification"));
1598             }
1599 
1600             file_list = get_file_list(argc, argv, 1, exact_match);
1601             for (li = file_list; li != NULL; li = li->next) {
1602                 char *dumpstr;
1603 		int is_outdated;
1604 
1605                 if (!holding_file_get_dumpfile((char *)li->data, &file)) {
1606                     g_fprintf(stderr, _("Error reading %s\n"), (char *)li->data);
1607                     continue;
1608                 }
1609 
1610 	        is_outdated = holding_file_is_outdated(&file);
1611 
1612                 dumpstr = cmdline_format_dumpspec_components(file.name, file.disk, file.datestamp, NULL);
1613 		/* only print this entry if we're printing everything, or if it's outdated and
1614 		 * we're only printing outdated files (-o) */
1615 		if (!outdated_list || is_outdated) {
1616 		    if (long_list) {
1617 			g_printf("%-10lld %-2d %-4s %s\n",
1618 			       (long long)holding_file_size((char *)li->data, 0),
1619 			       file.dumplevel,
1620 			       is_outdated? " *":"",
1621 			       dumpstr);
1622 		    } else {
1623 			g_printf("%s\n", dumpstr);
1624 		    }
1625 		}
1626                 amfree(dumpstr);
1627 		dumpfile_free_data(&file);
1628             }
1629             slist_free_full(file_list, g_free);
1630             break;
1631 
1632         case HOLDING_DELETE:
1633             argc -= 4; argv += 4;
1634 
1635             file_list = get_file_list(argc, argv, 0, exact_match);
1636             for (li = file_list; li != NULL; li = li->next) {
1637                 g_fprintf(stderr, _("Deleting '%s'\n"), (char *)li->data);
1638                 /* remove it from the catalog */
1639                 if (!remove_holding_file_from_catalog((char *)li->data))
1640                     exit(1);
1641 
1642                 /* unlink it */
1643                 if (!holding_file_unlink((char *)li->data)) {
1644                     error(_("Could not delete '%s'"), (char *)li->data);
1645                 }
1646             }
1647             slist_free_full(file_list, g_free);
1648             break;
1649     }
1650 }
1651 
1652 
1653 /* ------------------------ */
1654 
1655 
1656 /* shared code with planner.c */
1657 
1658 int
bump_thresh(int level)1659 bump_thresh(
1660     int		level)
1661 {
1662     gint64 bump = getconf_int64(CNF_BUMPSIZE);
1663     double mult = getconf_real(CNF_BUMPMULT);
1664 
1665     while(--level)
1666 	bump = (int)((double)bump * mult);
1667     return bump;
1668 }
1669 
1670 void
bumpsize(int argc,char ** argv)1671 bumpsize(
1672     int		argc,
1673     char **	argv)
1674 {
1675     int l;
1676     int conf_bumppercent = getconf_int(CNF_BUMPPERCENT);
1677     double conf_bumpmult = getconf_real(CNF_BUMPMULT);
1678 
1679     (void)argc;	/* Quiet unused parameter warning */
1680     (void)argv;	/* Quiet unused parameter warning */
1681 
1682     g_printf(_("Current bump parameters:\n"));
1683     if(conf_bumppercent == 0) {
1684 	g_printf(_("  bumpsize %5jd KB\t- minimum savings (threshold) to bump level 1 -> 2\n"),
1685 	       (intmax_t)getconf_int64(CNF_BUMPSIZE));
1686 	g_printf(_("  bumpdays %5d\t- minimum days at each level\n"),
1687 	       getconf_int(CNF_BUMPDAYS));
1688 	g_printf(_("  bumpmult %5.5lg\t- threshold = bumpsize * bumpmult**(level-1)\n\n"),
1689 	       conf_bumpmult);
1690 
1691 	g_printf(_("      Bump -> To  Threshold\n"));
1692 	for(l = 1; l < 9; l++)
1693 	    g_printf(_("\t%d  ->  %d  %9d KB\n"), l, l+1, bump_thresh(l));
1694 	putchar('\n');
1695     }
1696     else {
1697 	double bumppercent = (double)conf_bumppercent;
1698 
1699 	g_printf(_("  bumppercent %3d %%\t- minimum savings (threshold) to bump level 1 -> 2\n"),
1700 	       conf_bumppercent);
1701 	g_printf(_("  bumpdays %5d\t- minimum days at each level\n"),
1702 	       getconf_int(CNF_BUMPDAYS));
1703 	g_printf(_("  bumpmult %5.5lg\t- threshold = disk_size * bumppercent * bumpmult**(level-1)\n\n"),
1704 	       conf_bumpmult);
1705 	g_printf(_("      Bump -> To  Threshold\n"));
1706 	for(l = 1; l < 9; l++) {
1707 	    g_printf(_("\t%d  ->  %d  %7.2lf %%\n"), l, l+1, bumppercent);
1708 	    bumppercent *= conf_bumpmult;
1709 	    if(bumppercent >= 100.000) { bumppercent = 100.0;}
1710 	}
1711 	putchar('\n');
1712     }
1713 }
1714 
1715 /* ----------------------------------------------- */
1716 
1717 void export_one(disk_t *dp);
1718 
1719 void
export_db(int argc,char ** argv)1720 export_db(
1721     int		argc,
1722     char **	argv)
1723 {
1724     disk_t *dp;
1725     time_t curtime;
1726     char hostname[MAX_HOSTNAME_LENGTH+1];
1727     int i;
1728 
1729     g_printf(_("CURINFO Version %s CONF %s\n"), VERSION, getconf_str(CNF_ORG));
1730 
1731     curtime = time(0);
1732     if(gethostname(hostname, SIZEOF(hostname)-1) == -1) {
1733 	error(_("could not determine host name: %s\n"), strerror(errno));
1734 	/*NOTREACHED*/
1735     }
1736     hostname[SIZEOF(hostname)-1] = '\0';
1737     g_printf(_("# Generated by:\n#    host: %s\n#    date: %s"),
1738 	   hostname, ctime(&curtime));
1739 
1740     g_printf(_("#    command:"));
1741     for(i = 0; i < argc; i++)
1742 	g_printf(_(" %s"), argv[i]);
1743 
1744     g_printf(_("\n# This file can be merged back in with \"amadmin import\".\n"));
1745     g_printf(_("# Edit only with care.\n"));
1746 
1747     if(argc >= 4)
1748 	diskloop(argc, argv, "export", export_one);
1749     else for(dp = diskq.head; dp != NULL; dp = dp->next)
1750 	export_one(dp);
1751 }
1752 
1753 void
export_one(disk_t * dp)1754 export_one(
1755     disk_t *	dp)
1756 {
1757     info_t info;
1758     int i,l;
1759     char *qhost, *qdisk;
1760 
1761     if(get_info(dp->host->hostname, dp->name, &info)) {
1762 	g_fprintf(stderr, _("Warning: no curinfo record for %s:%s\n"),
1763 		dp->host->hostname, dp->name);
1764 	return;
1765     }
1766     qhost = quote_string(dp->host->hostname);
1767     qdisk = quote_string(dp->name);
1768     g_printf(_("host: %s\ndisk: %s\n"), qhost, qdisk);
1769     g_printf(_("command: %u\n"), info.command);
1770     g_printf(_("last_level: %d\n"),info.last_level);
1771     g_printf(_("consecutive_runs: %d\n"),info.consecutive_runs);
1772     g_printf(_("full-rate:"));
1773     for(i=0;i<AVG_COUNT;i++) g_printf(_(" %lf"), info.full.rate[i]);
1774     g_printf(_("\nfull-comp:"));
1775     for(i=0;i<AVG_COUNT;i++) g_printf(_(" %lf"), info.full.comp[i]);
1776 
1777     g_printf(_("\nincr-rate:"));
1778     for(i=0;i<AVG_COUNT;i++) g_printf(_(" %lf"), info.incr.rate[i]);
1779     g_printf(_("\nincr-comp:"));
1780     for(i=0;i<AVG_COUNT;i++) g_printf(_(" %lf"), info.incr.comp[i]);
1781     g_printf("\n");
1782     for(l=0;l<DUMP_LEVELS;l++) {
1783 	if(info.inf[l].date < (time_t)0 && info.inf[l].label[0] == '\0') continue;
1784 	g_printf(_("stats: %d %lld %lld %jd %jd %lld %s\n"), l,
1785 	       (long long)info.inf[l].size,
1786 	       (long long)info.inf[l].csize,
1787 	       (intmax_t)info.inf[l].secs,
1788 	       (intmax_t)info.inf[l].date,
1789 	       (long long)info.inf[l].filenum,
1790 	       info.inf[l].label);
1791     }
1792     for(l=0;info.history[l].level > -1;l++) {
1793 	g_printf(_("history: %d %lld %lld %jd\n"),
1794 	       info.history[l].level,
1795 	       (long long)info.history[l].size,
1796 	       (long long)info.history[l].csize,
1797 	       (intmax_t)info.history[l].date);
1798     }
1799     g_printf("//\n");
1800     amfree(qhost);
1801     amfree(qdisk);
1802 }
1803 
1804 /* ----------------------------------------------- */
1805 
1806 int import_one(void);
1807 char *impget_line(void);
1808 
1809 void
import_db(int argc,char ** argv)1810 import_db(
1811     int		argc,
1812     char **	argv)
1813 {
1814     int vers_maj;
1815     int vers_min;
1816     int vers_patch;
1817     int newer;
1818     char *org;
1819     char *line = NULL;
1820     char *hdr;
1821     char *s;
1822     int rc;
1823     int ch;
1824 
1825     (void)argc;	/* Quiet unused parameter warning */
1826     (void)argv;	/* Quiet unused parameter warning */
1827 
1828     /* process header line */
1829 
1830     if((line = agets(stdin)) == NULL) {
1831 	g_fprintf(stderr, _("%s: empty input.\n"), get_pname());
1832 	return;
1833     }
1834 
1835     s = line;
1836     ch = *s++;
1837 
1838     hdr = "version";
1839     if(strncmp_const_skip(s - 1, "CURINFO Version", s, ch) != 0) {
1840 	goto bad_header;
1841     }
1842     ch = *s++;
1843     skip_whitespace(s, ch);
1844     if(ch == '\0'
1845        || sscanf(s - 1, "%d.%d.%d", &vers_maj, &vers_min, &vers_patch) != 3) {
1846 	vers_patch = -1;
1847 	if (sscanf(s - 1, "%d.%d", &vers_maj, &vers_min) != 2) {
1848 	    goto bad_header;
1849 	}
1850     }
1851 
1852     skip_integer(s, ch);			/* skip over major */
1853     if(ch != '.') {
1854 	goto bad_header;
1855     }
1856     ch = *s++;
1857     skip_integer(s, ch);			/* skip over minor */
1858     if (vers_patch != -1) {
1859 	if (ch != '.') {
1860 	    goto bad_header;
1861 	}
1862 	ch = *s++;
1863 	skip_integer(s, ch);			/* skip over patch */
1864     } else {
1865 	vers_patch = 0;
1866     }
1867 
1868     hdr = "comment";
1869     if(ch == '\0') {
1870 	goto bad_header;
1871     }
1872     skip_non_whitespace(s, ch);
1873     s[-1] = '\0';
1874 
1875     hdr = "CONF";
1876     skip_whitespace(s, ch);			/* find the org keyword */
1877     if(ch == '\0' || strncmp_const_skip(s - 1, "CONF", s, ch) != 0) {
1878 	goto bad_header;
1879     }
1880     ch = *s++;
1881 
1882     hdr = "org";
1883     skip_whitespace(s, ch);			/* find the org string */
1884     if(ch == '\0') {
1885 	goto bad_header;
1886     }
1887     org = s - 1;
1888 
1889     /*@ignore@*/
1890     newer = (vers_maj != VERSION_MAJOR)? vers_maj > VERSION_MAJOR :
1891 	    (vers_min != VERSION_MINOR)? vers_min > VERSION_MINOR :
1892 					 vers_patch > VERSION_PATCH;
1893     if(newer)
1894 	g_fprintf(stderr,
1895 	     _("%s: WARNING: input is from newer Amanda version: %d.%d.%d.\n"),
1896 		get_pname(), vers_maj, vers_min, vers_patch);
1897     /*@end@*/
1898 
1899     if(strcmp(org, getconf_str(CNF_ORG)) != 0) {
1900 	g_fprintf(stderr, _("%s: WARNING: input is from different org: %s\n"),
1901 		get_pname(), org);
1902     }
1903 
1904     do {
1905     	rc = import_one();
1906     } while (rc);
1907 
1908     amfree(line);
1909     return;
1910 
1911  bad_header:
1912 
1913     /*@i@*/ amfree(line);
1914     g_fprintf(stderr, _("%s: bad CURINFO header line in input: %s.\n"),
1915 	    get_pname(), hdr);
1916     g_fprintf(stderr, _("    Was the input in \"amadmin export\" format?\n"));
1917     return;
1918 }
1919 
1920 
1921 int
import_one(void)1922 import_one(void)
1923 {
1924     info_t info;
1925     stats_t onestat;
1926     int rc, level;
1927     char *line = NULL;
1928     char *s, *fp;
1929     int ch;
1930     int nb_history, i;
1931     char *hostname = NULL;
1932     char *diskname = NULL;
1933     long long off_t_tmp;
1934     long long time_t_tmp;
1935 
1936     memset(&info, 0, SIZEOF(info_t));
1937 
1938     for(level = 0; level < DUMP_LEVELS; level++) {
1939         info.inf[level].date = (time_t)-1;
1940     }
1941 
1942     /* get host: disk: command: lines */
1943 
1944     hostname = diskname = NULL;
1945 
1946     if((line = impget_line()) == NULL) return 0;	/* nothing there */
1947     s = line;
1948     ch = *s++;
1949 
1950     skip_whitespace(s, ch);
1951     if(ch == '\0' || strncmp_const_skip(s - 1, "host:", s, ch) != 0) goto parse_err;
1952     skip_whitespace(s, ch);
1953     if(ch == '\0') goto parse_err;
1954     fp = s-1;
1955     skip_quoted_string(s, ch);
1956     s[-1] = '\0';
1957     hostname = unquote_string(fp);
1958     s[-1] = (char)ch;
1959 
1960     skip_whitespace(s, ch);
1961     while (ch == 0) {
1962       amfree(line);
1963       if((line = impget_line()) == NULL) goto shortfile_err;
1964       s = line;
1965       ch = *s++;
1966       skip_whitespace(s, ch);
1967     }
1968     if(strncmp_const_skip(s - 1, "disk:", s, ch) != 0) goto parse_err;
1969     skip_whitespace(s, ch);
1970     if(ch == '\0') goto parse_err;
1971     fp = s-1;
1972     skip_quoted_string(s, ch);
1973     s[-1] = '\0';
1974     diskname = unquote_string(fp);
1975     s[-1] = (char)ch;
1976 
1977     amfree(line);
1978     if((line = impget_line()) == NULL) goto shortfile_err;
1979     if(sscanf(line, "command: %u", &info.command) != 1) goto parse_err;
1980 
1981     /* get last_level and consecutive_runs */
1982 
1983     amfree(line);
1984     if((line = impget_line()) == NULL) goto shortfile_err;
1985     rc = sscanf(line, "last_level: %d", &info.last_level);
1986     if(rc == 1) {
1987 	amfree(line);
1988 	if((line = impget_line()) == NULL) goto shortfile_err;
1989 	if(sscanf(line, "consecutive_runs: %d", &info.consecutive_runs) != 1) goto parse_err;
1990 	amfree(line);
1991 	if((line = impget_line()) == NULL) goto shortfile_err;
1992     }
1993 
1994     /* get rate: and comp: lines for full dumps */
1995 
1996     rc = sscanf(line, "full-rate: %lf %lf %lf",
1997 		&info.full.rate[0], &info.full.rate[1], &info.full.rate[2]);
1998     if(rc != 3) goto parse_err;
1999 
2000     amfree(line);
2001     if((line = impget_line()) == NULL) goto shortfile_err;
2002     rc = sscanf(line, "full-comp: %lf %lf %lf",
2003 		&info.full.comp[0], &info.full.comp[1], &info.full.comp[2]);
2004     if(rc != 3) goto parse_err;
2005 
2006     /* get rate: and comp: lines for incr dumps */
2007 
2008     amfree(line);
2009     if((line = impget_line()) == NULL) goto shortfile_err;
2010     rc = sscanf(line, "incr-rate: %lf %lf %lf",
2011 		&info.incr.rate[0], &info.incr.rate[1], &info.incr.rate[2]);
2012     if(rc != 3) goto parse_err;
2013 
2014     amfree(line);
2015     if((line = impget_line()) == NULL) goto shortfile_err;
2016     rc = sscanf(line, "incr-comp: %lf %lf %lf",
2017 		&info.incr.comp[0], &info.incr.comp[1], &info.incr.comp[2]);
2018     if(rc != 3) goto parse_err;
2019 
2020     /* get stats for dump levels */
2021 
2022     while(1) {
2023 	amfree(line);
2024 	if((line = impget_line()) == NULL) goto shortfile_err;
2025 	if(strncmp_const(line, "//") == 0) {
2026 	    /* end of record */
2027 	    break;
2028 	}
2029 	if(strncmp_const(line, "history:") == 0) {
2030 	    /* end of record */
2031 	    break;
2032 	}
2033 	memset(&onestat, 0, SIZEOF(onestat));
2034 
2035 	s = line;
2036 	ch = *s++;
2037 
2038 	skip_whitespace(s, ch);
2039 	if(ch == '\0' || strncmp_const_skip(s - 1, "stats:", s, ch) != 0) {
2040 	    goto parse_err;
2041 	}
2042 
2043 	skip_whitespace(s, ch);
2044 	if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
2045 	    goto parse_err;
2046 	}
2047 	skip_integer(s, ch);
2048 
2049 	skip_whitespace(s, ch);
2050 	if(ch == '\0' || sscanf(s - 1, "%lld", &off_t_tmp) != 1) {
2051 	    goto parse_err;
2052 	}
2053 	onestat.size = (off_t)off_t_tmp;
2054 	skip_integer(s, ch);
2055 
2056 	skip_whitespace(s, ch);
2057 	if(ch == '\0' || sscanf(s - 1, "%lld", &off_t_tmp) != 1) {
2058 	    goto parse_err;
2059 	}
2060 	onestat.csize = (off_t)off_t_tmp;
2061 	skip_integer(s, ch);
2062 
2063 	skip_whitespace(s, ch);
2064 	if(ch == '\0' || sscanf(s - 1, "%lld", &time_t_tmp) != 1) {
2065 	    goto parse_err;
2066 	}
2067         onestat.secs = (time_t)time_t_tmp;
2068 	skip_integer(s, ch);
2069 
2070 	skip_whitespace(s, ch);
2071 	if(ch == '\0' || sscanf(s - 1, "%lld", &time_t_tmp) != 1) {
2072 	    goto parse_err;
2073 	}
2074 	/* time_t not guarranteed to be long */
2075 	/*@i1@*/ onestat.date = (time_t)time_t_tmp;
2076 	skip_integer(s, ch);
2077 
2078 	skip_whitespace(s, ch);
2079 	if(ch != '\0') {
2080 	    if(sscanf(s - 1, "%lld", &off_t_tmp) != 1) {
2081 		goto parse_err;
2082 	    }
2083 	    onestat.filenum = (off_t)off_t_tmp;
2084 	    skip_integer(s, ch);
2085 
2086 	    skip_whitespace(s, ch);
2087 	    if(ch == '\0') {
2088 		if (onestat.filenum != 0)
2089 		    goto parse_err;
2090 		onestat.label[0] = '\0';
2091 	    } else {
2092 		strncpy(onestat.label, s - 1, SIZEOF(onestat.label)-1);
2093 		onestat.label[SIZEOF(onestat.label)-1] = '\0';
2094 	    }
2095 	}
2096 
2097 	if(level < 0 || level > 9) goto parse_err;
2098 
2099 	info.inf[level] = onestat;
2100     }
2101     nb_history = 0;
2102     for(i=0;i<=NB_HISTORY;i++) {
2103 	info.history[i].level = -2;
2104     }
2105     while(1) {
2106 	history_t onehistory;
2107 
2108 	if(line[0] == '/' && line[1] == '/') {
2109 	    info.history[nb_history].level = -2;
2110 	    rc = 0;
2111 	    break;
2112 	}
2113 	memset(&onehistory, 0, SIZEOF(onehistory));
2114 	s = line;
2115 	ch = *s++;
2116 	if(strncmp_const_skip(line, "history:", s, ch) != 0) {
2117 	    break;
2118 	}
2119 
2120 	skip_whitespace(s, ch);
2121 	if(ch == '\0' || sscanf((s - 1), "%d", &onehistory.level) != 1) {
2122 	    break;
2123 	}
2124 	skip_integer(s, ch);
2125 
2126 	skip_whitespace(s, ch);
2127 	if(ch == '\0' || sscanf((s - 1), "%lld", &off_t_tmp) != 1) {
2128 	    break;
2129 	}
2130 	onehistory.size = (off_t)off_t_tmp;
2131 	skip_integer(s, ch);
2132 
2133 	skip_whitespace(s, ch);
2134 	if(ch == '\0' || sscanf((s - 1), "%lld", &off_t_tmp) != 1) {
2135 	    break;
2136 	}
2137 	onehistory.csize = (off_t)off_t_tmp;
2138 	skip_integer(s, ch);
2139 
2140 	skip_whitespace(s, ch);
2141 	if((ch == '\0') || sscanf((s - 1), "%lld", &time_t_tmp) != 1) {
2142 	    break;
2143 	}
2144 	/* time_t not guarranteed to be long */
2145 	/*@i1@*/ onehistory.date = (time_t)time_t_tmp;
2146 	skip_integer(s, ch);
2147 
2148 	info.history[nb_history++] = onehistory;
2149 	amfree(line);
2150 	if((line = impget_line()) == NULL) goto shortfile_err;
2151     }
2152     /*@i@*/ amfree(line);
2153 
2154     /* got a full record, now write it out to the database */
2155 
2156     if(put_info(hostname, diskname, &info)) {
2157 	g_fprintf(stderr, _("%s: error writing record for %s:%s\n"),
2158 		get_pname(), hostname, diskname);
2159     }
2160     amfree(hostname);
2161     amfree(diskname);
2162     return 1;
2163 
2164  parse_err:
2165     /*@i@*/ amfree(line);
2166     amfree(hostname);
2167     amfree(diskname);
2168     g_fprintf(stderr, _("%s: parse error reading import record.\n"), get_pname());
2169     return 0;
2170 
2171  shortfile_err:
2172     /*@i@*/ amfree(line);
2173     amfree(hostname);
2174     amfree(diskname);
2175     g_fprintf(stderr, _("%s: short file reading import record.\n"), get_pname());
2176     return 0;
2177 }
2178 
2179 char *
impget_line(void)2180 impget_line(void)
2181 {
2182     char *line;
2183     char *s;
2184     int ch;
2185 
2186     for(; (line = agets(stdin)) != NULL; free(line)) {
2187 	s = line;
2188 	ch = *s++;
2189 
2190 	skip_whitespace(s, ch);
2191 	if(ch == '#') {
2192 	    /* ignore comment lines */
2193 	    continue;
2194 	} else if(ch) {
2195 	    /* found non-blank, return line */
2196 	    return line;
2197 	}
2198 	/* otherwise, a blank line, so keep going */
2199     }
2200     if(ferror(stdin)) {
2201 	g_fprintf(stderr, _("%s: reading stdin: %s\n"),
2202 		get_pname(), strerror(errno));
2203     }
2204     return NULL;
2205 }
2206 
2207 /* ----------------------------------------------- */
2208 
2209 void
disklist_one(disk_t * dp)2210 disklist_one(
2211     disk_t *	dp)
2212 {
2213     am_host_t *hp;
2214     netif_t *ip;
2215     dumptype_t *dtype = lookup_dumptype(dp->dtype_name);
2216 
2217     hp = dp->host;
2218     ip = hp->netif;
2219 
2220     g_printf("line %d (%s):\n", dp->line, dp->filename);
2221 
2222     g_printf("    host %s:\n", hp->hostname);
2223     g_printf("        interface %s\n",
2224 	   interface_name(ip->config)[0] ? interface_name(ip->config) : "default");
2225     g_printf("    disk %s:\n", dp->name);
2226     if (dp->device) g_printf("        device %s\n", dp->device);
2227 
2228     g_printf("        program \"%s\"\n", dp->program);
2229     if (dp->application)
2230 	g_printf("        application \"%s\"\n", dp->application);
2231 
2232     dump_dumptype(dtype, "  ", print_default, print_source);
2233 
2234     g_printf("        spindle %d\n", dp->spindle);
2235 
2236     g_printf("\n");
2237 }
2238 
2239 void
disklist(int argc,char ** argv)2240 disklist(
2241     int		argc,
2242     char **	argv)
2243 {
2244     disk_t *dp;
2245 
2246     if(argc >= 4)
2247 	diskloop(argc, argv, "disklist", disklist_one);
2248     else
2249 	for(dp = diskq.head; dp != NULL; dp = dp->next)
2250 	    disklist_one(dp);
2251 }
2252 
2253 /* ----------------------------------------------- */
2254 
2255 void
hosts(int argc G_GNUC_UNUSED,char ** argv G_GNUC_UNUSED)2256 hosts(
2257     int		argc G_GNUC_UNUSED,
2258     char **	argv G_GNUC_UNUSED)
2259 {
2260     disk_t *dp;
2261     gint sentinel = 1;
2262     GHashTable *seen = g_hash_table_new(g_str_hash, g_str_equal);
2263 
2264     /* enumerate all hosts, skipping those that have been seen (since
2265      * there may be more than one DLE on a host */
2266     for(dp = diskq.head; dp != NULL; dp = dp->next) {
2267 	char *hostname = dp->host->hostname;
2268 	if (g_hash_table_lookup(seen, hostname))
2269 	    continue;
2270 	g_printf("%s\n", hostname);
2271 	g_hash_table_insert(seen, hostname, &sentinel);
2272     }
2273     g_hash_table_destroy(seen);
2274 }
2275 
2276 /* ----------------------------------------------- */
2277 
2278 void
dles(int argc G_GNUC_UNUSED,char ** argv G_GNUC_UNUSED)2279 dles(
2280     int		argc G_GNUC_UNUSED,
2281     char **	argv G_GNUC_UNUSED)
2282 {
2283     disk_t *dp;
2284 
2285     for(dp = diskq.head; dp != NULL; dp = dp->next)
2286 	g_printf("%s %s\n", dp->host->hostname, dp->name);
2287 }
2288 
2289 /* ----------------------------------------------- */
2290 
2291 void
show_version(int argc,char ** argv)2292 show_version(
2293     int		argc,
2294     char **	argv)
2295 {
2296     int i;
2297 
2298     (void)argc;	/* Quiet unused parameter warning */
2299     (void)argv;	/* Quiet unused parameter warning */
2300 
2301     for(i = 0; version_info[i] != NULL; i++)
2302 	g_printf("%s", version_info[i]);
2303 }
2304 
2305 
show_config(int argc G_GNUC_UNUSED,char ** argv G_GNUC_UNUSED)2306 void show_config(
2307     int argc G_GNUC_UNUSED,
2308     char **argv G_GNUC_UNUSED)
2309 {
2310     dump_configuration(print_default, print_source);
2311 }
2312 
2313