e824e1ef286555960ca682cfe184f06cb63392e9
[debian/amanda] / client-src / sendbackup.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1999 University of Maryland at College Park
4  * All Rights Reserved.
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation, and that the name of U.M. not be used in advertising or
11  * publicity pertaining to distribution of the software without specific,
12  * written prior permission.  U.M. makes no representations about the
13  * suitability of this software for any purpose.  It is provided "as is"
14  * without express or implied warranty.
15  *
16  * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18  * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  *
23  * Authors: the Amanda Development Team.  Its members are listed in a
24  * file named AUTHORS, in the root directory of this distribution.
25  */
26 /* 
27  * $Id: sendbackup.c,v 1.77 2006/03/09 16:51:41 martinea Exp $
28  *
29  * common code for the sendbackup-* programs.
30  */
31
32 #include "amanda.h"
33 #include "sendbackup.h"
34 #include "clock.h"
35 #include "pipespawn.h"
36 #include "amfeatures.h"
37 #include "amandad.h"
38 #include "arglist.h"
39 #include "getfsent.h"
40 #include "version.h"
41
42 #define TIMEOUT 30
43
44 int comppid = -1;
45 int dumppid = -1;
46 int tarpid = -1;
47 int encpid = -1;
48 int indexpid = -1;
49 char *errorstr = NULL;
50
51 int datafd;
52 int mesgfd;
53 int indexfd;
54
55 option_t *options;
56
57 long dump_size = -1;
58
59 backup_program_t *program = NULL;
60
61 static am_feature_t *our_features = NULL;
62 static char *our_feature_string = NULL;
63 static g_option_t *g_options = NULL;
64
65 /* local functions */
66 int main P((int argc, char **argv));
67 char *optionstr P((option_t *options));
68 char *childstr P((int pid));
69 int check_status P((int pid, amwait_t w));
70
71 int pipefork P((void (*func) P((void)), char *fname, int *stdinfd,
72                 int stdoutfd, int stderrfd));
73 void parse_backup_messages P((int mesgin));
74 static void process_dumpline P((char *str));
75 static void save_fd P((int *, int));
76
77 char *optionstr(options)
78 option_t *options;
79 {
80     static char *optstr = NULL;
81     char *compress_opt;
82     char *encrypt_opt;
83     char *decrypt_opt;
84     char *record_opt = "";
85     char *index_opt = "";
86     char *auth_opt;
87     char *exclude_file_opt;
88     char *exclude_list_opt;
89     char *exc = NULL;
90     sle_t *excl;
91
92     if(options->compress == COMPR_BEST)
93         compress_opt = stralloc("compress-best;");
94     else if(options->compress == COMPR_FAST)
95         compress_opt = stralloc("compress-fast;");
96     else if(options->compress == COMPR_SERVER_BEST)
97         compress_opt = stralloc("srvcomp-best;");
98     else if(options->compress == COMPR_SERVER_FAST)
99         compress_opt = stralloc("srvcomp-fast;");
100     else if(options->compress == COMPR_SERVER_CUST)
101         compress_opt = vstralloc("srvcomp-cust=", options->srvcompprog, ";", NULL);
102     else if(options->compress == COMPR_CUST)
103         compress_opt = vstralloc("comp-cust=", options->clntcompprog, ";", NULL);
104     else
105         compress_opt = stralloc("");
106     
107     if(options->encrypt == ENCRYPT_CUST) {
108       encrypt_opt = vstralloc("encrypt-cust=", options->clnt_encrypt, ";", NULL);
109       if (options->clnt_decrypt_opt)
110         decrypt_opt = vstralloc("client-decrypt-option=", options->clnt_decrypt_opt, ";", NULL);
111       else
112         decrypt_opt = stralloc("");
113     }
114     else if(options->encrypt == ENCRYPT_SERV_CUST) {
115       encrypt_opt = vstralloc("encrypt-serv-cust=", options->srv_encrypt, ";", NULL);
116       if(options->srv_decrypt_opt)
117         decrypt_opt = vstralloc("server-decrypt-option=", options->srv_decrypt_opt, ";", NULL);
118       else
119         decrypt_opt = stralloc("");
120     }
121     else {
122         encrypt_opt = stralloc("");
123         decrypt_opt = stralloc("");
124     }
125
126     if(options->no_record) record_opt = "no-record;";
127     if(options->auth) auth_opt = vstralloc("auth=", options->auth, ";", NULL);
128         else auth_opt = stralloc("");
129     if(options->createindex) index_opt = "index;";
130
131     exclude_file_opt = stralloc("");
132     if(options->exclude_file) {
133         for(excl = options->exclude_file->first; excl != NULL; excl=excl->next){
134             exc = newvstralloc(exc, "exclude-file=", excl->name, ";", NULL);
135             strappend(exclude_file_opt, exc);
136         }
137     }
138     exclude_list_opt = stralloc("");
139     if(options->exclude_list) {
140         for(excl = options->exclude_list->first; excl != NULL; excl=excl->next){
141             exc = newvstralloc(exc, "exclude-list=", excl->name, ";", NULL);
142             strappend(exclude_list_opt, exc);
143         }
144     }
145     optstr = newvstralloc(optstr,
146                           compress_opt,
147                           encrypt_opt,
148                           decrypt_opt,
149                           record_opt,
150                           index_opt,
151                           auth_opt,
152                           exclude_file_opt,
153                           exclude_list_opt,
154                           NULL);
155     amfree(compress_opt);
156     amfree(encrypt_opt);
157     amfree(decrypt_opt);
158     amfree(auth_opt);
159     amfree(exclude_file_opt);
160     amfree(exclude_list_opt);
161     return optstr;
162 }
163
164
165 int main(argc, argv)
166 int argc;
167 char **argv;
168 {
169     int interactive = 0;
170     int level, mesgpipe[2];
171     char *prog, *disk, *amdevice, *dumpdate, *stroptions;
172     char *line = NULL;
173     char *err_extra = NULL;
174     char *s;
175     int i;
176     int ch;
177     unsigned long malloc_hist_1, malloc_size_1;
178     unsigned long malloc_hist_2, malloc_size_2;
179
180     /* initialize */
181
182     safe_fd(DATA_FD_OFFSET, DATA_FD_COUNT);
183     safe_cd();
184
185     set_pname("sendbackup");
186
187     /* Don't die when child closes pipe */
188     signal(SIGPIPE, SIG_IGN);
189
190     malloc_size_1 = malloc_inuse(&malloc_hist_1);
191
192     interactive = (argc > 1 && strcmp(argv[1],"-t") == 0);
193     erroutput_type = (ERR_INTERACTIVE|ERR_SYSLOG);
194     dbopen();
195     startclock();
196     dbprintf(("%s: version %s\n", get_pname(), version()));
197
198     our_features = am_init_feature_set();
199     our_feature_string = am_feature_to_string(our_features);
200
201     if(interactive) {
202         /*
203          * In interactive (debug) mode, the backup data is sent to
204          * /dev/null and none of the network connections back to driver
205          * programs on the tape host are set up.  The index service is
206          * run and goes to stdout.
207          */
208         fprintf(stderr, "%s: running in interactive test mode\n", get_pname());
209         fflush(stderr);
210     }
211
212     prog = NULL;
213     disk = NULL;
214     amdevice = NULL;
215     dumpdate = NULL;
216     stroptions = NULL;
217
218     for(; (line = agets(stdin)) != NULL; free(line)) {
219         if(interactive) {
220             fprintf(stderr, "%s> ", get_pname());
221             fflush(stderr);
222         }
223 #define sc "OPTIONS "
224         if(strncmp(line, sc, sizeof(sc)-1) == 0) {
225 #undef sc
226             g_options = parse_g_options(line+8, 1);
227             if(!g_options->hostname) {
228                 g_options->hostname = alloc(MAX_HOSTNAME_LENGTH+1);
229                 gethostname(g_options->hostname, MAX_HOSTNAME_LENGTH);
230                 g_options->hostname[MAX_HOSTNAME_LENGTH] = '\0';
231             }
232             continue;
233         }
234
235         if (prog != NULL) {
236             err_extra = "multiple requests";
237             goto err;
238         }
239
240         s = line;
241         ch = *s++;
242
243         skip_whitespace(s, ch);                 /* find the program name */
244         if(ch == '\0') {
245             err_extra = "no program name";
246             goto err;                           /* no program name */
247         }
248         prog = s - 1;
249         skip_non_whitespace(s, ch);
250         s[-1] = '\0';
251         prog = stralloc(prog);
252
253         skip_whitespace(s, ch);                 /* find the disk name */
254         if(ch == '\0') {
255             err_extra = "no disk name";
256             goto err;                           /* no disk name */
257         }
258         amfree(disk);
259         disk = s - 1;
260         skip_non_whitespace(s, ch);
261         s[-1] = '\0';
262         disk = stralloc(disk);
263
264         skip_whitespace(s, ch);                 /* find the device or level */
265         if (ch == '\0') {
266             err_extra = "bad level";
267             goto err;
268         }
269
270         if(!isdigit((int)s[-1])) {
271             amfree(amdevice);
272             amdevice = s - 1;
273             skip_non_whitespace(s, ch);
274             s[-1] = '\0';
275             amdevice = stralloc(amdevice);
276             skip_whitespace(s, ch);             /* find level number */
277         }
278         else {
279             amdevice = stralloc(disk);
280         }
281
282                                                 /* find the level number */
283         if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
284             err_extra = "bad level";
285             goto err;                           /* bad level */
286         }
287         skip_integer(s, ch);
288
289         skip_whitespace(s, ch);                 /* find the dump date */
290         if(ch == '\0') {
291             err_extra = "no dumpdate";
292             goto err;                           /* no dumpdate */
293         }
294         amfree(dumpdate);
295         dumpdate = s - 1;
296         skip_non_whitespace(s, ch);
297         s[-1] = '\0';
298         dumpdate = stralloc(dumpdate);
299
300         skip_whitespace(s, ch);                 /* find the options keyword */
301         if(ch == '\0') {
302             err_extra = "no options";
303             goto err;                           /* no options */
304         }
305 #define sc "OPTIONS "
306         if(strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
307             err_extra = "no OPTIONS keyword";
308             goto err;                           /* no options */
309         }
310         s += sizeof(sc)-1;
311         ch = s[-1];
312 #undef sc
313         skip_whitespace(s, ch);                 /* find the options string */
314         if(ch == '\0') {
315             err_extra = "bad options string";
316             goto err;                           /* no options */
317         }
318         amfree(stroptions);
319         stroptions = stralloc(s - 1);
320     }
321     amfree(line);
322
323     dbprintf(("  parsed request as: program `%s'\n", prog));
324     dbprintf(("                     disk `%s'\n", disk));
325     dbprintf(("                     device `%s'\n", amdevice));
326     dbprintf(("                     level %d\n", level));
327     dbprintf(("                     since %s\n", dumpdate));
328     dbprintf(("                     options `%s'\n", stroptions));
329
330     for(i = 0; programs[i]; i++) {
331         if (strcmp(programs[i]->name, prog) == 0) {
332             break;
333         }
334     }
335     if (programs[i] == NULL) {
336         error("ERROR [%s: unknown program %s]", get_pname(), prog);
337     }
338     program = programs[i];
339
340     options = parse_options(stroptions, disk, amdevice, g_options->features, 0);
341
342     if(!interactive) {
343         datafd = DATA_FD_OFFSET + 0;
344         mesgfd = DATA_FD_OFFSET + 1;
345         indexfd = DATA_FD_OFFSET + 2;
346     }
347     if (!options->createindex)
348         indexfd = -1;
349
350     printf("CONNECT DATA %d MESG %d INDEX %d\n",
351            datafd, mesgfd, indexfd);
352     printf("OPTIONS ");
353     if(am_has_feature(g_options->features, fe_rep_options_features)) {
354         printf("features=%s;", our_feature_string);
355     }
356     if(am_has_feature(g_options->features, fe_rep_options_hostname)) {
357         printf("hostname=%s;", g_options->hostname);
358     }
359     if(am_has_feature(g_options->features, fe_rep_options_sendbackup_options)) {
360         printf("%s", optionstr(options));
361     }
362     printf("\n");
363     fflush(stdout);
364     if (freopen("/dev/null", "w", stdout) == NULL) {
365         dbprintf(("%s: error redirecting stdout to /dev/null: %s\n",
366             debug_prefix_time(NULL), mesgfd, strerror(errno)));
367         exit(1);
368     }
369
370     if(interactive) {
371       if((datafd = open("/dev/null", O_RDWR)) < 0) {
372         s = strerror(errno);
373         error("ERROR [%s: open of /dev/null for debug data stream: %s]\n",
374                   get_pname(), s);
375       }
376       mesgfd = 2;
377       indexfd = 1;
378     }
379
380     if(!interactive) {
381       if(datafd == -1 || mesgfd == -1 || (options->createindex && indexfd == -1)) {
382         dbclose();
383         exit(1);
384       }
385     }
386
387     if(!interactive) {
388       /* redirect stderr */
389       if(dup2(mesgfd, 2) == -1) {
390           dbprintf(("%s: error redirecting stderr to fd %d: %s\n",
391               debug_prefix_time(NULL), mesgfd, strerror(errno)));
392           dbclose();
393           exit(1);
394       }
395     }
396
397     if(pipe(mesgpipe) == -1) {
398       error("error [opening mesg pipe: %s]", strerror(errno));
399     }
400
401     program->start_backup(g_options->hostname, disk, amdevice, level, dumpdate, datafd, mesgpipe[1],
402                           indexfd);
403     dbprintf(("%s: started backup\n", debug_prefix_time(NULL)));
404     parse_backup_messages(mesgpipe[0]);
405     dbprintf(("%s: parsed backup messages\n", debug_prefix_time(NULL)));
406
407     amfree(prog);
408     amfree(disk);
409     amfree(amdevice);
410     amfree(dumpdate);
411     amfree(stroptions);
412     amfree(our_feature_string);
413     am_release_feature_set(our_features);
414     our_features = NULL;
415     am_release_feature_set(g_options->features);
416     g_options->features = NULL;
417     amfree(g_options->hostname);
418     amfree(g_options->str);
419     amfree(g_options);
420
421     dbclose();
422
423     malloc_size_2 = malloc_inuse(&malloc_hist_2);
424
425     if(malloc_size_1 != malloc_size_2) {
426         malloc_list(fileno(stderr), malloc_hist_1, malloc_hist_2);
427     }
428
429     return 0;
430
431  err:
432     printf("FORMAT ERROR IN REQUEST PACKET\n");
433     dbprintf(("%s: REQ packet is bogus%s%s\n",
434               debug_prefix_time(NULL),
435               err_extra ? ": " : "",
436               err_extra ? err_extra : ""));
437     dbclose();
438     return 1;
439 }
440
441 char *childstr(pid)
442 int pid;
443 /*
444  * Returns a string for a child process.  Checks the saved dump and
445  * compress pids to see which it is.
446  */
447 {
448     if(pid == dumppid) return program->backup_name;
449     if(pid == comppid) return "compress";
450     if(pid == encpid) return "encrypt";
451     if(pid == indexpid) return "index";
452     return "unknown";
453 }
454
455
456 int check_status(pid, w)
457 int pid;
458 amwait_t w;
459 /*
460  * Determine if the child return status really indicates an error.
461  * If so, add the error message to the error string; more than one
462  * child can have an error.
463  */
464 {
465     char *thiserr = NULL;
466     char *str;
467     int ret, sig, rc;
468     char number[NUM_STR_SIZE];
469
470     str = childstr(pid);
471
472     if(WIFSIGNALED(w)) {
473         ret = 0;
474         rc = sig = WTERMSIG(w);
475     } else {
476         sig = 0;
477         rc = ret = WEXITSTATUS(w);
478     }
479
480     if(pid == indexpid) {
481         /*
482          * Treat an index failure (other than signal) as a "STRANGE"
483          * rather than an error so the dump goes ahead and gets processed
484          * but the failure is noted.
485          */
486         if(ret != 0) {
487             fprintf(stderr, "? %s returned %d\n", str, ret);
488             rc = 0;
489         }
490     }
491
492 #ifndef HAVE_GZIP
493     if(pid == comppid) {
494         /*
495          * compress returns 2 sometimes, but it is ok.
496          */
497         if(ret == 2) {
498             rc = 0;
499         }
500     }
501 #endif
502
503 #ifdef DUMP_RETURNS_1
504     if(pid == dumppid && tarpid == -1) {
505         /*
506          * Ultrix dump returns 1 sometimes, but it is ok.
507          */
508         if(ret == 1) {
509             rc = 0;
510         }
511     }
512 #endif
513
514 #ifdef IGNORE_TAR_ERRORS
515     if(pid == tarpid) {
516         /*
517          * tar bitches about active filesystems, but we do not care.
518          */
519         if(ret == 2) {
520             rc = 0;
521         }
522     }
523 #endif
524
525     if(rc == 0) {
526         return 0;                               /* normal exit */
527     }
528
529     if(ret == 0) {
530         snprintf(number, sizeof(number), "%d", sig);
531         thiserr = vstralloc(str, " got signal ", number, NULL);
532     } else {
533         snprintf(number, sizeof(number), "%d", ret);
534         thiserr = vstralloc(str, " returned ", number, NULL);
535     }
536
537     if(errorstr) {
538         strappend(errorstr, ", ");
539         strappend(errorstr, thiserr);
540         amfree(thiserr);
541     } else {
542         errorstr = thiserr;
543         thiserr = NULL;
544     }
545     return 1;
546 }
547
548
549 /* Send header info to the message file.
550 */
551 void info_tapeheader()
552 {
553     fprintf(stderr, "%s: info BACKUP=%s\n", get_pname(), program->backup_name);
554
555     fprintf(stderr, "%s: info RECOVER_CMD=", get_pname());
556     if (options->compress == COMPR_FAST || options->compress == COMPR_BEST)
557         fprintf(stderr, "%s %s |", UNCOMPRESS_PATH,
558 #ifdef UNCOMPRESS_OPT
559                 UNCOMPRESS_OPT
560 #else
561                 ""
562 #endif
563                 );
564
565     fprintf(stderr, "%s -f... -\n", program->restore_name);
566
567     if (options->compress == COMPR_FAST || options->compress == COMPR_BEST)
568         fprintf(stderr, "%s: info COMPRESS_SUFFIX=%s\n",
569                         get_pname(), COMPRESS_SUFFIX);
570
571     fprintf(stderr, "%s: info end\n", get_pname());
572 }
573
574 int pipefork(func, fname, stdinfd, stdoutfd, stderrfd)
575 void (*func) P((void));
576 char *fname;
577 int *stdinfd;
578 int stdoutfd, stderrfd;
579 {
580     int pid, inpipe[2];
581
582     dbprintf(("%s: forking function %s in pipeline\n",
583         debug_prefix_time(NULL), fname));
584
585     if(pipe(inpipe) == -1) {
586         error("error [open pipe to %s: %s]", fname, strerror(errno));
587     }
588
589     switch(pid = fork()) {
590     case -1:
591         error("error [fork %s: %s]", fname, strerror(errno));
592     default:    /* parent process */
593         aclose(inpipe[0]);      /* close input side of pipe */
594         *stdinfd = inpipe[1];
595         break;
596     case 0:             /* child process */
597         aclose(inpipe[1]);      /* close output side of pipe */
598
599         if(dup2(inpipe[0], 0) == -1) {
600             error("error [fork %s: dup2(%d, in): %s]",
601                   fname, inpipe[0], strerror(errno));
602         }
603         if(dup2(stdoutfd, 1) == -1) {
604             error("error [fork %s: dup2(%d, out): %s]",
605                   fname, stdoutfd, strerror(errno));
606         }
607         if(dup2(stderrfd, 2) == -1) {
608             error("error [fork %s: dup2(%d, err): %s]",
609                   fname, stderrfd, strerror(errno));
610         }
611
612         func();
613         exit(0);
614         /* NOTREACHED */
615     }
616     return pid;
617 }
618
619 void parse_backup_messages(mesgin)
620 int mesgin;
621 {
622     int goterror, wpid;
623     amwait_t retstat;
624     char *line;
625
626     goterror = 0;
627     amfree(errorstr);
628
629     for(; (line = areads(mesgin)) != NULL; free(line)) {
630         process_dumpline(line);
631     }
632
633     if(errno) {
634         error("error [read mesg pipe: %s]", strerror(errno));
635     }
636
637     while((wpid = wait(&retstat)) != -1) {
638         if(check_status(wpid, retstat)) goterror = 1;
639     }
640
641     if(errorstr) {
642         error("error [%s]", errorstr);
643     } else if(dump_size == -1) {
644         error("error [no backup size line]");
645     }
646
647     program->end_backup(goterror);
648
649     fprintf(stderr, "%s: size %ld\n", get_pname(), dump_size);
650     fprintf(stderr, "%s: end\n", get_pname());
651 }
652
653
654 double first_num P((char *str));
655
656 double first_num(str)
657 char *str;
658 /*
659  * Returns the value of the first integer in a string.
660  */
661 {
662     char *num;
663     int ch;
664     double d;
665
666     ch = *str++;
667     while(ch && !isdigit(ch)) ch = *str++;
668     num = str - 1;
669     while(isdigit(ch) || ch == '.') ch = *str++;
670     str[-1] = '\0';
671     d = atof(num);
672     str[-1] = ch;
673     return d;
674 }
675
676
677 static void process_dumpline(str)
678 char *str;
679 {
680     regex_t *rp;
681     char *type;
682     char startchr;
683
684     for(rp = program->re_table; rp->regex != NULL; rp++) {
685         if(match(rp->regex, str)) {
686             break;
687         }
688     }
689     if(rp->typ == DMP_SIZE) {
690         dump_size = (long)((first_num(str) * rp->scale + 1023.0)/1024.0);
691     }
692     switch(rp->typ) {
693     case DMP_NORMAL:
694         type = "normal";
695         startchr = '|';
696         break;
697     case DMP_STRANGE:
698         type = "strange";
699         startchr = '?';
700         break;
701     case DMP_SIZE:
702         type = "size";
703         startchr = '|';
704         break;
705     case DMP_ERROR:
706         type = "error";
707         startchr = '?';
708         break;
709     default:
710         /*
711          * Should never get here.
712          */
713         type = "unknown";
714         startchr = '!';
715         break;
716     }
717     dbprintf(("%s: %3d: %7s(%c): %s\n",
718               debug_prefix_time(NULL),
719               rp->srcline,
720               type,
721               startchr,
722               str));
723     fprintf(stderr, "%c %s\n", startchr, str);
724 }
725
726
727 /* start_index.  Creates an index file from the output of dump/tar.
728    It arranges that input is the fd to be written by the dump process.
729    If createindex is not enabled, it does nothing.  If it is not, a
730    new process will be created that tees input both to a pipe whose
731    read fd is dup2'ed input and to a program that outputs an index
732    file to `index'.
733
734    make sure that the chat from restore doesn't go to stderr cause
735    this goes back to amanda which doesn't expect to see it
736    (2>/dev/null should do it)
737
738    Originally by Alan M. McIvor, 13 April 1996
739
740    Adapted by Alexandre Oliva, 1 May 1997
741
742    This program owes a lot to tee.c from GNU sh-utils and dumptee.c
743    from the DeeJay backup package.
744
745 */
746
747 static volatile int index_finished = 0;
748
749 static void save_fd(fd, min)
750 int *fd, min;
751 {
752   int origfd = *fd;
753
754   while (*fd >= 0 && *fd < min) {
755     int newfd = dup(*fd);
756     if (newfd == -1)
757       dbprintf(("%s: unable to save file descriptor [%s]\n",
758         debug_prefix_time(NULL), strerror(errno)));
759     *fd = newfd;
760   }
761   if (origfd != *fd)
762     dbprintf(("%s: dupped file descriptor %i to %i\n",
763       debug_prefix_time(NULL), origfd, *fd));
764 }
765
766 void start_index(createindex, input, mesg, index, cmd)
767 int createindex, input, mesg, index;
768 char *cmd;
769 {
770   int pipefd[2];
771   FILE *pipe_fp;
772   int exitcode;
773
774   if (!createindex)
775     return;
776
777   if (pipe(pipefd) != 0) {
778     error("creating index pipe: %s", strerror(errno));
779   }
780
781   switch(indexpid = fork()) {
782   case -1:
783     error("forking index tee process: %s", strerror(errno));
784
785   default:
786     aclose(pipefd[0]);
787     if (dup2(pipefd[1], input) == -1) {
788       error("dup'ping index tee output: %s", strerror(errno));
789     }
790     aclose(pipefd[1]);
791     return;
792
793   case 0:
794     break;
795   }
796
797   /* now in a child process */
798   save_fd(&pipefd[0], 4);
799   save_fd(&index, 4);
800   save_fd(&mesg, 4);
801   save_fd(&input, 4);
802   dup2(pipefd[0], 0);
803   dup2(index, 1);
804   dup2(mesg, 2);
805   dup2(input, 3);
806   for(index = 4; index < FD_SETSIZE; index++) {
807     if (index != dbfd()) {
808       close(index);
809     }
810   }
811
812   if ((pipe_fp = popen(cmd, "w")) == NULL) {
813     error("couldn't start index creator [%s]", strerror(errno));
814   }
815
816   dbprintf(("%s: started index creator: \"%s\"\n",
817     debug_prefix_time(NULL), cmd));
818   while(1) {
819     char buffer[BUFSIZ], *ptr;
820     int bytes_read;
821     int bytes_written;
822     int just_written;
823
824     do {
825         bytes_read = read(0, buffer, sizeof(buffer));
826     } while ((bytes_read < 0) && ((errno == EINTR) || (errno == EAGAIN)));
827
828     if (bytes_read < 0) {
829       error("index tee cannot read [%s]", strerror(errno));
830     }
831
832     if (bytes_read == 0)
833       break; /* finished */
834
835     /* write the stuff to the subprocess */
836     ptr = buffer;
837     bytes_written = 0;
838     just_written = fullwrite(fileno(pipe_fp), ptr, bytes_read);
839     if (just_written < 0) {
840         /* the signal handler may have assigned to index_finished
841          * just as we waited for write() to complete.
842          */
843         if (errno != EPIPE) {
844             dbprintf(("%s: index tee cannot write to index creator [%s]\n",
845                             debug_prefix_time(NULL), strerror(errno)));
846         }
847     } else {
848         bytes_written += just_written;
849         ptr += just_written;
850     }
851
852     /* write the stuff to stdout, ensuring none lost when interrupt
853        occurs */
854     ptr = buffer;
855     bytes_written = 0;
856     just_written = fullwrite(3, ptr, bytes_read);
857     if (just_written < 0) {
858         error("index tee cannot write [%s]", strerror(errno));
859         /* NOTREACHED */
860     } else {
861         bytes_written += just_written;
862         ptr += just_written;
863     }
864   }
865
866   aclose(pipefd[1]);
867
868   /* finished */
869   /* check the exit code of the pipe and moan if not 0 */
870   if ((exitcode = pclose(pipe_fp)) != 0) {
871     dbprintf(("%s: index pipe returned %d\n",
872       debug_prefix_time(NULL), exitcode));
873   } else {
874     dbprintf(("%s: index created successfully\n", debug_prefix_time(NULL)));
875   }
876   pipe_fp = NULL;
877
878   exit(exitcode);
879 }
880
881 extern backup_program_t dump_program, gnutar_program;
882
883 backup_program_t *programs[] = {
884   &dump_program, &gnutar_program, NULL
885 };