Imported Upstream version 2.4.5
[debian/amanda] / server-src / planner.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  * Authors: the Amanda Development Team.  Its members are listed in a
24  * file named AUTHORS, in the root directory of this distribution.
25  */
26 /*
27  * $Id: planner.c,v 1.76.2.15.2.13.2.32.2.16 2005/03/16 18:09:50 martinea Exp $
28  *
29  * backup schedule planner for the Amanda backup system.
30  */
31 #include "amanda.h"
32 #include "arglist.h"
33 #include "conffile.h"
34 #include "diskfile.h"
35 #include "tapefile.h"
36 #include "infofile.h"
37 #include "logfile.h"
38 #include "clock.h"
39 #include "dgram.h"
40 #include "protocol.h"
41 #include "version.h"
42 #include "amfeatures.h"
43 #include "server_util.h"
44 #include "holding.h"
45
46 #define MAX_LEVELS                  3   /* max# of estimates per filesys */
47
48 #define RUNS_REDZONE                5   /* should be in conf file? */
49
50 #define PROMOTE_THRESHOLD        0.05   /* if <5% unbalanced, don't promote */
51 #define DEFAULT_DUMPRATE         30.0   /* K/s */
52
53 /* configuration file stuff */
54
55 char *conf_tapetype;
56 int conf_maxdumpsize;
57 int conf_runtapes;
58 int conf_dumpcycle;
59 int conf_runspercycle;
60 int conf_tapecycle;
61 int conf_etimeout;
62 int conf_reserve;
63 int conf_autoflush;
64
65 #define HOST_READY                              ((void *)0)     /* must be 0 */
66 #define HOST_ACTIVE                             ((void *)1)
67 #define HOST_DONE                               ((void *)2)
68
69 #define DISK_READY                              0               /* must be 0 */
70 #define DISK_ACTIVE                             1
71 #define DISK_PARTIALY_DONE                      2
72 #define DISK_DONE                               3
73
74 typedef struct est_s {
75     int state;
76     int got_estimate;
77     int dump_priority;
78     int dump_level;
79     long dump_size;
80     int degr_level;     /* if dump_level == 0, what would be the inc level */
81     long degr_size;
82     int last_level;
83     long last_lev0size;
84     int next_level0;
85     int level_days;
86     int promote;
87     double fullrate, incrrate;
88     double fullcomp, incrcomp;
89     char *errstr;
90     int level[MAX_LEVELS];
91     char *dumpdate[MAX_LEVELS];
92     long est_size[MAX_LEVELS];
93 } est_t;
94
95 #define est(dp) ((est_t *)(dp)->up)
96
97 /* pestq = partial estimate */
98 disklist_t startq, waitq, pestq, estq, failq, schedq;
99 long total_size;
100 double total_lev0, balanced_size, balance_threshold;
101 unsigned long tape_length, tape_mark;
102 int result_port, amanda_port;
103
104 static am_feature_t *our_features = NULL;
105 static char *our_feature_string = NULL;
106
107 #ifdef KRB4_SECURITY
108 int kamanda_port;
109 #endif
110
111 tapetype_t *tape;
112 long tt_blocksize;
113 long tt_blocksize_kb;
114 int runs_per_cycle = 0;
115 time_t today;
116
117 dgram_t *msg;
118
119 /* We keep a LIFO queue of before images for all modifications made
120  * to schedq in our attempt to make the schedule fit on the tape.
121  * Enough information is stored to reinstate a dump if it turns out
122  * that it shouldn't have been touched after all.
123  */
124 typedef struct bi_s {
125     struct bi_s *next;
126     struct bi_s *prev;
127     int deleted;                /* 0=modified, 1=deleted */
128     disk_t *dp;                 /* The disk that was changed */
129     int level;                  /* The original level */
130     long size;                  /* The original size */
131     char *errstr;               /* A message describing why this disk is here */
132 } bi_t;
133
134 typedef struct bilist_s {
135     bi_t *head, *tail;
136 } bilist_t;
137
138 bilist_t biq;                   /* The BI queue itself */
139
140 char *datestamp = NULL;
141
142 /*
143  * ========================================================================
144  * MAIN PROGRAM
145  *
146  */
147
148 static void setup_estimate P((disk_t *dp));
149 static void get_estimates P((void));
150 static void analyze_estimate P((disk_t *dp));
151 static void handle_failed P((disk_t *dp));
152 static void delay_dumps P((void));
153 static int promote_highest_priority_incremental P((void));
154 static int promote_hills P((void));
155 static void output_scheduleline P((disk_t *dp));
156
157 int main(argc, argv)
158 int argc;
159 char **argv;
160 {
161     disklist_t *origqp;
162     disk_t *dp;
163     int moved_one;
164     char **vp;
165     unsigned long malloc_hist_1, malloc_size_1;
166     unsigned long malloc_hist_2, malloc_size_2;
167     long initial_size;
168     int fd;
169     char *conffile;
170     char *conf_diskfile;
171     char *conf_tapelist;
172     char *conf_infofile;
173     times_t section_start;
174
175     for(fd = 3; fd < FD_SETSIZE; fd++) {
176         /*
177          * Make sure nobody spoofs us with a lot of extra open files
178          * that would cause an open we do to get a very high file
179          * descriptor, which in turn might be used as an index into
180          * an array (e.g. an fd_set).
181          */
182         close(fd);
183     }
184
185     setvbuf(stderr, (char *)NULL, _IOLBF, 0);
186
187     if (argc > 1) {
188         config_name = stralloc(argv[1]);
189         config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
190     } else {
191         char my_cwd[STR_SIZE];
192
193         if (getcwd(my_cwd, sizeof(my_cwd)) == NULL) {
194             error("cannot determine current working directory");
195         }
196         config_dir = stralloc2(my_cwd, "/");
197         if ((config_name = strrchr(my_cwd, '/')) != NULL) {
198             config_name = stralloc(config_name + 1);
199         }
200     }
201
202     safe_cd();
203
204     set_pname("planner");
205
206     malloc_size_1 = malloc_inuse(&malloc_hist_1);
207
208     erroutput_type = (ERR_AMANDALOG|ERR_INTERACTIVE);
209     set_logerror(logerror);
210     startclock();
211     section_start = curclock();
212
213     our_features = am_init_feature_set();
214     our_feature_string = am_feature_to_string(our_features);
215
216     fprintf(stderr, "%s: pid %ld executable %s version %s\n",
217             get_pname(), (long) getpid(), argv[0], version());
218     for(vp = version_info; *vp != NULL; vp++)
219         fprintf(stderr, "%s: %s", get_pname(), *vp);
220
221     /*
222      * 1. Networking Setup
223      *
224      * Planner runs setuid to get a priviledged socket for BSD security.
225      * We get the socket right away as root, then setuid back to a normal
226      * user.  If we are not using BSD security, planner is not installed
227      * setuid root.
228      */
229
230     /* set up dgram port first thing */
231
232     msg = dgram_alloc();
233
234     if(dgram_bind(msg, &result_port) == -1) {
235         error("could not bind result datagram port: %s", strerror(errno));
236     }
237
238     if(geteuid() == 0) {
239         /* set both real and effective uid's to real uid, likewise for gid */
240         setgid(getgid());
241         setuid(getuid());
242     }
243
244     /*
245      * From this point on we are running under our real uid, so we don't
246      * have to worry about opening security holes below.  Make sure we
247      * are a valid user.
248      */
249
250     if(getpwuid(getuid()) == NULL) {
251         error("can't get login name for my uid %ld", (long)getuid());
252     }
253
254     /*
255      * 2. Read in Configuration Information
256      *
257      * All the Amanda configuration files are loaded before we begin.
258      */
259
260     fprintf(stderr,"READING CONF FILES...\n");
261
262     conffile = stralloc2(config_dir, CONFFILE_NAME);
263     if(read_conffile(conffile)) {
264         error("errors processing config file \"%s\"", conffile);
265     }
266     amfree(conffile);
267     conf_diskfile = getconf_str(CNF_DISKFILE);
268     if (*conf_diskfile == '/') {
269         conf_diskfile = stralloc(conf_diskfile);
270     } else {
271         conf_diskfile = stralloc2(config_dir, conf_diskfile);
272     }
273     if((origqp = read_diskfile(conf_diskfile)) == NULL) {
274         error("could not load disklist \"%s\"", conf_diskfile);
275     }
276     match_disklist(origqp, argc-2, argv+2);
277     for(dp = origqp->head; dp != NULL; dp = dp->next) {
278         if(dp->todo)
279             log_add(L_DISK, "%s %s", dp->host->hostname, dp->name);
280     }
281     amfree(conf_diskfile);
282     conf_tapelist = getconf_str(CNF_TAPELIST);
283     if (*conf_tapelist == '/') {
284         conf_tapelist = stralloc(conf_tapelist);
285     } else {
286         conf_tapelist = stralloc2(config_dir, conf_tapelist);
287     }
288     if(read_tapelist(conf_tapelist)) {
289         error("could not load tapelist \"%s\"", conf_tapelist);
290     }
291     amfree(conf_tapelist);
292     conf_infofile = getconf_str(CNF_INFOFILE);
293     if (*conf_infofile == '/') {
294         conf_infofile = stralloc(conf_infofile);
295     } else {
296         conf_infofile = stralloc2(config_dir, conf_infofile);
297     }
298     if(open_infofile(conf_infofile)) {
299         error("could not open info db \"%s\"", conf_infofile);
300     }
301     amfree(conf_infofile);
302
303     conf_tapetype = getconf_str(CNF_TAPETYPE);
304     conf_maxdumpsize = getconf_int(CNF_MAXDUMPSIZE);
305     conf_runtapes = getconf_int(CNF_RUNTAPES);
306     conf_dumpcycle = getconf_int(CNF_DUMPCYCLE);
307     conf_runspercycle = getconf_int(CNF_RUNSPERCYCLE);
308     conf_tapecycle = getconf_int(CNF_TAPECYCLE);
309     conf_etimeout = getconf_int(CNF_ETIMEOUT);
310     conf_reserve  = getconf_int(CNF_RESERVE);
311     conf_autoflush = getconf_int(CNF_AUTOFLUSH);
312
313     amfree(datestamp);
314     today = time(0);
315     datestamp = construct_datestamp(NULL);
316     log_add(L_START, "date %s", datestamp);
317
318     /* some initializations */
319
320     if(conf_runspercycle == 0) {
321         runs_per_cycle = conf_dumpcycle;
322     } else if(conf_runspercycle == -1 ) {
323         runs_per_cycle = guess_runs_from_tapelist();
324     } else
325         runs_per_cycle = conf_runspercycle;
326
327     if (runs_per_cycle <= 0) {
328         runs_per_cycle = 1;
329     }
330
331     /*
332      * do some basic sanity checking
333      */
334      if(conf_tapecycle <= runs_per_cycle) {
335         log_add(L_WARNING, "tapecycle (%d) <= runspercycle (%d)",
336                 conf_tapecycle, runs_per_cycle);
337      }
338     
339     tape = lookup_tapetype(conf_tapetype);
340     if(conf_maxdumpsize > 0) {
341         tape_length = conf_maxdumpsize;
342     }
343     else {
344         tape_length = tape->length * conf_runtapes;
345     }
346     tape_mark   = tape->filemark;
347     tt_blocksize_kb = tape->blocksize;
348     tt_blocksize = tt_blocksize_kb * 1024;
349
350     proto_init(msg->socket, today, 1000); /* XXX handles should eq nhosts */
351
352 #ifdef KRB4_SECURITY
353     kerberos_service_init();
354 #endif
355
356     fprintf(stderr, "%s: time %s: startup took %s secs\n",
357                     get_pname(),
358                     walltime_str(curclock()),
359                     walltime_str(timessub(curclock(), section_start)));
360
361     /*
362      * 3. Send autoflush dumps left on the holding disks
363      *
364      * This should give us something to do while we generate the new
365      * dump schedule.
366      */
367
368     fprintf(stderr,"\nSENDING FLUSHES...\n");
369     if(conf_autoflush) {
370         dumpfile_t file;
371         sl_t *holding_list;
372         sle_t *holding_file;
373         holding_list = get_flush(NULL, NULL, 0, 0);
374         for(holding_file=holding_list->first; holding_file != NULL;
375                                        holding_file = holding_file->next) {
376             get_dumpfile(holding_file->name, &file);
377             
378             log_add(L_DISK, "%s %s", file.name, file.disk);
379             fprintf(stderr,
380                     "FLUSH %s %s %s %d %s\n",
381                     file.name,
382                     file.disk,
383                     file.datestamp,
384                     file.dumplevel,
385                     holding_file->name);
386             fprintf(stdout,
387                     "FLUSH %s %s %s %d %s\n",
388                     file.name,
389                     file.disk,
390                     file.datestamp,
391                     file.dumplevel,
392                     holding_file->name);
393         }
394         free_sl(holding_list);
395         holding_list = NULL;
396     }
397     fprintf(stderr, "ENDFLUSH\n");
398     fprintf(stdout, "ENDFLUSH\n");
399     fflush(stdout);
400
401     /*
402      * 4. Calculate Preliminary Dump Levels
403      *
404      * Before we can get estimates from the remote slave hosts, we make a
405      * first attempt at guessing what dump levels we will be dumping at
406      * based on the curinfo database.
407      */
408
409     fprintf(stderr,"\nSETTING UP FOR ESTIMATES...\n");
410     section_start = curclock();
411
412     startq.head = startq.tail = NULL;
413     while(!empty(*origqp)) {
414         disk_t *dp = dequeue_disk(origqp);
415         if(dp->todo == 1) {
416             setup_estimate(dp);
417         }
418     }
419
420     fprintf(stderr, "%s: time %s: setting up estimates took %s secs\n",
421                     get_pname(),
422                     walltime_str(curclock()),
423                     walltime_str(timessub(curclock(), section_start)));
424
425
426     /*
427      * 5. Get Dump Size Estimates from Remote Client Hosts
428      *
429      * Each host is queried (in parallel) for dump size information on all
430      * of its disks, and the results gathered as they come in.
431      */
432
433     /* go out and get the dump estimates */
434
435     fprintf(stderr,"\nGETTING ESTIMATES...\n");
436     section_start = curclock();
437
438     estq.head = estq.tail = NULL;
439     pestq.head = pestq.tail = NULL;
440     waitq.head = waitq.tail = NULL;
441     failq.head = failq.tail = NULL;
442
443     get_estimates();
444
445     fprintf(stderr, "%s: time %s: getting estimates took %s secs\n",
446                     get_pname(),
447                     walltime_str(curclock()),
448                     walltime_str(timessub(curclock(), section_start)));
449
450     /*
451      * At this point, all disks with estimates are in estq, and
452      * all the disks on hosts that didn't respond to our inquiry
453      * are in failq.
454      */
455
456     dump_queue("FAILED", failq, 15, stderr);
457     dump_queue("DONE", estq, 15, stderr);
458
459
460     /*
461      * 6. Analyze Dump Estimates
462      *
463      * Each disk's estimates are looked at to determine what level it
464      * should dump at, and to calculate the expected size and time taking
465      * historical dump rates and compression ratios into account.  The
466      * total expected size is accumulated as well.
467      */
468
469     fprintf(stderr,"\nANALYZING ESTIMATES...\n");
470     section_start = curclock();
471
472                         /* an empty tape still has a label and an endmark */
473     total_size = (tt_blocksize_kb + tape_mark) * 2;
474     total_lev0 = 0.0;
475     balanced_size = 0.0;
476
477     schedq.head = schedq.tail = NULL;
478     while(!empty(estq)) analyze_estimate(dequeue_disk(&estq));
479     while(!empty(failq)) handle_failed(dequeue_disk(&failq));
480
481     /*
482      * At this point, all the disks are on schedq sorted by priority.
483      * The total estimated size of the backups is in total_size.
484      */
485
486     {
487         disk_t *dp;
488
489         fprintf(stderr, "INITIAL SCHEDULE (size %ld):\n", total_size);
490         for(dp = schedq.head; dp != NULL; dp = dp->next) {
491             fprintf(stderr, "  %s %s pri %d lev %d size %ld\n",
492                     dp->host->hostname, dp->name, est(dp)->dump_priority,
493                     est(dp)->dump_level, est(dp)->dump_size);
494         }
495     }
496
497
498     /*
499      * 7. Delay Dumps if Schedule Too Big
500      *
501      * If the generated schedule is too big to fit on the tape, we need to
502      * delay some full dumps to make room.  Incrementals will be done
503      * instead (except for new or forced disks).
504      *
505      * In extreme cases, delaying all the full dumps is not even enough.
506      * If so, some low-priority incrementals will be skipped completely
507      * until the dumps fit on the tape.
508      */
509
510     fprintf(stderr,
511       "\nDELAYING DUMPS IF NEEDED, total_size %ld, tape length %lu mark %lu\n",
512             total_size, tape_length, tape_mark);
513
514     initial_size = total_size;
515
516     delay_dumps();
517
518     /* XXX - why bother checking this? */
519     if(empty(schedq) && total_size < initial_size)
520         error("cannot fit anything on tape, bailing out");
521
522
523     /*
524      * 8. Promote Dumps if Schedule Too Small
525      *
526      * Amanda attempts to balance the full dumps over the length of the
527      * dump cycle.  If this night's full dumps are too small relative to
528      * the other nights, promote some high-priority full dumps that will be
529      * due for the next run, to full dumps for tonight, taking care not to
530      * overflow the tape size.
531      *
532      * This doesn't work too well for small sites.  For these we scan ahead
533      * looking for nights that have an excessive number of dumps and promote
534      * one of them.
535      *
536      * Amanda never delays full dumps just for the sake of balancing the
537      * schedule, so it can take a full cycle to balance the schedule after
538      * a big bump.
539      */
540
541     fprintf(stderr,
542      "\nPROMOTING DUMPS IF NEEDED, total_lev0 %1.0f, balanced_size %1.0f...\n",
543             total_lev0, balanced_size);
544
545     balance_threshold = balanced_size * PROMOTE_THRESHOLD;
546     moved_one = 1;
547     while((balanced_size - total_lev0) > balance_threshold && moved_one)
548         moved_one = promote_highest_priority_incremental();
549
550     moved_one = promote_hills();
551
552     fprintf(stderr, "%s: time %s: analysis took %s secs\n",
553                     get_pname(),
554                     walltime_str(curclock()),
555                     walltime_str(timessub(curclock(), section_start)));
556
557
558     /*
559      * 9. Output Schedule
560      *
561      * The schedule goes to stdout, presumably to driver.  A copy is written
562      * on stderr for the debug file.
563      */
564
565     fprintf(stderr,"\nGENERATING SCHEDULE:\n--------\n");
566
567     while(!empty(schedq)) output_scheduleline(dequeue_disk(&schedq));
568     fprintf(stderr, "--------\n");
569
570     close_infofile();
571     log_add(L_FINISH, "date %s time %s", datestamp, walltime_str(curclock()));
572
573     amfree(msg);
574     amfree(datestamp);
575     amfree(config_dir);
576     amfree(config_name);
577     amfree(our_feature_string);
578     am_release_feature_set(our_features);
579     our_features = NULL;
580
581     malloc_size_2 = malloc_inuse(&malloc_hist_2);
582
583     if(malloc_size_1 != malloc_size_2) {
584         malloc_list(fileno(stderr), malloc_hist_1, malloc_hist_2);
585     }
586
587     return 0;
588 }
589
590
591 \f
592 /*
593  * ========================================================================
594  * SETUP FOR ESTIMATES
595  *
596  */
597
598 static int last_level P((info_t *info));                  /* subroutines */
599 static long est_size P((disk_t *dp, int level));
600 static long est_tape_size P((disk_t *dp, int level));
601 static int next_level0 P((disk_t *dp, info_t *info));
602 static int runs_at P((info_t *info, int lev));
603 static long bump_thresh P((int level, long size_level_0, int bumppercent, int bumpsize, double bumpmult));
604 static int when_overwrite P((char *label));
605
606 static void askfor(ep, seq, lev, info)
607 est_t *ep;      /* esimate data block */
608 int seq;        /* sequence number of request */
609 int lev;        /* dump level being requested */
610 info_t *info;   /* info block for disk */
611 {
612     if(seq < 0 || seq >= MAX_LEVELS) {
613         error("error [planner askfor: seq out of range 0..%d: %d]",
614               MAX_LEVELS, seq);
615     }
616     if(lev < -1 || lev >= DUMP_LEVELS) {
617         error("error [planner askfor: lev out of range -1..%d: %d]",
618               DUMP_LEVELS, lev);
619     }
620
621     if (lev == -1) {
622         ep->level[seq] = -1;
623         ep->dumpdate[seq] = (char *)0;
624         ep->est_size[seq] = -2;
625         return;
626     }
627
628     ep->level[seq] = lev;
629
630     ep->dumpdate[seq] = stralloc(get_dumpdate(info,lev));
631     malloc_mark(ep->dumpdate[seq]);
632
633     ep->est_size[seq] = -2;
634
635     return;
636 }
637
638 static void
639 setup_estimate(dp)
640      disk_t *dp;
641 {
642     est_t *ep;
643     info_t info;
644     int i;
645
646     assert(dp && dp->host);
647     fprintf(stderr, "%s: time %s: setting up estimates for %s:%s\n",
648                     get_pname(), walltime_str(curclock()),
649                     dp->host->hostname, dp->name);
650
651     /* get current information about disk */
652
653     if(get_info(dp->host->hostname, dp->name, &info)) {
654         /* no record for this disk, make a note of it */
655         log_add(L_INFO, "Adding new disk %s:%s.", dp->host->hostname, dp->name);
656     }
657
658     /* setup working data struct for disk */
659
660     ep = alloc(sizeof(est_t));
661     malloc_mark(ep);
662     dp->up = (void *) ep;
663     ep->state = DISK_READY;
664     ep->dump_size = -1;
665     ep->dump_priority = dp->priority;
666     ep->errstr = 0;
667     ep->promote = 0;
668
669     /* calculated fields */
670
671     if(info.command & FORCE_FULL) {
672         /* force a level 0, kind of like a new disk */
673         if(dp->strategy == DS_NOFULL) {
674             /*
675              * XXX - Not sure what it means to force a no-full disk.  The
676              * purpose of no-full is to just dump changes relative to a
677              * stable base, for example root partitions that vary only
678              * slightly from a site-wide prototype.  Only the variations
679              * are dumped.
680              *
681              * If we allow a level 0 onto the Amanda cycle, then we are
682              * hosed when that tape gets re-used next.  Disallow this for
683              * now.
684              */
685             log_add(L_ERROR,
686                     "Cannot force full dump of %s:%s with no-full option.",
687                     dp->host->hostname, dp->name);
688
689             /* clear force command */
690             if(info.command & FORCE_FULL)
691                 info.command ^= FORCE_FULL;
692             if(put_info(dp->host->hostname, dp->name, &info))
693                 error("could not put info record for %s:%s: %s",
694                       dp->host->hostname, dp->name, strerror(errno));
695             ep->last_level = last_level(&info);
696             ep->next_level0 = next_level0(dp, &info);
697         }
698         else {
699             ep->last_level = -1;
700             ep->next_level0 = -conf_dumpcycle;
701             log_add(L_INFO, "Forcing full dump of %s:%s as directed.",
702                     dp->host->hostname, dp->name);
703         }
704     }
705     else if(dp->strategy == DS_NOFULL) {
706         /* force estimate of level 1 */
707         ep->last_level = 1;
708         ep->next_level0 = next_level0(dp, &info);
709     }
710     else {
711         ep->last_level = last_level(&info);
712         ep->next_level0 = next_level0(dp, &info);
713     }
714
715     /* adjust priority levels */
716
717     if(ep->next_level0 < 0) {
718         fprintf(stderr,"%s:%s overdue %d day%s for level 0\n",
719                 dp->host->hostname, dp->name,
720                 - ep->next_level0, ((- ep->next_level0) == 1) ? "" : "s");
721         ep->dump_priority -= ep->next_level0;
722         /* warn if dump will be overwritten */
723         if(ep->last_level > -1) {
724             int overwrite_runs = when_overwrite(info.inf[0].label);
725             if(overwrite_runs == 0) {
726                 log_add(L_WARNING,
727                   "Last full dump of %s:%s on tape %s overwritten on this run.",
728                         dp->host->hostname, dp->name, info.inf[0].label);
729             }
730             else if(overwrite_runs < RUNS_REDZONE) {
731                 log_add(L_WARNING,
732                   "Last full dump of %s:%s on tape %s overwritten in %d run%s.",
733                         dp->host->hostname, dp->name, info.inf[0].label,
734                     overwrite_runs, overwrite_runs == 1? "" : "s");
735             }
736         }
737     }
738     else if(info.command & FORCE_FULL)
739         ep->dump_priority += 1;
740     /* else XXX bump up the priority of incrementals that failed last night */
741
742     /* handle external level 0 dumps */
743
744     if(dp->skip_full && dp->strategy != DS_NOINC) {
745         if(ep->next_level0 <= 0) {
746             /* update the date field */
747             info.inf[0].date = today;
748             if(info.command & FORCE_FULL)
749                 info.command ^= FORCE_FULL;
750             ep->next_level0 += conf_dumpcycle;
751             ep->last_level = 0;
752             if(put_info(dp->host->hostname, dp->name, &info))
753                 error("could not put info record for %s:%s: %s",
754                       dp->host->hostname, dp->name, strerror(errno));
755             log_add(L_INFO, "Skipping full dump of %s:%s today.",
756                     dp->host->hostname, dp->name);
757             fprintf(stderr,"%s:%s lev 0 skipped due to skip-full flag\n",
758                     dp->host->hostname, dp->name);
759             /* don't enqueue the disk */
760             askfor(ep, 0, -1, &info);
761             askfor(ep, 1, -1, &info);
762             askfor(ep, 2, -1, &info);
763             fprintf(stderr, "%s: SKIPPED %s %s 0 [skip-full]\n",
764                     get_pname(), dp->host->hostname, dp->name);
765             log_add(L_SUCCESS, "%s %s %s 0 [skipped: skip-full]",
766                     dp->host->hostname, dp->name, datestamp);
767             return;
768         }
769
770         if(ep->last_level == -1) {
771             /* probably a new disk, but skip-full means no full! */
772             ep->last_level = 0;
773         }
774
775         if(ep->next_level0 == 1) {
776             log_add(L_WARNING, "Skipping full dump of %s:%s tomorrow.",
777                     dp->host->hostname, dp->name);
778         }
779     }
780
781     /* handle "skip-incr" type archives */
782
783     if(dp->skip_incr && ep->next_level0 > 0) {
784         fprintf(stderr,"%s:%s lev 1 skipped due to skip-incr flag\n",
785                 dp->host->hostname, dp->name);
786         /* don't enqueue the disk */
787         askfor(ep, 0, -1, &info);
788         askfor(ep, 1, -1, &info);
789         askfor(ep, 2, -1, &info);
790
791         fprintf(stderr, "%s: SKIPPED %s %s 1 [skip-incr]\n",
792                 get_pname(), dp->host->hostname, dp->name);
793
794         log_add(L_SUCCESS, "%s %s %s 1 [skipped: skip-incr]",
795                 dp->host->hostname, dp->name, datestamp);
796         return;
797     }
798
799     if( ep->last_level == -1 && ep->next_level0 > 0 && 
800         dp->strategy != DS_NOFULL && dp->strategy != DS_INCRONLY &&
801         conf_reserve == 100) {
802         log_add(L_WARNING,
803              "%s:%s mismatch: no tapelist record, but curinfo next_level0: %d.",
804                 dp->host->hostname, dp->name, ep->next_level0);
805         ep->next_level0 = 0;
806     }
807
808     if(ep->last_level == 0) ep->level_days = 0;
809     else ep->level_days = runs_at(&info, ep->last_level);
810     ep->last_lev0size = info.inf[0].csize;
811
812     ep->fullrate = perf_average(info.full.rate, 0.0);
813     ep->incrrate = perf_average(info.incr.rate, 0.0);
814
815     ep->fullcomp = perf_average(info.full.comp, dp->comprate[0]);
816     ep->incrcomp = perf_average(info.incr.comp, dp->comprate[1]);
817
818     /* determine which estimates to get */
819
820     i = 0;
821
822     if(dp->strategy == DS_NOINC ||
823        (!dp->skip_full &&
824         (!(info.command & FORCE_BUMP) ||
825          dp->skip_incr ||
826          ep->last_level == -1))){
827
828         if(info.command & FORCE_BUMP && ep->last_level == -1) {
829             log_add(L_INFO,
830                   "Remove force-bump command of %s:%s because it's a new disk.",
831                     dp->host->hostname, dp->name);
832         }
833         switch (dp->strategy) {
834         case DS_STANDARD: 
835         case DS_NOINC:
836             askfor(ep, i++, 0, &info);
837             if(dp->skip_full) {
838                 log_add(L_INFO,
839                   "Ignoring skip_full for %s:%s because the strategy is NOINC.",
840                         dp->host->hostname, dp->name);
841             }
842             if(info.command & FORCE_BUMP) {
843                 log_add(L_INFO,
844                  "Ignoring FORCE_BUMP for %s:%s because the strategy is NOINC.",
845                         dp->host->hostname, dp->name);
846             }
847             
848             break;
849
850         case DS_NOFULL:
851             break;
852
853         case DS_INCRONLY:
854             if (info.command & FORCE_FULL)
855                 askfor(ep, i++, 0, &info);
856             break;
857         }
858     }
859
860     if(!dp->skip_incr && !(dp->strategy == DS_NOINC)) {
861         if(ep->last_level == -1) {              /* a new disk */
862             if(dp->strategy == DS_NOFULL || dp->strategy == DS_INCRONLY) {
863                 askfor(ep, i++, 1, &info);
864             } else {
865                 assert(!dp->skip_full);         /* should be handled above */
866             }
867         } else {                                /* not new, pick normally */
868             int curr_level;
869
870             curr_level = ep->last_level;
871
872             if(info.command & FORCE_NO_BUMP) {
873                 if(curr_level > 0) { /* level 0 already asked for */
874                     askfor(ep, i++, curr_level, &info);
875                 }
876                 log_add(L_INFO,"Preventing bump of %s:%s as directed.",
877                         dp->host->hostname, dp->name);
878             }
879             else if((info.command & FORCE_BUMP)
880                     && curr_level + 1 < DUMP_LEVELS) {
881                 askfor(ep, i++, curr_level+1, &info);
882                 log_add(L_INFO,"Bumping of %s:%s at level %d as directed.",
883                         dp->host->hostname, dp->name, curr_level+1);
884             }
885             else if(curr_level == 0) {
886                 askfor(ep, i++, 1, &info);
887             }
888             else {
889                 askfor(ep, i++, curr_level, &info);
890                 /*
891                  * If last time we dumped less than the threshold, then this
892                  * time we will too, OR the extra size will be charged to both
893                  * cur_level and cur_level + 1, so we will never bump.  Also,
894                  * if we haven't been at this level 2 days, or the dump failed
895                  * last night, we can't bump.
896                  */
897                 if((info.inf[curr_level].size == 0 || /* no data, try it anyway */
898                     (((info.inf[curr_level].size > bump_thresh(curr_level, info.inf[0].size,dp->bumppercent, dp->bumpsize, dp->bumpmult)))
899                      && ep->level_days >= dp->bumpdays))
900                    && curr_level + 1 < DUMP_LEVELS) {
901                     askfor(ep, i++, curr_level+1, &info);
902                 }
903             } 
904         }
905     }
906
907     while(i < MAX_LEVELS)       /* mark end of estimates */
908         askfor(ep, i++, -1, &info);
909
910     /* debug output */
911
912     fprintf(stderr, "setup_estimate: %s:%s: command %d, options: %s    last_level %d next_level0 %d level_days %d    getting estimates %d (%ld) %d (%ld) %d (%ld)\n",
913             dp->host->hostname, dp->name, info.command,
914             dp->strategy == DS_NOFULL ? "no-full" :
915                  dp->strategy == DS_INCRONLY ? "incr-only" :
916                  dp->skip_full ? "skip-full" :
917                  dp->skip_incr ? "skip-incr" : "none",
918             ep->last_level, ep->next_level0, ep->level_days,
919             ep->level[0], ep->est_size[0],
920             ep->level[1], ep->est_size[1],
921             ep->level[2], ep->est_size[2]);
922
923     assert(ep->level[0] != -1);
924     enqueue_disk(&startq, dp);
925 }
926
927 static int when_overwrite(label)
928 char *label;
929 {
930     tape_t *tp;
931
932     if((tp = lookup_tapelabel(label)) == NULL)
933         return 1;       /* "shouldn't happen", but trigger warning message */
934     else if(!reusable_tape(tp))
935         return 1024;
936     else if(lookup_nb_tape() > conf_tapecycle)
937         return (lookup_nb_tape() - tp->position) / conf_runtapes;
938     else
939         return (conf_tapecycle - tp->position) / conf_runtapes;
940 }
941
942 /* Return the estimated size for a particular dump */
943 static long est_size(dp, level)
944 disk_t *dp;
945 int level;
946 {
947     int i;
948
949     for(i = 0; i < MAX_LEVELS; i++) {
950         if(level == est(dp)->level[i])
951             return est(dp)->est_size[i];
952     }
953     return -1;
954 }
955
956 /* Return the estimated on-tape size of a particular dump */
957 static long est_tape_size(dp, level)
958 disk_t *dp;
959 int level;
960 {
961     long size;
962     double ratio;
963
964     size = est_size(dp, level);
965
966     if(size == -1) return size;
967
968     if(dp->compress == COMP_NONE)
969         return size;
970
971     if(level == 0) ratio = est(dp)->fullcomp;
972     else ratio = est(dp)->incrcomp;
973
974     /*
975      * make sure over-inflated compression ratios don't throw off the
976      * estimates, this is mostly for when you have a small dump getting
977      * compressed which takes up alot more disk/tape space relatively due
978      * to the overhead of the compression.  This is specifically for
979      * Digital Unix vdump.  This patch is courtesy of Rudolf Gabler
980      * (RUG@USM.Uni-Muenchen.DE)
981      */
982
983     if(ratio > 1.1) ratio = 1.1;
984
985     size *= ratio;
986
987     /*
988      * Ratio can be very small in some error situations, so make sure
989      * size goes back greater than zero.  It may not be right, but
990      * indicates we did get an estimate.
991      */
992     if(size <= 0) {
993         size = 1;
994     }
995
996     return size;
997 }
998
999
1000 /* what was the level of the last successful dump to tape? */
1001 static int last_level(info)
1002 info_t *info;
1003 {
1004     int min_pos, min_level, i;
1005     time_t lev0_date, last_date;
1006     tape_t *tp;
1007
1008     if(info->last_level != -1)
1009         return info->last_level;
1010
1011     /* to keep compatibility with old infofile */
1012     min_pos = 1000000000;
1013     min_level = -1;
1014     lev0_date = EPOCH;
1015     last_date = EPOCH;
1016     for(i = 0; i < 9; i++) {
1017         if(conf_reserve < 100) {
1018             if(i == 0) lev0_date = info->inf[0].date;
1019             else if(info->inf[i].date < lev0_date) continue;
1020             if(info->inf[i].date > last_date) {
1021                 last_date = info->inf[i].date;
1022                 min_level = i;
1023             }
1024         }
1025         else {
1026             if((tp = lookup_tapelabel(info->inf[i].label)) == NULL) continue;
1027             /* cull any entries from previous cycles */
1028             if(i == 0) lev0_date = info->inf[0].date;
1029             else if(info->inf[i].date < lev0_date) continue;
1030
1031             if(tp->position < min_pos) {
1032                 min_pos = tp->position;
1033                 min_level = i;
1034             }
1035         }
1036     }
1037     info->last_level = i;
1038     return min_level;
1039 }
1040
1041 /* when is next level 0 due? 0 = today, 1 = tomorrow, etc*/
1042 static int
1043 next_level0(dp, info)
1044      disk_t *dp;
1045      info_t *info;
1046 {
1047     if(dp->strategy == DS_NOFULL || dp->strategy == DS_INCRONLY)
1048         return 1;               /* fake it */
1049     else if (dp->strategy == DS_NOINC)
1050         return 0;
1051     else if(info->inf[0].date < (time_t)0)
1052         return -days_diff(EPOCH, today);        /* new disk */
1053     else
1054         return dp->dumpcycle - days_diff(info->inf[0].date, today);
1055 }
1056
1057 /* how many runs at current level? */
1058 static int runs_at(info, lev)
1059 info_t *info;
1060 int lev;
1061 {
1062     tape_t *cur_tape, *old_tape;
1063     int last, nb_runs;
1064
1065     last = last_level(info);
1066     if(lev != last) return 0;
1067     if(lev == 0) return 1;
1068
1069     if(info->consecutive_runs != -1)
1070         return info->consecutive_runs;
1071
1072     /* to keep compatibility with old infofile */
1073     cur_tape = lookup_tapelabel(info->inf[lev].label);
1074     old_tape = lookup_tapelabel(info->inf[lev-1].label);
1075     if(cur_tape == NULL || old_tape == NULL) return 0;
1076
1077     nb_runs = (old_tape->position - cur_tape->position) / conf_runtapes;
1078     info->consecutive_runs = nb_runs;
1079
1080     return nb_runs;
1081 }
1082
1083
1084 static long bump_thresh(level, size_level_0, bumppercent, bumpsize, bumpmult)
1085 int level;
1086 long size_level_0;
1087 int bumppercent;
1088 int bumpsize;
1089 double bumpmult;
1090 {
1091     double bump;
1092
1093     if(bumppercent != 0 && size_level_0 > 1024) {
1094         bump = (size_level_0 * bumppercent)/100.0;
1095     }
1096     else {
1097         bump = bumpsize;
1098     }
1099     while(--level) bump = bump * bumpmult;
1100
1101     return (long)bump;
1102 }
1103
1104
1105 \f
1106 /*
1107  * ========================================================================
1108  * GET REMOTE DUMP SIZE ESTIMATES
1109  *
1110  */
1111
1112 static void getsize P((am_host_t *hostp));
1113 static disk_t *lookup_hostdisk P((am_host_t *hp, char *str));
1114 static void handle_result P((proto_t *p, pkt_t *pkt));
1115
1116
1117 static void get_estimates P((void))
1118 {
1119     am_host_t *hostp;
1120     disk_t *dp;
1121     struct servent *amandad;
1122     int something_started;
1123
1124     if((amandad = getservbyname(AMANDA_SERVICE_NAME, "udp")) == NULL)
1125         amanda_port = AMANDA_SERVICE_DEFAULT;
1126     else
1127         amanda_port = ntohs(amandad->s_port);
1128
1129 #ifdef KRB4_SECURITY
1130     if((amandad = getservbyname(KAMANDA_SERVICE_NAME, "udp")) == NULL)
1131         kamanda_port = KAMANDA_SERVICE_DEFAULT;
1132     else
1133         kamanda_port = ntohs(amandad->s_port);
1134 #endif
1135
1136     something_started = 1;
1137     while(something_started) {
1138         something_started = 0;
1139         for(dp = startq.head; dp != NULL; dp = dp->next) {
1140             hostp = dp->host;
1141             if(hostp->up == HOST_READY) {
1142                 something_started = 1;
1143                 getsize(hostp);
1144                 check_protocol();
1145                 /*
1146                  * dp is no longer on startq, so dp->next is not valid
1147                  * and we have to start all over.
1148                  */
1149                 break;
1150             }
1151         }
1152     }
1153     run_protocol();
1154
1155     while(!empty(waitq)) {
1156         disk_t *dp = dequeue_disk(&waitq);
1157         est(dp)->errstr = "hmm, disk was stranded on waitq";
1158         enqueue_disk(&failq, dp);
1159     }
1160
1161     while(!empty(pestq)) {
1162         disk_t *dp = dequeue_disk(&pestq);
1163
1164         if(est(dp)->level[0] != -1 && est(dp)->est_size[0] < 0) {
1165             if(est(dp)->est_size[0] == -1) {
1166                 log_add(L_WARNING,
1167                         "disk %s:%s, estimate of level %d failed: %d.",
1168                         dp->host->hostname, dp->name,
1169                         est(dp)->level[0], est(dp)->est_size[0]);
1170             }
1171             else {
1172                 log_add(L_WARNING,
1173                         "disk %s:%s, estimate of level %d timed out: %d.",
1174                         dp->host->hostname, dp->name,
1175                         est(dp)->level[0], est(dp)->est_size[0]);
1176             }
1177             est(dp)->level[0] = -1;
1178         }
1179
1180         if(est(dp)->level[1] != -1 && est(dp)->est_size[1] < 0) {
1181             if(est(dp)->est_size[1] == -1) {
1182                 log_add(L_WARNING,
1183                         "disk %s:%s, estimate of level %d failed: %d.",
1184                         dp->host->hostname, dp->name,
1185                         est(dp)->level[1], est(dp)->est_size[1]);
1186             }
1187             else {
1188                 log_add(L_WARNING,
1189                         "disk %s:%s, estimate of level %d timed out: %d.",
1190                         dp->host->hostname, dp->name,
1191                         est(dp)->level[1], est(dp)->est_size[1]);
1192             }
1193             est(dp)->level[1] = -1;
1194         }
1195
1196         if(est(dp)->level[2] != -1 && est(dp)->est_size[2] < 0) {
1197             if(est(dp)->est_size[2] == -1) {
1198                 log_add(L_WARNING,
1199                         "disk %s:%s, estimate of level %d failed: %d.",
1200                         dp->host->hostname, dp->name,
1201                         est(dp)->level[2], est(dp)->est_size[2]);
1202             }
1203             else {
1204                 log_add(L_WARNING,
1205                         "disk %s:%s, estimate of level %d timed out: %d.",
1206                         dp->host->hostname, dp->name,
1207                         est(dp)->level[2], est(dp)->est_size[2]);
1208             }
1209             est(dp)->level[2] = -1;
1210         }
1211
1212         if((est(dp)->level[0] != -1 && est(dp)->est_size[0] > 0) ||
1213            (est(dp)->level[1] != -1 && est(dp)->est_size[1] > 0) ||
1214            (est(dp)->level[2] != -1 && est(dp)->est_size[2] > 0)) {
1215             enqueue_disk(&estq, dp);
1216         }
1217         else {
1218            est(dp)->errstr = vstralloc("disk ", dp->name,
1219                                        ", all estimate timed out", NULL);
1220            enqueue_disk(&failq, dp);
1221         }
1222     }
1223 }
1224
1225 static void getsize(hostp)
1226 am_host_t *hostp;
1227 {
1228     disklist_t *destqp;
1229     disk_t *dp;
1230     char *req = NULL, *errstr = NULL;
1231     int i, estimates, rc, timeout, disk_state, req_len;
1232     char number[NUM_STR_SIZE];
1233     char *calcsize;
1234
1235     assert(hostp->disks != NULL);
1236
1237     if(hostp->up != HOST_READY) {
1238         return;
1239     }
1240
1241     /*
1242      * The first time through here we send a "noop" request.  This will
1243      * return the feature list from the client if it supports that.
1244      * If it does not, handle_result() will set the feature list to an
1245      * empty structure.  In either case, we do the disks on the second
1246      * (and subsequent) pass(es).
1247      */
1248
1249     if(hostp->features != NULL) { /* sendsize service */
1250         int nb_client = 0;
1251         int nb_server = 0;
1252
1253         int has_features = am_has_feature(hostp->features,
1254                                           fe_req_options_features);
1255         int has_hostname = am_has_feature(hostp->features,
1256                                           fe_req_options_hostname);
1257         int has_maxdumps = am_has_feature(hostp->features,
1258                                           fe_req_options_maxdumps);
1259
1260         ap_snprintf(number, sizeof(number), "%d", hostp->maxdumps);
1261         req = vstralloc("SERVICE ", "sendsize", "\n",
1262                         "OPTIONS ",
1263                         has_features ? "features=" : "",
1264                         has_features ? our_feature_string : "",
1265                         has_features ? ";" : "",
1266                         has_maxdumps ? "maxdumps=" : "",
1267                         has_maxdumps ? number : "",
1268                         has_maxdumps ? ";" : "",
1269                         has_hostname ? "hostname=" : "",
1270                         has_hostname ? hostp->hostname : "",
1271                         has_hostname ? ";" : "",
1272                         "\n",
1273                         NULL);
1274         req_len = strlen(req);
1275         req_len += 128;                             /* room for SECURITY ... */
1276         estimates = 0;
1277         for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1278             char *s = NULL;
1279             int s_len = 0;
1280     
1281             if(dp->todo == 0) continue;
1282
1283             if(est(dp)->state != DISK_READY) {
1284                 continue;
1285             }
1286
1287             est(dp)->got_estimate = 0;
1288             if(est(dp)->level[0] == -1) {
1289                 est(dp)->state = DISK_DONE;
1290                 continue;   /* ignore this disk */
1291             }
1292     
1293             if(dp->estimate == ES_CLIENT ||
1294                dp->estimate == ES_CALCSIZE) {
1295                 nb_client++;
1296     
1297                 for(i = 0; i < MAX_LEVELS; i++) {
1298                     char *l;
1299                     char *exclude1 = "";
1300                     char *exclude2 = "";
1301                     char *excludefree = NULL;
1302                     char spindle[NUM_STR_SIZE];
1303                     char level[NUM_STR_SIZE];
1304                     int lev = est(dp)->level[i];
1305     
1306                     if(lev == -1) break;
1307     
1308                     ap_snprintf(level, sizeof(level), "%d", lev);
1309                     ap_snprintf(spindle, sizeof(spindle), "%d", dp->spindle);
1310                     if(am_has_feature(hostp->features,fe_sendsize_req_options)){
1311                         exclude1 = " OPTIONS |";
1312                         exclude2 = optionstr(dp, hostp->features, NULL);
1313                         excludefree = exclude2;
1314                     }
1315                     else {
1316                         if(dp->exclude_file &&
1317                            dp->exclude_file->nb_element == 1) {
1318                             exclude1 = " exclude-file=";
1319                             exclude2 = dp->exclude_file->first->name;
1320                         }
1321                         else if(dp->exclude_list &&
1322                                 dp->exclude_list->nb_element == 1) {
1323                             exclude1 = " exclude-list=";
1324                             exclude2 = dp->exclude_list->first->name;
1325                         }
1326                     }
1327                     if(dp->estimate == ES_CALCSIZE &&
1328                        !am_has_feature(hostp->features, fe_calcsize_estimate)) {
1329                         log_add(L_WARNING,"%s:%s does not support CALCSIZE for estimate, using CLIENT.\n",
1330                                 hostp->hostname, dp->name);
1331                         dp->estimate = ES_CLIENT;
1332                     }
1333                     if(dp->estimate == ES_CLIENT)
1334                         calcsize = "";
1335                     else
1336                         calcsize = "CALCSIZE ";
1337
1338                     if(dp->device) {
1339                         l = vstralloc(calcsize,
1340                                       dp->program,  " ",
1341                                       dp->name, " ",
1342                                       dp->device, " ",
1343                                       level, " ",
1344                                       est(dp)->dumpdate[i], " ", spindle,
1345                                       exclude1,
1346                                       exclude2,
1347                                       "\n",
1348                                       NULL);
1349                     }
1350                     else {
1351                         l = vstralloc(calcsize,
1352                                       dp->program, " ",
1353                                       dp->name, " ",
1354                                       level, " ",
1355                                       est(dp)->dumpdate[i], " ", spindle,
1356                                       exclude1,
1357                                       exclude2,
1358                                       "\n",
1359                                       NULL);
1360                     }
1361                     amfree(excludefree);
1362                     strappend(s, l);
1363                     s_len += strlen(l);
1364                     amfree(l);
1365                 }
1366                 /*
1367                  * Allow 2X for err response.
1368                  */
1369                 if(req_len + s_len > MAX_DGRAM / 2) {
1370                     amfree(s);
1371                     break;
1372                 }
1373                 estimates += i;
1374                 strappend(req, s);
1375                 req_len += s_len;
1376                 amfree(s);
1377                 est(dp)->state = DISK_ACTIVE;
1378                 remove_disk(&startq, dp);
1379             }
1380             else if (dp->estimate == ES_SERVER) {
1381                 info_t info;
1382
1383                 nb_server++;
1384                 get_info(dp->host->hostname, dp->name, &info);
1385                 for(i = 0; i < MAX_LEVELS; i++) {
1386                     int j;
1387                     int lev = est(dp)->level[i];
1388
1389                     if(lev == -1) break;
1390                     if(lev == 0) { /* use latest level 0, should do extrapolation */
1391                         long est_size = 0;
1392                         int nb_est = 0;
1393
1394                         for(j=NB_HISTORY-2;j>=0;j--) {
1395                             if(info.history[j].level == 0) {
1396                                 est_size = info.history[j].size;
1397                                 nb_est++;
1398                             }
1399                         }
1400                         if(nb_est > 0) {
1401                             est(dp)->est_size[i] = est_size;
1402                         }
1403                         else if(info.inf[lev].size > 1000) { /* stats */
1404                             est(dp)->est_size[i] = info.inf[lev].size;
1405                         }
1406                         else {
1407                             est(dp)->est_size[i] = 1000000;
1408                         }
1409                     }
1410                     else if(lev == est(dp)->last_level) {
1411                         /* means of all X day at the same level */
1412                         #define NB_DAY 30
1413                         int nb_day = 0;
1414                         long est_size_day[NB_DAY];
1415                         int nb_est_day[NB_DAY];
1416
1417                         for(j=0;j<NB_DAY;j++) {
1418                             est_size_day[j]=0;
1419                             nb_est_day[j]=0;
1420                         }
1421
1422                         for(j=NB_HISTORY-2;j>=0;j--) {
1423                             if(info.history[j].level <= 0) continue;
1424                             if(info.history[j].level == info.history[j+1].level) {
1425                                 if(nb_day <NB_DAY-1) nb_day++;
1426                                 est_size_day[nb_day] += info.history[j].size;
1427                                 nb_est_day[nb_day]++;
1428                             }
1429                             else {
1430                                 nb_day=0;
1431                             }
1432                         }
1433                         nb_day = info.consecutive_runs + 1;
1434                         if(nb_day > NB_DAY-1) nb_day = NB_DAY-1;
1435
1436                         while(nb_day > 0 && nb_est_day[nb_day] == 0) nb_day--;
1437
1438                         if(nb_est_day[nb_day] > 0) {
1439                             est(dp)->est_size[i] =
1440                                       est_size_day[nb_day] / nb_est_day[nb_day];
1441                         }
1442                         else if(info.inf[lev].size > 1000) { /* stats */
1443                             est(dp)->est_size[i] = info.inf[lev].size;
1444                         }
1445                         else {
1446                             est(dp)->est_size[i] = 10000;
1447                         }
1448                     }
1449                     else if(lev == est(dp)->last_level + 1) {
1450                         /* means of all first day at a new level */
1451                         long est_size = 0;
1452                         int nb_est = 0;
1453
1454                         for(j=NB_HISTORY-2;j>=0;j--) {
1455                             if(info.history[j].level <= 0) continue;
1456                             if(info.history[j].level == info.history[j+1].level + 1 ) {
1457                                 est_size += info.history[j].size;
1458                                 nb_est++;
1459                             }
1460                         }
1461                         if(nb_est > 0) {
1462                             est(dp)->est_size[i] = est_size / nb_est;
1463                         }
1464                         else if(info.inf[lev].size > 1000) { /* stats */
1465                             est(dp)->est_size[i] = info.inf[lev].size;
1466                         }
1467                         else {
1468                             est(dp)->est_size[i] = 100000;
1469                         }
1470                     }
1471                 }
1472                 fprintf(stderr,"%s time %s: got result for host %s disk %s:",
1473                         get_pname(), walltime_str(curclock()),
1474                         dp->host->hostname, dp->name);
1475                 fprintf(stderr," %d -> %ldK, %d -> %ldK, %d -> %ldK\n",
1476                         est(dp)->level[0], est(dp)->est_size[0],
1477                         est(dp)->level[1], est(dp)->est_size[1],
1478                         est(dp)->level[2], est(dp)->est_size[2]);
1479                 est(dp)->state = DISK_DONE;
1480                 remove_disk(&startq, dp);
1481                 enqueue_disk(&estq, dp);
1482             }
1483         }
1484         if(estimates == 0) {
1485             amfree(req);
1486             hostp->up = HOST_DONE;
1487             return;
1488         }
1489
1490         if (conf_etimeout < 0) {
1491             timeout = - conf_etimeout;
1492         } else {
1493             timeout = estimates * conf_etimeout;
1494         }
1495     } else { /* noop service */
1496         req = vstralloc("SERVICE ", "noop", "\n",
1497                         "OPTIONS ",
1498                         "features=", our_feature_string, ";",
1499                         "\n",
1500                         NULL);
1501         /*
1502          * We use ctimeout for the "noop" request because it should be
1503          * very fast and etimeout has other side effects.
1504          */
1505         timeout = getconf_int(CNF_CTIMEOUT);
1506     }
1507
1508 #ifdef KRB4_SECURITY
1509     if(hostp->disks->auth == AUTH_KRB4)
1510         rc = make_krb_request(hostp->hostname, kamanda_port, req,
1511                               hostp, timeout, handle_result);
1512     else
1513 #endif
1514         rc = make_request(hostp->hostname, amanda_port, req,
1515                           hostp, timeout, handle_result);
1516
1517     req = NULL;                                 /* do not own this any more */
1518
1519     if(rc) {
1520         errstr = vstralloc("could not resolve hostname \"",
1521                            hostp->hostname,
1522                            "\"",
1523                            NULL);
1524         destqp = &failq;
1525         hostp->up = HOST_DONE;
1526         disk_state = DISK_DONE;
1527     }
1528     else {
1529         errstr = NULL;
1530         destqp = &waitq;
1531         hostp->up = HOST_ACTIVE;
1532         disk_state = DISK_ACTIVE;
1533     }
1534
1535     for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1536         if(dp->todo && est(dp)->state == DISK_ACTIVE) {
1537             est(dp)->state = disk_state;
1538             est(dp)->errstr = errstr;
1539             errstr = NULL;
1540             enqueue_disk(destqp, dp);
1541         }
1542     }
1543     amfree(errstr);
1544 }
1545
1546 static disk_t *lookup_hostdisk(hp, str)
1547 am_host_t *hp;
1548 char *str;
1549 {
1550     disk_t *dp;
1551
1552     for(dp = hp->disks; dp != NULL; dp = dp->hostnext)
1553         if(strcmp(str, dp->name) == 0) return dp;
1554
1555     return NULL;
1556 }
1557
1558
1559 static void handle_result(p, pkt)
1560 proto_t *p;
1561 pkt_t *pkt;
1562 {
1563     int level, i;
1564     long size;
1565     disk_t *dp;
1566     am_host_t *hostp;
1567     char *msgdisk=NULL, *msgdisk_undo=NULL, msgdisk_undo_ch = '\0';
1568     char *errbuf = NULL;
1569     char *line;
1570     char *fp;
1571     char *s;
1572     char *t;
1573     int ch;
1574     int tch;
1575
1576     hostp = (am_host_t *) p->datap;
1577     hostp->up = HOST_READY;
1578
1579     if(p->state == S_FAILED && pkt == NULL) {
1580         if(p->prevstate == S_REPWAIT) {
1581             errbuf = vstralloc("Estimate timeout from ", hostp->hostname,
1582                                NULL);
1583         }
1584         else {
1585             errbuf = vstralloc("Request to ", hostp->hostname, " timed out.",
1586                                NULL);
1587         }
1588         goto error_return;
1589     }
1590
1591 #if 0
1592     fprintf(stderr, "got %sresponse from %s:\n----\n%s----\n\n",
1593             (p->state == S_FAILED) ? "NAK " : "", hostp->hostname, pkt->body);
1594 #endif
1595
1596 #ifdef KRB4_SECURITY
1597     if(hostp->disks->auth == AUTH_KRB4 &&
1598        !check_mutual_authenticator(host2key(hostp->hostname), pkt, p)) {
1599         errbuf = vstralloc(hostp->hostname,
1600                            "[mutual-authentication failed]",
1601                            NULL);
1602         goto error_return;
1603     }
1604 #endif
1605
1606     msgdisk_undo = NULL;
1607     s = pkt->body;
1608     ch = *s++;
1609     while(ch) {
1610         line = s - 1;
1611         skip_line(s, ch);
1612         if (s[-2] == '\n') {
1613             s[-2] = '\0';
1614         }
1615
1616 #define sc "OPTIONS "
1617         if(strncmp(line, sc, sizeof(sc)-1) == 0) {
1618 #undef sc
1619
1620 #define sc "features="
1621             t = strstr(line, sc);
1622             if(t != NULL && (isspace((int)t[-1]) || t[-1] == ';')) {
1623                 t += sizeof(sc)-1;
1624 #undef sc
1625                 am_release_feature_set(hostp->features);
1626                 if((hostp->features = am_string_to_feature(t)) == NULL) {
1627                     errbuf = vstralloc(hostp->hostname,
1628                                        ": bad features value: ",
1629                                        line,
1630                                        "\n",
1631                                        NULL);
1632                     goto error_return;
1633                 }
1634             }
1635
1636             continue;
1637         }
1638
1639 #define sc "ERROR "
1640         if(strncmp(line, sc, sizeof(sc)-1) == 0) {
1641             t = line + sizeof(sc)-1;
1642             tch = t[-1];
1643 #undef sc
1644
1645             fp = t - 1;
1646             skip_whitespace(t, tch);
1647             if (tch == '\n') {
1648                 t[-1] = '\0';
1649             }
1650             /*
1651              * If the "error" is that the "noop" service is unknown, it
1652              * just means the client is "old" (does not support the servie).
1653              * We can ignore this.
1654              */
1655             if(hostp->features == NULL
1656                && p->state == S_FAILED
1657                && (strcmp(t - 1, "unknown service: noop") == 0
1658                    || strcmp(t - 1, "noop: invalid service") == 0)) {
1659                 continue;
1660             } else {
1661                 errbuf = vstralloc(hostp->hostname,
1662                                    (p->state == S_FAILED) ? "NAK " : "",
1663                                    ": ",
1664                                    fp,
1665                                    NULL);
1666                 goto error_return;
1667             }
1668         }
1669
1670         msgdisk = t = line;
1671         tch = *t++;
1672         skip_non_whitespace(t, tch);
1673         msgdisk_undo = t - 1;
1674         msgdisk_undo_ch = *msgdisk_undo;
1675         *msgdisk_undo = '\0';
1676
1677         skip_whitespace(t, tch);
1678         if (sscanf(t - 1, "%d SIZE %ld", &level, &size) != 2) {
1679             goto bad_msg;
1680         }
1681
1682         dp = lookup_hostdisk(hostp, msgdisk);
1683
1684         *msgdisk_undo = msgdisk_undo_ch;        /* for error message */
1685         msgdisk_undo = NULL;
1686
1687         if(dp == NULL) {
1688             log_add(L_ERROR, "%s: invalid reply from sendsize: `%s'\n",
1689                     hostp->hostname, line);
1690         } else {
1691             for(i = 0; i < MAX_LEVELS; i++) {
1692                 if(est(dp)->level[i] == level) {
1693                     est(dp)->est_size[i] = size;
1694                     break;
1695                 }
1696             }
1697             if(i == MAX_LEVELS) {
1698                 goto bad_msg;                   /* this est wasn't requested */
1699             }
1700             est(dp)->got_estimate++;
1701         }
1702     }
1703
1704     if(hostp->up == HOST_READY && hostp->features == NULL) {
1705         /*
1706          * The client does not support the features list, so give it an
1707          * empty one.
1708          */
1709         dbprintf(("%s: no feature set from host %s\n",
1710                   debug_prefix_time(NULL), hostp->hostname));
1711         hostp->features = am_set_default_feature_set();
1712     }
1713
1714     /* XXX what about disks that only got some estimates...  do we care? */
1715     /* XXX amanda 2.1 treated that case as a bad msg */
1716
1717     for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1718         if(dp->todo == 0) continue;
1719         if(est(dp)->state != DISK_ACTIVE &&
1720            est(dp)->state != DISK_PARTIALY_DONE) continue;
1721
1722         if(est(dp)->state == DISK_ACTIVE) {
1723             remove_disk(&waitq, dp);
1724         }
1725         else if(est(dp)->state == DISK_PARTIALY_DONE) {
1726             remove_disk(&pestq, dp);
1727         }
1728
1729         if(pkt->type == P_REP) {
1730             est(dp)->state = DISK_DONE;
1731         }
1732         else if(pkt->type == P_PREP) {
1733             est(dp)->state = DISK_PARTIALY_DONE;
1734         }
1735
1736         if(est(dp)->level[0] == -1) continue;   /* ignore this disk */
1737
1738
1739         if(pkt->type == P_PREP) {
1740                 fprintf(stderr,"%s: time %s: got partial result for host %s disk %s:",
1741                         get_pname(), walltime_str(curclock()),
1742                         dp->host->hostname, dp->name);
1743                 fprintf(stderr," %d -> %ldK, %d -> %ldK, %d -> %ldK\n",
1744                         est(dp)->level[0], est(dp)->est_size[0],
1745                         est(dp)->level[1], est(dp)->est_size[1],
1746                         est(dp)->level[2], est(dp)->est_size[2]);
1747             enqueue_disk(&pestq, dp);
1748         }
1749         else if(pkt->type == P_REP) {
1750                 fprintf(stderr,"%s: time %s: got result for host %s disk %s:",
1751                         get_pname(), walltime_str(curclock()),
1752                         dp->host->hostname, dp->name);
1753                 fprintf(stderr," %d -> %ldK, %d -> %ldK, %d -> %ldK\n",
1754                         est(dp)->level[0], est(dp)->est_size[0],
1755                         est(dp)->level[1], est(dp)->est_size[1],
1756                         est(dp)->level[2], est(dp)->est_size[2]);
1757                 if((est(dp)->level[0] != -1 && est(dp)->est_size[0] > 0) ||
1758                    (est(dp)->level[1] != -1 && est(dp)->est_size[1] > 0) ||
1759                    (est(dp)->level[2] != -1 && est(dp)->est_size[2] > 0)) {
1760
1761                     if(est(dp)->level[2] != -1 && est(dp)->est_size[2] < 0) {
1762                         log_add(L_WARNING,
1763                                 "disk %s:%s, estimate of level %d failed: %d.",
1764                                 dp->host->hostname, dp->name,
1765                                 est(dp)->level[2], est(dp)->est_size[2]);
1766                         est(dp)->level[2] = -1;
1767                     }
1768                     if(est(dp)->level[1] != -1 && est(dp)->est_size[1] < 0) {
1769                         log_add(L_WARNING,
1770                                 "disk %s:%s, estimate of level %d failed: %d.",
1771                                 dp->host->hostname, dp->name,
1772                                 est(dp)->level[1], est(dp)->est_size[1]);
1773                         est(dp)->level[1] = -1;
1774                     }
1775                     if(est(dp)->level[0] != -1 && est(dp)->est_size[0] < 0) {
1776                         log_add(L_WARNING,
1777                                 "disk %s:%s, estimate of level %d failed: %d.",
1778                                 dp->host->hostname, dp->name,
1779                                 est(dp)->level[0], est(dp)->est_size[0]);
1780                         est(dp)->level[0] = -1;
1781                     }
1782                     enqueue_disk(&estq, dp);
1783             }
1784             else {
1785                 enqueue_disk(&failq, dp);
1786                 if(est(dp)->got_estimate) {
1787                     est(dp)->errstr = vstralloc("disk ", dp->name,
1788                                                 ", all estimate failed", NULL);
1789                 }
1790                 else {
1791                     fprintf(stderr, "error result for host %s disk %s: missing estimate\n",
1792                             dp->host->hostname, dp->name);
1793                     est(dp)->errstr = vstralloc("missing result for ", dp->name,
1794                                                 " in ", dp->host->hostname,
1795                                                 " response",
1796                                                 NULL);
1797                 }
1798             }
1799         }
1800     }
1801     getsize(hostp);
1802     return;
1803
1804  bad_msg:
1805
1806     if(msgdisk_undo) {
1807         *msgdisk_undo = msgdisk_undo_ch;
1808         msgdisk_undo = NULL;
1809     }
1810     fprintf(stderr,"got a bad message, stopped at:\n");
1811     fprintf(stderr,"----\n%s\n----\n\n", line);
1812     errbuf = stralloc2("badly formatted response from ", hostp->hostname);
1813     /* fall through to ... */
1814
1815  error_return:
1816
1817     if(msgdisk_undo) {
1818         *msgdisk_undo = msgdisk_undo_ch;
1819         msgdisk_undo = NULL;
1820     }
1821     i = 0;
1822     for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1823         if(est(dp)->state == DISK_ACTIVE) {
1824             est(dp)->state = DISK_DONE;
1825             remove_disk(&waitq, dp);
1826             enqueue_disk(&failq, dp);
1827             i++;
1828
1829             est(dp)->errstr = stralloc(errbuf);
1830             fprintf(stderr, "%s: time %s: error result for host %s disk %s: %s\n",
1831                 get_pname(), walltime_str(curclock()),
1832                 dp->host->hostname, dp->name, errbuf);
1833         }
1834     }
1835     if(i == 0) {
1836         /*
1837          * If there were no disks involved, make sure the error gets
1838          * reported.
1839          */
1840         log_add(L_ERROR, "%s", errbuf);
1841     }
1842     hostp->up = HOST_DONE;
1843     amfree(errbuf);
1844 }
1845
1846
1847
1848 \f
1849 /*
1850  * ========================================================================
1851  * ANALYSE ESTIMATES
1852  *
1853  */
1854
1855 static int schedule_order P((disk_t *a, disk_t *b));      /* subroutines */
1856 static int pick_inclevel P((disk_t *dp));
1857
1858 static void analyze_estimate(dp)
1859 disk_t *dp;
1860 {
1861     est_t *ep;
1862     info_t info;
1863     int have_info = 0;
1864
1865     ep = est(dp);
1866
1867     fprintf(stderr, "pondering %s:%s... ",
1868             dp->host->hostname, dp->name);
1869     fprintf(stderr, "next_level0 %d last_level %d ",
1870             ep->next_level0, ep->last_level);
1871
1872     if(get_info(dp->host->hostname, dp->name, &info) == 0) {
1873         have_info = 1;
1874     }
1875
1876     ep->degr_level = -1;
1877     ep->degr_size = -1;
1878
1879     if(ep->next_level0 <= 0
1880        || (have_info && ep->last_level == 0 && (info.command & FORCE_NO_BUMP))) {
1881         if(ep->next_level0 <= 0) {
1882             fprintf(stderr,"(due for level 0) ");
1883         }
1884         ep->dump_level = 0;
1885         ep->dump_size = est_tape_size(dp, 0);
1886         if(ep->dump_size <= 0) {
1887             fprintf(stderr,
1888                     "(no estimate for level 0, picking an incr level)\n");
1889             ep->dump_level = pick_inclevel(dp);
1890             ep->dump_size = est_tape_size(dp, ep->dump_level);
1891
1892             if(ep->dump_size == -1) {
1893                 ep->dump_level = ep->dump_level + 1;
1894                 ep->dump_size = est_tape_size(dp, ep->dump_level);
1895             }
1896         }
1897         else {
1898             total_lev0 += (double) ep->dump_size;
1899             if(ep->last_level == -1 || dp->skip_incr) {
1900                 fprintf(stderr,"(%s disk, can't switch to degraded mode)\n",
1901                         dp->skip_incr? "skip-incr":"new");
1902                 ep->degr_level = -1;
1903                 ep->degr_size = -1;
1904             }
1905             else {
1906                 /* fill in degraded mode info */
1907                 fprintf(stderr,"(picking inclevel for degraded mode)");
1908                 ep->degr_level = pick_inclevel(dp);
1909                 ep->degr_size = est_tape_size(dp, ep->degr_level);
1910                 if(ep->degr_size == -1) {
1911                     ep->degr_level = ep->degr_level + 1;
1912                     ep->degr_size = est_tape_size(dp, ep->degr_level);
1913                 }
1914                 if(ep->degr_size == -1) {
1915                     fprintf(stderr,"(no inc estimate)");
1916                     ep->degr_level = -1;
1917                 }
1918                 fprintf(stderr,"\n");
1919             }
1920         }
1921     }
1922     else {
1923         fprintf(stderr,"(not due for a full dump, picking an incr level)\n");
1924         /* XXX - if this returns -1 may be we should force a total? */
1925         ep->dump_level = pick_inclevel(dp);
1926         ep->dump_size = est_tape_size(dp, ep->dump_level);
1927
1928         if(ep->dump_size == -1) {
1929             ep->dump_level = ep->last_level;
1930             ep->dump_size = est_tape_size(dp, ep->dump_level);
1931         }
1932         if(ep->dump_size == -1) {
1933             ep->dump_level = ep->last_level + 1;
1934             ep->dump_size = est_tape_size(dp, ep->dump_level);
1935         }
1936         if(ep->dump_size == -1) {
1937             ep->dump_level = 0;
1938             ep->dump_size = est_tape_size(dp, ep->dump_level);
1939         }
1940     }
1941
1942     fprintf(stderr,"  curr level %d size %ld ", ep->dump_level, ep->dump_size);
1943
1944     insert_disk(&schedq, dp, schedule_order);
1945
1946     total_size += tt_blocksize_kb + ep->dump_size + tape_mark;
1947
1948     /* update the balanced size */
1949     if(!(dp->skip_full || dp->strategy == DS_NOFULL || 
1950          dp->strategy == DS_INCRONLY)) {
1951         long lev0size;
1952
1953         lev0size = est_tape_size(dp, 0);
1954         if(lev0size == -1) lev0size = ep->last_lev0size;
1955
1956         balanced_size += lev0size / runs_per_cycle;
1957     }
1958
1959     fprintf(stderr,"total size %ld total_lev0 %1.0f balanced-lev0size %1.0f\n",
1960             total_size, total_lev0, balanced_size);
1961 }
1962
1963 static void handle_failed(dp)
1964 disk_t *dp;
1965 {
1966     char *errstr;
1967
1968 /*
1969  * From George Scott <George.Scott@cc.monash.edu.au>:
1970  * --------
1971  * If a machine is down when the planner is run it guesses from historical
1972  * data what the size of tonights dump is likely to be and schedules a
1973  * dump anyway.  The dumper then usually discovers that that machine is
1974  * still down and ends up with a half full tape.  Unfortunately the
1975  * planner had to delay another dump because it thought that the tape was
1976  * full.  The fix here is for the planner to ignore unavailable machines
1977  * rather than ignore the fact that they are unavailable.
1978  * --------
1979  */
1980
1981 #ifdef old_behavior
1982     if(est(dp)->last_level != -1) {
1983         log_add(L_WARNING,
1984                 "Could not get estimate for %s:%s, using historical data.",
1985                 dp->host->hostname, dp->name);
1986         analyze_estimate(dp);
1987         return;
1988     }
1989 #endif
1990
1991     errstr = est(dp)->errstr? est(dp)->errstr : "hmm, no error indicator!";
1992
1993     fprintf(stderr, "%s: FAILED %s %s %s 0 [%s]\n",
1994         get_pname(), dp->host->hostname, dp->name, datestamp, errstr);
1995
1996     log_add(L_FAIL, "%s %s %s 0 [%s]", dp->host->hostname, dp->name, datestamp, errstr);
1997
1998     /* XXX - memory leak with *dp */
1999 }
2000
2001
2002 static int schedule_order(a, b)
2003 disk_t *a, *b;
2004 /*
2005  * insert-sort by decreasing priority, then
2006  * by decreasing size within priority levels.
2007  */
2008 {
2009     int diff;
2010     long ldiff;
2011
2012     diff = est(b)->dump_priority - est(a)->dump_priority;
2013     if(diff != 0) return diff;
2014
2015     ldiff = est(b)->dump_size - est(a)->dump_size;
2016     if(ldiff < 0) return -1; /* XXX - there has to be a better way to dothis */
2017     if(ldiff > 0) return 1;
2018     return 0;
2019 }
2020
2021
2022 static int pick_inclevel(dp)
2023 disk_t *dp;
2024 {
2025     int base_level, bump_level;
2026     long base_size, bump_size;
2027     long thresh;
2028
2029     base_level = est(dp)->last_level;
2030
2031     /* if last night was level 0, do level 1 tonight, no ifs or buts */
2032     if(base_level == 0) {
2033         fprintf(stderr,"   picklev: last night 0, so tonight level 1\n");
2034         return 1;
2035     }
2036
2037     /* if no-full option set, always do level 1 */
2038     if(dp->strategy == DS_NOFULL) {
2039         fprintf(stderr,"   picklev: no-full set, so always level 1\n");
2040         return 1;
2041     }
2042
2043     base_size = est_size(dp, base_level);
2044
2045     /* if we didn't get an estimate, we can't do an inc */
2046     if(base_size == -1) {
2047         base_size = est_size(dp, base_level+1);
2048         if(base_size > 0) /* FORCE_BUMP */
2049             return base_level+1;
2050         fprintf(stderr,"   picklev: no estimate for level %d, so no incs\n", base_level);
2051         return base_level;
2052     }
2053
2054     thresh = bump_thresh(base_level, est_size(dp, 0), dp->bumppercent, dp->bumpsize, dp->bumpmult);
2055
2056     fprintf(stderr,
2057             "   pick: size %ld level %d days %d (thresh %ldK, %d days)\n",
2058             base_size, base_level, est(dp)->level_days,
2059             thresh, dp->bumpdays);
2060
2061     if(base_level == 9
2062        || est(dp)->level_days < dp->bumpdays
2063        || base_size <= thresh)
2064             return base_level;
2065
2066     bump_level = base_level + 1;
2067     bump_size = est_size(dp, bump_level);
2068
2069     if(bump_size == -1) return base_level;
2070
2071     fprintf(stderr, "   pick: next size %ld... ", bump_size);
2072
2073     if(base_size - bump_size < thresh) {
2074         fprintf(stderr, "not bumped\n");
2075         return base_level;
2076     }
2077
2078     fprintf(stderr, "BUMPED\n");
2079     log_add(L_INFO, "Incremental of %s:%s bumped to level %d.",
2080             dp->host->hostname, dp->name, bump_level);
2081
2082     return bump_level;
2083 }
2084
2085
2086
2087 \f
2088 /*
2089 ** ========================================================================
2090 ** ADJUST SCHEDULE
2091 **
2092 ** We have two strategies here:
2093 **
2094 ** 1. Delay dumps
2095 **
2096 ** If we are trying to fit too much on the tape something has to go.  We
2097 ** try to delay totals until tomorrow by converting them into incrementals
2098 ** and, if that is not effective enough, dropping incrementals altogether.
2099 ** While we are searching for the guilty dump (the one that is really
2100 ** causing the schedule to be oversize) we have probably trampled on a lot of
2101 ** innocent dumps, so we maintain a "before image" list and use this to
2102 ** put back what we can.
2103 **
2104 ** 2. Promote dumps.
2105 **
2106 ** We try to keep the amount of tape used by total dumps the same each night.
2107 ** If there is some spare tape in this run we have a look to see if any of
2108 ** tonights incrementals could be promoted to totals and leave us with a
2109 ** more balanced cycle.
2110 */
2111
2112 static void delay_one_dump P((disk_t *dp, int delete, ...));
2113
2114 static void delay_dumps P((void))
2115 /* delay any dumps that will not fit */
2116 {
2117     disk_t *dp, *ndp, *preserve;
2118     bi_t *bi, *nbi;
2119     long new_total;     /* New total_size */
2120     char est_kb[20];     /* Text formatted dump size */
2121     int nb_forced_level_0;
2122     info_t info;
2123     int delete;
2124     char *message;
2125
2126     biq.head = biq.tail = NULL;
2127
2128     /*
2129     ** 1. Delay dumps that are way oversize.
2130     **
2131     ** Dumps larger that the size of the tapes we are using are just plain
2132     ** not going to fit no matter how many other dumps we drop.  Delay
2133     ** oversize totals until tomorrow (by which time my owner will have
2134     ** resolved the problem!) and drop incrementals altogether.  Naturally
2135     ** a large total might be delayed into a large incremental so these
2136     ** need to be checked for separately.
2137     */
2138
2139     for(dp = schedq.head; dp != NULL; dp = ndp) {
2140         ndp = dp->next; /* remove_disk zaps this */
2141
2142         if (est(dp)->dump_size <= tape->length) {
2143             continue;
2144         }
2145
2146         /* Format dumpsize for messages */
2147         ap_snprintf(est_kb, 20, "%ld KB,", est(dp)->dump_size);
2148
2149         if(est(dp)->dump_level == 0) {
2150             if(dp->skip_incr) {
2151                 delete = 1;
2152                 message = "but cannot incremental dump skip-incr disk";
2153             }
2154             else if(est(dp)->last_level < 0) {
2155                 delete = 1;
2156                 message = "but cannot incremental dump new disk";
2157             }
2158             else if(est(dp)->degr_level < 0) {
2159                 delete = 1;
2160                 message = "but no incremental estimate";
2161             }
2162             else {
2163                 delete = 0;
2164                 message = "full dump delayed";
2165             }
2166         }
2167         else {
2168             delete = 1;
2169             message = "skipping incremental";
2170         }
2171         delay_one_dump(dp, delete, "dump larger than tape,", est_kb,
2172                        message, NULL);
2173     }
2174
2175     /*
2176     ** 2. Delay total dumps.
2177     **
2178     ** Delay total dumps until tomorrow (or the day after!).  We start with
2179     ** the lowest priority (most dispensable) and work forwards.  We take
2180     ** care not to delay *all* the dumps since this could lead to a stale
2181     ** mate [for any one disk there are only three ways tomorrows dump will
2182     ** be smaller than todays: 1. we do a level 0 today so tomorows dump
2183     ** will be a level 1; 2. the disk gets more data so that it is bumped
2184     ** tomorrow (this can be a slow process); and, 3. the disk looses some
2185     ** data (when does that ever happen?)].
2186     */
2187
2188     nb_forced_level_0 = 0;
2189     preserve = NULL;
2190     for(dp = schedq.head; dp != NULL && preserve == NULL; dp = dp->next)
2191         if(est(dp)->dump_level == 0)
2192             preserve = dp;
2193
2194     /* 2.a. Do not delay forced full */
2195     for(dp = schedq.tail;
2196                 dp != NULL && total_size > tape_length;
2197                 dp = ndp) {
2198         ndp = dp->prev;
2199
2200         if(est(dp)->dump_level != 0) continue;
2201
2202         get_info(dp->host->hostname, dp->name, &info);
2203         if(info.command & FORCE_FULL) {
2204             nb_forced_level_0 += 1;
2205             preserve = dp;
2206             continue;
2207         }
2208
2209         if(dp != preserve) {
2210
2211             /* Format dumpsize for messages */
2212             ap_snprintf(est_kb, 20, "%ld KB,", est(dp)->dump_size);
2213
2214             if(dp->skip_incr) {
2215                 delete = 1;
2216                 message = "but cannot incremental dump skip-incr disk";
2217             }
2218             else if(est(dp)->last_level < 0) {
2219                 delete = 1;
2220                 message = "but cannot incremental dump new disk";
2221             }
2222             else if(est(dp)->degr_level < 0) {
2223                 delete = 1;
2224                 message = "but no incremental estimate";
2225             }
2226             else {
2227                 delete = 0;
2228                 message = "full dump delayed";
2229             }
2230             delay_one_dump(dp, delete, "dumps too big,", est_kb,
2231                            message, NULL);
2232         }
2233     }
2234
2235     /* 2.b. Delay forced full if needed */
2236     if(nb_forced_level_0 > 0 && total_size > tape_length) {
2237         for(dp = schedq.tail;
2238                 dp != NULL && total_size > tape_length;
2239                 dp = ndp) {
2240             ndp = dp->prev;
2241
2242             if(est(dp)->dump_level == 0 && dp != preserve) {
2243
2244                 /* Format dumpsize for messages */
2245                 ap_snprintf(est_kb, 20, "%ld KB,", est(dp)->dump_size);
2246
2247                 if(dp->skip_incr) {
2248                     delete = 1;
2249                     message = "but cannot incremental dump skip-incr disk";
2250                 }
2251                 else if(est(dp)->last_level < 0) {
2252                     delete = 1;
2253                     message = "but cannot incremental dump new disk";
2254                 }
2255                 else if(est(dp)->degr_level < 0) {
2256                     delete = 1;
2257                     message = "but no incremental estimate";
2258                 }
2259                 else {
2260                     delete = 0;
2261                     message = "full dump delayed";
2262                 }
2263                 delay_one_dump(dp, delete, "dumps too big,", est_kb,
2264                                message, NULL);
2265             }
2266         }
2267     }
2268
2269     /*
2270     ** 3. Delay incremental dumps.
2271     **
2272     ** Delay incremental dumps until tomorrow.  This is a last ditch attempt
2273     ** at making things fit.  Again, we start with the lowest priority (most
2274     ** dispensable) and work forwards.
2275     */
2276
2277     for(dp = schedq.tail;
2278             dp != NULL && total_size > tape_length;
2279             dp = ndp) {
2280         ndp = dp->prev;
2281
2282         if(est(dp)->dump_level != 0) {
2283
2284             /* Format dumpsize for messages */
2285             ap_snprintf(est_kb, 20, "%ld KB,", est(dp)->dump_size);
2286
2287             delay_one_dump(dp, 1,
2288                            "dumps way too big,",
2289                            est_kb,
2290                            "must skip incremental dumps",
2291                            NULL);
2292         }
2293     }
2294
2295     /*
2296     ** 4. Reinstate delayed dumps.
2297     **
2298     ** We might not have needed to stomp on all of the dumps we have just
2299     ** delayed above.  Try to reinstate them all starting with the last one
2300     ** and working forwards.  It is unlikely that the last one will fit back
2301     ** in but why complicate the code?
2302     */
2303
2304     for(bi = biq.tail; bi != NULL; bi = nbi) {
2305         nbi = bi->prev;
2306         dp = bi->dp;
2307
2308         if(bi->deleted)
2309             new_total = total_size + tt_blocksize_kb + bi->size + tape_mark;
2310         else
2311             new_total = total_size - est(dp)->dump_size + bi->size;
2312
2313         if(new_total <= tape_length && bi->size < tape->length) {
2314             /* reinstate it */
2315             total_size = new_total;
2316             if(bi->deleted) {
2317                 if(bi->level == 0) {
2318                     total_lev0 += (double) bi->size;
2319                 }
2320                 insert_disk(&schedq, dp, schedule_order);
2321             }
2322             else {
2323                 est(dp)->dump_level = bi->level;
2324                 est(dp)->dump_size = bi->size;
2325             }
2326
2327             /* Keep it clean */
2328             if(bi->next == NULL)
2329                 biq.tail = bi->prev;
2330             else
2331                 (bi->next)->prev = bi->prev;
2332             if(bi->prev == NULL)
2333                 biq.head = bi->next;
2334             else
2335                 (bi->prev)->next = bi->next;
2336             amfree(bi->errstr);
2337             amfree(bi);
2338         }
2339     }
2340
2341     /*
2342     ** 5. Output messages about what we have done.
2343     **
2344     ** We can't output messages while we are delaying dumps because we might
2345     ** reinstate them later.  We remember all the messages and output them
2346     ** now.
2347     */
2348
2349     for(bi = biq.head; bi != NULL; bi = nbi) {
2350         nbi = bi->next;
2351
2352         if(bi->deleted) {
2353             fprintf(stderr, "%s: FAILED %s\n", get_pname(), bi->errstr);
2354             log_add(L_FAIL, "%s", bi->errstr);
2355         }
2356         else {
2357             dp = bi->dp;
2358             fprintf(stderr, "  delay: %s now at level %d\n",
2359                 bi->errstr, est(dp)->dump_level);
2360             log_add(L_INFO, "%s", bi->errstr);
2361         }
2362
2363         /* Clean up - dont be too fancy! */
2364         amfree(bi->errstr);
2365         amfree(bi);
2366     }
2367
2368     fprintf(stderr, "  delay: Total size now %ld.\n", total_size);
2369
2370     return;
2371 }
2372
2373
2374 /*
2375  * Remove a dump or modify it from full to incremental.
2376  * Keep track of it on the bi q in case we can add it back later.
2377  */
2378 arglist_function1(static void delay_one_dump,
2379                   disk_t *, dp,
2380                   int, delete)
2381 {
2382     bi_t *bi;
2383     va_list argp;
2384     char level_str[NUM_STR_SIZE];
2385     char *sep;
2386     char *next;
2387
2388     arglist_start(argp, delete);
2389
2390     total_size -= tt_blocksize_kb + est(dp)->dump_size + tape_mark;
2391     if(est(dp)->dump_level == 0) {
2392         total_lev0 -= (double) est(dp)->dump_size;
2393     }
2394
2395     bi = alloc(sizeof(bi_t));
2396     bi->next = NULL;
2397     bi->prev = biq.tail;
2398     if(biq.tail == NULL)
2399         biq.head = bi;
2400     else
2401         biq.tail->next = bi;
2402     biq.tail = bi;
2403
2404     bi->deleted = delete;
2405     bi->dp = dp;
2406     bi->level = est(dp)->dump_level;
2407     bi->size = est(dp)->dump_size;
2408
2409     ap_snprintf(level_str, sizeof(level_str), "%d", est(dp)->dump_level);
2410     bi->errstr = vstralloc(dp->host->hostname,
2411                            " ", dp->name,
2412                            " ", datestamp ? datestamp : "?",
2413                            " ", level_str,
2414                            NULL);
2415     sep = " [";
2416     while ((next = arglist_val(argp, char *)) != NULL) {
2417         bi->errstr = newvstralloc(bi->errstr, bi->errstr, sep, next, NULL);
2418         sep = " ";
2419     }
2420     strappend(bi->errstr, "]");
2421     arglist_end(argp);
2422
2423     if (delete) {
2424         remove_disk(&schedq, dp);
2425     } else {
2426         est(dp)->dump_level = est(dp)->degr_level;
2427         est(dp)->dump_size = est(dp)->degr_size;
2428         total_size += tt_blocksize_kb + est(dp)->dump_size + tape_mark;
2429     }
2430
2431     return;
2432 }
2433
2434
2435 static int promote_highest_priority_incremental P((void))
2436 {
2437     disk_t *dp, *dp1, *dp_promote;
2438     long new_size, new_total, new_lev0;
2439     int check_days;
2440     int nb_today, nb_same_day, nb_today2;
2441     int nb_disk_today, nb_disk_same_day;
2442
2443     /*
2444      * return 1 if did so; must update total_size correctly; must not
2445      * cause total_size to exceed tape_length
2446      */
2447
2448     dp_promote = NULL;
2449     for(dp = schedq.head; dp != NULL; dp = dp->next) {
2450
2451         est(dp)->promote = -1000;
2452
2453         if(est_size(dp,0) <= 0)
2454             continue;
2455
2456         if(est(dp)->next_level0 <= 0)
2457             continue;
2458
2459         if(est(dp)->next_level0 > dp->maxpromoteday)
2460             continue;
2461
2462         new_size = est_tape_size(dp, 0);
2463         new_total = total_size - est(dp)->dump_size + new_size;
2464         new_lev0 = total_lev0 + new_size;
2465
2466         nb_today = 0;
2467         nb_same_day = 0;
2468         nb_disk_today = 0;
2469         nb_disk_same_day = 0;
2470         for(dp1 = schedq.head; dp1 != NULL; dp1 = dp1->next) {
2471             if(est(dp1)->dump_level == 0)
2472                 nb_disk_today++;
2473             else if(est(dp1)->next_level0 == est(dp)->next_level0)
2474                 nb_disk_same_day++;
2475             if(strcmp(dp->host->hostname, dp1->host->hostname) == 0) {
2476                 if(est(dp1)->dump_level == 0)
2477                     nb_today++;
2478                 else if(est(dp1)->next_level0 == est(dp)->next_level0)
2479                     nb_same_day++;
2480             }
2481         }
2482
2483         /* do not promote if overflow tape */
2484         if(new_total > tape_length) continue;
2485
2486         /* do not promote if overflow balanced size and something today */
2487         /* promote if nothing today */
2488         if(new_lev0 > balanced_size+balance_threshold && nb_disk_today > 0)
2489             continue;
2490
2491         /* do not promote if only one disk due that day and nothing today */
2492         if(nb_disk_same_day == 1 && nb_disk_today == 0) continue;
2493
2494         nb_today2 = nb_today*nb_today;
2495         if(nb_today == 0 && nb_same_day > 1) nb_same_day++;
2496
2497         if(nb_same_day >= nb_today2) {
2498             est(dp)->promote = ((nb_same_day - nb_today2)*(nb_same_day - nb_today2)) + 
2499                                conf_dumpcycle - est(dp)->next_level0;
2500         }
2501         else {
2502             est(dp)->promote = -nb_today2 +
2503                                conf_dumpcycle - est(dp)->next_level0;
2504         }
2505
2506         if(!dp_promote || est(dp_promote)->promote < est(dp)->promote) {
2507             dp_promote = dp;
2508             fprintf(stderr,"   try %s:%s %d %d %d = %d\n",
2509                     dp->host->hostname, dp->name, nb_same_day, nb_today, est(dp)->next_level0, est(dp)->promote);
2510         }
2511         else {
2512             fprintf(stderr,"no try %s:%s %d %d %d = %d\n",
2513                     dp->host->hostname, dp->name, nb_same_day, nb_today, est(dp)->next_level0, est(dp)->promote);
2514         }
2515     }
2516
2517     if(dp_promote) {
2518         dp = dp_promote;
2519
2520         new_size = est_tape_size(dp, 0);
2521         new_total = total_size - est(dp)->dump_size + new_size;
2522         new_lev0 = total_lev0 + new_size;
2523
2524         total_size = new_total;
2525         total_lev0 = new_lev0;
2526         check_days = est(dp)->next_level0;
2527         est(dp)->degr_level = est(dp)->dump_level;
2528         est(dp)->degr_size = est(dp)->dump_size;
2529         est(dp)->dump_level = 0;
2530         est(dp)->dump_size = new_size;
2531         est(dp)->next_level0 = 0;
2532
2533         fprintf(stderr,
2534               "   promote: moving %s:%s up, total_lev0 %1.0f, total_size %ld\n",
2535                 dp->host->hostname, dp->name,
2536                 total_lev0, total_size);
2537
2538         log_add(L_INFO,
2539                 "Full dump of %s:%s promoted from %d day%s ahead.",
2540                 dp->host->hostname, dp->name,
2541                 check_days, (check_days == 1) ? "" : "s");
2542         return 1;
2543     }
2544     return 0;
2545 }
2546
2547
2548 static int promote_hills P((void))
2549 {
2550     disk_t *dp;
2551     struct balance_stats {
2552         int disks;
2553         long size;
2554     } *sp = NULL;
2555     int days;
2556     int hill_days = 0;
2557     long hill_size;
2558     long new_size;
2559     long new_total;
2560     int my_dumpcycle;
2561
2562     /* If we are already doing a level 0 don't bother */
2563     if(total_lev0 > 0)
2564         return 0;
2565
2566     /* Do the guts of an "amadmin balance" */
2567     my_dumpcycle = conf_dumpcycle;
2568     if(my_dumpcycle > 10000) my_dumpcycle = 10000;
2569
2570     sp = (struct balance_stats *)
2571         alloc(sizeof(struct balance_stats) * my_dumpcycle);
2572
2573     for(days = 0; days < my_dumpcycle; days++)
2574         sp[days].disks = sp[days].size = 0;
2575
2576     for(dp = schedq.head; dp != NULL; dp = dp->next) {
2577         days = est(dp)->next_level0;   /* This is > 0 by definition */
2578         if(days<my_dumpcycle && !dp->skip_full && dp->strategy != DS_NOFULL &&
2579            dp->strategy != DS_INCRONLY) {
2580             sp[days].disks++;
2581             sp[days].size += est(dp)->last_lev0size;
2582         }
2583     }
2584
2585     /* Search for a suitable big hill and cut it down */
2586     while(1) {
2587         /* Find the tallest hill */
2588         hill_size = 0;
2589         for(days = 0; days < my_dumpcycle; days++) {
2590             if(sp[days].disks > 1 && sp[days].size > hill_size) {
2591                 hill_size = sp[days].size;
2592                 hill_days = days;
2593             }
2594         }
2595
2596         if(hill_size <= 0) break;       /* no suitable hills */
2597
2598         /* Find all the dumps in that hill and try and remove one */
2599         for(dp = schedq.head; dp != NULL; dp = dp->next) {
2600             if(est(dp)->next_level0 != hill_days ||
2601                est(dp)->next_level0 > dp->maxpromoteday ||
2602                dp->skip_full ||
2603                dp->strategy == DS_NOFULL ||
2604                dp->strategy == DS_INCRONLY)
2605                 continue;
2606             new_size = est_tape_size(dp, 0);
2607             new_total = total_size - est(dp)->dump_size + new_size;
2608             if(new_total > tape_length)
2609                 continue;
2610             /* We found a disk we can promote */
2611             total_size = new_total;
2612             total_lev0 += new_size;
2613             est(dp)->degr_level = est(dp)->dump_level;
2614             est(dp)->degr_size = est(dp)->dump_size;
2615             est(dp)->dump_level = 0;
2616             est(dp)->next_level0 = 0;
2617             est(dp)->dump_size = new_size;
2618
2619             fprintf(stderr,
2620                     "   promote: moving %s:%s up, total_lev0 %1.0f, total_size %ld\n",
2621                     dp->host->hostname, dp->name,
2622                     total_lev0, total_size);
2623
2624             log_add(L_INFO,
2625                     "Full dump of %s:%s specially promoted from %d day%s ahead.",
2626                     dp->host->hostname, dp->name,
2627                     hill_days, (hill_days == 1) ? "" : "s");
2628
2629             amfree(sp);
2630             return 1;
2631         }
2632         /* All the disks in that hill were unsuitable. */
2633         sp[hill_days].disks = 0;        /* Don't get tricked again */
2634     }
2635
2636     amfree(sp);
2637     return 0;
2638 }
2639
2640 /*
2641  * ========================================================================
2642  * OUTPUT SCHEDULE
2643  *
2644  * XXX - memory leak - we shouldn't just throw away *dp
2645  */
2646 static void output_scheduleline(dp)
2647     disk_t *dp;
2648 {
2649     est_t *ep;
2650     long dump_time = 0, degr_time = 0;
2651     char *schedline = NULL, *degr_str = NULL;
2652     char dump_priority_str[NUM_STR_SIZE];
2653     char dump_level_str[NUM_STR_SIZE];
2654     char dump_size_str[NUM_STR_SIZE];
2655     char dump_time_str[NUM_STR_SIZE];
2656     char degr_level_str[NUM_STR_SIZE];
2657     char degr_size_str[NUM_STR_SIZE];
2658     char degr_time_str[NUM_STR_SIZE];
2659     char *dump_date, *degr_date;
2660     char *features;
2661     int i;
2662
2663     ep = est(dp);
2664
2665     if(ep->dump_size == -1) {
2666         /* no estimate, fail the disk */
2667         fprintf(stderr,
2668                 "%s: FAILED %s %s %s %d [no estimate]\n",
2669                 get_pname(),
2670                 dp->host->hostname, dp->name, datestamp, ep->dump_level);
2671         log_add(L_FAIL, "%s %s %s %d [no estimate]",
2672                 dp->host->hostname, dp->name, datestamp, ep->dump_level);
2673         return;
2674     }
2675
2676     dump_date = degr_date = (char *)0;
2677     for(i = 0; i < MAX_LEVELS; i++) {
2678         if(ep->dump_level == ep->level[i])
2679             dump_date = ep->dumpdate[i];
2680         if(ep->degr_level == ep->level[i])
2681             degr_date = ep->dumpdate[i];
2682     }
2683
2684 #define fix_rate(rate) (rate < 1.0 ? DEFAULT_DUMPRATE : rate)
2685
2686     if(ep->dump_level == 0) {
2687         dump_time = ep->dump_size / fix_rate(ep->fullrate);
2688
2689         if(ep->degr_size != -1) {
2690             degr_time = ep->degr_size / fix_rate(ep->incrrate);
2691         }
2692     }
2693     else {
2694         dump_time = ep->dump_size / fix_rate(ep->incrrate);
2695     }
2696
2697     if(ep->dump_level == 0 && ep->degr_size != -1) {
2698         ap_snprintf(degr_level_str, sizeof(degr_level_str),
2699                     "%d", ep->degr_level);
2700         ap_snprintf(degr_size_str, sizeof(degr_size_str),
2701                     "%ld", ep->degr_size);
2702         ap_snprintf(degr_time_str, sizeof(degr_time_str),
2703                     "%ld", degr_time);
2704         degr_str = vstralloc(" ", degr_level_str,
2705                              " ", degr_date,
2706                              " ", degr_size_str,
2707                              " ", degr_time_str,
2708                              NULL);
2709     }
2710     ap_snprintf(dump_priority_str, sizeof(dump_priority_str),
2711                 "%d", ep->dump_priority);
2712     ap_snprintf(dump_level_str, sizeof(dump_level_str),
2713                 "%d", ep->dump_level);
2714     ap_snprintf(dump_size_str, sizeof(dump_size_str),
2715                 "%ld", ep->dump_size);
2716     ap_snprintf(dump_time_str, sizeof(dump_time_str),
2717                 "%ld", dump_time);
2718     features = am_feature_to_string(dp->host->features);
2719     schedline = vstralloc("DUMP ",dp->host->hostname,
2720                           " ", features,
2721                           " ", dp->name,
2722                           " ", datestamp,
2723                           " ", dump_priority_str,
2724                           " ", dump_level_str,
2725                           " ", dump_date,
2726                           " ", dump_size_str,
2727                           " ", dump_time_str,
2728                           degr_str ? degr_str : "",
2729                           "\n", NULL);
2730
2731     fputs(schedline, stdout);
2732     fputs(schedline, stderr);
2733     amfree(features);
2734     amfree(schedline);
2735     amfree(degr_str);
2736 }