fd71862517aa81e17977217e612c718874066f47
[debian/amanda] / client-src / sendbackup.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  * 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.44.2.9.4.4.2.16 2004/01/14 12:59:12 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 "stream.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 data_socket, data_port, dataf;
52 int mesg_socket, mesg_port, mesgf;
53 int index_socket, index_port, indexf;
54
55 option_t *options;
56
57 #ifdef KRB4_SECURITY
58 #include "sendbackup-krb4.h"
59 #else                                   /* I'd tell you what this does */
60 #define NAUGHTY_BITS                    /* but then I'd have to kill you */
61 #endif
62
63 long dump_size = -1;
64
65 backup_program_t *program = NULL;
66
67 static am_feature_t *our_features = NULL;
68 static char *our_feature_string = NULL;
69 static g_option_t *g_options = NULL;
70
71 /* local functions */
72 int main P((int argc, char **argv));
73 char *optionstr P((option_t *options));
74 char *childstr P((int pid));
75 int check_status P((int pid, amwait_t w));
76
77 int pipefork P((void (*func) P((void)), char *fname, int *stdinfd,
78                 int stdoutfd, int stderrfd));
79 void parse_backup_messages P((int mesgin));
80 static void process_dumpline P((char *str));
81
82
83 char *optionstr(option_t *options)
84 {
85     static char *optstr = NULL;
86     char *compress_opt = "";
87     char *record_opt = "";
88     char *bsd_opt = "";
89     char *krb4_opt = "";
90     char *kencrypt_opt = "";
91     char *index_opt = "";
92     char *exclude_file_opt;
93     char *exclude_list_opt;
94     char *exc = NULL;
95     sle_t *excl;
96
97     if(options->compress == COMPR_BEST)
98         compress_opt = "compress-best;";
99     else if(options->compress == COMPR_FAST)
100         compress_opt = "compress-fast;";
101     else if(options->compress == COMPR_SERVER_BEST)
102         compress_opt = "srvcomp-best;";
103     else if(options->compress == COMPR_SERVER_FAST)
104         compress_opt = "srvcomp-fast;";
105     if(options->no_record) record_opt = "no-record;";
106     if(options->bsd_auth) bsd_opt = "bsd-auth;";
107 #ifdef KRB4_SECURITY
108     if(options->krb4_auth) krb4_opt = "krb4-auth;";
109     if(options->kencrypt) kencrypt_opt = "kencrypt;";
110 #endif
111     if(options->createindex) index_opt = "index;";
112
113     exclude_file_opt = stralloc("");
114     if(options->exclude_file) {
115         for(excl = options->exclude_file->first; excl != NULL; excl=excl->next){
116             exc = newvstralloc(exc, "exclude-file=", excl->name, ";", NULL);
117             strappend(exclude_file_opt, exc);
118         }
119     }
120     exclude_list_opt = stralloc("");
121     if(options->exclude_list) {
122         for(excl = options->exclude_list->first; excl != NULL; excl=excl->next){
123             exc = newvstralloc(exc, "exclude-list=", excl->name, ";", NULL);
124             strappend(exclude_list_opt, exc);
125         }
126     }
127     optstr = newvstralloc(optstr,
128                           compress_opt,
129                           record_opt,
130                           bsd_opt,
131                           krb4_opt,
132                           kencrypt_opt,
133                           index_opt,
134                           exclude_file_opt,
135                           exclude_list_opt,
136                           NULL);
137     return optstr;
138 }
139
140
141 int main(argc, argv)
142 int argc;
143 char **argv;
144 {
145     int interactive = 0;
146     int level, mesgpipe[2];
147     char *prog, *disk, *amdevice, *dumpdate, *stroptions;
148     char *line = NULL;
149     char *err_extra = NULL;
150     char *s;
151     int i;
152     int ch;
153     unsigned long malloc_hist_1, malloc_size_1;
154     unsigned long malloc_hist_2, malloc_size_2;
155     int fd;
156
157     /* initialize */
158
159     for(fd = 3; fd < FD_SETSIZE; fd++) {
160         /*
161          * Make sure nobody spoofs us with a lot of extra open files
162          * that would cause an open we do to get a very high file
163          * descriptor, which in turn might be used as an index into
164          * an array (e.g. an fd_set).
165          */
166 #ifdef KRB4_SECURITY
167         if (fd != KEY_PIPE)     /* XXX interface needs to be fixed */
168 #endif
169                 close(fd);
170     }
171
172     safe_cd();
173
174     set_pname("sendbackup");
175
176     malloc_size_1 = malloc_inuse(&malloc_hist_1);
177
178     interactive = (argc > 1 && strcmp(argv[1],"-t") == 0);
179     erroutput_type = (ERR_INTERACTIVE|ERR_SYSLOG);
180     dbopen();
181     startclock();
182     dbprintf(("%s: version %s\n", argv[0], version()));
183
184     our_features = am_init_feature_set();
185     our_feature_string = am_feature_to_string(our_features);
186
187     if(interactive) {
188         /*
189          * In interactive (debug) mode, the backup data is sent to
190          * /dev/null and none of the network connections back to driver
191          * programs on the tape host are set up.  The index service is
192          * run and goes to stdout.
193          */
194         fprintf(stderr, "%s: running in interactive test mode\n", get_pname());
195         fflush(stderr);
196     }
197
198     prog = NULL;
199     disk = NULL;
200     amdevice = NULL;
201     dumpdate = NULL;
202     stroptions = NULL;
203
204     /* parse dump request */
205
206     for(; (line = agets(stdin)) != NULL; free(line)) {
207         if(interactive) {
208             fprintf(stderr, "%s> ", get_pname());
209             fflush(stderr);
210         }
211 #define sc "OPTIONS "
212         if(strncmp(line, sc, sizeof(sc)-1) == 0) {
213 #undef sc
214             g_options = parse_g_options(line+8, 1);
215             if(!g_options->hostname) {
216                 g_options->hostname = alloc(MAX_HOSTNAME_LENGTH+1);
217                 gethostname(g_options->hostname, MAX_HOSTNAME_LENGTH);
218                 g_options->hostname[MAX_HOSTNAME_LENGTH] = '\0';
219             }
220             continue;
221         }
222
223         if (prog != NULL) {
224             err_extra = "multiple requests";
225             goto err;
226         }
227
228         s = line;
229         ch = *s++;
230
231         skip_whitespace(s, ch);                 /* find the program name */
232         if(ch == '\0') {
233             err_extra = "no program name";
234             goto err;                           /* no program name */
235         }
236         prog = s - 1;
237         skip_non_whitespace(s, ch);
238         s[-1] = '\0';
239         prog = stralloc(prog);
240
241         skip_whitespace(s, ch);                 /* find the disk name */
242         if(ch == '\0') {
243             err_extra = "no disk name";
244             goto err;                           /* no disk name */
245         }
246         amfree(disk);
247         disk = s - 1;
248         skip_non_whitespace(s, ch);
249         s[-1] = '\0';
250         disk = stralloc(disk);
251
252         skip_whitespace(s, ch);                 /* find the device or level */
253         if (ch == '\0') {
254             err_extra = "bad level";
255             goto err;
256         }
257
258         if(!isdigit((int)s[-1])) {
259             amfree(amdevice);
260             amdevice = s - 1;
261             skip_non_whitespace(s, ch);
262             s[-1] = '\0';
263             amdevice = stralloc(amdevice);
264             skip_whitespace(s, ch);             /* find level number */
265         }
266         else {
267             amdevice = stralloc(disk);
268         }
269
270                                                 /* find the level number */
271         if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
272             err_extra = "bad level";
273             goto err;                           /* bad level */
274         }
275         skip_integer(s, ch);
276
277         skip_whitespace(s, ch);                 /* find the dump date */
278         if(ch == '\0') {
279             err_extra = "no dumpdate";
280             goto err;                           /* no dumpdate */
281         }
282         amfree(dumpdate);
283         dumpdate = s - 1;
284         skip_non_whitespace(s, ch);
285         s[-1] = '\0';
286         dumpdate = stralloc(dumpdate);
287
288         skip_whitespace(s, ch);                 /* find the options keyword */
289         if(ch == '\0') {
290             err_extra = "no options";
291             goto err;                           /* no options */
292         }
293 #define sc "OPTIONS "
294         if(strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
295             err_extra = "no OPTIONS keyword";
296             goto err;                           /* no options */
297         }
298         s += sizeof(sc)-1;
299         ch = s[-1];
300 #undef sc
301         skip_whitespace(s, ch);                 /* find the options string */
302         if(ch == '\0') {
303             err_extra = "bad options string";
304             goto err;                           /* no options */
305         }
306         amfree(stroptions);
307         stroptions = stralloc(s - 1);
308     }
309     amfree(line);
310
311     dbprintf(("  parsed request as: program `%s'\n", prog));
312     dbprintf(("                     disk `%s'\n", disk));
313     dbprintf(("                     device `%s'\n", amdevice));
314     dbprintf(("                     level %d\n", level));
315     dbprintf(("                     since %s\n", dumpdate));
316     dbprintf(("                     options `%s'\n", stroptions));
317
318     for(i = 0; programs[i] != NULL; i++) {
319         if (strcmp(programs[i]->name, prog) == 0) {
320             break;
321         }
322     }
323     if (programs[i] == NULL) {
324         error("ERROR [%s: unknown program %s]", get_pname(), prog);
325     }
326     program = programs[i];
327
328     options = parse_options(stroptions, disk, amdevice, g_options->features, 0);
329
330 #ifdef KRB4_SECURITY
331     /* modification by BIS@BBN 4/25/2003:
332      * with the option processing changes in amanda 2.4.4, must change
333      * the conditional from krb4_auth to options->krb4_auth */
334     if(options->krb4_auth) {
335         if(read(KEY_PIPE, session_key, sizeof session_key) 
336            != sizeof session_key) {
337           error("ERROR [%s: could not read session key]", get_pname());
338         }
339     }
340 #endif
341
342     if(!interactive) {
343       data_socket = stream_server(&data_port, STREAM_BUFSIZE, -1);
344       if(data_socket < 0) {
345         error("ERROR [%s: could not create data socket: %s]",
346               get_pname(), strerror(errno));
347       }
348       mesg_socket = stream_server(&mesg_port, -1, -1);
349       if(mesg_socket < 0) {
350         error("ERROR [%s: could not create mesg socket: %s]",
351               get_pname(), strerror(errno));
352       }
353     }
354     if (!interactive && options->createindex) {
355       index_socket = stream_server(&index_port, -1, -1);
356       if(index_socket < 0) {
357         error("ERROR [%s: could not create index socket: %s]",
358               get_pname(), strerror(errno));
359       }
360     } else {
361       index_port = -1;
362     }
363
364     printf("CONNECT DATA %d MESG %d INDEX %d\n",
365            data_port, mesg_port, index_port);
366     printf("OPTIONS ");
367     if(am_has_feature(g_options->features, fe_rep_options_features)) {
368         printf("features=%s;", our_feature_string);
369     }
370     if(am_has_feature(g_options->features, fe_rep_options_hostname)) {
371         printf("hostname=%s;", g_options->hostname);
372     }
373     if(am_has_feature(g_options->features, fe_rep_options_sendbackup_options)) {
374         printf("%s", optionstr(options));
375     }
376     printf("\n");
377     fflush(stdout);
378     freopen("/dev/null", "w", stdout);
379
380     if (options->createindex)
381       dbprintf(("%s: waiting for connect on %d, then %d, then %d\n",
382                 debug_prefix_time(NULL), data_port, mesg_port, index_port));
383     else
384       dbprintf(("%s: waiting for connect on %d, then %d\n",
385                 debug_prefix_time(NULL), data_port, mesg_port));
386
387     if(interactive) {
388       if((dataf = open("/dev/null", O_RDWR)) < 0) {
389         error("ERROR [%s: open of /dev/null for debug data stream: %s]",
390               get_pname(), strerror(errno));
391       }
392       mesgf = 2;
393     } else {
394       dataf = stream_accept(data_socket, TIMEOUT, -1, -1);
395       if(dataf == -1) {
396         dbprintf(("%s: timeout on data port %d\n",
397                   debug_prefix_time(NULL), data_port));
398       }
399       mesgf = stream_accept(mesg_socket, TIMEOUT, -1, -1);
400       if(mesgf == -1) {
401         dbprintf(("%s: timeout on mesg port %d\n",
402                   debug_prefix_time(NULL), mesg_port));
403       }
404     }
405     if(interactive) {
406       indexf = 1;
407     } else if (options->createindex) {
408       indexf = stream_accept(index_socket, TIMEOUT, -1, -1);
409       if (indexf == -1) {
410         dbprintf(("%s: timeout on index port %d\n",
411                   debug_prefix_time(NULL), index_port));
412       }
413     }
414
415     if(!interactive) {
416       if(dataf == -1 || mesgf == -1 || (options->createindex && indexf == -1)) {
417         dbclose();
418         exit(1);
419       }
420     }
421
422     dbprintf(("%s: got all connections\n", debug_prefix_time(NULL)));
423
424 #ifdef KRB4_SECURITY
425     if(!interactive) {
426       /* modification by BIS@BBN 4/25/2003:
427        * with the option processing changes in amanda 2.4.4, must change
428        * the conditional from krb4_auth to options->krb4_auth */
429       if (options->krb4_auth) {
430         if(kerberos_handshake(dataf, session_key) == 0) {
431             dbprintf(("%s: kerberos_handshake on data socket failed\n",
432                       debug_prefix_time(NULL)));
433             dbclose();
434             exit(1);
435         } else {
436             dbprintf(("%s: kerberos_handshake on data socket succeeded\n",
437                       debug_prefix_time(NULL)));
438
439         }
440
441         if(kerberos_handshake(mesgf, session_key) == 0) {
442             dbprintf(("%s: kerberos_handshake on mesg socket failed\n",
443                       debug_prefix_time(NULL)));
444             dbclose();
445             exit(1);
446         } else {
447             dbprintf(("%s: kerberos_handshake on mesg socket succeeded\n",
448                       debug_prefix_time(NULL)));
449
450         }
451
452         dbprintf(("%s: kerberos handshakes succeeded!\n",
453                   debug_prefix_time(NULL)));
454       }
455     }
456 #endif
457
458     if(!interactive) {
459       /* redirect stderr */
460       if(dup2(mesgf, 2) == -1) {
461           dbprintf(("%s: error redirecting stderr: %s\n",
462                     debug_prefix(NULL), strerror(errno)));
463           dbclose();
464           exit(1);
465       }
466     }
467
468     if(pipe(mesgpipe) == -1) {
469       error("error [opening mesg pipe: %s]", strerror(errno));
470     }
471
472     program->start_backup(g_options->hostname, disk, amdevice, level, dumpdate,
473                           dataf, mesgpipe[1], indexf);
474     parse_backup_messages(mesgpipe[0]);
475
476     amfree(prog);
477     amfree(disk);
478     amfree(amdevice);
479     amfree(dumpdate);
480     amfree(stroptions);
481     amfree(our_feature_string);
482     am_release_feature_set(our_features);
483     our_features = NULL;
484     am_release_feature_set(g_options->features);
485     g_options->features = NULL;
486     amfree(g_options->hostname);
487     amfree(g_options->str);
488     amfree(g_options);
489
490     dbclose();
491
492     malloc_size_2 = malloc_inuse(&malloc_hist_2);
493
494     if(malloc_size_1 != malloc_size_2) {
495         malloc_list(fileno(stderr), malloc_hist_1, malloc_hist_2);
496     }
497
498     return 0;
499
500  err:
501     printf("FORMAT ERROR IN REQUEST PACKET\n");
502     dbprintf(("%s: REQ packet is bogus%s%s\n",
503               debug_prefix_time(NULL),
504               err_extra ? ": " : "",
505               err_extra ? err_extra : ""));
506     dbclose();
507     return 1;
508 }
509
510 char *childstr(pid)
511 int pid;
512 /*
513  * Returns a string for a child process.  Checks the saved dump and
514  * compress pids to see which it is.
515  */
516 {
517     if(pid == dumppid) return program->backup_name;
518     if(pid == comppid) return "compress";
519     if(pid == encpid)  return "kencrypt";
520     if(pid == indexpid) return "index";
521     return "unknown";
522 }
523
524
525 int check_status(pid, w)
526 int pid;
527 amwait_t w;
528 /*
529  * Determine if the child return status really indicates an error.
530  * If so, add the error message to the error string; more than one
531  * child can have an error.
532  */
533 {
534     char *thiserr = NULL;
535     char *str;
536     int ret, sig, rc;
537     char number[NUM_STR_SIZE];
538
539     str = childstr(pid);
540
541     if(WIFSIGNALED(w)) {
542         ret = 0;
543         rc = sig = WTERMSIG(w);
544     } else {
545         sig = 0;
546         rc = ret = WEXITSTATUS(w);
547     }
548
549     if(pid == indexpid) {
550         /*
551          * Treat an index failure (other than signal) as a "STRANGE"
552          * rather than an error so the dump goes ahead and gets processed
553          * but the failure is noted.
554          */
555         if(ret != 0) {
556             fprintf(stderr, "? %s returned %d\n", str, ret);
557             rc = 0;
558         }
559     }
560
561 #ifndef HAVE_GZIP
562     if(pid == comppid) {
563         /*
564          * compress returns 2 sometimes, but it is ok.
565          */
566         if(ret == 2) {
567             rc = 0;
568         }
569     }
570 #endif
571
572 #ifdef DUMP_RETURNS_1
573     if(pid == dumppid && tarpid == -1) {
574         /*
575          * Ultrix dump returns 1 sometimes, but it is ok.
576          */
577         if(ret == 1) {
578             rc = 0;
579         }
580     }
581 #endif
582
583 #ifdef IGNORE_TAR_ERRORS
584     if(pid == tarpid) {
585         /*
586          * tar bitches about active filesystems, but we do not care.
587          */
588         if(ret == 2) {
589             rc = 0;
590         }
591     }
592 #endif
593
594     if(rc == 0) {
595         return 0;                               /* normal exit */
596     }
597
598     if(ret == 0) {
599         ap_snprintf(number, sizeof(number), "%d", sig);
600         thiserr = vstralloc(str, " got signal ", number, NULL);
601     } else {
602         ap_snprintf(number, sizeof(number), "%d", ret);
603         thiserr = vstralloc(str, " returned ", number, NULL);
604     }
605
606     if(errorstr) {
607         strappend(errorstr, ", ");
608         strappend(errorstr, thiserr);
609         amfree(thiserr);
610     } else {
611         errorstr = thiserr;
612         thiserr = NULL;
613     }
614     return 1;
615 }
616
617
618 /* Send header info to the message file.
619 */
620 void write_tapeheader()
621 {
622     fprintf(stderr, "%s: info BACKUP=%s\n", get_pname(), program->backup_name);
623
624     fprintf(stderr, "%s: info RECOVER_CMD=", get_pname());
625     if (options->compress == COMPR_FAST || options->compress == COMPR_BEST)
626         fprintf(stderr, "%s %s |", UNCOMPRESS_PATH,
627 #ifdef UNCOMPRESS_OPT
628                 UNCOMPRESS_OPT
629 #else
630                 ""
631 #endif
632                 );
633
634     fprintf(stderr, "%s -f... -\n", program->restore_name);
635
636     if (options->compress == COMPR_FAST || options->compress == COMPR_BEST)
637         fprintf(stderr, "%s: info COMPRESS_SUFFIX=%s\n",
638                         get_pname(), COMPRESS_SUFFIX);
639
640     fprintf(stderr, "%s: info end\n", get_pname());
641 }
642
643 int pipefork(func, fname, stdinfd, stdoutfd, stderrfd)
644 void (*func) P((void));
645 char *fname;
646 int *stdinfd;
647 int stdoutfd, stderrfd;
648 {
649     int pid, inpipe[2];
650
651     dbprintf(("%s: forking function %s in pipeline\n",
652               debug_prefix_time(NULL), fname));
653
654     if(pipe(inpipe) == -1) {
655         error("error [open pipe to %s: %s]", fname, strerror(errno));
656     }
657
658     switch(pid = fork()) {
659     case -1:
660         error("error [fork %s: %s]", fname, strerror(errno));
661     default:    /* parent process */
662         aclose(inpipe[0]);      /* close input side of pipe */
663         *stdinfd = inpipe[1];
664         break;
665     case 0:             /* child process */
666         aclose(inpipe[1]);      /* close output side of pipe */
667
668         if(dup2(inpipe[0], 0) == -1) {
669             error("error [dup2 0 %s: dup2 in: %s]", fname, strerror(errno));
670         }
671         if(dup2(stdoutfd, 1) == -1) {
672             error("error [dup2 1 %s: dup2 out: %s]", fname, strerror(errno));
673         }
674         if(dup2(stderrfd, 2) == -1) {
675             error("error [dup2 2 %s: dup2 err: %s]", fname, strerror(errno));
676         }
677
678         func();
679         exit(0);
680         /* NOTREACHED */
681     }
682     return pid;
683 }
684
685 void parse_backup_messages(mesgin)
686 int mesgin;
687 {
688     int goterror, wpid;
689     amwait_t retstat;
690     char *line;
691
692     goterror = 0;
693     amfree(errorstr);
694
695     for(; (line = areads(mesgin)) != NULL; free(line)) {
696         process_dumpline(line);
697     }
698
699     if(errno) {
700         error("error [read mesg pipe: %s]", strerror(errno));
701     }
702
703     while((wpid = wait(&retstat)) != -1) {
704         if(check_status(wpid, retstat)) goterror = 1;
705     }
706
707     if(errorstr) {
708         error("error [%s]", errorstr);
709     } else if(dump_size == -1) {
710         error("error [no backup size line]");
711     }
712
713     program->end_backup(goterror);
714
715     fprintf(stderr, "%s: size %ld\n", get_pname(), dump_size);
716     fprintf(stderr, "%s: end\n", get_pname());
717 }
718
719
720 double first_num P((char *str));
721
722 double first_num(str)
723 char *str;
724 /*
725  * Returns the value of the first integer in a string.
726  */
727 {
728     char *num;
729     int ch;
730     double d;
731
732     ch = *str++;
733     while(ch && !isdigit(ch)) ch = *str++;
734     num = str - 1;
735     while(isdigit(ch) || ch == '.') ch = *str++;
736     str[-1] = '\0';
737     d = atof(num);
738     str[-1] = ch;
739     return d;
740 }
741
742 static void process_dumpline(str)
743 char *str;
744 {
745     regex_t *rp;
746     char *type;
747     char startchr;
748
749     for(rp = program->re_table; rp->regex != NULL; rp++) {
750         if(match(rp->regex, str)) {
751             break;
752         }
753     }
754     if(rp->typ == DMP_SIZE) {
755         dump_size = (long)((first_num(str) * rp->scale + 1023.0)/1024.0);
756     }
757     switch(rp->typ) {
758     case DMP_NORMAL:
759         type = "normal";
760         startchr = '|';
761         break;
762     case DMP_STRANGE:
763         type = "strange";
764         startchr = '?';
765         break;
766     case DMP_SIZE:
767         type = "size";
768         startchr = '|';
769         break;
770     case DMP_ERROR:
771         type = "error";
772         startchr = '?';
773         break;
774     default:
775         /*
776          * Should never get here.
777          */
778         type = "unknown";
779         startchr = '!';
780         break;
781     }
782     dbprintf(("%s: %3d: %7s(%c): %s\n",
783               debug_prefix_time(NULL),
784               rp->srcline,
785               type,
786               startchr,
787               str));
788     fprintf(stderr, "%c %s\n", startchr, str);
789 }
790
791
792 /* start_index.  Creates an index file from the output of dump/tar.
793    It arranges that input is the fd to be written by the dump process.
794    If createindex is not enabled, it does nothing.  If it is not, a
795    new process will be created that tees input both to a pipe whose
796    read fd is dup2'ed input and to a program that outputs an index
797    file to `index'.
798
799    make sure that the chat from restore doesn't go to stderr cause
800    this goes back to amanda which doesn't expect to see it
801    (2>/dev/null should do it)
802
803    Originally by Alan M. McIvor, 13 April 1996
804
805    Adapted by Alexandre Oliva, 1 May 1997
806
807    This program owes a lot to tee.c from GNU sh-utils and dumptee.c
808    from the DeeJay backup package.
809
810 */
811
812 static volatile int index_finished = 0;
813
814 static void index_closed(sig)
815 int sig;
816 {
817   index_finished = 1;
818 }
819
820 void save_fd(fd, min)
821 int *fd, min;
822 {
823   int origfd = *fd;
824
825   while (*fd >= 0 && *fd < min) {
826     int newfd = dup(*fd);
827     if (newfd == -1)
828       dbprintf(("%s: unable to save file descriptor [%s]\n",
829                 debug_prefix(NULL), strerror(errno)));
830     *fd = newfd;
831   }
832   if (origfd != *fd)
833     dbprintf(("%s: dupped file descriptor %i to %i\n",
834               debug_prefix(NULL), origfd, *fd));
835 }
836
837 void start_index(createindex, input, mesg, index, cmd)
838 int createindex, input, mesg, index;
839 char *cmd;
840 {
841   struct sigaction act, oact;
842   int pipefd[2];
843   FILE *pipe_fp;
844   int exitcode;
845
846   if (!createindex)
847     return;
848
849   if (pipe(pipefd) != 0) {
850     error("creating index pipe: %s", strerror(errno));
851   }
852
853   switch(indexpid = fork()) {
854   case -1:
855     error("forking index tee process: %s", strerror(errno));
856
857   default:
858     aclose(pipefd[0]);
859     if (dup2(pipefd[1], input) == -1) {
860       error("dup'ping index tee output: %s", strerror(errno));
861     }
862     aclose(pipefd[1]);
863     return;
864
865   case 0:
866     break;
867   }
868
869   /* now in a child process */
870   save_fd(&pipefd[0], 4);
871   save_fd(&index, 4);
872   save_fd(&mesg, 4);
873   save_fd(&input, 4);
874   dup2(pipefd[0], 0);
875   dup2(index, 1);
876   dup2(mesg, 2);
877   dup2(input, 3);
878   for(index = 4; index < FD_SETSIZE; index++) {
879     if (index != dbfd()) {
880       close(index);
881     }
882   }
883
884   /* set up a signal handler for SIGPIPE for when the pipe is finished
885      creating the index file */
886   /* at that point we obviously want to stop writing to it */
887   act.sa_handler = index_closed;
888   sigemptyset(&act.sa_mask);
889   act.sa_flags = 0;
890   if (sigaction(SIGPIPE, &act, &oact) != 0) {
891     error("couldn't set index SIGPIPE handler [%s]", strerror(errno));
892   }
893
894   if ((pipe_fp = popen(cmd, "w")) == NULL) {
895     error("couldn't start index creator [%s]", strerror(errno));
896   }
897
898   dbprintf(("%s: started index creator: \"%s\"\n",
899             debug_prefix_time(NULL), cmd));
900   while(1) {
901     char buffer[BUFSIZ], *ptr;
902     int bytes_read;
903     int bytes_written;
904     int just_written;
905
906     bytes_read = read(0, buffer, sizeof(buffer));
907     if ((bytes_read < 0) && (errno == EINTR))
908       continue;
909
910     if (bytes_read < 0) {
911       error("index tee cannot read [%s]", strerror(errno));
912     }
913
914     if (bytes_read == 0)
915       break; /* finished */
916
917     /* write the stuff to the subprocess */
918     ptr = buffer;
919     bytes_written = 0;
920     while (bytes_read > bytes_written && !index_finished) {
921       just_written = write(fileno(pipe_fp), ptr, bytes_read - bytes_written);
922       if (just_written < 0) {
923           /* the signal handler may have assigned to index_finished
924            * just as we waited for write() to complete. */
925           if (!index_finished) {
926               dbprintf(("%s: index tee cannot write to index creator [%s]\n",
927                         debug_prefix_time(NULL), strerror(errno)));
928               index_finished = 1;
929         }
930       } else {
931         bytes_written += just_written;
932         ptr += just_written;
933       }
934     }
935
936     /* write the stuff to stdout, ensuring none lost when interrupt
937        occurs */
938     ptr = buffer;
939     bytes_written = 0;
940     while (bytes_read > bytes_written) {
941       just_written = write(3, ptr, bytes_read - bytes_written);
942       if ((just_written < 0) && (errno == EINTR))
943         continue;
944       if (just_written < 0) {
945         error("index tee cannot write [%s]", strerror(errno));
946       } else {
947         bytes_written += just_written;
948         ptr += just_written;
949       }
950     }
951   }
952
953   aclose(pipefd[1]);
954
955   /* finished */
956   /* check the exit code of the pipe and moan if not 0 */
957   if ((exitcode = pclose(pipe_fp)) != 0) {
958     dbprintf(("%s: index pipe returned %d\n",
959               debug_prefix_time(NULL), exitcode));
960   } else {
961     dbprintf(("%s: index created successfully\n", debug_prefix_time(NULL)));
962   }
963   pipe_fp = NULL;
964
965   exit(exitcode);
966 }
967
968 extern backup_program_t dump_program, gnutar_program;
969
970 backup_program_t *programs[] = {
971   &dump_program, &gnutar_program, NULL
972 };
973
974 #ifdef KRB4_SECURITY
975 #include "sendbackup-krb4.c"
976 #endif