Imported Upstream version 2.6.0p2
[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     int     nb_days = 1;
769     int     runtapes;
770     tape_t *tp;
771     int     i, j;
772     int     skip;
773     int     nb_new_tape;
774
775     if(argc > 4 && strcmp(argv[3],"--days") == 0) {
776         nb_days = atoi(argv[4]);
777         if(nb_days < 1) {
778             g_printf(_("days must be an integer bigger than 0\n"));
779             return;
780         }
781         if (nb_days > 10000)
782             nb_days = 10000;
783     }
784
785     runtapes = getconf_int(CNF_RUNTAPES);
786     tp = lookup_last_reusable_tape(0);
787     skip = 0;
788
789     for ( j=0 ; j < nb_days ; j++ ) {
790         nb_new_tape=0;
791         for ( i=0 ; i < runtapes ; i++ ) {
792             if(i==0)
793                 g_fprintf(stdout, _("The next Amanda run should go onto "));
794             if(tp != NULL) {
795                 if (nb_new_tape > 0) {
796                     if (nb_new_tape == 1)
797                         g_fprintf(stdout, _("1 new tape.\n"));
798                     else
799                         g_fprintf(stdout, _("%d new tapes.\n"), nb_new_tape);
800                     g_fprintf(stdout, "                                   ");
801                     nb_new_tape = 0;
802                 }
803                 g_fprintf(stdout, _("tape %s or a new tape.\n"), tp->label);
804                 if (i < runtapes-1)
805                     g_fprintf(stdout, "                                   ");
806             } else {
807                 nb_new_tape++;
808             }
809             skip++;
810
811             tp = lookup_last_reusable_tape(skip);
812         }
813         if (nb_new_tape > 0) {
814             if (nb_new_tape == 1)
815                 g_fprintf(stdout, _("1 new tape.\n"));
816             else
817                 g_fprintf(stdout, _("%d new tapes.\n"), nb_new_tape);
818         }
819     }
820
821     print_new_tapes(stdout, nb_days * runtapes);
822 }
823
824 /* ----------------------------------------------- */
825
826 void
827 balance(
828     int         argc,
829     char **     argv)
830 {
831     disk_t *dp;
832     struct balance_stats {
833         int disks;
834         off_t origsize, outsize;
835     } *sp;
836     int conf_runspercycle, conf_dumpcycle;
837     int seq, runs_per_cycle, overdue, max_overdue;
838     int later, total, balance, distinct;
839     double fseq, disk_dumpcycle;
840     info_t info;
841     off_t total_balanced, balanced;
842     int empty_day;
843
844     time(&today);
845     conf_dumpcycle = getconf_int(CNF_DUMPCYCLE);
846     conf_runspercycle = getconf_int(CNF_RUNSPERCYCLE);
847     later = conf_dumpcycle;
848     overdue = 0;
849     max_overdue = 0;
850
851     if(argc > 4 && strcmp(argv[3],"--days") == 0) {
852         later = atoi(argv[4]);
853         if(later < 0) later = conf_dumpcycle;
854     }
855     if(later > 10000) later = 10000;
856
857     if(conf_runspercycle == 0) {
858         runs_per_cycle = conf_dumpcycle;
859     } else if(conf_runspercycle == -1 ) {
860         runs_per_cycle = guess_runs_from_tapelist();
861     } else
862         runs_per_cycle = conf_runspercycle;
863
864     if (runs_per_cycle <= 0) {
865         runs_per_cycle = 1;
866     }
867
868     total = later + 1;
869     balance = later + 2;
870     distinct = later + 3;
871
872     sp = (struct balance_stats *)
873         alloc(SIZEOF(struct balance_stats) * (distinct+1));
874
875     for(seq=0; seq <= distinct; seq++) {
876         sp[seq].disks = 0;
877         sp[seq].origsize = sp[seq].outsize = (off_t)0;
878     }
879
880     for(dp = diskq.head; dp != NULL; dp = dp->next) {
881         if(get_info(dp->host->hostname, dp->name, &info)) {
882             g_printf(_("new disk %s:%s ignored.\n"), dp->host->hostname, dp->name);
883             continue;
884         }
885         if (dp->strategy == DS_NOFULL) {
886             continue;
887         }
888         sp[distinct].disks++;
889         sp[distinct].origsize += info.inf[0].size/(off_t)unitdivisor;
890         sp[distinct].outsize += info.inf[0].csize/(off_t)unitdivisor;
891
892         sp[balance].disks++;
893         if(dp->dumpcycle == 0) {
894             sp[balance].origsize += (info.inf[0].size/(off_t)unitdivisor) * (off_t)runs_per_cycle;
895             sp[balance].outsize += (info.inf[0].csize/(off_t)unitdivisor) * (off_t)runs_per_cycle;
896         }
897         else {
898             sp[balance].origsize += (info.inf[0].size/(off_t)unitdivisor) *
899                                     (off_t)(conf_dumpcycle / dp->dumpcycle);
900             sp[balance].outsize += (info.inf[0].csize/(off_t)unitdivisor) *
901                                    (off_t)(conf_dumpcycle / dp->dumpcycle);
902         }
903
904         disk_dumpcycle = (double)dp->dumpcycle;
905         if(dp->dumpcycle <= 0)
906             disk_dumpcycle = ((double)conf_dumpcycle) / ((double)runs_per_cycle);
907
908         seq = next_level0(dp, &info);
909         fseq = seq + 0.0001;
910         do {
911             if(seq < 0) {
912                 overdue++;
913                 if (-seq > max_overdue)
914                     max_overdue = -seq;
915                 seq = 0;
916                 fseq = seq + 0.0001;
917             }
918             if(seq > later) {
919                 seq = later;
920             }
921             
922             sp[seq].disks++;
923             sp[seq].origsize += info.inf[0].size/(off_t)unitdivisor;
924             sp[seq].outsize += info.inf[0].csize/(off_t)unitdivisor;
925
926             if(seq < later) {
927                 sp[total].disks++;
928                 sp[total].origsize += info.inf[0].size/(off_t)unitdivisor;
929                 sp[total].outsize += info.inf[0].csize/(off_t)unitdivisor;
930             }
931             
932             /* See, if there's another run in this dumpcycle */
933             fseq += disk_dumpcycle;
934             seq = (int)fseq;
935         } while (seq < later);
936     }
937
938     if(sp[total].outsize == (off_t)0 && sp[later].outsize == (off_t)0) {
939         g_printf(_("\nNo data to report on yet.\n"));
940         amfree(sp);
941         return;
942     }
943
944     balanced = sp[balance].outsize / (off_t)runs_per_cycle;
945     if(conf_dumpcycle == later) {
946         total_balanced = sp[total].outsize / (off_t)runs_per_cycle;
947     }
948     else {
949         total_balanced = (((sp[total].outsize/(off_t)1024) * (off_t)conf_dumpcycle)
950                             / (off_t)(runs_per_cycle * later)) * (off_t)1024;
951     }
952
953     empty_day = 0;
954     g_printf(_("\n due-date  #fs    orig %cB     out %cB   balance\n"),
955            displayunit[0], displayunit[0]);
956     g_printf("----------------------------------------------\n");
957     for(seq = 0; seq < later; seq++) {
958         if(sp[seq].disks == 0 &&
959            ((seq > 0 && sp[seq-1].disks == 0) ||
960             ((seq < later-1) && sp[seq+1].disks == 0))) {
961             empty_day++;
962         }
963         else {
964             if(empty_day > 0) {
965                 g_printf("\n");
966                 empty_day = 0;
967             }
968             g_printf(_("%-9.9s  %3d %10lld %10lld "),
969                    seqdatestr(seq), sp[seq].disks,
970                    (long long)sp[seq].origsize,
971                    (long long)sp[seq].outsize);
972             if(!sp[seq].outsize) g_printf("     --- \n");
973             else g_printf(_("%+8.1lf%%\n"),
974                         (((double)sp[seq].outsize - (double)balanced) * 100.0 /
975                         (double)balanced));
976         }
977     }
978
979     if(sp[later].disks != 0) {
980         g_printf(_("later      %3d %10lld %10lld "),
981                sp[later].disks,
982                (long long)sp[later].origsize,
983                (long long)sp[later].outsize);
984         if(!sp[later].outsize) g_printf("     --- \n");
985         else g_printf(_("%+8.1lf%%\n"),
986                     (((double)sp[later].outsize - (double)balanced) * 100.0 /
987                     (double)balanced));
988     }
989     g_printf("----------------------------------------------\n");
990     g_printf(_("TOTAL      %3d %10lld %10lld %9lld\n"),
991            sp[total].disks,
992            (long long)sp[total].origsize,
993            (long long)sp[total].outsize,
994            (long long)total_balanced);
995     if (sp[balance].origsize != sp[total].origsize ||
996         sp[balance].outsize != sp[total].outsize ||
997         balanced != total_balanced) {
998         g_printf(_("BALANCED       %10lld %10lld %9lld\n"),
999                (long long)sp[balance].origsize,
1000                (long long)sp[balance].outsize,
1001                (long long)balanced);
1002     }
1003     if (sp[distinct].disks != sp[total].disks) {
1004         g_printf(_("DISTINCT   %3d %10lld %10lld\n"),
1005                sp[distinct].disks,
1006                (long long)sp[distinct].origsize,
1007                (long long)sp[distinct].outsize);
1008     }
1009     g_printf(plural(_("  (estimated %d run per dumpcycle)\n"),
1010                   _("  (estimated %d runs per dumpcycle)\n"),
1011                   runs_per_cycle),
1012            runs_per_cycle);
1013     if (overdue) {
1014         g_printf(plural(_(" (%d filesystem overdue."),
1015                       _(" (%d filesystems overdue."), overdue),
1016                overdue);
1017         g_printf(plural(_(" The most being overdue %d day.)\n"),
1018                       _(" The most being overdue %d days.)\n"), max_overdue),
1019                max_overdue);
1020     }
1021     amfree(sp);
1022 }
1023
1024
1025 /* ----------------------------------------------- */
1026
1027 void
1028 find(
1029     int         argc,
1030     char **     argv)
1031 {
1032     int start_argc;
1033     char *sort_order = NULL;
1034     find_result_t *output_find;
1035     char *errstr;
1036
1037     if(argc < 3) {
1038         g_fprintf(stderr,
1039                 _("%s: expecting \"find [--sort <hkdlpbf>] [hostname [<disk>]]*\"\n"),
1040                 get_pname());
1041         usage();
1042     }
1043
1044
1045     sort_order = newstralloc(sort_order, DEFAULT_SORT_ORDER);
1046     if(argc > 4 && strcmp(argv[3],"--sort") == 0) {
1047         size_t i, valid_sort=1;
1048
1049         for(i = strlen(argv[4]); i > 0; i--) {
1050             switch (argv[4][i - 1]) {
1051             case 'h':
1052             case 'H':
1053             case 'k':
1054             case 'K':
1055             case 'd':
1056             case 'D':
1057             case 'f':
1058             case 'F':
1059             case 'l':
1060             case 'L':
1061             case 'p':
1062             case 'P':
1063             case 'b':
1064             case 'B':
1065                     break;
1066             default: valid_sort=0;
1067             }
1068         }
1069         if(valid_sort) {
1070             sort_order = newstralloc(sort_order, argv[4]);
1071         } else {
1072             g_printf(_("Invalid sort order: %s\n"), argv[4]);
1073             g_printf(_("Use default sort order: %s\n"), sort_order);
1074         }
1075         start_argc=6;
1076     } else {
1077         start_argc=4;
1078     }
1079     errstr = match_disklist(&diskq, argc-(start_argc-1), argv+(start_argc-1));
1080     if (errstr) {
1081         g_printf("%s", errstr);
1082         amfree(errstr);
1083     }
1084
1085     output_find = find_dump(&diskq);
1086     if(argc-(start_argc-1) > 0) {
1087         free_find_result(&output_find);
1088         errstr = match_disklist(&diskq, argc-(start_argc-1),
1089                                         argv+(start_argc-1));
1090         if (errstr) {
1091             g_printf("%s", errstr);
1092             amfree(errstr);
1093         }
1094         output_find = find_dump(NULL);
1095     }
1096
1097     sort_find_result(sort_order, &output_find);
1098     print_find_result(output_find);
1099     free_find_result(&output_find);
1100
1101     amfree(sort_order);
1102 }
1103
1104
1105 /* ------------------------ */
1106
1107 static GSList *
1108 get_file_list(
1109     int argc,
1110     char **argv,
1111     int allow_empty)
1112 {
1113     GSList * file_list = NULL;
1114     GSList * dumplist;
1115     int flags;
1116
1117     flags = CMDLINE_PARSE_DATESTAMP;
1118     if (allow_empty) flags |= CMDLINE_EMPTY_TO_WILDCARD;
1119     dumplist = cmdline_parse_dumpspecs(argc, argv, flags);
1120
1121     file_list = cmdline_match_holding(dumplist);
1122     dumpspec_list_free(dumplist);
1123
1124     return file_list;
1125 }
1126
1127 /* Given a file header, find the history element in curinfo most likely
1128  * corresponding to that dump (this is not an exact science).
1129  *
1130  * @param info: the info_t element for this DLE
1131  * @param file: the header of the file
1132  * @returns: index of the matching history element, or -1 if not found
1133  */
1134 static int
1135 holding_file_find_history(
1136     info_t *info,
1137     dumpfile_t *file)
1138 {
1139     int matching_hist_idx = -1;
1140     int nhist;
1141     int i;
1142
1143     /* Begin by trying to find the history element matching this dump.
1144      * The datestamp on the dump is for the entire run of amdump, while the
1145      * 'date' in the history element of 'info' is the time the dump itself
1146      * began.  A matching history element, then, is the earliest element
1147      * with a 'date' equal to or later than the date of the dumpfile.
1148      *
1149      * We compare using formatted datestamps; even using seconds since epoch,
1150      * we would still face timezone issues, and have to do a reverse (timezone
1151      * to gmt) translation.
1152      */
1153
1154     /* get to the end of the history list and search backward */
1155     for (nhist = 0; info->history[nhist].level > -1; nhist++) /* empty loop */;
1156     for (i = nhist-1; i > -1; i--) {
1157         char *info_datestamp = get_timestamp_from_time(info->history[i].date);
1158         int order = strcmp(file->datestamp, info_datestamp);
1159         amfree(info_datestamp);
1160
1161         if (order <= 0) {
1162             /* only a match if the levels are equal */
1163             if (info->history[i].level == file->dumplevel) {
1164                 matching_hist_idx = i;
1165             }
1166             break;
1167         }
1168     }
1169
1170     return matching_hist_idx;
1171 }
1172
1173 /* A holding file is 'outdated' if a subsequent dump of the same DLE was made
1174  * at the same level or a lower leve; for example, a level 2 dump is outdated if
1175  * there is a subsequent level 2, or a subsequent level 0.
1176  *
1177  * @param file: the header of the file
1178  * @returns: true if the file is outdated
1179  */
1180 static int
1181 holding_file_is_outdated(
1182     dumpfile_t *file)
1183 {
1184     info_t info;
1185     int matching_hist_idx;
1186
1187     if (get_info(file->name, file->disk, &info) == -1) {
1188         return 0; /* assume it's not outdated */
1189     }
1190
1191     /* if the last level is less than the level of this dump, then
1192      * it's outdated */
1193     if (info.last_level < file->dumplevel)
1194         return 1;
1195
1196     /* otherwise, we need to see if this dump is the last at its level */
1197     matching_hist_idx = holding_file_find_history(&info, file);
1198     if (matching_hist_idx == -1) {
1199         return 0; /* assume it's not outdated */
1200     }
1201
1202     /* compare the date of the history element with the most recent date
1203      * for this level.  If they match, then this is the last dump at this
1204      * level, and we checked above for more recent lower-level dumps, so
1205      * the dump is not outdated. */
1206     if (info.history[matching_hist_idx].date == 
1207         info.inf[info.history[matching_hist_idx].level].date) {
1208         return 0;
1209     } else {
1210         return 1;
1211     }
1212 }
1213
1214 static int
1215 remove_holding_file_from_catalog(
1216     char *filename)
1217 {
1218     static int warnings_printed; /* only print once per invocation */
1219     dumpfile_t file;
1220     info_t info;
1221     int matching_hist_idx = -1;
1222     history_t matching_hist; /* will be a copy */
1223     int i;
1224
1225     if (!holding_file_get_dumpfile(filename, &file)) {
1226         g_printf(_("Could not read holding file %s\n"), filename);
1227         return 0;
1228     }
1229
1230     if (get_info(file.name, file.disk, &info) == -1) {
1231             g_printf(_("WARNING: No curinfo record for %s:%s\n"), file.name, file.disk);
1232             return 1; /* not an error */
1233     }
1234
1235     matching_hist_idx = holding_file_find_history(&info, &file);
1236
1237     if (matching_hist_idx == -1) {
1238         g_printf(_("WARNING: No dump matching %s found in curinfo.\n"), filename);
1239         return 1; /* not an error */
1240     }
1241
1242     /* make a copy */
1243     matching_hist = info.history[matching_hist_idx];
1244
1245     /* Remove the history element itself before doing the stats */
1246     for (i = matching_hist_idx; i <= NB_HISTORY; i++) {
1247         info.history[i] = info.history[i+1];
1248     }
1249     info.history[NB_HISTORY].level = -1;
1250
1251     /* Remove stats for that history element, if necessary.  Doing so
1252      * will result in an inconsistent set of backups, so we warn the
1253      * user and adjust last_level to make the next dump get us a 
1254      * consistent picture. */
1255     if (matching_hist.date == info.inf[matching_hist.level].date) {
1256         /* search for an earlier dump at this level */
1257         for (i = matching_hist_idx; info.history[i].level > -1; i++) {
1258             if (info.history[i].level == matching_hist.level)
1259                 break;
1260         }
1261
1262         if (info.history[i].level < 0) {
1263             /* not found => zero it out */
1264             info.inf[matching_hist.level].date = (time_t)-1; /* flag as not set */
1265             info.inf[matching_hist.level].label[0] = '\0';
1266         } else {
1267             /* found => reconstruct stats as best we can */
1268             info.inf[matching_hist.level].size = info.history[i].size;
1269             info.inf[matching_hist.level].csize = info.history[i].csize;
1270             info.inf[matching_hist.level].secs = info.history[i].secs;
1271             info.inf[matching_hist.level].date = info.history[i].date;
1272             info.inf[matching_hist.level].filenum = 0; /* we don't know */
1273             info.inf[matching_hist.level].label[0] = '\0'; /* we don't know */
1274         }
1275
1276         /* set last_level to the level we just deleted, and set command
1277          * appropriately to make sure planner does a new dump at this level
1278          * or lower */
1279         info.last_level = matching_hist.level;
1280         if (info.last_level == 0) {
1281             g_printf(_("WARNING: Deleting the most recent full dump; forcing a full dump at next run.\n"));
1282             SET(info.command, FORCE_FULL);
1283         } else {
1284             g_printf(_("WARNING: Deleting the most recent level %d dump; forcing a level %d dump or \nWARNING: lower at next run.\n"),
1285                 info.last_level, info.last_level);
1286             SET(info.command, FORCE_NO_BUMP);
1287         }
1288
1289         /* Search for and display any subsequent runs that depended on this one */
1290         warnings_printed = 0;
1291         for (i = matching_hist_idx-1; i >= 0; i--) {
1292             char *datestamp;
1293             if (info.history[i].level <= matching_hist.level) break;
1294
1295             datestamp = get_timestamp_from_time(info.history[i].date);
1296             g_printf(_("WARNING: Level %d dump made %s can no longer be accurately restored.\n"), 
1297                 info.history[i].level, datestamp);
1298             amfree(datestamp);
1299
1300             warnings_printed = 1;
1301         }
1302         if (warnings_printed)
1303             g_printf(_("WARNING: (note, dates shown above are for dumps, and may be later than the\nWARNING: corresponding run date)\n"));
1304     }
1305
1306     /* recalculate consecutive_runs based on the history: find the first run
1307      * at this level, and then count the consecutive runs at that level. This
1308      * number may be zero (if we just deleted the last run at this level) */
1309     info.consecutive_runs = 0;
1310     for (i = 0; info.history[i].level >= 0; i++) {
1311         if (info.history[i].level == info.last_level) break;
1312     }
1313     while (info.history[i+info.consecutive_runs].level == info.last_level)
1314         info.consecutive_runs++;
1315
1316     /* this function doesn't touch the performance stats */
1317
1318     /* write out the changes */
1319     if (put_info(file.name, file.disk, &info) == -1) {
1320             g_printf(_("Could not write curinfo record for %s:%s\n"), file.name, file.disk);
1321             return 0;
1322     }
1323
1324     return 1;
1325 }
1326
1327 void
1328 holding(
1329     int         argc,
1330     char **     argv)
1331 {
1332     GSList *file_list;
1333     GSList *li;
1334     enum { HOLDING_USAGE, HOLDING_LIST, HOLDING_DELETE } action = HOLDING_USAGE;
1335     int long_list = 0;
1336     int outdated_list = 0;
1337     dumpfile_t file;
1338
1339     if (argc < 4)
1340         action = HOLDING_USAGE;
1341     else if (strcmp(argv[3], "list") == 0 && argc >= 4)
1342         action = HOLDING_LIST;
1343     else if (strcmp(argv[3], "delete") == 0 && argc > 4)
1344         action = HOLDING_DELETE;
1345
1346     switch (action) {
1347         case HOLDING_USAGE:
1348             g_fprintf(stderr,
1349                     _("%s: expecting \"holding list [-l] [-d]\" or \"holding delete <host> [ .. ]\"\n"),
1350                     get_pname());
1351             usage();
1352             return;
1353
1354         case HOLDING_LIST:
1355             argc -= 4; argv += 4;
1356             while (argc && argv[0][0] == '-') {
1357                 switch (argv[0][1]) {
1358                     case 'l': 
1359                         long_list = 1; 
1360                         break;
1361                     case 'd': /* have to use '-d', and not '-o', because of parse_config */
1362                         outdated_list = 1;
1363                         break;
1364                     default:
1365                         g_fprintf(stderr, _("Unknown option -%c\n"), argv[0][1]);
1366                         usage();
1367                         return;
1368                 }
1369                 argc--; argv++;
1370             }
1371
1372             /* header */
1373             if (long_list) {
1374                 g_printf("%-10s %-2s %-4s %s\n", 
1375                     _("size (kB)"), _("lv"), _("outd"), _("dump specification"));
1376             }
1377
1378             file_list = get_file_list(argc, argv, 1);
1379             for (li = file_list; li != NULL; li = li->next) {
1380                 char *dumpstr;
1381                 int is_outdated;
1382
1383                 if (!holding_file_get_dumpfile((char *)li->data, &file)) {
1384                     g_fprintf(stderr, _("Error reading %s\n"), (char *)li->data);
1385                     continue;
1386                 }
1387
1388                 is_outdated = holding_file_is_outdated(&file);
1389
1390                 dumpstr = cmdline_format_dumpspec_components(file.name, file.disk, file.datestamp, NULL);
1391                 /* only print this entry if we're printing everything, or if it's outdated and
1392                  * we're only printing outdated files (-o) */
1393                 if (!outdated_list || is_outdated) {
1394                     if (long_list) {
1395                         g_printf("%-10lld %-2d %-4s %s\n", 
1396                                (long long)holding_file_size((char *)li->data, 0),
1397                                file.dumplevel,
1398                                is_outdated? " *":"",
1399                                dumpstr);
1400                     } else {
1401                         g_printf("%s\n", dumpstr);
1402                     }
1403                 }
1404                 amfree(dumpstr);
1405             }
1406             g_slist_free_full(file_list);
1407             break;
1408
1409         case HOLDING_DELETE:
1410             argc -= 4; argv += 4;
1411
1412             file_list = get_file_list(argc, argv, 0);
1413             for (li = file_list; li != NULL; li = li->next) {
1414                 g_fprintf(stderr, _("Deleting '%s'\n"), (char *)li->data);
1415                 /* remove it from the catalog */
1416                 if (!remove_holding_file_from_catalog((char *)li->data))
1417                     exit(1);
1418
1419                 /* unlink it */
1420                 if (!holding_file_unlink((char *)li->data)) {
1421                     error(_("Could not delete '%s'"), (char *)li->data);
1422                 }
1423             }
1424             g_slist_free_full(file_list);
1425             break;
1426     }
1427 }
1428
1429
1430 /* ------------------------ */
1431
1432
1433 /* shared code with planner.c */
1434
1435 int
1436 bump_thresh(
1437     int         level)
1438 {
1439     int bump = getconf_int(CNF_BUMPSIZE);
1440     double mult = getconf_real(CNF_BUMPMULT);
1441
1442     while(--level)
1443         bump = (int)((double)bump * mult);
1444     return bump;
1445 }
1446
1447 void
1448 bumpsize(
1449     int         argc,
1450     char **     argv)
1451 {
1452     int l;
1453     int conf_bumppercent = getconf_int(CNF_BUMPPERCENT);
1454     double conf_bumpmult = getconf_real(CNF_BUMPMULT);
1455
1456     (void)argc; /* Quiet unused parameter warning */
1457     (void)argv; /* Quiet unused parameter warning */
1458
1459     g_printf(_("Current bump parameters:\n"));
1460     if(conf_bumppercent == 0) {
1461         g_printf(_("  bumpsize %5d KB\t- minimum savings (threshold) to bump level 1 -> 2\n"),
1462                getconf_int(CNF_BUMPSIZE));
1463         g_printf(_("  bumpdays %5d\t- minimum days at each level\n"),
1464                getconf_int(CNF_BUMPDAYS));
1465         g_printf(_("  bumpmult %5.5lg\t- threshold = bumpsize * bumpmult**(level-1)\n\n"),
1466                conf_bumpmult);
1467
1468         g_printf(_("      Bump -> To  Threshold\n"));
1469         for(l = 1; l < 9; l++)
1470             g_printf(_("\t%d  ->  %d  %9d KB\n"), l, l+1, bump_thresh(l));
1471         putchar('\n');
1472     }
1473     else {
1474         double bumppercent = (double)conf_bumppercent;
1475
1476         g_printf(_("  bumppercent %3d %%\t- minimum savings (threshold) to bump level 1 -> 2\n"),
1477                conf_bumppercent);
1478         g_printf(_("  bumpdays %5d\t- minimum days at each level\n"),
1479                getconf_int(CNF_BUMPDAYS));
1480         g_printf(_("  bumpmult %5.5lg\t- threshold = disk_size * bumppercent * bumpmult**(level-1)\n\n"),
1481                conf_bumpmult);
1482         g_printf(_("      Bump -> To  Threshold\n"));
1483         for(l = 1; l < 9; l++) {
1484             g_printf(_("\t%d  ->  %d  %7.2lf %%\n"), l, l+1, bumppercent);
1485             bumppercent *= conf_bumpmult;
1486             if(bumppercent >= 100.000) { bumppercent = 100.0;}
1487         }
1488         putchar('\n');
1489     }
1490 }
1491
1492 /* ----------------------------------------------- */
1493
1494 void export_one(disk_t *dp);
1495
1496 void
1497 export_db(
1498     int         argc,
1499     char **     argv)
1500 {
1501     disk_t *dp;
1502     time_t curtime;
1503     char hostname[MAX_HOSTNAME_LENGTH+1];
1504     int i;
1505
1506     g_printf(_("CURINFO Version %s CONF %s\n"), version(), getconf_str(CNF_ORG));
1507
1508     curtime = time(0);
1509     if(gethostname(hostname, SIZEOF(hostname)-1) == -1) {
1510         error(_("could not determine host name: %s\n"), strerror(errno));
1511         /*NOTREACHED*/
1512     }
1513     hostname[SIZEOF(hostname)-1] = '\0';
1514     g_printf(_("# Generated by:\n#    host: %s\n#    date: %s"),
1515            hostname, ctime(&curtime));
1516
1517     g_printf(_("#    command:"));
1518     for(i = 0; i < argc; i++)
1519         g_printf(_(" %s"), argv[i]);
1520
1521     g_printf(_("\n# This file can be merged back in with \"amadmin import\".\n"));
1522     g_printf(_("# Edit only with care.\n"));
1523
1524     if(argc >= 4)
1525         diskloop(argc, argv, "export", export_one);
1526     else for(dp = diskq.head; dp != NULL; dp = dp->next)
1527         export_one(dp);
1528 }
1529
1530 void
1531 export_one(
1532     disk_t *    dp)
1533 {
1534     info_t info;
1535     int i,l;
1536
1537     if(get_info(dp->host->hostname, dp->name, &info)) {
1538         g_fprintf(stderr, _("Warning: no curinfo record for %s:%s\n"),
1539                 dp->host->hostname, dp->name);
1540         return;
1541     }
1542     g_printf(_("host: %s\ndisk: %s\n"), dp->host->hostname, dp->name);
1543     g_printf(_("command: %u\n"), info.command);
1544     g_printf(_("last_level: %d\n"),info.last_level);
1545     g_printf(_("consecutive_runs: %d\n"),info.consecutive_runs);
1546     g_printf(_("full-rate:"));
1547     for(i=0;i<AVG_COUNT;i++) g_printf(_(" %lf"), info.full.rate[i]);
1548     g_printf(_("\nfull-comp:"));
1549     for(i=0;i<AVG_COUNT;i++) g_printf(_(" %lf"), info.full.comp[i]);
1550
1551     g_printf(_("\nincr-rate:"));
1552     for(i=0;i<AVG_COUNT;i++) g_printf(_(" %lf"), info.incr.rate[i]);
1553     g_printf(_("\nincr-comp:"));
1554     for(i=0;i<AVG_COUNT;i++) g_printf(_(" %lf"), info.incr.comp[i]);
1555     g_printf("\n");
1556     for(l=0;l<DUMP_LEVELS;l++) {
1557         if(info.inf[l].date < (time_t)0 && info.inf[l].label[0] == '\0') continue;
1558         g_printf(_("stats: %d %lld %lld %jd %jd %lld %s\n"), l,
1559                (long long)info.inf[l].size,
1560                (long long)info.inf[l].csize,
1561                (intmax_t)info.inf[l].secs,
1562                (intmax_t)info.inf[l].date,
1563                (long long)info.inf[l].filenum,
1564                info.inf[l].label);
1565     }
1566     for(l=0;info.history[l].level > -1;l++) {
1567         g_printf(_("history: %d %lld %lld %jd\n"),
1568                info.history[l].level,
1569                (long long)info.history[l].size,
1570                (long long)info.history[l].csize,
1571                (intmax_t)info.history[l].date);
1572     }
1573     g_printf("//\n");
1574 }
1575
1576 /* ----------------------------------------------- */
1577
1578 int import_one(void);
1579 char *impget_line(void);
1580
1581 void
1582 import_db(
1583     int         argc,
1584     char **     argv)
1585 {
1586     int vers_maj;
1587     int vers_min;
1588     int vers_patch;
1589     int newer;
1590     char *org;
1591     char *line = NULL;
1592     char *hdr;
1593     char *s;
1594     int rc;
1595     int ch;
1596
1597     (void)argc; /* Quiet unused parameter warning */
1598     (void)argv; /* Quiet unused parameter warning */
1599
1600     /* process header line */
1601
1602     if((line = agets(stdin)) == NULL) {
1603         g_fprintf(stderr, _("%s: empty input.\n"), get_pname());
1604         return;
1605     }
1606
1607     s = line;
1608     ch = *s++;
1609
1610     hdr = "version";
1611     if(strncmp_const_skip(s - 1, "CURINFO Version", s, ch) != 0) {
1612         goto bad_header;
1613     }
1614     ch = *s++;
1615     skip_whitespace(s, ch);
1616     if(ch == '\0'
1617        || sscanf(s - 1, "%d.%d.%d", &vers_maj, &vers_min, &vers_patch) != 3) {
1618         goto bad_header;
1619     }
1620
1621     skip_integer(s, ch);                        /* skip over major */
1622     if(ch != '.') {
1623         goto bad_header;
1624     }
1625     ch = *s++;
1626     skip_integer(s, ch);                        /* skip over minor */
1627     if(ch != '.') {
1628         goto bad_header;
1629     }
1630     ch = *s++;
1631     skip_integer(s, ch);                        /* skip over patch */
1632
1633     hdr = "comment";
1634     if(ch == '\0') {
1635         goto bad_header;
1636     }
1637     skip_non_whitespace(s, ch);
1638     s[-1] = '\0';
1639
1640     hdr = "CONF";
1641     skip_whitespace(s, ch);                     /* find the org keyword */
1642     if(ch == '\0' || strncmp_const_skip(s - 1, "CONF", s, ch) != 0) {
1643         goto bad_header;
1644     }
1645     ch = *s++;
1646
1647     hdr = "org";
1648     skip_whitespace(s, ch);                     /* find the org string */
1649     if(ch == '\0') {
1650         goto bad_header;
1651     }
1652     org = s - 1;
1653
1654     /*@ignore@*/
1655     newer = (vers_maj != VERSION_MAJOR)? vers_maj > VERSION_MAJOR :
1656             (vers_min != VERSION_MINOR)? vers_min > VERSION_MINOR :
1657                                          vers_patch > VERSION_PATCH;
1658     if(newer)
1659         g_fprintf(stderr,
1660              _("%s: WARNING: input is from newer Amanda version: %d.%d.%d.\n"),
1661                 get_pname(), vers_maj, vers_min, vers_patch);
1662     /*@end@*/
1663
1664     if(strcmp(org, getconf_str(CNF_ORG)) != 0) {
1665         g_fprintf(stderr, _("%s: WARNING: input is from different org: %s\n"),
1666                 get_pname(), org);
1667     }
1668
1669     do {
1670         rc = import_one();
1671     } while (rc);
1672
1673     amfree(line);
1674     return;
1675
1676  bad_header:
1677
1678     /*@i@*/ amfree(line);
1679     g_fprintf(stderr, _("%s: bad CURINFO header line in input: %s.\n"),
1680             get_pname(), hdr);
1681     g_fprintf(stderr, _("    Was the input in \"amadmin export\" format?\n"));
1682     return;
1683 }
1684
1685
1686 int
1687 import_one(void)
1688 {
1689     info_t info;
1690     stats_t onestat;
1691     int rc, level;
1692     char *line = NULL;
1693     char *s, *fp;
1694     int ch;
1695     int nb_history, i;
1696     char *hostname = NULL;
1697     char *diskname = NULL;
1698     long long off_t_tmp;
1699     long long time_t_tmp;
1700
1701     memset(&info, 0, SIZEOF(info_t));
1702
1703     for(level = 0; level < DUMP_LEVELS; level++) {
1704         info.inf[level].date = (time_t)-1;
1705     }
1706
1707     /* get host: disk: command: lines */
1708
1709     hostname = diskname = NULL;
1710
1711     if((line = impget_line()) == NULL) return 0;        /* nothing there */
1712     s = line;
1713     ch = *s++;
1714
1715     skip_whitespace(s, ch);
1716     if(ch == '\0' || strncmp_const_skip(s - 1, "host:", s, ch) != 0) goto parse_err;
1717     skip_whitespace(s, ch);
1718     if(ch == '\0') goto parse_err;
1719     fp = s-1;
1720     skip_non_whitespace(s, ch);
1721     s[-1] = '\0';
1722     hostname = stralloc(fp);
1723     s[-1] = (char)ch;
1724
1725     skip_whitespace(s, ch);
1726     while (ch == 0) {
1727       amfree(line);
1728       if((line = impget_line()) == NULL) goto shortfile_err;
1729       s = line;
1730       ch = *s++;
1731       skip_whitespace(s, ch);
1732     }
1733     if(strncmp_const_skip(s - 1, "disk:", s, ch) != 0) goto parse_err;
1734     skip_whitespace(s, ch);
1735     if(ch == '\0') goto parse_err;
1736     fp = s-1;
1737     skip_non_whitespace(s, ch);
1738     s[-1] = '\0';
1739     diskname = stralloc(fp);
1740     s[-1] = (char)ch;
1741
1742     amfree(line);
1743     if((line = impget_line()) == NULL) goto shortfile_err;
1744     if(sscanf(line, "command: %u", &info.command) != 1) goto parse_err;
1745
1746     /* get last_level and consecutive_runs */
1747
1748     amfree(line);
1749     if((line = impget_line()) == NULL) goto shortfile_err;
1750     rc = sscanf(line, "last_level: %d", &info.last_level);
1751     if(rc == 1) {
1752         amfree(line);
1753         if((line = impget_line()) == NULL) goto shortfile_err;
1754         if(sscanf(line, "consecutive_runs: %d", &info.consecutive_runs) != 1) goto parse_err;
1755         amfree(line);
1756         if((line = impget_line()) == NULL) goto shortfile_err;
1757     }
1758
1759     /* get rate: and comp: lines for full dumps */
1760
1761     rc = sscanf(line, "full-rate: %lf %lf %lf",
1762                 &info.full.rate[0], &info.full.rate[1], &info.full.rate[2]);
1763     if(rc != 3) goto parse_err;
1764
1765     amfree(line);
1766     if((line = impget_line()) == NULL) goto shortfile_err;
1767     rc = sscanf(line, "full-comp: %lf %lf %lf",
1768                 &info.full.comp[0], &info.full.comp[1], &info.full.comp[2]);
1769     if(rc != 3) goto parse_err;
1770
1771     /* get rate: and comp: lines for incr dumps */
1772
1773     amfree(line);
1774     if((line = impget_line()) == NULL) goto shortfile_err;
1775     rc = sscanf(line, "incr-rate: %lf %lf %lf",
1776                 &info.incr.rate[0], &info.incr.rate[1], &info.incr.rate[2]);
1777     if(rc != 3) goto parse_err;
1778
1779     amfree(line);
1780     if((line = impget_line()) == NULL) goto shortfile_err;
1781     rc = sscanf(line, "incr-comp: %lf %lf %lf",
1782                 &info.incr.comp[0], &info.incr.comp[1], &info.incr.comp[2]);
1783     if(rc != 3) goto parse_err;
1784
1785     /* get stats for dump levels */
1786
1787     while(1) {
1788         amfree(line);
1789         if((line = impget_line()) == NULL) goto shortfile_err;
1790         if(strncmp_const(line, "//") == 0) {
1791             /* end of record */
1792             break;
1793         }
1794         if(strncmp_const(line, "history:") == 0) {
1795             /* end of record */
1796             break;
1797         }
1798         memset(&onestat, 0, SIZEOF(onestat));
1799
1800         s = line;
1801         ch = *s++;
1802
1803         skip_whitespace(s, ch);
1804         if(ch == '\0' || strncmp_const_skip(s - 1, "stats:", s, ch) != 0) {
1805             goto parse_err;
1806         }
1807
1808         skip_whitespace(s, ch);
1809         if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
1810             goto parse_err;
1811         }
1812         skip_integer(s, ch);
1813
1814         skip_whitespace(s, ch);
1815         if(ch == '\0' || sscanf(s - 1, "%lld", &off_t_tmp) != 1) {
1816             goto parse_err;
1817         }
1818         onestat.size = (off_t)off_t_tmp;
1819         skip_integer(s, ch);
1820
1821         skip_whitespace(s, ch);
1822         if(ch == '\0' || sscanf(s - 1, "%lld", &off_t_tmp) != 1) {
1823             goto parse_err;
1824         }
1825         onestat.csize = (off_t)off_t_tmp;
1826         skip_integer(s, ch);
1827
1828         skip_whitespace(s, ch);
1829         if(ch == '\0' || sscanf(s - 1, "%lld", &time_t_tmp) != 1) {
1830             goto parse_err;
1831         }
1832         onestat.secs = (time_t)time_t_tmp;
1833         skip_integer(s, ch);
1834
1835         skip_whitespace(s, ch);
1836         if(ch == '\0' || sscanf(s - 1, "%lld", &time_t_tmp) != 1) {
1837             goto parse_err;
1838         }
1839         /* time_t not guarranteed to be long */
1840         /*@i1@*/ onestat.date = (time_t)time_t_tmp;
1841         skip_integer(s, ch);
1842
1843         skip_whitespace(s, ch);
1844         if(ch != '\0') {
1845             if(sscanf(s - 1, "%lld", &off_t_tmp) != 1) {
1846                 goto parse_err;
1847             }
1848             onestat.filenum = (off_t)off_t_tmp;
1849             skip_integer(s, ch);
1850
1851             skip_whitespace(s, ch);
1852             if(ch == '\0') {
1853                 if (onestat.filenum != 0)
1854                     goto parse_err;
1855                 onestat.label[0] = '\0';
1856             } else {
1857                 strncpy(onestat.label, s - 1, SIZEOF(onestat.label)-1);
1858                 onestat.label[SIZEOF(onestat.label)-1] = '\0';
1859             }
1860         }
1861
1862         if(level < 0 || level > 9) goto parse_err;
1863
1864         info.inf[level] = onestat;
1865     }
1866     nb_history = 0;
1867     for(i=0;i<=NB_HISTORY;i++) {
1868         info.history[i].level = -2;
1869     }
1870     while(1) {
1871         history_t onehistory;
1872
1873         if(line[0] == '/' && line[1] == '/') {
1874             info.history[nb_history].level = -2;
1875             rc = 0;
1876             break;
1877         }
1878         memset(&onehistory, 0, SIZEOF(onehistory));
1879         s = line;
1880         ch = *s++;
1881         if(strncmp_const_skip(line, "history:", s, ch) != 0) {
1882             break;
1883         }
1884
1885         skip_whitespace(s, ch);
1886         if(ch == '\0' || sscanf((s - 1), "%d", &onehistory.level) != 1) {
1887             break;
1888         }
1889         skip_integer(s, ch);
1890
1891         skip_whitespace(s, ch);
1892         if(ch == '\0' || sscanf((s - 1), "%lld", &off_t_tmp) != 1) {
1893             break;
1894         }
1895         onehistory.size = (off_t)off_t_tmp;
1896         skip_integer(s, ch);
1897
1898         skip_whitespace(s, ch);
1899         if(ch == '\0' || sscanf((s - 1), "%lld", &off_t_tmp) != 1) {
1900             break;
1901         }
1902         onehistory.csize = (off_t)off_t_tmp;
1903         skip_integer(s, ch);
1904
1905         skip_whitespace(s, ch);
1906         if((ch == '\0') || sscanf((s - 1), "%lld", &time_t_tmp) != 1) {
1907             break;
1908         }
1909         /* time_t not guarranteed to be long */
1910         /*@i1@*/ onehistory.date = (time_t)time_t_tmp;
1911         skip_integer(s, ch);
1912
1913         info.history[nb_history++] = onehistory;
1914         amfree(line);
1915         if((line = impget_line()) == NULL) goto shortfile_err;
1916     }
1917     /*@i@*/ amfree(line);
1918
1919     /* got a full record, now write it out to the database */
1920
1921     if(put_info(hostname, diskname, &info)) {
1922         g_fprintf(stderr, _("%s: error writing record for %s:%s\n"),
1923                 get_pname(), hostname, diskname);
1924     }
1925     amfree(hostname);
1926     amfree(diskname);
1927     return 1;
1928
1929  parse_err:
1930     /*@i@*/ amfree(line);
1931     amfree(hostname);
1932     amfree(diskname);
1933     g_fprintf(stderr, _("%s: parse error reading import record.\n"), get_pname());
1934     return 0;
1935
1936  shortfile_err:
1937     /*@i@*/ amfree(line);
1938     amfree(hostname);
1939     amfree(diskname);
1940     g_fprintf(stderr, _("%s: short file reading import record.\n"), get_pname());
1941     return 0;
1942 }
1943
1944 char *
1945 impget_line(void)
1946 {
1947     char *line;
1948     char *s;
1949     int ch;
1950
1951     for(; (line = agets(stdin)) != NULL; free(line)) {
1952         s = line;
1953         ch = *s++;
1954
1955         skip_whitespace(s, ch);
1956         if(ch == '#') {
1957             /* ignore comment lines */
1958             continue;
1959         } else if(ch) {
1960             /* found non-blank, return line */
1961             return line;
1962         }
1963         /* otherwise, a blank line, so keep going */
1964     }
1965     if(ferror(stdin)) {
1966         g_fprintf(stderr, _("%s: reading stdin: %s\n"),
1967                 get_pname(), strerror(errno));
1968     }
1969     return NULL;
1970 }
1971
1972 /* ----------------------------------------------- */
1973
1974 void
1975 disklist_one(
1976     disk_t *    dp)
1977 {
1978     am_host_t *hp;
1979     netif_t *ip;
1980     sle_t *excl;
1981
1982     hp = dp->host;
1983     ip = hp->netif;
1984
1985     g_printf("line %d:\n", dp->line);
1986
1987     g_printf("    host %s:\n", hp->hostname);
1988     g_printf("        interface %s\n",
1989            interface_name(ip->config)[0] ? interface_name(ip->config) : "default");
1990     g_printf("    disk %s:\n", dp->name);
1991     if(dp->device) g_printf("        device %s\n", dp->device);
1992
1993     g_printf("        program \"%s\"\n", dp->program);
1994     if(dp->exclude_file != NULL && dp->exclude_file->nb_element > 0) {
1995         g_printf("        exclude file");
1996         for(excl = dp->exclude_file->first; excl != NULL; excl = excl->next) {
1997             g_printf(" \"%s\"", excl->name);
1998         }
1999         g_printf("\n");
2000     }
2001     if(dp->exclude_list != NULL && dp->exclude_list->nb_element > 0) {
2002         g_printf("        exclude list");
2003         if(dp->exclude_optional) g_printf(" optional");
2004         for(excl = dp->exclude_list->first; excl != NULL; excl = excl->next) {
2005             g_printf(" \"%s\"", excl->name);
2006         }
2007         g_printf("\n");
2008     }
2009     if(dp->include_file != NULL && dp->include_file->nb_element > 0) {
2010         g_printf("        include file");
2011         for(excl = dp->include_file->first; excl != NULL; excl = excl->next) {
2012             g_printf(" \"%s\"", excl->name);
2013         }
2014         g_printf("\n");
2015     }
2016     if(dp->include_list != NULL && dp->include_list->nb_element > 0) {
2017         g_printf("        include list");
2018         if(dp->include_optional) g_printf(" optional");
2019         for(excl = dp->include_list->first; excl != NULL; excl = excl->next) {
2020             g_printf(" \"%s\"", excl->name);
2021         }
2022         g_printf("\n");
2023     }
2024     g_printf("        priority %d\n", dp->priority);
2025     g_printf("        dumpcycle %d\n", dp->dumpcycle);
2026     g_printf("        maxdumps %d\n", dp->maxdumps);
2027     g_printf("        maxpromoteday %d\n", dp->maxpromoteday);
2028     if(dp->bumppercent > 0) {
2029         g_printf("        bumppercent %d\n", dp->bumppercent);
2030     }
2031     else {
2032         g_printf("        bumpsize %lld\n",
2033                 (long long)dp->bumpsize);
2034     }
2035     g_printf("        bumpdays %d\n", dp->bumpdays);
2036     g_printf("        bumpmult %lf\n", dp->bumpmult);
2037
2038     g_printf("        strategy ");
2039     switch(dp->strategy) {
2040     case DS_SKIP:
2041         g_printf("SKIP\n");
2042         break;
2043     case DS_STANDARD:
2044         g_printf("STANDARD\n");
2045         break;
2046     case DS_NOFULL:
2047         g_printf("NOFULL\n");
2048         break;
2049     case DS_NOINC:
2050         g_printf("NOINC\n");
2051         break;
2052     case DS_HANOI:
2053         g_printf("HANOI\n");
2054         break;
2055     case DS_INCRONLY:
2056         g_printf("INCRONLY\n");
2057         break;
2058     }
2059     g_printf("        ignore %s\n", (dp->ignore? "YES" : "NO"));
2060     g_printf("        estimate ");
2061     switch(dp->estimate) {
2062     case ES_CLIENT:
2063         g_printf("CLIENT\n");
2064         break;
2065     case ES_SERVER:
2066         g_printf("SERVER\n");
2067         break;
2068     case ES_CALCSIZE:
2069         g_printf("CALCSIZE\n");
2070         break;
2071     }
2072
2073     g_printf("        compress ");
2074     switch(dp->compress) {
2075     case COMP_NONE:
2076         g_printf("NONE\n");
2077         break;
2078     case COMP_FAST:
2079         g_printf("CLIENT FAST\n");
2080         break;
2081     case COMP_BEST:
2082         g_printf("CLIENT BEST\n");
2083         break;
2084     case COMP_SERVER_FAST:
2085         g_printf("SERVER FAST\n");
2086         break;
2087     case COMP_SERVER_BEST:
2088         g_printf("SERVER BEST\n");
2089         break;
2090     }
2091     if(dp->compress != COMP_NONE) {
2092         g_printf("        comprate %.2lf %.2lf\n",
2093                dp->comprate[0], dp->comprate[1]);
2094     }
2095
2096     g_printf("        encrypt ");
2097     switch(dp->encrypt) {
2098     case ENCRYPT_NONE:
2099         g_printf("NONE\n");
2100         break;
2101     case ENCRYPT_CUST:
2102         g_printf("CLIENT\n");
2103         break;
2104     case ENCRYPT_SERV_CUST:
2105         g_printf("SERVER\n");
2106         break;
2107     }
2108
2109     g_printf("        auth %s\n", dp->security_driver);
2110     g_printf("        kencrypt %s\n", (dp->kencrypt? "YES" : "NO"));
2111     g_printf("        amandad_path %s\n", dp->amandad_path);
2112     g_printf("        client_username %s\n", dp->client_username);
2113     g_printf("        ssh_keys %s\n", dp->ssh_keys);
2114
2115     g_printf("        holdingdisk ");
2116     switch(dp->to_holdingdisk) {
2117     case HOLD_NEVER:
2118         g_printf("NEVER\n");
2119         break;
2120     case HOLD_AUTO:
2121         g_printf("AUTO\n");
2122         break;
2123     case HOLD_REQUIRED:
2124         g_printf("REQUIRED\n");
2125         break;
2126     }
2127
2128     g_printf("        record %s\n", (dp->record? "YES" : "NO"));
2129     g_printf("        index %s\n", (dp->index? "YES" : "NO"));
2130     g_printf("        starttime %04d\n", (int)dp->starttime);
2131     if(dp->tape_splitsize > (off_t)0) {
2132         g_printf("        tape_splitsize %lld\n",
2133                (long long)dp->tape_splitsize);
2134     }
2135     if(dp->split_diskbuffer) {
2136         g_printf("        split_diskbuffer %s\n", dp->split_diskbuffer);
2137     }
2138     if(dp->fallback_splitsize > (off_t)0) {
2139         g_printf("        fallback_splitsize %lldMb\n",
2140                (long long)(dp->fallback_splitsize / (off_t)1024));
2141     }
2142     g_printf("        skip-incr %s\n", (dp->skip_incr? "YES" : "NO"));
2143     g_printf("        skip-full %s\n", (dp->skip_full? "YES" : "NO"));
2144     g_printf("        spindle %d\n", dp->spindle);
2145
2146     g_printf("\n");
2147 }
2148
2149 void
2150 disklist(
2151     int         argc,
2152     char **     argv)
2153 {
2154     disk_t *dp;
2155
2156     if(argc >= 4)
2157         diskloop(argc, argv, "disklist", disklist_one);
2158     else
2159         for(dp = diskq.head; dp != NULL; dp = dp->next)
2160             disklist_one(dp);
2161 }
2162
2163 void
2164 show_version(
2165     int         argc,
2166     char **     argv)
2167 {
2168     int i;
2169
2170     (void)argc; /* Quiet unused parameter warning */
2171     (void)argv; /* Quiet unused parameter warning */
2172
2173     for(i = 0; version_info[i] != NULL; i++)
2174         g_printf("%s", version_info[i]);
2175 }
2176
2177
2178 void show_config(
2179     int argc G_GNUC_UNUSED,
2180     char **argv G_GNUC_UNUSED)
2181 {
2182     dump_configuration();
2183 }
2184