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