sybdump.c

#ifdef __sun
#	include <alloca.h>
#	include <sys/sendfile.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <sys/param.h>
#include <signal.h>
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <poll.h>
#include <signal.h>
#include <bzlib.h>
#include <zlib.h>

#include <sybfront.h>
#include <sybdb.h>

#ifndef __GNUC__
#	define __unused
#endif

static DBPROCESS *sybconnect(char *appname, char *user, char *pw, char *server);
static void sybinit(void);
static int err_handler(DBPROCESS *dbp, int severity, int dberr, int oserr,
    char *dberrstr, char *oserrstr);
static int msg_handler(DBPROCESS *dbp, DBINT msgno, int msgstate, int severity,
    char *msgtext, char *srvname, char *procname, int line);
static int dumper(DBPROCESS *dbp, const char *db), loader(DBPROCESS *dbp, const char *db);
static int transfer(const char *from, DBPROCESS *one, const char *to, DBPROCESS *two,
    unsigned short low, unsigned short high);
static void sighandler(int sig);

union {
	FILE	*pf;
	BZFILE	*bz;
	gzFile	 gz;
} dump;
enum {
	PLAIN, BZIP, GZIP
} dumptype = PLAIN;
static int level = 0, seekable = 0;

static struct addrinfo *ai;
static char ip[63];
static unsigned short port = 0;
static int	sock, debug = 0, Timeout = INFTIM;
static DBINT	awaiting = 0 /*, awaiting1 = 0 */; /* should be an array really */
static struct dumpinfo {
	const char	*db;
	DBPROCESS	*dbp;
} dumpinfo;

#define dprintf(format, ...)	if (debug) fprintf(stderr, "%s: " format "\n", \
	__func__, __VA_ARGS__)

static void
sybdrain(DBPROCESS *dbp)
{
	int	res;

	for (;;) {
		res = dbresults(dbp);
		dprintf("dbresults(%p) returned %d", dbp, res);
		if (res == NO_MORE_RESULTS)
			break;
	}
}

static int	stopnow = 0;

static void
sighandler(int sig)
{
	if (sig == SIGALRM) {
		fprintf(stderr, "Got SIGALRM -- leaving now.\n");
		exit(EX_UNAVAILABLE);
	}
	if (stopnow++ > 3) {
		fprintf(stderr, "Ok, repeated stop requests -- leaving.\n");
		exit(EX_UNAVAILABLE);
	} else
		fprintf(stderr, "Got signal %d, exiting\n", sig);
}

static void
sybstatements(DBPROCESS *dbp, const char * const commands[], const char *db,
    const char *address, unsigned short port, char *buf, size_t buflen)
{
	int	len;

	while (*commands) {
		len = snprintf(buf, buflen, *commands, db, address, port) + 2;
		if (len >= (int)buflen) {
			fprintf(stderr, "%s: provided buffer is not big "
			    "enough\n", __func__);
			exit(EX_SOFTWARE);
		}
		dprintf("\t%s", buf);
		/* XXX For some reason, we must append spaces */
		buf[len - 2] = ' '; buf[len - 1] = '\0';
		dbcmd(dbp, buf);
		buf += len;
		buflen -= len;
		commands++;
	}
	awaiting = 3101; /* May get a notice about db being busy */
	if (dbsqlexec(dbp) == FAIL) {
		fprintf(stderr, "failed to send above statements to server\n");
		exit(EX_PROTOCOL);
	}

	dprintf("commands above sent to server %p", dbp);

	if (awaiting == 0)
		exit(EX_PROTOCOL);

	awaiting = 412402; /* syb_open() failure */
	len = dbresults(dbp);
	dprintf("dbresults(%p) returned %d", dbp, len);
#if 0
	dbpoll(dbp, -1, &dbp, &len);
	dprintf("dbpoll(%p) returned %d", dbp, len);
	len = dbresults(dbp);
	dprintf("dbresults(%p) returned %d", dbp, len);
#endif

	if (awaiting == 0)
		exit(EX_PROTOCOL);

}

