v19i102: Save files that have changed in the last little while

Rich Salz rsalz at uunet.uu.net
Sat Jul 1 00:50:06 AEST 1989


Submitted-by: Rayan Zachariassen <rayan at ai.toronto.edu>
Posting-number: Volume 19, Issue 102
Archive-name: backup

The backup program is an online disk-to-disk incremental backup utility
that maintains a file queue in a seperate partition, copying files that
have been modified since its last run to the special backup filesystem.
As space is needed on the backup partition, just enough of the oldest files
are removed to make space for new file copies.  In other words, a fixed
size (in disk blocks) queue of recently modified files.

Assumes SunOS filesystem interface (vnodes, etc.), would not be hard to
port to straight BSD filesystem.

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# If this archive is complete, you will see the following message at the end:
#		"End of shell archive."
#
# Contents:
#   README Makefile backup.8 backup.c backup.conf
#   backup.filter getback.1 getback.c getback.sh ilw.c
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f README -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"README\"
else
echo shar: Extracting \"README\" \(1083 characters\)
sed "s/^X//" >README <<'END_OF_README'
XThe backup program is an online disk-to-disk incremental backup utility
Xthat maintains a file queue in a seperate partition, copying files that
Xhave been modified since its last run to the special backup filesystem.
XAs space is needed on the backup partition, just enough of the oldest files
Xare removed to make space for new file copies.  In other words, a fixed
Xsize (in disk blocks) queue of recently modified files.
X
X
XDefaults, overridden by command line arguments:
X
X	BACKUP		/backup
X	CONFIG		/etc/backup.conf
X	MAXSIZE		100k
X	DELTA		24hr
X
XAssumes SunOS filesystem interface (vnodes, etc.), would not
Xbe hard to port to straight BSD filesystem.
X
XInstallation:
X
X- Edit Makefile.
X- Tweak default parameters in backup.c if you don't like the above values.
X- Create and install a configuration file, see the example in ./backup.conf.
X- make; make install
X- Allocate a /backup partition, create and mount an empty filesystem on it.
X- Add a crontab entry, e.g.:
X	37 4,12,20 * * * /local/etc/backup
X
XThanks to Ken Lalonde for code tweaks, getback.sh, and most of this README file
X
Xrayan
END_OF_README
if test 1083 -ne `wc -c <README`; then
    echo shar: \"README\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f Makefile -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"Makefile\"
else
echo shar: Extracting \"Makefile\" \(549 characters\)
sed "s/^X//" >Makefile <<'END_OF_Makefile'
XDESTDIR=/local
XCOPTS = # -Bstatic
XCFLAGS = $(COPTS) -DCONFIG=\"$(DESTDIR)/etc/backup.conf\"
X
Xall:	backup getback
X
Xbackup:	backup.o ilw.o
X	$(CC) $(CFLAGS) -o backup backup.o ilw.o
X
Xinstall: all
X	install -m 710 backup $(DESTDIR)/etc/backup
X	install -m 755 getback $(DESTDIR)/bin/getback
X	install -c -m 644 backup.8 $(DESTDIR)/man/man8
X	install -c -m 644 getback.1 $(DESTDIR)/man/man1
X
Xclean:
X	rm -f *.o backup getback make.log
X
Xdist:
X	shar README Makefile backup.8 backup.c backup.conf backup.filter getback.1 getback.c getback.sh ilw.c > backup.shar
END_OF_Makefile
if test 549 -ne `wc -c <Makefile`; then
    echo shar: \"Makefile\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f backup.8 -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"backup.8\"
else
echo shar: Extracting \"backup.8\" \(4871 characters\)
sed "s/^X//" >backup.8 <<'END_OF_backup.8'
X.TH BACKUP 8 "UofToronto"
X.SH NAME
Xbackup \- save files that have changed in the last little while
X.SH SYNOPSIS
X.B backup
X[ -devz
X.RI -b #
X] [ -c
X.I backup.conf
X] [ -o
X.I /backup
X] [
X.I /path
X\&... ]
X.SH DESCRIPTION
X.I Backup
Xcopies to a special filesystem hierarchy all files which have been modified
Xsince the previous run of
X.I backup
Xon that subtree of the filesystem.
XThe subtree(s) may be specified explicitly on the command line.
XIf none are given, a configuration file (default is
X.BR /local/etc/backup.conf )
Xis read to determine which subtrees to scan for recently changed files.
XIf subtrees are explicitly listed, a run is forced.  Otherwise,
X.I backup
Xwill only do its deed if the subtree is due for backing up as indicated
Xby the interval time, and the last backup scan time, in the configuration
Xfile.  If the subtree is not found in the configuration file, all files
Xchanged during the last day will be selected.
X.PP
XThe format of the configuration file is:
X.IP -
Xlines starting with
X.B #
Xare ignored.
X.IP -
Xall other lines contain four space-separated fields:
X.IP
XField 1 is the path name of a subtree to back up.
X.IP
XField 2 is the desired interval between backups.
X.IP
XField 3 is the name of a filter file containing glob patterns,
Xone per line, describing names of files to
X.I ignore
Xwhen scanning the subtree.
X.IP
XField 4 is a timestamp in the exact syntax of
X.BR ctime (3).
X.PP
XThis is a sample configuration file:
X.ta 1.7i,2.3i,3.7i
X.nf
X
X# path	intvl	filter file	last done
X/usr	8h	/dev/null	Sat Apr 30 00:33:03 1988
X/homes/neat/car	8h	/etc/filter.u	Sat Apr 30 00:33:03 1988
X/homes/neat/cdr	8h	/etc/filter.u	Sat Apr 30 00:33:03 1988
X/var	24h	/dev/null	Sat Apr 30 00:33:03 1988
X.fi
X.PP
XThe first three fields are maintained manually (and defaulted if
X.I backup
Xis given a subtree argument not appearing in the configuration file).
XThe last field is maintained by the program itself.  After every successful
Xrun, the configuration file is written out containing the updated timestamp
Xinformation.  The purpose of the configuration file is to allow all information
Xbe maintained in this form, and the
X.I backup
Xprogram started very frequently by
X.BR cron (8).
X.PP
XBacked up files are stored in a parallel directory hierarchy under the
Xroot of the backup hierarchy (by default
X.BR /backup ).
XEach backed up file has associated with it a directory whose relative
Xpathname under the root of the backup hierarchy is its full pathname.
XThis directory contains one or more backed up files in files named by
Xthe date of the backup.  Thus, a user with a file
X.br
X.sp
X.I /homes/neat/car/user/src/stuff.c
X.br
X.sp
Xthat he has been working on and hence has been backed up a number
Xof times might find a set of files
X.br
X.sp
X.IR /backup/homes/neat/car/user/src/stuff.c/Apr30-06:05 ,
X.IR /backup/homes/neat/car/user/src/stuff.c/Apr30-09:05 ,
X.br
X.sp
Xand
X.br
X.sp
X.IR /backup/homes/neat/car/user/src/stuff.c/Apr30-12:05 .
X.br
X.sp
XPermissions and owner/group are copied with the files.
X.PP
XAs the backups are very expensive in terms of file space only
Xfiles of less than some size, currently 100 kilobytes, are saved.
XFiles can be deselected by creating a filter file, and adding to it
Xglob patterns that would match suspicious names.   Typically,
Xfor example
X.IR core ,
X.IR a.out ,
Xemacs temporaries like
X.I #*
Xor
X.I *~
Xand many others, are not backed up.
X.PP
XWhen filespace usage on the backup root's file system passes a high water mark,
Xcurrently 95%,
X.I
Xbackup
Xdeletes files in order by modification date until it has enough space under
Xthe high water mark to add new backup files.
X.PP
X.I Backup
Xreads the entire i-list for the filesystem, saving information such as the
Xmodification time and type of the inode.  Thereafter all the appropriate
Xfile names are identified by tree walks in the filesystem, and backed up.
XIf files must be deleted from the backup hierarchy,
Xthen it does the same for the backup subtree (only deleting instead of
Xbackup up!).
X.PP
XThe various options are:
X.IP -b
Xthe argument is the largest size in kilobytes of files to consider backing up.
X.IP -c
Xspecifies an alternate configuration file.
X.IP -d
Xturns a little bit of debugging output on.
X.IP -e
Xwill not ignore executable files when looking for files to back up.
X.IP -o
Xspecifies an alternate backup hierarchy to place backed up files.
X.IP -v
Xturns on verbosity, and can be doubled for more chatter.
X.IP -z
Xasks the program to go through the motions, but to not take any action
Xwhatsoever.
X.SH FILES
X.ta 2.5i
X/backup	- mounted filesystem for backups
X.br
X/local/etc/backup.conf	- backup configuration information
X.SH INFO
XOriginal idea by Ciaran O'Donnell of U of Waterloo, whose version was
Xwritten in 1975.  This is an independent reimplementation native to the
XSunOS environment, written by Rayan Zachariassen at U of Toronto
X(bug fixes and improvements to <rayan at ai.toronto.edu>).
END_OF_backup.8
if test 4871 -ne `wc -c <backup.8`; then
    echo shar: \"backup.8\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f backup.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"backup.c\"
