backup.c

Go to the documentation of this file.
00001 /* backup - backup a directory          Author: Andy Tanenbaum */
00002 
00003 /* This program recursively backs up a directory.  It has two typical uses:
00004  *
00005  * 1. Backing up a directory to 1 or more diskettes
00006  * 2. Backing up RAM disk to a shadow directory on hard disk
00007  *
00008  * The backup directory or medium may be empty, in which case, the entire
00009  * source directory is copied, or it may contain an old backup, in which
00010  * case only those files that are new or out of date are copied.  In this
00011  * respect, 'backup' resembles 'make', except that no 'makefile' is needed.
00012  * The backed up copy may optionally be compressed to save space.
00013  *
00014  * The following flags exist:
00015  *
00016  *      -d  At the top level, only back up directories (not loose files)
00017  *      -j  Don't copy junk: *.Z, *.bak, *.log, a.out, and core
00018  *      -m  If ENOSPC encountered, ask for another diskette
00019  *      -n  No directories, only loose files are backed up
00020  *      -o  Don't copy *.o files
00021  *      -r  Restore files (ie. uncompress if necessary)
00022  *      -s  Don't copy *.s files
00023  *      -t  Set creation date of target-file equal to cdate of source-file
00024  *      -v  Verbose (announce what is being done)
00025  *      -z  Compress on backup/uncompress on restore
00026  *
00027  * Patches:
00028  *      30 Mar 91.      Added restore option.  cwr. 
00029  *       9 Sep 91.      Changed user interface.  cwr.
00030  *      21 Jan 93.      Revised error messages.  cwr.
00031  *      29 Mar 95.      Added -o, NARROW define.  cwr.
00032  */
00033 
00034 #include <sys/types.h>
00035 #include <sys/stat.h>
00036 #include <errno.h>
00037 #include <fcntl.h>
00038 #include <utime.h>
00039 #include <stdlib.h>
00040 #include <string.h>
00041 #include <unistd.h>
00042 #include <sys/wait.h>
00043 #include <stdio.h>
00044 #include <dirent.h>
00045 
00046 #define NAME_SIZE _DIRENT_NAME_LEN
00047 
00048 #undef NARROW                   /* Width of verbose output */
00049 #define COPY_SIZE 4096
00050 #define MAX_ENTRIES 512
00051 #define MAX_PATH 256
00052 #define NONFATAL 0
00053 #define FATAL 1
00054 #define NO_SAVINGS 512          /* compress can return code 2 */
00055 #define OUT_OF_SPACE 2
00056 
00057 struct dirent dir_ent[MAX_ENTRIES];
00058 int entries = 0;
00059 
00060 struct sorted {
00061   int mode;                     /* file mode */
00062   char *namep;                  /* pointer to name in dir_buf */
00063   long acctime;                 /* time of last access */
00064   long modtime;                 /* time of last modification */
00065 } sorted[MAX_ENTRIES];
00066 
00067 char copybuf[COPY_SIZE];
00068 char *pname;
00069 int dflag, jflag, mflag, nflag, oflag, rflag, sflag, tflag, vflag, zflag;
00070 
00071 extern int errno;
00072 extern char **environ;
00073 
00074 _PROTOTYPE(int main, (int argc, char **argv));
00075 _PROTOTYPE(void maketarget, (char *dir2));
00076 _PROTOTYPE(int make_dir, (char *dir));
00077 _PROTOTYPE(int stat_all, (char *dir1, int n));
00078 _PROTOTYPE(void sort_dir, (int m));
00079 _PROTOTYPE(void process, (int m, char *dir1, char *dir2));
00080 _PROTOTYPE(void swap, (struct sorted *sp1, struct sorted *sp2));
00081 _PROTOTYPE(int copy, (char *dir1, struct sorted *sp, char *cbuf2));
00082 _PROTOTYPE(int zcopy, (char *src, char *targ));
00083 _PROTOTYPE(void copydir, (char *dir1, char *dir2, char *namep));
00084 _PROTOTYPE(void newdisk, (char *dir));
00085 _PROTOTYPE(void usage, (void));
00086 _PROTOTYPE(void error, (int type, char *s1, char *s2, char *s3));
00087 
00088 int main(argc, argv)
00089 int argc;
00090 char *argv[];
00091 {
00092   int ct, n, m, fd;
00093   char *dir1, *dir2, *cp, c;
00094   struct stat s;
00095   struct dirent *e;
00096   DIR *DIR1, *DIR2;
00097 
00098   (void) sync();
00099 
00100   /* Get the flags */
00101   if ((pname = strrchr(argv[0], '/')) == (char *)NULL)
00102         pname = argv[0];
00103   else
00104         pname++;
00105   if (argc < 3 || argc > 4) usage();
00106   if (argc == 4) {
00107         cp = argv[1];
00108         if (*cp++ != '-') usage();
00109         while ((c = *cp++) != '\0') {
00110                 switch (c) {
00111                     case 'd':   dflag++;        break;
00112                     case 'j':   jflag++;        break;
00113                     case 'm':   mflag++;        break;
00114                     case 'n':   nflag++;        break;
00115                     case 'o':   oflag++;        break;
00116                     case 's':   sflag++;        break;
00117                     case 'r':   rflag++;        break;
00118                     case 't':   tflag++;        break;
00119                     case 'v':   vflag++;        break;
00120                     case 'z':   zflag++;        break;
00121                     default:    usage();
00122                 }
00123         }
00124         dir1 = argv[2];
00125         dir2 = argv[3];
00126   } else {
00127         dir1 = argv[1];
00128         dir2 = argv[2];
00129   }
00130   if (!strcmp(pname, "restore") && !rflag) rflag++;
00131 
00132   /* Check for a valid source */
00133   if (stat(dir1, &s) < 0) error(FATAL, "cannot stat ", dir1, "");
00134   if ((s.st_mode & S_IFMT) != S_IFDIR) error(FATAL, "non-directory ", dir1, "");
00135 
00136   /* Read in the source directory */
00137   if(!(DIR1 = opendir(dir1))) {
00138         perror(dir1);
00139         return 1;
00140   }
00141   while(entries < MAX_ENTRIES && (e=readdir(DIR1)))
00142         memcpy(&dir_ent[entries++], e, sizeof(*e));
00143   closedir(DIR1);
00144   if (entries == MAX_ENTRIES)
00145         error(FATAL, "directory ", dir1, " is too large");
00146 
00147   /* Create the target directory. */
00148   maketarget(dir2);
00149 
00150   /* Stat all the entries. */
00151   n = entries;
00152   m = stat_all(dir1, n);
00153 
00154   /* Remove non-entries and sort what's left. */
00155   sort_dir(m);
00156 
00157   /* Process each of the m entries one at a time. */
00158   process(m, dir1, dir2);
00159   return(0);
00160 }
00161 
00162 
00163 void maketarget(dir2)
00164 char *dir2;
00165 {
00166 /* The target directory is created if it does not already exist. */
00167 
00168   char *p, c, dbuf[MAX_PATH];
00169 
00170   if (access(dir2, 6) == 0)
00171         return;                 /* if target exists, we're done */
00172   if (make_dir(dir2) == 0) return;      /* we just made it */
00173 
00174   /* We have to try creating all the higher level directories. */
00175   strcpy(dbuf, dir2);
00176   p = dbuf + 1;
00177   while (1) {
00178         while (*p != '/' && *p != '\0') p++;
00179         c = *p;                 /* either / or \0 */
00180         *p = 0;
00181         make_dir(dbuf);
00182         if (c == '\0') return;
00183         *p = c;
00184         p++;
00185   }
00186 }
00187 
00188 int make_dir(dir)
00189 char *dir;
00190 {
00191 /* Create a directory. */
00192   int pid, status;
00193 
00194   if ((pid = fork()) < 0)
00195         error(FATAL, "cannot fork off mkdir to create ", dir, "");
00196   if (pid > 0) {
00197         /* Parent process waits for child (mkdir). */
00198         wait(&status);
00199         return(status);
00200   } else {
00201         /* Child process executes mkdir */
00202         close(2);               /* don't want mkdir's error messages */
00203         execle("/bin/mkdir", "mkdir", dir, (char *) 0, environ);
00204         execle("/usr/bin/mkdir", "mkdir", dir, (char *) 0, environ);
00205         error(FATAL, "cannot execute mkdir", "", "");
00206   }
00207   return(0);
00208 }
00209 
00210 
00211 int stat_all(dir1, n)
00212 char *dir1;
00213 int n;
00214 {
00215 /* Stat all the directory entries.  By doing this all at once, the disk
00216  * head can stay in the inode area.
00217  */
00218 
00219   int i, j;
00220   char cbuf[MAX_PATH];
00221   struct stat s;
00222 
00223   for (i = 0; i < n; i++) {
00224         /* Mark "." and ".." as null entries, as well as unstatable ones. */
00225         if (strcmp(dir_ent[i].d_name, ".") == 0) dir_ent[i].d_ino = 0;
00226         if (strcmp(dir_ent[i].d_name, "..") == 0) dir_ent[i].d_ino = 0;
00227         if (dir_ent[i].d_ino == 0) continue;
00228 
00229         /* Stat the file. */
00230         snprintf(cbuf, sizeof(cbuf), "%s/%s", dir1, dir_ent[i].d_name);
00231         if (stat(cbuf, &s) < 0) {
00232                 error(NONFATAL, "cannot stat ", cbuf, "");
00233                 dir_ent[i].d_ino = 0;   /* mark as unusable */
00234                 continue;
00235         }
00236         sorted[i].mode = s.st_mode;
00237         sorted[i].acctime = s.st_atime;
00238         sorted[i].modtime = s.st_mtime;
00239         sorted[i].namep = dir_ent[i].d_name;
00240         sorted[i].namep[NAME_SIZE-1] = '\0';
00241   }
00242 
00243   /* Squeeze out all the entries whose ino field is 0. */
00244   j = 0;
00245   for (i = 0; i < n; i++) {
00246         if (dir_ent[i].d_ino != 0) {
00247                 sorted[j] = sorted[i];
00248                 j++;
00249         }
00250   }
00251   return(j);
00252 }
00253 
00254 
00255 void sort_dir(m)
00256 int m;
00257 {
00258 /* Sort the directory using bubble sort. */
00259 
00260   struct sorted *sp1, *sp2;
00261 
00262   for (sp1 = &sorted[0]; sp1 < &sorted[m - 1]; sp1++) {
00263         for (sp2 = sp1 + 1; sp2 < &sorted[m]; sp2++) {
00264                 if (strcmp(sp1->namep, sp2->namep) > 0)
00265                         swap(sp1, sp2);
00266         }
00267   }
00268 }
00269 
00270 
00271 void process(m, dir1, dir2)
00272 int m;
00273 char *dir1, *dir2;
00274 {
00275 /* Process each entry in sorted[].  If it is a regular file, stat the target
00276  * file.  The the source is newer, copy it.  If the entry is a directory,
00277  * recursively call the entire program to process the directory.
00278  */
00279 
00280   int er, fmode, res;
00281   struct sorted *sp;
00282   struct stat s;
00283   char cbuf[MAX_PATH];
00284 
00285   for (sp = &sorted[0]; sp < &sorted[m]; sp++) {
00286         int namlen;
00287         fmode = sp->mode & S_IFMT;
00288         if (fmode == S_IFREG) {
00289                 /* Regular file.  Construct target name and stat it. */
00290                 snprintf(cbuf, sizeof(cbuf), "%s/%s", dir2, sp->namep);
00291                 namlen = strlen(sp->namep);
00292                 /* Switch between compressed and uncompressed file names */
00293                 if (zflag && !rflag && strncmp((sp->namep + namlen - 2), ".Z", (size_t)2)
00294                                 && (namlen <= (NAME_SIZE - 2)))
00295                         strncat(cbuf, ".Z", (size_t)2);
00296                 if (zflag && rflag && !strncmp((sp->namep + namlen - 2), ".Z", (size_t)2))
00297                         cbuf[strlen(cbuf) - 2] = '\0';
00298                 er = stat(cbuf, &s);
00299                 if (er < 0 || sp->modtime > s.st_mtime) {
00300                         res = copy(dir1, sp, cbuf);
00301                 } else {
00302                         res = NONFATAL;
00303                 }
00304 
00305                 /* Check status of the copy. */
00306                 if (res == OUT_OF_SPACE) {
00307                         printf("Out of space while copying to %s\n", cbuf);
00308                         /* We ran out of space copying a regular file. */
00309                         if (mflag == 0)
00310                                 error(FATAL, "Quitting, disk full", "", "");
00311 
00312                         /* If -m, ask for new diskette and continue. */
00313                         newdisk(dir2);
00314                         sp--;
00315                         continue;
00316                 }
00317         } else if (fmode == S_IFDIR) {
00318                 /* Directory.  Execute this program recursively. */
00319                 copydir(dir1, dir2, sp->namep);
00320         } else if (fmode == S_IFBLK || fmode == S_IFCHR) {
00321                 /* Special file. */
00322                 strncpy(cbuf, sp->namep, sizeof(cbuf));
00323                 printf("%s is special file.  Not backed up.\n", cbuf);
00324         }
00325   }
00326 }
00327 
00328 
00329 
00330 
00331 void swap(sp1, sp2)
00332 struct sorted *sp1, *sp2;
00333 {
00334 /* Swap two directory entries. */
00335 
00336   struct sorted d;
00337 
00338   d = *sp1;
00339   *sp1 = *sp2;
00340   *sp2 = d;
00341 }
00342 
00343 
00344 int copy(dir1, sp, cbuf2)
00345 struct sorted *sp;
00346 char *dir1, *cbuf2;
00347 {
00348 /* Copy a regular file. */
00349 
00350   int fd1, fd2, nr, nw, res, n;
00351   char cbuf1[MAX_PATH], *p;
00352 #ifdef NARROW
00353   char *msg = (rflag || strcmp(pname, "backup")) ? "Restored" : "Backing up";
00354 #endif
00355 
00356   /* If the -j or -o or -s flags were given, suppress certain files. */
00357   p = sp->namep;
00358   n = strlen(p);
00359   if (n > NAME_SIZE) n = NAME_SIZE;
00360   if (jflag) {
00361         if (strcmp(p, "a.out") == 0) return(0);
00362         if (strcmp(p, "core") == 0) return (0);
00363         if (strcmp(p + n - 2, ".Z") == 0) return (0);
00364         if (strcmp(p + n - 4, ".bak") == 0) return (0);
00365         if (strcmp(p + n - 4, ".log") == 0) return (0);
00366   }
00367   if (oflag) {
00368         if (strcmp(p + n - 2, ".o") == 0) return(0);
00369   }
00370   if (sflag) {
00371         if (strcmp(p + n - 2, ".s") == 0) return(0);
00372   }
00373   res = 0;
00374   if (dflag) return(0);         /* backup -d means only directories */
00375   strcpy(cbuf1, dir1);
00376   strncat(cbuf1, "/", (size_t)1);
00377   strncat(cbuf1, sp->namep, (size_t)NAME_SIZE); /* cbuf1 = source file name */
00378 
00379   /* At this point, cbuf1 contains the source file name, cbuf2 the target. */
00380   fd1 = open(cbuf1, O_RDONLY);
00381   if (fd1 < 0) {
00382         error(NONFATAL, "cannot open ", cbuf1, "");
00383         return(res);
00384   }
00385   fd2 = creat(cbuf2, (sp->mode | S_IWUSR) & 07777);
00386   if (fd2 < 0) {
00387         if (errno == ENFILE) {
00388                 close(fd1);
00389                 return(OUT_OF_SPACE);
00390         }
00391         error(NONFATAL, "cannot create ", cbuf2, "");
00392         close(fd1);
00393         return(res);
00394   }
00395 
00396   /* Both files are now open.  Do the copying. */
00397   if (!rflag && strncmp((sp->namep + n - 2), ".Z", (size_t)2) ||
00398                 rflag && !strncmp((sp->namep + n - 2), ".Z", (size_t)2)) {
00399         if (zflag && (rflag || (n <= (NAME_SIZE - 2)))) {
00400                 close(fd1);
00401                 close(fd2);
00402                 res = zcopy(cbuf1, cbuf2);
00403                 if (tflag) utime(cbuf2, (struct utimbuf *) & (sp->acctime));
00404                 if (res != 0) unlink(cbuf2); /* if error, get rid of the corpse */
00405 #ifdef NARROW
00406                 if (vflag && res == 0) printf("%s %s\n", msg, cbuf1);
00407 #else
00408                 if (vflag && res == 0) {
00409                         printf("%-37.37s -> %-37.37s\n", cbuf1, cbuf2);
00410                         if (strlen(cbuf1) > 37 || strlen(cbuf2) > 37)
00411                                 printf("%37.37s    %37.37s\n",
00412                                 (strlen(cbuf1) > 37) ? (cbuf1 + 37) : "",
00413                                 (strlen(cbuf2) > 37) ? (cbuf2 + 37) : "");
00414                 }
00415 #endif
00416                 return(res);
00417         }
00418   }
00419   while (1) {
00420         nr = read(fd1, copybuf, COPY_SIZE);
00421         if (nr == 0) break;
00422         if (nr < 0) {
00423                 error(NONFATAL, "read error on ", cbuf1, "");
00424                 res = EIO;
00425                 break;
00426         }
00427         nw = write(fd2, copybuf, nr);
00428         if (nw < 0) {
00429                 if (errno == ENOSPC) {
00430                         /* Device is full. */
00431                         res = OUT_OF_SPACE;
00432                         break;
00433                 }
00434 
00435                 /* True write error. */
00436                 error(NONFATAL, "write error on ", cbuf2, "");
00437                 res = EIO;
00438                 break;
00439         }
00440   }
00441   if (res == 0) {
00442 #ifdef NARROW
00443         if (vflag) printf("%s %s\n", msg, cbuf1);
00444 #else
00445         if (vflag) {
00446                 printf("%-37.37s -> %-37.37s\n", cbuf1, cbuf2);
00447                 if (strlen(cbuf1) > 37 || strlen(cbuf2) > 37)
00448                         printf("%37.37s    %37.37s\n",
00449                         (strlen(cbuf1) > 37) ? (cbuf1 + 37) : "",
00450                         (strlen(cbuf2) > 37) ? (cbuf2 + 37) : "");
00451         }
00452 #endif
00453   } else {
00454         unlink(cbuf2);
00455   }
00456   close(fd1);
00457   close(fd2);
00458   if (tflag) utime(cbuf2, (struct utimbuf *) & (sp->acctime));
00459   return(res);
00460 }
00461 
00462 
00463 int zcopy(src, targ)
00464 char *src, *targ;
00465 {
00466 
00467   int pid, status, res, s;
00468   char fbuf[20];
00469 
00470   /* These flags go for compress and gzip. */
00471   strcpy(fbuf, "-c");
00472   if (rflag)
00473         strcat(fbuf, "d");
00474   else
00475         strcat(fbuf, "f");
00476 
00477   if ((pid = fork()) < 0) error(FATAL, "cannot fork", "", "");
00478   if (pid > 0) {
00479         wait(&status);
00480 
00481         /* Error codes 0 and 2 are ok, assume others mean disk is full. */
00482         res = (status == 0 || status == NO_SAVINGS ? 0 : OUT_OF_SPACE);
00483         return(res);
00484   } else {
00485         /* Child must execute compress. */
00486         close(1);
00487         s = open(targ, O_RDWR);
00488         if (s < 0) error(FATAL, "cannot write on ", "targ", "");
00489         execle("/usr/bin/gzip", "gzip", fbuf, src, (char *)0, environ);
00490         execle("/usr/local/bin/gzip", "gzip", fbuf, src, (char *)0, environ);
00491         execle("/bin/compress", "compress", fbuf, src, (char *)0, environ);
00492         execle("/usr/bin/compress", "compress", fbuf, src, (char *)0, environ);
00493         error(FATAL, "cannot exec gzip or compress", "", "");
00494   }
00495   return(0);
00496 }
00497 
00498 
00499 void copydir(dir1, dir2, namep)
00500 char *dir1, *dir2, *namep;
00501 {
00502 /* Copy a directory. */
00503 
00504   int pid, status;
00505   char fbuf[20], d1buf[MAX_PATH], d2buf[MAX_PATH];
00506 
00507   if (nflag) return;    /* backup -n means no directories */
00508 
00509   fbuf[0] = '\0';
00510 
00511   /* Handle directory copy by forking off 'backup' ! */
00512   if (jflag || mflag || oflag || rflag || sflag || tflag || vflag || zflag)
00513         strcpy(fbuf, "-");
00514   if (jflag) strcat(fbuf, "j");
00515   if (mflag) strcat(fbuf, "m");
00516   if (oflag) strcat(fbuf, "o");
00517   if (rflag) strcat(fbuf, "r");
00518   if (sflag) strcat(fbuf, "s");
00519   if (tflag) strcat(fbuf, "t");
00520   if (vflag) strcat(fbuf, "v");
00521   if (zflag) strcat(fbuf, "z");
00522   snprintf(d1buf, sizeof(d1buf), "%s/%s", dir1, namep);
00523   snprintf(d2buf, sizeof(d2buf), "%s/%s", dir2, namep);
00524 
00525   if ((pid = fork()) < 0) error(FATAL, "cannot fork", "", "");
00526   if (pid > 0) {
00527         /* Parent waits for child, then returns. */
00528         wait(&status);
00529         return;
00530   }
00531 
00532   if (fbuf[0] == '-') {
00533         execle(pname, pname, fbuf, d1buf, d2buf, (char *) 0, environ);
00534         execle("/bin/backup", "backup", fbuf, d1buf, d2buf, (char *)0,environ);
00535         execle("/usr/bin/backup","backup",fbuf,d1buf,d2buf,(char *)0,environ);
00536         error(FATAL, "cannot recursively exec backup", "", "");
00537   } else {
00538         execle(pname, pname, d1buf, d2buf, (char *) 0, environ);
00539         execle("/bin/backup", "backup", d1buf, d2buf, (char *)0,environ);
00540         execle("/usr/bin/backup","backup", d1buf, d2buf, (char *)0,environ);
00541         error(FATAL, "cannot recursively exec backup", "", "");
00542   }
00543 }
00544 
00545 void newdisk(dir)
00546 char *dir;
00547 {
00548 /* Ask for a new diskette. A big problem is that this program does not
00549  * know which device is being used and where it is mounted on.  As an
00550  * emergency solution, fork off a shell and ask the user to do the work.
00551  */
00552 
00553   int pid, status;
00554 
00555   printf("\nDiskette full. Please do the following:\n");
00556   printf("   1. Unmount the diskette using /etc/umount\n");
00557   printf("   2. Physically replace the diskette by the next one.\n");
00558   printf("   3. Mount the new diskette using /etc/mount\n");
00559   printf("   4. Type CTRL-D to return to the backup/restore program\n");
00560 
00561   if ((pid = fork()) < 0) error(FATAL, "cannot fork", "", "");
00562   if (pid > 0) {
00563         wait(&status);
00564         maketarget(dir);        /* make the directory */
00565   } else {
00566         execle("/bin/sh", "sh", "-i", (char *) 0, environ);
00567         execle("/usr/bin/sh", "sh", "-i", (char *) 0, environ);
00568         error(FATAL, "cannot execute shell to ask for new diskette", "", "");
00569   }
00570 }
00571 
00572 void usage()
00573 {
00574   fprintf(stderr, "Usage: %s [-djmnorstvz] dir1 dir2\n", pname);
00575   exit(2);
00576 }
00577 
00578 
00579 void error(type, s1, s2, s3)
00580 int type;
00581 char *s1, *s2, *s3;
00582 {
00583   fprintf(stderr, "%s: %s%s%s\n", pname, s1, s2, s3);
00584 
00585   if (type == NONFATAL)
00586         return;
00587   else
00588         exit(type);
00589 }

Generated on Fri Apr 14 22:56:57 2006 for minix by  doxygen 1.4.6