diff options
-rw-r--r-- | COPYRIGHT | 27 | ||||
-rw-r--r-- | Makefile | 144 | ||||
-rw-r--r-- | README | 541 | ||||
-rw-r--r-- | TODO | 27 | ||||
-rw-r--r-- | client.conf | 4 | ||||
-rw-r--r-- | compat.c | 29 | ||||
-rw-r--r-- | compat.h | 37 | ||||
-rw-r--r-- | conf.c | 336 | ||||
-rw-r--r-- | conf.h | 77 | ||||
-rw-r--r-- | config.h | 100 | ||||
-rw-r--r-- | disklist | 5 | ||||
-rw-r--r-- | dumper.c | 1075 | ||||
-rw-r--r-- | dumptypes | 5 | ||||
-rw-r--r-- | err.c | 129 | ||||
-rw-r--r-- | err.h | 13 | ||||
-rw-r--r-- | getconf.c | 38 | ||||
-rw-r--r-- | hostlist | 1 | ||||
-rw-r--r-- | label.c | 92 | ||||
-rw-r--r-- | lock.c | 15 | ||||
-rw-r--r-- | lock.h | 9 | ||||
-rw-r--r-- | report.sh | 18 | ||||
-rw-r--r-- | restore.c | 240 | ||||
-rw-r--r-- | run-dump.c | 490 | ||||
-rwxr-xr-x | run-estimate | bin | 0 -> 41816 bytes | |||
-rw-r--r-- | run-estimate.c | 145 | ||||
-rw-r--r-- | server.conf | 24 | ||||
-rw-r--r-- | smrsh.8 | 104 | ||||
-rw-r--r-- | smrsh.c | 221 | ||||
-rw-r--r-- | strutil.c | 41 | ||||
-rw-r--r-- | strutil.h | 8 | ||||
-rw-r--r-- | tapeio.c | 166 | ||||
-rw-r--r-- | tapeio.h | 34 | ||||
-rw-r--r-- | taper.c | 297 |
33 files changed, 4492 insertions, 0 deletions
diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..0169319 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,27 @@ +$Id: COPYRIGHT,v 1.2 1999/02/02 23:32:36 shmit Exp $ + +Copyright (c) 1998, Brian Cully <shmit@kublai.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The names of contributers to this software may not be used to endorse + or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6a453ed --- /dev/null +++ b/Makefile @@ -0,0 +1,144 @@ +# $Id: Makefile,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $ + +PREFIX=/home/shmit +#PREFIX=/usr/local/ticra +BINDIR=$(PREFIX)/bin +LIBEXECDIR=$(PREFIX)/libexec/ticra +DATADIR=$(PREFIX)/libdata/ticra + +CC=gcc +INSTALL=install + +BINMODE=755 +DATAMODE=644 + +# This is needed for FreeBSD machines. +OSLIBS=-lutil + +# SysV Machines. +#OSLIBS=-lnsl -lsocket + +PATHFLAGS=-DBINDIR=\"$(BINDIR)\" -DLIBEXECDIR=\"$(LIBEXECDIR)\" \ + -DDATADIR=\"$(DATADIR)\" +COPTS=-Wall -g -O3 -pipe -DDEBUG + +CFLAGS=$(COPTS) $(PATHFLAGS) +LIBS=$(OSLIBS) + +# Source files. +SOURCES=dumper.c run-dump.c taper.c label.c conf.c strutil.c err.c compat.c \ + smrsh.c lock.c + +# Programs to build. +CLIENT_BIN=smrsh +CLIENT_LIBEXEC=run-dump run-estimate +SERVER_BIN=dumper restore label getconf +SERVER_LIBEXEC=taper report + +# Examples files to install. +EXAMPLES=client.conf server.conf disklist hostlist dumptypes + +.c.o: + $(CC) $(CFLAGS) -c $< +.sh: + cp -p $< $@ + chmod 755 $@ + +all: server client tags + +server: $(SERVER_BIN) $(SERVER_LIBEXEC) + +client: $(CLIENT_BIN) $(CLIENT_LIBEXEC) + +tags: $(SOURCES) + ctags $(SOURCES) + +install: install-server install-client install-examples + +install-server: server + mkdir -p $(BINDIR) + mkdir -p $(LIBEXECDIR) + $(INSTALL) -c -m $(BINMODE) $(SERVER_BIN) $(BINDIR) + $(INSTALL) -c -m $(BINMODE) $(SERVER_LIBEXEC) $(LIBEXECDIR) + +install-client: client + mkdir -p $(BINDIR) + mkdir -p $(LIBEXECDIR) + $(INSTALL) -c -m $(BINMODE) $(CLIENT_BIN) $(BINDIR) + $(INSTALL) -c -m $(BINMODE) $(CLIENT_LIBEXEC) $(LIBEXECDIR) + +install-examples: + mkdir -p $(DATADIR)/examples + $(INSTALL) -c -m $(DATAMODE) $(EXAMPLES) $(DATADIR)/examples + +clean: + rm -f $(SERVER_BIN) $(SERVER_LIBEXEC) $(CLIENT_BIN) $(CLIENT_LIBEXEC) \ + tags *.o + +# +# Binary dependencies. +# +DUMPER_OBJS=dumper.o conf.o lock.o strutil.o err.o compat.o +dumper: $(DUMPER_OBJS) + $(CC) $(CFLAGS) -o $@ $(DUMPER_OBJS) $(LIBS) + +RUN-DUMP_OBJS=run-dump.o conf.o strutil.o err.o compat.o +run-dump: $(RUN-DUMP_OBJS) + $(CC) $(CFLAGS) -o $@ $(RUN-DUMP_OBJS) $(LIBS) + +RUN-ESTIMATE_OBJS=run-estimate.o conf.o strutil.o err.o +run-estimate: $(RUN-ESTIMATE_OBJS) + $(CC) $(CFLAGS) -o $@ $(RUN-ESTIMATE_OBJS) + +TAPER_OBJS=taper.o tapeio.o conf.o lock.o strutil.o err.o compat.o +taper: $(TAPER_OBJS) + $(CC) $(CFLAGS) -o $@ $(TAPER_OBJS) $(LIBS) + +RESTORE_OBJS=restore.o tapeio.o conf.o lock.o strutil.o compat.o +restore: $(RESTORE_OBJS) + $(CC) $(CFLAGS) -o $@ $(RESTORE_OBJS) + +LABEL_OBJS=label.o tapeio.o conf.o lock.o strutil.o err.o compat.o +label: $(LABEL_OBJS) + $(CC) $(CFLAGS) -o $@ $(LABEL_OBJS) + +GETCONF_OBJS=getconf.o conf.o strutil.o err.o +getconf: $(GETCONF_OBJS) + $(CC) $(CFLAGS) -o $@ $(GETCONF_OBJS) + +SMRSH_OBJS=smrsh.o +smrsh: $(SMRSH_OBJS) + $(CC) $(CFLAGS) -o $@ $(SMRSH_OBJS) + +# +# Dependency rules. +# +dumper.o: conf.h config.h err.h lock.h + +run-dump.o: conf.h strutil.h config.h + +run-estimate.o: conf.h config.h err.h + +taper.o: tapeio.h conf.h config.h err.h lock.h + +restore.o: tapeio.h conf.h config.h lock.h + +label.o: tapeio.h conf.h config.h lock.h + +getconf.o: conf.h config.h + +smrsh.o: config.h + +tapeio.o: tapeio.h err.h strutil.h + +conf.o: conf.h strutil.h config.h err.h + +err.o: err.h config.h + +lock.o: lock.h config.h + +strutil.o: strutil.h config.h + +compat.o: compat.h config.h + +config.h: compat.h Makefile @@ -0,0 +1,541 @@ +$Id: README,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $ + + TICRA + ----- + Brian Cully <shmit@rcn.com> + +Contents: + 0 - Introduction + 1 - Installation Instructions + 1.1 - Building the Package + 1.2 - Configuring + 1.2.1 - Server Configuration + 1.2.1.1 - Hostlist + 1.2.1.2 - Server.conf + 1.2.2 - Client Configuration + 1.2.2.1 - Disklist + 1.2.2.2 - Dumptypes + 1.2.2.3 - Client.conf + 2 - Common Problems + 3 - How it works + 3.1 - Gathering Estimates + 3.2 - Gathering the Dumps + 3.2.1 - The Master + 3.2.2 - The Slaves + 3.2.2.1 - Run-dump + 3.3 - Putting It on to the Tape + 3.3.1 - Writing the Dump + 3.4 - Sending out the Reports + 4 - What Still Needs to be Done + Appendix A - Example Configuration Files + Appendix B - The Tape Format + Appendix C - Design Decisions + +Part 0: Introduction +-------------------- +TICRA is a backup system. Wow. Toot toot toot. + +Part 1: Installation Instructions +--------------------------------- +Installation consists of three parts, building the package, installing +the binaries and sample data files, and configuring the client and +server. + +Before you build the package you should know whether the target machine +is a client or a server, as the build process is a bit different +depending on the target. + +1.1: Building the Package +------------------------- +Before you build the package, you should poke through the Makefile and +set it up for your environment. In particular, you should verify that +the binaries are installed into the proper place, and that the OSLIBS +match the platform upon which you are trying to install. + +You should also poke through config.h and make sure things are set up as +you would like there. Of particular interest is the PATH_RSH macro, +which you should set to point to either rsh or something equivalent +(like ssh). + +Once that's set up, you can build the binaries. As stated above, you +build differently depending on whether you're installing the client or +server. If you're installing the server, you type: + +% make server + +If you're installing the client, you type: + +% make client + +Of course, if you don't care you can just type: + +% make + +and both will be built. + +Watch the build process for errors and correct them if you can. Then +proceed to install the binaries when everything is compiled properly. + +Then you install the binaries and example files. To do this for the +server you type: + +% make install-server + +To install the client, you type: + +% make install-client + +Finally, to install the example files: + +% make install-examples + +If you want to install everything at once you can just: + +% make install + +1.2: Configuring +---------------- +Now that everything is installed you have to do the configuration. The +process is very different depending on whether you are installing a +client or a server, so they're covered in different sections. + +All configuration files have fields seperated on word boundries, ignore +blank lines, and ignore everything after a `#'. + +The one thing that must be taken care of for both client and server is +the setup of the backup account in the password file. The backup +account must be named `ticra' and must use the shell `smrsh' which was +installed into BINDIR as specified in the Makefile (probably +/usr/local/ticra/bin). + +1.2.1: Server Configuration +--------------------------- +To configure the server you must edit two files: server.conf and +hostlist. Hostlist contains a list of hosts to which the server will +connect and make backups. Server.conf contains the rest of the server +configuration. + +1.2.1.1: Hostlist +----------------- +The hostlist file is simply a list of hosts to which the backup operator +should connect. It consists of one entry per line, each of which is a +hostname. + +The backup operator must be able to connect to each host in the hostlist +in order to back it up. The connection process uses rsh, or whatever was +specified in the config.h file, so you must make sure that the backup +operator can connect to the target host /before/ adding it to the +hostlist file, otherwise an error will occur during the backup. + +1.2.1.2: Server.conf +-------------------- +The server.conf file contains the real configuration information for the +server. The variables that can be set and their functions are specified +below: + +manager: The e-mail address of the backup operator. This address + gets the reports generated by the reporter process. + +logdir: The directory that contains the info and error logs. + +infolog: The name of the log that contains informational output + generated by the dumper process. It can be set to `-' to + log to standard output instead of a file. + +errorlog: The name of the log that contains error output generated + by the dumper process. It can be set to `-' to log to + standard error instead of a file. + +timeout: How many seconds to wait on a opening a connection + before giving up. + +hostlist: The filename that contains the hostlist. + +spooldir: The directory that backups will be stored in before they + are written to disk. + +spoolsize: The maximum amount of data that can be written to the + spooldir, in megabytes. + +tapedev: The name of the tape device. + +tapesize: The size of the tape, in megabytes. + +labelstr: The label on the tape to be written must start with this + in order for data to be written. + +1.2.2: Client Configuration +--------------------------- +The client end of things has three files that must be configured +before it can be backed up. These three files are `disklist', +`dumptypes', and `client.conf'. + +Disklist contains a list of disks to be backed up, what kind of +backup will be performed on the disk, whether or not compression +is enabled, and finally, what type of authentication should be used +on the disk. + +Client.conf describes what each kind of backup is and what port +will be used for unauthenticated backups. + +Because TICRA uses rsh to communicate between the client and server, +you must make sure that the backup server can initiate a connection +to the client over rsh (or the equivalent that was specified in +config.h). + +1.2.2.1: Disklist +----------------- +The disklist contains one entry per line, in the following format: + +filesystem dumptype compression auth-type + +These fields are described below: + +filesystem: The name of the device or directory to be backed up. + +dumptype: The name of the type of dump to use, the exact + meaning of which is set in the `dumptypes' file. + +compression: `uncompressed' is the only type currently supported. + +auth-type: `noauth' is the only type currently supported. + +1.2.2.2: Dumptypes +------------------ +The dumptypes file contains the definition for the dumps which are +specified in the disklist. It consists of one entry per line, in the +following format: + +dumpname dumpline estimateline regexp + +These fields mean: + +dumpname: The name of type of dump, this is what's used in + `disklist'. + +dumpline: The command line that's used to dump this type + onto standard output. Simple substitution is + performed as follows: + %l - Dump level. + %v - Filesystem name (from disklist). + %% - % + +estimateline: The command line that's used to get a size + estimate for a filesystem. Substitution is + performed as in dumpline. + +regexp: The regular expression which extracts the size + in 1K blocks for a dump from the estimate. + +1.2.2.3: Client.conf +-------------------- +The client.conf file specifies what port should be used for +unauthenticated backups. + +The fields are: + +port: The port that will be used for unauthenticated + backups. Must be a number. + +Part 2: Common Problems +----------------------- +No one uses this, how can there be common problems? + +Part 3: How it Works +-------------------- +3.1: Gathering Estimates +------------------------ +The server machine first reads its hostlist and connects to each +host sequentially over an RSH pipe, asking it for a list of disks +to backup, what type of authentication to use (either `noauth' or +`kerbV'), and an estimate of how much space the dump will take up. +It uses this information to put together a schedule of which disks +to dump and at what level to dump. If the authentication type is +`noauth' then the dumper asks which port should be used to connect +to for the dump. + +3.2: Gathering the Dumps +------------------------ +3.2.1: The Master +----------------- +Once the estimates have been gathered and the schedule has been +worked out, the master dumper process then identifies itself as +such via setproctitle(3) (not available on all platforms). + +The master then forks and execs the taper process, opening pipes +to it in the process. It saves the open file descriptors for use +with the slaves. + +In order to do the actual dumping the master then forks once for +each entry in the hostlist, each of these processes will be referred +to as slaves. + +Once all the slaves have finished, the master tells the taper that +there is no further data to be written to the tape and waits for +the taper to finish writing everything to the tape. When the taper +is done, the master finishes off by running the report generating +script, `report'. + + +3.2.2: The Slaves +----------------- +The slaves do all the real work with regards to client communication. +They first open up an RSH pipe to their client which is used as a +control session. The RSH pipe execs the run-dump process and the +two (dumper and run-dump) exchange control information and error +messages over the pipe. + +The slave tells the run-dump process on the client to open up a +socket on the port specified in the client's client.conf file (which +was given to the server during the estimation gathering process). + +The slave then goes through each disk it knows about iteratively +requesting a dump from the run-dump on the client. For each disk +it opens a new connection to the client over the negotiated port +and waits for data to start coming down the socket (from now on, +we'll refer to this as the `data connection'). + +Normally, the slave will just put the data from the data connection +into a file in the spooling directory (as specified in the server.conf +file) in the format: + +<hostname>:<diskname> + +where <hostname> is the name specified in the hostlist file on the +server and <diskname> is the name specified in the disklist file +on the client. + +Once the dump is completely written to the spooling directory, the +slave tells the taper to write that file to tape (using the pipe +passed down from the master). + +If, however, the estimate for the disk says it will be too big for +the spooling directory (also specified in the server.conf file), +then the slave will instead dump the data directly to the taper. + +When all disks are finished, the slave tells run-dump that it's +done and closes the RSH control connection. + +3.2.2.1: Run-dump +----------------- +Run-dump reads through the disklist and the client.conf file. It +responds to requests from the server to execute a dump and open a +connection on a given port (specified in the client.conf file and +passed over to the server as part of the estimate process). + +When a dump is requested for a disk, run-dump executes the process +for the dump type specified in the disklist. Run-dump does very +crude syntax substitution on the command line, allowing it to run +arbitrary commands to do the dump. This means that the only real +difference between the types `dump' and `tar' is that the type +`dump' is capable of calculating estimates and using leveled dumps, +whereas tar is not. + +The only other thing run-dump responds to is a port command. This +tells run-dump to open up a socket on a given port so that the +server can connect to it. This is the channel that is used to send +the dump over to the server. + +For obvious reasons, the port command needs to come before the dump +command. + +3.3: Putting It on to the Tape +------------------------------ +Once the taper process is created, it rewinds the tape and checks +the label against the one in the server.conf file, if the label +doesn't match, it sends an error and dies. + +If the tape matches the label, however, the taper will wait on the +control pipes waiting for to be told either to dump something to +tape or to quit. + +The taper can be told to either write a file to the tape or write +a stream of data. In either event, it first writes a file header +describing the data and then writes the data. + +When the taper is done writing to the tape, and the master dumper +has told the taper to quit, the taper will write an end of tape +marker; in the future this will be used to store more than one +periods worth of dumps on a single tape. The taper then exits. + +3.3.1: Writing the Dump +----------------------- +It is expected that writing to tape will be slower than grabbing +the dump from the target machine (although, this is certainly not +always the case!), moreover, there will be multiple dumper processes +all writing to the spooling disk, but only one taper. Because of +this, the taper has to keep a queue of things to write to tape +while it is busy actually writing data to the tape. + +To accomplish this, when the taper first receives a request to +write something to tape, it forks off a child to accomplish the +task of actually writing a file to tape, while the parent sits on +the control connection waiting for more requests and adding them +to the queue. + +When the child dies (after it's done writing to tape, or on an +error), the parent forks off another child to deal with the next +item in the queue. This goes on until the master dumper says there +aren't going to be any more things added to the queue. + +When the master dumper says everything is finished, the taper stops +forking off children and goes through the rest of the queue +iteratively. + +3.4: Sending out the Reports +---------------------------- +The reporter doesn't do much of anything fancy right now, it just +mails the error and info logs to the manager and renames the log +files to contain a date stamp. + +Part 4: What Still Needs to be Done +----------------------------------- +Check out the file TODO in this directory for a list of things that +still need to be accomplished. + +In particular, this document lies in many places. Notably, the +estimate process doesn't actually grab an estimate, there is no +scheduling being done, backup levels aren't being used (so, currently, +there is no difference between tar and dump), and there's no facility +to dump straight to tape (so you'd better make sure that none of +your dumps are bigger than the spooling directory). + +Appendix A: Example Configuration Files +--------------------------------------- + +-----------+ + |server.conf| + +-----------+ +# To whom mail should go. +manager shmit@erols.com + +# Where to log informational and error messages. +logdir /usr/local/ticra/var/log +infolog - +errorlog - + +# How many seconds to wait on a connection before giving up. +timeout 10 + +# List of machines to backup. +hostlist /usr/local/ticra/libdata/ticra/hostlist + +# Where dumps will be spooled before being dumped to tape. +spooldir /var/holding +spoolsize 50 + +# The tape device on which to dump. +tapedev /dev/nrst0 +tapesize 50 +labelstr TEST00 + +--------+ + |hostlist| + +--------+ +localhost + +-----------+ + |client.conf| + +-----------+ +# Port to use for non-authenticated dumps. +port 31337 + +# Syntax lines for dump types. Substitution rules are: +# %l - dump level +# %v - volume name +# %% - % +dump "dump -%luf - %v" +tar "tar -clf - %v" + +--------+ + |disklist| + +--------+ +# Filesystem dumptype compression auth-type +#--------------------------------------------------------- +/tmp tar uncompressed noauth +/var dump uncompressed noauth + +Appendix B: The Tape Format +--------------------------- +Physical Start of Tape +TAPE LABEL: 8192 bytes +Tape EOF +START OF TAPE: 8192 bytes +Tape EOF +Repeat for each file on tape: + FILE HEADER: 8192 bytes + <hostname>:<diskname> <date> + <hostname>: Machine from which the disk came. + <diskname>: Name of disk on said machine. + <date>: Date of dump in DDMMYYYY format + Tape EOF +STOP OF TAPE: 8192 bytes +Tape EOF + +Appendix C: Design Decisions +---------------------------- +This package has been designed primarily to back up data in a Very +Large Organization, although it should work even at small companies +without any hassle. + +When I thought `Very Large Organization', I saw an organization +that necessarily had many different departments, all of whom want +as much control as possible as to how to back up their data. After +all, if the Head of Finance wants to change /var/db/account to be +backup up with tar instead of dump, he shouldn't have to call over +to the backup operator to do it. + +Moreover, I didn't want the backup operator to have to spend his +days toiling with lots of different hosts and departments trying +to get things done their way, and should be able to spend as little +time as possible dealing with other people and the lack of reasonable +communication which only complicates and confuses the matter. + +Keep in mind, that I work backups, and I'm lazy, so I wanted to +make things as easy as possible for me. However, I think you'll +find that my decisions don't make life any harder for anyone, but +make it more convenient (all true lazy bones try and make life +easier for everyone). + +To that end, I've tried to make as little as possible configurable +on the server end of things, instead pushing the responsibility +over to the client end. + +I came from an AMANDA background, so I was fairly biased when I +started writing this software. I kept many of the ideas of AMANDA +but thought that a few things needed changing. + +In particular, I wanted to separate the disks to be backed up from +the hosts to be backed up, and keep the disks in a separate file +on the client so the client managers could configure which disks +are backed up and the type of backup that is used. + +I also wanted to have the client define how the dump was done, +instead of the server, this simplified things in a larger environment +for a number of reasons: + + * Across a diverse network with many different types of + clients and file systems you can't be guaranteed that a + program called `dump' or `tar' will exist or will do the + right thing. Instead of having the backup operator know + these details that are largely unimportant to him, I opted + to have the person that runs the client in question fill + in the blanks. + + * The size of the server.conf file would become rather + unwieldy if it had to contain entries for every possible + type of dump that would be used in an organization. + + * There is no need for the server to know what kind of data + is in the backup stream. + +The other main goal was to have software that actually worked. My +experience with AMANDA was mostly loathsome, mainly due to what I +considered really poor design decisions, like the use of UDP, +embedded data flow information, the poor use of RSH. I've endeavored +to fix these design problems with what I consider to be better +solutions. + +Apropos of that, I also endeavored to make the code clean and used +a style the moves in that direction. If you're interested in it +you should read style(9) on any *BSD system. I also used ANSI +prototypes and function prefixes. I feel they're easy to read, and +the main argument I see against them (namely, compatibility) I +don't find valid with this software (I use so many POSIXism that +if you have them, you'll have a compiler that can handle ANSI C). @@ -0,0 +1,27 @@ +$Id: TODO,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $ + +What needs to be done, in order of priority, before I will consider +this package done: + +Mail reports. This includes making sure everything gets logged properly, +and not just dumping to stderr and being ignored. + +Use backup estimates, dump according to size. + +Don't dump immediately when holding disk is full and dump volume can +fit. + +Make more use of return values. + +Handle dumping straight to tape when the holding disk isn't big enough +to hold the dump. + +Properly schedule taping when more than one volume can't be dumped to +the holding disk. + +Do backup scheduling. Actually use backup levels instead of only doing +level 0's (which is extremely lame). + +Give this thing away to the rest of the world. + +Unify common sources. diff --git a/client.conf b/client.conf new file mode 100644 index 0000000..74d73f5 --- /dev/null +++ b/client.conf @@ -0,0 +1,4 @@ +# $Id: client.conf,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $ + +# Port to use for non-authenticated dumps. +port 31337 diff --git a/compat.c b/compat.c new file mode 100644 index 0000000..c123d07 --- /dev/null +++ b/compat.c @@ -0,0 +1,29 @@ +#include "config.h" +#include "compat.h" + +#include <stdarg.h> +#include <stdio.h> + +RCSID("$Id: compat.c,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $"); + +#ifdef NEED_SETPROCTITLE +void +setproctitle(const char *fmt, ...) +{ +} +#endif + +#ifdef NEED_SNPRINTF +int +snprintf(char *str, size_t size, const char *fmt, ...) +{ + va_list ap; + int rc; + + va_start(ap, fmt); + rc = vsprintf(str, fmt, ap); + va_end(ap); + + return rc; +} +#endif diff --git a/compat.h b/compat.h new file mode 100644 index 0000000..6bfc524 --- /dev/null +++ b/compat.h @@ -0,0 +1,37 @@ +/* $Id: compat.h,v 1.2 2000/02/22 22:50:33 shmit Exp $ */ + +#ifndef COMPAT_H +# define COMPAT_H + +#include <sys/types.h> + +#ifdef __NetBSD__ +#define sockopt_t int +#endif + +#ifdef __FreeBSD__ +#define NEED_LIBUTIL +#define sockopt_t int +#endif + +#ifdef __svr4__ +#define NEED_STRINGS +#define NEED_SETPROCTITLE +#define NEED_SNPRINTF +#define sockopt_t int +#endif + +#ifdef __linux__ +#define NEED_SETPROCTITLE +#define sockopt_t char +#endif + +#ifdef NEED_SETPROCTITLE +void setproctitle(const char *fmt, ...); +#endif + +#ifdef NEED_SNPRINTF +int snprintf(char *str, size_t size, const char *fmt, ...); +#endif + +#endif @@ -0,0 +1,336 @@ +#include "config.h" +#include "conf.h" +#include "err.h" +#include "strutil.h" + +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +RCSID("$Id: conf.c,v 1.2 2000/02/22 22:50:33 shmit Exp $"); + +/* Granularity to use when dynamically allocating storage. */ +const int MALLOC_GRANULARITY = 4; + +/* TODO: GC configuration and disklist. */ + +config_t configuration[] = { + { MANAGER, STROPT, 0, -1 }, + { LOGDIR, STROPT, 0, -1 }, + { INFOLOG, STROPT, 0, -1 }, + { ERRORLOG, STROPT, 0, -1 }, + { HOSTLIST, STROPT, 0, -1 }, + { TIMEOUT, NUMOPT, 0, -1 }, + { RETRIES, NUMOPT, 0, -1 }, + { SPOOLDIR, STROPT, 0, -1 }, + { SPOOLSIZE, NUMOPT, 0, -1 }, + { TAPEDEV, STROPT, 0, -1 }, + { TAPESIZE, NUMOPT, 0, -1 }, + { LABELSTR, STROPT, 0, -1 }, + { PORTOPT, NUMOPT, 0, -1 }, + { 0, 0, 0, 0 } +}; + +dump_t *dumptypes = NULL; +int numtypes = 0; + +static config_t * +find_option(const char *name) +{ + config_t *p = configuration; + + while (p->name) { + if (!strcmp(name, p->name)) + break; + p++; + } + + if (!p->name) + return NULL; + + return p; +} + +/* + * I really hate this sort of code, but I really hate what I'd have + * to do without it. + */ +const config_t * +findopt(const char *name) +{ + config_t *p; + + p = find_option(name); + if (!p || p->numvalue == -1) + return NULL; + + return p; +} + +int +read_config(const char *config_filen) +{ + FILE *config_file; + char buffer[MAXLINE]; + int lineno = 0; + + config_file = fopen(config_filen, "r"); + if (!config_file) { + perror("Error: Couldn't open config file"); + return -1; + } + + while (fgets(buffer, sizeof(buffer), config_file)) { + config_t *optptr; + char *index; + char option[MAXLINE], optarg[MAXLINE]; + + lineno++; + index = getword(buffer, option); + if (!index) + continue; + + index = getword(index, optarg); + if (!index) { + fprintf(stderr, + "Error: option `%s' has no argument" + " on line %d.\n", + option, lineno); + return -1; + } + + optptr = find_option(option); + if (!optptr) { + fprintf(stderr, + "Error: option `%s' on line %d" + " doesn't exist.\n", + option, lineno); + return -1; + } + + switch (optptr->type) { + case STROPT: + index = malloc(sizeof(char)*strlen(optarg)); + if (!index) { + perror("Error: couldn't malloc"); + return -1; + } + + strcpy(index, optarg); + optptr->strvalue = index; + optptr->numvalue = 0; + break; + case NUMOPT: + optptr->numvalue = strtol(optarg, &index, 10); + if (*index) { + fprintf(stderr, + "Error: value `%s' on line %d is not" + " a number.\n", + optarg, lineno); + return -1; + } + break; + } + } + + fclose(config_file); + return 0; +} + +/* + * Find a dumptype with a given name. + */ +const dump_t * +find_dumptype(const char *name) +{ + int i; + + for (i = 0; i < numtypes; i++) { + if (!strncmp(dumptypes[i].d_name, name, + sizeof(dumptypes[i].d_name))) + return &dumptypes[i]; + } + return NULL; +} + +/* + * Read the dumptypes out of the configuration file. + */ +int +read_dumptypes(const char *filen) +{ + FILE *list; + char buffer[MAXLINE]; + + list = fopen(filen, "r"); + if (!list) { + err("Couldn't open dumptypes `%s': %s.\n", + filen, strerror(errno)); + return -1; + } + + dumptypes = malloc(sizeof(dump_t) * MALLOC_GRANULARITY); + if (!dumptypes) { + err("Couldn't allocate enough space for dumptypes table: %s.\n", + strerror(errno)); + return -1; + } + + while (fgets(buffer, sizeof(buffer), list)) { + char *index; + + index = getword(buffer, dumptypes[numtypes].d_name); + if (!index) + continue; + + index = getword(index, dumptypes[numtypes].d_cmdline); + if (!index) { + err("Dumptype `%s' has no command line.", + dumptypes[numtypes].d_name); + free(dumptypes); + return -1; + } + + index = getword(index, dumptypes[numtypes].d_estline); + if (!index) { + dumptypes[numtypes].d_estline[0] = '\0'; + dumptypes[numtypes].d_regexp[0] = '\0'; + } else { + index = getword(buffer, dumptypes[numtypes].d_regexp); + if (!index) { + err("Dumptype `%s' has an estimate line," + " but not a corresponding regexp.", + dumptypes[numtypes].d_name); + free(dumptypes); + return -1; + } + } + + /* Allocate more buffer space, if it's needed. */ + if ((++numtypes % MALLOC_GRANULARITY) == 0) { + dumptypes = realloc(dumptypes, + sizeof(dump_t) * + (numtypes + MALLOC_GRANULARITY)); + if (!dumptypes) { + err("Couldn't allocate enough space for" + " dumptypes table: %s.\n", strerror(errno)); + free(dumptypes); + return -1; + } + } + } + fclose(list); + + /* Free up unused memory. */ + /* + * TODO: find out if this should actually barf the entire process. + * it really shouldn't fail, so I think it's a non-issue, but it + * should be thought about anyway. + */ + dumptypes = realloc(dumptypes, sizeof(dump_t) * numtypes); + if (!dumptypes) { + err("Couldn't free unused space in dumptypes table: %s.\n", + strerror(errno)); + free(dumptypes); + return -1; + } + + return 0; +} + +/* + * TODO: decide if non-fatal errors (like invalid dump types) should + * barf everything. + */ +disklist_t * +read_disklist(const char *filen) +{ + FILE *list; + disklist_t *dl = NULL; + char buffer[MAXLINE]; + + list = fopen(filen, "r"); + if (!list) { + err("Couldn't open disklist `%s': %s.\n", + filen, strerror(errno)); + return NULL; + } + + while (fgets(buffer, sizeof(buffer), list)) { + char vol[MAXLINE], type[MAXLINE], comp[MAXLINE], auth[MAXLINE]; + char *index; + disklist_t *p; + + index = getword(buffer, vol); + if (!index) + continue; + + index = getword(index, type); + if (!index) { + err("Volume `%s' has no dump type.\n", vol); + return NULL; + } + + index = getword(index, comp); + if (!index) { + strcpy(comp, DC_UNCOMP); + strcpy(auth, DA_NONE); + } else { + index = getword(index, auth); + if (!index) + strcpy(auth, DA_NONE); + } + + p = malloc(sizeof(disklist_t)); + if (!p) { + err("Couldn't malloc space for disklist node: %s.\n", + strerror(errno)); + return NULL; + } + p->next = dl; + + strncpy(p->disk.vol, vol, sizeof(p->disk.vol)); + + p->disk.type = find_dumptype(type); + if (!p->disk.type) { + err("Dump type `%s' isn't defined in dumptypes.\n", + type); + free(p); + return NULL; + } + + /* Extract the compression options. */ + if (!strcmp(comp, DC_UNCOMP)) + p->disk.comp = UNCOMP; + else if (!strcmp(comp, DC_COMP)) + p->disk.comp = COMP; + else if (!strcmp(comp, DC_COMP9)) + p->disk.comp = COMP9; + else if (!strcmp(comp, DC_COMPFAST)) + p->disk.comp = COMPFAST; + else { + err("Error: compression type %s isn't supported.\n", + comp); + free(p); + return NULL; + } + + /* Extract method of authentication. */ + if (!strcmp(auth, DA_NONE)) + p->disk.auth = NOAUTH; + else if (!strcmp(auth, DA_RSH)) + p->disk.auth = RSH; + else { + err("Error: auth type %s isn't supported.\n", auth); + free(p); + return NULL; + } + + dl = p; + } + + fclose(list); + return dl; +} @@ -0,0 +1,77 @@ +/* $Id: conf.h,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $ */ + +#ifndef CONF_H +# define CONF_H + +/* Option names. */ +#define MANAGER "manager" +#define LOGDIR "logdir" +#define INFOLOG "infolog" +#define ERRORLOG "errorlog" +#define HOSTLIST "hostlist" +#define TIMEOUT "timeout" +#define RETRIES "retries" +#define SPOOLDIR "spooldir" +#define SPOOLSIZE "spoolsize" +#define TAPEDEV "tapedev" +#define TAPESIZE "tapesize" +#define LABELSTR "labelstr" +#define PORTOPT "port" +#define DUMPOPT "dump" +#define TAROPT "tar" +#define SIZEOPT "size" +#define SIZE_RE "size_re" + +/* Compression names. */ +#define DC_UNCOMP "uncompressed" +#define DC_COMP "compressed" +#define DC_COMP9 "comp-best" +#define DC_COMPFAST "comp-fast" + +/* Auth names. */ +#define DA_NONE "noauth" +#define DA_RSH "rsh" + +typedef enum { STROPT, NUMOPT } opt_t; +typedef enum { UNCOMP, COMP, COMP9, COMPFAST } comp_t; +typedef enum { NOAUTH, RSH } auth_t; + +struct _config_t { + char *name; + opt_t type; + char *strvalue; + long numvalue; +}; +typedef struct _config_t config_t; + +struct _dump_t { + char d_name[BUFFSIZE]; + char d_cmdline[MAXPATHLEN]; + char d_estline[MAXPATHLEN]; + char d_regexp[BUFFSIZE]; +}; +typedef struct _dump_t dump_t; + +struct _disk_t { + char vol[MAXPATHLEN]; + const dump_t *type; + comp_t comp; + auth_t auth; +}; +typedef struct _disk_t disk_t; + +struct _disklist_t { + disk_t disk; + struct _disklist_t *next; +}; +typedef struct _disklist_t disklist_t; + +const config_t *findopt(const char *name); +int read_config(const char *filen); + +const dump_t *find_dumptype(const char *name); +int read_dumptypes(const char *filen); + +disklist_t *read_disklist(const char *filen); + +#endif diff --git a/config.h b/config.h new file mode 100644 index 0000000..e4c55b3 --- /dev/null +++ b/config.h @@ -0,0 +1,100 @@ +/* $Id: config.h,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $ */ + +#ifndef CONFIG_H +# define CONFIG_H + +#include <limits.h> +#include <sys/param.h> +#include <sys/time.h> + +#include "compat.h" + +#ifndef PREFIX +#define PREFIX "/home/shmit/src/ticra" +#endif + +#ifndef BINDIR +#define BINDIR PREFIX +#endif + +#ifndef LIBEXECDIR +#define LIBEXECDIR PREFIX +#endif + +#ifndef DATADIR +#define DATADIR PREFIX +#endif + +/* Path to place debugging files. */ +#define TMPPATH "/tmp" + +/* Default config file to load. */ +#define SERVER_CONFIG_FILE DATADIR "/server.conf" +#define CLIENT_CONFIG_FILE DATADIR "/client.conf" + +/* Path to rsh or replacement. Should probably go into config file. */ +#define PATH_RSH "/usr/local/bin/ssh" + +/* If random syscalls fail with EAGAIN, how many times to try. */ +#define MAXRETRIES 10 + +/* --- You probably don't want to edit any of the below. --- */ + +/* + * Dumper client/server protocol below, edit at your own risk. + */ + +/* Initialization protocol. */ +#define INIT_REQ "WHAT DO YOU WANT" +#define INIT_ACK "INFORMATION" + +/* Single transaction protocol. */ +#define REQ_ACK "ACK" +#define REQ_NACK "NACK" + +#define PORT_REQ "PORT" +#define DUMP_SENDME "SENDME" +#define DUMP_DONE "DONE" +#define DUMP_ERROR "ERROR" + +/* Disk request fields. */ +#define DISK_REQ "IHAVE" +#define DISK_NAME "NAME" +#define DISK_AUTH "AUTH" +#define DISK_SIZE "SIZE" +#define DISK_LEVEL "LEVEL" + +/* Auth request field. */ +#define AUTH_NOAUTH "NONE" +#define AUTH_RSH "RSH" + +/* + * Taper protocol below, same as above. + */ +#define TAPER_START "YOU WONT GET IT" +#define TAPER_STOP "BY HOOK OR BY CROOK, WE WILL" + +/* + * OS macros/defines below. + */ +#define RCSID(x) static const char rcsid[] __attribute__((__unused__)) = x + +/* Buffer size to use to dump to tape. */ +#define BUFFSIZE 8192 + +#ifndef MAXLINE +#define MAXLINE 256 +#endif + +#ifndef MAXPATHLEN +#define MAXPATHLEN 128 +#endif + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 256 +#endif + +#ifndef FD_SETSIZE +#define FD_SETSIZE 256 +#endif +#endif diff --git a/disklist b/disklist new file mode 100644 index 0000000..00a6bd9 --- /dev/null +++ b/disklist @@ -0,0 +1,5 @@ +# $Id: disklist,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $ +# Filesystem dumptype compression auth-type +#--------------------------------------------------------- +/tmp tar uncompressed noauth +/var dump uncompressed noauth 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; +} diff --git a/dumptypes b/dumptypes new file mode 100644 index 0000000..59e498a --- /dev/null +++ b/dumptypes @@ -0,0 +1,5 @@ +# $Id: dumptypes,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $ +# Name dumpline estimateline regexp +#------------------------------------------------------------------------- +dump "dump -%luf - %v" "dump -S%l %v" "estimated (.*) tape blocks" +tar "tar -clf - %v" @@ -0,0 +1,129 @@ +#include "config.h" +#include "err.h" + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <unistd.h> + +#ifdef NEED_STRINGS +#include <strings.h> +#endif + +RCSID("$Id: err.c,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $"); + +FILE *errfile = NULL; +FILE *infofile = NULL; + +void +err(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + printf(DUMP_ERROR "\n"); + fflush(stdout); + vfprintf(stderr, fmt, ap); + fflush(stderr); + va_end(ap); +} + +/* TODO: use stream reading instead of raw mode. */ +void +readerr(int infd, const char *host) +{ + static char buffer[MAXLINE]; + char *i, *p; + static ssize_t leftover = 0; + ssize_t readb; + + while ((readb = read(infd, buffer+leftover, + sizeof(buffer)-leftover)) > 0) { + p = buffer; + while ((i = index(p, '\n'))) { + *i = '\0'; + + log_err("%s: %s\n", host, p); + p = i+1; + } + + if (p-buffer < readb) { + leftover = readb-(p-buffer); + memmove(buffer, p, leftover); + + /* TODO: dynamically allocate in this case. */ + if (leftover == sizeof(buffer)) { + fprintf(stderr, "DEBUG: really big line.\n"); + buffer[sizeof(buffer)] = '\0'; + log_err("%s: %s\n", host, buffer); + leftover = 0; + } + } + } +} + +void +initlog(const char *infon, const char *errorn) +{ + const char *i; + + i = rindex(infon, '/'); + if (i) + i++; + else + i = infon; + if (!strcmp(i, "-")) + infofile = stdout; + else { + unlink(infon); + infofile = fopen(infon, "w"); + if (!infofile) { + fprintf(stderr, + "Error: couldn't write log file `%s'," + " using stderr: %s.\n", infon, strerror(errno)); + infofile = stdout; + } + } + + i = rindex(errorn, '/'); + if (i) + i++; + else + i = errorn; + if (!strcmp(i, "-")) + errfile = stderr; + else { + unlink(errorn); + errfile = fopen(errorn, "w"); + if (!errfile) { + fprintf(stderr, + "Error: couldn't write log file `%s'," + " using stderr: %s.\n", + errorn, strerror(errno)); + errfile = stderr; + } + } +} + +void +log_info(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(infofile, fmt, ap); + va_end(ap); +} + +void +log_err(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(errfile, fmt, ap); + va_end(ap); +} @@ -0,0 +1,13 @@ +/* $Id: err.h,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $ */ + +#ifndef ERR_H +# define ERR_H + +void err(const char *fmt, ...); +void readerr(int infd, const char *host); + +void initlog(const char *infon, const char *errorn); +void log_err(const char *fmt, ...); +void log_info(const char *fmt, ...); + +#endif diff --git a/getconf.c b/getconf.c new file mode 100644 index 0000000..69f6b69 --- /dev/null +++ b/getconf.c @@ -0,0 +1,38 @@ +#include "config.h" +#include "conf.h" + +#include <stdio.h> + +RCSID("$Id: getconf.c,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $"); + +int +main(int argc, char *argv[]) +{ + if (read_config(SERVER_CONFIG_FILE) == -1) + return 1; + + while (--argc) { + const config_t *option; + + option = findopt(argv[argc]); + if (!option) + fprintf(stderr, + "Error: option `%s' doesn't exist or" + " is not set.\n", argv[argc]); + else + switch (option->type) { + case NUMOPT: + printf("%ld\n", option->numvalue); + break; + case STROPT: + printf("%s\n", option->strvalue); + break; + default: + fprintf(stderr, + "Error: don't understand option type" + " (this shouldn't happen).\n"); + } + } + + return 0; +} diff --git a/hostlist b/hostlist new file mode 100644 index 0000000..2fbb50c --- /dev/null +++ b/hostlist @@ -0,0 +1 @@ +localhost @@ -0,0 +1,92 @@ +#include "config.h" +#include "conf.h" +#include "lock.h" +#include "tapeio.h" + +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +RCSID("$Id: label.c,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $"); + +char *progname; + +void +usage() +{ + fprintf(stderr, "Usage: %s label\n", progname); +} + +int +main(int argc, char *argv[]) +{ + const config_t *option; + tapelabel_t label; + int fd; + + if (read_config(SERVER_CONFIG_FILE) == -1) { + perror("Error: couldn't read config file"); + return 1; + } + + option = findopt(LABELSTR); + if (!option) { + fprintf(stderr, "Error: " LABELSTR " option isn't set.\n"); + return 1; + } + + if (memcmp(argv[1], option->strvalue, strlen(option->strvalue))) + fprintf(stderr, + "Warning: desired label doesn't match " + "labelstring option.\n"); + + option = findopt(TAPEDEV); + if (!option) { + fprintf(stderr, "Error: " TAPEDEV " option isn't set.\n"); + return 1; + } + + fd = open(option->strvalue, O_WRONLY); + if (fd == -1) { + perror("Error: couldn't open tape device"); + return 1; + } + + printf("Rewinding device..."); + fflush(stdout); + if (mt_rewind(fd) == -1) { + perror("Error: couldn't rewind tape device"); + close(fd); + return 1; + } + + printf("writing label `%s'...", argv[1]); + fflush(stdout); + + strncpy(label.labelstr, argv[1], sizeof(label.labelstr)); + label.date = time(NULL); + if (writelabel(fd, &label) == -1) { + close(fd); + return 1; + } + + if (mt_weof(fd, 1) == -1) { + perror("Error: couldn't write EOF marker"); + close(fd); + return 1; + } + + printf("rewinding device..."); + fflush(stdout); + if (mt_rewind(fd) == -1) { + perror("Error: couldn't rewind tape device"); + close(fd); + return 1; + } + + printf("done.\n"); + close(fd); + return 0; +} @@ -0,0 +1,15 @@ +#include "config.h" +#include "lock.h" + +RCSID("$Id: lock.c,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $"); + +int +lock_create(const char *name) +{ + return 0; +} + +void +lock_delete(const char *name) +{ +} @@ -0,0 +1,9 @@ +/* $Id: lock.h,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $ */ + +#ifndef LOCK_H +# define LOCK_H + +int lock_create(const char *name); +void lock_delete(const char *name); + +#endif diff --git a/report.sh b/report.sh new file mode 100644 index 0000000..ea5f98a --- /dev/null +++ b/report.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +# $Id: report.sh,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $ + +manager=`getconf manager` +logdir=`getconf logdir` +infolog=`getconf infolog` +errorlog=`getconf errorlog` + +if [ "$infolog" != "-" ]; then + Mail -s "backup info report" "$manager" < "$logdir/$infolog" + rm -f "$logdir/$infolog" +fi + +if [ "$errorlog" != "-" -a -s "$logdir/$errorlog" ]; then + Mail -s "backup error report" "$manager" < "$logdir/$errorlog" + rm -f "$logdir/$errorlog" +fi diff --git a/restore.c b/restore.c new file mode 100644 index 0000000..3133c89 --- /dev/null +++ b/restore.c @@ -0,0 +1,240 @@ +#include "config.h" +#include "conf.h" +#include "lock.h" +#include "tapeio.h" +#include "strutil.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <time.h> +#include <unistd.h> + +RCSID("$Id: restore.c,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $"); + +char *progname; + +void +err(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "Error: "); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +void +warn(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "Warning: "); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +void +sig_pipe(int signo) +{ + return; +} + +int +skip(int tapefd, int count) +{ + if (mt_fsf(tapefd, count) == -1) { + err("couldn't fast-forward to next file header: %s.\n", + strerror(errno)); + return -1; + } + return 0; +} + +int +restore(int tapefd, fileheader_t *header, int pipeflag) +{ + char buffer[BUFFSIZE]; + struct tm *tm; + ssize_t readb, total_read = 0; + int outfd; + + tm = localtime(&header->date); + fprintf(stderr, "Restoring %s:%s dumped on %02d-%02d-%04d.\n", + header->host, header->vol, + tm->tm_mday, tm->tm_mon+1, tm->tm_year+1900); + + if (pipeflag) + outfd = STDOUT_FILENO; + else + outfd = STDOUT_FILENO; + + do { + ssize_t wroteb = 0; + + readb = read(tapefd, buffer, sizeof(buffer)); + if (readb == -1) { + err("couldn't read dump from tape: %s.\n", + strerror(errno)); + return -1; + } + + total_read += readb; + while (wroteb < readb) { + ssize_t n; + + n = write(outfd, buffer+wroteb, readb - wroteb); + if (n == -1) { + if (errno == EAGAIN) + continue; + else if (errno == EPIPE) { + warn("pipe reader has quit, skipping" + " to next file.\n"); + return skip(tapefd, 1); + } + err("couldn't write dump to disk: %s.\n", + strerror(errno)); + return -1; + } + wroteb += readb; + } + } while (readb); + + return skip(tapefd, 0); +} + +void +usage() +{ + fprintf(stderr, "Usage: %s [-h] [-p] hostname [volume]\n", progname); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + struct tm *tm; + const config_t *option; + char *labelstr, *tapedev, *hostname = NULL, *volume = NULL; + tapelabel_t label; + int pipeflag = 0; + int tapefd; + + progname = argv[0]; + + if (argc < 2 || argc > 4) + usage(); + + while (--argc) { + if (*argv[argc] == '-') { + switch (*(argv[argc]+1)) { + case 'h': + usage(); + break; + case 'p': + pipeflag = 1; + break; + default: + usage(); + } + } else { + if (!volume) + volume = argv[argc]; + else if (!hostname) + hostname = argv[argc]; + else + usage(); + } + } + + /* Re-order arguments if only one was given. */ + if (!hostname) { + if (volume) { + hostname = volume; + volume = NULL; + } else + usage(); + } + + if (read_config(SERVER_CONFIG_FILE) == -1) + return 1; + + option = findopt(LABELSTR); + if (!option) { + err(LABELSTR " hasn't been set.\n"); + return 1; + } + labelstr = option->strvalue; + + option = findopt(TAPEDEV); + if (!option) { + err(TAPEDEV " hasn't been set.\n"); + return 1; + } + tapedev = option->strvalue; + + tapefd = open(tapedev, O_RDONLY); + if (tapefd == -1) { + err("couldn't open tape device %s: %s.\n", tapedev, + strerror(errno)); + return -1; + } + + if (mt_rewind(tapefd) == -1) { + err("couldn't rewind tape: %s.\n", strerror(errno)); + return 1; + } + + if (readlabel(tapefd, &label) == -1) + return 2; + if (memcmp(label.labelstr, labelstr, sizeof(labelstr))) { + err("label `%s' doesn't match expected `%s'.\n", + label.labelstr, labelstr); + return 2; + } + + tm = localtime(&label.date); + fprintf(stderr, "Tape `%s' created on %02d-%02d-%04d.\n", + label.labelstr, tm->tm_mday, tm->tm_mon+1, tm->tm_year+1900); + + for (;;) { + fileheader_t header; + + if (readheader(tapefd, &header) == -1) { + err("couldn't read header from tape: %s.\n", + strerror(errno)); + break; + } + + if (header.type == STOPMARK) + break; + + if (!strcmp(header.host, hostname)) { + if (volume && strcmp(header.vol, volume)) { + fprintf(stderr, + "Skipping %s:%s.\n", + header.host, header.vol); + if (skip(tapefd, 1) == -1) + break; + continue; + } + + if (restore(tapefd, &header, pipeflag) == -1) + break; + } else { + fprintf(stderr, "Skipping %s:%s.\n", + header.host, header.vol); + + if (skip(tapefd, 1) == -1) + break; + } + } + + close(tapefd); + return 0; +} diff --git a/run-dump.c b/run-dump.c new file mode 100644 index 0000000..a5a34ff --- /dev/null +++ b/run-dump.c @@ -0,0 +1,490 @@ +#include "config.h" +#include "conf.h" +#include "err.h" +#include "strutil.h" + +#include <errno.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#ifdef NEED_LIBUTIL +#include <libutil.h> +#endif + +#ifdef NEED_STRINGS +#include <strings.h> +#endif + +RCSID("$Id: run-dump.c,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $"); + +short exit_status = -1; + +void +sig_pipe(int signo) +{ + err("SIGPIPE caught.\n"); + exit_status = -2; +} + +void +sig_child(int signo) +{ + int status; + + if (wait(&status) == -1) { + err("Couldn't clean up child: %s.\n", strerror(errno)); + return; + } + + exit_status = WEXITSTATUS(status); +} + +char ** +genargs(const char *expression, int level, const char *vol) +{ + /* TODO: generate dynamically. */ + static char *args[32]; + char word[MAXLINE]; + const char *p = expression; + char *sub; + int i = 0; + + while (i < 31 && (p = getword(p, word))) { + sub = index(word, '%'); + if (!sub) { + args[i] = malloc(strlen(word)+1); + strcpy(args[i], word); + } else { + args[i] = malloc(MAXLINE); + + *sub = '\0'; + switch (*++sub) { + case 'l': + snprintf(args[i], MAXLINE, "%s%d%s", + word, level, ++sub); + break; + case 'v': + snprintf(args[i], MAXLINE, "%s%s%s", + word, vol, ++sub); + break; + case '%': + snprintf(args[i], MAXLINE, "%s%%%s", + word, ++sub); + break; + default: + fprintf(stderr, + "Don't understand substitution" + " code `%%%c'\n", *sub); + return NULL; + } + } + i++; + } + sub = index(word, '%'); + if (!sub) { + args[i] = malloc(strlen(word)+1); + strcpy(args[i], word); + } else { + args[i] = malloc(MAXLINE); + + *sub = '\0'; + switch (*++sub) { + case 'l': + snprintf(args[i], MAXLINE, "%s%d%s", + word, level, ++sub); + break; + case 'v': + snprintf(args[i], MAXLINE, "%s%s%s", + word, vol, ++sub); + break; + case '%': + snprintf(args[i], MAXLINE, "%s%%%s", + word, ++sub); + break; + default: + fprintf(stderr, + "Don't understand substitution" + " code `%%%c'\n", *sub); + return NULL; + } + } + + args[++i] = NULL; + + return args; +} + +int +senddump(int outfd, disk_t *disk, int dumplevel) +{ + char buffer[MAXLINE]; + char path[MAXPATHLEN] = ""; + int pout[2], perr[2], errfd = -1, error = 0; + pid_t pid; + ssize_t readb; + struct sigaction sa, ocsa, opsa; + + setproctitle("Dumping %s", disk->vol); + + if (pipe(pout) == -1 || pipe(perr) == -1) { + err("Couldn't create pipes: %s.\n", strerror(errno)); + close(outfd); + return -1; + } + + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = sig_child; + sigaction(SIGCHLD, &sa, &ocsa); + sa.sa_handler = sig_pipe; + sigaction(SIGPIPE, &sa, &opsa); + + pid = fork(); + if (pid == -1) { + err("Couldn't fork: %s.\n", strerror(errno)); + close(outfd); + return -1; + } else if (!pid) { + char **args; + + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + close(pout[0]); close(perr[0]); + if (dup2(pout[1], STDOUT_FILENO) == -1 || + dup2(perr[1], STDERR_FILENO) == -1) { + err("Couldn't set up pipes: %s.\n", strerror(errno)); + exit(1); + } + + args = genargs(disk->type->d_cmdline, dumplevel, disk->vol); + if (!args) + exit(1); + + if (execvp(args[0], args) == -1) { + fprintf(stderr, "Couldn't exec %s: %s.\n", + args[0], strerror(errno)); + exit(1); + } + } + + close(pout[1]); close(perr[1]); + if (fcntl(pout[0], F_SETFL, O_NONBLOCK) == -1 || + fcntl(perr[0], F_SETFL, O_NONBLOCK) == -1) { + err("Couldn't set pipes to non-blocking: %s.\n", + strerror(errno)); + close(outfd); + return -1; + } + + while (exit_status == -1) { + fd_set readfds; + + FD_ZERO(&readfds); + FD_SET(pout[0], &readfds); + FD_SET(perr[0], &readfds); + + readb = select(FD_SETSIZE, &readfds, NULL, NULL, NULL); + if (readb == -1) { + if (errno == EINTR) + continue; + fprintf(stderr, + "Dumper died abnormally: %s.\n", + strerror(errno)); + error = 1; + break; + } + + if (FD_ISSET(pout[0], &readfds)) { + READO: + readb = read(pout[0], buffer, sizeof(buffer)); + if (readb == -1) { + if (errno == EINTR) + goto READO; + fprintf(stderr, "Read died abnormally: %s.\n", + strerror(errno)); + error = 1; + break; + } + + write(outfd, buffer, readb); + } + if (FD_ISSET(perr[0], &readfds)) { + if (errfd == -1) { + sprintf(path, "%s/ticra-dumperr.%d", TMPPATH, + getpid()); + errfd = open(path, + O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (errfd == -1) { + err("Couldn't open error output file" + " `%s': %s.\n", path, + strerror(errno)); + error = 1; + break; + } + } + READE: + readb = read(perr[0], buffer, sizeof(buffer)); + if (readb == -1) { + if (errno == EINTR) + goto READE; + fprintf(stderr, + "Stderr read died abnormally: %s\n", + strerror(errno)); + error = 1; + break; + } + + write(errfd, buffer, readb); + } + } + if (errfd != -1) + close(errfd); + close(outfd); + + sigaction(SIGPIPE, NULL, &opsa); + sigaction(SIGCHLD, NULL, &ocsa); + + if (!exit_status) { + printf(DUMP_DONE "\n"); + fflush(stdout); + if (*path) + (void)unlink(path); + return 0; + } + + + switch (exit_status) { + case -1: + fprintf(stderr, "Exit status unset.\n"); + break; + case -2: + /* TODO: handle me. */ + err("You're probably dead, this shouldn't happen.\n"); + return -1; + default: + if (*path) { + fprintf(stderr, + "Error occured while dumping %s;" + " output follows:\n", disk->vol); + fflush(stderr); + + errfd = open(path, O_RDONLY); + if (errfd != -1) { + while ((readb = read(errfd, buffer, + sizeof(buffer))) > 0) + write(STDERR_FILENO, buffer, readb); + close(errfd); + break; + } + } + fprintf(stderr, "Dump process returned %d.\n", exit_status); + } + + printf(DUMP_DONE "\n"); + fflush(stdout); + return 0; +} + +int +do_sendme(disklist_t *disklist, char *buffer, int sock) +{ + char word[MAXLINE], vol[MAXLINE]; + disklist_t *disk = disklist; + char *p = buffer; + auth_t authtype = -1; + int dumplevel = 0; + + while ((p = getword(p, word))) { + if (!strncmp(word, DISK_NAME, sizeof(word))) { + p = getword(p, vol); + if (!p) { + err("NAME without argument"); + return -1; + } + } else if (!strncmp(word, DISK_LEVEL, sizeof(word))) { + p = getword(p, word); + if (!p) { + err("LEVEL command without argument"); + return -1; + } + dumplevel = atoi(word); + } else if (!strncmp(word, DISK_AUTH, sizeof(word))) { + p = getword(p, word); + if (!strncmp(word, AUTH_NOAUTH, sizeof(word))) + authtype = NOAUTH; + else if (!strncmp(word, AUTH_RSH, sizeof(word))) + authtype = RSH; + else { + err("Don't understand auth type `%s'.\n", + word); + return -1; + } + } else { + err("Don't understand DISK field `%s'.\n", word); + return -1; + } + } + + while (disk && strncmp(disk->disk.vol, vol, sizeof(disk->disk.vol))) + disk = disk->next; + + if (!disk) { + err("Disk `%s' not found in diskilst.\n", vol); + return -1; + } + + /* TODO: figure this stuff out. */ + switch (authtype) { + case NOAUTH: { + struct sockaddr_in fa; + int newsock, fromlen = sizeof(fa); + + exit_status = -1; + printf(REQ_ACK "\n"); + fflush(stdout); + + /* + * TODO: for some reason accept doesn't die when the + * sshd does. + */ + setproctitle("Waiting for connection"); + newsock = accept(sock, (struct sockaddr *)&fa, &fromlen); + if (newsock == -1) { + err("Couldn't accept a new connection: %s.\n", + strerror(errno)); + return -1; + } + + if (senddump(newsock, &disk->disk, dumplevel) == -1) + return -1; + }; break; + case RSH: + err("RSH auth currently not supported.\n"); + break; + default: + err("AUTH field has not been set.\n", authtype); + } + + return 0; +} + +int +main(int argc, char *argv[]) +{ + char buffer[MAXLINE]; + disklist_t *disklist; + int sock = 0; + + if (read_config(CLIENT_CONFIG_FILE) == -1) { + err("Couldn't open config file %s: %s.\n", + CLIENT_CONFIG_FILE, strerror(errno)); + return 1; + } + + printf(INIT_REQ "\n"); + fflush(stdout); + + if (fgets(buffer, sizeof(buffer), stdin) == NULL) { + err("NULL input waiting for handshake ACK.\n"); + return 1; + } + + if (strncmp(buffer, INIT_ACK "\n", sizeof(buffer))) { + err("Recieved invalid handshake ACK: %s.", buffer); + return 1; + } + + if (read_dumptypes(DATADIR "/dumptypes") == -1) + return 1; + + disklist = read_disklist(DATADIR "/disklist"); + if (!disklist) + return 1; + + setproctitle("Waiting for instructions"); + while (fgets(buffer, sizeof(buffer), stdin)) { + char word[MAXLINE]; + char *p = buffer; + + p = getword(p, word); + if (!strncmp(word, DUMP_SENDME, sizeof(word))) { + if (do_sendme(disklist, p, sock) == -1) + return 2; + } else if (!strncmp(word, DUMP_DONE, sizeof(word))) + break; + else if (!strncmp(word, PORT_REQ, sizeof(word))) { + sockopt_t opt; + struct sockaddr_in sa; + int port; + + p = getword(p, word); + if (!p) { + err("PORT command without argument.\n"); + return 3; + } + port = atoi(word); + if (!p) { + err("PORT `%s' isn't a number.\n", word); + return 3; + } + + if (!port) + break; + + sock = socket(PF_INET, SOCK_STREAM, 0); + if (!sock) { + err("Couldn't initialize socket: %s.\n", + strerror(errno)); + return 3; + } + + opt = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + &opt, sizeof(opt)) == -1) { + err("Couldn't make socket re-usable: %s.\n", + strerror(errno)); + return 3; + } + + sa.sin_family = AF_INET; + sa.sin_port = htons(port); + sa.sin_addr.s_addr = INADDR_ANY; + if (bind(sock, (struct sockaddr *)&sa, + sizeof(sa)) == -1) { + err("Couldn't bind to port %d: %s.\n", + port, strerror(errno)); + return 3; + } + + if (listen(sock, 0) == -1) { + err("Couldn't set backlog on socket: %s.\n", + strerror(errno)); + return 3; + } + + printf(REQ_ACK "\n"); + fflush(stdout); + } else { + err("Don't understand command `%s'.\n", word); + return 4; + } + + setproctitle("Waiting for instructions"); + } + + return 0; +} diff --git a/run-estimate b/run-estimate Binary files differnew file mode 100755 index 0000000..47fdaff --- /dev/null +++ b/run-estimate diff --git a/run-estimate.c b/run-estimate.c new file mode 100644 index 0000000..f97761d --- /dev/null +++ b/run-estimate.c @@ -0,0 +1,145 @@ +#include "config.h" +#include "conf.h" +#include "err.h" + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#ifdef NEED_LIBUTIL +#include <libutil.h> +#endif + +RCSID("$Id: run-estimate.c,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $"); + +short exit_status = -1; + +void +sig_pipe(int signo) +{ + err("SIGPIPE caught.\n"); + exit_status = -2; +} + +void +sig_child(int signo) +{ + int status; + + if (wait(&status) == -1) { + err("Couldn't clean up child: %s.\n", strerror(errno)); + return; + } + + exit_status = WEXITSTATUS(status); +} + +int +init_connection(disklist_t *disklist, long port) +{ + char buffer[MAXLINE]; + + printf(PORT_REQ " %ld\n", port); + fflush(stdout); + + if (fgets(buffer, sizeof(buffer), stdin) == NULL) { + err("NULL input waiting for ACK.\n"); + return -1; + } + + if (!strcmp(buffer, REQ_NACK "\n")) { + err("OK. I'm bailing.\n"); + return -1; + } else if (strcmp(buffer, REQ_ACK "\n")) { + err("Don't know what `%s' means, giving up.\n", buffer); + return -1; + } + + while (disklist) { + char authtype[MAXLINE]; + + switch (disklist->disk.auth) { + case RSH: + strcpy(authtype, AUTH_RSH); + break; + case NOAUTH: + default: + strcpy(authtype, AUTH_NOAUTH); + } + + printf("%s %s %s %s %s\n", DISK_REQ, + DISK_NAME, disklist->disk.vol, + DISK_AUTH, authtype); + + fflush(stdout); + + disklist = disklist->next; + + if (fgets(buffer, sizeof(buffer), stdin) == NULL) { + err("NULL input waiting for ACK.\n"); + return -1; + } + + if (!strcmp(buffer, REQ_NACK "\n")) + /* TODO: remove item from disklist in this event. */ + continue; + else if (strcmp(buffer, REQ_ACK "\n")) { + err("Don't know what `%s' means, giving up.\n", + buffer); + return -1; + } + } + + printf(DUMP_DONE "\n"); + return 0; +} + +int +main(int argc, char *argv[]) +{ + char buffer[MAXLINE]; + const config_t *option; + disklist_t *disklist; + long port; + + if (read_config(CLIENT_CONFIG_FILE) == -1) + return 1; + + option = findopt(PORTOPT); + if (!option) { + err("Couldn't find " PORTOPT " in config file.\n"); + return 1; + } + port = option->numvalue; + + printf(INIT_REQ "\n"); + fflush(stdout); + + if (fgets(buffer, sizeof(buffer), stdin) == NULL) { + err("NULL input waiting for handshake ACK.\n"); + return 1; + } + + if (strncmp(buffer, INIT_ACK "\n", sizeof(buffer))) { + err("Recieved invalid handshake ACK: %s.", buffer); + return 1; + } + + if (read_dumptypes(DATADIR "/dumptypes") == -1) + return 1; + + disklist = read_disklist(DATADIR "/disklist"); + if (!disklist) + return 1; + + if (init_connection(disklist, port) == -1) + return 1; + + return 0; +} diff --git a/server.conf b/server.conf new file mode 100644 index 0000000..eb944c7 --- /dev/null +++ b/server.conf @@ -0,0 +1,24 @@ +# $Id: server.conf,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $ + +# To whom mail should go. +manager shmit@erols.com + +# Where to log informational and error messages. +logdir /home/shmit/src/ticra/logs +infolog - +errorlog - + +# How many seconds to wait on a connection before giving up. +timeout 10 + +# List of machines to backup. +hostlist hostlist + +# Where dumps will be spooled before being dumped to tape. +spooldir /var/holding +spoolsize 50 + +# The tape device on which to dump. +tapedev /dev/nrst0 +tapesize 50 +labelstr TEST00 @@ -0,0 +1,104 @@ +.\" Copyright (c) 1993 Eric P. Allman +.\" Copyright (c) 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the University of +.\" California, Berkeley and its contributors. +.\" 4. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)smrsh.8 8.2 (Berkeley) 1/9/96 +.\" +.TH SMRSH 8 11/02/93 +.SH NAME +smrsh \- restricted shell for sendmail +.SH SYNOPSIS +.B smrsh +.B \-c +command +.SH DESCRIPTION +The +.I smrsh +program is intended as a replacement for +.I sh +for use in the ``prog'' mailer in +.IR sendmail (8) +configuration files. +It sharply limits the commands that can be run using the +``|program'' syntax of +.I sendmail +in order to improve the over all security of your system. +Briefly, even if a ``bad guy'' can get sendmail to run a program +without going through an alias or forward file, +.I smrsh +limits the set of programs that he or she can execute. +.PP +Briefly, +.I smrsh +limits programs to be in the directory +/usr/local/ticra/bin +allowing the system administrator to choose the set of acceptable commands. +It also rejects any commands with the characters +`\`', `<', `>', `|', `;', `&', `$', `(', `)', `\er' (carriage return), +or `\en' (newline) +on the command line to prevent ``end run'' attacks. +.PP +Initial pathnames on programs are stripped, +so forwarding to ``/usr/bin/vacation'', +``/home/server/mydir/bin/vacation'', +and +``vacation'' +all actually forward to +``/usr/local/ticra/bin/vacation''. +.PP +System administrators should be conservative about populating +/usr/local/ticra/bin. +Reasonable additions are +.IR vacation (1), +.IR procmail (1), +and the like. +No matter how brow-beaten you may be, +never include any shell or shell-like program +(such as +.IR perl (1)) +in the +sm.bin +directory. +Note that this does not restrict the use of shell or perl scripts +in the sm.bin directory (using the ``#!'' syntax); +it simply disallows execution of arbitrary programs. +.SH COMPILATION +Compilation should be trivial on most systems. +You may need to use \-DPATH=\e"\fIpath\fP\e" +to adjust the default search path +(defaults to ``/bin:/usr/bin'') +and/or \-DBINDIR=\e"\fIdir\fP\e" +to change the default program directory +(defaults to ``/usr/local/ticra/bin''). +.SH FILES +/usr/local/ticra/bin \- directory for restricted programs +.SH SEE ALSO +sendmail(8) @@ -0,0 +1,221 @@ +/* + * Copyright (c) 1993 Eric P. Allman + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* +** SMRSH -- sendmail restricted shell +** +** This is a patch to get around the prog mailer bugs in most +** versions of sendmail. +** +** Use this in place of /bin/sh in the "prog" mailer definition +** in your sendmail.cf file. You then create LIBEXECDIR (owned by +** root, mode 755) and put links to any programs you want +** available to prog mailers in that directory. This should +** include things like "vacation" and "procmail", but not "sed" +** or "sh". +** +** Leading pathnames are stripped from program names so that +** existing .forward files that reference things like +** "/usr/ucb/vacation" will continue to work. +** +** The following characters are completely illegal: +** < > | ^ ; & $ ` ( ) \n \r +** This is more restrictive than strictly necessary. +** +** To use this, edit /etc/sendmail.cf, search for ^Mprog, and +** change P=/bin/sh to P=/usr/libexec/smrsh, where this compiled +** binary is installed /usr/libexec/smrsh. +** +** This can be used on any version of sendmail. +** +** In loving memory of RTM. 11/02/93. +*/ + +#include "config.h" + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <syslog.h> +#include <sys/file.h> +#include <unistd.h> + +RCSID("$Id: smrsh.c,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $"); + +/* characters disallowed in the shell "-c" argument */ +#define SPECIALS "<|>^();&`$\r\n" + +/* default search path */ +#ifndef PATH +#define PATH "/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin" +#endif + +int +main(int argc, char *argv[]) +{ + register char *p; + register char *q; + register char *cmd; + int i; + char *newenv[2]; + char cmdbuf[1000]; + char pathbuf[1000]; + + openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL); + + strcpy(pathbuf, "PATH="); + strcat(pathbuf, PATH); + newenv[0] = pathbuf; + newenv[1] = NULL; + + /* + ** Do basic argv usage checking + */ + + if (argc != 3 || strcmp(argv[1], "-c") != 0) + { + fprintf(stderr, "Usage: %s -c command\n", argv[0]); + syslog(LOG_ERR, "usage"); + exit(EX_USAGE); + } + + /* + ** Disallow special shell syntax. This is overly restrictive, + ** but it should shut down all attacks. + ** Be sure to include 8-bit versions, since many shells strip + ** the address to 7 bits before checking. + */ + + strcpy(cmdbuf, SPECIALS); + for (p = cmdbuf; *p != '\0'; p++) + *p |= '\200'; + strcat(cmdbuf, SPECIALS); + p = strpbrk(argv[2], cmdbuf); + if (p != NULL) + { + fprintf(stderr, "%s: cannot use %c in command\n", + argv[0], *p); + syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s", + getuid(), *p, argv[2]); + exit(EX_UNAVAILABLE); + } + + /* + ** Do a quick sanity check on command line length. + */ + + i = strlen(argv[2]); + if (i > (sizeof cmdbuf - sizeof LIBEXECDIR - 2)) + { + fprintf(stderr, "%s: command too long: %s\n", argv[0], argv[2]); + syslog(LOG_WARNING, "command too long: %.40s", argv[2]); + exit(EX_UNAVAILABLE); + } + + /* + ** Strip off a leading pathname on the command name. For + ** example, change /usr/ucb/vacation to vacation. + */ + + /* strip leading spaces */ + for (q = argv[2]; *q != '\0' && isascii(*q) && isspace(*q); ) + q++; + + /* find the end of the command name */ + p = strpbrk(q, " \t"); + if (p == NULL) + cmd = &q[strlen(q)]; + else + { + *p = '\0'; + cmd = p; + } + + /* search backwards for last / (allow for 0200 bit) */ + while (cmd > q) + { + if ((*--cmd & 0177) == '/') + { + cmd++; + break; + } + } + + /* cmd now points at final component of path name */ + + /* + ** Check to see if the command name is legal. + */ + + (void) strcpy(cmdbuf, LIBEXECDIR); + (void) strcat(cmdbuf, "/"); + (void) strcat(cmdbuf, cmd); +#ifdef DEBUG + printf("Trying %s\n", cmdbuf); +#endif + if (access(cmdbuf, X_OK) < 0) + { + /* oops.... crack attack possiblity */ + fprintf(stderr, "%s: %s not available for ticra programs\n", + argv[0], cmd); + if (p != NULL) + *p = ' '; + syslog(LOG_CRIT, "uid %d: attempt to use %s", getuid(), cmd); + exit(EX_UNAVAILABLE); + } + if (p != NULL) + *p = ' '; + + /* + ** Create the actual shell input. + */ + + strcpy(cmdbuf, LIBEXECDIR); + strcat(cmdbuf, "/"); + strcat(cmdbuf, cmd); + + /* + ** Now invoke the shell + */ + +#ifdef DEBUG + printf("%s\n", cmdbuf); +#endif + execle("/bin/sh", "/bin/sh", "-c", cmdbuf, NULL, newenv); + syslog(LOG_CRIT, "Cannot exec /bin/sh: %m"); + perror("/bin/sh"); + exit(EX_OSFILE); +} diff --git a/strutil.c b/strutil.c new file mode 100644 index 0000000..cc7a1e2 --- /dev/null +++ b/strutil.c @@ -0,0 +1,41 @@ +#include "config.h" +#include "strutil.h" + +#include <ctype.h> +#include <string.h> + +RCSID("$Id: strutil.c,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $"); + +char * +getword(const char *src, char *dst) +{ + int inquote = 0; + + while (isspace(*src)) + src++; + + while (*src) { + if (*src == '\\' && *++src) + *dst++ = *src++; + else if (*src == '"') { + if (inquote) + inquote = 0; + else + inquote = 1; + src++; + } else if (*src == '#') { + if (!inquote) + break; + *dst++ = *src++; + } else if (!inquote && isspace(*src)) + break; + else + *dst++ = *src++; + } + *dst = '\0'; + + if (!*src || *src == '#') + return NULL; + + return (char *)src; +} diff --git a/strutil.h b/strutil.h new file mode 100644 index 0000000..06e4b13 --- /dev/null +++ b/strutil.h @@ -0,0 +1,8 @@ +/* $Id: strutil.h,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $ */ + +#ifndef STRUTIL_H +# define STRUTIL_H + +char *getword(const char *src, char *dst); + +#endif diff --git a/tapeio.c b/tapeio.c new file mode 100644 index 0000000..02a7250 --- /dev/null +++ b/tapeio.c @@ -0,0 +1,166 @@ +#include "config.h" +#include "err.h" +#include "strutil.h" +#include "tapeio.h" + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mtio.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <time.h> +#include <unistd.h> + +#ifdef NEED_STRINGS +#include <strings.h> +#endif + +RCSID("$Id: tapeio.c,v 1.2 2000/02/22 22:50:33 shmit Exp $"); + +int +mt_rewind(int fd) +{ + struct mtop mt; + + mt.mt_op = MTREW; + mt.mt_count = 1; + return ioctl(fd, MTIOCTOP, &mt); +} + +int +mt_fsf(int fd, int count) +{ + struct mtop mt; + + mt.mt_op = MTFSF; + mt.mt_count = count; + return ioctl(fd, MTIOCTOP, &mt); +} + +int +mt_weof(int fd, int count) +{ + struct mtop mt; + + mt.mt_op = MTWEOF; + mt.mt_count = count; + return ioctl(fd, MTIOCTOP, &mt); +} + +int +readlabel(int fd, tapelabel_t *label) +{ + char buffer[HEADERSIZE], date[MAXLINE]; + char *p; + struct tm tm; + ssize_t readb; + + do { + readb = read(fd, buffer, sizeof(buffer)); + if (readb == -1) { + err("Taper: couldn't read label from tape: %s.\n", + strerror(errno)); + return -1; + } + } while (readb); + + p = getword(buffer, label->labelstr); + getword(p, date); + sscanf(date, "%02d%02d%04d", &tm.tm_mday, &tm.tm_mon, &tm.tm_year); + tm.tm_mon--; tm.tm_year -= 1900; tm.tm_isdst = -1; + + label->date = mktime(&tm); + + if (mt_fsf(fd, 0) == -1) { + err("Taper: couldn't fast-forward past label: %s.\n", + strerror(errno)); + return -1; + } + + return 0; +} + +int +writelabel(int fd, tapelabel_t *label) +{ + char buffer[HEADERSIZE]; + struct tm *tm; + int wroteb; + + tm = localtime(&label->date); + snprintf(buffer, sizeof(buffer), "%s %02d%02d%04d", label->labelstr, + tm->tm_mday, tm->tm_mon+1, tm->tm_year+1900); + + wroteb = write(fd, buffer, sizeof(buffer)); + if (wroteb == -1) + return -1; + + return mt_weof(fd, 1); +} + +int +readheader(int fd, fileheader_t *header) +{ + char buffer[HEADERSIZE]; + ssize_t readb; + + do { + readb = read(fd, buffer, sizeof(buffer)); + if (readb == -1) + return -1; + } while (readb); + + bzero(header, sizeof(header)); + if (!strncmp(TAPER_START, buffer, sizeof(buffer))) + header->type = STARTMARK; + else if (!strncmp(TAPER_STOP, buffer, sizeof(buffer))) + header->type = STOPMARK; + else { + char *vol; + struct tm tm; + + header->type = FILEMARK; + vol = index(buffer, ':'); + if (vol) { + *vol = '\0'; + sscanf(vol+1, "%s %02d%02d%04d", header->vol, + &tm.tm_mday, &tm.tm_mon, &tm.tm_year); + tm.tm_mon--; tm.tm_year -= 1900; tm.tm_isdst = -1; + header->date = mktime(&tm); + } + strncpy(header->host, buffer, sizeof(header->host)); + } + + return mt_fsf(fd, 0); +} + +int +writeheader(int fd, fileheader_t *header) +{ + char buffer[HEADERSIZE]; + struct tm *tm; + ssize_t wroteb; + + switch (header->type) { + case STARTMARK: + strncpy(buffer, TAPER_START, sizeof(buffer)); + break; + case STOPMARK: + strncpy(buffer, TAPER_STOP, sizeof(buffer)); + break; + case FILEMARK: + tm = localtime(&header->date); + snprintf(buffer, sizeof(buffer), "%s:%s %02d%02d%04d", + header->host, header->vol, + tm->tm_mday, tm->tm_mon+1, tm->tm_year+1900); + break; + } + + wroteb = write(fd, buffer, sizeof(buffer)); + if (wroteb == -1) + return -1; + + return mt_weof(fd, 1); +} diff --git a/tapeio.h b/tapeio.h new file mode 100644 index 0000000..bbea95b --- /dev/null +++ b/tapeio.h @@ -0,0 +1,34 @@ +/* $Id: tapeio.h,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $ */ + +#ifndef TAPEIO_H +# define TAPEIO_H + +#define HEADERSIZE 8192 + +typedef enum { STARTMARK, STOPMARK, FILEMARK } filetype_t; + +struct tapelabel { + char labelstr[MAXLINE]; + time_t date; +}; +typedef struct tapelabel tapelabel_t; + +struct fileheader { + char host[MAXLINE]; + char vol[MAXLINE]; + filetype_t type; + time_t date; +}; +typedef struct fileheader fileheader_t; + +/* Access primitives. */ +int mt_rewind(int fd); +int mt_fsf(int fd, int count); +int mt_weof(int fd, int count); + +int readlabel(int fd, tapelabel_t *label); +int writelabel(int fd, tapelabel_t *label); +int readheader(int fd, fileheader_t *header); +int writeheader(int fd, fileheader_t *header); + +#endif @@ -0,0 +1,297 @@ +#include "config.h" +#include "conf.h" +#include "err.h" +#include "lock.h" +#include "tapeio.h" + +#include <errno.h> +#include <fcntl.h> +#include <setjmp.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mtio.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: taper.c,v 1.1.1.1 1999/02/02 23:29:39 shmit Exp $"); + +struct tapelist { + char filen[MAXPATHLEN]; + struct tapelist *next; +}; + +int canjmp = 0; +int taperunning = 0; +sigjmp_buf child_buf; + +void +sig_child(int signo) +{ + int status; + + (void)wait(&status); + + taperunning = 0; + if (canjmp) + siglongjmp(child_buf, 1); +} + +void +tapefile(int tapefd, const char *filen) +{ + char buffer[BUFFSIZE]; + char *vol; + fileheader_t header; + ssize_t total = 0, readb; + time_t start; + int fd; + + setproctitle("Dumping %s to tape", filen); + + fd = open(filen, O_RDONLY); + if (fd == -1) { + err("%s: couldn't open: %s.\n", filen, strerror(errno)); + return; + } + + /* Write the header. */ + header.type = FILEMARK; + header.date = time(NULL); + + /* Extract host and volume names from filename. */ + vol = (char *)rindex(filen, '/'); + if (vol) + strncpy(buffer, vol+1, sizeof(buffer)); + else + strncpy(buffer, filen, sizeof(buffer)); + + vol = index(buffer, ':'); + if (vol) { + *vol = '\0'; + strncpy(header.vol, vol+1, sizeof(header.vol)); + } else + header.vol[0] = '\0'; + strncpy(header.host, buffer, sizeof(header.host)); + + if (writeheader(tapefd, &header) == -1) { + err("%s: couldn't write file header: %s.\n", + filen, strerror(errno)); + return; + } + + /* Write the data. */ + start = time(NULL); + while ((readb = read(fd, buffer, sizeof(buffer))) > 0) { + ssize_t wrote = 0, n; + + total += readb; + while (wrote < readb) { + n = write(tapefd, buffer, readb); + if (n == -1) { + err("%s: couldn't write to tape: %s.\n", + filen, strerror(errno)); + close(fd); + return; + } + wrote += n; + } + } + if (readb == -1) + err("%s: couldn't read from dump: %s.\n", filen, + strerror(errno)); + + if (mt_weof(tapefd, 1) == -1) + err("%s: couldn't write EOF marker: %s.\n", + filen, strerror(errno)); + + printf("DEBUG: wrote %d bytes in %ld seconds.\n", + total, time(NULL)-start); + + close(fd); +} + +int +main(int argc, char *argv[]) +{ + struct tapelist *list = NULL; + const config_t *option; + char *labelstr, *tapedev; + struct sigaction sa; + fileheader_t header; + tapelabel_t label; + sigset_t set, oset; + int tapefd; + + if (read_config(SERVER_CONFIG_FILE) == -1) + return 1; + + option = findopt(LABELSTR); + if (!option) { + err("Taper: " LABELSTR " hasn't been set.\n"); + return 1; + } + labelstr = option->strvalue; + + option = findopt(TAPEDEV); + if (!option) { + err("Taper: " TAPEDEV " hasn't been set.\n"); + return 1; + } + tapedev = option->strvalue; + + tapefd = open(tapedev, O_RDWR); + if (tapefd == -1) { + err("Taper: Couldn't open tape device %s: %s.\n", + tapedev, strerror(errno)); + return 1; + } + + if (mt_rewind(tapefd) == -1) { + err("Taper: couldn't rewind tape: %s.\n", strerror(errno)); + return 1; + } + + if (readlabel(tapefd, &label) == -1) { + close(tapefd); + return 1; + } + if (memcmp(label.labelstr, labelstr, sizeof(labelstr))) { + err("Taper: label `%s' doesn't match expected label `%s'.\n", + label.labelstr, labelstr); + return 1; + } + + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = sig_child; + sigaction(SIGCHLD, &sa, NULL); + + /* Loop waiting for input from the dumper. */ + for (;;) { + char buffer[MAXLINE]; + + if (!canjmp && sigsetjmp(child_buf, 1)) { + if (list) { + struct tapelist *p = list; + pid_t pid; + + taperunning = 1; + pid = fork(); + if (pid == -1) { + err("Taper: couldn't fork: %s.\n", + strerror(errno)); + return 1; + } else if (!pid) { + tapefile(tapefd, p->filen); + exit(0); + } + list = list->next; + free(p); + } + } + canjmp = 1; + + if (!fgets(buffer, sizeof(buffer), stdin)) { + err("Taper: dumper died abnormally.\n"); + break; + } + + sigemptyset(&set); + sigaddset(&set, SIGCHLD); + sigprocmask(SIG_BLOCK, &set, &oset); + + if (!memcmp(buffer, DISK_REQ, sizeof(DISK_REQ)-1)) { + char *index = buffer+sizeof(DISK_REQ)-1; + + /* + * Special case the event when the taper isn't + * running and the list is NULL so we can start + * the process and continue it when we've dumped + * everything so far to tape. + */ + index[strlen(index)-1] = '\0'; + if (!list && !taperunning) { + pid_t pid; + + taperunning = 1; + pid = fork(); + if (pid == -1) { + err("Taper: couldn't fork: %s.\n", + strerror(errno)); + break; + } else if (!pid) { + tapefile(tapefd, index); + exit(0); + } + } else { + struct tapelist *p, *q = list; + + p = malloc(sizeof(struct tapelist)); + if (!p) { + err("Taper: couldn't malloc: %s.\n", + strerror(errno)); + break; + } + strncpy(p->filen, index, sizeof(p->filen)); + p->next = NULL; + + if (q) { + while (q->next) + q = q->next; + q->next = p; + } else + list = p; + } + } + if (!memcmp(buffer, DUMP_DONE, sizeof(DUMP_DONE)-1)) + break; + + sigprocmask(SIG_SETMASK, &oset, NULL); + } + + if (list || taperunning) { + /* + * Make sig_child exit without a siglongjmp, then unblock + * the signal. + */ + canjmp = 0; + sigprocmask(SIG_SETMASK, &oset, NULL); + + /* Wait for the first child to exit. */ + sigemptyset(&set); + sigsuspend(&set); + + /* Then run through the pending files. */ + while (list) { + struct tapelist *p = list; + + tapefile(tapefd, list->filen); + list = list->next; + free(p); + } + } + + header.type = STOPMARK; + if (writeheader(tapefd, &header) == -1) { + err("Taper: couldn't write end-of-tape marker: %s.\n", + strerror(errno)); + close(tapefd); + return 1; + } + + close(tapefd); + return 0; +} |