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