static int
intraccept(int s)
{
	int	result;
#if 0
	struct pollfd	 pollfd;

	pollfd.fd = s;
	pollfd.events = POLLRDNORM;
	result = poll(&pollfd, 1, 10000 /* 10 seconds */);
	switch (result) {
	case 0:
		fprintf(stderr, "Timeout (%d seconds) awaiting connection\n",
		    10000/1000);
		exit(EX_PROTOCOL);
	case -1:
		perror("poll");
		exit(EX_PROTOCOL);
	case 1:
		dprintf("Incoming connection detected on %d", s);
		break;
	default:
		fprintf(stderr, "Unexpected value %d returned by poll\n",
		    result);
		exit(EX_SOFTWARE);
	}
#endif

	for (;;) {
		result = accept(s, NULL, 0);
		if (result >= 0)
			break;
		if (stopnow)
			exit(EX_UNAVAILABLE);
		if (errno == EINTR)
			continue;
		perror("accept");
		exit(EX_SOFTWARE);
	}

	dprintf("socket %d accepted", result);
	if (fcntl(result, F_SETFL, O_NONBLOCK) == -1) {
		perror("fcntl(..., O_NONBLOCK)");
		exit(EX_OSERR);
	}

	return result;
}

static void
close_dump(void)
{
	switch (dumptype) {
	case BZIP:
		BZ2_bzclose(dump.bz);
		break;
	case GZIP:
		gzclose(dump.gz);
		break;
	case PLAIN:
		fclose(dump.pf);
	}
}

