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