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