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