home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Usenet 1994 October
/
usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso
/
unix
/
volume19
/
backup
/
backup.c
< prev
next >
Wrap
C/C++ Source or Header
|
1989-06-29
|
28KB
|
1,122 lines
/*
* backup - do incremental disk-to-disk backups of individual files
*
* This code is a complete reimplementation (for the SunOS environment)
* of an original idea by Ciaran O'Donnell. This implementation is
* Copyright 1988 by Rayan Zachariassen, solely to prevent you selling
* it or putting your name on it. Free (gratis) redistribution is
* encouraged.
*/
#ifndef lint
static char *RCSid = "$Header: /ai/car/src/etc/backup/RCS/backup.c,v 1.2 89/05/23 22:13:19 rayan Exp $";
#endif lint
#include <stdio.h>
#include <ctype.h>
#include <mntent.h>
#include <values.h>
#include <errno.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/vnode.h>
#include <sys/vfs.h>
#include <ufs/inode.h>
#include <ufs/fs.h>
#include <sys/dir.h>
#define HIWATER 95 /* % of backup filesystem used at high water */
#define LOWATER 85 /* % of backup filesystem used at low water */
#define BACKUP "/backup" /* backup filesystem mount point */
#ifndef CONFIG
#define CONFIG "/etc/backup.conf" /* configuration file */
#endif /* !CONFIG */
#define MAXSIZE 100000 /* default maximum size of files to back up */
#define DELTA (24*60*60) /* default time since last backup */
int maxsize = MAXSIZE; /* maximum size in bytes of files to back up */
int doexecs = 0; /* should we back up executables? */
struct a_path {
char *path; /* path to tree hierarchy we want to back up */
char *glob; /* pointer to the contents of the filter file */
time_t newer; /* back up files newer than this time */
struct config *config;/* back pointer to config structure */
struct a_path *next;
};
struct a_dev {
char *text; /* raw device name */
struct a_path *paths; /* pointer to list of paths */
struct a_dev *next; /* pointer to next device */
};
struct a_dev *devlist = NULL;
struct config {
char *path; /* top of hierarchy we want to back up */
char *interval; /* how often to do incrementals */
char *filter; /* name of file containing regexp's */
char *line; /* the config file line excluding time field */
time_t lasttime; /* last time incrementals were done here */
struct config *next;
};
char *progname, *backup, *devbackup, *backupmnt;
int debug, verbose, dryrun;
time_t now, lasttime;
extern int optind;
extern char *optarg;
extern char *emalloc();
#define EMSG(x) (errno < sys_nerr ? sys_errlist[x] : \
(sprintf(errmsgbuf, "unknown error %d", errno), errmsgbuf))
extern int errno, sys_nerr;
extern char *sys_errlist[];
char errmsgbuf[30];
main(argc, argv)
int argc;
char *argv[];
{
int c, errflag;
char *cffile;
struct a_dev *lp;
struct a_path *pp;
struct config *cf, *cfp;
FILE *cf_fp;
extern time_t time();
extern char *getdevice(), *getpath(), *ctime();
extern struct config *readconfig();
extern void writeconfig();
progname = argv[0];
errflag = 0;
dryrun = 0;
debug = verbose = 0;
backup = BACKUP;
cffile = CONFIG;
while ((c = getopt(argc, argv, "bc:devo:z")) != EOF) {
switch (c) {
case 'b': /* don't back up files larger than arg kbytes */
if ((maxsize = atoi(optarg)) < 1) {
fprintf(stderr,
"%s: illegal argument to -b: %d\n",
progname, optarg);
++errflag;
} else
maxsize <<= 10; /* multiple of 1k */
break;
case 'c': /* specify alternate configuration file */
cffile = optarg;
break;
case 'd': /* debugging output */
++debug;
break;
case 'e': /* copy executables */
++doexecs;
break;
case 'v': /* be (more and more) verbose */
++verbose;
break;
case 'o': /* specify alternate backup location */
backup = optarg;
break;
case 'z': /* fake it */
++dryrun;
break;
default:
++errflag;
}
}
(void) time(&now);
if ((cf_fp = fopen(cffile, "r+")) == NULL) {
fprintf(stderr, "%s: open(%s): %s\n",
progname, cffile, EMSG(errno) /* ... */);
Usage();
}
if (verbose
&& flock(fileno(cf_fp), LOCK_EX|LOCK_NB) < 0
&& errno == EWOULDBLOCK)
printf("waiting for exclusive lock on %s\n", cffile);
if (flock(fileno(cf_fp), LOCK_EX) < 0) {
fprintf(stderr, "%s: can't get lock on %s\n",
progname, cffile);
exit(1);
} else if (verbose > 1) {
setbuf(stdout, (char *)NULL);
printf("Start at %slocked %s\n", ctime(&now), cffile);
}
cf = readconfig(cf_fp);
if (optind == argc) {
/* figure out what we should back up */
for (cfp = cf; cfp != NULL; cfp = cfp->next) {
if (cfp->lasttime == 0) {
if (debug)
printf("%s: lasttime is 0\n", cfp->path);
continue;
}
/* give 5 minutes leeway */
if (cfp->lasttime + interval(cfp->interval) < now+300) {
/* add this path to the list */
if (debug)
printf("adding %s\n", cfp->path);
addpath(cfp);
} else if (debug)
printf("ignoring %s: lasttime=%d interval=%s now=%d\n",
cfp->path, cfp->lasttime, cfp->interval, now);
}
} else {
for (; optind < argc; ++optind) {
for (cfp = cf; cfp != NULL; cfp = cfp->next) {
if (cfp->lasttime == 0)
continue;
if (strcmp(cfp->path, argv[optind]) == 0) {
addpath(cfp);
break;
}
}
if (cfp == NULL) {
/* append new entry to config file */
for (cfp = cf; cfp != NULL && cfp->next != NULL;
cfp = cfp->next)
continue;
if (cfp != NULL) {
cfp->next = (struct config *)
emalloc((u_int)sizeof (struct config));
cfp = cfp->next;
cfp->next = NULL;
cfp->line = NULL;
cfp->interval = "24h";
cfp->path = argv[optind];
cfp->filter = "/dev/null";
}
cfp->lasttime = now - DELTA;
addpath(cfp);
}
}
}
if ((devbackup = getdevice(backup, MNTTAB)) == NULL &&
(devbackup = getdevice(backup, MOUNTED)) == NULL) {
fprintf(stderr, "%s: could not determine backup device\n",
progname);
exit(1);
}
if ((backupmnt = getpath(devbackup, MNTTAB)) == NULL &&
(backupmnt = getpath(devbackup, MOUNTED)) == NULL) {
fprintf(stderr, "%s: could not determine mount point for %s\n",
progname, devbackup);
++errflag;
}
if (errflag)
Usage();
(void) umask(0);
for (lp = devlist; lp != NULL; lp = lp->next)
if (doit(lp->paths, lp->text))
for (pp = lp->paths; pp != NULL; pp = pp->next)
pp->config->lasttime = now;
if (!dryrun)
writeconfig(cf_fp, cf);
(void) flock(fileno(cf_fp), LOCK_UN);
if (verbose > 1) {
time(&now);
printf("unlocked %s\nEnd at %s", cffile, ctime(&now));
}
(void) fclose(cf_fp);
#ifdef PROF
/* drop profiling data in a known place */
chdir("/tmp");
#endif
exit(0);
}
Usage()
{
fprintf(stderr,
"Usage: %s [ -devz -b# ] [ -c backup.conf ] [ -o /backup ] [ /path ... ]\n",
progname);
exit(1);
}
/*
* The filter files contain glob expressions, one per line. We need to
* keep track of which filter files we've read in, since several paths
* may share the same filters.
*/
struct filter {
char *name;
char *contents;
} filters[50];
int maxfilters = 0;
/* return pointer to contents of a filter file stored away */
char *
readfilter(file)
char *file;
{
int i, fd;
struct stat stbuf;
if (strcmp(file, "/dev/null") == 0)
return NULL;
for (i = 0; i < maxfilters
&& i < (sizeof filters/sizeof (struct filter)); ++i) {
if (strcmp(file, filters[i].name) == 0)
return filters[i].contents;
}
if (i == (sizeof filters/sizeof (struct filter))) {
fprintf(stderr, "%s: ran out of filters\n", progname);
exit(1);
}
if ((fd = open(file, O_RDONLY, 0)) < 0) {
fprintf(stderr, "%s: can't open filter file %s\n",
progname, file);
exit(1);
}
if (fstat(fd, &stbuf) < 0) {
fprintf(stderr, "%s: can't stat open filter file %s\n",
progname, file);
exit(1);
}
filters[i].contents = emalloc((u_int)stbuf.st_size+1);
if (read(fd, filters[i].contents, stbuf.st_size) < stbuf.st_size) {
fprintf(stderr, "%s: couldn't read %d bytes from %s\n",
progname, stbuf.st_size, file);
}
*(filters[i].contents+stbuf.st_size) = '\0';
filters[i].name = file;
++maxfilters;
(void) close(fd);
return filters[i].contents;
}
/*
* We maintain a two-level linked list structure (i.e. list of lists),
* associating paths with their raw device. When there are several paths
* on the same device, we want to handle them simultaneously and only
* do the ilist walking once per device. The root of this structure is
* devlist.
*/
int
addpath(cfp)
struct config *cfp;
{
struct a_dev *lp;
struct a_path *pp;
char *rawdevice;
if (cfp->path == NULL || *cfp->path != '/') {
fprintf(stderr, "%s: illegal path: %s\n",
progname,
cfp->path == NULL ? "(null)" : cfp->path);
} else if ((rawdevice = getdevice(cfp->path, MNTTAB)) == NULL) {
fprintf(stderr, "%s: no device for %s\n",
progname, cfp->path);
} else if (*rawdevice != '/') {
fprintf(stderr, "%s: bad device %s for %s\n",
progname, rawdevice, cfp->path);
} else {
/* link the path, device pair into lists */
for (lp = devlist; lp != NULL; lp = lp->next)
if (strcmp(lp->text, rawdevice) == 0)
break;
pp = (struct a_path *)
emalloc((u_int)sizeof (struct a_path));
pp->path = cfp->path;
pp->newer = cfp->lasttime;
pp->glob = readfilter(cfp->filter);
pp->config = cfp;
if (lp != NULL)
pp->next = lp->paths;
else {
lp = (struct a_dev *)
emalloc((u_int)sizeof (struct a_dev));
lp->next = devlist;
devlist = lp;
lp->text = emalloc((u_int)strlen(rawdevice)+1);
(void) strcpy(lp->text, rawdevice);
pp->next = NULL;
}
lp->paths = pp;
}
}
/* return the name of the raw device corresponding to a particular file */
char *
getdevice(path, table)
char *path;
char *table;
{
char *cp, *s;
struct mntent *me;
FILE *fp;
struct stat stpath, stdev;
if (lstat(path, &stpath) < 0) {
fprintf(stderr, "%s: lstat(%s): %s\n",
progname, path, EMSG(errno));
return NULL;
}
if (!((stpath.st_mode & S_IFMT) & (S_IFREG | S_IFDIR))) {
fprintf(stderr, "%s: %s is a special file\n", progname, path);
return NULL;
}
if ((fp = setmntent(table, "r")) == NULL) {
fprintf(stderr, "%s: setmntent(%s): %s\n",
progname, table, EMSG(errno));
return NULL;
}
while ((me = getmntent(fp)) != NULL) {
if (strcmp(me->mnt_type, MNTTYPE_42) == 0
&& strncmp(me->mnt_fsname, "/dev/", 5) == 0
&& lstat(me->mnt_fsname, &stdev) == 0
&& stdev.st_rdev == stpath.st_dev) {
(void) endmntent(fp);
cp = emalloc((u_int)strlen(me->mnt_fsname)+2);
(void) strcpy(cp, me->mnt_fsname);
s = cp + strlen(cp) + 1;
while (s > cp && *(s - 1) != '/')
*s = *(s-1), --s;
if (s > cp)
*s = 'r';
return cp;
}
}
(void) endmntent(fp);
return NULL;
}
/* get the mount point of the filesystem on the raw device */
char *
getpath(device, table)
char *device;
char *table;
{
char *cp;
struct mntent *me;
FILE *fp;
char devpath[MAXPATHLEN];
struct stat stpath, stdev;
extern char *rindex();
(void) strcpy(devpath, device);
if ((cp = rindex(devpath, '/')) != NULL) {
++cp;
if (*cp == 'r')
while ((*cp = *(cp+1)) != '\0')
++cp;
}
if ((fp = setmntent(table, "r")) == NULL) {
fprintf(stderr, "%s: setmntent(%s): %s\n",
progname, table, EMSG(errno));
return NULL;
}
while ((me = getmntent(fp)) != NULL) {
if (strcmp(me->mnt_type, MNTTYPE_42) == 0
&& strcmp(me->mnt_fsname, devpath) == 0
&& lstat(me->mnt_fsname, &stdev) == 0
&& lstat(me->mnt_dir, &stpath) == 0
&& stdev.st_rdev == stpath.st_dev) {
(void) endmntent(fp);
cp = emalloc((u_int)strlen(me->mnt_dir)+1);
(void) strcpy(cp, me->mnt_dir);
return cp;
}
}
(void) endmntent(fp);
return NULL;
}
#define sblock sb_un.u_sblock
struct iinfo {
int inum; /* must be int so can be -ve too */
u_int blks;
time_t mtime;
};
int
cmpiinfo(iip1, iip2)
register struct iinfo *iip1, *iip2;
{
return iip1->mtime - iip2->mtime;
}
struct iinfo *stack[2];
long stacksize[2];
long needspace[2];
int top = -1;
char *dirmask;
int mustfree;
#define SET(v,i) ((v)[(i)/BITSPERBYTE] |= (1<<((i)%BITSPERBYTE)))
#define TST(v,i) ((v)[(i)/BITSPERBYTE] & (1<<((i)%BITSPERBYTE)))
int
doit(path, dev)
struct a_path *path;
char *dev;
{
register struct iinfo *st;
register int i, inum;
int fd, hiwater, lowater;
u_int nfiles;
union { struct fs u_sblock; char dummy[SBSIZE]; } sb_un;
char *bitvec, *dirvec, pathbuf[MAXPATHLEN];
struct a_path *pp;
struct statfs fsbuf;
extern int itest(), mkbackup(), rmbackup();
extern char *calloc();
if (debug)
printf("doing %s\n", dev);
if ((fd = open(dev, O_RDONLY, 0)) < 0) {
fprintf(stderr, "%s: open(%s): %s\n",
progname, dev, EMSG(errno));
return 0;
}
if (bread(fd, SBLOCK, (char *)&sblock, (long) SBSIZE) < 0) {
fprintf(stderr, "%s: can't read superblock from %s: %s\n",
progname, dev, EMSG(errno));
return 0;
}
(void) close(fd);
nfiles = sblock.fs_ipg * sblock.fs_ncg;
stack[++top] = (struct iinfo *)calloc(nfiles, sizeof (struct iinfo));
stacksize[top] = 0;
needspace[top] = 0;
dirvec = calloc((nfiles/BITSPERBYTE)+1, 1);
dirmask = dirvec;
if (top == 0) {
/* figure out the oldest lasttime before i-list walk */
lasttime = now;
for (pp = path; pp != NULL; pp = pp->next)
if (pp->newer < lasttime)
lasttime = pp->newer;
}
if (debug)
printf("%s: scan %d inodes\n", dev, nfiles);
(void) ilw(dev, itest, 1);
if (verbose)
printf("%s: found %d candidate files, with %d blocks total\n",
dev, stacksize[top], needspace[top]);
if (stacksize[top] == 0) {
(void) free(dirvec);
(void) free((char *)stack[top--]);
return 0;
}
bitvec = calloc((nfiles/BITSPERBYTE)+1, 1);
if (top == 0) {
/*
* This is the filesystem we want to back up.
* First make sure there is enough free space in the backup
* filesystem (if not, call myself recursively), then run
* a file tree walker to copy the indicated files to the
* backup filesystem.
*/
if (statfs(backup, &fsbuf) < 0) {
fprintf(stderr, "%s: statfs(%s): %s\n",
progname, backup, EMSG(errno));
exit(1);
}
hiwater = (fsbuf.f_blocks-fsbuf.f_bfree
+fsbuf.f_bavail)*HIWATER/100;
if (fsbuf.f_blocks - fsbuf.f_bfree + needspace[top] > hiwater) {
/* need to free some space */
struct a_path backupdesc;
/*
* If you want to free so free space will be at
* LOWATER after backup finishes, then enable the
* next line and do s/hiwater/lowater/ in the
* following line defining mustfree.
*/
/* lowater = +(hiwater*LOWATER)/HIWATER; */
mustfree = fsbuf.f_blocks - fsbuf.f_bfree
+ needspace[top] - hiwater;
/* select all files */
lasttime = 0;
maxsize = sblock.fs_dsize * DEV_BSIZE;
backupdesc.path = backup;
backupdesc.next = NULL;
backupdesc.glob = NULL;
backupdesc.newer = lasttime;
if (!doit(&backupdesc, devbackup)) {
fprintf(stderr,
"%s: Can't walk %s to free space\n",
progname, backup);
exit(1);
}
}
for (i = 0, st = stack[top]; i < nfiles; ++i, ++st) {
if (st->mtime > 0) {
SET(bitvec, st->inum);
}
}
for (; path != NULL; path = path->next) {
if (chdir(path->path) < 0) {
fprintf(stderr, "%s: chdir(%s): %s\n",
progname, path->path, EMSG(errno));
exit(1);
}
(void) sprintf(pathbuf, "%s/", path->path);
lasttime = path->newer;
if (verbose)
printf("%s: select mtime within %d sec in %s\n",
dev, now - lasttime, path->path);
walk(pathbuf, pathbuf + strlen(pathbuf), path->glob,
mkbackup, bitvec, dirvec);
}
} else {
/*
* This is the backup filesystem.
* Sort the inodes selected into oldest-first, then run
* a file tree walker to delete the files until we have
* enough space *and* are under the low water mark.
*/
/* assert strcmp(path->path, backup) == 0 */
/* compress the inode array */
st = stack[top];
for (i = inum = 0; i < stacksize[top]; ++inum) {
if ((st+inum)->mtime > 0) {
(st+i)->inum = inum;
(st+i)->blks = (st+inum)->blks;
(st+i)->mtime = (st+inum)->mtime;
++i;
}
}
if (chdir(path->path) < 0) {
fprintf(stderr, "%s: chdir(%s): %s\n",
progname, path->path, EMSG(errno));
exit(1);
}
(void) sprintf(pathbuf, "%s/", path->path);
if (strcmp(backup, backupmnt) != 0) {
/* backup area is not an entire filesystem */
walk(pathbuf, pathbuf + strlen(pathbuf), (char *)NULL,
(int (*)())NULL, (char *)NULL, dirvec);
/* now all possible inums are negative and rest +ve */
st = stack[top];
for (i = inum = 0; i < stacksize[top]; ++inum) {
if ((st+inum)->inum < 0) {
(st+i)->inum = inum;
++i;
} else
(st+i)->inum = 0;
}
/* now all possible inums are +ve and rest 0 */
}
/* sort it oldest first */
qsort((char *)stack[top], stacksize[top],
sizeof (struct iinfo), cmpiinfo);
/* mustfree has been set in our parent doit() */
/* go from oldest to newest, truncate after mustfree blocks */
st = stack[top];
for (i = 0; i < stacksize[top] && mustfree > 0; ++i, ++st) {
if (st->inum > 2 && st->mtime > 0) {
mustfree -= st->blks;
SET(bitvec, st->inum);
}
}
(void) sprintf(pathbuf, "%s/", path->path);
walk(pathbuf, pathbuf + strlen(pathbuf), (char *)NULL,
rmbackup, bitvec, dirvec);
}
(void) free(bitvec);
(void) free(dirvec);
(void) free((char *)stack[top--]);
return 1;
}
/* This routine is used by the inode list walker) to test for relevant inodes */
itest(ip, inum)
struct dinode *ip;
int inum;
{
register struct iinfo *iip;
if ((ip->di_mode & S_IFMT) == S_IFREG
&& ip->di_mtime > lasttime
&& ip->di_size < maxsize
&& (doexecs || (ip->di_mode & 07111) == 0)) {
/* we have a candidate for backing up */
iip = stack[top] + inum;
iip->inum = inum;
stacksize[top] += 1;
needspace[top] += (iip->blks = ip->di_blocks);
iip->mtime = ip->di_mtime;
/*
printf("%6d:\tmode=0%o uid=%d gid=%d size=%d nlink=%d, a=%D m=%D c=%D\n",
inum, ip->di_mode, ip->di_uid, ip->di_gid, ip->di_size,
ip->di_nlink, ip->di_atime, ip->di_mtime, ip->di_ctime);
*/
} else if ((ip->di_mode & S_IFMT) == S_IFDIR)
SET(dirmask, inum);
return 0;
}
/*
* Create all the directories down a particular backup path, copying stat
* info from the original directories.
*/
creatdirs(dirpath, origpath, stbufp)
char *dirpath, *origpath;
struct stat *stbufp;
{
char *cp;
struct stat stbuf;
extern char *rindex();
if (mkdir(dirpath, stbufp->st_mode & 0777) < 0) {
if (errno == ENOENT) {
if ((cp = rindex(dirpath, '/')) > dirpath) {
*cp = '\0';
creatdirs(dirpath, origpath, stbufp);
*cp = '/';
}
(void) mkdir(dirpath, (stbufp->st_mode & 0777)|0111);
if (stat(origpath, &stbuf) == 0) {
(void) chown(dirpath, stbuf.st_uid,
stbuf.st_gid);
if (stbuf.st_mode & 0400)
stbuf.st_mode |= 0100;
if (stbuf.st_mode & 040)
stbuf.st_mode |= 010;
if (stbuf.st_mode & 04)
stbuf.st_mode |= 01;
(void) chmod(dirpath, stbuf.st_mode & 0777);
} else
fprintf(stderr, "%s: stat(%s): %s\n",
progname, origpath, EMSG(errno));
}
} else if (stat(origpath, &stbuf) == 0) {
(void) chown(dirpath, stbuf.st_uid, stbuf.st_gid);
if (stbuf.st_mode & 0400)
stbuf.st_mode |= 0100;
if (stbuf.st_mode & 040)
stbuf.st_mode |= 010;
if (stbuf.st_mode & 04)
stbuf.st_mode |= 01;
(void) chmod(dirpath, stbuf.st_mode & 0777);
} else
fprintf(stderr, "%s: stat(%s): %s\n",
progname, origpath, EMSG(errno));
}
/* Create an actual backup file, return its file descriptor */
int
creatbackup(path, stbufp, filename)
char *path, *filename;
struct stat *stbufp;
{
int fd;
char *cp, *ct;
ct = ctime(&stbufp->st_mtime);
(void) sprintf(filename, "%s%s/", backup, path);
cp = filename + strlen(filename);
*cp++ = ct[4]; *cp++ = ct[5]; *cp++ = ct[6];
*cp++ = ct[8] == ' ' ? '0' : ct[8]; *cp++ = ct[9];
*cp++ = '-';
*cp++ = ct[11]; *cp++ = ct[12]; *cp++ = ct[13];
*cp++ = ct[14]; *cp++ = ct[15]; *cp = '\0';
fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, stbufp->st_mode & 0777);
if (fd < 0) {
if (errno == ENOENT) {
cp = rindex(filename, '/');
*cp = '\0';
creatdirs(filename, filename+strlen(backup), stbufp);
*cp = '/';
}
fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC,
stbufp->st_mode & 0777);
}
if (fd < 0) {
fprintf(stderr, "%s: open(%s): %s\n",
progname, filename, EMSG(errno));
return -1;
}
(void) fchown(fd, stbufp->st_uid, stbufp->st_gid);
return fd;
}
/* This routine called from walk() to make a backup of a file */
int
mkbackup(path, glob)
char *path, *glob;
{
struct stat stbuf;
int n, bfd, fd;
char bigbuf[8*1024], bpath[MAXPATHLEN];
struct timeval tv[2];
if (doglob(glob, path))
return;
if ((fd = open(path, O_RDONLY, 0)) < 0) {
/*
* File may have been removed under our feet.
* Don't make noise about that.
*/
if (errno == ENOENT)
return;
fprintf(stderr, "%s: open(%s): %s\n",
progname, path, EMSG(errno));
return;
}
if (fstat(fd, &stbuf) < 0) {
fprintf(stderr, "%s: fstat(%s): %s\n",
progname, path, EMSG(errno));
(void) close(fd);
return;
}
if (stbuf.st_mtime < lasttime) {
(void) close(fd);
return;
}
if (stbuf.st_size == 0) {
(void) close(fd);
return;
}
if (dryrun || verbose > 1)
printf("copy %s\n", path);
if (dryrun) {
(void) close(fd);
return;
}
if ((bfd = creatbackup(path, &stbuf, bpath)) < 0) {
(void) close(fd);
return;
}
while ((n = read(fd, bigbuf, sizeof bigbuf)) > 0)
if (write(bfd, bigbuf, n) < n) {
fprintf(stderr, "%s: write error: %s\n",
progname, EMSG(errno));
/* saving a little bit is better than nothing... */
break;
}
(void) close(fd);
(void) close(bfd);
/* Preserve file times, so "find /backup -newer ..." works */
tv[0].tv_sec = stbuf.st_atime;
tv[1].tv_sec = stbuf.st_mtime;
tv[0].tv_usec = tv[1].tv_usec = 0;
(void) utimes(bpath, tv);
}
static int rmcnt;
/* This routine is called from walk() to rm a backup file (and parent dir) */
/* ARGSUSED */
int
rmbackup(path, glob)
char *path, *glob;
{
if (dryrun || verbose > 1)
printf("remove %s\n", path);
if (dryrun)
return;
(void) unlink(path);
++rmcnt;
}
/* a file tree walker (a la ftw(3)) for this application (see ftw(3) BUGS) */
walk(path, cp, glob, fn, vector, dirvec)
char *path, *cp, *glob;
int (*fn)();
char *vector, *dirvec;
{
register struct direct *dp;
register DIR *dirp;
register char *eos;
register int n;
if ((dirp = opendir(".")) == NULL) {
fprintf(stderr, "%s: opendir(%s): %s\n",
progname, path, EMSG(errno));
/* error is usually "too many open files", so don't exit */
return;
}
for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
if (dp->d_name[0] == '.'
&& (dp->d_namlen == 1
|| (dp->d_name[1] == '.' && dp->d_namlen == 2)))
continue;
if (vector == NULL) /* magic for backup partition */
(stack[top]+dp->d_fileno)->inum = -dp->d_fileno;
if (vector != NULL && TST(vector, dp->d_fileno)) {
(void) strcpy(cp, dp->d_name);
(*fn)(path, glob);
} else if (TST(dirvec, dp->d_fileno)
&& chdir(dp->d_name) == 0) {
(void) strcpy(cp, dp->d_name);
eos = cp + dp->d_namlen;
*eos++ = '/';
*eos = '\0';
n = rmcnt;
walk(path, eos, glob, fn, vector, dirvec);
(void) chdir("..");
if (fn == rmbackup && n != rmcnt) {
*--eos = '\0'; /* clobber trailing '/' */
(void) rmdir(path);
}
}
}
(void) closedir(dirp);
}
/* Malloc that prints error and dies if it can't honour memory request */
char *
emalloc(n)
u_int n;
{
char *cp;
extern char *malloc();
if ((cp = malloc(n)) == NULL) {
fprintf(stderr, "%s: malloc failure!\n", progname);
exit(1);
}
return cp;
}
/* Read and parse the configuration file */
struct config *
readconfig(fp)
FILE *fp;
{
struct config *cfe, *cfhead, **pcfenp;
char *cp, *s, buf[BUFSIZ];
cfhead = NULL;
pcfenp = &cfhead;
rewind(fp);
while (fgets(buf, sizeof buf, fp) != NULL) {
cfe = (struct config *)emalloc((u_int)sizeof (struct config));
cfe->line = emalloc((u_int)strlen(buf));
(void) strncpy(cfe->line, buf, strlen(buf)-1);
cfe->next = NULL;
cp = buf;
while (isascii(*cp) && isspace(*cp))
++cp;
s = cp;
while (isascii(*cp) && !isspace(*cp))
++cp;
if (*s != '#')
*cp++ = '\0';
cfe->path = emalloc((u_int)strlen(s)+1);
(void) strcpy(cfe->path, s);
if (*s == '#') {
*(cfe->path+strlen(s)-1) = '\0'; /* kill NL */
cfe->lasttime = 0;
*pcfenp = cfe;
pcfenp = &cfe->next;
continue;
}
while (isascii(*cp) && isspace(*cp))
++cp;
s = cp;
while (isascii(*cp) && !isspace(*cp))
++cp;
*cp++ = '\0';
cfe->interval = emalloc((u_int)strlen(s)+1);
(void) strcpy(cfe->interval, s);
while (isascii(*cp) && isspace(*cp))
++cp;
s = cp;
while (isascii(*cp) && !isspace(*cp))
++cp;
*cp++ = '\0';
cfe->filter = emalloc((u_int)strlen(s)+1);
(void) strcpy(cfe->filter, s);
while (isascii(*cp) && isspace(*cp))
++cp;
/* interpret ctime */
*(cfe->line + (cp - buf)) = '\0';
cfe->lasttime = ctime2time(cp);
*pcfenp = cfe;
pcfenp = &cfe->next;
}
return cfhead;
}
/* Write the configuration file out with the updated information */
void
writeconfig(fp, cfp)
FILE *fp;
struct config *cfp;
{
rewind(fp);
for (; cfp != NULL; cfp = cfp->next) {
if (cfp->lasttime == 0) {
fprintf(fp, "%s\n", cfp->path); /* comment */
continue;
}
if (cfp->line == NULL) /* new entry */
fprintf(fp, "%s\t%s\t%s\t%s",
cfp->path, cfp->interval, cfp->filter,
ctime(&cfp->lasttime));
else
fprintf(fp, "%s%s", cfp->line, ctime(&cfp->lasttime));
}
}
/* Parse a ctime() string into seconds since epoch */
char *ap[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0 };
struct timezone tz = { 0 };
int
ctime2time(s)
char *s;
{
static int isdst, flag = 0;
int sec, century, year, month, dayinmonth, julian, i;
struct timeval tv;
struct tm *tms;
if (strlen(s) < 25)
return 0;
if (!flag) {
(void) gettimeofday(&tv, &tz);
tms = localtime(&now);
isdst = tms->tm_isdst;
flag = 1;
}
century = atoi(s+20)/100;
year = atoi(s+22);
dayinmonth = atoi(s+8);
for (i = 0; ap[i] != NULL; ++i) {
if (strncmp(s+4, ap[i], 3) == 0)
break;
}
if (ap[i] == NULL)
month = -1;
else {
if ((month = ++i) > 2)
month -= 3;
else
month += 9, year--;
}
sec = atoi(s+17) + 60*(atoi(s+14) + 60*atoi(s+11));
/* this is a standard julian date formula of unknown origin */
julian = (146097L * century)/4L + (1461L * year)/4L
+ (153L * month + 2L)/5L + dayinmonth - 719469L;
sec += julian * 24 * 60 * 60 + (tz.tz_minuteswest-(isdst*60))*60;
return sec;
}
/* Parse an interval string, e.g. 2h30m or 8h or 15s, the obvious meanings */
int
interval(s)
char *s;
{
int i, sec;
if (s == NULL)
return 0;
i = sec = 0;
while (*s != '\0' && isascii(*s)) {
if (isdigit(*s)) {
i *= 10;
i += *s - '0';
} else if (*s == 'h')
sec += 3600*i, i = 0;
else if (*s == 'm')
sec += 60*i, i = 0;
else if (*s == 's')
sec += i, i = 0;
++s;
}
return sec;
}
/* Test if path is de-selected by the glob patterns in the filter string */
int
doglob(filter, path)
char *filter, *path;
{
register char *cp;
for (cp = filter; cp != NULL && *cp != '\0';) {
if (match(cp, path))
return 1;
while (*cp != '\n' && *cp != '\0')
++cp;
if (*cp == '\n')
++cp;
}
return 0;
}
/* General glob pattern match routine, customized to exit on newline */
int
match(pattern, string)
register char *pattern, *string;
{
while (1)
switch (*pattern) {
case '*':
++pattern;
do {
if (match(pattern, string))
return 1;
} while (*string++ != '\0');
return 0;
break;
case '[':
if (*string == '\0')
return 0;
while ((*++pattern != ']') && (*pattern != *string))
if (*pattern == '\0')
return 0;
if (*pattern == ']')
return 0;
while (*pattern++ != ']')
if (*pattern == '\0')
return 0;
++string;
break;
case '?':
++pattern;
if (*string++ == '\0')
return 0;
break;
case '\n':
return (*string == '\0');
default:
if (*pattern++ != *string++)
return 0;
}
}