#include "config.h" #include "conf.h" #include "err.h" #include "lock.h" #include "strutil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NEED_LIBUTIL #include #endif #ifdef NEED_STRINGS #include #endif RCSID("$Id: dumper.c,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $"); struct diskinfo { char vol[MAXPATHLEN]; /* Volume name. */ int dumplevel; /* Level of dump, if applicable. */ ssize_t size; /* How big dump is expected to be. */ auth_t authtype; /* Type of auth for dump. */ struct diskinfo *next; }; typedef struct diskinfo diskinfo_t; struct hostinfo { char host[MAXHOSTNAMELEN]; /* Hostname. */ int socket; /* The socket for the dump. */ int port; /* Port on which to send dump. */ int pin[2], pout[2], perr[2]; /* Connection to RSH pipe. */ diskinfo_t *disks; /* Disk information. */ struct hostinfo *next; }; typedef struct hostinfo hostinfo_t; int taperpipe[2]; short exit_status = -1; /* * If something terminates abnormally, this will be called. */ void sig_pipe(int signo) { log_err("SIGPIPE caught.\n"); exit_status = -3; } /* * Clean up a child. */ void sig_child(int signo) { int status; if (wait(&status) == -1) log_err("Warning: couldn't clean up child.\n"); exit_status = WEXITSTATUS(status); } /* * Tell the taper to write a file to tape. */ void write_to_tape(const char *spooldir, const char *host, const char *filen) { char buffer[MAXLINE]; struct sigaction sa, osa; /* * If SIGPIPE comes when we're trying to write to the taper * descriptor, silently ignore it. */ sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, &osa); log_info("%s: telling taper to write `%s/%s:%s' to tape.\n", host, spooldir, host, filen); sprintf(buffer, DISK_REQ "%s/%s:%s\n", spooldir, host, filen); write(taperpipe[1], buffer, strlen(buffer)); /* Put the old handler back. */ sigaction(SIGPIPE, &osa, NULL); } /* * Create a file on the holding which will contain the dump. */ inline int open_stagefile(const char *spooldir, const char *host, const char *name) { char filen[MAXPATHLEN]; /* TODO: sanity check the name argument. */ log_info("%s: dumping output to `%s/%s:%s'.\n", host, spooldir, host, name); snprintf(filen, sizeof(filen), "%s/%s:%s", spooldir, host, name); return open(filen, O_WRONLY|O_CREAT|O_TRUNC, 0666); } /* * Clear out remaining data on a file descriptor. */ int flush(const char *host, int infd, int outfd, ssize_t *total) { char buffer[MAXLINE]; ssize_t readb; log_info("%s: flushing remaining output.\n", host); if (fcntl(infd, F_SETFL, 0) == -1) { log_err("%s: couldn't set input to non-blocking: %s.\n", host, strerror(errno)); return 0; } READ: while ((readb = read(infd, buffer, sizeof(buffer))) > 0) { ssize_t wrote = 0; *total += readb; while (wrote < readb) { ssize_t n; WRITE: n = write(outfd, buffer, readb-wrote); if (n == -1) { if (errno == EINTR) goto WRITE; log_err("TODO: fix this message.\n"); return 0; } wrote += n; } } if (readb == -1) { if (errno == EINTR || errno == EAGAIN) goto READ; log_err("%s: error occurred while flushing" " remaining output: %s.\n", host, strerror(errno)); return 0; } return 1; } /* * Say `Hello' to the remote host and wait for a 'How are you'. */ int do_handshake(int infd, int outfd, char *host) { char buffer[MAXLINE]; ssize_t readb; read_handshake: readb = read(infd, buffer, sizeof(buffer)); if (readb == -1) { if (errno == EINTR) goto read_handshake; log_err("Master: couldn't read handshake from %s: %s.\n", host, strerror(errno)); return -1; } buffer[readb-1] = '\0'; if (strncmp(buffer, INIT_REQ, sizeof(buffer))) { log_err("Master: Bad protocol from %s: handshake: %s.\n", host, buffer); return -1; } if (write(outfd, INIT_ACK "\n", sizeof(INIT_ACK)) == -1) { log_err("Master: couldn't send handshake ACK to %s: %s.\n", host, strerror(errno)); return -1; } return 0; } /* * Handle error conditions on the remote host. */ int do_error(int infd, const char *host) { if (fcntl(infd, F_SETFL, 0) == -1) { log_err("%s: couldn't set fd to" " blocking mode.\n", host); return 0; } readerr(infd, host); return 1; } /* * Ask the remote host to send us a disk. */ int do_newfile(diskinfo_t *disk, hostinfo_t *host, const char *spooldir, int *infd, int *outfd) { char buffer[MAXLINE], word[MAXLINE], filen[MAXPATHLEN]; char *i = filen; ssize_t readb; switch (disk->authtype) { case NOAUTH: snprintf(buffer, sizeof(buffer), "%s %s %s %s %d %s %s\n", DUMP_SENDME, DISK_NAME, disk->vol, DISK_LEVEL, disk->dumplevel, DISK_AUTH, AUTH_NOAUTH); break; case RSH: snprintf(buffer, sizeof(buffer), "%s %s %s %s %d %s %s\n", DUMP_SENDME, DISK_NAME, disk->vol, DISK_LEVEL, disk->dumplevel, DISK_AUTH, AUTH_RSH); break; default: log_err("%s: unknown auth type for %s: %d.\n", host->host, disk->vol, disk->authtype); break; } if (write(host->pin[1], buffer, strlen(buffer)) == -1) { log_err("%s: couldn't send instructions: %s.\n", host->host, strerror(errno)); return -1; } READ: readb = read(host->pout[0], buffer, sizeof(buffer)); if (readb == -1) { if (errno == EINTR) goto READ; log_err("%s: couldn't read ACK for disk `%s': %s.\n", host->host, disk->vol, strerror(errno)); return -1; } else if (!readb) { log_err("%s: rsh client died while setting up connection.\n", host->host); return -1; } buffer[readb] = '\0'; i = getword(buffer, word); if (!strncmp(word, DUMP_ERROR, sizeof(word))) { do_error(host->perr[0], host->host); return -1; } else if (strncmp(word, REQ_ACK, sizeof(word))) { log_err("%s: don't understand `%s'.\n", host->host, buffer); return -1; } i = filen; strncpy(filen, disk->vol, sizeof(filen)); while ((i = index(i, '/'))) *i = '_'; *outfd = open_stagefile(spooldir, host->host, filen); if (*outfd == -1) return -1; switch (disk->authtype) { case NOAUTH: { struct hostent *he; struct sockaddr_in sa; *infd = socket(PF_INET, SOCK_STREAM, 0); if (*infd == -1) { log_err("%s: couldn't create socket: %s.\n", host->host, strerror(errno)); return -1; } /* TODO: make sure we're not an IP address. */ he = gethostbyname(host->host); if (!he) { log_err("%s: couldn't resolve hostname `%s'.\n", host->host, host->host); return -1; } sa.sin_family = AF_INET; sa.sin_port = htons(host->port); memcpy(&sa.sin_addr.s_addr, he->h_addr, sizeof(sa.sin_addr.s_addr)); if (connect(*infd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { log_err("%s: couldn't connect: %s.\n", host->host, strerror(errno)); return -1; } }; break; case RSH: log_err("%s: RSH auth not supported right now.\n", host->host); return -1; break; default: log_err("%s: don't understand AUTH type: %d.\n", host->host, disk->authtype); return -1; } return 0; } /* * Read a disk from a stream and spool it to the holding disk. */ int read_backup(diskinfo_t *disk, hostinfo_t *host, const char *spooldir, int timeout) { char buffer[MAXLINE]; fd_set readfds, exceptfds; ssize_t readb, total = 0; time_t start; struct timeval tv; int infd, outfd; int done = 0, error = 0; fcntl(host->pout[0], F_SETFL, 0); if (do_newfile(disk, host, spooldir, &infd, &outfd) == -1) { if (exit_status != -1) return -1; else return 0; } fcntl(host->pout[0], F_SETFL, O_NONBLOCK); start = time(NULL); while (exit_status == -1) { FD_ZERO(&readfds); FD_ZERO(&exceptfds); FD_SET(infd, &readfds); FD_SET(infd, &exceptfds); FD_SET(host->pout[0], &readfds); FD_SET(host->pout[0], &exceptfds); FD_SET(host->perr[0], &readfds); FD_SET(host->perr[0], &exceptfds); tv.tv_sec = timeout; tv.tv_usec = 0; if (select(FD_SETSIZE, &readfds, NULL, &exceptfds, &tv) == -1) { if (errno == EINTR) continue; log_err("%s: select failed: %s.\n", host->host, strerror(errno)); return -1; } /* TODO: check exceptfds. */ if (FD_ISSET(host->pout[0], &readfds)) { char word[MAXLINE]; char *p; READSTDOUT: readb = read(host->pout[0], buffer, sizeof(buffer)); if (readb == -1) { if (errno == EINTR) goto READSTDOUT; log_err("%s: read died with: %s.\n", host->host, strerror(errno)); break; } else if (!readb) { log_err("%s: rsh client died.\n", host->host); break; } p = getword(buffer, word); if (!strncmp(word, DUMP_ERROR, sizeof(word))) { if (*p == '\n') p++; do_error(host->perr[0], host->host); error = 1; break; } else if (!strncmp(word, DUMP_DONE, sizeof(word))) { flush(host->host, infd, outfd, &total); log_info("%s: Wrote %ld bytes in %d seconds.\n", host->host, total, time(NULL)-start); done = 1; break; } else { log_err("%s: No header on following output;" " assuming error:\n", host->host); do_error(host->pout[0], host->host); error = 1; break; } } if (FD_ISSET(host->perr[0], &readfds)) { READSTDERR: readb = read(host->perr[0], buffer, sizeof(buffer)); if (readb == -1) { if (errno == EINTR) goto READSTDERR; log_err("%s: read of stderr died with: %s.\n", host->host, strerror(errno)); break; } else if (!readb) { log_err("%s: rsh client died.\n", host->host); } readerr(host->perr[0], host->host); } if (FD_ISSET(infd, &readfds)) { READDATA: readb = read(infd, buffer, sizeof(buffer)); if (readb == -1) { if (errno == EINTR) goto READDATA; log_err("%s: read died with: %s.\n", host->host, strerror(errno)); break; } else if (!readb) break; write(outfd, buffer, readb); total += readb; } } /* If we haven't seen any control information, scan for it now. */ if (!done && !error) { ssize_t readb; /* TODO: check stderr too. */ fcntl(host->pout[0], F_SETFL, 0); READ: readb = read(host->pout[0], buffer, sizeof(buffer)); if (readb == -1) { if (errno == EINTR) goto READ; log_err("%s: read returned -1: %s.\n", host->host, strerror(errno)); } else if (readb) { char word[MAXLINE]; char *p; p = getword(buffer, word); if (!strncmp(word, DUMP_ERROR, sizeof(word))) { if (*p == '\n') p++; do_error(host->perr[0], host->host); return -1; } else if (!strncmp(word, DUMP_DONE, sizeof(word))) { flush(host->host, infd, outfd, &total); log_info("%s: Wrote %ld bytes in %d seconds.\n", host->host, total, time(NULL)-start); done = 1; } else { log_err("%s: No header on following output;" " assuming error:\n", host->host); do_error(host->pout[0], host->host); return -1; } } } if (done) { char filen[MAXLINE]; char *i; strncpy(filen, disk->vol, sizeof(filen)); i = filen; while ((i = index(i, '/'))) *i = '_'; write_to_tape(spooldir, host->host, filen); return 0; } if (error) return -1; /* * Exit status is grabbed by the wait function in sig_child. * If we've come this far, we haven't recieved the DONE command * from run-dump, so try and get some information and log it. */ switch (exit_status) { case -1: log_err("%s: premature death of rsh client.\n", host->host); break; case -2: log_err("%s: connection timed out.\n", host->host); break; case -3: log_err("%s: SIGPIPE received: this shoudn't have happened.\n", host->host); break; default: log_err("%s: run-dump exit status: %d.\n", host->host, exit_status); } return -1; } /* * Open up a connection to a host and back it up. */ int do_backup(hostinfo_t *host, const char *spooldir, long timeout) { diskinfo_t *disk = host->disks; pid_t pid; int status; struct sigaction sa, opsa, ocsa; log_info("%s: starting backup.\n", host->host); /* Set up signal handlers. */ sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sa.sa_handler = sig_pipe; sigaction(SIGPIPE, &sa, &opsa); sa.sa_handler = sig_child; sigaction(SIGCHLD, &sa, &ocsa); /* Set up the RSH master pipe and save file descriptors. */ if (pipe(host->pin) == -1 || pipe(host->pout) == -1 || pipe(host->perr) == -1) { log_err("Master: couldn't allocate pipes for %s: %s.\n", host, strerror(errno)); return -1; } pid = fork(); if (pid < 0){ log_err("%s: Couldn't fork: %s.\n", host->host, strerror(errno)); return -1; } else if (!pid) { close(host->pin[1]); close(host->pout[0]); close(host->perr[0]); if (dup2(host->pin[0], STDIN_FILENO) == -1 || dup2(host->pout[1], STDOUT_FILENO) == -1 || dup2(host->perr[1], STDERR_FILENO) == -1) exit(1); execlp(PATH_RSH, PATH_RSH, host, "run-dump", NULL); err("Couldn't exec run-dump: %s.\n", strerror(errno)); exit(1); } close(host->pin[0]); close(host->pout[1]); close(host->perr[1]); if (do_handshake(host->pout[0], host->pin[1], host->host) == -1) return -1; if (host->port) { char buffer[MAXLINE], word[MAXLINE]; char *p = buffer; ssize_t readb; snprintf(buffer, sizeof(buffer), "%s %d\n", PORT_REQ, host->port); write(host->pin[1], buffer, strlen(buffer)); READ: readb = read(host->pout[0], buffer, sizeof(buffer)); if (readb == -1) { if (errno == EINTR) goto READ; log_err("%s: read died: %s.\n", host->host, strerror(errno)); return -1; } p = getword(buffer, word); if (!strncmp(word, DUMP_ERROR, sizeof(word))) { do_error(host->perr[0], host->host); return -1; } else if (strncmp(word, REQ_ACK, sizeof(word))) { log_err("%s: couldn't negotiate port.\n", host->host); return -1; } } /* TODO: this should probably happen before the handshake. */ if (fcntl(host->pout[0], F_SETFL, O_NONBLOCK) == -1 || fcntl(host->perr[0], F_SETFL, O_NONBLOCK) == -1) { log_err("%s: couldn't put pipes into non blocking mode: %s.\n", host->host, strerror(errno)); return -1; } while (disk) { if (read_backup(disk, host, spooldir, timeout) == -1) break; disk = disk->next; } /* Put the old handlers back. */ sigaction(SIGCHLD, &ocsa, NULL); sigaction(SIGPIPE, &opsa, NULL); if (!disk) /* TODO: check return value? */ write(host->pin[1], DUMP_DONE "\n", sizeof(DUMP_DONE)); /* Potentially there is stuff on the stderr stream, so grab it now. */ do_error(host->perr[0], host->host); /* Potentially, the child is still alive. */ (void)wait(&status); close(host->pin[1]); close(host->pout[0]); close(host->perr[0]); log_info("%s: finished backup.\n", host->host); if (disk) return -1; return 0; } /* * Parse buffer into fields appropriate for diskinfo. */ int add_disk(char *buffer, hostinfo_t *host) { char word[MAXLINE]; diskinfo_t *disk; char *p = buffer; disk = malloc(sizeof(diskinfo_t)); if (!disk) { log_err("Master: couldn't allocate %d bytes.\n", sizeof(diskinfo_t)); return -1; } disk->vol[0] = '\0'; disk->dumplevel = 0; disk->size = 0; disk->authtype = RSH; disk->next = host->disks; while ((p = getword(p, word))) { if (!strncmp(word, DISK_NAME, sizeof(word))) { p = getword(p, disk->vol); } else if (!strncmp(word, DISK_AUTH, sizeof(word))) { p = getword(p, word); if (!strncmp(word, AUTH_NOAUTH, sizeof(word))) disk->authtype = NOAUTH; else if (!strncmp(word, AUTH_RSH, sizeof(word))) disk->authtype = RSH; else { log_err("Master: recieved AUTH field I don't" " recognize from %s: %s.\n", host->host, word); write(host->pin[1], REQ_NACK "\n", sizeof(REQ_NACK)); free(disk); return -1; } } else if (!strncmp(word, DISK_SIZE, sizeof(word))) { p = getword(p, word); } else { log_err("Master: recieved DISK field I don't" " recognize from %s: %s.\n", host->host, word); write(host->pin[1], REQ_NACK "\n", sizeof(REQ_NACK)); free(disk); return -1; } } write(host->pin[1], REQ_ACK "\n", sizeof(REQ_ACK)); host->disks = disk; return 0; } /* * Get a list of disks to dump with estimates (if applicable), and * a port to use for unauthenticated backups. */ int init_connection(hostinfo_t *host) { char line[MAXLINE]; ssize_t readb; int done = 0; if (do_handshake(host->pout[0], host->pin[1], host->host) == -1) return -1; for (;;) { char *p = line; char word[MAXLINE]; READ: readb = read(host->pout[0], line, sizeof(line)); if (readb == -1) { if (errno == EINTR) goto READ; log_err("Master: couldn't read from %s: %s.\n", host->host, strerror(errno)); return -1; } if (readb == 0) { log_err("Master: connection to %s" " prematurely closed.\n", host->host); return -1; } line[readb] = '\0'; p = getword(line, word); if (!strncmp(word, PORT_REQ, sizeof(word))) { p = getword(p, word); if (!p) { log_err("Master: recieved a PORT" " request without an" " argument.\n"); return -1; } host->port = atoi(word); if (write(host->pin[1], REQ_ACK "\n", sizeof(REQ_ACK)) == -1) { log_err("Master: couldn't ACK PORT" " request: %s.\n", strerror(errno)); return -1; } continue; } if (!strncmp(word, DISK_REQ, sizeof(word))) { if (add_disk(p, host) == -1) break; continue; } if (!strncmp(word, DUMP_DONE, sizeof(word))) { done = 1; break; } if (!strncmp(word, DUMP_ERROR, sizeof(word))) { if (*p == '\n') p++; do_error(host->perr[0], host->host); break; } log_err("Master: bad protocol from %s: recieved" " command I don't understand: %s.\n", host->host, word); break; } if (!done) { log_err("Master: connection to %s prematurely terminated.\n", host->host); return -1; } return 0; } /* * Open a connection to a host, get an estimate, and close the connection. */ int addhost(hostinfo_t **hosts, const char *host) { hostinfo_t *curhost; pid_t pid; int status; struct sigaction sa, opsa, ocsa; log_info("Master: getting estimate for %s.\n", host); curhost = malloc(sizeof(hostinfo_t)); if (!curhost) { log_err("Master: couldn't allocate %d bytes for %s.\n", sizeof(hostinfo_t), host); return -1; } strncpy(curhost->host, host, sizeof(curhost->host)); curhost->port = 0; curhost->socket = 0; curhost->disks = NULL; if (pipe(curhost->pin) == -1 || pipe(curhost->pout) == -1 || pipe(curhost->perr) == -1) { log_err("Master: couldn't allocate pipes for %s: %s.\n", host, strerror(errno)); free(curhost); return -1; } /* Set up signal handlers. */ sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sa.sa_handler = sig_pipe; sigaction(SIGPIPE, &sa, &opsa); sa.sa_handler = sig_child; sigaction(SIGCHLD, &sa, &ocsa); /* Now set up the RSH pipe and save file descriptors. */ pid = fork(); if (pid < 0){ free(curhost); return -1; } else if (!pid) { close(curhost->pin[1]); close(curhost->pout[0]); close(curhost->perr[0]); if (dup2(curhost->pin[0], STDIN_FILENO) == -1 || dup2(curhost->pout[1], STDOUT_FILENO) == -1 || dup2(curhost->perr[1], STDERR_FILENO) == -1) exit(1); execlp(PATH_RSH, PATH_RSH, host, "run-estimate", NULL); err("Couldn't exec run-dump: %s.\n", strerror(errno)); exit(1); } close(curhost->pin[0]); close(curhost->pout[1]); close(curhost->perr[1]); /* Figure out what the client wants dumped and how. */ if (init_connection(curhost) == -1) { free(curhost); kill(pid, SIGHUP); return -1; } /* Close the RSH connection. */ close(curhost->pin[1]); close(curhost->pout[0]); close(curhost->perr[0]); /* Put the old handlers back. */ sigaction(SIGCHLD, &ocsa, NULL); sigaction(SIGPIPE, &opsa, NULL); /* * Potentially, the child has died after clobbering the signal * handler, so hang out here and wait for it. */ /* TODO: check out exist status. */ (void)wait(&status); /* Finished cleanly, add to list. */ curhost->next = *hosts; *hosts = curhost; return 0; } int main(int argc, char *argv[]) { FILE *host_list; const config_t *option; hostinfo_t *hosts = NULL, *curhost; char *spooldir; char infofilen[MAXPATHLEN], errorfilen[MAXPATHLEN]; char host[MAXHOSTNAMELEN]; int i = 0, retries, status; pid_t taperpid, pid; long timeout; if (read_config(SERVER_CONFIG_FILE) == -1) return 1; /* * TODO: if an error occurs causing abnormal termination, call the * reporter anyway. */ option = findopt(LOGDIR); if (!option) { fprintf(stderr, "Warning: `" LOGDIR "' has not been set," " using %s.\n", getcwd(NULL, MAXPATHLEN)); strcpy(infofilen, "./"); strcpy(errorfilen, "./"); } else { strcpy(infofilen, option->strvalue); strcat(infofilen, "/"); strcpy(errorfilen, option->strvalue); strcat(errorfilen, "/"); } option = findopt(INFOLOG); if (!option) { fprintf(stderr, "Warning: `" INFOLOG "' has not been set," " using stdout.\n"); strcpy(infofilen, "-"); } else strcat(infofilen, option->strvalue); option = findopt(ERRORLOG); if (!option) { fprintf(stderr, "Warning: `" ERRORLOG "' has not been set," " using stderr.\n"); strcpy(errorfilen, "-"); } else strcat(errorfilen, option->strvalue); initlog(infofilen, errorfilen); option = findopt(HOSTLIST); if (!option) { log_err("Master: hostlist hasn't been specified.\n"); return 1; } host_list = fopen(option->strvalue, "r"); if (!host_list) { log_err("Master: couldn't open host list.\n"); return 2; } option = findopt(SPOOLDIR); if (!option) { log_err("Master: spooldir hasn't been specified.\n"); return 1; } spooldir = option->strvalue; option = findopt(TIMEOUT); if (!option) { log_err("Master: timeout hasn't been specified.\n"); return 1; } timeout = option->numvalue; /* TODO: create lock here. */ /* Initialize host information by contacting said host. */ while (fgets(host, sizeof(host), host_list)) { host[strlen(host)-1] = '\0'; if (addhost(&hosts, host) == -1) log_err("Master: couldn't initiate connection" " with %s.\n", host); } fclose(host_list); /* * TODO: do backup scheduling here. */ /* Start up the taper. */ if (pipe(taperpipe) == -1) { log_err("Master: couldn't create pipes for taper.\n"); return 1; } taperpid = fork(); if (taperpid == -1) { log_err("Master: couldn't fork for taper.\n"); return 1; } else if (!taperpid) { close(taperpipe[1]); dup2(taperpipe[0], STDIN_FILENO); (void)execlp(LIBEXECDIR "/taper", "taper", NULL); log_err("Master: couldn't exec taper: %s.\n", strerror(errno)); exit(1); } close(taperpipe[0]); /* * Main backup loop, open up connections to each host and * back them up. */ setproctitle("Master"); curhost = hosts; while (curhost) { retries = 0; FORK: pid = fork(); i++; if (pid < 0) { i--; if (errno == EAGAIN) { if (++retries > MAXRETRIES) { log_err("Fatal: couldn't fork, " "retries exceeded.\n"); break; } log_err("Master: couldn't fork, sleeping.\n"); sleep(5); goto FORK; } log_err("Master: couldn't fork, dying.\n"); break; } else if (!pid) { setproctitle("Running backup of %s", curhost->host); if (do_backup(curhost, spooldir, timeout)) { log_err("Master: error occurred while" " backing up %s.\n", curhost->host); exit(2); } exit(0); } curhost = curhost->next; } /* Now hang out and wait for everything to finish up. */ while (i) { if (wait(&status) == taperpid) { log_err("Master: taper died!\n"); } else i--; } log_info("Master: waiting for taper to finish.\n"); /* Allow these things to fail silently when the taper is dead. */ (void)write(taperpipe[1], DUMP_DONE "\n", sizeof(DUMP_DONE)+1); (void)waitpid(taperpid, &status, 0); /* Now fire off something to generate and send reports. */ switch (vfork()) { case 0: (void)execlp(LIBEXECDIR "/report", "report", NULL); perror("Error: couldn't exec report generator"); _exit(3); case -1: perror("Couldn't spawn report generator"); return 1; } /* TODO: unlock everything. */ return 0; }