Imported Upstream version 3.3.3
[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  * Copyright (c) 2007-2012 Zmanda, Inc.  All Rights Reserved.
5  * All Rights Reserved.
6  *
7  * Permission to use, copy, modify, distribute, and sell this software and its
8  * documentation for any purpose is hereby granted without fee, provided that
9  * the above copyright notice appear in all copies and that both that
10  * copyright notice and this permission notice appear in supporting
11  * documentation, and that the name of U.M. not be used in advertising or
12  * publicity pertaining to distribution of the software without specific,
13  * written prior permission.  U.M. makes no representations about the
14  * suitability of this software for any purpose.  It is provided "as is"
15  * without express or implied warranty.
16  *
17  * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
19  * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
21  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
22  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23  *
24  * Author: James da Silva, Systems Design and Analysis Group
25  *                         Computer Science Department
26  *                         University of Maryland at College Park
27  */
28 /*
29  * $Id: driverio.c,v 1.92 2006/08/24 01:57:16 paddy_s Exp $
30  *
31  * I/O-related functions for driver program
32  */
33 #include "amanda.h"
34 #include "util.h"
35 #include "clock.h"
36 #include "server_util.h"
37 #include "conffile.h"
38 #include "diskfile.h"
39 #include "infofile.h"
40 #include "logfile.h"
41 #include "timestamp.h"
42
43 #define GLOBAL          /* the global variables defined here */
44 #include "driverio.h"
45
46 int nb_chunker = 0;
47
48 static const char *childstr(int);
49
50 void
51 init_driverio(void)
52 {
53     dumper_t *dumper;
54
55     taper_fd = -1;
56
57     for(dumper = dmptable; dumper < dmptable + MAX_DUMPERS; dumper++) {
58         dumper->fd = -1;
59     }
60 }
61
62
63 static const char *
64 childstr(
65     int fd)
66 {
67     static char buf[NUM_STR_SIZE + 32];
68     dumper_t *dumper;
69
70     if (fd == taper_fd)
71         return ("taper");
72
73     for (dumper = dmptable; dumper < dmptable + MAX_DUMPERS; dumper++) {
74         if (dumper->fd == fd)
75             return (dumper->name);
76         if (dumper->chunker && dumper->chunker->fd == fd)
77             return (dumper->chunker->name);
78     }
79     g_snprintf(buf, SIZEOF(buf), _("unknown child (fd %d)"), fd);
80     return (buf);
81 }
82
83
84 void
85 startup_tape_process(
86     char *taper_program,
87     int   taper_parallel_write,
88     gboolean no_taper)
89 {
90     int       fd[2];
91     int       i;
92     char    **config_options;
93     taper_t  *taper;
94
95     /* always allocate the tapetable */
96     tapetable = calloc(sizeof(taper_t), taper_parallel_write+1);
97
98     for (taper = tapetable, i = 0; i < taper_parallel_write; taper++, i++) {
99         taper->name = g_strdup_printf("worker%d", i);
100         taper->sendresult = 0;
101         taper->input_error = NULL;
102         taper->tape_error = NULL;
103         taper->result = 0;
104         taper->dumper = NULL;
105         taper->disk = NULL;
106         taper->first_label = NULL;
107         taper->first_fileno = 0;
108         taper->state = TAPER_STATE_DEFAULT;
109         taper->left = 0;
110         taper->written = 0;
111
112         /* jump right to degraded mode if there's no taper */
113         if (no_taper) {
114             taper->tape_error = g_strdup("no taper started (--no-taper)");
115             taper->result = BOGUS;
116         }
117     }
118
119     /* don't start the taper if we're not supposed to */
120     if (no_taper)
121         return;
122
123     if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1) {
124         error(_("taper pipe: %s"), strerror(errno));
125         /*NOTREACHED*/
126     }
127     if(fd[0] < 0 || fd[0] >= (int)FD_SETSIZE) {
128         error(_("taper socketpair 0: descriptor %d out of range (0 .. %d)\n"),
129               fd[0], (int)FD_SETSIZE-1);
130         /*NOTREACHED*/
131     }
132     if(fd[1] < 0 || fd[1] >= (int)FD_SETSIZE) {
133         error(_("taper socketpair 1: descriptor %d out of range (0 .. %d)\n"),
134               fd[1], (int)FD_SETSIZE-1);
135         /*NOTREACHED*/
136     }
137
138     switch(taper_pid = fork()) {
139     case -1:
140         error(_("fork taper: %s"), strerror(errno));
141         /*NOTREACHED*/
142
143     case 0:     /* child process */
144         aclose(fd[0]);
145         if(dup2(fd[1], 0) == -1 || dup2(fd[1], 1) == -1)
146             error(_("taper dup2: %s"), strerror(errno));
147         config_options = get_config_options(2);
148         config_options[0] = "taper";
149         config_options[1] = get_config_name();
150         safe_fd(-1, 0);
151         execve(taper_program, config_options, safe_env());
152         error("exec %s: %s", taper_program, strerror(errno));
153         /*NOTREACHED*/
154
155     default:    /* parent process */
156         aclose(fd[1]);
157         taper_fd = fd[0];
158         taper_ev_read = NULL;
159     }
160 }
161
162 void
163 startup_dump_process(
164     dumper_t *dumper,
165     char *dumper_program)
166 {
167     int    fd[2];
168     char **config_options;
169
170     if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1) {
171         error(_("%s pipe: %s"), dumper->name, strerror(errno));
172         /*NOTREACHED*/
173     }
174
175     switch(dumper->pid = fork()) {
176     case -1:
177         error(_("fork %s: %s"), dumper->name, strerror(errno));
178         /*NOTREACHED*/
179
180     case 0:             /* child process */
181         aclose(fd[0]);
182         if(dup2(fd[1], 0) == -1 || dup2(fd[1], 1) == -1)
183             error(_("%s dup2: %s"), dumper->name, strerror(errno));
184         config_options = get_config_options(2);
185         config_options[0] = dumper->name ? dumper->name : "dumper",
186         config_options[1] = get_config_name();
187         safe_fd(-1, 0);
188         execve(dumper_program, config_options, safe_env());
189         error(_("exec %s (%s): %s"), dumper_program,
190               dumper->name, strerror(errno));
191         /*NOTREACHED*/
192
193     default:    /* parent process */
194         aclose(fd[1]);
195         dumper->fd = fd[0];
196         dumper->ev_read = NULL;
197         dumper->busy = dumper->down = 0;
198         dumper->dp = NULL;
199         g_fprintf(stderr,_("driver: started %s pid %u\n"),
200                 dumper->name, (unsigned)dumper->pid);
201         fflush(stderr);
202     }
203 }
204
205 void
206 startup_dump_processes(
207     char *dumper_program,
208     int inparallel,
209     char *timestamp)
210 {
211     int i;
212     dumper_t *dumper;
213     char number[NUM_STR_SIZE];
214
215     for(dumper = dmptable, i = 0; i < inparallel; dumper++, i++) {
216         g_snprintf(number, SIZEOF(number), "%d", i);
217         dumper->name = stralloc2("dumper", number);
218         dumper->chunker = &chktable[i];
219         chktable[i].name = stralloc2("chunker", number);
220         chktable[i].dumper = dumper;
221         chktable[i].fd = -1;
222
223         startup_dump_process(dumper, dumper_program);
224         dumper_cmd(dumper, START, NULL, (void *)timestamp);
225     }
226 }
227
228 void
229 startup_chunk_process(
230     chunker_t *chunker,
231     char *chunker_program)
232 {
233     int    fd[2];
234     char **config_options;
235
236     if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1) {
237         error(_("%s pipe: %s"), chunker->name, strerror(errno));
238         /*NOTREACHED*/
239     }
240
241     switch(chunker->pid = fork()) {
242     case -1:
243         error(_("fork %s: %s"), chunker->name, strerror(errno));
244         /*NOTREACHED*/
245
246     case 0:             /* child process */
247         aclose(fd[0]);
248         if(dup2(fd[1], 0) == -1 || dup2(fd[1], 1) == -1) {
249             error(_("%s dup2: %s"), chunker->name, strerror(errno));
250             /*NOTREACHED*/
251         }
252         config_options = get_config_options(2);
253         config_options[0] = chunker->name ? chunker->name : "chunker",
254         config_options[1] = get_config_name();
255         safe_fd(-1, 0);
256         execve(chunker_program, config_options, safe_env());
257         error(_("exec %s (%s): %s"), chunker_program,
258               chunker->name, strerror(errno));
259         /*NOTREACHED*/
260
261     default:    /* parent process */
262         aclose(fd[1]);
263         chunker->down = 0;
264         chunker->fd = fd[0];
265         chunker->ev_read = NULL;
266         g_fprintf(stderr,_("driver: started %s pid %u\n"),
267                 chunker->name, (unsigned)chunker->pid);
268         fflush(stderr);
269     }
270 }
271
272 cmd_t
273 getresult(
274     int fd,
275     int show,
276     int *result_argc,
277     char ***result_argv)
278 {
279     cmd_t t;
280     char *line;
281
282     if((line = areads(fd)) == NULL) {
283         if(errno) {
284             g_fprintf(stderr, _("reading result from %s: %s"), childstr(fd), strerror(errno));
285         }
286         *result_argv = NULL;
287         *result_argc = 0;                               /* EOF */
288     } else {
289         *result_argv = split_quoted_strings(line);
290         *result_argc = g_strv_length(*result_argv);
291     }
292
293     if(show) {
294         g_printf(_("driver: result time %s from %s:"),
295                walltime_str(curclock()),
296                childstr(fd));
297         if(line) {
298             g_printf(" %s", line);
299             putchar('\n');
300         } else {
301             g_printf(" (eof)\n");
302         }
303         fflush(stdout);
304     }
305     amfree(line);
306
307     if(*result_argc < 1) return BOGUS;
308
309     for(t = (cmd_t)(BOGUS+1); t < LAST_TOK; t++)
310         if(strcmp((*result_argv)[0], cmdstr[t]) == 0) return t;
311
312     return BOGUS;
313 }
314
315
316 static char *
317 taper_splitting_args(
318         disk_t *dp)
319 {
320     GString *args = NULL;
321     char *q = NULL;
322     dumptype_t *dt = dp->config;
323     tapetype_t *tt;
324
325     tt = lookup_tapetype(getconf_str(CNF_TAPETYPE));
326     g_assert(tt != NULL);
327
328     args = g_string_new("");
329
330     /* old dumptype-based parameters, using empty strings when not seen */
331     if (dt) { /* 'dt' may be NULL for flushes */
332         if (dumptype_seen(dt, DUMPTYPE_TAPE_SPLITSIZE)) {
333             g_string_append_printf(args, "%ju ",
334                         (uintmax_t)dumptype_get_tape_splitsize(dt)*1024);
335         } else {
336             g_string_append(args, "\"\" ");
337         }
338
339         q = quote_string(dumptype_seen(dt, DUMPTYPE_SPLIT_DISKBUFFER)?
340                 dumptype_get_split_diskbuffer(dt) : "");
341         g_string_append_printf(args, "%s ", q);
342         g_free(q);
343
344         if (dumptype_seen(dt, DUMPTYPE_FALLBACK_SPLITSIZE)) {
345             g_string_append_printf(args, "%ju ",
346                         (uintmax_t)dumptype_get_fallback_splitsize(dt)*1024);
347         } else {
348             g_string_append(args, "\"\" ");
349         }
350
351         if (dumptype_seen(dt, DUMPTYPE_ALLOW_SPLIT)) {
352             g_string_append_printf(args, "%d ",
353                         (int)dumptype_get_allow_split(dt));
354         } else {
355             g_string_append(args, "\"\" ");
356         }
357     } else {
358         g_string_append(args, "\"\" \"\" \"\" \"\" ");
359     }
360
361     /* new tapetype-based parameters */
362     if (tapetype_seen(tt, TAPETYPE_PART_SIZE)) {
363         g_string_append_printf(args, "%ju ",
364                     (uintmax_t)tapetype_get_part_size(tt)*1024);
365     } else {
366         g_string_append(args, "\"\" ");
367     }
368
369     q = "";
370     if (tapetype_seen(tt, TAPETYPE_PART_CACHE_TYPE)) {
371         switch (tapetype_get_part_cache_type(tt)) {
372             default:
373             case PART_CACHE_TYPE_NONE:
374                 q = "none";
375                 break;
376
377             case PART_CACHE_TYPE_MEMORY:
378                 q = "memory";
379                 break;
380
381             case PART_CACHE_TYPE_DISK:
382                 q = "disk";
383                 break;
384         }
385     }
386     q = quote_string(q);
387     g_string_append_printf(args, "%s ", q);
388     g_free(q);
389
390     q = quote_string(tapetype_seen(tt, TAPETYPE_PART_CACHE_DIR)?
391             tapetype_get_part_cache_dir(tt) : "");
392     g_string_append_printf(args, "%s ", q);
393     g_free(q);
394
395     if (tapetype_seen(tt, TAPETYPE_PART_CACHE_MAX_SIZE)) {
396         g_string_append_printf(args, "%ju ",
397                     (uintmax_t)tapetype_get_part_cache_max_size(tt)*1024);
398     } else {
399         g_string_append(args, "\"\" ");
400     }
401
402
403     return g_string_free(args, FALSE);
404 }
405
406 int
407 taper_cmd(
408     cmd_t cmd,
409     void *ptr,
410     char *destname,
411     int level,
412     char *datestamp)
413 {
414     char *cmdline = NULL;
415     char number[NUM_STR_SIZE];
416     char orig_kb[NUM_STR_SIZE];
417     char *data_path;
418     disk_t *dp;
419     char *qname;
420     char *qdest;
421     char *q;
422     char *splitargs;
423     uintmax_t origsize;
424
425     switch(cmd) {
426     case START_TAPER:
427         cmdline = vstralloc(cmdstr[cmd],
428                             " ", destname,
429                             " ", datestamp,
430                             "\n", NULL);
431         break;
432     case CLOSE_VOLUME:
433         dp = (disk_t *) ptr;
434         cmdline = g_strjoin(NULL, cmdstr[cmd],
435                             " ", sched(dp)->taper->name,
436                             "\n", NULL);
437         break;
438     case FILE_WRITE:
439         dp = (disk_t *) ptr;
440         qname = quote_string(dp->name);
441         qdest = quote_string(destname);
442         g_snprintf(number, SIZEOF(number), "%d", level);
443         if (sched(dp)->origsize >= 0)
444             origsize = sched(dp)->origsize;
445         else
446             origsize = 0;
447         g_snprintf(orig_kb, SIZEOF(orig_kb), "%ju", origsize);
448         splitargs = taper_splitting_args(dp);
449         cmdline = vstralloc(cmdstr[cmd],
450                             " ", sched(dp)->taper->name,
451                             " ", disk2serial(dp),
452                             " ", qdest,
453                             " ", dp->host->hostname,
454                             " ", qname,
455                             " ", number,
456                             " ", datestamp,
457                             " ", splitargs,
458                                  orig_kb,
459                             "\n", NULL);
460         amfree(splitargs);
461         amfree(qdest);
462         amfree(qname);
463         break;
464
465     case PORT_WRITE:
466         dp = (disk_t *) ptr;
467         qname = quote_string(dp->name);
468         g_snprintf(number, SIZEOF(number), "%d", level);
469         data_path = data_path_to_string(dp->data_path);
470
471         /*
472           If we haven't been given a place to buffer split dumps to disk,
473           make the argument something besides and empty string so's taper
474           won't get confused
475         */
476         splitargs = taper_splitting_args(dp);
477         cmdline = vstralloc(cmdstr[cmd],
478                             " ", sched(dp)->taper->name,
479                             " ", disk2serial(dp),
480                             " ", dp->host->hostname,
481                             " ", qname,
482                             " ", number,
483                             " ", datestamp,
484                             " ", splitargs,
485                                  data_path,
486                             "\n", NULL);
487         amfree(splitargs);
488         amfree(qname);
489         break;
490     case DONE: /* handle */
491         dp = (disk_t *) ptr;
492         if (sched(dp)->origsize >= 0)
493             origsize = sched(dp)->origsize;
494         else
495             origsize = 0;
496         g_snprintf(number, SIZEOF(number), "%ju", origsize);
497         cmdline = vstralloc(cmdstr[cmd],
498                             " ", sched(dp)->taper->name,
499                             " ", disk2serial(dp),
500                             " ", number,
501                             "\n", NULL);
502         break;
503     case FAILED: /* handle */
504         dp = (disk_t *) ptr;
505         cmdline = vstralloc(cmdstr[cmd],
506                             " ", sched(dp)->taper->name,
507                             " ", disk2serial(dp),
508                             "\n", NULL);
509         break;
510     case NO_NEW_TAPE:
511         dp = (disk_t *) ptr;
512         q = quote_string(destname);     /* reason why no new tape */
513         cmdline = vstralloc(cmdstr[cmd],
514                             " ", sched(dp)->taper->name,
515                             " ", disk2serial(dp),
516                             " ", q,
517                             "\n", NULL);
518         amfree(q);
519         break;
520     case NEW_TAPE:
521         dp = (disk_t *) ptr;
522         cmdline = vstralloc(cmdstr[cmd],
523                             " ", sched(dp)->taper->name,
524                             " ", disk2serial(dp),
525                             "\n", NULL);
526         break;
527     case START_SCAN:
528         dp = (disk_t *) ptr;
529         cmdline = vstralloc(cmdstr[cmd],
530                             " ", sched(dp)->taper->name,
531                             " ", disk2serial(dp),
532                             "\n", NULL);
533         break;
534     case TAKE_SCRIBE_FROM:
535         dp = (disk_t *) ptr;
536         cmdline = vstralloc(cmdstr[cmd],
537                             " ", sched(dp)->taper->name,
538                             " ", disk2serial(dp),
539                             " ", destname,  /* name of worker */
540                             "\n", NULL);
541         break;
542     case QUIT:
543         cmdline = stralloc2(cmdstr[cmd], "\n");
544         break;
545     default:
546         error(_("Don't know how to send %s command to taper"), cmdstr[cmd]);
547         /*NOTREACHED*/
548     }
549
550     /*
551      * Note: cmdline already has a '\n'.
552      */
553     g_printf(_("driver: send-cmd time %s to taper: %s"),
554            walltime_str(curclock()), cmdline);
555     fflush(stdout);
556     if ((full_write(taper_fd, cmdline, strlen(cmdline))) < strlen(cmdline)) {
557         g_printf(_("writing taper command '%s' failed: %s\n"),
558                 cmdline, strerror(errno));
559         fflush(stdout);
560         amfree(cmdline);
561         return 0;
562     }
563     if(cmd == QUIT) aclose(taper_fd);
564     amfree(cmdline);
565     return 1;
566 }
567
568 int
569 dumper_cmd(
570     dumper_t *dumper,
571     cmd_t cmd,
572     disk_t *dp,
573     char   *mesg)
574 {
575     char *cmdline = NULL;
576     char number[NUM_STR_SIZE];
577     char numberport[NUM_STR_SIZE];
578     char maxwarnings[NUM_STR_SIZE];
579     char *o, *oo;
580     char *device;
581     char *features;
582     char *qname;
583     char *qmesg;
584
585     switch(cmd) {
586     case START:
587         cmdline = vstralloc(cmdstr[cmd], " ", mesg, "\n", NULL);
588         break;
589     case PORT_DUMP:
590         if(dp && dp->device) {
591             device = dp->device;
592         }
593         else {
594             device = "NODEVICE";
595         }
596
597         if (dp != NULL) {
598             application_t *application = NULL;
599             char *plugin;
600             char *qplugin;
601             char *qamandad_path;
602             char *qclient_username;
603             char *qclient_port;
604             char *qssh_keys;
605             char *d_prop;
606
607             if (dp->application != NULL) {
608                 application = lookup_application(dp->application);
609                 g_assert(application != NULL);
610             }
611
612             device = quote_string((dp->device) ? dp->device : "NODEVICE");
613             qname = quote_string(dp->name);
614             g_snprintf(number, SIZEOF(number), "%d", sched(dp)->level);
615             g_snprintf(numberport, SIZEOF(numberport), "%d", dumper->output_port);
616             g_snprintf(maxwarnings, SIZEOF(maxwarnings), "%d", dp->max_warnings);
617             features = am_feature_to_string(dp->host->features);
618             if (am_has_feature(dp->host->features, fe_req_xml)) {
619                 o = xml_optionstr(dp, 1);
620
621                 d_prop = xml_dumptype_properties(dp);
622                 vstrextend(&o, d_prop, NULL);
623                 amfree(d_prop);
624
625                 if (application) {
626                     char *xml_app;
627                     xml_app = xml_application(dp, application,
628                                               dp->host->features);
629                     vstrextend(&o, xml_app, NULL);
630                     amfree(xml_app);
631                 }
632                 oo = quote_string(o);
633                 amfree(o);
634                 o = oo;
635             } else {
636                 o = optionstr(dp);
637             }
638
639             g_assert(dp->program);
640             if (0 == strcmp(dp->program, "APPLICATION")) {
641                 g_assert(application != NULL);
642                 plugin = application_get_plugin(application);
643             } else {
644                 plugin = dp->program;
645             }
646             qplugin = quote_string(plugin);
647             qamandad_path = quote_string(dp->amandad_path);
648             qclient_username = quote_string(dp->client_username);
649             qclient_port = quote_string(dp->client_port);
650             qssh_keys = quote_string(dp->ssh_keys);
651             dbprintf("security_driver %s\n", dp->auth);
652
653             cmdline = vstralloc(cmdstr[cmd],
654                             " ", disk2serial(dp),
655                             " ", numberport,
656                             " ", dp->host->hostname,
657                             " ", features,
658                             " ", qname,
659                             " ", device,
660                             " ", number,
661                             " ", sched(dp)->dumpdate,
662                             " ", qplugin,
663                             " ", qamandad_path,
664                             " ", qclient_username,
665                             " ", qclient_port,
666                             " ", qssh_keys,
667                             " ", dp->auth,
668                             " ", data_path_to_string(dp->data_path),
669                             " ", dp->dataport_list,
670                             " ", maxwarnings,
671                             " |", o,
672                             "\n", NULL);
673             amfree(qplugin);
674             amfree(qamandad_path);
675             amfree(qclient_username);
676             amfree(qclient_port);
677             amfree(qssh_keys);
678             amfree(features);
679             amfree(o);
680             amfree(qname);
681             amfree(device);
682         } else {
683                 error(_("PORT-DUMP without disk pointer\n"));
684                 /*NOTREACHED*/
685         }
686         break;
687     case QUIT:
688     case ABORT:
689         qmesg = quote_string(mesg);
690         cmdline = vstralloc(cmdstr[cmd], " ", qmesg, "\n", NULL );
691         amfree(qmesg);
692         break;
693     default:
694         error(_("Don't know how to send %s command to dumper"), cmdstr[cmd]);
695         /*NOTREACHED*/
696     }
697
698     /*
699      * Note: cmdline already has a '\n'.
700      */
701     if(dumper->down) {
702         g_printf(_("driver: send-cmd time %s ignored to down dumper %s: %s"),
703                walltime_str(curclock()), dumper->name, cmdline);
704     } else {
705         g_printf(_("driver: send-cmd time %s to %s: %s"),
706                walltime_str(curclock()), dumper->name, cmdline);
707         fflush(stdout);
708         if (full_write(dumper->fd, cmdline, strlen(cmdline)) < strlen(cmdline)) {
709             g_printf(_("writing %s command: %s\n"), dumper->name, strerror(errno));
710             fflush(stdout);
711             amfree(cmdline);
712             return 0;
713         }
714         if (cmd == QUIT) aclose(dumper->fd);
715     }
716     amfree(cmdline);
717     return 1;
718 }
719
720 int
721 chunker_cmd(
722     chunker_t *chunker,
723     cmd_t cmd,
724     disk_t *dp,
725     char   *mesg)
726 {
727     char *cmdline = NULL;
728     char number[NUM_STR_SIZE];
729     char chunksize[NUM_STR_SIZE];
730     char use[NUM_STR_SIZE];
731     char *o;
732     int activehd=0;
733     assignedhd_t **h=NULL;
734     char *features;
735     char *qname;
736     char *qdest;
737
738     switch(cmd) {
739     case START:
740         cmdline = vstralloc(cmdstr[cmd], " ", mesg, "\n", NULL);
741         break;
742     case PORT_WRITE:
743         if(dp && sched(dp) && sched(dp)->holdp) {
744             h = sched(dp)->holdp;
745             activehd = sched(dp)->activehd;
746         }
747
748         if (dp && h) {
749             qname = quote_string(dp->name);
750             qdest = quote_string(sched(dp)->destname);
751             h[activehd]->disk->allocated_dumpers++;
752             g_snprintf(number, SIZEOF(number), "%d", sched(dp)->level);
753             g_snprintf(chunksize, SIZEOF(chunksize), "%lld",
754                     (long long)holdingdisk_get_chunksize(h[0]->disk->hdisk));
755             g_snprintf(use, SIZEOF(use), "%lld",
756                     (long long)h[0]->reserved);
757             features = am_feature_to_string(dp->host->features);
758             o = optionstr(dp);
759             cmdline = vstralloc(cmdstr[cmd],
760                             " ", disk2serial(dp),
761                             " ", qdest,
762                             " ", dp->host->hostname,
763                             " ", features,
764                             " ", qname,
765                             " ", number,
766                             " ", sched(dp)->dumpdate,
767                             " ", chunksize,
768                             " ", dp->program,
769                             " ", use,
770                             " |", o,
771                             "\n", NULL);
772             amfree(features);
773             amfree(o);
774             amfree(qdest);
775             amfree(qname);
776         } else {
777                 error(_("%s command without disk and holding disk.\n"),
778                       cmdstr[cmd]);
779                 /*NOTREACHED*/
780         }
781         break;
782     case CONTINUE:
783         if(dp && sched(dp) && sched(dp)->holdp) {
784             h = sched(dp)->holdp;
785             activehd = sched(dp)->activehd;
786         }
787
788         if(dp && h) {
789             qname = quote_string(dp->name);
790             qdest = quote_string(h[activehd]->destname);
791             h[activehd]->disk->allocated_dumpers++;
792             g_snprintf(chunksize, SIZEOF(chunksize), "%lld", 
793                      (long long)holdingdisk_get_chunksize(h[activehd]->disk->hdisk));
794             g_snprintf(use, SIZEOF(use), "%lld", 
795                      (long long)(h[activehd]->reserved - h[activehd]->used));
796             cmdline = vstralloc(cmdstr[cmd],
797                                 " ", disk2serial(dp),
798                                 " ", qdest,
799                                 " ", chunksize,
800                                 " ", use,
801                                 "\n", NULL );
802             amfree(qdest);
803             amfree(qname);
804         } else {
805             cmdline = stralloc2(cmdstr[cmd], "\n");
806         }
807         break;
808     case QUIT:
809     case ABORT:
810         {
811             char *q = quote_string(mesg);
812             cmdline = vstralloc(cmdstr[cmd], " ", q, "\n", NULL);
813             amfree(q);
814         }
815         break;
816     case DONE:
817     case FAILED:
818         if( dp ) {
819             cmdline = vstralloc(cmdstr[cmd],
820                                 " ", disk2serial(dp),
821                                 "\n",  NULL);
822         } else {
823             cmdline = vstralloc(cmdstr[cmd], "\n", NULL);
824         }
825         break;
826     default:
827         error(_("Don't know how to send %s command to chunker"), cmdstr[cmd]);
828         /*NOTREACHED*/
829     }
830
831     /*
832      * Note: cmdline already has a '\n'.
833      */
834     g_printf(_("driver: send-cmd time %s to %s: %s"),
835            walltime_str(curclock()), chunker->name, cmdline);
836     fflush(stdout);
837     if (full_write(chunker->fd, cmdline, strlen(cmdline)) < strlen(cmdline)) {
838         g_printf(_("writing %s command: %s\n"), chunker->name, strerror(errno));
839         fflush(stdout);
840         amfree(cmdline);
841         return 0;
842     }
843     if (cmd == QUIT) aclose(chunker->fd);
844     amfree(cmdline);
845     return 1;
846 }
847
848 #define MAX_SERIAL MAX_DUMPERS*2        /* one for each dumper and taper */
849
850 long generation = 1;
851
852 struct serial_s {
853     long gen;
854     disk_t *dp;
855 } stable[MAX_SERIAL];
856
857 disk_t *
858 serial2disk(
859     char *str)
860 {
861     int rc, s;
862     long gen;
863
864     rc = sscanf(str, "%d-%ld", &s, &gen);
865     if(rc != 2) {
866         error(_("error [serial2disk \"%s\" parse error]"), str);
867         /*NOTREACHED*/
868     } else if (s < 0 || s >= MAX_SERIAL) {
869         error(_("error [serial out of range 0..%d: %d]"), MAX_SERIAL, s);
870         /*NOTREACHED*/
871     }
872     if(gen != stable[s].gen)
873         g_printf(_("driver: serial2disk error time %s serial gen mismatch %s\n"),
874                walltime_str(curclock()), str);
875     return stable[s].dp;
876 }
877
878 void
879 free_serial(
880     char *str)
881 {
882     int rc, s;
883     long gen;
884
885     rc = sscanf(str, _("%d-%ld"), &s, &gen);
886     if(!(rc == 2 && s >= 0 && s < MAX_SERIAL)) {
887         /* nuke self to get core dump for Brett */
888         g_fprintf(stderr, _("driver: free_serial: str \"%s\" rc %d s %d\n"),
889                 str, rc, s);
890         fflush(stderr);
891         abort();
892     }
893
894     if(gen != stable[s].gen)
895         g_printf(_("driver: free_serial error time %s serial gen mismatch %s\n"),
896                walltime_str(curclock()),str);
897     stable[s].gen = 0;
898     stable[s].dp = NULL;
899 }
900
901
902 void
903 free_serial_dp(
904     disk_t *dp)
905 {
906     int s;
907
908     for(s = 0; s < MAX_SERIAL; s++) {
909         if(stable[s].dp == dp) {
910             stable[s].gen = 0;
911             stable[s].dp = NULL;
912             return;
913         }
914     }
915
916     g_printf(_("driver: error time %s serial not found for disk %s\n"),
917            walltime_str(curclock()), dp->name);
918 }
919
920
921 void
922 check_unfree_serial(void)
923 {
924     int s;
925
926     /* find used serial number */
927     for(s = 0; s < MAX_SERIAL; s++) {
928         if(stable[s].gen != 0 || stable[s].dp != NULL) {
929             g_printf(_("driver: error time %s bug: serial in use: %02d-%05ld\n"),
930                    walltime_str(curclock()), s, stable[s].gen);
931         }
932     }
933 }
934
935 char *disk2serial(
936     disk_t *dp)
937 {
938     int s;
939     static char str[NUM_STR_SIZE];
940
941     for(s = 0; s < MAX_SERIAL; s++) {
942         if(stable[s].dp == dp) {
943             g_snprintf(str, SIZEOF(str), "%02d-%05ld", s, stable[s].gen);
944             return str;
945         }
946     }
947
948     /* find unused serial number */
949     for(s = 0; s < MAX_SERIAL; s++)
950         if(stable[s].gen == 0 && stable[s].dp == NULL)
951             break;
952     if(s >= MAX_SERIAL) {
953         g_printf(_("driver: error time %s bug: out of serial numbers\n"),
954                walltime_str(curclock()));
955         s = 0;
956     }
957
958     stable[s].gen = generation++;
959     stable[s].dp = dp;
960
961     g_snprintf(str, SIZEOF(str), "%02d-%05ld", s, stable[s].gen);
962     return str;
963 }
964
965 void
966 update_info_dumper(
967      disk_t *dp,
968      off_t origsize,
969      off_t dumpsize,
970      time_t dumptime)
971 {
972     int level, i;
973     info_t info;
974     stats_t *infp;
975     perf_t *perfp;
976     char *conf_infofile;
977
978     level = sched(dp)->level;
979
980     conf_infofile = config_dir_relative(getconf_str(CNF_INFOFILE));
981     if (open_infofile(conf_infofile)) {
982         error(_("could not open info db \"%s\""), conf_infofile);
983         /*NOTREACHED*/
984     }
985     amfree(conf_infofile);
986
987     get_info(dp->host->hostname, dp->name, &info);
988
989     /* Clean up information about this and higher-level dumps.  This
990        assumes that update_info_dumper() is always run before
991        update_info_taper(). */
992     for (i = level; i < DUMP_LEVELS; ++i) {
993       infp = &info.inf[i];
994       infp->size = (off_t)-1;
995       infp->csize = (off_t)-1;
996       infp->secs = (time_t)-1;
997       infp->date = (time_t)-1;
998       infp->label[0] = '\0';
999       infp->filenum = 0;
1000     }
1001
1002     /* now store information about this dump */
1003     infp = &info.inf[level];
1004     infp->size = origsize;
1005     infp->csize = dumpsize;
1006     infp->secs = dumptime;
1007     if (sched(dp)->timestamp == 0) {
1008         infp->date = 0;
1009     } else {
1010         infp->date = get_time_from_timestamp(sched(dp)->datestamp);
1011     }
1012
1013     if(level == 0) perfp = &info.full;
1014     else perfp = &info.incr;
1015
1016     /* Update the stats, but only if the new values are meaningful */
1017     if(dp->compress != COMP_NONE && origsize > (off_t)0) {
1018         newperf(perfp->comp, (double)dumpsize/(double)origsize);
1019     }
1020     if(dumptime > (time_t)0) {
1021         if((off_t)dumptime >= dumpsize)
1022             newperf(perfp->rate, 1);
1023         else
1024             newperf(perfp->rate, (double)dumpsize/(double)dumptime);
1025     }
1026
1027     if(origsize >= (off_t)0 && getconf_int(CNF_RESERVE)<100) {
1028         info.command = NO_COMMAND;
1029     }
1030
1031     if (origsize >= (off_t)0 && level == info.last_level) {
1032         info.consecutive_runs++;
1033     } else if (origsize >= (off_t)0) {
1034         info.last_level = level;
1035         info.consecutive_runs = 1;
1036     }
1037
1038     if(origsize >= (off_t)0 && dumpsize >= (off_t)0) {
1039         for(i=NB_HISTORY-1;i>0;i--) {
1040             info.history[i] = info.history[i-1];
1041         }
1042
1043         info.history[0].level = level;
1044         info.history[0].size  = origsize;
1045         info.history[0].csize = dumpsize;
1046         if (sched(dp)->timestamp == 0) {
1047             info.history[0].date = 0;
1048         } else {
1049             info.history[0].date = get_time_from_timestamp(sched(dp)->datestamp);
1050         }
1051         info.history[0].secs  = dumptime;
1052     }
1053
1054     if (put_info(dp->host->hostname, dp->name, &info)) {
1055         int save_errno = errno;
1056         g_fprintf(stderr, _("infofile update failed (%s,'%s'): %s\n"),
1057                   dp->host->hostname, dp->name, strerror(save_errno));
1058         log_add(L_ERROR, _("infofile update failed (%s,'%s'): %s\n"),
1059                 dp->host->hostname, dp->name, strerror(save_errno));
1060         error(_("infofile update failed (%s,'%s'): %s\n"),
1061               dp->host->hostname, dp->name, strerror(save_errno));
1062         /*NOTREACHED*/
1063     }
1064
1065     close_infofile();
1066 }
1067
1068 void
1069 update_info_taper(
1070     disk_t *dp,
1071     char *label,
1072     off_t filenum,
1073     int level)
1074 {
1075     info_t info;
1076     stats_t *infp;
1077     int rc;
1078
1079     rc = open_infofile(getconf_str(CNF_INFOFILE));
1080     if(rc) {
1081         error(_("could not open infofile %s: %s (%d)"), getconf_str(CNF_INFOFILE),
1082               strerror(errno), rc);
1083         /*NOTREACHED*/
1084     }
1085
1086     get_info(dp->host->hostname, dp->name, &info);
1087
1088     infp = &info.inf[level];
1089     /* XXX - should we record these two if no-record? */
1090     strncpy(infp->label, label, SIZEOF(infp->label)-1);
1091     infp->label[SIZEOF(infp->label)-1] = '\0';
1092     infp->filenum = filenum;
1093
1094     info.command = NO_COMMAND;
1095
1096     if (put_info(dp->host->hostname, dp->name, &info)) {
1097         int save_errno = errno;
1098         g_fprintf(stderr, _("infofile update failed (%s,'%s'): %s\n"),
1099                   dp->host->hostname, dp->name, strerror(save_errno));
1100         log_add(L_ERROR, _("infofile update failed (%s,'%s'): %s\n"),
1101                 dp->host->hostname, dp->name, strerror(save_errno));
1102         error(_("infofile update failed (%s,'%s'): %s\n"),
1103               dp->host->hostname, dp->name, strerror(save_errno));
1104         /*NOTREACHED*/
1105     }
1106     close_infofile();
1107 }
1108
1109 /* Free an array of pointers to assignedhd_t after freeing the
1110  * assignedhd_t themselves. The array must be NULL-terminated.
1111  */
1112 void free_assignedhd(
1113     assignedhd_t **ahd)
1114 {
1115     int i;
1116
1117     if( !ahd ) { return; }
1118
1119     for( i = 0; ahd[i]; i++ ) {
1120         amfree(ahd[i]->destname);
1121         amfree(ahd[i]);
1122     }
1123     amfree(ahd);
1124 }