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