home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume19 / backup / backup.c < prev    next >
C/C++ Source or Header  |  1989-06-29  |  28KB  |  1,122 lines

  1. /*
  2.  * backup - do incremental disk-to-disk backups of individual files
  3.  *
  4.  * This code is a complete reimplementation (for the SunOS environment)
  5.  * of an original idea by Ciaran O'Donnell.  This implementation is
  6.  * Copyright 1988 by Rayan Zachariassen, solely to prevent you selling
  7.  * it or putting your name on it.  Free (gratis) redistribution is
  8.  * encouraged.
  9.  */
  10.  
  11. #ifndef    lint
  12. static char *RCSid = "$Header: /ai/car/src/etc/backup/RCS/backup.c,v 1.2 89/05/23 22:13:19 rayan Exp $";
  13. #endif    lint
  14.  
  15. #include <stdio.h>
  16. #include <ctype.h>
  17. #include <mntent.h>
  18. #include <values.h>
  19. #include <errno.h>
  20. #include <sys/param.h>
  21. #include <sys/types.h>
  22. #include <sys/file.h>
  23. #include <sys/stat.h>
  24. #include <sys/time.h>
  25. #include <sys/vnode.h>
  26. #include <sys/vfs.h>
  27. #include <ufs/inode.h>
  28. #include <ufs/fs.h>
  29. #include <sys/dir.h>
  30.  
  31. #define    HIWATER        95    /* % of backup filesystem used at high water */
  32. #define    LOWATER        85    /* % of backup filesystem used at low water */
  33.  
  34. #define    BACKUP        "/backup"    /* backup filesystem mount point */
  35. #ifndef    CONFIG
  36. #define    CONFIG        "/etc/backup.conf"    /* configuration file */
  37. #endif    /* !CONFIG */
  38.  
  39. #define    MAXSIZE        100000    /* default maximum size of files to back up */
  40.  
  41. #define    DELTA        (24*60*60)    /* default time since last backup */
  42.  
  43. int    maxsize = MAXSIZE;    /* maximum size in bytes of files to back up */
  44. int    doexecs = 0;        /* should we back up executables? */
  45.  
  46. struct a_path {
  47.     char        *path;    /* path to tree hierarchy we want to back up */
  48.     char        *glob;    /* pointer to the contents of the filter file */
  49.     time_t        newer;    /* back up files newer than this time */
  50.     struct config    *config;/* back pointer to config structure */
  51.     struct a_path    *next;
  52. };
  53.  
  54. struct a_dev {
  55.     char        *text;    /* raw device name */
  56.     struct a_path    *paths;    /* pointer to list of paths */
  57.     struct a_dev    *next;    /* pointer to next device */
  58. };
  59.  
  60. struct a_dev    *devlist = NULL;
  61.  
  62. struct config {
  63.     char    *path;        /* top of hierarchy we want to back up */
  64.     char    *interval;    /* how often to do incrementals */
  65.     char    *filter;    /* name of file containing regexp's */
  66.     char    *line;        /* the config file line excluding time field */
  67.     time_t    lasttime;    /* last time incrementals were done here */
  68.     struct config    *next;
  69. };
  70.  
  71. char    *progname, *backup, *devbackup, *backupmnt;
  72. int    debug, verbose, dryrun;
  73. time_t    now, lasttime;
  74.  
  75. extern int    optind;
  76. extern char    *optarg;
  77. extern char    *emalloc();
  78.  
  79. #define    EMSG(x)    (errno < sys_nerr ? sys_errlist[x] : \
  80.            (sprintf(errmsgbuf, "unknown error %d", errno), errmsgbuf))
  81.  
  82. extern int    errno, sys_nerr;
  83. extern char    *sys_errlist[];
  84. char        errmsgbuf[30];
  85.  
  86. main(argc, argv)
  87.     int    argc;
  88.     char    *argv[];
  89. {
  90.     int        c, errflag;
  91.     char        *cffile;
  92.     struct a_dev    *lp;
  93.     struct a_path    *pp;
  94.     struct config    *cf, *cfp;
  95.     FILE        *cf_fp;
  96.     extern time_t time();
  97.     extern char *getdevice(), *getpath(), *ctime();
  98.     extern struct config *readconfig();
  99.     extern void writeconfig();
  100.  
  101.     progname = argv[0];
  102.     errflag = 0;
  103.     dryrun = 0;
  104.     debug = verbose = 0;
  105.     backup = BACKUP;
  106.     cffile = CONFIG;
  107.     while ((c = getopt(argc, argv, "bc:devo:z")) != EOF) {
  108.         switch (c) {
  109.         case 'b':    /* don't back up files larger than arg kbytes */
  110.             if ((maxsize = atoi(optarg)) < 1) {
  111.                 fprintf(stderr,
  112.                     "%s: illegal argument to -b: %d\n",
  113.                     progname, optarg);
  114.                 ++errflag;
  115.             } else
  116.                 maxsize <<= 10;        /* multiple of 1k */
  117.             break;
  118.         case 'c':    /* specify alternate configuration file */
  119.             cffile = optarg;
  120.             break;
  121.         case 'd':    /* debugging output */
  122.             ++debug;
  123.             break;
  124.         case 'e':    /* copy executables */
  125.             ++doexecs;
  126.             break;
  127.         case 'v':    /* be (more and more) verbose */
  128.             ++verbose;
  129.             break;
  130.         case 'o':    /* specify alternate backup location */
  131.             backup = optarg;
  132.             break;
  133.         case 'z':    /* fake it */
  134.             ++dryrun;
  135.             break;
  136.         default:
  137.             ++errflag;
  138.         }
  139.     }
  140.     (void) time(&now);
  141.     if ((cf_fp = fopen(cffile, "r+")) == NULL) {
  142.         fprintf(stderr, "%s: open(%s): %s\n",
  143.                 progname, cffile, EMSG(errno) /* ... */);
  144.         Usage();
  145.     }
  146.     if (verbose
  147.         && flock(fileno(cf_fp), LOCK_EX|LOCK_NB) < 0
  148.         && errno == EWOULDBLOCK)
  149.         printf("waiting for exclusive lock on %s\n", cffile);
  150.     if (flock(fileno(cf_fp), LOCK_EX) < 0) {
  151.         fprintf(stderr, "%s: can't get lock on %s\n",
  152.                 progname, cffile);
  153.         exit(1);
  154.     } else if (verbose > 1) {
  155.         setbuf(stdout, (char *)NULL);
  156.         printf("Start at %slocked %s\n", ctime(&now), cffile);
  157.     }
  158.     cf = readconfig(cf_fp);
  159.     if (optind == argc) {
  160.         /* figure out what we should back up */
  161.         for (cfp = cf; cfp != NULL; cfp = cfp->next) {
  162.             if (cfp->lasttime == 0) {
  163.                 if (debug)
  164.                     printf("%s: lasttime is 0\n", cfp->path);
  165.                 continue;
  166.             }
  167.             /* give 5 minutes leeway */
  168.             if (cfp->lasttime + interval(cfp->interval) < now+300) {
  169.                 /* add this path to the list */
  170.                 if (debug)
  171.                     printf("adding %s\n", cfp->path);
  172.                 addpath(cfp);
  173.             } else if (debug)
  174.             printf("ignoring %s: lasttime=%d interval=%s now=%d\n",
  175.                 cfp->path, cfp->lasttime, cfp->interval, now);
  176.         }
  177.     } else {
  178.         for (; optind < argc; ++optind) {
  179.             for (cfp = cf; cfp != NULL; cfp = cfp->next) {
  180.                 if (cfp->lasttime == 0)
  181.                     continue;
  182.                 if (strcmp(cfp->path, argv[optind]) == 0) {
  183.                     addpath(cfp);
  184.                     break;
  185.                 }
  186.             }
  187.             if (cfp == NULL) {
  188.                 /* append new entry to config file */
  189.                 for (cfp = cf; cfp != NULL && cfp->next != NULL;
  190.                      cfp = cfp->next)
  191.                     continue;
  192.                 if (cfp != NULL) {
  193.                     cfp->next = (struct config *)
  194.                      emalloc((u_int)sizeof (struct config));
  195.                     cfp = cfp->next;
  196.                     cfp->next = NULL;
  197.                     cfp->line = NULL;
  198.                     cfp->interval = "24h";
  199.                     cfp->path = argv[optind];
  200.                     cfp->filter = "/dev/null";
  201.                 }
  202.                 cfp->lasttime = now - DELTA;
  203.                 addpath(cfp);
  204.             }
  205.         }
  206.     }
  207.  
  208.     if ((devbackup = getdevice(backup, MNTTAB)) == NULL &&
  209.         (devbackup = getdevice(backup, MOUNTED)) == NULL) {
  210.         fprintf(stderr, "%s: could not determine backup device\n",
  211.                 progname);
  212.         exit(1);
  213.     }
  214.     if ((backupmnt = getpath(devbackup, MNTTAB)) == NULL &&
  215.         (backupmnt = getpath(devbackup, MOUNTED)) == NULL) {
  216.         fprintf(stderr, "%s: could not determine mount point for %s\n",
  217.                 progname, devbackup);
  218.         ++errflag;
  219.     }
  220.     if (errflag)
  221.         Usage();
  222.     (void) umask(0);
  223.     for (lp = devlist; lp != NULL; lp = lp->next)
  224.         if (doit(lp->paths, lp->text))
  225.             for (pp = lp->paths; pp != NULL; pp = pp->next)
  226.                 pp->config->lasttime = now;
  227.     if (!dryrun)
  228.         writeconfig(cf_fp, cf);
  229.     (void) flock(fileno(cf_fp), LOCK_UN);
  230.     if (verbose > 1) {
  231.         time(&now);
  232.         printf("unlocked %s\nEnd at %s", cffile, ctime(&now));
  233.     }
  234.     (void) fclose(cf_fp);
  235. #ifdef PROF
  236.     /* drop profiling data in a known place */
  237.     chdir("/tmp");
  238. #endif
  239.     exit(0);
  240. }
  241.  
  242. Usage()
  243. {
  244.     fprintf(stderr,
  245. "Usage: %s [ -devz -b# ] [ -c backup.conf ] [ -o /backup ] [ /path ... ]\n",
  246.             progname);
  247.     exit(1);
  248. }
  249.  
  250. /*
  251.  * The filter files contain glob expressions, one per line.  We need to
  252.  * keep track of which filter files we've read in, since several paths
  253.  * may share the same filters.
  254.  */
  255.  
  256. struct filter {
  257.     char    *name;
  258.     char    *contents;
  259. } filters[50];
  260.  
  261. int    maxfilters = 0;
  262.  
  263. /* return pointer to contents of a filter file stored away */
  264.  
  265. char *
  266. readfilter(file)
  267.     char    *file;
  268. {
  269.     int        i, fd;
  270.     struct stat    stbuf;
  271.  
  272.     if (strcmp(file, "/dev/null") == 0)
  273.         return NULL;
  274.     for (i = 0; i < maxfilters
  275.            && i < (sizeof filters/sizeof (struct filter)); ++i) {
  276.         if (strcmp(file, filters[i].name) == 0)
  277.             return filters[i].contents;
  278.     }
  279.     if (i == (sizeof filters/sizeof (struct filter))) {
  280.         fprintf(stderr, "%s: ran out of filters\n", progname);
  281.         exit(1);
  282.     }
  283.     if ((fd = open(file, O_RDONLY, 0)) < 0) {
  284.         fprintf(stderr, "%s: can't open filter file %s\n",
  285.                 progname, file);
  286.         exit(1);
  287.     }
  288.     if (fstat(fd, &stbuf) < 0) {
  289.         fprintf(stderr, "%s: can't stat open filter file %s\n",
  290.                 progname, file);
  291.         exit(1);
  292.     }
  293.     filters[i].contents = emalloc((u_int)stbuf.st_size+1);
  294.     if (read(fd, filters[i].contents, stbuf.st_size) < stbuf.st_size) {
  295.         fprintf(stderr, "%s: couldn't read %d bytes from %s\n",
  296.                 progname, stbuf.st_size, file);
  297.     }
  298.     *(filters[i].contents+stbuf.st_size) = '\0';
  299.     filters[i].name = file;
  300.     ++maxfilters;
  301.     (void) close(fd);
  302.     return filters[i].contents;
  303. }
  304.  
  305. /*
  306.  * We maintain a two-level linked list structure (i.e. list of lists),
  307.  * associating paths with their raw device.  When there are several paths
  308.  * on the same device, we want to handle them simultaneously and only
  309.  * do the ilist walking once per device.  The root of this structure is
  310.  * devlist.
  311.  */
  312.  
  313. int
  314. addpath(cfp)
  315.     struct config    *cfp;
  316. {
  317.     struct a_dev    *lp;
  318.     struct a_path    *pp;
  319.     char    *rawdevice;
  320.  
  321.     if (cfp->path == NULL || *cfp->path != '/') {
  322.         fprintf(stderr, "%s: illegal path: %s\n",
  323.                 progname,
  324.                 cfp->path == NULL ? "(null)" : cfp->path);
  325.     } else if ((rawdevice = getdevice(cfp->path, MNTTAB)) == NULL) {
  326.         fprintf(stderr, "%s: no device for %s\n",
  327.                 progname, cfp->path);
  328.     } else if (*rawdevice != '/') {
  329.         fprintf(stderr, "%s: bad device %s for %s\n",
  330.                 progname, rawdevice, cfp->path);
  331.     } else {
  332.         /* link the path, device pair into lists */
  333.         for (lp = devlist; lp != NULL; lp = lp->next)
  334.             if (strcmp(lp->text, rawdevice) == 0)
  335.                 break;
  336.         pp = (struct a_path *)
  337.                 emalloc((u_int)sizeof (struct a_path));
  338.         pp->path = cfp->path;
  339.         pp->newer = cfp->lasttime;
  340.         pp->glob = readfilter(cfp->filter);
  341.         pp->config = cfp;
  342.         if (lp != NULL)
  343.             pp->next = lp->paths;
  344.         else {
  345.             lp = (struct a_dev *)
  346.                 emalloc((u_int)sizeof (struct a_dev));
  347.             lp->next = devlist;
  348.             devlist = lp;
  349.             lp->text = emalloc((u_int)strlen(rawdevice)+1);
  350.             (void) strcpy(lp->text, rawdevice);
  351.             pp->next = NULL;
  352.         }
  353.         lp->paths = pp;
  354.     }
  355. }
  356.  
  357. /* return the name of the raw device corresponding to a particular file */
  358.  
  359. char *
  360. getdevice(path, table)
  361.     char *path;
  362.     char *table;
  363. {
  364.     char    *cp, *s;
  365.     struct mntent *me;
  366.     FILE    *fp;
  367.     struct stat stpath, stdev;
  368.  
  369.     if (lstat(path, &stpath) < 0) {
  370.         fprintf(stderr, "%s: lstat(%s): %s\n",
  371.                 progname, path, EMSG(errno));
  372.         return NULL;
  373.     }
  374.     if (!((stpath.st_mode & S_IFMT) & (S_IFREG | S_IFDIR))) {
  375.         fprintf(stderr, "%s: %s is a special file\n", progname, path);
  376.         return NULL;
  377.     }
  378.     if ((fp = setmntent(table, "r")) == NULL) {
  379.         fprintf(stderr, "%s: setmntent(%s): %s\n",
  380.                 progname, table, EMSG(errno));
  381.         return NULL;
  382.     }
  383.     while ((me = getmntent(fp)) != NULL) {
  384.         if (strcmp(me->mnt_type, MNTTYPE_42) == 0
  385.             && strncmp(me->mnt_fsname, "/dev/", 5) == 0
  386.             && lstat(me->mnt_fsname, &stdev) == 0
  387.             && stdev.st_rdev == stpath.st_dev) {
  388.             (void) endmntent(fp);
  389.             cp = emalloc((u_int)strlen(me->mnt_fsname)+2);
  390.             (void) strcpy(cp, me->mnt_fsname);
  391.             s = cp + strlen(cp) + 1;
  392.             while (s > cp && *(s - 1) != '/')
  393.                 *s = *(s-1), --s;
  394.             if (s > cp)
  395.                 *s = 'r';
  396.             return cp;
  397.         }
  398.     }
  399.     (void) endmntent(fp);
  400.     return NULL;
  401. }
  402.  
  403. /* get the mount point of the filesystem on the raw device */
  404.  
  405. char *
  406. getpath(device, table)
  407.     char *device;
  408.     char *table;
  409. {
  410.     char        *cp;
  411.     struct mntent    *me;
  412.     FILE        *fp;
  413.     char        devpath[MAXPATHLEN];
  414.     struct stat    stpath, stdev;
  415.     extern char    *rindex();
  416.  
  417.     (void) strcpy(devpath, device);
  418.     if ((cp = rindex(devpath, '/')) != NULL) {
  419.         ++cp;
  420.         if (*cp == 'r')
  421.             while ((*cp = *(cp+1)) != '\0')
  422.                 ++cp;
  423.     }
  424.     if ((fp = setmntent(table, "r")) == NULL) {
  425.         fprintf(stderr, "%s: setmntent(%s): %s\n",
  426.                 progname, table, EMSG(errno));
  427.         return NULL;
  428.     }
  429.     while ((me = getmntent(fp)) != NULL) {
  430.         if (strcmp(me->mnt_type, MNTTYPE_42) == 0
  431.             && strcmp(me->mnt_fsname, devpath) == 0
  432.             && lstat(me->mnt_fsname, &stdev) == 0
  433.             && lstat(me->mnt_dir, &stpath) == 0
  434.             && stdev.st_rdev == stpath.st_dev) {
  435.             (void) endmntent(fp);
  436.             cp = emalloc((u_int)strlen(me->mnt_dir)+1);
  437.             (void) strcpy(cp, me->mnt_dir);
  438.             return cp;
  439.         }
  440.     }
  441.     (void) endmntent(fp);
  442.     return NULL;
  443. }
  444.  
  445. #define sblock    sb_un.u_sblock
  446.  
  447. struct iinfo {
  448.     int    inum;            /* must be int so can be -ve too */
  449.     u_int    blks;
  450.     time_t    mtime;
  451. };
  452.  
  453. int
  454. cmpiinfo(iip1, iip2)
  455.     register struct iinfo *iip1, *iip2;
  456. {
  457.     return iip1->mtime - iip2->mtime;
  458. }
  459.  
  460. struct iinfo    *stack[2];
  461. long        stacksize[2];
  462. long        needspace[2];
  463. int        top = -1;
  464.  
  465. char        *dirmask;
  466. int        mustfree;
  467.  
  468. #define    SET(v,i)    ((v)[(i)/BITSPERBYTE] |= (1<<((i)%BITSPERBYTE)))
  469. #define    TST(v,i)    ((v)[(i)/BITSPERBYTE] & (1<<((i)%BITSPERBYTE)))
  470.  
  471. int
  472. doit(path, dev)
  473.     struct a_path    *path;
  474.     char        *dev;
  475. {
  476.     register struct iinfo    *st;
  477.     register int    i, inum;
  478.     int    fd, hiwater, lowater;
  479.     u_int    nfiles;
  480.     union { struct fs u_sblock; char dummy[SBSIZE]; } sb_un;
  481.     char    *bitvec, *dirvec, pathbuf[MAXPATHLEN];
  482.     struct a_path    *pp;
  483.     struct statfs    fsbuf;
  484.     extern int itest(), mkbackup(), rmbackup();
  485.     extern char *calloc();
  486.  
  487.     if (debug)
  488.         printf("doing %s\n", dev);
  489.     if ((fd = open(dev, O_RDONLY, 0)) < 0) {
  490.         fprintf(stderr, "%s: open(%s): %s\n",
  491.                 progname, dev, EMSG(errno));
  492.         return 0;
  493.     }
  494.     if (bread(fd, SBLOCK, (char *)&sblock, (long) SBSIZE) < 0) {
  495.         fprintf(stderr, "%s: can't read superblock from %s: %s\n",
  496.                 progname, dev, EMSG(errno));
  497.         return 0;
  498.     }
  499.     (void) close(fd);
  500.     nfiles = sblock.fs_ipg * sblock.fs_ncg;
  501.     stack[++top] = (struct iinfo *)calloc(nfiles, sizeof (struct iinfo));
  502.     stacksize[top] = 0;
  503.     needspace[top] = 0;
  504.     dirvec = calloc((nfiles/BITSPERBYTE)+1, 1);
  505.     dirmask = dirvec;
  506.     if (top == 0) {
  507.         /* figure out the oldest lasttime before i-list walk */
  508.         lasttime = now;
  509.         for (pp = path; pp != NULL; pp = pp->next)
  510.             if (pp->newer < lasttime)
  511.                 lasttime = pp->newer;
  512.     }
  513.     if (debug)
  514.         printf("%s: scan %d inodes\n", dev, nfiles);
  515.     (void) ilw(dev, itest, 1);
  516.     if (verbose)
  517.         printf("%s: found %d candidate files, with %d blocks total\n",
  518.                   dev, stacksize[top], needspace[top]);
  519.     if (stacksize[top] == 0) {
  520.         (void) free(dirvec);
  521.         (void) free((char *)stack[top--]);
  522.         return 0;
  523.     }
  524.     bitvec = calloc((nfiles/BITSPERBYTE)+1, 1);
  525.     if (top == 0) {
  526.         /*
  527.          * This is the filesystem we want to back up.
  528.          * First make sure there is enough free space in the backup
  529.          * filesystem (if not, call myself recursively), then run
  530.          * a file tree walker to copy the indicated files to the
  531.          * backup filesystem.
  532.          */
  533.         if (statfs(backup, &fsbuf) < 0) {
  534.             fprintf(stderr, "%s: statfs(%s): %s\n",
  535.                     progname, backup, EMSG(errno));
  536.             exit(1);
  537.         }
  538.         hiwater = (fsbuf.f_blocks-fsbuf.f_bfree
  539.                         +fsbuf.f_bavail)*HIWATER/100;
  540.         if (fsbuf.f_blocks - fsbuf.f_bfree + needspace[top] > hiwater) {
  541.             /* need to free some space */
  542.             struct a_path    backupdesc;
  543.             /*
  544.              * If you want to free so free space will be at
  545.              * LOWATER after backup finishes, then enable the
  546.              * next line and do s/hiwater/lowater/ in the
  547.              * following line defining mustfree.
  548.              */
  549.             /* lowater = +(hiwater*LOWATER)/HIWATER; */
  550.             mustfree = fsbuf.f_blocks - fsbuf.f_bfree
  551.                           + needspace[top] - hiwater;
  552.             /* select all files */
  553.             lasttime = 0;
  554.             maxsize = sblock.fs_dsize * DEV_BSIZE;
  555.             backupdesc.path = backup;
  556.             backupdesc.next = NULL;
  557.             backupdesc.glob = NULL;
  558.             backupdesc.newer = lasttime;
  559.             if (!doit(&backupdesc, devbackup)) {
  560.                 fprintf(stderr,
  561.                     "%s: Can't walk %s to free space\n",
  562.                         progname, backup);
  563.                 exit(1);
  564.             }
  565.         }
  566.         for (i = 0, st = stack[top]; i < nfiles; ++i, ++st) {
  567.             if (st->mtime > 0) {
  568.                 SET(bitvec, st->inum);
  569.             }
  570.         }
  571.         for (; path != NULL; path = path->next) {
  572.             if (chdir(path->path) < 0) {
  573.                 fprintf(stderr, "%s: chdir(%s): %s\n",
  574.                     progname, path->path, EMSG(errno));
  575.                 exit(1);
  576.             }
  577.             (void) sprintf(pathbuf, "%s/", path->path);
  578.             lasttime = path->newer;
  579.             if (verbose)
  580.                 printf("%s: select mtime within %d sec in %s\n",
  581.                         dev, now - lasttime, path->path);
  582.             walk(pathbuf, pathbuf + strlen(pathbuf), path->glob,
  583.                       mkbackup, bitvec, dirvec);
  584.         }
  585.     } else {
  586.         /*
  587.          * This is the backup filesystem.
  588.          * Sort the inodes selected into oldest-first, then run
  589.          * a file tree walker to delete the files until we have
  590.          * enough space *and* are under the low water mark.
  591.          */
  592.  
  593.         /* assert strcmp(path->path, backup) == 0 */
  594.         /* compress the inode array */
  595.         st = stack[top];
  596.         for (i = inum = 0; i < stacksize[top]; ++inum) {
  597.             if ((st+inum)->mtime > 0) {
  598.                 (st+i)->inum = inum;
  599.                 (st+i)->blks = (st+inum)->blks;
  600.                 (st+i)->mtime = (st+inum)->mtime;
  601.                 ++i;
  602.             }
  603.         }
  604.         if (chdir(path->path) < 0) {
  605.             fprintf(stderr, "%s: chdir(%s): %s\n",
  606.                     progname, path->path, EMSG(errno));
  607.             exit(1);
  608.         }
  609.         (void) sprintf(pathbuf, "%s/", path->path);
  610.         if (strcmp(backup, backupmnt) != 0) {
  611.             /* backup area is not an entire filesystem */
  612.             walk(pathbuf, pathbuf + strlen(pathbuf), (char *)NULL,
  613.                       (int (*)())NULL, (char *)NULL, dirvec);
  614.             /* now all possible inums are negative and rest +ve */
  615.             st = stack[top];
  616.             for (i = inum = 0; i < stacksize[top]; ++inum) {
  617.                 if ((st+inum)->inum < 0) {
  618.                     (st+i)->inum = inum;
  619.                     ++i;
  620.                 } else
  621.                     (st+i)->inum = 0;
  622.             }
  623.             /* now all possible inums are +ve and rest 0 */
  624.         }
  625.         /* sort it oldest first */
  626.         qsort((char *)stack[top], stacksize[top],
  627.               sizeof (struct iinfo), cmpiinfo);
  628.         /* mustfree has been set in our parent doit() */
  629.         /* go from oldest to newest, truncate after mustfree blocks */
  630.         st = stack[top];
  631.         for (i = 0; i < stacksize[top] && mustfree > 0; ++i, ++st) {
  632.             if (st->inum > 2 && st->mtime > 0) {
  633.                 mustfree -= st->blks;
  634.                 SET(bitvec, st->inum);
  635.             }
  636.         }
  637.         (void) sprintf(pathbuf, "%s/", path->path);
  638.         walk(pathbuf, pathbuf + strlen(pathbuf), (char *)NULL,
  639.                   rmbackup, bitvec, dirvec);
  640.     }
  641.     (void) free(bitvec);
  642.     (void) free(dirvec);
  643.     (void) free((char *)stack[top--]);
  644.     return 1;
  645. }
  646.  
  647. /* This routine is used by the inode list walker) to test for relevant inodes */
  648.  
  649. itest(ip, inum)
  650.     struct dinode    *ip;
  651.     int        inum;
  652. {
  653.     register struct iinfo *iip;
  654.  
  655.     if ((ip->di_mode & S_IFMT) == S_IFREG
  656.         && ip->di_mtime > lasttime
  657.         && ip->di_size < maxsize
  658.         && (doexecs || (ip->di_mode & 07111) == 0)) {
  659.         /* we have a candidate for backing up */
  660.         iip = stack[top] + inum;
  661.         iip->inum = inum;
  662.         stacksize[top] += 1;
  663.         needspace[top] += (iip->blks = ip->di_blocks);
  664.         iip->mtime = ip->di_mtime;
  665.         /*
  666. printf("%6d:\tmode=0%o uid=%d gid=%d size=%d nlink=%d, a=%D m=%D c=%D\n",
  667.         inum, ip->di_mode, ip->di_uid, ip->di_gid, ip->di_size,
  668.         ip->di_nlink, ip->di_atime, ip->di_mtime, ip->di_ctime);
  669.         */
  670.     } else if ((ip->di_mode & S_IFMT) == S_IFDIR)
  671.         SET(dirmask, inum);
  672.     return 0;
  673. }
  674.  
  675. /*
  676.  * Create all the directories down a particular backup path, copying stat
  677.  * info from the original directories.
  678.  */
  679.  
  680. creatdirs(dirpath, origpath, stbufp)
  681.     char        *dirpath, *origpath;
  682.     struct stat    *stbufp;
  683. {
  684.     char *cp;
  685.     struct stat    stbuf;
  686.     extern char *rindex();
  687.  
  688.     if (mkdir(dirpath, stbufp->st_mode & 0777) < 0) {
  689.         if (errno == ENOENT) {
  690.             if ((cp = rindex(dirpath, '/')) > dirpath) {
  691.                 *cp = '\0';
  692.                 creatdirs(dirpath, origpath, stbufp);
  693.                 *cp = '/';
  694.             }
  695.             (void) mkdir(dirpath, (stbufp->st_mode & 0777)|0111);
  696.             if (stat(origpath, &stbuf) == 0) {
  697.                 (void) chown(dirpath, stbuf.st_uid,
  698.                               stbuf.st_gid);
  699.                 if (stbuf.st_mode & 0400)
  700.                     stbuf.st_mode |= 0100;
  701.                 if (stbuf.st_mode & 040)
  702.                     stbuf.st_mode |= 010;
  703.                 if (stbuf.st_mode & 04)
  704.                     stbuf.st_mode |= 01;
  705.                 (void) chmod(dirpath, stbuf.st_mode & 0777);
  706.             } else
  707.                 fprintf(stderr, "%s: stat(%s): %s\n",
  708.                     progname, origpath, EMSG(errno));
  709.         }
  710.     } else if (stat(origpath, &stbuf) == 0) {
  711.         (void) chown(dirpath, stbuf.st_uid, stbuf.st_gid);
  712.         if (stbuf.st_mode & 0400)
  713.             stbuf.st_mode |= 0100;
  714.         if (stbuf.st_mode & 040)
  715.             stbuf.st_mode |= 010;
  716.         if (stbuf.st_mode & 04)
  717.             stbuf.st_mode |= 01;
  718.         (void) chmod(dirpath, stbuf.st_mode & 0777);
  719.     } else
  720.         fprintf(stderr, "%s: stat(%s): %s\n",
  721.                 progname, origpath, EMSG(errno));
  722. }
  723.  
  724. /* Create an actual backup file, return its file descriptor */
  725.  
  726. int
  727. creatbackup(path, stbufp, filename)
  728.     char    *path, *filename;
  729.     struct stat    *stbufp;
  730. {
  731.     int    fd;
  732.     char    *cp, *ct;
  733.  
  734.     ct = ctime(&stbufp->st_mtime);
  735.     (void) sprintf(filename, "%s%s/", backup, path);
  736.     cp = filename + strlen(filename);
  737.     *cp++ = ct[4]; *cp++ = ct[5]; *cp++ = ct[6];
  738.     *cp++ = ct[8] == ' ' ? '0' : ct[8]; *cp++ = ct[9];
  739.     *cp++ = '-';
  740.     *cp++ = ct[11]; *cp++ = ct[12]; *cp++ = ct[13];
  741.     *cp++ = ct[14]; *cp++ = ct[15]; *cp = '\0';
  742.     fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, stbufp->st_mode & 0777);
  743.     if (fd < 0) {
  744.         if (errno == ENOENT) {
  745.             cp = rindex(filename, '/');
  746.             *cp = '\0';
  747.             creatdirs(filename, filename+strlen(backup), stbufp);
  748.             *cp = '/';
  749.         }
  750.         fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC,
  751.                     stbufp->st_mode & 0777);
  752.     }
  753.     if (fd < 0) {
  754.         fprintf(stderr, "%s: open(%s): %s\n",
  755.                 progname, filename, EMSG(errno));
  756.         return -1;
  757.     }
  758.     (void) fchown(fd, stbufp->st_uid, stbufp->st_gid);
  759.     return fd;
  760. }
  761.  
  762. /* This routine called from walk() to make a backup of a file */
  763.  
  764. int
  765. mkbackup(path, glob)
  766.     char    *path, *glob;
  767. {
  768.     struct stat    stbuf;
  769.     int    n, bfd, fd;
  770.     char    bigbuf[8*1024], bpath[MAXPATHLEN];
  771.     struct timeval tv[2];
  772.  
  773.     if (doglob(glob, path))
  774.         return;
  775.     if ((fd = open(path, O_RDONLY, 0)) < 0) {
  776.         /*
  777.          * File may have been removed under our feet.
  778.          * Don't make noise about that.
  779.          */
  780.         if (errno == ENOENT)
  781.             return;
  782.         fprintf(stderr, "%s: open(%s): %s\n",
  783.                 progname, path, EMSG(errno));
  784.         return;
  785.     }
  786.     if (fstat(fd, &stbuf) < 0) {
  787.         fprintf(stderr, "%s: fstat(%s): %s\n",
  788.                 progname, path, EMSG(errno));
  789.         (void) close(fd);
  790.         return;
  791.     }
  792.     if (stbuf.st_mtime < lasttime) {
  793.         (void) close(fd);
  794.         return;
  795.     }
  796.     if (stbuf.st_size == 0) {
  797.         (void) close(fd);
  798.         return;
  799.     }
  800.     if (dryrun || verbose > 1)
  801.         printf("copy %s\n", path);
  802.     if (dryrun) {
  803.         (void) close(fd);
  804.         return;
  805.     }
  806.     if ((bfd = creatbackup(path, &stbuf, bpath)) < 0) {
  807.         (void) close(fd);
  808.         return;
  809.     }
  810.     while ((n = read(fd, bigbuf, sizeof bigbuf)) > 0)
  811.         if (write(bfd, bigbuf, n) < n) {
  812.             fprintf(stderr, "%s: write error: %s\n",
  813.                     progname, EMSG(errno));
  814.             /* saving a little bit is better than nothing... */
  815.             break;
  816.         }
  817.     (void) close(fd);
  818.     (void) close(bfd);
  819.     /* Preserve file times, so "find /backup -newer ..." works */
  820.     tv[0].tv_sec = stbuf.st_atime;
  821.     tv[1].tv_sec = stbuf.st_mtime;
  822.     tv[0].tv_usec = tv[1].tv_usec = 0;
  823.     (void) utimes(bpath, tv);
  824. }
  825.  
  826. static int rmcnt;
  827.  
  828. /* This routine is called from walk() to rm a backup file (and parent dir) */
  829.  
  830. /* ARGSUSED */
  831. int
  832. rmbackup(path, glob)
  833.     char    *path, *glob;
  834. {
  835.  
  836.     if (dryrun || verbose > 1)
  837.         printf("remove %s\n", path);
  838.     if (dryrun)
  839.         return;
  840.     (void) unlink(path);
  841.     ++rmcnt;
  842. }
  843.  
  844. /* a file tree walker (a la ftw(3)) for this application (see ftw(3) BUGS) */
  845.  
  846. walk(path, cp, glob, fn, vector, dirvec)
  847.     char    *path, *cp, *glob;
  848.     int    (*fn)();
  849.     char    *vector, *dirvec;
  850. {
  851.     register struct direct *dp;
  852.     register DIR *dirp;
  853.     register char *eos;
  854.     register int n;
  855.  
  856.     if ((dirp = opendir(".")) == NULL) {
  857.         fprintf(stderr, "%s: opendir(%s): %s\n",
  858.                 progname, path, EMSG(errno));
  859.         /* error is usually "too many open files", so don't exit */
  860.         return;
  861.     }
  862.     for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
  863.         if (dp->d_name[0] == '.'
  864.             && (dp->d_namlen == 1
  865.             || (dp->d_name[1] == '.' && dp->d_namlen == 2)))
  866.             continue;
  867.         if (vector == NULL)    /* magic for backup partition */
  868.             (stack[top]+dp->d_fileno)->inum = -dp->d_fileno;
  869.         if (vector != NULL && TST(vector, dp->d_fileno)) {
  870.             (void) strcpy(cp, dp->d_name);
  871.             (*fn)(path, glob);
  872.         } else if (TST(dirvec, dp->d_fileno)
  873.                   && chdir(dp->d_name) == 0) {
  874.             (void) strcpy(cp, dp->d_name);
  875.             eos = cp + dp->d_namlen;
  876.             *eos++ = '/';
  877.             *eos = '\0';
  878.             n = rmcnt;
  879.             walk(path, eos, glob, fn, vector, dirvec);
  880.             (void) chdir("..");
  881.             if (fn == rmbackup && n != rmcnt) {
  882.                 *--eos = '\0';    /* clobber trailing '/' */
  883.                 (void) rmdir(path);
  884.             }
  885.         }
  886.     }
  887.     (void) closedir(dirp);
  888. }
  889.  
  890. /* Malloc that prints error and dies if it can't honour memory request */
  891.  
  892. char *
  893. emalloc(n)
  894.     u_int    n;
  895. {
  896.     char *cp;
  897.     extern char *malloc();
  898.  
  899.     if ((cp = malloc(n)) == NULL) {
  900.         fprintf(stderr, "%s: malloc failure!\n", progname);
  901.         exit(1);
  902.     }
  903.     return cp;
  904. }
  905.  
  906. /* Read and parse the configuration file */
  907.  
  908. struct config *
  909. readconfig(fp)
  910.     FILE    *fp;
  911. {
  912.     struct config    *cfe, *cfhead, **pcfenp;
  913.     char    *cp, *s, buf[BUFSIZ];
  914.  
  915.     cfhead = NULL;
  916.     pcfenp = &cfhead;
  917.     rewind(fp);
  918.     while (fgets(buf, sizeof buf, fp) != NULL) {
  919.         cfe = (struct config *)emalloc((u_int)sizeof (struct config));
  920.         cfe->line = emalloc((u_int)strlen(buf));
  921.         (void) strncpy(cfe->line, buf, strlen(buf)-1);
  922.         cfe->next = NULL;
  923.         cp = buf;
  924.         while (isascii(*cp) && isspace(*cp))
  925.             ++cp;
  926.         s = cp;
  927.         while (isascii(*cp) && !isspace(*cp))
  928.             ++cp;
  929.         if (*s != '#')
  930.             *cp++ = '\0';
  931.         cfe->path = emalloc((u_int)strlen(s)+1);
  932.         (void) strcpy(cfe->path, s);
  933.         if (*s == '#') {
  934.             *(cfe->path+strlen(s)-1) = '\0';    /* kill NL */
  935.             cfe->lasttime = 0;
  936.             *pcfenp = cfe;
  937.             pcfenp = &cfe->next;
  938.             continue;
  939.         }
  940.         while (isascii(*cp) && isspace(*cp))
  941.             ++cp;
  942.         s = cp;
  943.         while (isascii(*cp) && !isspace(*cp))
  944.             ++cp;
  945.         *cp++ = '\0';
  946.         cfe->interval = emalloc((u_int)strlen(s)+1);
  947.         (void) strcpy(cfe->interval, s);
  948.         while (isascii(*cp) && isspace(*cp))
  949.             ++cp;
  950.         s = cp;
  951.         while (isascii(*cp) && !isspace(*cp))
  952.             ++cp;
  953.         *cp++ = '\0';
  954.         cfe->filter = emalloc((u_int)strlen(s)+1);
  955.         (void) strcpy(cfe->filter, s);
  956.         while (isascii(*cp) && isspace(*cp))
  957.             ++cp;
  958.         /* interpret ctime */
  959.         *(cfe->line + (cp - buf)) = '\0';
  960.         cfe->lasttime = ctime2time(cp);
  961.         *pcfenp = cfe;
  962.         pcfenp = &cfe->next;
  963.     }
  964.     return cfhead;
  965. }
  966.  
  967. /* Write the configuration file out with the updated information */
  968.  
  969. void
  970. writeconfig(fp, cfp)
  971.     FILE        *fp;
  972.     struct config    *cfp;
  973. {
  974.     rewind(fp);
  975.     for (; cfp != NULL; cfp = cfp->next) {
  976.         if (cfp->lasttime == 0) {
  977.             fprintf(fp, "%s\n", cfp->path);    /* comment */
  978.             continue;
  979.         }
  980.         if (cfp->line == NULL)    /* new entry */
  981.             fprintf(fp, "%s\t%s\t%s\t%s",
  982.                     cfp->path, cfp->interval, cfp->filter,
  983.                     ctime(&cfp->lasttime));
  984.         else
  985.             fprintf(fp, "%s%s", cfp->line, ctime(&cfp->lasttime));
  986.     }
  987. }
  988.  
  989. /* Parse a ctime() string into seconds since epoch */
  990.  
  991. char    *ap[] = {    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  992.             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0 };
  993.  
  994. struct timezone    tz = { 0 };
  995.  
  996. int
  997. ctime2time(s)
  998.     char    *s;
  999. {
  1000.     static int    isdst, flag = 0;
  1001.     int    sec, century, year, month, dayinmonth, julian, i;
  1002.     struct timeval    tv;
  1003.     struct tm *tms;
  1004.  
  1005.     if (strlen(s) < 25)
  1006.         return 0;
  1007.     if (!flag) {
  1008.         (void) gettimeofday(&tv, &tz);
  1009.         tms = localtime(&now);
  1010.         isdst = tms->tm_isdst;
  1011.         flag = 1;
  1012.     }
  1013.     century = atoi(s+20)/100;
  1014.     year = atoi(s+22);
  1015.     dayinmonth = atoi(s+8);
  1016.     for (i = 0; ap[i] != NULL; ++i) {
  1017.         if (strncmp(s+4, ap[i], 3) == 0)
  1018.             break;
  1019.     }
  1020.     if (ap[i] == NULL)
  1021.         month = -1;
  1022.     else {
  1023.         if ((month = ++i) > 2)
  1024.             month -= 3;
  1025.         else
  1026.             month += 9, year--;
  1027.     }
  1028.     sec = atoi(s+17) + 60*(atoi(s+14) + 60*atoi(s+11));
  1029.     /* this is a standard julian date formula of unknown origin */
  1030.     julian = (146097L * century)/4L + (1461L * year)/4L
  1031.         + (153L * month + 2L)/5L + dayinmonth - 719469L;
  1032.     sec += julian * 24 * 60 * 60 + (tz.tz_minuteswest-(isdst*60))*60;
  1033.     return sec;
  1034. }
  1035.  
  1036. /* Parse an interval string, e.g. 2h30m or 8h or 15s, the obvious meanings */
  1037.  
  1038. int
  1039. interval(s)
  1040.     char    *s;
  1041. {
  1042.     int    i, sec;
  1043.  
  1044.     if (s == NULL)
  1045.         return 0;
  1046.     i = sec = 0;
  1047.     while (*s != '\0' && isascii(*s)) {
  1048.         if (isdigit(*s)) {
  1049.             i *= 10;
  1050.             i += *s - '0';
  1051.         } else if (*s == 'h')
  1052.             sec += 3600*i, i = 0;
  1053.         else if (*s == 'm')
  1054.             sec += 60*i, i = 0;
  1055.         else if (*s == 's')
  1056.             sec += i, i = 0;
  1057.         ++s;
  1058.     }
  1059.     return sec;
  1060. }
  1061.  
  1062. /* Test if path is de-selected by the glob patterns in the filter string */
  1063.  
  1064. int
  1065. doglob(filter, path)
  1066.     char    *filter, *path;
  1067. {
  1068.     register char    *cp;
  1069.  
  1070.     for (cp = filter; cp != NULL && *cp != '\0';) {
  1071.         if (match(cp, path))
  1072.             return 1;
  1073.         while (*cp != '\n' && *cp != '\0')
  1074.             ++cp;
  1075.         if (*cp == '\n')
  1076.             ++cp;
  1077.     }
  1078.     return 0;
  1079. }
  1080.  
  1081. /* General glob pattern match routine, customized to exit on newline */
  1082.  
  1083. int
  1084. match(pattern, string)
  1085.     register char    *pattern, *string;
  1086. {
  1087.     while (1)
  1088.         switch (*pattern) {
  1089.         case '*':
  1090.             ++pattern;
  1091.             do {
  1092.                 if (match(pattern, string))
  1093.                     return 1;
  1094.             } while (*string++ != '\0');
  1095.             return 0;
  1096.             break;
  1097.         case '[':
  1098.             if (*string == '\0')
  1099.                 return 0;
  1100.             while ((*++pattern != ']') && (*pattern != *string))
  1101.                 if (*pattern == '\0')
  1102.                     return 0;
  1103.             if (*pattern == ']')
  1104.                 return 0;
  1105.             while (*pattern++ != ']')
  1106.                 if (*pattern == '\0')
  1107.                     return 0;
  1108.             ++string;
  1109.             break;
  1110.         case '?':
  1111.             ++pattern;
  1112.             if (*string++ == '\0')
  1113.                 return 0;
  1114.             break;
  1115.         case '\n':
  1116.             return (*string == '\0');
  1117.         default:
  1118.             if (*pattern++ != *string++)
  1119.                 return 0;
  1120.         }
  1121. }
  1122.