static int
loader(DBPROCESS *dbp, const char *db)
{
	char		 buf[65536*4];
	ssize_t		 size;
	off_t		 offset = 0;
	int		 incoming = 10, result = 1, use_sf;
	int		 timeout = 1000;
#ifdef SF_NODISKIO
	off_t		 sent;
	int		 flags = 0;
#else
	size_t		 sent;
#endif
	struct pollfd	 pollfds[2];
	struct stat	 sb;
	int		 sybsock = DBIORDESC(dbp);
	const char	*commands[] = {
		"load database %s from 'pipe::P%s:%hu'",
		"online database %s",
		NULL
	};
	const char	*optional[] = {
		"use %s",
		"EXEC sp_post_xpload",
		NULL
	};

	if (dumptype == PLAIN) {
		if (fstat(fileno(dump.pf), &sb)) {
			perror("fstat");
			exit(EX_NOINPUT);
		}
		use_sf = seekable;
		dprintf("stdin is %sa seekable (%d), will %suse sendfile",
		    use_sf ? "" : "not ", use_sf, use_sf ? "" : "not ");
	} else
		use_sf = 0;

	sybstatements(dbp, commands, db, ip, port, buf, sizeof buf);

	awaiting = 8009; /* failure */
	pollfds[0].fd = sybsock;
	pollfds[0].events = POLLRDNORM|POLLIN;
	pollfds[1].fd = sock;
	pollfds[1].events = POLLWRNORM|POLLOUT|POLLRDNORM|POLLIN;

	for (;;) {
		int	err;

		err = poll(pollfds, 2, timeout);
		if (err == 0) {
			if (incoming) {
				dprintf("Will wait for %d more %d-second "
				    "periods for incoming connection",
				    incoming--, timeout/1000);
				continue;
			}
			fprintf(stderr, "Giving up on ever receiving "
			    "connection for loading\n");
			exit(EX_PROTOCOL);
		}
		if (err == -1 && errno != EINTR) {
			perror("poll");
			close(incoming);
			return 1;
		}
		if (stopnow)
			exit(EX_UNAVAILABLE);

		if (pollfds[0].revents) {
			dprintf("server had something to say (socket %.*sready)", 
			    pollfds[1].revents ? 0 : 4, "not ");
			dbresults(dbp);
			if (pollfds[1].fd == sock && awaiting == 0)
				exit(EX_PROTOCOL);
		}

		if (pollfds[1].revents) {
			if (pollfds[1].fd == sock) {
				incoming = intraccept(sock);
				pollfds[1].fd = incoming;
				dprintf("Accepted sock %d as %d. "
				    "Sending first chunk",
				    sock, incoming);
				timeout = Timeout;
				/*
				 * May get a notice about running sp_post_xpload
				 */
				awaiting = 3163;
			}
			if (use_sf) {
#ifdef SF_NODISKIO	/* BSD sendfile */
				flags = SF_NODISKIO - flags;
				size = sendfile(fileno(dump.pf), incoming, offset, 0,
				    NULL, &sent, flags);
				if (size > 0)
					dprintf("done sendfile-ing with %d. "
					    "sent %llu bytes",
					    flags, (unsigned long long)sent);
				if (size == 0 || errno == EAGAIN || errno == EBUSY) {
					offset += sent;
					if (offset == sb.st_size) {
						result = 0;
						goto out;
					}
					continue;
				}
				perror("sendfile");
				result = 1;
				goto out;
#else	/* Linux/Solaris sendfile */
				struct sendfilevec sfv = {
					fileno(dump.pf), 0
				};
				sfv.sfv_off = offset;
				sfv.sfv_len = sb.st_size - offset;

				dprintf("sending %d bytes out, offset now %lld",
				    (int)(sb.st_size - offset), (unsigned long long)offset);
				signal(SIGPIPE, SIG_IGN);
				size = sendfilev(incoming, &sfv, 1, &sent);
				signal(SIGPIPE, sighandler);
				offset += sent;
				if (size >= 0) {
					dprintf("sent everything out, offset now %lld",
					    (unsigned long long)offset);
					goto out;
				}
				if (errno == EAGAIN) {
					dprintf("sent some bytes out, offset now %lld",
					    (unsigned long long)offset);
					continue;
				}
				perror("sendfile");
				result = 1;
				goto out;
#endif
			} else {
				if (offset == 0) {
					static int notfirst;

					/* fill the buffer again */
					switch (dumptype) {
					case PLAIN:
						size = fread(buf, 1,
						    sizeof buf, dump.pf);
						if (size == 0) {
							if (ferror(stdin)) {
								perror("fread");
								result = 1;
							}
							goto out;
						}
						break;
					case GZIP:
						size = gzread(dump.gz, buf,
						    sizeof buf);
						if (size == -1) {
							fprintf(stderr, "gzread: "
							    "%s\n", gzerror(dump.gz,
								&notfirst));
							goto out;
						}
						break;
					case BZIP:
						size = BZ2_bzread(dump.bz, buf,
						    sizeof buf);
						if (size == -1) {
							fprintf(stderr, "bzread: "
							    "%s\n",
							    BZ2_bzerror(dump.bz,
								&notfirst));
							goto out;
						}
						break;
					}
					if (!notfirst) {
						dprintf("Read %lld. The dump starts "
						    "with `%.4s'", (long long)size, buf);
						notfirst = 1;
					}
				}

				if (size == 0) {
					result = 0;
					goto out;
				}
				dprintf("sending %d bytes (starting at %d)",
				    (int)size, (int)offset);
				signal(SIGPIPE, SIG_IGN);
				sent = write(incoming, buf + offset, size);
				signal(SIGPIPE, sighandler);
				dprintf("sent %d bytes (starting at %d)",
				    (int)sent, (int)offset);
				if (sent == -1) {
					perror("sending data");
					result = 1;
					goto out;
				}
				size -= sent;
				offset = (offset + sent) % sizeof buf;
			}
		}
	}

out:
	close(incoming);
	close_dump();

	dprintf("ended %ssuccessfully, socket %d closed, "
	    "awaiting server's confirmation...", result ? "un" : "", incoming);
	sybdrain(dbp);
	if (awaiting == 0) { /* Got a notice to run sp_post_xpload */
		fprintf(stderr, "%s: Server suggested, we run `%s', obeying\n",
		    __func__, "sp_post_xpload");
		sybstatements(dbp, optional, db, ip, port, buf, sizeof buf);
		sybdrain(dbp);
	}

	return result;
}

