diff options
Diffstat (limited to 'dumper.c')
-rw-r--r-- | dumper.c | 1075 |
1 files changed, 1075 insertions, 0 deletions
diff --git a/dumper.c b/dumper.c new file mode 100644 index 0000000..f11acc6 --- /dev/null +++ b/dumper.c @@ -0,0 +1,1075 @@ +#include "config.h" +#include "conf.h" +#include "err.h" +#include "lock.h" +#include "strutil.h" + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <netinet/in.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +#ifdef NEED_LIBUTIL +#include <libutil.h> +#endif + +#ifdef NEED_STRINGS +#include <strings.h> +#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; +} |