Imported Upstream version 2.5.0
[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 2006/03/11 21:57:18 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(dp && sched(dp) && sched(dp)->holdp) {
444         h = sched(dp)->holdp;
445         activehd = sched(dp)->activehd;
446     }
447
448     switch(cmd) {
449     case PORT_WRITE:
450         if (dp && h) {
451             holdalloc(h[activehd]->disk)->allocated_dumpers++;
452             snprintf(number, sizeof(number), "%d", sched(dp)->level);
453             snprintf(chunksize, sizeof(chunksize), "%ld", h[0]->disk->chunksize);
454             snprintf(use, sizeof(use), "%ld", h[0]->reserved );
455             features = am_feature_to_string(dp->host->features);
456             o = optionstr(dp, dp->host->features, NULL);
457             cmdline = vstralloc(cmdstr[cmd],
458                             " ", disk2serial(dp),
459                             " ", sched(dp)->destname,
460                             " ", dp->host->hostname,
461                             " ", features,
462                             " ", dp->name,
463                             " ", number,
464                             " ", sched(dp)->dumpdate,
465                             " ", chunksize,
466                             " ", dp->program,
467                             " ", use,
468                             " |", o,
469                             "\n", NULL);
470             amfree(features);
471             amfree(o);
472         } else {
473                 error("Write command without disk and holding disk.\n",
474                       cmdstr[cmd]);
475                 /*NOTREACHED*/
476         }
477         break;
478     case CONTINUE:
479         if( dp && h) {
480             holdalloc(h[activehd]->disk)->allocated_dumpers++;
481             snprintf(chunksize, sizeof(chunksize), "%ld", 
482                      h[activehd]->disk->chunksize );
483             snprintf(use, sizeof(use), "%ld", 
484                      h[activehd]->reserved - h[activehd]->used );
485             cmdline = vstralloc(cmdstr[cmd],
486                                 " ", disk2serial(dp),
487                                 " ", h[activehd]->destname,
488                                 " ", chunksize,
489                                 " ", use,
490                                 "\n", NULL );
491         } else {
492             cmdline = stralloc2(cmdstr[cmd], "\n");
493         }
494         break;
495     case QUIT:
496         cmdline = stralloc2(cmdstr[cmd], "\n");
497         break;
498     case DONE:
499     case FAILED:
500         if( dp) {
501             cmdline = vstralloc(cmdstr[cmd],
502                                 " ", disk2serial(dp),
503                                 "\n",  NULL);
504         } else {
505             cmdline = vstralloc(cmdstr[cmd], "\n");
506         }
507         break;
508     default:
509         error("Don't know how to send %s command to chunker", cmdstr[cmd]);
510     }
511     /*
512      * Note: cmdline already has a '\n'.
513      */
514     printf("driver: send-cmd time %s to %s: %s",
515            walltime_str(curclock()), chunker->name, cmdline);
516     fflush(stdout);
517     if (fullwrite(chunker->fd, cmdline, strlen(cmdline)) < 0) {
518         printf("writing %s command: %s\n", chunker->name, strerror(errno));
519         fflush(stdout);
520         amfree(cmdline);
521         return 0;
522     }
523     amfree(cmdline);
524     return 1;
525 }
526
527 #define MAX_SERIAL MAX_DUMPERS+1        /* one for the taper */
528
529 long generation = 1;
530
531 struct serial_s {
532     long gen;
533     disk_t *dp;
534 } stable[MAX_SERIAL];
535
536 disk_t *serial2disk(str)
537 char *str;
538 {
539     int rc, s;
540     long gen;
541
542     rc = sscanf(str, "%d-%ld", &s, &gen);
543     if(rc != 2) {
544         error("error [serial2disk \"%s\" parse error]", str);
545     } else if (s < 0 || s >= MAX_SERIAL) {
546         error("error [serial out of range 0..%d: %d]", MAX_SERIAL, s);
547     }
548     if(gen != stable[s].gen)
549         printf("driver: error time %s serial gen mismatch %s\n",
550                walltime_str(curclock()), str);
551     return stable[s].dp;
552 }
553
554 void free_serial(str)
555 char *str;
556 {
557     int rc, s;
558     long gen;
559
560     rc = sscanf(str, "%d-%ld", &s, &gen);
561     if(!(rc == 2 && s >= 0 && s < MAX_SERIAL)) {
562         /* nuke self to get core dump for Brett */
563         fprintf(stderr, "driver: free_serial: str \"%s\" rc %d s %d\n",
564                 str, rc, s);
565         fflush(stderr);
566         abort();
567     }
568
569     if(gen != stable[s].gen)
570         printf("driver: error time %s serial gen mismatch\n",
571                walltime_str(curclock()));
572     stable[s].gen = 0;
573     stable[s].dp = NULL;
574 }
575
576
577 void free_serial_dp(dp)
578 disk_t *dp;
579 {
580     int s;
581
582     for(s = 0; s < MAX_SERIAL; s++) {
583         if(stable[s].dp == dp) {
584             stable[s].gen = 0;
585             stable[s].dp = NULL;
586             return;
587         }
588     }
589
590     printf("driver: error time %s serial not found\n",
591            walltime_str(curclock()));
592 }
593
594
595 void check_unfree_serial()
596 {
597     int s;
598
599     /* find used serial number */
600     for(s = 0; s < MAX_SERIAL; s++) {
601         if(stable[s].gen != 0 || stable[s].dp != NULL) {
602             printf("driver: error time %s bug: serial in use: %02d-%05ld\n",
603                    walltime_str(curclock()), s, stable[s].gen);
604         }
605     }
606 }
607
608 char *disk2serial(dp)
609 disk_t *dp;
610 {
611     int s;
612     static char str[NUM_STR_SIZE];
613
614     for(s = 0; s < MAX_SERIAL; s++) {
615         if(stable[s].dp == dp) {
616             snprintf(str, sizeof(str), "%02d-%05ld", s, stable[s].gen);
617             return str;
618         }
619     }
620
621     /* find unused serial number */
622     for(s = 0; s < MAX_SERIAL; s++)
623         if(stable[s].gen == 0 && stable[s].dp == NULL)
624             break;
625     if(s >= MAX_SERIAL) {
626         printf("driver: error time %s bug: out of serial numbers\n",
627                walltime_str(curclock()));
628         s = 0;
629     }
630
631     stable[s].gen = generation++;
632     stable[s].dp = dp;
633
634     snprintf(str, sizeof(str), "%02d-%05ld", s, stable[s].gen);
635     return str;
636 }
637
638 void update_info_dumper(dp, origsize, dumpsize, dumptime)
639      disk_t *dp;
640      long origsize;
641      long dumpsize;
642      long dumptime;
643 {
644     int level, i;
645     info_t info;
646     stats_t *infp;
647     perf_t *perfp;
648     char *conf_infofile;
649
650     level = sched(dp)->level;
651
652     conf_infofile = getconf_str(CNF_INFOFILE);
653     if (*conf_infofile == '/') {
654         conf_infofile = stralloc(conf_infofile);
655     } else {
656         conf_infofile = stralloc2(config_dir, conf_infofile);
657     }
658     if (open_infofile(conf_infofile)) {
659         error("could not open info db \"%s\"", conf_infofile);
660     }
661     amfree(conf_infofile);
662
663     get_info(dp->host->hostname, dp->name, &info);
664
665     /* Clean up information about this and higher-level dumps.  This
666        assumes that update_info_dumper() is always run before
667        update_info_taper(). */
668     for (i = level; i < DUMP_LEVELS; ++i) {
669       infp = &info.inf[i];
670       infp->size = -1;
671       infp->csize = -1;
672       infp->secs = -1;
673       infp->date = (time_t)-1;
674       infp->label[0] = '\0';
675       infp->filenum = 0;
676     }
677
678     /* now store information about this dump */
679     infp = &info.inf[level];
680     infp->size = origsize;
681     infp->csize = dumpsize;
682     infp->secs = dumptime;
683     infp->date = sched(dp)->timestamp;
684
685     if(level == 0) perfp = &info.full;
686     else perfp = &info.incr;
687
688     /* Update the stats, but only if the new values are meaningful */
689     if(dp->compress != COMP_NONE && origsize > 0L) {
690         newperf(perfp->comp, dumpsize/(float)origsize);
691     }
692     if(dumptime > 0L) {
693         if(dumptime >= dumpsize)
694             newperf(perfp->rate, 1);
695         else
696             newperf(perfp->rate, dumpsize/dumptime);
697     }
698
699     if(getconf_int(CNF_RESERVE)<100) {
700         info.command = NO_COMMAND;
701     }
702
703     if(level == info.last_level)
704         info.consecutive_runs++;
705     else {
706         info.last_level = level;
707         info.consecutive_runs = 1;
708     }
709
710     if(origsize >=0 && dumpsize >=0) {
711         for(i=NB_HISTORY-1;i>0;i--) {
712             info.history[i] = info.history[i-1];
713         }
714
715         info.history[0].level = level;
716         info.history[0].size  = origsize;
717         info.history[0].csize = dumpsize;
718         info.history[0].date  = sched(dp)->timestamp;
719         info.history[0].secs  = dumptime;
720     }
721
722     if(put_info(dp->host->hostname, dp->name, &info))
723         error("infofile update failed (%s,%s)\n", dp->host->hostname, dp->name);
724
725     close_infofile();
726 }
727
728 void update_info_taper(dp, label, filenum, level)
729 disk_t *dp;
730 char *label;
731 int filenum;
732 int level;
733 {
734     info_t info;
735     stats_t *infp;
736     int rc;
737
738     rc = open_infofile(getconf_str(CNF_INFOFILE));
739     if(rc)
740         error("could not open infofile %s: %s (%d)", getconf_str(CNF_INFOFILE),
741               strerror(errno), rc);
742
743     get_info(dp->host->hostname, dp->name, &info);
744
745     infp = &info.inf[level];
746     /* XXX - should we record these two if no-record? */
747     strncpy(infp->label, label, sizeof(infp->label)-1);
748     infp->label[sizeof(infp->label)-1] = '\0';
749     infp->filenum = filenum;
750
751     info.command = NO_COMMAND;
752
753     if(put_info(dp->host->hostname, dp->name, &info))
754         error("infofile update failed (%s,%s)\n", dp->host->hostname, dp->name);
755
756     close_infofile();
757 }
758
759 /* Free an array of pointers to assignedhd_t after freeing the
760  * assignedhd_t themselves. The array must be NULL-terminated.
761  */
762 void free_assignedhd( ahd )
763 assignedhd_t **ahd;
764 {
765     int i;
766
767     if( !ahd ) { return; }
768
769     for( i = 0; ahd[i]; i++ ) {
770         amfree(ahd[i]->destname);
771         amfree(ahd[i]);
772     }
773     amfree(ahd);
774 }