home *** CD-ROM | disk | FTP | other *** search
- /*
- * 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;
- }
- }
-