else
echo shar: Extracting \"backup.c\" \(28044 characters\)
sed "s/^X//" >backup.c <<'END_OF_backup.c'
X/*
X * backup - do incremental disk-to-disk backups of individual files
X *
X * This code is a complete reimplementation (for the SunOS environment)
X * of an original idea by Ciaran O'Donnell.  This implementation is
X * Copyright 1988 by Rayan Zachariassen, solely to prevent you selling
X * it or putting your name on it.  Free (gratis) redistribution is
X * encouraged.
X */
X
X#ifndef	lint
Xstatic char *RCSid = "$Header: /ai/car/src/etc/backup/RCS/backup.c,v 1.2 89/05/23 22:13:19 rayan Exp $";
X#endif	lint
X
X#include <stdio.h>
X#include <ctype.h>
X#include <mntent.h>
X#include <values.h>
X#include <errno.h>
X#include <sys/param.h>
X#include <sys/types.h>
X#include <sys/file.h>
X#include <sys/stat.h>
X#include <sys/time.h>
X#include <sys/vnode.h>
X#include <sys/vfs.h>
X#include <ufs/inode.h>
X#include <ufs/fs.h>
X#include <sys/dir.h>
X
X#define	HIWATER		95	/* % of backup filesystem used at high water */
X#define	LOWATER		85	/* % of backup filesystem used at low water */
X
X#define	BACKUP		"/backup"	/* backup filesystem mount point */
X#ifndef	CONFIG
X#define	CONFIG		"/etc/backup.conf"	/* configuration file */
X#endif	/* !CONFIG */
X
X#define	MAXSIZE		100000	/* default maximum size of files to back up */
X
X#define	DELTA		(24*60*60)	/* default time since last backup */
X
Xint	maxsize = MAXSIZE;	/* maximum size in bytes of files to back up */
Xint	doexecs = 0;		/* should we back up executables? */
X
Xstruct a_path {
X	char		*path;	/* path to tree hierarchy we want to back up */
X	char		*glob;	/* pointer to the contents of the filter file */
X	time_t		newer;	/* back up files newer than this time */
X	struct config	*config;/* back pointer to config structure */
X	struct a_path	*next;
X};
X
Xstruct a_dev {
X	char		*text;	/* raw device name */
X	struct a_path	*paths;	/* pointer to list of paths */
X	struct a_dev	*next;	/* pointer to next device */
X};
X
Xstruct a_dev	*devlist = NULL;
X
Xstruct config {
X	char	*path;		/* top of hierarchy we want to back up */
X	char	*interval;	/* how often to do incrementals */
X	char	*filter;	/* name of file containing regexp's */
X	char	*line;		/* the config file line excluding time field */
X	time_t	lasttime;	/* last time incrementals were done here */
X	struct config	*next;
X};
X
Xchar	*progname, *backup, *devbackup, *backupmnt;
Xint	debug, verbose, dryrun;
Xtime_t	now, lasttime;
X
Xextern int	optind;
Xextern char	*optarg;
Xextern char	*emalloc();
X
X#define	EMSG(x)	(errno < sys_nerr ? sys_errlist[x] : \
X	       (sprintf(errmsgbuf, "unknown error %d", errno), errmsgbuf))
X
Xextern int	errno, sys_nerr;
Xextern char	*sys_errlist[];
Xchar		errmsgbuf[30];
X
Xmain(argc, argv)
X	int	argc;
X	char	*argv[];
X{
X	int		c, errflag;
X	char		*cffile;
X	struct a_dev	*lp;
X	struct a_path	*pp;
X	struct config	*cf, *cfp;
X	FILE		*cf_fp;
X	extern time_t time();
X	extern char *getdevice(), *getpath(), *ctime();
X	extern struct config *readconfig();
X	extern void writeconfig();
X
X	progname = argv[0];
X	errflag = 0;
X	dryrun = 0;
X	debug = verbose = 0;
X	backup = BACKUP;
X	cffile = CONFIG;
X	while ((c = getopt(argc, argv, "bc:devo:z")) != EOF) {
X		switch (c) {
X		case 'b':	/* don't back up files larger than arg kbytes */
X			if ((maxsize = atoi(optarg)) < 1) {
X				fprintf(stderr,
X					"%s: illegal argument to -b: %d\n",
X					progname, optarg);
X				++errflag;
X			} else
X				maxsize <<= 10;		/* multiple of 1k */
X			break;
X		case 'c':	/* specify alternate configuration file */
X			cffile = optarg;
X			break;
X		case 'd':	/* debugging output */
X			++debug;
X			break;
X		case 'e':	/* copy executables */
X			++doexecs;
X			break;
X		case 'v':	/* be (more and more) verbose */
X			++verbose;
X			break;
X		case 'o':	/* specify alternate backup location */
X			backup = optarg;
X			break;
X		case 'z':	/* fake it */
X			++dryrun;
X			break;
X		default:
X			++errflag;
X		}
X	}
X	(void) time(&now);
X	if ((cf_fp = fopen(cffile, "r+")) == NULL) {
X		fprintf(stderr, "%s: open(%s): %s\n",
X				progname, cffile, EMSG(errno) /* ... */);
X		Usage();
X	}
X	if (verbose
X	    && flock(fileno(cf_fp), LOCK_EX|LOCK_NB) < 0
X	    && errno == EWOULDBLOCK)
X		printf("waiting for exclusive lock on %s\n", cffile);
X	if (flock(fileno(cf_fp), LOCK_EX) < 0) {
X		fprintf(stderr, "%s: can't get lock on %s\n",
X				progname, cffile);
X		exit(1);
X	} else if (verbose > 1) {
X		setbuf(stdout, (char *)NULL);
X		printf("Start at %slocked %s\n", ctime(&now), cffile);
X	}
X	cf = readconfig(cf_fp);
X	if (optind == argc) {
X		/* figure out what we should back up */
X		for (cfp = cf; cfp != NULL; cfp = cfp->next) {
X			if (cfp->lasttime == 0) {
X				if (debug)
X					printf("%s: lasttime is 0\n", cfp->path);
X				continue;
X			}
X			/* give 5 minutes leeway */
X			if (cfp->lasttime + interval(cfp->interval) < now+300) {
X				/* add this path to the list */
X				if (debug)
X					printf("adding %s\n", cfp->path);
X				addpath(cfp);
X			} else if (debug)
X			printf("ignoring %s: lasttime=%d interval=%s now=%d\n",
X				cfp->path, cfp->lasttime, cfp->interval, now);
X		}
X	} else {
X		for (; optind < argc; ++optind) {
X			for (cfp = cf; cfp != NULL; cfp = cfp->next) {
X				if (cfp->lasttime == 0)
X					continue;
X				if (strcmp(cfp->path, argv[optind]) == 0) {
X					addpath(cfp);
X					break;
X				}
X			}
X			if (cfp == NULL) {
X				/* append new entry to config file */
X				for (cfp = cf; cfp != NULL && cfp->next != NULL;
X					 cfp = cfp->next)
X					continue;
X				if (cfp != NULL) {
X					cfp->next = (struct config *)
X					 emalloc((u_int)sizeof (struct config));
X					cfp = cfp->next;
X					cfp->next = NULL;
X					cfp->line = NULL;
X					cfp->interval = "24h";
X					cfp->path = argv[optind];
X					cfp->filter = "/dev/null";
X				}
X				cfp->lasttime = now - DELTA;
X				addpath(cfp);
X			}
X		}
X	}
X
X	if ((devbackup = getdevice(backup, MNTTAB)) == NULL &&
X	    (devbackup = getdevice(backup, MOUNTED)) == NULL) {
X		fprintf(stderr, "%s: could not determine backup device\n",
X				progname);
X		exit(1);
X	}
X	if ((backupmnt = getpath(devbackup, MNTTAB)) == NULL &&
X	    (backupmnt = getpath(devbackup, MOUNTED)) == NULL) {
X		fprintf(stderr, "%s: could not determine mount point for %s\n",
X				progname, devbackup);
X		++errflag;
X	}
X	if (errflag)
X		Usage();
X	(void) umask(0);
X	for (lp = devlist; lp != NULL; lp = lp->next)
X		if (doit(lp->paths, lp->text))
X			for (pp = lp->paths; pp != NULL; pp = pp->next)
X				pp->config->lasttime = now;
X	if (!dryrun)
X		writeconfig(cf_fp, cf);
X	(void) flock(fileno(cf_fp), LOCK_UN);
X	if (verbose > 1) {
X		time(&now);
X		printf("unlocked %s\nEnd at %s", cffile, ctime(&now));
X	}
X	(void) fclose(cf_fp);
X#ifdef PROF
X	/* drop profiling data in a known place */
X	chdir("/tmp");
X#endif
X	exit(0);
X}
X
XUsage()
X{
X	fprintf(stderr,
X"Usage: %s [ -devz -b# ] [ -c backup.conf ] [ -o /backup ] [ /path ... ]\n",
X			progname);
X	exit(1);
X}
X
X/*
X * The filter files contain glob expressions, one per line.  We need to
X * keep track of which filter files we've read in, since several paths
X * may share the same filters.
X */
X
Xstruct filter {
X	char	*name;
X	char	*contents;
X} filters[50];
X
Xint	maxfilters = 0;
X
X/* return pointer to contents of a filter file stored away */
X
Xchar *
Xreadfilter(file)
X	char	*file;
X{
X	int		i, fd;
X	struct stat	stbuf;
X
X	if (strcmp(file, "/dev/null") == 0)
X		return NULL;
X	for (i = 0; i < maxfilters
X	       && i < (sizeof filters/sizeof (struct filter)); ++i) {
X		if (strcmp(file, filters[i].name) == 0)
X			return filters[i].contents;
X	}
X	if (i == (sizeof filters/sizeof (struct filter))) {
X		fprintf(stderr, "%s: ran out of filters\n", progname);
X		exit(1);
X	}
X	if ((fd = open(file, O_RDONLY, 0)) < 0) {
X		fprintf(stderr, "%s: can't open filter file %s\n",
X				progname, file);
X		exit(1);
X	}
X	if (fstat(fd, &stbuf) < 0) {
X		fprintf(stderr, "%s: can't stat open filter file %s\n",
X				progname, file);
X		exit(1);
X	}
X	filters[i].contents = emalloc((u_int)stbuf.st_size+1);
X	if (read(fd, filters[i].contents, stbuf.st_size) < stbuf.st_size) {
X		fprintf(stderr, "%s: couldn't read %d bytes from %s\n",
X				progname, stbuf.st_size, file);
X	}
X	*(filters[i].contents+stbuf.st_size) = '\0';
X	filters[i].name = file;
X	++maxfilters;
X	(void) close(fd);
X	return filters[i].contents;
X}
X
X/*
X * We maintain a two-level linked list structure (i.e. list of lists),
X * associating paths with their raw device.  When there are several paths
X * on the same device, we want to handle them simultaneously and only
X * do the ilist walking once per device.  The root of this structure is
X * devlist.
X */
X
Xint
Xaddpath(cfp)
X	struct config	*cfp;
X{
X	struct a_dev	*lp;
X	struct a_path	*pp;
X	char	*rawdevice;
X
X	if (cfp->path == NULL || *cfp->path != '/') {
X		fprintf(stderr, "%s: illegal path: %s\n",
X				progname,
X				cfp->path == NULL ? "(null)" : cfp->path);
X	} else if ((rawdevice = getdevice(cfp->path, MNTTAB)) == NULL) {
X		fprintf(stderr, "%s: no device for %s\n",
X				progname, cfp->path);
X	} else if (*rawdevice != '/') {
X		fprintf(stderr, "%s: bad device %s for %s\n",
X				progname, rawdevice, cfp->path);
X	} else {
X		/* link the path, device pair into lists */
X		for (lp = devlist; lp != NULL; lp = lp->next)
X			if (strcmp(lp->text, rawdevice) == 0)
X				break;
X		pp = (struct a_path *)
X				emalloc((u_int)sizeof (struct a_path));
X		pp->path = cfp->path;
X		pp->newer = cfp->lasttime;
X		pp->glob = readfilter(cfp->filter);
X		pp->config = cfp;
X		if (lp != NULL)
X			pp->next = lp->paths;
X		else {
X			lp = (struct a_dev *)
X				emalloc((u_int)sizeof (struct a_dev));
X			lp->next = devlist;
X			devlist = lp;
X			lp->text = emalloc((u_int)strlen(rawdevice)+1);
X			(void) strcpy(lp->text, rawdevice);
X			pp->next = NULL;
X		}
X		lp->paths = pp;
X	}
X}
X
X/* return the name of the raw device corresponding to a particular file */
X
Xchar *
Xgetdevice(path, table)
X	char *path;
X	char *table;
X{
X	char	*cp, *s;
X	struct mntent *me;
X	FILE	*fp;
X	struct stat stpath, stdev;
X
X	if (lstat(path, &stpath) < 0) {
X		fprintf(stderr, "%s: lstat(%s): %s\n",
X				progname, path, EMSG(errno));
X		return NULL;
X	}
X	if (!((stpath.st_mode & S_IFMT) & (S_IFREG | S_IFDIR))) {
X		fprintf(stderr, "%s: %s is a special file\n", progname, path);
X		return NULL;
X	}
X	if ((fp = setmntent(table, "r")) == NULL) {
X		fprintf(stderr, "%s: setmntent(%s): %s\n",
X				progname, table, EMSG(errno));
X		return NULL;
X	}
X	while ((me = getmntent(fp)) != NULL) {
X		if (strcmp(me->mnt_type, MNTTYPE_42) == 0
X		    && strncmp(me->mnt_fsname, "/dev/", 5) == 0
X		    && lstat(me->mnt_fsname, &stdev) == 0
X		    && stdev.st_rdev == stpath.st_dev) {
X			(void) endmntent(fp);
X			cp = emalloc((u_int)strlen(me->mnt_fsname)+2);
X			(void) strcpy(cp, me->mnt_fsname);
X			s = cp + strlen(cp) + 1;
X			while (s > cp && *(s - 1) != '/')
X				*s = *(s-1), --s;
X			if (s > cp)
X				*s = 'r';
X			return cp;
X		}
X	}
X	(void) endmntent(fp);
X	return NULL;
X}
X
X/* get the mount point of the filesystem on the raw device */
X
Xchar *
Xgetpath(device, table)
X	char *device;
X	char *table;
X{
X	char		*cp;
X	struct mntent	*me;
X	FILE		*fp;
X	char		devpath[MAXPATHLEN];
X	struct stat	stpath, stdev;
X	extern char	*rindex();
X
X	(void) strcpy(devpath, device);
X	if ((cp = rindex(devpath, '/')) != NULL) {
X		++cp;
X		if (*cp == 'r')
X			while ((*cp = *(cp+1)) != '\0')
X				++cp;
X	}
X	if ((fp = setmntent(table, "r")) == NULL) {
X		fprintf(stderr, "%s: setmntent(%s): %s\n",
X				progname, table, EMSG(errno));
X		return NULL;
X	}
X	while ((me = getmntent(fp)) != NULL) {
X		if (strcmp(me->mnt_type, MNTTYPE_42) == 0
X		    && strcmp(me->mnt_fsname, devpath) == 0
X		    && lstat(me->mnt_fsname, &stdev) == 0
X		    && lstat(me->mnt_dir, &stpath) == 0
X		    && stdev.st_rdev == stpath.st_dev) {
X			(void) endmntent(fp);
X			cp = emalloc((u_int)strlen(me->mnt_dir)+1);
X			(void) strcpy(cp, me->mnt_dir);
X			return cp;
X		}
X	}
X	(void) endmntent(fp);
X	return NULL;
X}
X
X#define sblock	sb_un.u_sblock
X
Xstruct iinfo {
X	int	inum;			/* must be int so can be -ve too */
X	u_int	blks;
X	time_t	mtime;
X};
X
Xint
Xcmpiinfo(iip1, iip2)
X	register struct iinfo *iip1, *iip2;
X{
X	return iip1->mtime - iip2->mtime;
X}
X
Xstruct iinfo	*stack[2];
Xlong		stacksize[2];
Xlong		needspace[2];
Xint		top = -1;
X
Xchar		*dirmask;
Xint		mustfree;
X
X#define	SET(v,i)	((v)[(i)/BITSPERBYTE] |= (1<<((i)%BITSPERBYTE)))
X#define	TST(v,i)	((v)[(i)/BITSPERBYTE] & (1<<((i)%BITSPERBYTE)))
X
Xint
Xdoit(path, dev)
X	struct a_path	*path;
X	char		*dev;
X{
X	register struct iinfo	*st;
X	register int	i, inum;
X	int	fd, hiwater, lowater;
X	u_int	nfiles;
X	union { struct fs u_sblock; char dummy[SBSIZE]; } sb_un;
X	char	*bitvec, *dirvec, pathbuf[MAXPATHLEN];
X	struct a_path	*pp;
X	struct statfs	fsbuf;
X	extern int itest(), mkbackup(), rmbackup();
X	extern char *calloc();
X
X	if (debug)
X		printf("doing %s\n", dev);
X	if ((fd = open(dev, O_RDONLY, 0)) < 0) {
X		fprintf(stderr, "%s: open(%s): %s\n",
X				progname, dev, EMSG(errno));
X		return 0;
X	}
X	if (bread(fd, SBLOCK, (char *)&sblock, (long) SBSIZE) < 0) {
X		fprintf(stderr, "%s: can't read superblock from %s: %s\n",
X				progname, dev, EMSG(errno));
X		return 0;
X	}
X	(void) close(fd);
X	nfiles = sblock.fs_ipg * sblock.fs_ncg;
X	stack[++top] = (struct iinfo *)calloc(nfiles, sizeof (struct iinfo));
X	stacksize[top] = 0;
X	needspace[top] = 0;
X	dirvec = calloc((nfiles/BITSPERBYTE)+1, 1);
X	dirmask = dirvec;
X	if (top == 0) {
X		/* figure out the oldest lasttime before i-list walk */
X		lasttime = now;
X		for (pp = path; pp != NULL; pp = pp->next)
X			if (pp->newer < lasttime)
X				lasttime = pp->newer;
X	}
X	if (debug)
X		printf("%s: scan %d inodes\n", dev, nfiles);
X	(void) ilw(dev, itest, 1);
X	if (verbose)
X		printf("%s: found %d candidate files, with %d blocks total\n",
X			      dev, stacksize[top], needspace[top]);
X	if (stacksize[top] == 0) {
X		(void) free(dirvec);
X		(void) free((char *)stack[top--]);
X		return 0;
X	}
X	bitvec = calloc((nfiles/BITSPERBYTE)+1, 1);
X	if (top == 0) {
X		/*
X		 * This is the filesystem we want to back up.
X		 * First make sure there is enough free space in the backup
X		 * filesystem (if not, call myself recursively), then run
X		 * a file tree walker to copy the indicated files to the
X		 * backup filesystem.
X		 */
X		if (statfs(backup, &fsbuf) < 0) {
X			fprintf(stderr, "%s: statfs(%s): %s\n",
X					progname, backup, EMSG(errno));
X			exit(1);
X		}
X		hiwater = (fsbuf.f_blocks-fsbuf.f_bfree
X			   		 +fsbuf.f_bavail)*HIWATER/100;
X		if (fsbuf.f_blocks - fsbuf.f_bfree + needspace[top] > hiwater) {
X			/* need to free some space */
X			struct a_path	backupdesc;
X			/*
X			 * If you want to free so free space will be at
X			 * LOWATER after backup finishes, then enable the
X			 * next line and do s/hiwater/lowater/ in the
X			 * following line defining mustfree.
X			 */
X			/* lowater = +(hiwater*LOWATER)/HIWATER; */
X			mustfree = fsbuf.f_blocks - fsbuf.f_bfree
X						  + needspace[top] - hiwater;
X			/* select all files */
X			lasttime = 0;
X			maxsize = sblock.fs_dsize * DEV_BSIZE;
X			backupdesc.path = backup;
X			backupdesc.next = NULL;
X			backupdesc.glob = NULL;
X			backupdesc.newer = lasttime;
X			if (!doit(&backupdesc, devbackup)) {
X				fprintf(stderr,
X					"%s: Can't walk %s to free space\n",
X						progname, backup);
X				exit(1);
X			}
X		}
X		for (i = 0, st = stack[top]; i < nfiles; ++i, ++st) {
X			if (st->mtime > 0) {
X				SET(bitvec, st->inum);
X			}
X		}
X		for (; path != NULL; path = path->next) {
X			if (chdir(path->path) < 0) {
X				fprintf(stderr, "%s: chdir(%s): %s\n",
X					progname, path->path, EMSG(errno));
X				exit(1);
X			}
X			(void) sprintf(pathbuf, "%s/", path->path);
X			lasttime = path->newer;
X			if (verbose)
X				printf("%s: select mtime within %d sec in %s\n",
X					    dev, now - lasttime, path->path);
X			walk(pathbuf, pathbuf + strlen(pathbuf), path->glob,
X				      mkbackup, bitvec, dirvec);
X		}
X	} else {
X		/*
X		 * This is the backup filesystem.
X		 * Sort the inodes selected into oldest-first, then run
X		 * a file tree walker to delete the files until we have
X		 * enough space *and* are under the low water mark.
X		 */
X
X		/* assert strcmp(path->path, backup) == 0 */
X		/* compress the inode array */
X		st = stack[top];
X		for (i = inum = 0; i < stacksize[top]; ++inum) {
X			if ((st+inum)->mtime > 0) {
X				(st+i)->inum = inum;
X				(st+i)->blks = (st+inum)->blks;
X				(st+i)->mtime = (st+inum)->mtime;
X				++i;
X			}
X		}
X		if (chdir(path->path) < 0) {
X			fprintf(stderr, "%s: chdir(%s): %s\n",
X					progname, path->path, EMSG(errno));
X			exit(1);
X		}
X		(void) sprintf(pathbuf, "%s/", path->path);
X		if (strcmp(backup, backupmnt) != 0) {
X			/* backup area is not an entire filesystem */
X			walk(pathbuf, pathbuf + strlen(pathbuf), (char *)NULL,
X				      (int (*)())NULL, (char *)NULL, dirvec);
X			/* now all possible inums are negative and rest +ve */
X			st = stack[top];
X			for (i = inum = 0; i < stacksize[top]; ++inum) {
X				if ((st+inum)->inum < 0) {
X					(st+i)->inum = inum;
X					++i;
X				} else
X					(st+i)->inum = 0;
X			}
X			/* now all possible inums are +ve and rest 0 */
X		}
X		/* sort it oldest first */
X		qsort((char *)stack[top], stacksize[top],
X		      sizeof (struct iinfo), cmpiinfo);
X		/* mustfree has been set in our parent doit() */
X		/* go from oldest to newest, truncate after mustfree blocks */
X		st = stack[top];
X		for (i = 0; i < stacksize[top] && mustfree > 0; ++i, ++st) {
X			if (st->inum > 2 && st->mtime > 0) {
X				mustfree -= st->blks;
X				SET(bitvec, st->inum);
X			}
X		}
X		(void) sprintf(pathbuf, "%s/", path->path);
X		walk(pathbuf, pathbuf + strlen(pathbuf), (char *)NULL,
X			      rmbackup, bitvec, dirvec);
X	}
X	(void) free(bitvec);
X	(void) free(dirvec);
X	(void) free((char *)stack[top--]);
X	return 1;
X}
X
X/* This routine is used by the inode list walker) to test for relevant inodes */
X
Xitest(ip, inum)
X	struct dinode	*ip;
X	int		inum;
X{
X	register struct iinfo *iip;
X
X	if ((ip->di_mode & S_IFMT) == S_IFREG
X	    && ip->di_mtime > lasttime
X	    && ip->di_size < maxsize
X	    && (doexecs || (ip->di_mode & 07111) == 0)) {
X		/* we have a candidate for backing up */
X		iip = stack[top] + inum;
X		iip->inum = inum;
X		stacksize[top] += 1;
X		needspace[top] += (iip->blks = ip->di_blocks);
X		iip->mtime = ip->di_mtime;
X		/*
Xprintf("%6d:\tmode=0%o uid=%d gid=%d size=%d nlink=%d, a=%D m=%D c=%D\n",
X		inum, ip->di_mode, ip->di_uid, ip->di_gid, ip->di_size,
X		ip->di_nlink, ip->di_atime, ip->di_mtime, ip->di_ctime);
X		*/
X	} else if ((ip->di_mode & S_IFMT) == S_IFDIR)
X		SET(dirmask, inum);
X	return 0;
X}
X
X/*
X * Create all the directories down a particular backup path, copying stat
X * info from the original directories.
X */
X
Xcreatdirs(dirpath, origpath, stbufp)
X	char		*dirpath, *origpath;
X	struct stat	*stbufp;
X{
X	char *cp;
X	struct stat	stbuf;
X	extern char *rindex();
X
X	if (mkdir(dirpath, stbufp->st_mode & 0777) < 0) {
X		if (errno == ENOENT) {
X			if ((cp = rindex(dirpath, '/')) > dirpath) {
X				*cp = '\0';
X				creatdirs(dirpath, origpath, stbufp);
X				*cp = '/';
X			}
X			(void) mkdir(dirpath, (stbufp->st_mode & 0777)|0111);
X			if (stat(origpath, &stbuf) == 0) {
X				(void) chown(dirpath, stbuf.st_uid,
X						      stbuf.st_gid);
X				if (stbuf.st_mode & 0400)
X					stbuf.st_mode |= 0100;
X				if (stbuf.st_mode & 040)
X					stbuf.st_mode |= 010;
X				if (stbuf.st_mode & 04)
X					stbuf.st_mode |= 01;
X				(void) chmod(dirpath, stbuf.st_mode & 0777);
X			} else
X				fprintf(stderr, "%s: stat(%s): %s\n",
X					progname, origpath, EMSG(errno));
X		}
X	} else if (stat(origpath, &stbuf) == 0) {
X		(void) chown(dirpath, stbuf.st_uid, stbuf.st_gid);
X		if (stbuf.st_mode & 0400)
X			stbuf.st_mode |= 0100;
X		if (stbuf.st_mode & 040)
X			stbuf.st_mode |= 010;
X		if (stbuf.st_mode & 04)
X			stbuf.st_mode |= 01;
X		(void) chmod(dirpath, stbuf.st_mode & 0777);
X	} else
X		fprintf(stderr, "%s: stat(%s): %s\n",
X				progname, origpath, EMSG(errno));
X}
X
X/* Create an actual backup file, return its file descriptor */
X
Xint
Xcreatbackup(path, stbufp, filename)
X	char	*path, *filename;
X	struct stat	*stbufp;
X{
X	int	fd;
X	char	*cp, *ct;
X
X	ct = ctime(&stbufp->st_mtime);
X	(void) sprintf(filename, "%s%s/", backup, path);
X	cp = filename + strlen(filename);
X	*cp++ = ct[4]; *cp++ = ct[5]; *cp++ = ct[6];
X	*cp++ = ct[8] == ' ' ? '0' : ct[8]; *cp++ = ct[9];
X	*cp++ = '-';
X	*cp++ = ct[11]; *cp++ = ct[12]; *cp++ = ct[13];
X	*cp++ = ct[14]; *cp++ = ct[15]; *cp = '\0';
X	fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, stbufp->st_mode & 0777);
X	if (fd < 0) {
X		if (errno == ENOENT) {
X			cp = rindex(filename, '/');
X			*cp = '\0';
X			creatdirs(filename, filename+strlen(backup), stbufp);
X			*cp = '/';
X		}
X		fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC,
X				    stbufp->st_mode & 0777);
X	}
X	if (fd < 0) {
X		fprintf(stderr, "%s: open(%s): %s\n",
X				progname, filename, EMSG(errno));
X		return -1;
X	}
X	(void) fchown(fd, stbufp->st_uid, stbufp->st_gid);
X	return fd;
X}
X
X/* This routine called from walk() to make a backup of a file */
X
Xint
Xmkbackup(path, glob)
X	char	*path, *glob;
X{
X	struct stat	stbuf;
X	int	n, bfd, fd;
X	char	bigbuf[8*1024], bpath[MAXPATHLEN];
X	struct timeval tv[2];
X
X	if (doglob(glob, path))
X		return;
X	if ((fd = open(path, O_RDONLY, 0)) < 0) {
X		/*
X		 * File may have been removed under our feet.
X		 * Don't make noise about that.
X		 */
X		if (errno == ENOENT)
X			return;
X		fprintf(stderr, "%s: open(%s): %s\n",
X				progname, path, EMSG(errno));
X		return;
X	}
X	if (fstat(fd, &stbuf) < 0) {
X		fprintf(stderr, "%s: fstat(%s): %s\n",
X				progname, path, EMSG(errno));
X		(void) close(fd);
X		return;
X	}
X	if (stbuf.st_mtime < lasttime) {
X		(void) close(fd);
X		return;
X	}
X	if (stbuf.st_size == 0) {
X		(void) close(fd);
X		return;
X	}
X	if (dryrun || verbose > 1)
X		printf("copy %s\n", path);
X	if (dryrun) {
X		(void) close(fd);
X		return;
X	}
X	if ((bfd = creatbackup(path, &stbuf, bpath)) < 0) {
X		(void) close(fd);
X		return;
X	}
X	while ((n = read(fd, bigbuf, sizeof bigbuf)) > 0)
X		if (write(bfd, bigbuf, n) < n) {
X			fprintf(stderr, "%s: write error: %s\n",
X					progname, EMSG(errno));
X			/* saving a little bit is better than nothing... */
X			break;
X		}
X	(void) close(fd);
X	(void) close(bfd);
X	/* Preserve file times, so "find /backup -newer ..." works */
X	tv[0].tv_sec = stbuf.st_atime;
X	tv[1].tv_sec = stbuf.st_mtime;
X	tv[0].tv_usec = tv[1].tv_usec = 0;
X	(void) utimes(bpath, tv);
X}
X
Xstatic int rmcnt;
X
X/* This routine is called from walk() to rm a backup file (and parent dir) */
X
X/* ARGSUSED */
Xint
Xrmbackup(path, glob)
X	char	*path, *glob;
X{
X
X	if (dryrun || verbose > 1)
X		printf("remove %s\n", path);
X	if (dryrun)
X		return;
X	(void) unlink(path);
X	++rmcnt;
X}
X
X/* a file tree walker (a la ftw(3)) for this application (see ftw(3) BUGS) */
X
Xwalk(path, cp, glob, fn, vector, dirvec)
X	char	*path, *cp, *glob;
X	int	(*fn)();
X	char	*vector, *dirvec;
X{
X	register struct direct *dp;
X	register DIR *dirp;
X	register char *eos;
X	register int n;
X
X	if ((dirp = opendir(".")) == NULL) {
X		fprintf(stderr, "%s: opendir(%s): %s\n",
X				progname, path, EMSG(errno));
X		/* error is usually "too many open files", so don't exit */
X		return;
X	}
X	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
X		if (dp->d_name[0] == '.'
X		    && (dp->d_namlen == 1
X			|| (dp->d_name[1] == '.' && dp->d_namlen == 2)))
X			continue;
X		if (vector == NULL)	/* magic for backup partition */
X			(stack[top]+dp->d_fileno)->inum = -dp->d_fileno;
X		if (vector != NULL && TST(vector, dp->d_fileno)) {
X			(void) strcpy(cp, dp->d_name);
X			(*fn)(path, glob);
X		} else if (TST(dirvec, dp->d_fileno)
X			      && chdir(dp->d_name) == 0) {
X			(void) strcpy(cp, dp->d_name);
X			eos = cp + dp->d_namlen;
X			*eos++ = '/';
X			*eos = '\0';
X			n = rmcnt;
X			walk(path, eos, glob, fn, vector, dirvec);
X			(void) chdir("..");
X			if (fn == rmbackup && n != rmcnt) {
X				*--eos = '\0';	/* clobber trailing '/' */
X				(void) rmdir(path);
X			}
X		}
X	}
X	(void) closedir(dirp);
X}
X
X/* Malloc that prints error and dies if it can't honour memory request */
X
Xchar *
Xemalloc(n)
X	u_int	n;
X{
X	char *cp;
X	extern char *malloc();
X
X	if ((cp = malloc(n)) == NULL) {
X		fprintf(stderr, "%s: malloc failure!\n", progname);
X		exit(1);
X	}
X	return cp;
X}
X
X/* Read and parse the configuration file */
X
Xstruct config *
Xreadconfig(fp)
X	FILE	*fp;
X{
X	struct config	*cfe, *cfhead, **pcfenp;
X	char	*cp, *s, buf[BUFSIZ];
X
X	cfhead = NULL;
X	pcfenp = &cfhead;
X	rewind(fp);
X	while (fgets(buf, sizeof buf, fp) != NULL) {
X		cfe = (struct config *)emalloc((u_int)sizeof (struct config));
X		cfe->line = emalloc((u_int)strlen(buf));
X		(void) strncpy(cfe->line, buf, strlen(buf)-1);
X		cfe->next = NULL;
X		cp = buf;
X		while (isascii(*cp) && isspace(*cp))
X			++cp;
X		s = cp;
X		while (isascii(*cp) && !isspace(*cp))
X			++cp;
X		if (*s != '#')
X			*cp++ = '\0';
X		cfe->path = emalloc((u_int)strlen(s)+1);
X		(void) strcpy(cfe->path, s);
X		if (*s == '#') {
X			*(cfe->path+strlen(s)-1) = '\0';	/* kill NL */
X			cfe->lasttime = 0;
X			*pcfenp = cfe;
X			pcfenp = &cfe->next;
X			continue;
X		}
X		while (isascii(*cp) && isspace(*cp))
X			++cp;
X		s = cp;
X		while (isascii(*cp) && !isspace(*cp))
X			++cp;
X		*cp++ = '\0';
X		cfe->interval = emalloc((u_int)strlen(s)+1);
X		(void) strcpy(cfe->interval, s);
X		while (isascii(*cp) && isspace(*cp))
X			++cp;
X		s = cp;
X		while (isascii(*cp) && !isspace(*cp))
X			++cp;
X		*cp++ = '\0';
X		cfe->filter = emalloc((u_int)strlen(s)+1);
X		(void) strcpy(cfe->filter, s);
X		while (isascii(*cp) && isspace(*cp))
X			++cp;
X		/* interpret ctime */
X		*(cfe->line + (cp - buf)) = '\0';
X		cfe->lasttime = ctime2time(cp);
X		*pcfenp = cfe;
X		pcfenp = &cfe->next;
X	}
X	return cfhead;
X}
X
X/* Write the configuration file out with the updated information */
X
Xvoid
Xwriteconfig(fp, cfp)
X	FILE		*fp;
X	struct config	*cfp;
X{
X	rewind(fp);
X	for (; cfp != NULL; cfp = cfp->next) {
X		if (cfp->lasttime == 0) {
X			fprintf(fp, "%s\n", cfp->path);	/* comment */
X			continue;
X		}
X		if (cfp->line == NULL)	/* new entry */
X			fprintf(fp, "%s\t%s\t%s\t%s",
X				    cfp->path, cfp->interval, cfp->filter,
X				    ctime(&cfp->lasttime));
X		else
X			fprintf(fp, "%s%s", cfp->line, ctime(&cfp->lasttime));
X	}
X}
X
X/* Parse a ctime() string into seconds since epoch */
X
Xchar	*ap[] = {	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
X			"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0 };
X
Xstruct timezone	tz = { 0 };
X
Xint
Xctime2time(s)
X	char	*s;
X{
X	static int	isdst, flag = 0;
X	int	sec, century, year, month, dayinmonth, julian, i;
X	struct timeval	tv;
X	struct tm *tms;
X
X	if (strlen(s) < 25)
X		return 0;
X	if (!flag) {
X		(void) gettimeofday(&tv, &tz);
X		tms = localtime(&now);
X		isdst = tms->tm_isdst;
X		flag = 1;
X	}
X	century = atoi(s+20)/100;
X	year = atoi(s+22);
X	dayinmonth = atoi(s+8);
X	for (i = 0; ap[i] != NULL; ++i) {
X		if (strncmp(s+4, ap[i], 3) == 0)
X			break;
X	}
X	if (ap[i] == NULL)
X		month = -1;
X	else {
X		if ((month = ++i) > 2)
X			month -= 3;
X		else
X			month += 9, year--;
X	}
X	sec = atoi(s+17) + 60*(atoi(s+14) + 60*atoi(s+11));
X	/* this is a standard julian date formula of unknown origin */
X	julian = (146097L * century)/4L + (1461L * year)/4L
X		+ (153L * month + 2L)/5L + dayinmonth - 719469L;
X	sec += julian * 24 * 60 * 60 + (tz.tz_minuteswest-(isdst*60))*60;
X	return sec;
X}
X
X/* Parse an interval string, e.g. 2h30m or 8h or 15s, the obvious meanings */
X
Xint
Xinterval(s)
X	char	*s;
X{
X	int	i, sec;
X
X	if (s == NULL)
X		return 0;
X	i = sec = 0;
X	while (*s != '\0' && isascii(*s)) {
X		if (isdigit(*s)) {
X			i *= 10;
X			i += *s - '0';
X		} else if (*s == 'h')
X			sec += 3600*i, i = 0;
X		else if (*s == 'm')
X			sec += 60*i, i = 0;
X		else if (*s == 's')
X			sec += i, i = 0;
X		++s;
X	}
X	return sec;
X}
X
X/* Test if path is de-selected by the glob patterns in the filter string */
X
Xint
Xdoglob(filter, path)
X	char	*filter, *path;
X{
X	register char	*cp;
X
X	for (cp = filter; cp != NULL && *cp != '\0';) {
X		if (match(cp, path))
X			return 1;
X		while (*cp != '\n' && *cp != '\0')
X			++cp;
X		if (*cp == '\n')
X			++cp;
X	}
X	return 0;
X}
X
X/* General glob pattern match routine, customized to exit on newline */
X
Xint
Xmatch(pattern, string)
X	register char	*pattern, *string;
X{
X	while (1)
X		switch (*pattern) {
X		case '*':
X			++pattern;
X			do {
X				if (match(pattern, string))
X					return 1;
X			} while (*string++ != '\0');
X			return 0;
X			break;
X		case '[':
X			if (*string == '\0')
X				return 0;
X			while ((*++pattern != ']') && (*pattern != *string))
X				if (*pattern == '\0')
X					return 0;
X			if (*pattern == ']')
X				return 0;
X			while (*pattern++ != ']')
X				if (*pattern == '\0')
X					return 0;
X			++string;
X			break;
X		case '?':
X			++pattern;
X			if (*string++ == '\0')
X				return 0;
X			break;
X		case '\n':
X			return (*string == '\0');
X		default:
X			if (*pattern++ != *string++)
X				return 0;
X		}
X}
END_OF_backup.c
if test 28044 -ne `wc -c <backup.c`; then
    echo shar: \"backup.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f backup.conf -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"backup.conf\"
else
echo shar: Extracting \"backup.conf\" \(363 characters\)
sed "s/^X//" >backup.conf <<'END_OF_backup.conf'
X# path			intvl	filter file		last done
X/homes/neat/car		8h	/ai/etc/backup.filter	Tue May 23 12:37:01 1989
X/homes/neat/cdr		8h	/ai/etc/backup.filter	Tue May 23 12:37:01 1989
X/homes/neat/lambda	8h	/ai/etc/backup.filter	Tue May 23 12:37:01 1989
X/homes/neat/eq		8h	/ai/etc/backup.filter	Tue May 23 12:37:01 1989
X/sys		8h	/ai/etc/backup.filter	Tue May 23 12:37:01 1989
END_OF_backup.conf
if test 363 -ne `wc -c <backup.conf`; then
    echo shar: \"backup.conf\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f backup.filter -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"backup.filter\"
else
echo shar: Extracting \"backup.filter\" \(109 characters\)
sed "s/^X//" >backup.filter <<'END_OF_backup.filter'
X*/core
X*/a.out
X*/*.o
X*/bin*
X*/#*
X*/*~
X*/.msg?rc
X*/.rnlast
X*/.rnsoft
X*/.oldnewsrc
X*/mbox
X*/.timecheck
X*/*.dvi
END_OF_backup.filter
if test 109 -ne `wc -c <backup.filter`; then
    echo shar: \"backup.filter\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f getback.1 -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"getback.1\"
else
echo shar: Extracting \"getback.1\" \(806 characters\)
sed "s/^X//" >getback.1 <<'END_OF_getback.1'
X.TH GETBACK 1 "UofToronto"
X.SH NAME
Xgetback \- Recover a backed up version of a file.
X.SH SYNOPSIS
X.B getback
X[ -o
X.I /backup
X]
X.I filename
X.SH DESCRIPTION
X.I Getback
Xfinds the the copies made of a given filename made by the backup(8)
Xprogram and allows them to be restored.
XThe default backup tree can be overridden with the command line option.
X.PP
XOnce the backup copies are found, all of them are printed in a list and
Xyou are asked which one you would like to restore.  If a file with the same
X.I filename
Xalready exists, you are asked to confirm before
X.I getback
Xwill overwrite it.
X.SH FILES
X.ta 2i
X/backup	- root of the backup filesystem
X.SH "SEE ALSO"
X.IR backup (8)
X.SH "INFO"
XBased on the original program by Larry Philps, this is a reimplementation
Xby Rayan Zachariassen, both at U of Toronto.
END_OF_getback.1
if test 806 -ne `wc -c <getback.1`; then
    echo shar: \"getback.1\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f getback.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"getback.c\"
else
echo shar: Extracting \"getback.c\" \(5861 characters\)
sed "s/^X//" >getback.c <<'END_OF_getback.c'
X/*
X * Getback - retrieve files stored away by the incremental backup mechanism.
X *
X * Based on an original program by Larry Philps.  This reimplementation is
X * Copyright 1988 by Rayan Zachariassen, solely to prevent you from selling
X * it or putting your name on it.  Free (gratis) redistribution is encouraged.
X */
X
X#include <stdio.h>
X#include <ctype.h>
X#include <signal.h>
X#include <sys/param.h>
X#include <sys/types.h>
X#include <sys/file.h>
X#include <sys/stat.h>
X#include <sys/dir.h>
X
X#define	BACKUP		"/backup"	/* root of backup hierarchy */
X
Xstruct copy {
X	char		*file;
X	struct stat	stbuf;
X	struct copy	*next;
X};
X
X#define	EMSG(x)	(errno < sys_nerr ? sys_errlist[x] : \
X	       (sprintf(errmsgbuf, "unknown error %d", errno), errmsgbuf))
X
Xextern int	errno, sys_nerr;
Xextern char	*sys_errlist[];
Xchar		errmsgbuf[30];
X
Xchar		*progname;
X
Xextern int	optind;
Xextern char	*optarg;
X
Xmain(argc, argv)
X	int	argc;
X	char	*argv[];
X{
X	int		c, errflag, i, copyindex, copyit, ifd, ofd, myuid;
X	char		*backup, *buf, line[BUFSIZ];
X	DIR		*dirp;
X	struct direct	*dp;
X	struct copy	*cep, *copyarr[1000];
X	struct stat	stbuf;
X	char		path[MAXPATHLEN], filename[MAXPATHLEN];
X	extern int	cmpcopy();
X	extern int	errno;
X	extern char	*emalloc(), *sbrk();
X
X	progname = argv[0];
X	backup = BACKUP;
X	errflag = 0;
X	while ((c = getopt(argc, argv, "o:")) != EOF) {
X		switch (c) {
X		case 'o':
X			backup = optarg;
X			break;
X		default:
X			++errflag;
X		}
X	}
X	if (optind != argc - 1) {
X		fprintf(stderr, "%s: missing filename\n", progname);
X		++errflag;
X	}
X	if (errflag) {
X		fprintf(stderr, "Usage: %s [ -o /backup ] filename\n",
X				progname);
X		exit(1);
X	}
X	if (argv[optind][0] == '/')
X		(void) strcpy(filename, argv[optind]);
X	else if (getwd(filename) == NULL) {
X		fprintf(stderr, "%s: %s\n", progname, filename);
X		exit(1);
X	} else
X		(void) sprintf(filename+strlen(filename), "/%s", argv[optind]);
X	(void) sprintf(path, "%s/%s", backup, filename);
X	if ((dirp = opendir(path)) == NULL) {
X		printf("There are no online backups of %s\n", argv[optind]);
X		exit(1);
X	}
X	copyindex = 0;
X	myuid = getuid();
X	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
X		if (dp->d_namlen == sizeof "Jan01-00:00" - 1
X		    && dp->d_name[5] == '-' && dp->d_name[8] == ':') {
X			(void) sprintf(path, "%s/%s/%s", backup, filename, dp->d_name);
X			if (stat(path, &stbuf) < 0)
X				continue;
X			if (myuid != 0 && myuid != stbuf.st_uid)
X				continue;
X			cep = (struct copy *)emalloc((u_int)sizeof (struct copy));
X			cep->file = emalloc(strlen(path)+1);
X			(void) strcpy(cep->file, path);
X			cep->stbuf = stbuf;
X			copyarr[copyindex++] = cep;
X		}
X	}
X	(void) closedir(dirp);
X	if (copyindex == 0) {
X		printf("There are no online backups of \"%s\"\n", argv[optind]);
X		exit(1);
X	} else if (copyindex == 1) {
X		printf("There is only one backup of \"%s\", dated %s",
X			      argv[optind], ctime(&copyarr[0]->stbuf.st_mtime));
X		printf("Retrieve this copy [y] ? ");
X		fflush(stdout);
X		(void) gets(line);
X		if (line[0] == '\0' || line[0] == 'y' || line[0] == 'Y') {
X			copyit = 0;
X		} else {
X			fprintf(stderr, "%s: nothing changed\n", progname);
X			exit(1);
X		}
X	} else {
X		printf("There are %d backup versions of \"%s\" from:\n\n",
X			      copyindex, argv[optind]);
X		qsort((char *)copyarr, copyindex, sizeof copyarr[0], cmpcopy);
X		for (i = 0; i < copyindex; ++i) {
X			printf("\t%2d.\t%.24s\t(%ld bytes)\n",
X			       i+1, ctime(&copyarr[i]->stbuf.st_mtime),
X			       copyarr[i]->stbuf.st_size);
X		}
X		(void) putchar('\n');
X	retry:
X		printf("Enter number corresponding to version you want [%d]: ",
X				copyindex);
X		fflush(stdout);
X		if (gets(line) == NULL)
X			line[0] = 'n';
X		if (line[0] == '\0')
X			copyit = copyindex - 1;
X		else if (line[0] == 'n' || line[0] == 'N') {
X			fprintf(stderr, "%s: nothing changed\n", progname);
X			exit(1);
X		} else if (!isdigit(line[0])
X				 || (i = atoi(line)) <= 0 || i > copyindex) {
X			printf("Answer must be a number from 1-%d, or <cr>.\n",
X				       copyindex);
X			goto retry;
X		} else
X			copyit = i-1;
X	}
X	if (stat(argv[optind], &stbuf) == 0) {
X		if (stbuf.st_uid != myuid) {
X			fprintf(stderr, "%s: you are not owner of %s\n",
X					progname, argv[optind]);
X			exit(1);
X		}
X		printf("\"%s\" exists, overwrite [y] ? ", argv[optind]);
X		(void) gets(line);
X		if (!(line[0] == '\0' || line[0] == 'y' || line[0] == 'Y')) {
X			fprintf(stderr, "%s: nothing changed\n", progname);
X			exit(1);
X		}
X	}
X	printf("Retrieving %.24s version of \"%s\" ... ",
X			   ctime(&copyarr[copyit]->stbuf.st_mtime),
X			   argv[optind]);
X	if ((buf = sbrk(copyarr[copyit]->stbuf.st_blksize)) == NULL) {
X		fprintf(stderr, "%s: sbrk(%d): %s\n",
X				progname, copyarr[copyit]->stbuf.st_blksize,
X				EMSG(errno));
X		exit(1);
X	}
X	if ((ifd = open(copyarr[copyit]->file, O_RDONLY, 0)) < 0) {
X		fprintf(stderr, "%s: open(%s): %s\n",
X				progname, copyarr[copyit]->file, EMSG(errno));
X		exit(1);
X	}
X
X	(void) sigsetmask(sigmask(SIGHUP)
X			| sigmask(SIGINT)
X			| sigmask(SIGQUIT)
X			| sigmask(SIGTERM)
X			| sigmask(SIGTSTP));
X
X	if ((ofd = open(argv[optind], O_WRONLY|O_CREAT|O_TRUNC, 0644)) < 0) {
X		fprintf(stderr, "%s: open(%s): %s\n",
X				progname, argv[optind], EMSG(errno));
X		exit(1);
X	}
X	(void) umask(0);
X	(void) fchmod(ofd, copyarr[copyit]->stbuf.st_mode);
X	(void) fchown(ofd, copyarr[copyit]->stbuf.st_uid,
X			   copyarr[copyit]->stbuf.st_gid);
X	while ((i = read(ifd, buf, copyarr[copyit]->stbuf.st_blksize)) > 0)
X		if (write(ofd, buf, i) < i) {
X			fprintf(stderr, "%s: write of %d bytes: %s\n",
X					progname, i, EMSG(errno));
X			exit(1);
X		}
X	(void) close(ofd);
X	(void) close(ifd);
X	printf("done!\n");
X	exit(0);
X}
X
Xint
Xcmpcopy(a, b)
X	struct copy **a, **b;
X{
X	return (*a)->stbuf.st_mtime - (*b)->stbuf.st_mtime;
X}
X
Xchar *
Xemalloc(n)
X	u_int	n;
X{
X	char *cp;
X	extern char *malloc();
X
X	if ((cp = malloc(n)) == NULL) {
X		fprintf(stderr, "%s: malloc(%u) failed!\n", progname, n);
X		exit(1);
X	}
X	return cp;
X}
END_OF_getback.c
if test 5861 -ne `wc -c <getback.c`; then
    echo shar: \"getback.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f getback.sh -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"getback.sh\"
