51ebb93375e58817802f6c73f2fad3a140433a7f
[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.105 2006/02/03 17:29:28 vectro Exp $
29  *
30  * controlling process for the Amanda backup system
31  */
32 #include "amanda.h"
33 #include "conffile.h"
34 #include "diskfile.h"
35 #include "tapefile.h"
36 #include "infofile.h"
37 #include "logfile.h"
38 #include "version.h"
39 #include "holding.h"
40 #include "find.h"
41
42 disklist_t diskq;
43
44 int main P((int argc, char **argv));
45 void usage P((void));
46 void force P((int argc, char **argv));
47 void force_one P((disk_t *dp));
48 void unforce P((int argc, char **argv));
49 void unforce_one P((disk_t *dp));
50 void force_bump P((int argc, char **argv));
51 void force_bump_one P((disk_t *dp));
52 void force_no_bump P((int argc, char **argv));
53 void force_no_bump_one P((disk_t *dp));
54 void unforce_bump P((int argc, char **argv));
55 void unforce_bump_one P((disk_t *dp));
56 void reuse P((int argc, char **argv));
57 void noreuse P((int argc, char **argv));
58 void info P((int argc, char **argv));
59 void info_one P((disk_t *dp));
60 void due P((int argc, char **argv));
61 void due_one P((disk_t *dp));
62 void find P((int argc, char **argv));
63 void delete P((int argc, char **argv));
64 void delete_one P((disk_t *dp));
65 void balance P((int argc, char **argv));
66 void tape P((int argc, char **argv));
67 void bumpsize P((int argc, char **argv));
68 void diskloop P((int argc, char **argv, char *cmdname,
69                  void (*func) P((disk_t *dp))));
70 char *seqdatestr P((int seq));
71 static int next_level0 P((disk_t *dp, info_t *info));
72 int bump_thresh P((int level));
73 void export_db P((int argc, char **argv));
74 void import_db P((int argc, char **argv));
75 void disklist P((int argc, char **argv));
76 void disklist_one P((disk_t *dp));
77 void show_version P((int argc, char **argv));
78 static void check_dumpuser P((void));
79
80 static char *conf_tapelist = NULL;
81 static char *displayunit;
82 static long int unitdivisor;
83
84 static const struct {
85     const char *name;
86     void (*fn) P((int, char **));
87     const char *usage;
88 } cmdtab[] = {
89     { "version", show_version,
90         "\t\t\t\t# Show version info." },
91     { "force", force,
92         " [<hostname> [<disks>]* ]+\t# Force level 0 at next run." },
93     { "unforce", unforce,
94         " [<hostname> [<disks>]* ]+\t# Clear force command." },
95     { "force-bump", force_bump,
96         " [<hostname> [<disks>]* ]+\t# Force bump at next run." },
97     { "force-no-bump", force_no_bump,
98         " [<hostname> [<disks>]* ]+\t# Force no-bump at next run." },
99     { "unforce-bump", unforce_bump,
100         " [<hostname> [<disks>]* ]+\t# Clear bump command." },
101     { "reuse", reuse,
102         " <tapelabel> ...\t\t# re-use this tape." },
103     { "no-reuse", noreuse,
104         " <tapelabel> ...\t# never re-use this tape." },
105     { "find", find,
106         " [<hostname> [<disks>]* ]*\t# Show which tapes these dumps are on." },
107     { "delete", delete,
108         " [<hostname> [<disks>]* ]+\t# Delete from database." },
109     { "info", info,
110         " [<hostname> [<disks>]* ]*\t# Show current info records." },
111     { "due", due,
112         " [<hostname> [<disks>]* ]*\t# Show due date." },
113     { "balance", balance,
114         " [-days <num>]\t\t# Show nightly dump size balance." },
115     { "tape", tape,
116         " [-days <num>]\t\t\t# Show which tape is due next." },
117     { "bumpsize", bumpsize,
118         "\t\t\t# Show current bump thresholds." },
119     { "export", export_db,
120         " [<hostname> [<disks>]* ]*\t# Export curinfo database to stdout." },
121     { "import", import_db,
122         "\t\t\t\t# Import curinfo database from stdin." },
123     { "disklist", disklist,
124         " [<hostname> [<disks>]* ]*\t# Debug disklist entries." },
125 };
126 #define NCMDS   (sizeof(cmdtab) / sizeof(cmdtab[0]))
127
128 int main(argc, argv)
129      int argc;
130      char **argv;
131 {
132     int i;
133     char *conf_diskfile;
134     char *conf_infofile;
135     char *conffile;
136     unsigned long malloc_hist_1, malloc_size_1;
137     unsigned long malloc_hist_2, malloc_size_2;
138
139     safe_fd(-1, 0);
140     safe_cd();
141
142     set_pname("amadmin");
143
144     /* Don't die when child closes pipe */
145     signal(SIGPIPE, SIG_IGN);
146
147     malloc_size_1 = malloc_inuse(&malloc_hist_1);
148
149     erroutput_type = ERR_INTERACTIVE;
150
151     if(argc < 3) usage();
152
153     if(strcmp(argv[2],"version") == 0) {
154         show_version(argc, argv);
155         goto done;
156     }
157
158     config_name = argv[1];
159     config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
160     conffile = stralloc2(config_dir, CONFFILE_NAME);
161
162     if(read_conffile(conffile))
163         error("errors processing config file \"%s\"", conffile);
164     amfree(conffile);
165
166     check_dumpuser();
167
168     conf_diskfile = getconf_str(CNF_DISKFILE);
169     if (*conf_diskfile == '/') {
170         conf_diskfile = stralloc(conf_diskfile);
171     } else {
172         conf_diskfile = stralloc2(config_dir, conf_diskfile);
173     }
174     if (read_diskfile(conf_diskfile, &diskq) < 0)
175         error("could not load disklist \"%s\"", conf_diskfile);
176     amfree(conf_diskfile);
177
178     conf_tapelist = getconf_str(CNF_TAPELIST);
179     if (*conf_tapelist == '/') {
180         conf_tapelist = stralloc(conf_tapelist);
181     } else {
182         conf_tapelist = stralloc2(config_dir, conf_tapelist);
183     }
184     if(read_tapelist(conf_tapelist))
185         error("could not load tapelist \"%s\"", conf_tapelist);
186
187     conf_infofile = getconf_str(CNF_INFOFILE);
188     if (*conf_infofile == '/') {
189         conf_infofile = stralloc(conf_infofile);
190     } else {
191         conf_infofile = stralloc2(config_dir, conf_infofile);
192     }
193     if(open_infofile(conf_infofile))
194         error("could not open info db \"%s\"", conf_infofile);
195     amfree(conf_infofile);
196
197     displayunit = getconf_str(CNF_DISPLAYUNIT);
198     unitdivisor = getconf_unit_divisor();
199
200     for (i = 0; i < NCMDS; i++)
201         if (strcmp(argv[2], cmdtab[i].name) == 0) {
202             (*cmdtab[i].fn)(argc, argv);
203             break;
204         }
205     if (i == NCMDS) {
206         fprintf(stderr, "%s: unknown command \"%s\"\n", argv[0], argv[2]);
207         usage();
208     }
209
210     close_infofile();
211     clear_tapelist();
212     amfree(conf_tapelist);
213     amfree(config_dir);
214
215 done:
216
217     malloc_size_2 = malloc_inuse(&malloc_hist_2);
218
219     if(malloc_size_1 != malloc_size_2) {
220         malloc_list(fileno(stderr), malloc_hist_1, malloc_hist_2);
221     }
222
223     return 0;
224 }
225
226
227 void usage P((void))
228 {
229     int i;
230
231     fprintf(stderr, "\nUsage: %s%s <conf> <command> {<args>} ...\n",
232             get_pname(), versionsuffix());
233     fprintf(stderr, "    Valid <command>s are:\n");
234     for (i = 0; i < NCMDS; i++)
235         fprintf(stderr, "\t%s%s\n", cmdtab[i].name, cmdtab[i].usage);
236     exit(1);
237 }
238
239
240 /* ----------------------------------------------- */
241
242 #define SECS_PER_DAY (24*60*60)
243 time_t today;
244
245 char *seqdatestr(seq)
246 int seq;
247 {
248     static char str[16];
249     static char *dow[7] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
250     time_t t = today + seq*SECS_PER_DAY;
251     struct tm *tm;
252
253     tm = localtime(&t);
254
255     snprintf(str, sizeof(str),
256                 "%2d/%02d %3s", tm->tm_mon+1, tm->tm_mday, dow[tm->tm_wday]);
257     return str;
258 }
259
260 #undef days_diff
261 #define days_diff(a, b)        (((b) - (a) + SECS_PER_DAY) / SECS_PER_DAY)
262
263 /* when is next level 0 due? 0 = tonight, 1 = tommorrow, etc*/
264 static int next_level0(dp, info)
265 disk_t *dp;
266 info_t *info;
267 {
268     if(dp->strategy == DS_NOFULL)
269         return 1;       /* fake it */
270     if(info->inf[0].date < (time_t)0)
271         return 0;       /* new disk */
272     else
273         return dp->dumpcycle - days_diff(info->inf[0].date, today);
274 }
275
276 static void check_dumpuser()
277 {
278     static int been_here = 0;
279     uid_t uid_me;
280     uid_t uid_dumpuser;
281     char *dumpuser;
282     struct passwd *pw;
283
284     if (been_here) {
285        return;
286     }
287     uid_me = getuid();
288     uid_dumpuser = uid_me;
289     dumpuser = getconf_str(CNF_DUMPUSER);
290
291     if ((pw = getpwnam(dumpuser)) == NULL) {
292         error("cannot look up dump user \"%s\"", dumpuser);
293         /* NOTREACHED */
294     }
295     uid_dumpuser = pw->pw_uid;
296     if ((pw = getpwuid(uid_me)) == NULL) {
297         error("cannot look up my own uid %ld", (long)uid_me);
298         /* NOTREACHED */
299     }
300     if (uid_me != uid_dumpuser) {
301         error("ERROR: running as user \"%s\" instead of \"%s\"",
302               pw->pw_name, dumpuser);
303     }
304     been_here = 1;
305     return;
306 }
307
308 /* ----------------------------------------------- */
309
310 void diskloop(argc, argv, cmdname, func)
311 int argc;
312 char **argv;
313 char *cmdname;
314 void (*func) P((disk_t *dp));
315 {
316     disk_t *dp;
317     int count = 0;
318
319     if(argc < 4) {
320         fprintf(stderr,"%s: expecting \"%s [<hostname> [<disks>]* ]+\"\n",
321                 get_pname(), cmdname);
322         usage();
323     }
324
325     match_disklist(&diskq, argc-3, argv+3);
326
327     for(dp = diskq.head; dp != NULL; dp = dp->next) {
328         if(dp->todo) {
329             count++;
330             func(dp);
331         }
332     }
333     if(count==0) {
334         fprintf(stderr,"%s: no disk matched\n",get_pname());
335     }
336 }
337
338 /* ----------------------------------------------- */
339
340
341 void force_one(dp)
342 disk_t *dp;
343 {
344     char *hostname = dp->host->hostname;
345     char *diskname = dp->name;
346     info_t info;
347
348 #if TEXTDB
349     check_dumpuser();
350 #endif
351     get_info(hostname, diskname, &info);
352     SET(info.command, FORCE_FULL);
353     if (ISSET(info.command, FORCE_BUMP)) {
354         CLR(info.command, FORCE_BUMP);
355         printf("%s: WARNING: %s:%s FORCE_BUMP command was cleared.\n",
356                get_pname(), hostname, diskname);
357     }
358     if(put_info(hostname, diskname, &info) == 0) {
359         printf("%s: %s:%s is set to a forced level 0 at next run.\n",
360                get_pname(), hostname, diskname);
361     } else {
362         fprintf(stderr, "%s: %s:%s could not be forced.\n",
363                 get_pname(), hostname, diskname);
364     }
365 }
366
367
368 void force(argc, argv)
369 int argc;
370 char **argv;
371 {
372     diskloop(argc, argv, "force", force_one);
373 }
374
375
376 /* ----------------------------------------------- */
377
378
379 void unforce_one(dp)
380 disk_t *dp;
381 {
382     char *hostname = dp->host->hostname;
383     char *diskname = dp->name;
384     info_t info;
385
386     get_info(hostname, diskname, &info);
387     if (ISSET(info.command, FORCE_FULL)) {
388 #if TEXTDB
389         check_dumpuser();
390 #endif
391         CLR(info.command, FORCE_FULL);
392         if(put_info(hostname, diskname, &info) == 0){
393             printf("%s: force command for %s:%s cleared.\n",
394                    get_pname(), hostname, diskname);
395         } else {
396             fprintf(stderr,
397                     "%s: force command for %s:%s could not be cleared.\n",
398                     get_pname(), hostname, diskname);
399         }
400     }
401     else {
402         printf("%s: no force command outstanding for %s:%s, unchanged.\n",
403                get_pname(), hostname, diskname);
404     }
405 }
406
407 void unforce(argc, argv)
408 int argc;
409 char **argv;
410 {
411     diskloop(argc, argv, "unforce", unforce_one);
412 }
413
414
415 /* ----------------------------------------------- */
416
417
418 void force_bump_one(dp)
419 disk_t *dp;
420 {
421     char *hostname = dp->host->hostname;
422     char *diskname = dp->name;
423     info_t info;
424
425 #if TEXTDB
426     check_dumpuser();
427 #endif
428     get_info(hostname, diskname, &info);
429     SET(info.command, FORCE_BUMP);
430     if (ISSET(info.command, FORCE_NO_BUMP)) {
431         CLR(info.command, FORCE_NO_BUMP);
432         printf("%s: WARNING: %s:%s FORCE_NO_BUMP command was cleared.\n",
433                get_pname(), hostname, diskname);
434     }
435     if (ISSET(info.command, FORCE_FULL)) {
436         CLR(info.command, FORCE_FULL);
437         printf("%s: WARNING: %s:%s FORCE_FULL command was cleared.\n",
438                get_pname(), hostname, diskname);
439     }
440     if(put_info(hostname, diskname, &info) == 0) {
441         printf("%s: %s:%s is set to bump at next run.\n",
442                get_pname(), hostname, diskname);
443     } else {
444         fprintf(stderr, "%s: %s:%s could not be forced to bump.\n",
445                 get_pname(), hostname, diskname);
446     }
447 }
448
449
450 void force_bump(argc, argv)
451 int argc;
452 char **argv;
453 {
454     diskloop(argc, argv, "force-bump", force_bump_one);
455 }
456
457
458 /* ----------------------------------------------- */
459
460
461 void force_no_bump_one(dp)
462 disk_t *dp;
463 {
464     char *hostname = dp->host->hostname;
465     char *diskname = dp->name;
466     info_t info;
467
468 #if TEXTDB
469     check_dumpuser();
470 #endif
471     get_info(hostname, diskname, &info);
472     SET(info.command, FORCE_NO_BUMP);
473     if (ISSET(info.command, FORCE_BUMP)) {
474         CLR(info.command, FORCE_BUMP);
475         printf("%s: WARNING: %s:%s FORCE_BUMP command was cleared.\n",
476                get_pname(), hostname, diskname);
477     }
478     if(put_info(hostname, diskname, &info) == 0) {
479         printf("%s: %s:%s is set to not bump at next run.\n",
480                get_pname(), hostname, diskname);
481     } else {
482         fprintf(stderr, "%s: %s:%s could not be force to not bump.\n",
483                 get_pname(), hostname, diskname);
484     }
485 }
486
487
488 void force_no_bump(argc, argv)
489 int argc;
490 char **argv;
491 {
492     diskloop(argc, argv, "force-no-bump", force_no_bump_one);
493 }
494
495
496 /* ----------------------------------------------- */
497
498
499 void unforce_bump_one(dp)
500 disk_t *dp;
501 {
502     char *hostname = dp->host->hostname;
503     char *diskname = dp->name;
504     info_t info;
505
506     get_info(hostname, diskname, &info);
507     if (ISSET(info.command, FORCE_BUMP|FORCE_NO_BUMP)) {
508 #if TEXTDB
509         check_dumpuser();
510 #endif
511         CLR(info.command, FORCE_BUMP|FORCE_NO_BUMP);
512         if(put_info(hostname, diskname, &info) == 0) {
513             printf("%s: bump command for %s:%s cleared.\n",
514                    get_pname(), hostname, diskname);
515         } else {
516             fprintf(stderr, "%s: %s:%s bump command could not be cleared.\n",
517                     get_pname(), hostname, diskname);
518         }
519     }
520     else {
521         printf("%s: no bump command outstanding for %s:%s, unchanged.\n",
522                get_pname(), hostname, diskname);
523     }
524 }
525
526
527 void unforce_bump(argc, argv)
528 int argc;
529 char **argv;
530 {
531     diskloop(argc, argv, "unforce-bump", unforce_bump_one);
532 }
533
534
535 /* ----------------------------------------------- */
536
537 void reuse(argc, argv)
538 int argc;
539 char **argv;
540 {
541     tape_t *tp;
542     int count;
543
544     if(argc < 4) {
545         fprintf(stderr,"%s: expecting \"reuse <tapelabel> ...\"\n",
546                 get_pname());
547         usage();
548     }
549
550     check_dumpuser();
551     for(count=3; count< argc; count++) {
552         tp = lookup_tapelabel(argv[count]);
553         if ( tp == NULL) {
554             fprintf(stderr, "reuse: tape label %s not found in tapelist.\n",
555                 argv[count]);
556             continue;
557         }
558         if( tp->reuse == 0 ) {
559             tp->reuse = 1;
560             printf("%s: marking tape %s as reusable.\n",
561                    get_pname(), argv[count]);
562         } else {
563             fprintf(stderr, "%s: tape %s already reusable.\n",
564                     get_pname(), argv[count]);
565         }
566     }
567
568     if(write_tapelist(conf_tapelist)) {
569         error("could not write tapelist \"%s\"", conf_tapelist);
570     }
571 }
572
573 void noreuse(argc, argv)
574 int argc;
575 char **argv;
576 {
577     tape_t *tp;
578     int count;
579
580     if(argc < 4) {
581         fprintf(stderr,"%s: expecting \"no-reuse <tapelabel> ...\"\n",
582                 get_pname());
583         usage();
584     }
585
586     check_dumpuser();
587     for(count=3; count< argc; count++) {
588         tp = lookup_tapelabel(argv[count]);
589         if ( tp == NULL) {
590             fprintf(stderr, "no-reuse: tape label %s not found in tapelist.\n",
591                 argv[count]);
592             continue;
593         }
594         if( tp->reuse == 1 ) {
595             tp->reuse = 0;
596             printf("%s: marking tape %s as not reusable.\n",
597                    get_pname(), argv[count]);
598         } else {
599             fprintf(stderr, "%s: tape %s already not reusable.\n",
600                     get_pname(), argv[count]);
601         }
602     }
603
604     if(write_tapelist(conf_tapelist)) {
605         error("could not write tapelist \"%s\"", conf_tapelist);
606     }
607 }
608
609
610 /* ----------------------------------------------- */
611
612 static int deleted;
613
614 void delete_one(dp)
615 disk_t *dp;
616 {
617     char *hostname = dp->host->hostname;
618     char *diskname = dp->name;
619     info_t info;
620
621     if(get_info(hostname, diskname, &info)) {
622         printf("%s: %s:%s NOT currently in database.\n",
623                get_pname(), hostname, diskname);
624         return;
625     }
626
627     deleted++;
628     if(del_info(hostname, diskname))
629         error("couldn't delete %s:%s from database: %s",
630               hostname, diskname, strerror(errno));
631     else
632         printf("%s: %s:%s deleted from curinfo database.\n",
633                get_pname(), hostname, diskname);
634 }
635
636 void delete(argc, argv)
637 int argc;
638 char **argv;
639 {
640     deleted = 0;
641     diskloop(argc, argv, "delete", delete_one);
642
643    if(deleted)
644         printf(
645          "%s: NOTE: you'll have to remove these from the disklist yourself.\n",
646          get_pname());
647 }
648
649 /* ----------------------------------------------- */
650
651 void info_one(dp)
652 disk_t *dp;
653 {
654     info_t info;
655     int lev;
656     struct tm *tm;
657     stats_t *sp;
658
659     get_info(dp->host->hostname, dp->name, &info);
660
661     printf("\nCurrent info for %s %s:\n", dp->host->hostname, dp->name);
662     if (ISSET(info.command, FORCE_FULL))
663         printf("  (Forcing to level 0 dump at next run)\n");
664     if (ISSET(info.command, FORCE_BUMP))
665         printf("  (Forcing bump at next run)\n");
666     if (ISSET(info.command, FORCE_NO_BUMP))
667         printf("  (Forcing no-bump at next run)\n");
668     printf("  Stats: dump rates (kps), Full:  %5.1f, %5.1f, %5.1f\n",
669            info.full.rate[0], info.full.rate[1], info.full.rate[2]);
670     printf("                    Incremental:  %5.1f, %5.1f, %5.1f\n",
671            info.incr.rate[0], info.incr.rate[1], info.incr.rate[2]);
672     printf("          compressed size, Full: %5.1f%%,%5.1f%%,%5.1f%%\n",
673            info.full.comp[0]*100, info.full.comp[1]*100, info.full.comp[2]*100);
674     printf("                    Incremental: %5.1f%%,%5.1f%%,%5.1f%%\n",
675            info.incr.comp[0]*100, info.incr.comp[1]*100, info.incr.comp[2]*100);
676
677     printf("  Dumps: lev datestmp  tape             file   origK   compK secs\n");
678     for(lev = 0, sp = &info.inf[0]; lev < 9; lev++, sp++) {
679         if(sp->date < (time_t)0 && sp->label[0] == '\0') continue;
680         tm = localtime(&sp->date);
681         printf("          %d  %04d%02d%02d  %-15s  %4d %7ld %7ld %4ld\n",
682                lev, tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
683                sp->label, sp->filenum, sp->size, sp->csize, sp->secs);
684     }
685 }
686
687
688 void info(argc, argv)
689 int argc;
690 char **argv;
691 {
692     disk_t *dp;
693
694     if(argc >= 4)
695         diskloop(argc, argv, "info", info_one);
696     else
697         for(dp = diskq.head; dp != NULL; dp = dp->next)
698             info_one(dp);
699 }
700
701 /* ----------------------------------------------- */
702
703 void due_one(dp)
704 disk_t *dp;
705 {
706     am_host_t *hp;
707     int days;
708     info_t info;
709
710     hp = dp->host;
711     if(get_info(hp->hostname, dp->name, &info)) {
712         printf("new disk %s:%s ignored.\n", hp->hostname, dp->name);
713     }
714     else {
715         days = next_level0(dp, &info);
716         if(days < 0) {
717             printf("Overdue %2d day%s %s:%s\n",
718                    -days, (-days == 1) ? ": " : "s:",
719                    hp->hostname, dp->name);
720         }
721         else if(days == 0) {
722             printf("Due today: %s:%s\n", hp->hostname, dp->name);
723         }
724         else {
725             printf("Due in %2d day%s %s:%s\n", days,
726                    (days == 1) ? ": " : "s:",
727                    hp->hostname, dp->name);
728         }
729     }
730 }
731
732 void due(argc, argv)
733 int argc;
734 char **argv;
735 {
736     disk_t *dp;
737
738     time(&today);
739     if(argc >= 4)
740         diskloop(argc, argv, "due", due_one);
741     else
742         for(dp = diskq.head; dp != NULL; dp = dp->next)
743             due_one(dp);
744 }
745
746 /* ----------------------------------------------- */
747
748 void tape(argc, argv)
749 int argc;
750 char **argv;
751 {
752     tape_t *tp, *lasttp;
753     int runtapes, i, j;
754     int nb_days = 1;
755
756     if(argc > 4 && strcmp(argv[3],"--days") == 0) {
757         nb_days = atoi(argv[4]);
758         if(nb_days < 1) {
759             printf("days must be an integer bigger than 0\n");
760             return;
761         }
762     }
763
764     runtapes = getconf_int(CNF_RUNTAPES);
765     tp = lookup_last_reusable_tape(0);
766
767     for ( j=0 ; j < nb_days ; j++ ) {
768         for ( i=0 ; i < runtapes ; i++ ) {
769             if(i==0)
770                 printf("The next Amanda run should go onto ");
771             else
772                 printf("                                   ");
773             if(tp != NULL)
774                 printf("tape %s or ", tp->label);
775             printf("a new tape.\n");
776         
777             tp = lookup_last_reusable_tape(i + 1);
778         }
779     }
780     lasttp = lookup_tapepos(lookup_nb_tape());
781     i = runtapes;
782     if(lasttp && i > 0 && lasttp->datestamp == 0) {
783         int c = 0;
784         while(lasttp && i > 0 && lasttp->datestamp == 0) {
785             c++;
786             lasttp = lasttp->prev;
787             i--;
788         }
789         lasttp = lookup_tapepos(lookup_nb_tape());
790         i = runtapes;
791         if(c == 1) {
792             printf("The next new tape already labelled is: %s.\n",
793                    lasttp->label);
794         }
795         else {
796             printf("The next %d new tapes already labelled are: %s", c,
797                    lasttp->label);
798             lasttp = lasttp->prev;
799             c--;
800             while(lasttp && c > 0 && lasttp->datestamp == 0) {
801                 printf(", %s", lasttp->label);
802                 lasttp = lasttp->prev;
803                 c--;
804             }
805             printf(".\n");
806         }
807     }
808 }
809
810 /* ----------------------------------------------- */
811
812 void balance(argc, argv)
813 int argc;
814 char **argv;
815 {
816     disk_t *dp;
817     struct balance_stats {
818         int disks;
819         long origsize, outsize;
820     } *sp;
821     int conf_runspercycle, conf_dumpcycle;
822     int seq, runs_per_cycle, overdue, max_overdue;
823     int later, total, balance, distinct;
824     float fseq, disk_dumpcycle;
825     info_t info;
826     long int total_balanced, balanced;
827     int empty_day;
828
829     time(&today);
830     conf_dumpcycle = getconf_int(CNF_DUMPCYCLE);
831     conf_runspercycle = getconf_int(CNF_RUNSPERCYCLE);
832     later = conf_dumpcycle;
833     if(later > 10000) later = 10000;
834     overdue = 0;
835     max_overdue = 0;
836
837     if(argc > 4 && strcmp(argv[3],"--days") == 0) {
838         later = atoi(argv[4]);
839         if(later < 0) later = conf_dumpcycle;
840     }
841
842     if(conf_runspercycle == 0) {
843         runs_per_cycle = conf_dumpcycle;
844     } else if(conf_runspercycle == -1 ) {
845         runs_per_cycle = guess_runs_from_tapelist();
846     } else
847         runs_per_cycle = conf_runspercycle;
848
849     if (runs_per_cycle <= 0) {
850         runs_per_cycle = 1;
851     }
852
853     total = later + 1;
854     balance = later + 2;
855     distinct = later + 3;
856
857     sp = (struct balance_stats *)
858         alloc(sizeof(struct balance_stats) * (distinct+1));
859
860     for(seq=0; seq <= distinct; seq++)
861         sp[seq].disks = sp[seq].origsize = sp[seq].outsize = 0;
862
863     for(dp = diskq.head; dp != NULL; dp = dp->next) {
864         if(get_info(dp->host->hostname, dp->name, &info)) {
865             printf("new disk %s:%s ignored.\n", dp->host->hostname, dp->name);
866             continue;
867         }
868         if (dp->strategy == DS_NOFULL) {
869             continue;
870         }
871         sp[distinct].disks++;
872         sp[distinct].origsize += info.inf[0].size/unitdivisor;
873         sp[distinct].outsize += info.inf[0].csize/unitdivisor;
874
875         sp[balance].disks++;
876         if(dp->dumpcycle == 0) {
877             sp[balance].origsize += (info.inf[0].size/unitdivisor) * runs_per_cycle;
878             sp[balance].outsize += (info.inf[0].csize/unitdivisor) * runs_per_cycle;
879         }
880         else {
881             sp[balance].origsize += (info.inf[0].size/unitdivisor) *
882                                     (conf_dumpcycle / dp->dumpcycle);
883             sp[balance].outsize += (info.inf[0].csize/unitdivisor) *
884                                    (conf_dumpcycle / dp->dumpcycle);
885         }
886
887         disk_dumpcycle = dp->dumpcycle;
888         if(dp->dumpcycle <= 0)
889             disk_dumpcycle = ((float)conf_dumpcycle) / ((float)runs_per_cycle);
890
891         seq = next_level0(dp, &info);
892         fseq = seq + 0.0001;
893         do {
894             if(seq < 0) {
895                 overdue++;
896                 if (-seq > max_overdue)
897                     max_overdue = -seq;
898                 seq = 0;
899                 fseq = seq + 0.0001;
900             }
901             if(seq > later) {
902                 seq = later;
903             }
904             
905             sp[seq].disks++;
906             sp[seq].origsize += info.inf[0].size/unitdivisor;
907             sp[seq].outsize += info.inf[0].csize/unitdivisor;
908
909             if(seq < later) {
910                 sp[total].disks++;
911                 sp[total].origsize += info.inf[0].size/unitdivisor;
912                 sp[total].outsize += info.inf[0].csize/unitdivisor;
913             }
914             
915             /* See, if there's another run in this dumpcycle */
916             fseq += disk_dumpcycle;
917             seq = fseq;
918         } while (seq < later);
919     }
920
921     if(sp[total].outsize == 0 && sp[later].outsize == 0) {
922         printf("\nNo data to report on yet.\n");
923         amfree(sp);
924         return;
925     }
926
927     balanced = sp[balance].outsize / runs_per_cycle;
928     if(conf_dumpcycle == later) {
929         total_balanced = sp[total].outsize / runs_per_cycle;
930     }
931     else {
932         total_balanced = 1024*(((sp[total].outsize/1024) * conf_dumpcycle)
933                             / (runs_per_cycle * later));
934     }
935
936     empty_day = 0;
937     printf("\n due-date  #fs    orig %cB     out %cB   balance\n",
938            displayunit[0], displayunit[0]);
939     printf("----------------------------------------------\n");
940     for(seq = 0; seq < later; seq++) {
941         if(sp[seq].disks == 0 &&
942            ((seq > 0 && sp[seq-1].disks == 0) ||
943             ((seq < later-1) && sp[seq+1].disks == 0))) {
944             empty_day++;
945         }
946         else {
947             if(empty_day > 0) {
948                 printf("\n");
949                 empty_day = 0;
950             }
951             printf("%-9.9s  %3d %10ld %10ld ",
952                    seqdatestr(seq), sp[seq].disks,
953                    sp[seq].origsize, sp[seq].outsize);
954             if(!sp[seq].outsize) printf("     --- \n");
955             else printf("%+8.1f%%\n",
956                         (sp[seq].outsize-balanced)*100.0/(double)balanced);
957         }
958     }
959
960     if(sp[later].disks != 0) {
961         printf("later      %3d %10ld %10ld ",
962                sp[later].disks,
963                sp[later].origsize, sp[later].outsize);
964         if(!sp[later].outsize) printf("     --- \n");
965         else printf("%+8.1f%%\n",
966                     (sp[later].outsize-balanced)*100.0/(double)balanced);
967     }
968     printf("----------------------------------------------\n");
969     printf("TOTAL      %3d %10ld %10ld %9ld\n", sp[total].disks,
970            sp[total].origsize, sp[total].outsize, total_balanced);
971     if (sp[balance].origsize != sp[total].origsize ||
972         sp[balance].outsize != sp[total].outsize ||
973         balanced != total_balanced) {
974         printf("BALANCED       %10ld %10ld %9ld\n",
975                sp[balance].origsize, sp[balance].outsize, balanced);
976     }
977     if (sp[distinct].disks != sp[total].disks) {
978         printf("DISTINCT   %3d %10ld %10ld\n", sp[distinct].disks,
979                sp[distinct].origsize, sp[distinct].outsize);
980     }
981     printf("  (estimated %d run%s per dumpcycle)\n",
982            runs_per_cycle, (runs_per_cycle == 1) ? "" : "s");
983     if (overdue) {
984         printf(" (%d filesystem%s overdue, the most being overdue %d day%s)\n",
985                overdue, (overdue == 1) ? "" : "s",
986                max_overdue, (max_overdue == 1) ? "" : "s");
987     }
988     amfree(sp);
989 }
990
991
992 /* ----------------------------------------------- */
993
994 void find(argc, argv)
995 int argc;
996 char **argv;
997 {
998     int start_argc;
999     char *sort_order = NULL;
1000     find_result_t *output_find;
1001
1002     if(argc < 3) {
1003         fprintf(stderr,
1004                 "%s: expecting \"find [--sort <hkdlpb>] [hostname [<disk>]]*\"\n",
1005                 get_pname());
1006         usage();
1007     }
1008
1009
1010     sort_order = newstralloc(sort_order, DEFAULT_SORT_ORDER);
1011     if(argc > 4 && strcmp(argv[3],"--sort") == 0) {
1012         int i, valid_sort=1;
1013
1014         for(i=strlen(argv[4])-1;i>=0;i--) {
1015             switch (argv[4][i]) {
1016             case 'h':
1017             case 'H':
1018             case 'k':
1019             case 'K':
1020             case 'd':
1021             case 'D':
1022             case 'l':
1023             case 'L':
1024             case 'b':
1025             case 'B':
1026                     break;
1027             default: valid_sort=0;
1028             }
1029         }
1030         if(valid_sort) {
1031             sort_order = newstralloc(sort_order, argv[4]);
1032         } else {
1033             printf("Invalid sort order: %s\n", argv[4]);
1034             printf("Use default sort order: %s\n", sort_order);
1035         }
1036         start_argc=6;
1037     } else {
1038         start_argc=4;
1039     }
1040     match_disklist(&diskq, argc-(start_argc-1), argv+(start_argc-1));
1041     output_find = find_dump(1, &diskq);
1042     if(argc-(start_argc-1) > 0) {
1043         free_find_result(&output_find);
1044         match_disklist(&diskq, argc-(start_argc-1), argv+(start_argc-1));
1045         output_find = find_dump(0, NULL);
1046     }
1047
1048     sort_find_result(sort_order, &output_find);
1049     print_find_result(output_find);
1050     free_find_result(&output_find);
1051
1052     amfree(sort_order);
1053 }
1054
1055
1056 /* ------------------------ */
1057
1058
1059 /* shared code with planner.c */
1060
1061 int bump_thresh(level)
1062 int level;
1063 {
1064     int bump = getconf_int(CNF_BUMPSIZE);
1065     double mult = getconf_real(CNF_BUMPMULT);
1066
1067     while(--level) bump = (int) bump * mult;
1068     return bump;
1069 }
1070
1071 void bumpsize(argc, argv)
1072 int argc;
1073 char **argv;
1074 {
1075     int l;
1076     int conf_bumppercent = getconf_int(CNF_BUMPPERCENT);
1077     double conf_bumpmult = getconf_real(CNF_BUMPMULT);
1078
1079     printf("Current bump parameters:\n");
1080     if(conf_bumppercent == 0) {
1081         printf("  bumpsize %5d KB\t- minimum savings (threshold) to bump level 1 -> 2\n",
1082                getconf_int(CNF_BUMPSIZE));
1083         printf("  bumpdays %5d\t- minimum days at each level\n",
1084                getconf_int(CNF_BUMPDAYS));
1085         printf("  bumpmult %5.5g\t- threshold = bumpsize * bumpmult**(level-1)\n\n",
1086                conf_bumpmult);
1087
1088         printf("      Bump -> To  Threshold\n");
1089         for(l = 1; l < 9; l++)
1090             printf("\t%d  ->  %d  %9d KB\n", l, l+1, bump_thresh(l));
1091         putchar('\n');
1092     }
1093     else {
1094         double bumppercent = conf_bumppercent;
1095
1096         printf("  bumppercent %3d %%\t- minimum savings (threshold) to bump level 1 -> 2\n",
1097                conf_bumppercent);
1098         printf("  bumpdays %5d\t- minimum days at each level\n",
1099                getconf_int(CNF_BUMPDAYS));
1100         printf("  bumpmult %5.5g\t- threshold = disk_size * bumppercent * bumpmult**(level-1)\n\n",
1101                conf_bumpmult);
1102         printf("      Bump -> To  Threshold\n");
1103         for(l = 1; l < 9; l++) {
1104             printf("\t%d  ->  %d  %7.2f %%\n", l, l+1, bumppercent);
1105             bumppercent *= conf_bumpmult;
1106             if(bumppercent >= 100.000) { bumppercent = 100.0;}
1107         }
1108         putchar('\n');
1109     }
1110 }
1111
1112 /* ----------------------------------------------- */
1113
1114 void export_one P((disk_t *dp));
1115
1116 void export_db(argc, argv)
1117 int argc;
1118 char **argv;
1119 {
1120     disk_t *dp;
1121     time_t curtime;
1122     char hostname[MAX_HOSTNAME_LENGTH+1];
1123     int i;
1124
1125     printf("CURINFO Version %s CONF %s\n", version(), getconf_str(CNF_ORG));
1126
1127     curtime = time(0);
1128     if(gethostname(hostname, sizeof(hostname)-1) == -1)
1129         error("could not determine host name: %s\n", strerror(errno));
1130     hostname[sizeof(hostname)-1] = '\0';
1131     printf("# Generated by:\n#    host: %s\n#    date: %s",
1132            hostname, ctime(&curtime));
1133
1134     printf("#    command:");
1135     for(i = 0; i < argc; i++)
1136         printf(" %s", argv[i]);
1137
1138     printf("\n# This file can be merged back in with \"amadmin import\".\n");
1139     printf("# Edit only with care.\n");
1140
1141     if(argc >= 4)
1142         diskloop(argc, argv, "export", export_one);
1143     else for(dp = diskq.head; dp != NULL; dp = dp->next)
1144         export_one(dp);
1145 }
1146
1147 void export_one(dp)
1148 disk_t *dp;
1149 {
1150     info_t info;
1151     int i,l;
1152
1153     if(get_info(dp->host->hostname, dp->name, &info)) {
1154         fprintf(stderr, "Warning: no curinfo record for %s:%s\n",
1155                 dp->host->hostname, dp->name);
1156         return;
1157     }
1158     printf("host: %s\ndisk: %s\n", dp->host->hostname, dp->name);
1159     printf("command: %d\n", info.command);
1160     printf("last_level: %d\n",info.last_level);
1161     printf("consecutive_runs: %d\n",info.consecutive_runs);
1162     printf("full-rate:");
1163     for(i=0;i<AVG_COUNT;i++) printf(" %f", info.full.rate[i]);
1164     printf("\nfull-comp:");
1165     for(i=0;i<AVG_COUNT;i++) printf(" %f", info.full.comp[i]);
1166
1167     printf("\nincr-rate:");
1168     for(i=0;i<AVG_COUNT;i++) printf(" %f", info.incr.rate[i]);
1169     printf("\nincr-comp:");
1170     for(i=0;i<AVG_COUNT;i++) printf(" %f", info.incr.comp[i]);
1171     printf("\n");
1172     for(l=0;l<DUMP_LEVELS;l++) {
1173         if(info.inf[l].date < (time_t)0 && info.inf[l].label[0] == '\0') continue;
1174         printf("stats: %d %ld %ld %ld %ld %d %s\n", l,
1175                info.inf[l].size, info.inf[l].csize, info.inf[l].secs,
1176                (long)info.inf[l].date, info.inf[l].filenum,
1177                info.inf[l].label);
1178     }
1179     for(l=0;info.history[l].level > -1;l++) {
1180         printf("history: %d %ld %ld %ld\n",info.history[l].level,
1181                info.history[l].size, info.history[l].csize,
1182                info.history[l].date);
1183     }
1184     printf("//\n");
1185 }
1186
1187 /* ----------------------------------------------- */
1188
1189 int import_one P((void));
1190 char *impget_line P((void));
1191
1192 void import_db(argc, argv)
1193 int argc;
1194 char **argv;
1195 {
1196     int vers_maj, vers_min, vers_patch, newer;
1197     char *org;
1198     char *line = NULL;
1199     char *hdr;
1200     char *s;
1201     int ch;
1202
1203     /* process header line */
1204
1205     if((line = agets(stdin)) == NULL) {
1206         fprintf(stderr, "%s: empty input.\n", get_pname());
1207         return;
1208     }
1209
1210     s = line;
1211     ch = *s++;
1212
1213     hdr = "version";
1214 #define sc "CURINFO Version"
1215     if(strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
1216         goto bad_header;
1217     }
1218     s += sizeof(sc)-1;
1219     ch = *s++;
1220 #undef sc
1221     skip_whitespace(s, ch);
1222     if(ch == '\0'
1223        || sscanf(s - 1, "%d.%d.%d", &vers_maj, &vers_min, &vers_patch) != 3) {
1224         goto bad_header;
1225     }
1226
1227     skip_integer(s, ch);                        /* skip over major */
1228     if(ch != '.') {
1229         goto bad_header;
1230     }
1231     ch = *s++;
1232     skip_integer(s, ch);                        /* skip over minor */
1233     if(ch != '.') {
1234         goto bad_header;
1235     }
1236     ch = *s++;
1237     skip_integer(s, ch);                        /* skip over patch */
1238
1239     hdr = "comment";
1240     if(ch == '\0') {
1241         goto bad_header;
1242     }
1243     skip_non_whitespace(s, ch);
1244     s[-1] = '\0';
1245
1246     hdr = "CONF";
1247     skip_whitespace(s, ch);                     /* find the org keyword */
1248 #define sc "CONF"
1249     if(ch == '\0' || strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
1250         goto bad_header;
1251     }
1252     s += sizeof(sc)-1;
1253     ch = *s++;
1254 #undef sc
1255
1256     hdr = "org";
1257     skip_whitespace(s, ch);                     /* find the org string */
1258     if(ch == '\0') {
1259         goto bad_header;
1260     }
1261     org = s - 1;
1262
1263     newer = (vers_maj != VERSION_MAJOR)? vers_maj > VERSION_MAJOR :
1264             (vers_min != VERSION_MINOR)? vers_min > VERSION_MINOR :
1265                                          vers_patch > VERSION_PATCH;
1266     if(newer)
1267         fprintf(stderr,
1268              "%s: WARNING: input is from newer Amanda version: %d.%d.%d.\n",
1269                 get_pname(), vers_maj, vers_min, vers_patch);
1270
1271     if(strcmp(org, getconf_str(CNF_ORG)) != 0) {
1272         fprintf(stderr, "%s: WARNING: input is from different org: %s\n",
1273                 get_pname(), org);
1274     }
1275
1276     while(import_one());
1277
1278     amfree(line);
1279     return;
1280
1281  bad_header:
1282
1283     amfree(line);
1284     fprintf(stderr, "%s: bad CURINFO header line in input: %s.\n",
1285             get_pname(), hdr);
1286     fprintf(stderr, "    Was the input in \"amadmin export\" format?\n");
1287     return;
1288 }
1289
1290
1291 int import_one P((void))
1292 {
1293     info_t info;
1294     stats_t onestat;
1295     int rc, level;
1296     long onedate;
1297     char *line = NULL;
1298     char *s, *fp;
1299     int ch;
1300     int nb_history, i;
1301     char *hostname = NULL;
1302     char *diskname = NULL;
1303
1304 #if TEXTDB
1305     check_dumpuser();
1306 #endif
1307
1308     memset(&info, 0, sizeof(info_t));
1309
1310     for(level = 0; level < DUMP_LEVELS; level++) {
1311         info.inf[level].date = (time_t)-1;
1312     }
1313
1314     /* get host: disk: command: lines */
1315
1316     hostname = diskname = NULL;
1317
1318     if((line = impget_line()) == NULL) return 0;        /* nothing there */
1319     s = line;
1320     ch = *s++;
1321
1322     skip_whitespace(s, ch);
1323 #define sc "host:"
1324     if(ch == '\0' || strncmp(s - 1, sc, sizeof(sc)-1) != 0) goto parse_err;
1325     s += sizeof(sc)-1;
1326     ch = s[-1];
1327 #undef sc
1328     skip_whitespace(s, ch);
1329     if(ch == '\0') goto parse_err;
1330     fp = s-1;
1331     skip_non_whitespace(s, ch);
1332     s[-1] = '\0';
1333     hostname = stralloc(fp);
1334     s[-1] = ch;
1335
1336     skip_whitespace(s, ch);
1337     while (ch == 0) {
1338       amfree(line);
1339       if((line = impget_line()) == NULL) goto shortfile_err;
1340       s = line;
1341       ch = *s++;
1342       skip_whitespace(s, ch);
1343     }
1344 #define sc "disk:"
1345     if(strncmp(s - 1, sc, sizeof(sc)-1) != 0) goto parse_err;
1346     s += sizeof(sc)-1;
1347     ch = s[-1];
1348 #undef sc
1349     skip_whitespace(s, ch);
1350     if(ch == '\0') goto parse_err;
1351     fp = s-1;
1352     skip_non_whitespace(s, ch);
1353     s[-1] = '\0';
1354     diskname = stralloc(fp);
1355     s[-1] = ch;
1356
1357     amfree(line);
1358     if((line = impget_line()) == NULL) goto shortfile_err;
1359     if(sscanf(line, "command: %d", &info.command) != 1) goto parse_err;
1360
1361     /* get last_level and consecutive_runs */
1362
1363     amfree(line);
1364     if((line = impget_line()) == NULL) goto shortfile_err;
1365     rc = sscanf(line, "last_level: %d", &info.last_level);
1366     if(rc == 1) {
1367         amfree(line);
1368         if((line = impget_line()) == NULL) goto shortfile_err;
1369         if(sscanf(line, "consecutive_runs: %d", &info.consecutive_runs) != 1) goto parse_err;
1370         amfree(line);
1371         if((line = impget_line()) == NULL) goto shortfile_err;
1372     }
1373
1374     /* get rate: and comp: lines for full dumps */
1375
1376     rc = sscanf(line, "full-rate: %f %f %f",
1377                 &info.full.rate[0], &info.full.rate[1], &info.full.rate[2]);
1378     if(rc != 3) goto parse_err;
1379
1380     amfree(line);
1381     if((line = impget_line()) == NULL) goto shortfile_err;
1382     rc = sscanf(line, "full-comp: %f %f %f",
1383                 &info.full.comp[0], &info.full.comp[1], &info.full.comp[2]);
1384     if(rc != 3) goto parse_err;
1385
1386     /* get rate: and comp: lines for incr dumps */
1387
1388     amfree(line);
1389     if((line = impget_line()) == NULL) goto shortfile_err;
1390     rc = sscanf(line, "incr-rate: %f %f %f",
1391                 &info.incr.rate[0], &info.incr.rate[1], &info.incr.rate[2]);
1392     if(rc != 3) goto parse_err;
1393
1394     amfree(line);
1395     if((line = impget_line()) == NULL) goto shortfile_err;
1396     rc = sscanf(line, "incr-comp: %f %f %f",
1397                 &info.incr.comp[0], &info.incr.comp[1], &info.incr.comp[2]);
1398     if(rc != 3) goto parse_err;
1399
1400     /* get stats for dump levels */
1401
1402     while(1) {
1403         amfree(line);
1404         if((line = impget_line()) == NULL) goto shortfile_err;
1405         if(strncmp(line, "//", 2) == 0) {
1406             /* end of record */
1407             break;
1408         }
1409         if(strncmp(line, "history:", 8) == 0) {
1410             /* end of record */
1411             break;
1412         }
1413         memset(&onestat, 0, sizeof(onestat));
1414
1415         s = line;
1416         ch = *s++;
1417
1418         skip_whitespace(s, ch);
1419 #define sc "stats:"
1420         if(ch == '\0' || strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
1421             goto parse_err;
1422         }
1423         s += sizeof(sc)-1;
1424         ch = s[-1];
1425 #undef sc
1426
1427         skip_whitespace(s, ch);
1428         if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
1429             goto parse_err;
1430         }
1431         skip_integer(s, ch);
1432
1433         skip_whitespace(s, ch);
1434         if(ch == '\0' || sscanf(s - 1, "%ld", &onestat.size) != 1) {
1435             goto parse_err;
1436         }
1437         skip_integer(s, ch);
1438
1439         skip_whitespace(s, ch);
1440         if(ch == '\0' || sscanf(s - 1, "%ld", &onestat.csize) != 1) {
1441             goto parse_err;
1442         }
1443         skip_integer(s, ch);
1444
1445         skip_whitespace(s, ch);
1446         if(ch == '\0' || sscanf(s - 1, "%ld", &onestat.secs) != 1) {
1447             goto parse_err;
1448         }
1449         skip_integer(s, ch);
1450
1451         skip_whitespace(s, ch);
1452         if(ch == '\0' || sscanf(s - 1, "%ld", &onedate) != 1) {
1453             goto parse_err;
1454         }
1455         skip_integer(s, ch);
1456
1457         skip_whitespace(s, ch);
1458         if(ch != '\0') {
1459             if(sscanf(s - 1, "%d", &onestat.filenum) != 1) {
1460                 goto parse_err;
1461             }
1462             skip_integer(s, ch);
1463
1464             skip_whitespace(s, ch);
1465             if(ch == '\0') {
1466                 if (onestat.filenum != 0)
1467                     goto parse_err;
1468                 onestat.label[0] = '\0';
1469             } else {
1470                 strncpy(onestat.label, s - 1, sizeof(onestat.label)-1);
1471                 onestat.label[sizeof(onestat.label)-1] = '\0';
1472             }
1473         }
1474
1475         /* time_t not guarranteed to be long */
1476         onestat.date = onedate;
1477         if(level < 0 || level > 9) goto parse_err;
1478
1479         info.inf[level] = onestat;
1480     }
1481     nb_history = 0;
1482     for(i=0;i<=NB_HISTORY;i++) {
1483         info.history[i].level = -2;
1484     }
1485     while(1) {
1486         history_t onehistory;
1487         long date;
1488
1489         if(line[0] == '/' && line[1] == '/') {
1490             info.history[nb_history].level = -2;
1491             rc = 0;
1492             break;
1493         }
1494         memset(&onehistory, 0, sizeof(onehistory));
1495         s = line;
1496         ch = *s++;
1497 #define sc "history:"
1498         if(strncmp(line, sc, sizeof(sc)-1) != 0) {
1499             break;
1500         }
1501         s += sizeof(sc)-1;
1502         ch = s[-1];
1503 #undef sc
1504         skip_whitespace(s, ch);
1505         if(ch == '\0' || sscanf((s - 1), "%d", &onehistory.level) != 1) {
1506             break;
1507         }
1508         skip_integer(s, ch);
1509
1510         skip_whitespace(s, ch);
1511         if(ch == '\0' || sscanf((s - 1), "%ld", &onehistory.size) != 1) {
1512             break;
1513         }
1514         skip_integer(s, ch);
1515
1516         skip_whitespace(s, ch);
1517         if(ch == '\0' || sscanf((s - 1), "%ld", &onehistory.csize) != 1) {
1518             break;
1519         }
1520         skip_integer(s, ch);
1521
1522         skip_whitespace(s, ch);
1523         if(ch == '\0' || sscanf((s - 1), "%ld", &date) != 1) {
1524             break;
1525         }
1526         skip_integer(s, ch);
1527         onehistory.date = date; /* time_t not guarranteed to be long */
1528
1529         info.history[nb_history++] = onehistory;
1530         amfree(line);
1531         if((line = impget_line()) == NULL) goto shortfile_err;
1532     }
1533     amfree(line);
1534
1535     /* got a full record, now write it out to the database */
1536
1537     if(put_info(hostname, diskname, &info)) {
1538         fprintf(stderr, "%s: error writing record for %s:%s\n",
1539                 get_pname(), hostname, diskname);
1540     }
1541     amfree(hostname);
1542     amfree(diskname);
1543     return 1;
1544
1545  parse_err:
1546     amfree(line);
1547     amfree(hostname);
1548     amfree(diskname);
1549     fprintf(stderr, "%s: parse error reading import record.\n", get_pname());
1550     return 0;
1551
1552  shortfile_err:
1553     amfree(line);
1554     amfree(hostname);
1555     amfree(diskname);
1556     fprintf(stderr, "%s: short file reading import record.\n", get_pname());
1557     return 0;
1558 }
1559
1560 char *
1561 impget_line ()
1562 {
1563     char *line;
1564     char *s;
1565     int ch;
1566
1567     for(; (line = agets(stdin)) != NULL; free(line)) {
1568         s = line;
1569         ch = *s++;
1570
1571         skip_whitespace(s, ch);
1572         if(ch == '#') {
1573             /* ignore comment lines */
1574             continue;
1575         } else if(ch) {
1576             /* found non-blank, return line */
1577             return line;
1578         }
1579         /* otherwise, a blank line, so keep going */
1580     }
1581     if(ferror(stdin)) {
1582         fprintf(stderr, "%s: reading stdin: %s\n",
1583                 get_pname(), strerror(errno));
1584     }
1585     return NULL;
1586 }
1587
1588 /* ----------------------------------------------- */
1589
1590 void disklist_one(dp)
1591 disk_t *dp;
1592 {
1593     am_host_t *hp;
1594     interface_t *ip;
1595     sle_t *excl;
1596     time_t st;
1597     struct tm *stm;
1598
1599     hp = dp->host;
1600     ip = hp->netif;
1601
1602     printf("line %d:\n", dp->line);
1603
1604     printf("    host %s:\n", hp->hostname);
1605     printf("        interface %s\n",
1606            ip->name[0] ? ip->name : "default");
1607
1608     printf("    disk %s:\n", dp->name);
1609     if(dp->device) printf("        device %s\n", dp->device);
1610
1611     printf("        program \"%s\"\n", dp->program);
1612     if(dp->exclude_file != NULL && dp->exclude_file->nb_element > 0) {
1613         printf("        exclude file");
1614         for(excl = dp->exclude_file->first; excl != NULL; excl = excl->next) {
1615             printf(" \"%s\"", excl->name);
1616         }
1617         printf("\n");
1618     }
1619     if(dp->exclude_list != NULL && dp->exclude_list->nb_element > 0) {
1620         printf("        exclude list");
1621         if(dp->exclude_optional) printf(" optional");
1622         for(excl = dp->exclude_list->first; excl != NULL; excl = excl->next) {
1623             printf(" \"%s\"", excl->name);
1624         }
1625         printf("\n");
1626     }
1627     if(dp->include_file != NULL && dp->include_file->nb_element > 0) {
1628         printf("        include file");
1629         for(excl = dp->include_file->first; excl != NULL; excl = excl->next) {
1630             printf(" \"%s\"", excl->name);
1631         }
1632         printf("\n");
1633     }
1634     if(dp->include_list != NULL && dp->include_list->nb_element > 0) {
1635         printf("        include list");
1636         if(dp->include_optional) printf(" optional");
1637         for(excl = dp->include_list->first; excl != NULL; excl = excl->next) {
1638             printf(" \"%s\"", excl->name);
1639         }
1640         printf("\n");
1641     }
1642     printf("        priority %ld\n", dp->priority);
1643     printf("        dumpcycle %ld\n", dp->dumpcycle);
1644     printf("        maxdumps %d\n", dp->maxdumps);
1645     printf("        maxpromoteday %d\n", dp->maxpromoteday);
1646     if(dp->bumppercent > 0) {
1647         printf("        bumppercent %d\n", dp->bumppercent);
1648     }
1649     else {
1650         printf("        bumpsize %d\n", dp->bumpsize);
1651     }
1652     printf("        bumpdays %d\n", dp->bumpdays);
1653     printf("        bumpmult %f\n", dp->bumpmult);
1654
1655     printf("        strategy ");
1656     switch(dp->strategy) {
1657     case DS_SKIP:
1658         printf("SKIP\n");
1659         break;
1660     case DS_STANDARD:
1661         printf("STANDARD\n");
1662         break;
1663     case DS_NOFULL:
1664         printf("NOFULL\n");
1665         break;
1666     case DS_NOINC:
1667         printf("NOINC\n");
1668         break;
1669     case DS_HANOI:
1670         printf("HANOI\n");
1671         break;
1672     case DS_INCRONLY:
1673         printf("INCRONLY\n");
1674         break;
1675     }
1676
1677     printf("        estimate ");
1678     switch(dp->estimate) {
1679     case ES_CLIENT:
1680         printf("CLIENT\n");
1681         break;
1682     case ES_SERVER:
1683         printf("SERVER\n");
1684         break;
1685     case ES_CALCSIZE:
1686         printf("CALCSIZE\n");
1687         break;
1688     }
1689
1690     printf("        compress ");
1691     switch(dp->compress) {
1692     case COMP_NONE:
1693         printf("NONE\n");
1694         break;
1695     case COMP_FAST:
1696         printf("CLIENT FAST\n");
1697         break;
1698     case COMP_BEST:
1699         printf("CLIENT BEST\n");
1700         break;
1701     case COMP_SERV_FAST:
1702         printf("SERVER FAST\n");
1703         break;
1704     case COMP_SERV_BEST:
1705         printf("SERVER BEST\n");
1706         break;
1707     }
1708     if(dp->compress != COMP_NONE) {
1709         printf("        comprate %.2f %.2f\n",
1710                dp->comprate[0], dp->comprate[1]);
1711     }
1712
1713     printf("        encrypt ");
1714     switch(dp->encrypt) {
1715     case ENCRYPT_NONE:
1716         printf("NONE\n");
1717         break;
1718     case ENCRYPT_CUST:
1719         printf("CLIENT\n");
1720         break;
1721     case ENCRYPT_SERV_CUST:
1722         printf("SERVER\n");
1723         break;
1724     }
1725
1726     printf("        auth %s\n", dp->security_driver);
1727     printf("        kencrypt %s\n", (dp->kencrypt? "YES" : "NO"));
1728     printf("        holdingdisk %s\n", (!dp->no_hold? "YES" : "NO"));
1729     printf("        record %s\n", (dp->record? "YES" : "NO"));
1730     printf("        index %s\n", (dp->index? "YES" : "NO"));
1731     st = dp->start_t;
1732         if(st) {
1733             stm = localtime(&st);
1734             printf("        starttime %d:%02d:%02d\n",
1735               stm->tm_hour, stm->tm_min, stm->tm_sec);
1736         }
1737    
1738     if(dp->tape_splitsize > 0) {
1739         printf("        tape_splitsize %ld\n", dp->tape_splitsize);
1740     }
1741     if(dp->split_diskbuffer) {
1742         printf("        split_diskbuffer %s\n", dp->split_diskbuffer);
1743     }
1744     if(dp->fallback_splitsize > 0) {
1745         printf("        fallback_splitsize %ldMb\n", (dp->fallback_splitsize / 1024));
1746     }
1747     printf("        skip-incr %s\n", (dp->skip_incr? "YES" : "NO"));
1748     printf("        skip-full %s\n", (dp->skip_full? "YES" : "NO"));
1749
1750     printf("\n");
1751 }
1752
1753 void disklist(argc, argv)
1754 int argc;
1755 char **argv;
1756 {
1757     disk_t *dp;
1758
1759     if(argc >= 4)
1760         diskloop(argc, argv, "disklist", disklist_one);
1761     else
1762         for(dp = diskq.head; dp != NULL; dp = dp->next)
1763             disklist_one(dp);
1764 }
1765
1766 void show_version(argc, argv)
1767 int argc;
1768 char **argv;
1769 {
1770     int i;
1771
1772     for(i = 0; version_info[i] != NULL; i++)
1773         printf("%s", version_info[i]);
1774 }