static int
dumper(DBPROCESS *dbp, const char *db)
{
	char		 buf[65536*4];
	ssize_t		 size;
	int		 incoming = 10, timeout = 1000;
	struct pollfd	 pollfds[2];
	int		 sybsock = DBIORDESC(dbp);
	const char	*commands[] = {
		"EXEC sp_dboption '%s', 'single user', 'TRUE'",
		"use %s",
		" EXEC sp_flushstats ",
		" checkpoint ",
		" use master ",
		" dump database %s to 'pipe::P%s:%hu' ",
		" EXEC sp_dboption '%s', 'single user', 'FALSE' ",
		" use %s ",
		" checkpoint ",
		NULL
	};

	sybstatements(dbp, commands, db, ip, port, buf, sizeof buf);

#if 0
	/* Await the message 602801, indicating success, up to 20 times */
	for (awaiting = 602801, incoming = 20; awaiting; incoming--)
		if (dbresults(dbp) == NO_MORE_RESULTS || !incoming)
			exit(EX_PROTOCOL);
#endif

	pollfds[0].fd = sock;
	pollfds[0].events = POLLWRNORM|POLLOUT|POLLRDNORM|POLLIN;
	pollfds[1].fd = sybsock;
	pollfds[1].events = POLLRDNORM;
	for (;;) {
		int	err;

		err = poll(pollfds, 2, timeout);
		if (err == 0) {
			if (incoming) {
				dprintf("Will wait for %d more %d-second "
				    "periods for incoming connection",
				    incoming--, timeout/1000);
				continue;
			}
			fprintf(stderr, "Giving up on ever receiving "
			    "connection for loading\n");
			exit(EX_PROTOCOL);
		}
		if (err == -1 && errno != EINTR) {
			perror("poll");
			close(incoming);
			return 1;
		}
		if (stopnow) {
			dprintf("Canceling operation on %p", dbp);
			close(incoming);
			dbcanquery(dbp);
			dbsettime(20); /* twenty seconds timeout */
			alarm(21); /* the above timeout seems faulty */
			sybdrain(dbp);
			exit(EX_UNAVAILABLE);
		}


		if (pollfds[1].revents)
			dbresults(dbp);

		if (pollfds[0].revents) {
			if (pollfds[0].fd == sock) {
				incoming = intraccept(sock);
				pollfds[0].fd = incoming;
				
				dprintf("Accepted sock %d as %d. "
				    "Reading first chunk",
				    sock, incoming);
				timeout = Timeout;
			}
			size = read(incoming, buf, sizeof buf);
			if (size == 0)
				break;
			if (size > 0) switch(dumptype) {
			case GZIP:
				gzwrite(dump.gz, buf, size);
				continue;
			case BZIP:
				BZ2_bzwrite(dump.bz, buf, size);
				continue;
			case PLAIN:
				fwrite(buf, 1, size, dump.pf);
				continue;
			}
			else if (errno == EAGAIN)
				continue;
			else {
				perror("reading from socket");
				close(incoming);
				return 1;
			}
		}
	}

	close(incoming);
	close_dump();
	dprintf("ended, %d closed, awaiting server's confirmation...", incoming);
	sybdrain(dbp);

	return 0;
}

static int
err_handler(DBPROCESS *dbp, int severity, int dberr, int oserr,
    char *dberrstr, char *oserrstr)
{

	if (1 || dberr != SYBESMSG) {
		if (dberrstr == NULL)
			dberrstr = "(null)";
		if (oserrstr == NULL)
			oserrstr = "(null)";

		fprintf(stderr, "ERR(%p): %s (%d) %s (%d)\n", dbp,
		    dberrstr, dberr, oserrstr, oserr);
		fflush(stderr);
	}
	return (dbp == NULL) || (DBDEAD(dbp)) ? INT_EXIT : INT_CANCEL;
}

