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