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