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