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