static int
msg_handler(DBPROCESS *dbp, DBINT msgno, int msgstate, int severity,
    char *msgtext, char *srvname, char *procname, int line)
{
	int	msglen;
	const char	*commandsdump[] = {
		"dump database %s to 'pipe::P%s:%hu'",
		"EXEC sp_dboption '%s', 'single user', 'FALSE'",
		"use %s"
		"checkpoint",
		NULL
	};

	msglen = strlen(msgtext);

	/* The last char is often new-line, so we output one less */
	if (msgtext[msglen-1] == '\n')
		msglen--;
	if (debug)
		fprintf(stderr, "%s: (%d, %d) %.*s (%sawaited)\n", srvname, (int)msgno,
		    msgstate, msglen, msgtext,
		    awaiting ? awaiting == msgno ? "" : "not " : "nothing ");
	else
		fprintf(stderr, "%s: %.*s\n", srvname, msglen, msgtext);

	if (awaiting && msgno == awaiting) {
		dprintf("Caught the expected message #%d", msgno);
		awaiting = 0;
	}

	if (msgno == 412501 && dbgetuserdata(dbp) &&
	    (msgtext = strstr(msgtext, "AWAITING ON"))) {
		if (sscanf(msgtext + sizeof("AWAITING ON"),
		    "%s %hu", ip, &port) == 2) {
			char	buf[1023];
			dprintf("Got the ip and the port number: %s:%hu",
			    ip, port);
			sybstatements(dumpinfo.dbp, commandsdump, dumpinfo.db,
			    ip, port, buf, sizeof buf);
		} else
			dprintf("Could not get ip:port from %s", msgtext);
	}

	return 0;
}

static void
sybinit(void)
{

	if (dbinit() == FAIL)
		exit(EX_SOFTWARE);

	dberrhandle(err_handler);
	dbmsghandle(msg_handler);
}

static DBPROCESS *
sybconnect(appname, user, pw, server)
	/* const */ char *appname, *user, *pw, *server;
{
	LOGINREC	*login;
	DBPROCESS	*dbp;

	login = dblogin();
	DBSETLUSER(login, user);
	DBSETLPWD(login, pw);
	DBSETLAPP(login, appname);

	dbp = dbopen(login, server);
	if (dbp == NULL) {
		fprintf(stderr, "Connecting to %s as %s failed\n",
		    server ? server : "null", user ? user : "null");
		exit(EX_DATAERR);
	}
	dprintf("dbopen succeeded (%p) for %s@%s", dbp, user ? user : "null",
	    server ? server : "null");
	dbloginfree(login);
	return dbp;
}

static void
getsock(unsigned short low, unsigned short high)
{
	int		err;

	sock = socket(ai->ai_family, SOCK_STREAM, 0);
	if (sock < 0) {
		perror("socket");
		exit(EX_TEMPFAIL);
	}
	for (port = low; port <= high; port++) {
		switch (ai->ai_family) {
		case AF_INET:
			((struct sockaddr_in *)ai->ai_addr)->sin_port = htons(port);
			break;
		case AF_INET6:
			((struct sockaddr_in6 *)ai->ai_addr)->sin6_port = htons(port);
			break;
		}
		if (bind(sock, (struct sockaddr *)ai->ai_addr, ai->ai_addrlen) == 0)
			break;
	}

	if (port > high) {
		fprintf(stderr, "Could not bind to any port in the "
		    "%hu-%hu range.\n", low, high);
		exit(EX_UNAVAILABLE);
	}

	err = getnameinfo((struct sockaddr *)ai->ai_addr, ai->ai_addrlen,
	    ip, sizeof ip, NULL, 0, NI_NUMERICHOST);
	if (err) {
		fprintf(stderr, "getnameinfo failed: %s", gai_strerror(err));
		exit(EX_SOFTWARE);
	}

	if (listen(sock, 1)) {
		perror("listen");
		exit(EX_TEMPFAIL);
	}

	dprintf("Bound to port %hu", port);
}

