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