1 /* backup - backup a directory Author: Andy Tanenbaum */ 2 3 /* This program recursively backs up a directory. It has two typical uses: 4 * 5 * 1. Backing up a directory to 1 or more diskettes 6 * 2. Backing up RAM disk to a shadow directory on hard disk 7 * 8 * The backup directory or medium may be empty, in which case, the entire 9 * source directory is copied, or it may contain an old backup, in which 10 * case only those files that are new or out of date are copied. In this 11 * respect, 'backup' resembles 'make', except that no 'makefile' is needed. 12 * The backed up copy may optionally be compressed to save space. 13 * 14 * The following flags exist: 15 * 16 * -d At the top level, only back up directories (not loose files) 17 * -j Don't copy junk: *.Z, *.bak, *.log, a.out, and core 18 * -m If ENOSPC encountered, ask for another diskette 19 * -n No directories, only loose files are backed up 20 * -o Don't copy *.o files 21 * -r Restore files (ie. uncompress if necessary) 22 * -s Don't copy *.s files 23 * -t Set creation date of target-file equal to cdate of source-file 24 * -v Verbose (announce what is being done) 25 * -z Compress on backup/uncompress on restore 26 * 27 * Patches: 28 * 30 Mar 91. Added restore option. cwr. 29 * 9 Sep 91. Changed user interface. cwr. 30 * 21 Jan 93. Revised error messages. cwr. 31 * 29 Mar 95. Added -o, NARROW define. cwr. 32 * 16 Nov 2013. Changed the functions to ANSI style. 33 * Author: Alexandre Beletti (rhiguita@gmail.com) 34 */ 35 36 #include <sys/types.h> 37 #include <sys/stat.h> 38 #include <sys/syslimits.h> 39 #include <errno.h> 40 #include <fcntl.h> 41 #include <utime.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <unistd.h> 45 #include <sys/wait.h> 46 #include <stdio.h> 47 #include <dirent.h> 48 49 #define NAME_SIZE NAME_MAX 50 51 #undef NARROW /* Width of verbose output */ 52 #define COPY_SIZE 4096 53 #define MAX_ENTRIES 512 54 #define MAX_PATH 256 55 #define NONFATAL 0 56 #define FATAL 1 57 #define NO_SAVINGS 512 /* compress can return code 2 */ 58 #define OUT_OF_SPACE 2 59 60 struct dirent_full { 61 struct dirent de; 62 char d_name[MAXNAMLEN+1]; 63 } dir_ent[MAX_ENTRIES]; 64 int entries = 0; 65 66 struct sorted { 67 int mode; /* file mode */ 68 char *namep; /* pointer to name in dir_buf */ 69 long acctime; /* time of last access */ 70 long modtime; /* time of last modification */ 71 } sorted[MAX_ENTRIES]; 72 73 char copybuf[COPY_SIZE]; 74 char *pname; 75 int dflag, jflag, mflag, nflag, oflag, rflag, sflag, tflag, vflag, zflag; 76 77 extern int errno; 78 extern char **environ; 79 80 int main(int argc, char *argv[]); 81 void maketarget(char *dir2); 82 int make_dir(char *dir); 83 int stat_all(char *dir1, int n); 84 void sort_dir(int m); 85 void process(int m, char *dir1, char *dir2); 86 void swap(struct sorted *sp1, struct sorted *sp2); 87 int copy(char *dir1, struct sorted *sp, char *cbuf2); 88 int zcopy(char *src, char *targ); 89 void copydir(char *dir1, char *dir2, char *namep); 90 void newdisk(char *dir); 91 void usage(void); 92 void error(int type, char *s1, char *s2, char *s3); 93 94 int main(int argc, char *argv[]) 95 { 96 int n, m; 97 char *dir1, *dir2, *cp, c; 98 struct stat s; 99 struct dirent *e; 100 DIR *DIR1; 101 102 (void) sync(); 103 104 /* Get the flags */ 105 if ((pname = strrchr(argv[0], '/')) == (char *)NULL) 106 pname = argv[0]; 107 else 108 pname++; 109 if (argc < 3 || argc > 4) usage(); 110 if (argc == 4) { 111 cp = argv[1]; 112 if (*cp++ != '-') usage(); 113 while ((c = *cp++) != '\0') { 114 switch (c) { 115 case 'd': dflag++; break; 116 case 'j': jflag++; break; 117 case 'm': mflag++; break; 118 case 'n': nflag++; break; 119 case 'o': oflag++; break; 120 case 's': sflag++; break; 121 case 'r': rflag++; break; 122 case 't': tflag++; break; 123 case 'v': vflag++; break; 124 case 'z': zflag++; break; 125 default: usage(); 126 } 127 } 128 dir1 = argv[2]; 129 dir2 = argv[3]; 130 } else { 131 dir1 = argv[1]; 132 dir2 = argv[2]; 133 } 134 if (!strcmp(pname, "restore") && !rflag) rflag++; 135 136 /* Check for a valid source */ 137 if (stat(dir1, &s) < 0) error(FATAL, "cannot stat ", dir1, ""); 138 if ((s.st_mode & S_IFMT) != S_IFDIR) error(FATAL, "non-directory ", dir1, ""); 139 140 /* Read in the source directory */ 141 if(!(DIR1 = opendir(dir1))) { 142 perror(dir1); 143 return 1; 144 } 145 for(entries = 0; entries < MAX_ENTRIES && (e=readdir(DIR1)); entries++) { 146 memcpy(&dir_ent[entries].de, e, sizeof(*e)); 147 snprintf(dir_ent[entries].d_name, MAXNAMLEN, "%s", e->d_name); 148 } 149 closedir(DIR1); 150 if (entries == MAX_ENTRIES) 151 error(FATAL, "directory ", dir1, " is too large"); 152 153 /* Create the target directory. */ 154 maketarget(dir2); 155 156 /* Stat all the entries. */ 157 n = entries; 158 m = stat_all(dir1, n); 159 160 /* Remove non-entries and sort what's left. */ 161 sort_dir(m); 162 163 /* Process each of the m entries one at a time. */ 164 process(m, dir1, dir2); 165 return(0); 166 } 167 168 169 void maketarget(char *dir2) 170 { 171 /* The target directory is created if it does not already exist. */ 172 173 char *p, c, dbuf[MAX_PATH]; 174 175 if (access(dir2, 6) == 0) 176 return; /* if target exists, we're done */ 177 if (make_dir(dir2) == 0) return; /* we just made it */ 178 179 /* We have to try creating all the higher level directories. */ 180 strcpy(dbuf, dir2); 181 p = dbuf + 1; 182 while (1) { 183 while (*p != '/' && *p != '\0') p++; 184 c = *p; /* either / or \0 */ 185 *p = 0; 186 make_dir(dbuf); 187 if (c == '\0') return; 188 *p = c; 189 p++; 190 } 191 } 192 193 int make_dir(char *dir) 194 { 195 /* Create a directory. */ 196 int pid, status; 197 198 if ((pid = fork()) < 0) 199 error(FATAL, "cannot fork off mkdir to create ", dir, ""); 200 if (pid > 0) { 201 /* Parent process waits for child (mkdir). */ 202 wait(&status); 203 return(status); 204 } else { 205 /* Child process executes mkdir */ 206 close(2); /* don't want mkdir's error messages */ 207 execle("/bin/mkdir", "mkdir", dir, (char *) 0, environ); 208 execle("/usr/bin/mkdir", "mkdir", dir, (char *) 0, environ); 209 error(FATAL, "cannot execute mkdir", "", ""); 210 } 211 return(0); 212 } 213 214 215 int stat_all(char *dir1, int n) 216 { 217 /* Stat all the directory entries. By doing this all at once, the disk 218 * head can stay in the inode area. 219 */ 220 221 int i, j; 222 char cbuf[MAX_PATH]; 223 struct stat s; 224 225 for (i = 0; i < n; i++) { 226 /* Mark "." and ".." as null entries, as well as unstatable ones. */ 227 if (strcmp(dir_ent[i].d_name, ".") == 0) dir_ent[i].de.d_ino = 0; 228 if (strcmp(dir_ent[i].d_name, "..") == 0) dir_ent[i].de.d_ino = 0; 229 if (dir_ent[i].de.d_ino == 0) continue; 230 231 /* Stat the file. */ 232 snprintf(cbuf, sizeof(cbuf), "%s/%s", dir1, dir_ent[i].d_name); 233 if (stat(cbuf, &s) < 0) { 234 error(NONFATAL, "cannot stat ", cbuf, ""); 235 dir_ent[i].de.d_ino = 0; /* mark as unusable */ 236 continue; 237 } 238 sorted[i].mode = s.st_mode; 239 sorted[i].acctime = s.st_atime; 240 sorted[i].modtime = s.st_mtime; 241 sorted[i].namep = dir_ent[i].d_name; 242 sorted[i].namep[NAME_SIZE-1] = '\0'; 243 } 244 245 /* Squeeze out all the entries whose ino field is 0. */ 246 j = 0; 247 for (i = 0; i < n; i++) { 248 if (dir_ent[i].de.d_ino != 0) { 249 sorted[j] = sorted[i]; 250 j++; 251 } 252 } 253 return(j); 254 } 255 256 257 void sort_dir(int m) 258 { 259 /* Sort the directory using bubble sort. */ 260 261 struct sorted *sp1, *sp2; 262 263 for (sp1 = &sorted[0]; sp1 < &sorted[m - 1]; sp1++) { 264 for (sp2 = sp1 + 1; sp2 < &sorted[m]; sp2++) { 265 if (strcmp(sp1->namep, sp2->namep) > 0) 266 swap(sp1, sp2); 267 } 268 } 269 } 270 271 272 void process(int m, char *dir1, char *dir2) 273 { 274 /* Process each entry in sorted[]. If it is a regular file, stat the target 275 * file. The the source is newer, copy it. If the entry is a directory, 276 * recursively call the entire program to process the directory. 277 */ 278 279 int er, fmode, res; 280 struct sorted *sp; 281 struct stat s; 282 char cbuf[MAX_PATH]; 283 284 for (sp = &sorted[0]; sp < &sorted[m]; sp++) { 285 int namlen; 286 fmode = sp->mode & S_IFMT; 287 if (fmode == S_IFREG) { 288 /* Regular file. Construct target name and stat it. */ 289 snprintf(cbuf, sizeof(cbuf), "%s/%s", dir2, sp->namep); 290 namlen = strlen(sp->namep); 291 /* Switch between compressed and uncompressed file names */ 292 if (zflag && !rflag && strncmp((sp->namep + namlen - 2), ".Z", (size_t)2) 293 && (namlen <= (NAME_SIZE - 2))) 294 strncat(cbuf, ".Z", (size_t)2); 295 if (zflag && rflag && !strncmp((sp->namep + namlen - 2), ".Z", (size_t)2)) 296 cbuf[strlen(cbuf) - 2] = '\0'; 297 er = stat(cbuf, &s); 298 if (er < 0 || sp->modtime > s.st_mtime) { 299 res = copy(dir1, sp, cbuf); 300 } else { 301 res = NONFATAL; 302 } 303 304 /* Check status of the copy. */ 305 if (res == OUT_OF_SPACE) { 306 printf("Out of space while copying to %s\n", cbuf); 307 /* We ran out of space copying a regular file. */ 308 if (mflag == 0) 309 error(FATAL, "Quitting, disk full", "", ""); 310 311 /* If -m, ask for new diskette and continue. */ 312 newdisk(dir2); 313 sp--; 314 continue; 315 } 316 } else if (fmode == S_IFDIR) { 317 /* Directory. Execute this program recursively. */ 318 copydir(dir1, dir2, sp->namep); 319 } else if (fmode == S_IFBLK || fmode == S_IFCHR) { 320 /* Special file. */ 321 strncpy(cbuf, sp->namep, sizeof(cbuf)); 322 printf("%s is special file. Not backed up.\n", cbuf); 323 } 324 } 325 } 326 327 328 329 330 void swap(struct sorted *sp1, struct sorted *sp2) 331 { 332 /* Swap two directory entries. */ 333 334 struct sorted d; 335 336 d = *sp1; 337 *sp1 = *sp2; 338 *sp2 = d; 339 } 340 341 342 int copy(char *dir1, struct sorted *sp, char *cbuf2) 343 { 344 /* Copy a regular file. */ 345 346 int fd1, fd2, nr, nw, res, n; 347 char cbuf1[MAX_PATH], *p; 348 #ifdef NARROW 349 char *msg = (rflag || strcmp(pname, "backup")) ? "Restored" : "Backing up"; 350 #endif 351 352 /* If the -j or -o or -s flags were given, suppress certain files. */ 353 p = sp->namep; 354 n = strlen(p); 355 if (n > NAME_SIZE) n = NAME_SIZE; 356 if (jflag) { 357 if (strcmp(p, "a.out") == 0) return(0); 358 if (strcmp(p, "core") == 0) return (0); 359 if (strcmp(p + n - 2, ".Z") == 0) return (0); 360 if (strcmp(p + n - 4, ".bak") == 0) return (0); 361 if (strcmp(p + n - 4, ".log") == 0) return (0); 362 } 363 if (oflag) { 364 if (strcmp(p + n - 2, ".o") == 0) return(0); 365 } 366 if (sflag) { 367 if (strcmp(p + n - 2, ".s") == 0) return(0); 368 } 369 res = 0; 370 if (dflag) return(0); /* backup -d means only directories */ 371 strcpy(cbuf1, dir1); 372 strncat(cbuf1, "/", (size_t)1); 373 strncat(cbuf1, sp->namep, (size_t)NAME_SIZE); /* cbuf1 = source file name */ 374 375 /* At this point, cbuf1 contains the source file name, cbuf2 the target. */ 376 fd1 = open(cbuf1, O_RDONLY); 377 if (fd1 < 0) { 378 error(NONFATAL, "cannot open ", cbuf1, ""); 379 return(res); 380 } 381 fd2 = creat(cbuf2, (sp->mode | S_IWUSR) & 07777); 382 if (fd2 < 0) { 383 if (errno == ENFILE) { 384 close(fd1); 385 return(OUT_OF_SPACE); 386 } 387 error(NONFATAL, "cannot create ", cbuf2, ""); 388 close(fd1); 389 return(res); 390 } 391 392 /* Both files are now open. Do the copying. */ 393 if ((!rflag && strncmp((sp->namep + n - 2), ".Z", (size_t)2)) || 394 (rflag && !strncmp((sp->namep + n - 2), ".Z", (size_t)2))) { 395 if (zflag && (rflag || (n <= (NAME_SIZE - 2)))) { 396 close(fd1); 397 close(fd2); 398 res = zcopy(cbuf1, cbuf2); 399 if (tflag) utime(cbuf2, (struct utimbuf *) & (sp->acctime)); 400 if (res != 0) unlink(cbuf2); /* if error, get rid of the corpse */ 401 #ifdef NARROW 402 if (vflag && res == 0) printf("%s %s\n", msg, cbuf1); 403 #else 404 if (vflag && res == 0) { 405 printf("%-37.37s -> %-37.37s\n", cbuf1, cbuf2); 406 if (strlen(cbuf1) > 37 || strlen(cbuf2) > 37) 407 printf("%37.37s %37.37s\n", 408 (strlen(cbuf1) > 37) ? (cbuf1 + 37) : "", 409 (strlen(cbuf2) > 37) ? (cbuf2 + 37) : ""); 410 } 411 #endif 412 return(res); 413 } 414 } 415 while (1) { 416 nr = read(fd1, copybuf, COPY_SIZE); 417 if (nr == 0) break; 418 if (nr < 0) { 419 error(NONFATAL, "read error on ", cbuf1, ""); 420 res = EIO; 421 break; 422 } 423 nw = write(fd2, copybuf, nr); 424 if (nw < 0) { 425 if (errno == ENOSPC) { 426 /* Device is full. */ 427 res = OUT_OF_SPACE; 428 break; 429 } 430 431 /* True write error. */ 432 error(NONFATAL, "write error on ", cbuf2, ""); 433 res = EIO; 434 break; 435 } 436 } 437 if (res == 0) { 438 #ifdef NARROW 439 if (vflag) printf("%s %s\n", msg, cbuf1); 440 #else 441 if (vflag) { 442 printf("%-37.37s -> %-37.37s\n", cbuf1, cbuf2); 443 if (strlen(cbuf1) > 37 || strlen(cbuf2) > 37) 444 printf("%37.37s %37.37s\n", 445 (strlen(cbuf1) > 37) ? (cbuf1 + 37) : "", 446 (strlen(cbuf2) > 37) ? (cbuf2 + 37) : ""); 447 } 448 #endif 449 } else { 450 unlink(cbuf2); 451 } 452 close(fd1); 453 close(fd2); 454 if (tflag) utime(cbuf2, (struct utimbuf *) & (sp->acctime)); 455 return(res); 456 } 457 458 459 int zcopy(char *src, char *targ) 460 { 461 462 int pid, status, res, s; 463 char fbuf[20]; 464 465 /* These flags go for compress and gzip. */ 466 strcpy(fbuf, "-c"); 467 if (rflag) 468 strcat(fbuf, "d"); 469 else 470 strcat(fbuf, "f"); 471 472 if ((pid = fork()) < 0) error(FATAL, "cannot fork", "", ""); 473 if (pid > 0) { 474 wait(&status); 475 476 /* Error codes 0 and 2 are ok, assume others mean disk is full. */ 477 res = (status == 0 || status == NO_SAVINGS ? 0 : OUT_OF_SPACE); 478 return(res); 479 } else { 480 /* Child must execute compress. */ 481 close(1); 482 s = open(targ, O_RDWR); 483 if (s < 0) error(FATAL, "cannot write on ", "targ", ""); 484 execle("/usr/bin/gzip", "gzip", fbuf, src, (char *)0, environ); 485 execle("/usr/local/bin/gzip", "gzip", fbuf, src, (char *)0, environ); 486 execle("/bin/compress", "compress", fbuf, src, (char *)0, environ); 487 execle("/usr/bin/compress", "compress", fbuf, src, (char *)0, environ); 488 error(FATAL, "cannot exec gzip or compress", "", ""); 489 } 490 return(0); 491 } 492 493 494 void copydir(char *dir1, char *dir2, char *namep) 495 { 496 /* Copy a directory. */ 497 498 int pid, status; 499 char fbuf[20], d1buf[MAX_PATH], d2buf[MAX_PATH]; 500 501 if (nflag) return; /* backup -n means no directories */ 502 503 fbuf[0] = '\0'; 504 505 /* Handle directory copy by forking off 'backup' ! */ 506 if (jflag || mflag || oflag || rflag || sflag || tflag || vflag || zflag) 507 strcpy(fbuf, "-"); 508 if (jflag) strcat(fbuf, "j"); 509 if (mflag) strcat(fbuf, "m"); 510 if (oflag) strcat(fbuf, "o"); 511 if (rflag) strcat(fbuf, "r"); 512 if (sflag) strcat(fbuf, "s"); 513 if (tflag) strcat(fbuf, "t"); 514 if (vflag) strcat(fbuf, "v"); 515 if (zflag) strcat(fbuf, "z"); 516 snprintf(d1buf, sizeof(d1buf), "%s/%s", dir1, namep); 517 snprintf(d2buf, sizeof(d2buf), "%s/%s", dir2, namep); 518 519 if ((pid = fork()) < 0) error(FATAL, "cannot fork", "", ""); 520 if (pid > 0) { 521 /* Parent waits for child, then returns. */ 522 wait(&status); 523 return; 524 } 525 526 if (fbuf[0] == '-') { 527 execle(pname, pname, fbuf, d1buf, d2buf, (char *) 0, environ); 528 execle("/bin/backup", "backup", fbuf, d1buf, d2buf, (char *)0,environ); 529 execle("/usr/bin/backup","backup",fbuf,d1buf,d2buf,(char *)0,environ); 530 error(FATAL, "cannot recursively exec backup", "", ""); 531 } else { 532 execle(pname, pname, d1buf, d2buf, (char *) 0, environ); 533 execle("/bin/backup", "backup", d1buf, d2buf, (char *)0,environ); 534 execle("/usr/bin/backup","backup", d1buf, d2buf, (char *)0,environ); 535 error(FATAL, "cannot recursively exec backup", "", ""); 536 } 537 } 538 539 void newdisk(char *dir) 540 { 541 /* Ask for a new diskette. A big problem is that this program does not 542 * know which device is being used and where it is mounted on. As an 543 * emergency solution, fork off a shell and ask the user to do the work. 544 */ 545 546 int pid, status; 547 548 printf("\nDiskette full. Please do the following:\n"); 549 printf(" 1. Unmount the diskette using /etc/umount\n"); 550 printf(" 2. Physically replace the diskette by the next one.\n"); 551 printf(" 3. Mount the new diskette using /etc/mount\n"); 552 printf(" 4. Type CTRL-D to return to the backup/restore program\n"); 553 554 if ((pid = fork()) < 0) error(FATAL, "cannot fork", "", ""); 555 if (pid > 0) { 556 wait(&status); 557 maketarget(dir); /* make the directory */ 558 } else { 559 execle("/bin/sh", "sh", "-i", (char *) 0, environ); 560 execle("/usr/bin/sh", "sh", "-i", (char *) 0, environ); 561 error(FATAL, "cannot execute shell to ask for new diskette", "", ""); 562 } 563 } 564 565 void usage() 566 { 567 fprintf(stderr, "Usage: %s [-djmnorstvz] dir1 dir2\n", pname); 568 exit(2); 569 } 570 571 572 void error(int type, char *s1, char *s2, char *s3) 573 { 574 fprintf(stderr, "%s: %s%s%s\n", pname, s1, s2, s3); 575 576 if (type == NONFATAL) 577 return; 578 else 579 exit(type); 580 } 581