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