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