static int
transfer(const char *from, DBPROCESS *one, const char *to, DBPROCESS *two,
    unsigned short low, unsigned short high)
{
	DBPROCESS	*other = NULL;
#if 1
	struct pollfd	 pollfds[2];
#endif
	char	buf[1023];
	int	result = 0, sybres, twospoke = 0;
	const char	*commandsload[] = {
		"load database %s from 'pipe::S:%s:%hu'",
		"online database %s",
		NULL
	};
	const char	*postload[] = {
		"use %s",
		"EXEC sp_post_xpload",
		NULL
	};
	const char	*predump[] = {
		"EXEC sp_dboption '%s', 'single user', 'TRUE'",
		"EXEC sp_flushstats",
		"use %s",
		"checkpoint ",
		"use master",
		NULL
	};
	/*
	 * First we tell the loading database to start waiting
	 */
	/*
	 * This makes sure, the statement constructed will contain
	 * the correct low port:
	 */
	sprintf(ip, "%hu", low);
	sybstatements(one, predump, from, NULL, 0, buf, sizeof buf);
	sybdrain(one);
	sybstatements(two, commandsload, to, ip, high, buf, sizeof buf);

	dumpinfo.db = from;
	dumpinfo.dbp = one;
	dbsetuserdata(two, (void *)1);
	dbresults(two); /* one more line of results expected */

	if (!port /* port and ip must be initialized by msg_handler */) {
		fprintf(stderr, "port remains uninitialized\n");
		exit(EX_PROTOCOL);
	}

	dprintf("Transfer initiated (to %s:%hu), dbpolling", ip, port);
	awaiting = 412402; /* Make sure, the source had no problems */
	dprintf("Checking %p for message #%d", one, awaiting);
	dbresults(one);
	dbresults(one);
	dbresults(one);
	if (awaiting == 0) {
		dprintf("Dumper (%p) reported difficulties, exiting", one);
		exit(EX_UNAVAILABLE);
	}
#if 1
	pollfds[0].fd = DBIORDESC(one);
	pollfds[1].fd = DBIORDESC(two);
	pollfds[0].events = pollfds[1].events = POLLRDNORM;
	for (;;) {
		sybres = poll(pollfds, 2, Timeout);
		switch (sybres) {
		case -1:
			perror("poll");
			continue;
		default:
			dprintf("poll returned %d, examining", sybres);
		}
		if (stopnow) {
			dbcanquery(one);
			dbcanquery(two);
			dbsettime(20); /* twenty seconds timeout */
			alarm(41); /* the above timeout seems faulty */
			sybdrain(one);
			sybdrain(two);
			exit(EX_UNAVAILABLE);
		}
		if (pollfds[0].revents) {
			dprintf("poll reported readiness of %p", one);
			sybres = dbresults(one);
			dprintf("dbresults(%p) returned %d", one, sybres);
			if (sybres != SUCCEED) {
				other = two;
				goto out;
			}
		}

		if (pollfds[1].revents) {
			dprintf("poll reported readiness of %p", two);
			twospoke = 1;
			awaiting = 304201; /* LOAD is complete */
			sybres = dbresults(two);
			dprintf("dbresults(%p) returned %d", two, sybres);
			if (awaiting == 0) {
				other = two;
				goto out;
			}
			if (sybres != SUCCEED) {
				other = one;
				goto out;
			}

		}
	}
out:
	awaiting = 3163; /* May get a notice about running sp_post_xpload */
	dprintf("Out of the poll loop, draining %p", other);
	if (other == two && !twospoke) {
		dprintf("Actually, %p never spoke, skipping it", two);
	} else
		sybdrain(other);

#else
	while (dbpoll(NULL, -1, &other, &sybres) != FAIL) {
		if (other == NULL)
			continue;
		dprintf("dbpoll returned %d for %p", sybres, other);
		if (dbresults(other) == NO_MORE_RESULTS) {
			if (other == one) {
				if (!twospoke)
					break;
			} else
				twospoke = 1;
			sybdrain(other == one ? two : one);
		}
	}
	if (other == NULL)
		dprintf("Other is %p. Strange (sybres is %d)", other, sybres);
#endif

	if (awaiting == 0) { /* Got a notice to run sp_post_xpload */
		fprintf(stderr, "%s: Server suggested, we run `%s', obeying\n",
		    __func__, "sp_post_xpload");
		sybstatements(two, postload, to, ip, port, buf, sizeof buf);
		sybdrain(two);
	}

	return result;
}

