d16ab8c63a58f5afb33e827fa0b4c5df5eed3897
[debian/amanda] / server-src / driverio.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998 University of Maryland at College Park
4  * All Rights Reserved.
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation, and that the name of U.M. not be used in advertising or
11  * publicity pertaining to distribution of the software without specific,
12  * written prior permission.  U.M. makes no representations about the
13  * suitability of this software for any purpose.  It is provided "as is"
14  * without express or implied warranty.
15  *
16  * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18  * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  *
23  * Author: James da Silva, Systems Design and Analysis Group
24  *                         Computer Science Department
25  *                         University of Maryland at College Park
26  */
27 /*
28  * $Id: driverio.c,v 1.92 2006/08/24 01:57:16 paddy_s Exp $
29  *
30  * I/O-related functions for driver program
31  */
32 #include "amanda.h"
33 #include "util.h"
34 #include "clock.h"
35 #include "server_util.h"
36 #include "conffile.h"
37 #include "diskfile.h"
38 #include "infofile.h"
39 #include "logfile.h"
40
41 #define GLOBAL          /* the global variables defined here */
42 #include "driverio.h"
43
44 int nb_chunker = 0;
45
46 static const char *childstr(int);
47
48 void
49 init_driverio(void)
50 {
51     dumper_t *dumper;
52
53     taper = -1;
54
55     for(dumper = dmptable; dumper < dmptable + MAX_DUMPERS; dumper++) {
56         dumper->fd = -1;
57     }
58 }
59
60
61 static const char *
62 childstr(
63     int fd)
64 {
65     static char buf[NUM_STR_SIZE + 32];
66     dumper_t *dumper;
67
68     if (fd == taper)
69         return ("taper");
70
71     for (dumper = dmptable; dumper < dmptable + MAX_DUMPERS; dumper++) {
72         if (dumper->fd == fd)
73             return (dumper->name);
74         if (dumper->chunker && dumper->chunker->fd == fd)
75             return (dumper->chunker->name);
76     }
77     g_snprintf(buf, SIZEOF(buf), _("unknown child (fd %d)"), fd);
78     return (buf);
79 }
80
81
82 void
83 startup_tape_process(
84     char *taper_program)
85 {
86     int    fd[2];
87     char **config_options;
88
89     if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1) {
90         error(_("taper pipe: %s"), strerror(errno));
91         /*NOTREACHED*/
92     }
93     if(fd[0] < 0 || fd[0] >= (int)FD_SETSIZE) {
94         error(_("taper socketpair 0: descriptor %d out of range (0 .. %d)\n"),
95               fd[0], (int)FD_SETSIZE-1);
96         /*NOTREACHED*/
97     }
98     if(fd[1] < 0 || fd[1] >= (int)FD_SETSIZE) {
99         error(_("taper socketpair 1: descriptor %d out of range (0 .. %d)\n"),
100               fd[1], (int)FD_SETSIZE-1);
101         /*NOTREACHED*/
102     }
103
104     switch(taper_pid = fork()) {
105     case -1:
106         error(_("fork taper: %s"), strerror(errno));
107         /*NOTREACHED*/
108
109     case 0:     /* child process */
110         aclose(fd[0]);
111         if(dup2(fd[1], 0) == -1 || dup2(fd[1], 1) == -1)
112             error(_("taper dup2: %s"), strerror(errno));
113         config_options = get_config_options(2);
114         config_options[0] = "taper";
115         config_options[1] = get_config_name();
116         safe_fd(-1, 0);
117         execve(taper_program, config_options, safe_env());
118         error("exec %s: %s", taper_program, strerror(errno));
119         /*NOTREACHED*/
120
121     default:    /* parent process */
122         aclose(fd[1]);
123         taper = fd[0];
124         taper_ev_read = NULL;
125     }
126 }
127
128 void
129 startup_dump_process(
130     dumper_t *dumper,
131     char *dumper_program)
132 {
133     int    fd[2];
134     char **config_options;
135
136     if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1) {
137         error(_("%s pipe: %s"), dumper->name, strerror(errno));
138         /*NOTREACHED*/
139     }
140
141     switch(dumper->pid = fork()) {
142     case -1:
143         error(_("fork %s: %s"), dumper->name, strerror(errno));
144         /*NOTREACHED*/
145
146     case 0:             /* child process */
147         aclose(fd[0]);
148         if(dup2(fd[1], 0) == -1 || dup2(fd[1], 1) == -1)
149             error(_("%s dup2: %s"), dumper->name, strerror(errno));
150         config_options = get_config_options(2);
151         config_options[0] = dumper->name ? dumper->name : "dumper",
152         config_options[1] = get_config_name();
153         safe_fd(-1, 0);
154         execve(dumper_program, config_options, safe_env());
155         error(_("exec %s (%s): %s"), dumper_program,
156               dumper->name, strerror(errno));
157         /*NOTREACHED*/
158
159     default:    /* parent process */
160         aclose(fd[1]);
161         dumper->fd = fd[0];
162         dumper->ev_read = NULL;
163         dumper->busy = dumper->down = 0;
164         dumper->dp = NULL;
165         g_fprintf(stderr,_("driver: started %s pid %u\n"),
166                 dumper->name, (unsigned)dumper->pid);
167         fflush(stderr);
168     }
169 }
170
171 void
172 startup_dump_processes(
173     char *dumper_program,
174     int inparallel,
175     char *timestamp)
176 {
177     int i;
178     dumper_t *dumper;
179     char number[NUM_STR_SIZE];
180
181     for(dumper = dmptable, i = 0; i < inparallel; dumper++, i++) {
182         g_snprintf(number, SIZEOF(number), "%d", i);
183         dumper->name = stralloc2("dumper", number);
184         dumper->chunker = &chktable[i];
185         chktable[i].name = stralloc2("chunker", number);
186         chktable[i].dumper = dumper;
187         chktable[i].fd = -1;
188
189         startup_dump_process(dumper, dumper_program);
190         dumper_cmd(dumper, START, NULL, (void *)timestamp);
191     }
192 }
193
194 void
195 startup_chunk_process(
196     chunker_t *chunker,
197     char *chunker_program)
198 {
199     int    fd[2];
200     char **config_options;
201
202     if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1) {
203         error(_("%s pipe: %s"), chunker->name, strerror(errno));
204         /*NOTREACHED*/
205     }
206
207     switch(chunker->pid = fork()) {
208     case -1:
209         error(_("fork %s: %s"), chunker->name, strerror(errno));
210         /*NOTREACHED*/
211
212     case 0:             /* child process */
213         aclose(fd[0]);
214         if(dup2(fd[1], 0) == -1 || dup2(fd[1], 1) == -1) {
215             error(_("%s dup2: %s"), chunker->name, strerror(errno));
216             /*NOTREACHED*/
217         }
218         config_options = get_config_options(2);
219         config_options[0] = chunker->name ? chunker->name : "chunker",
220         config_options[1] = get_config_name();
221         safe_fd(-1, 0);
222         execve(chunker_program, config_options, safe_env());
223         error(_("exec %s (%s): %s"), chunker_program,
224               chunker->name, strerror(errno));
225         /*NOTREACHED*/
226
227     default:    /* parent process */
228         aclose(fd[1]);
229         chunker->down = 0;
230         chunker->fd = fd[0];
231         chunker->ev_read = NULL;
232         g_fprintf(stderr,_("driver: started %s pid %u\n"),
233                 chunker->name, (unsigned)chunker->pid);
234         fflush(stderr);
235     }
236 }
237
238 cmd_t
239 getresult(
240     int fd,
241     int show,
242     int *result_argc,
243     char ***result_argv)
244 {
245     cmd_t t;
246     char *line;
247
248     if((line = areads(fd)) == NULL) {
249         if(errno) {
250             error(_("reading result from %s: %s"), childstr(fd), strerror(errno));
251             /*NOTREACHED*/
252         }
253         *result_argv = NULL;
254         *result_argc = 0;                               /* EOF */
255     } else {
256         *result_argv = split_quoted_strings(line);
257         *result_argc = g_strv_length(*result_argv);
258     }
259
260     if(show) {
261         g_printf(_("driver: result time %s from %s:"),
262                walltime_str(curclock()),
263                childstr(fd));
264         if(line) {
265             g_printf(" %s", line);
266             putchar('\n');
267         } else {
268             g_printf(" (eof)\n");
269         }
270         fflush(stdout);
271     }
272     amfree(line);
273
274     if(*result_argc < 1) return BOGUS;
275
276     for(t = (cmd_t)(BOGUS+1); t < LAST_TOK; t++)
277         if(strcmp((*result_argv)[0], cmdstr[t]) == 0) return t;
278
279     return BOGUS;
280 }
281
282
283 int
284 taper_cmd(
285     cmd_t cmd,
286     void *ptr,
287     char *destname,
288     int level,
289     char *datestamp)
290 {
291     char *cmdline = NULL;
292     char number[NUM_STR_SIZE];
293     char splitsize[NUM_STR_SIZE];
294     char fallback_splitsize[NUM_STR_SIZE];
295     char *diskbuffer = NULL;
296     disk_t *dp;
297     char *qname;
298     char *qdest;
299     char *q;
300
301     switch(cmd) {
302     case START_TAPER:
303         cmdline = vstralloc(cmdstr[cmd], " ", (char *)ptr, "\n", NULL);
304         break;
305     case FILE_WRITE:
306         dp = (disk_t *) ptr;
307         qname = quote_string(dp->name);
308         qdest = quote_string(destname);
309         g_snprintf(number, SIZEOF(number), "%d", level);
310         g_snprintf(splitsize, SIZEOF(splitsize), "%lld",
311                  (long long)dp->tape_splitsize * 1024);
312         cmdline = vstralloc(cmdstr[cmd],
313                             " ", disk2serial(dp),
314                             " ", qdest,
315                             " ", dp->host->hostname,
316                             " ", qname,
317                             " ", number,
318                             " ", datestamp,
319                             " ", splitsize,
320                             "\n", NULL);
321         amfree(qdest);
322         amfree(qname);
323         break;
324     case PORT_WRITE:
325         dp = (disk_t *) ptr;
326         qname = quote_string(dp->name);
327         g_snprintf(number, SIZEOF(number), "%d", level);
328
329         /*
330           If we haven't been given a place to buffer split dumps to disk,
331           make the argument something besides and empty string so's taper
332           won't get confused
333         */
334         if(!dp->split_diskbuffer || dp->split_diskbuffer[0] == '\0'){
335             diskbuffer = "NULL";
336         } else {
337             diskbuffer = dp->split_diskbuffer;
338         }
339         g_snprintf(splitsize, SIZEOF(splitsize), "%lld",
340                  (long long)dp->tape_splitsize * 1024);
341         g_snprintf(fallback_splitsize, SIZEOF(fallback_splitsize), "%lld",
342                  (long long)dp->fallback_splitsize * 1024);
343         cmdline = vstralloc(cmdstr[cmd],
344                             " ", disk2serial(dp),
345                             " ", dp->host->hostname,
346                             " ", qname,
347                             " ", number,
348                             " ", datestamp,
349                             " ", splitsize,
350                             " ", diskbuffer,
351                             " ", fallback_splitsize,
352                             "\n", NULL);
353         amfree(qname);
354         break;
355     case DONE: /* handle */
356     case FAILED: /* handle */
357         dp = (disk_t *) ptr;
358         cmdline = vstralloc(cmdstr[cmd],
359                             " ", disk2serial(dp),
360                             "\n", NULL);
361         break;
362     case NO_NEW_TAPE:
363         q = quote_string((char *)ptr);
364         cmdline = vstralloc(cmdstr[cmd],
365                             " ", q,
366                             "\n", NULL);
367         amfree(q);
368         break;
369     case NEW_TAPE:
370     case QUIT:
371         cmdline = stralloc2(cmdstr[cmd], "\n");
372         break;
373     default:
374         error(_("Don't know how to send %s command to taper"), cmdstr[cmd]);
375         /*NOTREACHED*/
376     }
377
378     /*
379      * Note: cmdline already has a '\n'.
380      */
381     g_printf(_("driver: send-cmd time %s to taper: %s"),
382            walltime_str(curclock()), cmdline);
383     fflush(stdout);
384     if ((full_write(taper, cmdline, strlen(cmdline))) < strlen(cmdline)) {
385         g_printf(_("writing taper command '%s' failed: %s\n"),
386                 cmdline, strerror(errno));
387         fflush(stdout);
388         amfree(cmdline);
389         return 0;
390     }
391     if(cmd == QUIT) aclose(taper);
392     amfree(cmdline);
393     return 1;
394 }
395
396 int
397 dumper_cmd(
398     dumper_t *dumper,
399     cmd_t cmd,
400     disk_t *dp,
401     char   *mesg)
402 {
403     char *cmdline = NULL;
404     char number[NUM_STR_SIZE];
405     char numberport[NUM_STR_SIZE];
406     char *o;
407     char *device;
408     char *features;
409     char *qname;
410     char *qmesg;
411
412     switch(cmd) {
413     case START:
414         cmdline = vstralloc(cmdstr[cmd], " ", mesg, "\n", NULL);
415         break;
416     case PORT_DUMP:
417         if(dp && dp->device) {
418             device = dp->device;
419         }
420         else {
421             device = "NODEVICE";
422         }
423
424         if (dp != NULL) {
425             device = quote_string((dp->device) ? dp->device : "NODEVICE");
426             qname = quote_string(dp->name);
427             g_snprintf(number, SIZEOF(number), "%d", sched(dp)->level);
428             g_snprintf(numberport, SIZEOF(numberport), "%d", dumper->output_port);
429             features = am_feature_to_string(dp->host->features);
430             if (am_has_feature(dp->host->features, fe_req_xml)) {
431                 o = xml_optionstr(dp, dp->host->features, NULL, 1);
432                 if (dp->application) {
433                     char *app = xml_application(dp->application,
434                                                 dp->host->features);
435                     vstrextend(&o, app, NULL);
436                     amfree(app);
437                 }
438                 o = quote_string(o);
439             } else {
440                 o = optionstr(dp, dp->host->features, NULL);
441             }
442             if ( o == NULL ) {
443               error(_("problem with option string, check the dumptype definition.\n"));
444             }
445
446             dbprintf("security_driver %s\n", dp->security_driver);
447             cmdline = vstralloc(cmdstr[cmd],
448                             " ", disk2serial(dp),
449                             " ", numberport,
450                             " ", dp->host->hostname,
451                             " ", features,
452                             " ", qname,
453                             " ", device,
454                             " ", number,
455                             " ", sched(dp)->dumpdate,
456                             " ", dp->program && strcmp(dp->program,"APPLICATION")!=0 ? dp->program: application_get_plugin(dp->application),
457                             " ", dp->amandad_path,
458                             " ", dp->client_username,
459                             " ", dp->ssh_keys,
460                             " ", dp->security_driver,
461                             " |", o,
462                             "\n", NULL);
463             amfree(features);
464             amfree(o);
465             amfree(qname);
466             amfree(device);
467         } else {
468                 error(_("PORT-DUMP without disk pointer\n"));
469                 /*NOTREACHED*/
470         }
471         break;
472     case QUIT:
473     case ABORT:
474         qmesg = quote_string(mesg);
475         cmdline = vstralloc(cmdstr[cmd], " ", qmesg, "\n", NULL );
476         amfree(qmesg);
477         break;
478     default:
479         error(_("Don't know how to send %s command to dumper"), cmdstr[cmd]);
480         /*NOTREACHED*/
481     }
482
483     /*
484      * Note: cmdline already has a '\n'.
485      */
486     if(dumper->down) {
487         g_printf(_("driver: send-cmd time %s ignored to down dumper %s: %s"),
488                walltime_str(curclock()), dumper->name, cmdline);
489     } else {
490         g_printf(_("driver: send-cmd time %s to %s: %s"),
491                walltime_str(curclock()), dumper->name, cmdline);
492         fflush(stdout);
493         if (full_write(dumper->fd, cmdline, strlen(cmdline)) < strlen(cmdline)) {
494             g_printf(_("writing %s command: %s\n"), dumper->name, strerror(errno));
495             fflush(stdout);
496             amfree(cmdline);
497             return 0;
498         }
499         if (cmd == QUIT) aclose(dumper->fd);
500     }
501     amfree(cmdline);
502     return 1;
503 }
504
505 int
506 chunker_cmd(
507     chunker_t *chunker,
508     cmd_t cmd,
509     disk_t *dp,
510     char   *mesg)
511 {
512     char *cmdline = NULL;
513     char number[NUM_STR_SIZE];
514     char chunksize[NUM_STR_SIZE];
515     char use[NUM_STR_SIZE];
516     char *o;
517     int activehd=0;
518     assignedhd_t **h=NULL;
519     char *features;
520     char *qname;
521     char *qdest;
522
523     switch(cmd) {
524     case START:
525         cmdline = vstralloc(cmdstr[cmd], " ", mesg, "\n", NULL);
526         break;
527     case PORT_WRITE:
528         if(dp && sched(dp) && sched(dp)->holdp) {
529             h = sched(dp)->holdp;
530             activehd = sched(dp)->activehd;
531         }
532
533         if (dp && h) {
534             qname = quote_string(dp->name);
535             qdest = quote_string(sched(dp)->destname);
536             h[activehd]->disk->allocated_dumpers++;
537             g_snprintf(number, SIZEOF(number), "%d", sched(dp)->level);
538             g_snprintf(chunksize, SIZEOF(chunksize), "%lld",
539                     (long long)holdingdisk_get_chunksize(h[0]->disk->hdisk));
540             g_snprintf(use, SIZEOF(use), "%lld",
541                     (long long)h[0]->reserved);
542             features = am_feature_to_string(dp->host->features);
543             o = optionstr(dp, dp->host->features, NULL);
544             if ( o == NULL ) {
545               error(_("problem with option string, check the dumptype definition.\n"));
546             }
547             cmdline = vstralloc(cmdstr[cmd],
548                             " ", disk2serial(dp),
549                             " ", qdest,
550                             " ", dp->host->hostname,
551                             " ", features,
552                             " ", qname,
553                             " ", number,
554                             " ", sched(dp)->dumpdate,
555                             " ", chunksize,
556                             " ", dp->program,
557                             " ", use,
558                             " |", o,
559                             "\n", NULL);
560             amfree(features);
561             amfree(o);
562             amfree(qdest);
563             amfree(qname);
564         } else {
565                 error(_("%s command without disk and holding disk.\n"),
566                       cmdstr[cmd]);
567                 /*NOTREACHED*/
568         }
569         break;
570     case CONTINUE:
571         if(dp && sched(dp) && sched(dp)->holdp) {
572             h = sched(dp)->holdp;
573             activehd = sched(dp)->activehd;
574         }
575
576         if(dp && h) {
577             qname = quote_string(dp->name);
578             qdest = quote_string(h[activehd]->destname);
579             h[activehd]->disk->allocated_dumpers++;
580             g_snprintf(chunksize, SIZEOF(chunksize), "%lld", 
581                      (long long)holdingdisk_get_chunksize(h[activehd]->disk->hdisk));
582             g_snprintf(use, SIZEOF(use), "%lld", 
583                      (long long)(h[activehd]->reserved - h[activehd]->used));
584             cmdline = vstralloc(cmdstr[cmd],
585                                 " ", disk2serial(dp),
586                                 " ", qdest,
587                                 " ", chunksize,
588                                 " ", use,
589                                 "\n", NULL );
590             amfree(qdest);
591             amfree(qname);
592         } else {
593             cmdline = stralloc2(cmdstr[cmd], "\n");
594         }
595         break;
596     case QUIT:
597     case ABORT:
598         {
599             char *q = quote_string(mesg);
600             cmdline = vstralloc(cmdstr[cmd], " ", q, "\n", NULL);
601             amfree(q);
602         }
603         break;
604     case DONE:
605     case FAILED:
606         if( dp ) {
607             cmdline = vstralloc(cmdstr[cmd],
608                                 " ", disk2serial(dp),
609                                 "\n",  NULL);
610         } else {
611             cmdline = vstralloc(cmdstr[cmd], "\n");
612         }
613         break;
614     default:
615         error(_("Don't know how to send %s command to chunker"), cmdstr[cmd]);
616         /*NOTREACHED*/
617     }
618
619     /*
620      * Note: cmdline already has a '\n'.
621      */
622     g_printf(_("driver: send-cmd time %s to %s: %s"),
623            walltime_str(curclock()), chunker->name, cmdline);
624     fflush(stdout);
625     if (full_write(chunker->fd, cmdline, strlen(cmdline)) < strlen(cmdline)) {
626         g_printf(_("writing %s command: %s\n"), chunker->name, strerror(errno));
627         fflush(stdout);
628         amfree(cmdline);
629         return 0;
630     }
631     if (cmd == QUIT) aclose(chunker->fd);
632     amfree(cmdline);
633     return 1;
634 }
635
636 #define MAX_SERIAL MAX_DUMPERS+1        /* one for the taper */
637
638 long generation = 1;
639
640 struct serial_s {
641     long gen;
642     disk_t *dp;
643 } stable[MAX_SERIAL];
644
645 disk_t *
646 serial2disk(
647     char *str)
648 {
649     int rc, s;
650     long gen;
651
652     rc = sscanf(str, "%d-%ld", &s, &gen);
653     if(rc != 2) {
654         error(_("error [serial2disk \"%s\" parse error]"), str);
655         /*NOTREACHED*/
656     } else if (s < 0 || s >= MAX_SERIAL) {
657         error(_("error [serial out of range 0..%d: %d]"), MAX_SERIAL, s);
658         /*NOTREACHED*/
659     }
660     if(gen != stable[s].gen)
661         g_printf(_("driver: serial2disk error time %s serial gen mismatch %s\n"),
662                walltime_str(curclock()), str);
663     return stable[s].dp;
664 }
665
666 void
667 free_serial(
668     char *str)
669 {
670     int rc, s;
671     long gen;
672
673     rc = sscanf(str, _("%d-%ld"), &s, &gen);
674     if(!(rc == 2 && s >= 0 && s < MAX_SERIAL)) {
675         /* nuke self to get core dump for Brett */
676         g_fprintf(stderr, _("driver: free_serial: str \"%s\" rc %d s %d\n"),
677                 str, rc, s);
678         fflush(stderr);
679         abort();
680     }
681
682     if(gen != stable[s].gen)
683         g_printf(_("driver: free_serial error time %s serial gen mismatch %s\n"),
684                walltime_str(curclock()),str);
685     stable[s].gen = 0;
686     stable[s].dp = NULL;
687 }
688
689
690 void
691 free_serial_dp(
692     disk_t *dp)
693 {
694     int s;
695
696     for(s = 0; s < MAX_SERIAL; s++) {
697         if(stable[s].dp == dp) {
698             stable[s].gen = 0;
699             stable[s].dp = NULL;
700             return;
701         }
702     }
703
704     g_printf(_("driver: error time %s serial not found\n"),
705            walltime_str(curclock()));
706 }
707
708
709 void
710 check_unfree_serial(void)
711 {
712     int s;
713
714     /* find used serial number */
715     for(s = 0; s < MAX_SERIAL; s++) {
716         if(stable[s].gen != 0 || stable[s].dp != NULL) {
717             g_printf(_("driver: error time %s bug: serial in use: %02d-%05ld\n"),
718                    walltime_str(curclock()), s, stable[s].gen);
719         }
720     }
721 }
722
723 char *disk2serial(
724     disk_t *dp)
725 {
726     int s;
727     static char str[NUM_STR_SIZE];
728
729     for(s = 0; s < MAX_SERIAL; s++) {
730         if(stable[s].dp == dp) {
731             g_snprintf(str, SIZEOF(str), "%02d-%05ld", s, stable[s].gen);
732             return str;
733         }
734     }
735
736     /* find unused serial number */
737     for(s = 0; s < MAX_SERIAL; s++)
738         if(stable[s].gen == 0 && stable[s].dp == NULL)
739             break;
740     if(s >= MAX_SERIAL) {
741         g_printf(_("driver: error time %s bug: out of serial numbers\n"),
742                walltime_str(curclock()));
743         s = 0;
744     }
745
746     stable[s].gen = generation++;
747     stable[s].dp = dp;
748
749     g_snprintf(str, SIZEOF(str), "%02d-%05ld", s, stable[s].gen);
750     return str;
751 }
752
753 void
754 update_info_dumper(
755      disk_t *dp,
756      off_t origsize,
757      off_t dumpsize,
758      time_t dumptime)
759 {
760     int level, i;
761     info_t info;
762     stats_t *infp;
763     perf_t *perfp;
764     char *conf_infofile;
765
766     level = sched(dp)->level;
767
768     conf_infofile = config_dir_relative(getconf_str(CNF_INFOFILE));
769     if (open_infofile(conf_infofile)) {
770         error(_("could not open info db \"%s\""), conf_infofile);
771         /*NOTREACHED*/
772     }
773     amfree(conf_infofile);
774
775     get_info(dp->host->hostname, dp->name, &info);
776
777     /* Clean up information about this and higher-level dumps.  This
778        assumes that update_info_dumper() is always run before
779        update_info_taper(). */
780     for (i = level; i < DUMP_LEVELS; ++i) {
781       infp = &info.inf[i];
782       infp->size = (off_t)-1;
783       infp->csize = (off_t)-1;
784       infp->secs = (time_t)-1;
785       infp->date = (time_t)-1;
786       infp->label[0] = '\0';
787       infp->filenum = 0;
788     }
789
790     /* now store information about this dump */
791     infp = &info.inf[level];
792     infp->size = origsize;
793     infp->csize = dumpsize;
794     infp->secs = dumptime;
795     infp->date = sched(dp)->timestamp;
796
797     if(level == 0) perfp = &info.full;
798     else perfp = &info.incr;
799
800     /* Update the stats, but only if the new values are meaningful */
801     if(dp->compress != COMP_NONE && origsize > (off_t)0) {
802         newperf(perfp->comp, (double)dumpsize/(double)origsize);
803     }
804     if(dumptime > (time_t)0) {
805         if((off_t)dumptime >= dumpsize)
806             newperf(perfp->rate, 1);
807         else
808             newperf(perfp->rate, (double)dumpsize/(double)dumptime);
809     }
810
811     if(origsize >= (off_t)0 && getconf_int(CNF_RESERVE)<100) {
812         info.command = NO_COMMAND;
813     }
814
815     if (origsize >= (off_t)0 && level == info.last_level) {
816         info.consecutive_runs++;
817     } else if (origsize >= (off_t)0 || level < info.last_level) {
818         info.last_level = level;
819         info.consecutive_runs = 1;
820     }
821
822     if(origsize >= (off_t)0 && dumpsize >= (off_t)0) {
823         for(i=NB_HISTORY-1;i>0;i--) {
824             info.history[i] = info.history[i-1];
825         }
826
827         info.history[0].level = level;
828         info.history[0].size  = origsize;
829         info.history[0].csize = dumpsize;
830         info.history[0].date  = sched(dp)->timestamp;
831         info.history[0].secs  = dumptime;
832     }
833
834     if(put_info(dp->host->hostname, dp->name, &info)) {
835         error(_("infofile update failed (%s,'%s')\n"), dp->host->hostname, dp->name);
836         /*NOTREACHED*/
837     }
838
839     close_infofile();
840 }
841
842 void
843 update_info_taper(
844     disk_t *dp,
845     char *label,
846     off_t filenum,
847     int level)
848 {
849     info_t info;
850     stats_t *infp;
851     int rc;
852
853     rc = open_infofile(getconf_str(CNF_INFOFILE));
854     if(rc) {
855         error(_("could not open infofile %s: %s (%d)"), getconf_str(CNF_INFOFILE),
856               strerror(errno), rc);
857         /*NOTREACHED*/
858     }
859
860     get_info(dp->host->hostname, dp->name, &info);
861
862     infp = &info.inf[level];
863     /* XXX - should we record these two if no-record? */
864     strncpy(infp->label, label, SIZEOF(infp->label)-1);
865     infp->label[SIZEOF(infp->label)-1] = '\0';
866     infp->filenum = filenum;
867
868     info.command = NO_COMMAND;
869
870     if(put_info(dp->host->hostname, dp->name, &info)) {
871         error(_("infofile update failed (%s,'%s')\n"), dp->host->hostname, dp->name);
872         /*NOTREACHED*/
873     }
874     close_infofile();
875 }
876
877 /* Free an array of pointers to assignedhd_t after freeing the
878  * assignedhd_t themselves. The array must be NULL-terminated.
879  */
880 void free_assignedhd(
881     assignedhd_t **ahd)
882 {
883     int i;
884
885     if( !ahd ) { return; }
886
887     for( i = 0; ahd[i]; i++ ) {
888         amfree(ahd[i]->destname);
889         amfree(ahd[i]);
890     }
891     amfree(ahd);
892 }