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