int
main(int argc, char *argv[])
{
	char		*U = "sa", *P = NULL, *A = NULL, *S = NULL, *me = NULL;
	char		*U2 = "sa", *P2 = NULL, *S2 = NULL;
	const char	*db, *db2, *fname = NULL;
	int		(*processor)(DBPROCESS *, const char *db) = NULL;
	int		 opt;
	void		(*sigint)(int) = sighandler;
	unsigned short	 high=65000, low = 1025; /* port numbers range */
	DBPROCESS	*one, *two;

	A = basename(argv[0]);
	while ((opt = getopt(argc, argv, "T:f:z:j:dDLS:s:U:u:P:p:c:C:M:t")) != -1) {
		switch (opt) {
		case 'd':
			debug = 1;
			break;
		case 'f':
			fname = optarg;
			break;
		case 'z': case 'j':
			level = strtol(optarg, NULL, 0);
			if (level < 1 || level > 9) {
				fprintf(stderr, "%s: specified level must be "
				    "an integer from 1 to 9", optarg);
				exit(EX_USAGE);
			}
			if (opt == 'z')
				dumptype = GZIP;
			else
				dumptype = BZIP;
		case 'c':
			low = strtol(optarg, NULL, 0);
			if (low == 0 || low > high) {
				fprintf(stderr, "%s port should be an integer "
				    "%s or at %hd\n", "low", "under", high);
				exit(EX_USAGE);
			}
			break;
		case 'C':
			high = strtol(optarg, NULL, 0);
			if (high == 0 || low > high) {
				fprintf(stderr, "%s port should be an integer "
				    "%s or at %hd\n", "high", "over", low);
				exit(EX_USAGE);
			}
			break;
		case 'L':
			processor = loader;
			break;
		case 'D':
			processor = dumper;
			break;
		case 'M':
			me = optarg;
			break;
		case 'P':
			P = optarg;
			break;
		case 'p':
			P2 = optarg;
			break;
		case 'S':
			S = optarg;
			break;
		case 's':
			S2 = optarg;
			break;
		case 'T':
			Timeout = atoi(optarg);
			dprintf("translating %s into %d seconds timeout",
			    optarg, Timeout);
			dbsettime(Timeout); /* twenty seconds timeout */
			Timeout *= 1000; /* translate into milliseconds */
			break;
		case 't':
			sigint = SIG_IGN;
			break;
		case 'U':
			U = optarg;
			break;
		case 'u':
			U2 = optarg;
			break;
		case 'h':
		default:
			fputs("No help for the wicked. Not yet...\n", stderr);
			exit(0);
		}
	
	}

	if (argc <= optind) {
		fprintf(stderr, "Database must be specified\n");
		exit(EX_USAGE);
	}

	/* Use handler to say goodbye to servers in case of a popular signal */
	signal(SIGINT, sigint);
	signal(SIGQUIT, sighandler);
	signal(SIGPIPE, sighandler);
	signal(SIGTERM, sighandler);
	signal(SIGALRM, sighandler);
	sybinit();
	atexit(dbexit);
	one = sybconnect(A, U, P, S);
	db = argv[optind];

	if (argc == optind + 1) {
		if (S2 || P2) {
			fprintf(stderr, "The second database not specified\n");
			exit(EX_USAGE);
		}
		/*
		 * Either dump or a load of one database are specified...
		 */

		/* If the action is not specified, guess it from the program's name.*/
		if (processor == NULL)
			processor = strstr(A, "load") == NULL ? dumper : loader;

		/* If the filename is given (-f), open it, otherwise use stdin/out */
		if (processor == dumper) {
			if (fname == NULL)
				dump.pf = stdout;
			else {
				dump.pf = fopen(fname, "wb");
				if (dump.pf == NULL) {
					perror(fname);
					return EX_IOERR;
				}
			}
		} else {
			if (fname == NULL)
				dump.pf = stdin;
			else {
				dump.pf = fopen(fname, "rb");
				if (dump.pf == NULL) {
					perror(fname);
					return EX_NOINPUT;
				}
			}
		}

		if (level) {
			char mode[3] = { 'w' };
			if (processor != dumper) {
				if (dumptype == GZIP)
					goto gzip;
				else
					goto bzip;
			}
			mode[1] = level;
			if (dumptype == GZIP) {
				dump.gz = gzdopen(fileno(dump.pf), mode);
			} else  {
				dump.bz = BZ2_bzdopen(fileno(dump.pf), mode);
			}
			if (dump.pf == NULL) {
				perror("can not initialize compressor");
				exit(EX_IOERR);
			}
		} else if (processor == loader) {
			int	magic = 0;

			magic = getc(dump.pf);
			dprintf("Input's first byte is %x.", magic);
			magic = ungetc(magic, dump.pf);
			if (lseek(fileno(dump.pf), 0, SEEK_SET)) {
				if (errno == ESPIPE)
					seekable = 0;
				else {
					perror("lseek on input");
					exit(EX_NOINPUT);
				}
			} else
				seekable = 1;
			switch (magic) {
			case EOF:
				perror(fname ? fname : "stdin");
				exit(EX_NOINPUT);
			bzip:
			case 'B':
				dumptype = BZIP;
				dprintf("%s-ing input.", "Bunzip2");
				dump.bz = BZ2_bzdopen(fileno(dump.pf), "rb");
				break;
			gzip:
			case '\037':
				dumptype = GZIP;
				dump.gz = gzdopen(fileno(dump.pf), "rb");
				dprintf("%s-ing input.", "Gunzip");
				break;
			case 'V': /* Sybase dumps begin with "VOL" */
				dumptype = PLAIN;
				break;
			default:
				fprintf(stderr, "Unexpected first character "
				    "in the input.\n");
				exit(EX_NOINPUT);
			}
			if (dump.pf == NULL) {
				perror("can not initialize compressor");
				exit(EX_NOINPUT);
			}
			if (magic && !seekable && dumptype != PLAIN) {
				fprintf(stderr, "Automatic checking for "
				    "compression is currently broken for"
				    "non-seekable stdin.\nPlease, pipe it"
				    " through `%scat' or specify an -%c"
				    " option explicitly\n",
				    dumptype == GZIP ? "gz" : "bz",
				    dumptype == GZIP ? 'z' : 'j');
				exit(EX_USAGE);
			}
		}

		if (me == NULL) {
			me = alloca(MAXHOSTNAMELEN);
			if (gethostname(me, MAXHOSTNAMELEN)) {
				perror("Use -M switch: gethostname");
				exit(EX_UNAVAILABLE);
			}
		}
		dprintf("Will use name %s as my address.", me);
		opt = getaddrinfo(me, NULL, NULL, &ai);
		if (opt) {
			fprintf(stderr, "%s: can't figure `%s' out: %s\n", A, me,
			    gai_strerror(opt));
			exit(EX_NOHOST);
		}

		getsock(low, high);
		return processor(one, db);
	}

	/*
	 * Ok, direct transfer is requested.
	 * Parse the second set of command-line options
	 */

	db2 = argv[optind + 1];
	two = sybconnect(A, U2, P2, S2);

	return transfer(db, one, db2, two, low, high);
}