else
echo shar: Extracting \"getback.sh\" \(2205 characters\)
sed "s/^X//" >getback.sh <<'END_OF_getback.sh'
X#!/bin/sh
X# getback [-o /backup] filename
X
XPATH=/bin:/usr/bin:/usr/ucb; export PATH
XBACKUP=/backup
Xmyname=`basename $0`
XTMP=/tmp/${myname}$$
XUSER=${USER-`whoami`} || exit
X
Xusage() {
X	echo "Usage: $myname [-o /backup] filename" 1>&2
X	exit 1
X}
Xsorry() {
X	echo "Sorry, there are no online backups of $filename"
X	exit 1
X}
Xgiveup() {
X	echo "$myname: nothing changed"
X	exit 1
X}
X
Xset -- `getopt o: $*`
Xif [ $? != 0 ]; then
X	usage
Xfi
Xfor i in $*; do
X	case $i in
X	-o) BACKUP=$2; shift 2;;
X	--) shift; break;;
X	esac
Xdone
X
Xif [ $# != 1 ]; then
X	echo "$myname: missing filename" 1>&2
X	usage
Xfi
X
Xfilename=$1
Xcase $filename in
X/*) backdir=$BACKUP$1;;
X*)  backdir=$BACKUP`/bin/pwd`/$1;;
Xesac
X
Xtest -d $backdir || sorry
Xtrap "/bin/rm -f $TMP; exit" 0 1 2 15
X# We could ensure that the backup filenames look reasonable here...
Xls -lrt $backdir | awk "\$1 ~ /^-/ && \$3 == \"$USER\"" >$TMP
Xncopies=`sed -n '$=' $TMP`
Xcase $ncopies in
X1)
X	set -- `cat $TMP`
X	echo "There is only one backup of \"$filename\", dated $5 $6 $7"
X	echo -n "Retrieve this copy [y] ? "
X	read ans
X	case "$ans" in
X	"" | y* | Y* ) ;;
X	*) giveup;;
X	esac
X	;;
X[0-9]*)
X	echo "There are $ncopies backup versions of \"$filename\" from:"
X	echo ""
X	awk '{printf "\t%2d.\t%s %2d %s\t(%d bytes)\n", NR,$5,$6,$7,$4}' $TMP
X	echo ""
X	while :; do
X		echo -n "Enter number corresponding to the version you want [$ncopies] "
X		read ans
X		case "$ans" in
X		"")
X			version=$ncopies
X			break;;
X		n* | N*)
X			giveup;;
X		[0-9]*)
X			if [ $ans -gt 0 -a $ans -le $ncopies ]; then
X				version=$ans
X				break
X			fi;;
X		esac
X		echo "Answer must be a number from 1 to $ncopies, or <cr>."
X	done
X	set -- `sed -n ${version}p $TMP`
X	;;
X*)
X	sorry;;
Xesac
Xif [ -f $filename ]; then
X	owner=`ls -l $filename | awk '{print $3}'`
X	if [ $owner != $USER ]; then
X		echo "$myname: you are not the owner of $filename"
X		exit 1
X	fi
X	echo -n "\"$filename\" exists, overwrite [y] ? "
X	read ans
X	case "$ans" in
X	"" | y* | Y* );;
X	*) giveup;;
X	esac
Xfi
Xecho -n "Retrieving $5 $6 $7 version of \"$filename\" ... "
Xcp -p $backdir/$8 $filename || {
X	echo $myname: copy failed
X	exit 1
X}
X# Update the times, but keep the modes
Xtouch -f $filename || echo $myname: Can\'t touch \"$filename\"
Xecho "done!"
Xexit 0
END_OF_getback.sh
if test 2205 -ne `wc -c <getback.sh`; then
    echo shar: \"getback.sh\" unpacked with wrong size!
fi
chmod +x getback.sh
# end of overwriting check
fi
if test -f ilw.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"ilw.c\"
else
echo shar: Extracting \"ilw.c\" \(3290 characters\)
sed "s/^X//" >ilw.c <<'END_OF_ilw.c'
X/*
X * i-list-walker -- apply a specified function to all inodes on a filesystem
X *
X * Based on original routines by Larry Philps.  This reimplementation is
X * Copyright 1988 by Rayan Zachariassen, solely to prevent you from selling
X * it or putting your name on it.  Free (gratis) redistribution is encouraged.
X */
X
X#include <stdio.h>
X#include <sys/param.h>
X#include <sys/types.h>
X#include <sys/file.h>
X#include <sys/time.h>
X#include <sys/vnode.h>
X#include <ufs/inode.h>
X#include <ufs/fs.h>
X
Xextern char *progname;		/* must be supplied by main program */
X
Xstatic char	errmsgbuf[30];
X#define	EMSG(x)	(errno < sys_nerr ? sys_errlist[x] : \
X	       (sprintf(errmsgbuf, "unknown error %d", errno), errmsgbuf))
X
Xextern int	errno, sys_nerr;
Xextern char	*sys_errlist[];
Xextern off_t	lseek();
X
Xint
Xbread(fd, bno, buf, cnt)
X	daddr_t	bno;
X	char	*buf;
X	long	cnt;
X{
X	if (lseek(fd, (off_t) bno * DEV_BSIZE, L_SET) == -1) {
X		fprintf(stderr, "%s: bread: lseek: %s\n",
X				progname, EMSG(errno));
X		return -2;
X	}
X	if (read(fd, buf, (int) cnt) != cnt) {
X		fprintf(stderr, "%s: read error at block %u: %s\n",
X				progname, bno, EMSG(errno));
X		return -1;
X	}
X	return 0;
X}
X
Xint
Xbwrite(fd, bno, buf, cnt)
X	daddr_t	bno;
X	char	*buf;
X	long	cnt;
X{
X	if (lseek(fd, (off_t) bno * DEV_BSIZE, L_SET) == -1) {
X		fprintf(stderr, "%s: bwrite: lseek: %s\n",
X				progname, EMSG(errno));
X		return -2;
X	}
X	if (write(fd, buf, (int) cnt) != cnt) {
X		fprintf(stderr, "%s: write error at block %u: %s\n",
X				progname, bno, EMSG(errno));
X		return -1;
X	}
X	return 0;
X}
X
X/*
X * i-list-walker
X *
X * Opens the raw device indicated, and calls the function fn with an inode
X * pointer for every inode on the device.  If the function returns non-zero,
X * the inode on the disk is updated, unless the readonly flag is set.
X *
X * Return -1 in case of error, +ve if inodes were updated, 0 otherwise.
X */
X
Xilw(rawdev, fn, readonly)
X	char	*rawdev;
X	int	(*fn)();
X	int	readonly;
X{
X	register u_int	ino;
X	register int	fd, j, nfiles, changed, touched;
X	struct   dinode	*ip;
X	daddr_t		iblk;
X	struct dinode	itab[MAXBSIZE/sizeof(struct dinode)];
X	union {
X		struct fs u_sblock;
X		char dummy[SBSIZE];
X	} sb_un;
X#define sblock	sb_un.u_sblock
X
X	if ((fd = open(rawdev, readonly ? O_RDONLY : O_RDWR, 0)) < 0) {
X		fprintf(stderr, "%s: open(%s): %s\n",
X				progname, rawdev, EMSG(errno));
X		return -1;
X	}
X	/*
X	 * Don't bother stat'ing the device, we might want to do this on
X	 * real files some day.
X	 */
X	/* printf ("starting filesystem %s\n", rawdev); */
X	sync();
X	if (bread(fd, SBLOCK, (char *)&sblock, (long) SBSIZE) < 0) {
X		close(fd);
X		return -1;
X	}
X	nfiles = sblock.fs_ipg * sblock.fs_ncg;
X	touched = 0;
X	for (ino = 0; ino < nfiles; touched += changed) {
X		iblk = fsbtodb(&sblock, itod(&sblock, ino));
X		if (bread(fd, iblk, (char *)itab, sblock.fs_bsize) < 0) {
X			(void) close(fd);
X			return -1;
X		}
X		changed = 0;
X		for (j = 0; j < INOPB(&sblock) && ino < nfiles; j++, ino++) {
X			ip = &itab[j];
X			if ((ino < ROOTINO) || ((ip->di_mode&IFMT) == 0))
X				continue;
X			if ((*fn)(ip, ino))
X				++changed;
X		}
X		if (!changed)
X			continue;
X		if (readonly)
X			printf("replacing block %d with %d changes\n",
X					  iblk, changed);
X		else if (bwrite(fd, iblk, (char *)itab, sblock.fs_bsize) < 0) {
X			(void) close(fd);
X			return -1;
X		}
X	}
X	return touched;
X}
END_OF_ilw.c
if test 3290 -ne `wc -c <ilw.c`; then
    echo shar: \"ilw.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
echo shar: End of shell archive.
exit 0

-- 
Please send comp.sources.unix-related mail to rsalz at uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.



More information about the Comp.sources.unix mailing list