aboutsummaryrefslogtreecommitdiffstats
path: root/dumper.c
diff options
context:
space:
mode:
Diffstat (limited to 'dumper.c')
-rw-r--r--dumper.c1075
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;
+}