252349327ad9241eea44f09383aadc70c1aaf5c8
[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.81.2.1 2006/04/23 18:52:04 martinea 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 "conffile.h"
36 #include "diskfile.h"
37 #include "infofile.h"
38 #include "logfile.h"
39 #include "token.h"
40 #include "server_util.h"
41
42 #define GLOBAL          /* the global variables defined here */
43 #include "driverio.h"
44
45 int nb_chunker = 0;
46
47 static const char *childstr P((int));
48
49 void init_driverio()
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(fd)
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->fd == fd)
75             return (dumper->chunker->name);
76     }
77     snprintf(buf, sizeof(buf), "unknown child (fd %d)", fd);
78     return (buf);
79 }
80
81
82 void startup_tape_process(taper_program)
83 char *taper_program;
84 {
85     int fd[2];
86
87     if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1)
88         error("taper pipe: %s", strerror(errno));
89     if(fd[0] < 0 || fd[0] >= FD_SETSIZE) {
90         error("taper socketpair 0: descriptor %d out of range (0 .. %d)\n",
91               fd[0], FD_SETSIZE-1);
92     }
93     if(fd[1] < 0 || fd[1] >= FD_SETSIZE) {
94         error("taper socketpair 1: descriptor %d out of range (0 .. %d)\n",
95               fd[1], FD_SETSIZE-1);
96     }
97
98     switch(taper_pid = fork()) {
99     case -1:
100         error("fork taper: %s", strerror(errno));
101     case 0:     /* child process */
102         aclose(fd[0]);
103         if(dup2(fd[1], 0) == -1 || dup2(fd[1], 1) == -1)
104             error("taper dup2: %s", strerror(errno));
105         execle(taper_program, "taper", config_name, (char *)0, safe_env());
106         error("exec %s: %s", taper_program, strerror(errno));
107     default:    /* parent process */
108         aclose(fd[1]);
109         taper = fd[0];
110         taper_ev_read = NULL;
111     }
112 }
113
114 void startup_dump_process(dumper, dumper_program)
115 dumper_t *dumper;
116 char *dumper_program;
117 {
118     int fd[2];
119
120     if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1)
121         error("%s pipe: %s", dumper->name, strerror(errno));
122
123     switch(dumper->pid = fork()) {
124     case -1:
125         error("fork %s: %s", dumper->name, strerror(errno));
126     case 0:             /* child process */
127         aclose(fd[0]);
128         if(dup2(fd[1], 0) == -1 || dup2(fd[1], 1) == -1)
129             error("%s dup2: %s", dumper->name, strerror(errno));
130         execle(dumper_program,
131                dumper->name ? dumper->name : "dumper",
132                config_name,
133                (char *)0,
134                safe_env());
135         error("exec %s (%s): %s", dumper_program,
136               dumper->name, strerror(errno));
137     default:    /* parent process */
138         aclose(fd[1]);
139         dumper->fd = fd[0];
140         dumper->ev_read = NULL;
141         dumper->busy = dumper->down = 0;
142         dumper->dp = NULL;
143         fprintf(stderr,"driver: started %s pid %d\n",
144                 dumper->name, dumper->pid);
145         fflush(stderr);
146     }
147 }
148
149 void startup_dump_processes(dumper_program, inparallel)
150 char *dumper_program;
151 int inparallel;
152 {
153     int i;
154     dumper_t *dumper;
155     char number[NUM_STR_SIZE];
156
157     for(dumper = dmptable, i = 0; i < inparallel; dumper++, i++) {
158         snprintf(number, sizeof(number), "%d", i);
159         dumper->name = stralloc2("dumper", number);
160         dumper->chunker = &chktable[i];
161         chktable[i].name = stralloc2("chunker", number);
162         chktable[i].dumper = dumper;
163         chktable[i].fd = -1;
164
165         startup_dump_process(dumper, dumper_program);
166     }
167 }
168
169 void startup_chunk_process(chunker, chunker_program)
170 chunker_t *chunker;
171 char *chunker_program;
172 {
173     int fd[2];
174
175     if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1)
176         error("%s pipe: %s", chunker->name, strerror(errno));
177
178     switch(chunker->pid = fork()) {
179     case -1:
180         error("fork %s: %s", chunker->name, strerror(errno));
181     case 0:             /* child process */
182         aclose(fd[0]);
183         if(dup2(fd[1], 0) == -1 || dup2(fd[1], 1) == -1)
184             error("%s dup2: %s", chunker->name, strerror(errno));
185         execle(chunker_program,
186                chunker->name ? chunker->name : "chunker",
187                config_name,
188                (char *)0,
189                safe_env());
190         error("exec %s (%s): %s", chunker_program,
191               chunker->name, strerror(errno));
192     default:    /* parent process */
193         aclose(fd[1]);
194         chunker->down = 0;
195         chunker->fd = fd[0];
196         chunker->ev_read = NULL;
197         fprintf(stderr,"driver: started %s pid %d\n",
198                 chunker->name, chunker->pid);
199         fflush(stderr);
200     }
201 }
202
203 cmd_t getresult(fd, show, result_argc, result_argv, max_arg)
204 int fd;
205 int show;
206 int *result_argc;
207 char **result_argv;
208 int max_arg;
209 {
210     int arg;
211     cmd_t t;
212     char *line;
213
214     if((line = areads(fd)) == NULL) {
215         if(errno) {
216             error("reading result from %s: %s", childstr(fd), strerror(errno));
217         }
218         *result_argc = 0;                               /* EOF */
219     } else {
220         *result_argc = split(line, result_argv, max_arg, " ");
221     }
222
223     if(show) {
224         printf("driver: result time %s from %s:",
225                walltime_str(curclock()),
226                childstr(fd));
227         if(line) {
228             for(arg = 1; arg <= *result_argc; arg++) {
229                 printf(" %s", result_argv[arg]);
230             }
231             putchar('\n');
232         } else {
233             printf(" (eof)\n");
234         }
235         fflush(stdout);
236     }
237     amfree(line);
238
239 #ifdef DEBUG
240     printf("argc = %d\n", *result_argc);
241     for(arg = 0; arg < *result_argc; arg++)
242         printf("argv[%d] = \"%s\"\n", arg, result_argv[arg]);
243 #endif
244
245     if(*result_argc < 1) return BOGUS;
246
247     for(t = (cmd_t)(BOGUS+1); t < LAST_TOK; t++)
248         if(strcmp(result_argv[1], cmdstr[t]) == 0) return t;
249
250     return BOGUS;
251 }
252
253
254 int taper_cmd(cmd, /* optional */ ptr, destname, level, datestamp)
255 cmd_t cmd;
256 void *ptr;
257 char *destname;
258 int level;
259 char *datestamp;
260 {
261     char *cmdline = NULL;
262     char number[NUM_STR_SIZE];
263     char splitsize[NUM_STR_SIZE];
264     char fallback_splitsize[NUM_STR_SIZE];
265     char *diskbuffer = NULL;
266     disk_t *dp;
267     char *features;
268
269     switch(cmd) {
270     case START_TAPER:
271         cmdline = vstralloc(cmdstr[cmd], " ", (char *)ptr, "\n", NULL);
272         break;
273     case FILE_WRITE:
274         dp = (disk_t *) ptr;
275         snprintf(number, sizeof(number), "%d", level);
276         snprintf(splitsize, sizeof(splitsize), "%ld", dp->tape_splitsize);
277         features = am_feature_to_string(dp->host->features);
278         cmdline = vstralloc(cmdstr[cmd],
279                             " ", disk2serial(dp),
280                             " ", destname,
281                             " ", dp->host->hostname,
282                             " ", features,
283                             " ", dp->name,
284                             " ", number,
285                             " ", datestamp,
286                             " ", splitsize,
287                             "\n", NULL);
288         amfree(features);
289         break;
290     case PORT_WRITE:
291         dp = (disk_t *) ptr;
292         snprintf(number, sizeof(number), "%d", level);
293
294         /*
295           If we haven't been given a place to buffer split dumps to disk,
296           make the argument something besides and empty string so's taper
297           won't get confused
298         */
299         if(!dp->split_diskbuffer || dp->split_diskbuffer[0] == '\0'){
300             diskbuffer = "NULL";
301         } else {
302             diskbuffer = dp->split_diskbuffer;
303         }
304         snprintf(splitsize, sizeof(splitsize), "%ld", dp->tape_splitsize);
305         snprintf(fallback_splitsize, sizeof(fallback_splitsize),
306                     "%ld", dp->fallback_splitsize);
307         features = am_feature_to_string(dp->host->features);
308         cmdline = vstralloc(cmdstr[cmd],
309                             " ", disk2serial(dp),
310                             " ", dp->host->hostname,
311                             " ", features,
312                             " ", dp->name,
313                             " ", number,
314                             " ", datestamp,
315                             " ", splitsize,
316                             " ", diskbuffer,
317                             " ", fallback_splitsize,
318                             "\n", NULL);
319         amfree(features);
320         break;
321     case QUIT:
322         cmdline = stralloc2(cmdstr[cmd], "\n");
323         break;
324     default:
325         error("Don't know how to send %s command to taper", cmdstr[cmd]);
326     }
327     /*
328      * Note: cmdline already has a '\n'.
329      */
330     printf("driver: send-cmd time %s to taper: %s",
331            walltime_str(curclock()), cmdline);
332     fflush(stdout);
333     if (fullwrite(taper, cmdline, strlen(cmdline)) < 0) {
334         printf("writing taper command: %s\n", strerror(errno));
335         fflush(stdout);
336         amfree(cmdline);
337         return 0;
338     }
339     amfree(cmdline);
340     return 1;
341 }
342
343 int dumper_cmd(dumper, cmd, /* optional */ dp)
344 dumper_t *dumper;
345 cmd_t cmd;
346 disk_t *dp;
347 {
348     char *cmdline = NULL;
349     char number[NUM_STR_SIZE];
350     char numberport[NUM_STR_SIZE];
351     char *o;
352     int activehd=0;
353     assignedhd_t **h=NULL;
354     char *device;
355     char *features;
356
357     if(dp && sched(dp) && sched(dp)->holdp) {
358         h = sched(dp)->holdp;
359         activehd = sched(dp)->activehd;
360     }
361
362     if(dp && dp->device) {
363         device = dp->device;
364     }
365     else {
366         device = "NODEVICE";
367     }
368
369     switch(cmd) {
370     case PORT_DUMP:
371         if (dp != NULL) {
372             snprintf(number, sizeof(number), "%d", sched(dp)->level);
373             snprintf(numberport, sizeof(numberport), "%d", dumper->output_port);
374             features = am_feature_to_string(dp->host->features);
375             o = optionstr(dp, dp->host->features, NULL);
376             cmdline = vstralloc(cmdstr[cmd],
377                             " ", disk2serial(dp),
378                             " ", numberport,
379                             " ", dp->host->hostname,
380                             " ", features,
381                             " ", dp->name,
382                             " ", device,
383                             " ", number,
384                             " ", sched(dp)->dumpdate,
385                             " ", dp->program,
386                             " |", o,
387                             "\n", NULL);
388             amfree(features);
389             amfree(o);
390         } else {
391                 error("PORT-DUMP without disk pointer\n");
392                 /*NOTREACHED*/
393         }
394         break;
395     case QUIT:
396     case ABORT:
397         if( dp ) {
398             cmdline = vstralloc(cmdstr[cmd],
399                                 " ", sched(dp)->destname,
400                                 "\n", NULL );
401         } else {
402             cmdline = stralloc2(cmdstr[cmd], "\n");
403         }
404         break;
405     default:
406         error("Don't know how to send %s command to dumper", cmdstr[cmd]);
407     }
408     /*
409      * Note: cmdline already has a '\n'.
410      */
411     if(dumper->down) {
412         printf("driver: send-cmd time %s ignored to down dumper %s: %s",
413                walltime_str(curclock()), dumper->name, cmdline);
414     } else {
415         printf("driver: send-cmd time %s to %s: %s",
416                walltime_str(curclock()), dumper->name, cmdline);
417         fflush(stdout);
418         if (fullwrite(dumper->fd, cmdline, strlen(cmdline)) < 0) {
419             printf("writing %s command: %s\n", dumper->name, strerror(errno));
420             fflush(stdout);
421             amfree(cmdline);
422             return 0;
423         }
424     }
425     amfree(cmdline);
426     return 1;
427 }
428
429 int chunker_cmd(chunker, cmd, dp)
430 chunker_t *chunker;
431 cmd_t cmd;
432 disk_t *dp;
433 {
434     char *cmdline = NULL;
435     char number[NUM_STR_SIZE];
436     char chunksize[NUM_STR_SIZE];
437     char use[NUM_STR_SIZE];
438     char *o;
439     int activehd=0;
440     assignedhd_t **h=NULL;
441     char *features;
442
443     if(cmd != START && dp && sched(dp) && sched(dp)->holdp) {
444         h = sched(dp)->holdp;
445         activehd = sched(dp)->activehd;
446     }
447
448     switch(cmd) {
449     case START:
450         cmdline = vstralloc(cmdstr[cmd], " ", (char *)dp, "\n", NULL);
451         break;
452     case PORT_WRITE:
453         if (dp && h) {
454             holdalloc(h[activehd]->disk)->allocated_dumpers++;
455             snprintf(number, sizeof(number), "%d", sched(dp)->level);
456             snprintf(chunksize, sizeof(chunksize), "%ld", h[0]->disk->chunksize);
457             snprintf(use, sizeof(use), "%ld", h[0]->reserved );
458             features = am_feature_to_string(dp->host->features);
459             o = optionstr(dp, dp->host->features, NULL);
460             cmdline = vstralloc(cmdstr[cmd],
461                             " ", disk2serial(dp),
462                             " ", sched(dp)->destname,
463                             " ", dp->host->hostname,
464                             " ", features,
465                             " ", dp->name,
466                             " ", number,
467                             " ", sched(dp)->dumpdate,
468                             " ", chunksize,
469                             " ", dp->program,
470                             " ", use,
471                             " |", o,
472                             "\n", NULL);
473             amfree(features);
474             amfree(o);
475         } else {
476                 error("Write command without disk and holding disk.\n",
477                       cmdstr[cmd]);
478                 /*NOTREACHED*/
479         }
480         break;
481     case CONTINUE:
482         if( dp && h) {
483             holdalloc(h[activehd]->disk)->allocated_dumpers++;
484             snprintf(chunksize, sizeof(chunksize), "%ld", 
485                      h[activehd]->disk->chunksize );
486             snprintf(use, sizeof(use), "%ld", 
487                      h[activehd]->reserved - h[activehd]->used );
488             cmdline = vstralloc(cmdstr[cmd],
489                                 " ", disk2serial(dp),
490                                 " ", h[activehd]->destname,
491                                 " ", chunksize,
492                                 " ", use,
493                                 "\n", NULL );
494         } else {
495             cmdline = stralloc2(cmdstr[cmd], "\n");
496         }
497         break;
498     case QUIT:
499         cmdline = stralloc2(cmdstr[cmd], "\n");
500         break;
501     case DONE:
502     case FAILED:
503         if( dp) {
504             cmdline = vstralloc(cmdstr[cmd],
505                                 " ", disk2serial(dp),
506                                 "\n",  NULL);
507         } else {
508             cmdline = vstralloc(cmdstr[cmd], "\n");
509         }
510         break;
511     default:
512         error("Don't know how to send %s command to chunker", cmdstr[cmd]);
513     }
514     /*
515      * Note: cmdline already has a '\n'.
516      */
517     printf("driver: send-cmd time %s to %s: %s",
518            walltime_str(curclock()), chunker->name, cmdline);
519     fflush(stdout);
520     if (fullwrite(chunker->fd, cmdline, strlen(cmdline)) < 0) {
521         printf("writing %s command: %s\n", chunker->name, strerror(errno));
522         fflush(stdout);
523         amfree(cmdline);
524         return 0;
525     }
526     amfree(cmdline);
527     return 1;
528 }
529
530 #define MAX_SERIAL MAX_DUMPERS+1        /* one for the taper */
531
532 long generation = 1;
533
534 struct serial_s {
535     long gen;
536     disk_t *dp;
537 } stable[MAX_SERIAL];
538
539 disk_t *serial2disk(str)
540 char *str;
541 {
542     int rc, s;
543     long gen;
544
545     rc = sscanf(str, "%d-%ld", &s, &gen);
546     if(rc != 2) {
547         error("error [serial2disk \"%s\" parse error]", str);
548     } else if (s < 0 || s >= MAX_SERIAL) {
549         error("error [serial out of range 0..%d: %d]", MAX_SERIAL, s);
550     }
551     if(gen != stable[s].gen)
552         printf("driver: error time %s serial gen mismatch %s\n",
553                walltime_str(curclock()), str);
554     return stable[s].dp;
555 }
556
557 void free_serial(str)
558 char *str;
559 {
560     int rc, s;
561     long gen;
562
563     rc = sscanf(str, "%d-%ld", &s, &gen);
564     if(!(rc == 2 && s >= 0 && s < MAX_SERIAL)) {
565         /* nuke self to get core dump for Brett */
566         fprintf(stderr, "driver: free_serial: str \"%s\" rc %d s %d\n",
567                 str, rc, s);
568         fflush(stderr);
569         abort();
570     }
571
572     if(gen != stable[s].gen)
573         printf("driver: error time %s serial gen mismatch\n",
574                walltime_str(curclock()));
575     stable[s].gen = 0;
576     stable[s].dp = NULL;
577 }
578
579
580 void free_serial_dp(dp)
581 disk_t *dp;
582 {
583     int s;
584
585     for(s = 0; s < MAX_SERIAL; s++) {
586         if(stable[s].dp == dp) {
587             stable[s].gen = 0;
588             stable[s].dp = NULL;
589             return;
590         }
591     }
592
593     printf("driver: error time %s serial not found\n",
594            walltime_str(curclock()));
595 }
596
597
598 void check_unfree_serial()
599 {
600     int s;
601
602     /* find used serial number */
603     for(s = 0; s < MAX_SERIAL; s++) {
604         if(stable[s].gen != 0 || stable[s].dp != NULL) {
605             printf("driver: error time %s bug: serial in use: %02d-%05ld\n",
606                    walltime_str(curclock()), s, stable[s].gen);
607         }
608     }
609 }
610
611 char *disk2serial(dp)
612 disk_t *dp;
613 {
614     int s;
615     static char str[NUM_STR_SIZE];
616
617     for(s = 0; s < MAX_SERIAL; s++) {
618         if(stable[s].dp == dp) {
619             snprintf(str, sizeof(str), "%02d-%05ld", s, stable[s].gen);
620             return str;
621         }
622     }
623
624     /* find unused serial number */
625     for(s = 0; s < MAX_SERIAL; s++)
626         if(stable[s].gen == 0 && stable[s].dp == NULL)
627             break;
628     if(s >= MAX_SERIAL) {
629         printf("driver: error time %s bug: out of serial numbers\n",
630                walltime_str(curclock()));
631         s = 0;
632     }
633
634     stable[s].gen = generation++;
635     stable[s].dp = dp;
636
637     snprintf(str, sizeof(str), "%02d-%05ld", s, stable[s].gen);
638     return str;
639 }
640
641 void update_info_dumper(dp, origsize, dumpsize, dumptime)
642      disk_t *dp;
643      long origsize;
644      long dumpsize;
645      long dumptime;
646 {
647     int level, i;
648     info_t info;
649     stats_t *infp;
650     perf_t *perfp;
651     char *conf_infofile;
652
653     level = sched(dp)->level;
654
655     conf_infofile = getconf_str(CNF_INFOFILE);
656     if (*conf_infofile == '/') {
657         conf_infofile = stralloc(conf_infofile);
658     } else {
659         conf_infofile = stralloc2(config_dir, conf_infofile);
660     }
661     if (open_infofile(conf_infofile)) {
662         error("could not open info db \"%s\"", conf_infofile);
663     }
664     amfree(conf_infofile);
665
666     get_info(dp->host->hostname, dp->name, &info);
667
668     /* Clean up information about this and higher-level dumps.  This
669        assumes that update_info_dumper() is always run before
670        update_info_taper(). */
671     for (i = level; i < DUMP_LEVELS; ++i) {
672       infp = &info.inf[i];
673       infp->size = -1;
674       infp->csize = -1;
675       infp->secs = -1;
676       infp->date = (time_t)-1;
677       infp->label[0] = '\0';
678       infp->filenum = 0;
679     }
680
681     /* now store information about this dump */
682     infp = &info.inf[level];
683     infp->size = origsize;
684     infp->csize = dumpsize;
685     infp->secs = dumptime;
686     infp->date = sched(dp)->timestamp;
687
688     if(level == 0) perfp = &info.full;
689     else perfp = &info.incr;
690
691     /* Update the stats, but only if the new values are meaningful */
692     if(dp->compress != COMP_NONE && origsize > 0L) {
693         newperf(perfp->comp, dumpsize/(float)origsize);
694     }
695     if(dumptime > 0L) {
696         if(dumptime >= dumpsize)
697             newperf(perfp->rate, 1);
698         else
699             newperf(perfp->rate, dumpsize/dumptime);
700     }
701
702     if(getconf_int(CNF_RESERVE)<100) {
703         info.command = NO_COMMAND;
704     }
705
706     if(level == info.last_level)
707         info.consecutive_runs++;
708     else {
709         info.last_level = level;
710         info.consecutive_runs = 1;
711     }
712
713     if(origsize >=0 && dumpsize >=0) {
714         for(i=NB_HISTORY-1;i>0;i--) {
715             info.history[i] = info.history[i-1];
716         }
717
718         info.history[0].level = level;
719         info.history[0].size  = origsize;
720         info.history[0].csize = dumpsize;
721         info.history[0].date  = sched(dp)->timestamp;
722         info.history[0].secs  = dumptime;
723     }
724
725     if(put_info(dp->host->hostname, dp->name, &info))
726         error("infofile update failed (%s,%s)\n", dp->host->hostname, dp->name);
727
728     close_infofile();
729 }
730
731 void update_info_taper(dp, label, filenum, level)
732 disk_t *dp;
733 char *label;
734 int filenum;
735 int level;
736 {
737     info_t info;
738     stats_t *infp;
739     int rc;
740
741     rc = open_infofile(getconf_str(CNF_INFOFILE));
742     if(rc)
743         error("could not open infofile %s: %s (%d)", getconf_str(CNF_INFOFILE),
744               strerror(errno), rc);
745
746     get_info(dp->host->hostname, dp->name, &info);
747
748     infp = &info.inf[level];
749     /* XXX - should we record these two if no-record? */
750     strncpy(infp->label, label, sizeof(infp->label)-1);
751     infp->label[sizeof(infp->label)-1] = '\0';
752     infp->filenum = filenum;
753
754     info.command = NO_COMMAND;
755
756     if(put_info(dp->host->hostname, dp->name, &info))
757         error("infofile update failed (%s,%s)\n", dp->host->hostname, dp->name);
758
759     close_infofile();
760 }
761
762 /* Free an array of pointers to assignedhd_t after freeing the
763  * assignedhd_t themselves. The array must be NULL-terminated.
764  */
765 void free_assignedhd( ahd )
766 assignedhd_t **ahd;
767 {
768     int i;
769
770     if( !ahd ) { return; }
771
772     for( i = 0; ahd[i]; i++ ) {
773         amfree(ahd[i]->destname);
774         amfree(ahd[i]);
775     }
776     amfree(ahd);
777 }