Subject: v19i102: Save files that have changed in the last little while Newsgroups: comp.sources.unix Sender: sources Approved: rsalz@uunet.UU.NET Submitted-by: Rayan Zachariassen 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 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 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 ). END_OF_backup.8 if test 4871 -ne `wc -c 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 X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include 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.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.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 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.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 X#include X#include X#include X#include X#include X#include X#include 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(©arr[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(©arr[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 .\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(©arr[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.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 ." 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 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 X#include X#include X#include X#include X#include X#include X#include 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