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