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