1 /* backup - backup a directory Author: Andy Tanenbaum */
3 /* This program recursively backs up a directory. It has two typical uses:
5 * 1. Backing up a directory to 1 or more diskettes
6 * 2. Backing up RAM disk to a shadow directory on hard disk
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.
14 * The following flags exist:
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
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.
34 #include <sys/types.h>
46 #define NAME_SIZE NAME_MAX
48 #undef NARROW /* Width of verbose output */
49 #define COPY_SIZE 4096
50 #define MAX_ENTRIES 512
54 #define NO_SAVINGS 512 /* compress can return code 2 */
55 #define OUT_OF_SPACE 2
57 struct dirent dir_ent
[MAX_ENTRIES
];
61 int mode
; /* file mode */
62 char *namep
; /* pointer to name in dir_buf */
63 long acctime
; /* time of last access */
64 long modtime
; /* time of last modification */
65 } sorted
[MAX_ENTRIES
];
67 char copybuf
[COPY_SIZE
];
69 int dflag
, jflag
, mflag
, nflag
, oflag
, rflag
, sflag
, tflag
, vflag
, zflag
;
72 extern char **environ
;
74 int main(int argc
, char **argv
);
75 void maketarget(char *dir2
);
76 int make_dir(char *dir
);
77 int stat_all(char *dir1
, int n
);
79 void process(int m
, char *dir1
, char *dir2
);
80 void swap(struct sorted
*sp1
, struct sorted
*sp2
);
81 int copy(char *dir1
, struct sorted
*sp
, char *cbuf2
);
82 int zcopy(char *src
, char *targ
);
83 void copydir(char *dir1
, char *dir2
, char *namep
);
84 void newdisk(char *dir
);
86 void error(int type
, char *s1
, char *s2
, char *s3
);
93 char *dir1
, *dir2
, *cp
, c
;
101 if ((pname
= strrchr(argv
[0], '/')) == (char *)NULL
)
105 if (argc
< 3 || argc
> 4) usage();
108 if (*cp
++ != '-') usage();
109 while ((c
= *cp
++) != '\0') {
111 case 'd': dflag
++; break;
112 case 'j': jflag
++; break;
113 case 'm': mflag
++; break;
114 case 'n': nflag
++; break;
115 case 'o': oflag
++; break;
116 case 's': sflag
++; break;
117 case 'r': rflag
++; break;
118 case 't': tflag
++; break;
119 case 'v': vflag
++; break;
120 case 'z': zflag
++; break;
130 if (!strcmp(pname
, "restore") && !rflag
) rflag
++;
132 /* Check for a valid source */
133 if (stat(dir1
, &s
) < 0) error(FATAL
, "cannot stat ", dir1
, "");
134 if ((s
.st_mode
& S_IFMT
) != S_IFDIR
) error(FATAL
, "non-directory ", dir1
, "");
136 /* Read in the source directory */
137 if(!(DIR1
= opendir(dir1
))) {
141 while(entries
< MAX_ENTRIES
&& (e
=readdir(DIR1
)))
142 memcpy(&dir_ent
[entries
++], e
, sizeof(*e
));
144 if (entries
== MAX_ENTRIES
)
145 error(FATAL
, "directory ", dir1
, " is too large");
147 /* Create the target directory. */
150 /* Stat all the entries. */
152 m
= stat_all(dir1
, n
);
154 /* Remove non-entries and sort what's left. */
157 /* Process each of the m entries one at a time. */
158 process(m
, dir1
, dir2
);
163 void maketarget(dir2
)
166 /* The target directory is created if it does not already exist. */
168 char *p
, c
, dbuf
[MAX_PATH
];
170 if (access(dir2
, 6) == 0)
171 return; /* if target exists, we're done */
172 if (make_dir(dir2
) == 0) return; /* we just made it */
174 /* We have to try creating all the higher level directories. */
178 while (*p
!= '/' && *p
!= '\0') p
++;
179 c
= *p
; /* either / or \0 */
182 if (c
== '\0') return;
191 /* Create a directory. */
194 if ((pid
= fork()) < 0)
195 error(FATAL
, "cannot fork off mkdir to create ", dir
, "");
197 /* Parent process waits for child (mkdir). */
201 /* Child process executes mkdir */
202 close(2); /* don't want mkdir's error messages */
203 execle("/bin/mkdir", "mkdir", dir
, (char *) 0, environ
);
204 execle("/usr/bin/mkdir", "mkdir", dir
, (char *) 0, environ
);
205 error(FATAL
, "cannot execute mkdir", "", "");
211 int stat_all(dir1
, n
)
215 /* Stat all the directory entries. By doing this all at once, the disk
216 * head can stay in the inode area.
223 for (i
= 0; i
< n
; i
++) {
224 /* Mark "." and ".." as null entries, as well as unstatable ones. */
225 if (strcmp(dir_ent
[i
].d_name
, ".") == 0) dir_ent
[i
].d_ino
= 0;
226 if (strcmp(dir_ent
[i
].d_name
, "..") == 0) dir_ent
[i
].d_ino
= 0;
227 if (dir_ent
[i
].d_ino
== 0) continue;
230 snprintf(cbuf
, sizeof(cbuf
), "%s/%s", dir1
, dir_ent
[i
].d_name
);
231 if (stat(cbuf
, &s
) < 0) {
232 error(NONFATAL
, "cannot stat ", cbuf
, "");
233 dir_ent
[i
].d_ino
= 0; /* mark as unusable */
236 sorted
[i
].mode
= s
.st_mode
;
237 sorted
[i
].acctime
= s
.st_atime
;
238 sorted
[i
].modtime
= s
.st_mtime
;
239 sorted
[i
].namep
= dir_ent
[i
].d_name
;
240 sorted
[i
].namep
[NAME_SIZE
-1] = '\0';
243 /* Squeeze out all the entries whose ino field is 0. */
245 for (i
= 0; i
< n
; i
++) {
246 if (dir_ent
[i
].d_ino
!= 0) {
247 sorted
[j
] = sorted
[i
];
258 /* Sort the directory using bubble sort. */
260 struct sorted
*sp1
, *sp2
;
262 for (sp1
= &sorted
[0]; sp1
< &sorted
[m
- 1]; sp1
++) {
263 for (sp2
= sp1
+ 1; sp2
< &sorted
[m
]; sp2
++) {
264 if (strcmp(sp1
->namep
, sp2
->namep
) > 0)
271 void process(m
, dir1
, dir2
)
275 /* Process each entry in sorted[]. If it is a regular file, stat the target
276 * file. The the source is newer, copy it. If the entry is a directory,
277 * recursively call the entire program to process the directory.
285 for (sp
= &sorted
[0]; sp
< &sorted
[m
]; sp
++) {
287 fmode
= sp
->mode
& S_IFMT
;
288 if (fmode
== S_IFREG
) {
289 /* Regular file. Construct target name and stat it. */
290 snprintf(cbuf
, sizeof(cbuf
), "%s/%s", dir2
, sp
->namep
);
291 namlen
= strlen(sp
->namep
);
292 /* Switch between compressed and uncompressed file names */
293 if (zflag
&& !rflag
&& strncmp((sp
->namep
+ namlen
- 2), ".Z", (size_t)2)
294 && (namlen
<= (NAME_SIZE
- 2)))
295 strncat(cbuf
, ".Z", (size_t)2);
296 if (zflag
&& rflag
&& !strncmp((sp
->namep
+ namlen
- 2), ".Z", (size_t)2))
297 cbuf
[strlen(cbuf
) - 2] = '\0';
299 if (er
< 0 || sp
->modtime
> s
.st_mtime
) {
300 res
= copy(dir1
, sp
, cbuf
);
305 /* Check status of the copy. */
306 if (res
== OUT_OF_SPACE
) {
307 printf("Out of space while copying to %s\n", cbuf
);
308 /* We ran out of space copying a regular file. */
310 error(FATAL
, "Quitting, disk full", "", "");
312 /* If -m, ask for new diskette and continue. */
317 } else if (fmode
== S_IFDIR
) {
318 /* Directory. Execute this program recursively. */
319 copydir(dir1
, dir2
, sp
->namep
);
320 } else if (fmode
== S_IFBLK
|| fmode
== S_IFCHR
) {
322 strncpy(cbuf
, sp
->namep
, sizeof(cbuf
));
323 printf("%s is special file. Not backed up.\n", cbuf
);
332 struct sorted
*sp1
, *sp2
;
334 /* Swap two directory entries. */
344 int copy(dir1
, sp
, cbuf2
)
348 /* Copy a regular file. */
350 int fd1
, fd2
, nr
, nw
, res
, n
;
351 char cbuf1
[MAX_PATH
], *p
;
353 char *msg
= (rflag
|| strcmp(pname
, "backup")) ? "Restored" : "Backing up";
356 /* If the -j or -o or -s flags were given, suppress certain files. */
359 if (n
> NAME_SIZE
) n
= NAME_SIZE
;
361 if (strcmp(p
, "a.out") == 0) return(0);
362 if (strcmp(p
, "core") == 0) return (0);
363 if (strcmp(p
+ n
- 2, ".Z") == 0) return (0);
364 if (strcmp(p
+ n
- 4, ".bak") == 0) return (0);
365 if (strcmp(p
+ n
- 4, ".log") == 0) return (0);
368 if (strcmp(p
+ n
- 2, ".o") == 0) return(0);
371 if (strcmp(p
+ n
- 2, ".s") == 0) return(0);
374 if (dflag
) return(0); /* backup -d means only directories */
376 strncat(cbuf1
, "/", (size_t)1);
377 strncat(cbuf1
, sp
->namep
, (size_t)NAME_SIZE
); /* cbuf1 = source file name */
379 /* At this point, cbuf1 contains the source file name, cbuf2 the target. */
380 fd1
= open(cbuf1
, O_RDONLY
);
382 error(NONFATAL
, "cannot open ", cbuf1
, "");
385 fd2
= creat(cbuf2
, (sp
->mode
| S_IWUSR
) & 07777);
387 if (errno
== ENFILE
) {
389 return(OUT_OF_SPACE
);
391 error(NONFATAL
, "cannot create ", cbuf2
, "");
396 /* Both files are now open. Do the copying. */
397 if (!rflag
&& strncmp((sp
->namep
+ n
- 2), ".Z", (size_t)2) ||
398 rflag
&& !strncmp((sp
->namep
+ n
- 2), ".Z", (size_t)2)) {
399 if (zflag
&& (rflag
|| (n
<= (NAME_SIZE
- 2)))) {
402 res
= zcopy(cbuf1
, cbuf2
);
403 if (tflag
) utime(cbuf2
, (struct utimbuf
*) & (sp
->acctime
));
404 if (res
!= 0) unlink(cbuf2
); /* if error, get rid of the corpse */
406 if (vflag
&& res
== 0) printf("%s %s\n", msg
, cbuf1
);
408 if (vflag
&& res
== 0) {
409 printf("%-37.37s -> %-37.37s\n", cbuf1
, cbuf2
);
410 if (strlen(cbuf1
) > 37 || strlen(cbuf2
) > 37)
411 printf("%37.37s %37.37s\n",
412 (strlen(cbuf1
) > 37) ? (cbuf1
+ 37) : "",
413 (strlen(cbuf2
) > 37) ? (cbuf2
+ 37) : "");
420 nr
= read(fd1
, copybuf
, COPY_SIZE
);
423 error(NONFATAL
, "read error on ", cbuf1
, "");
427 nw
= write(fd2
, copybuf
, nr
);
429 if (errno
== ENOSPC
) {
430 /* Device is full. */
435 /* True write error. */
436 error(NONFATAL
, "write error on ", cbuf2
, "");
443 if (vflag
) printf("%s %s\n", msg
, cbuf1
);
446 printf("%-37.37s -> %-37.37s\n", cbuf1
, cbuf2
);
447 if (strlen(cbuf1
) > 37 || strlen(cbuf2
) > 37)
448 printf("%37.37s %37.37s\n",
449 (strlen(cbuf1
) > 37) ? (cbuf1
+ 37) : "",
450 (strlen(cbuf2
) > 37) ? (cbuf2
+ 37) : "");
458 if (tflag
) utime(cbuf2
, (struct utimbuf
*) & (sp
->acctime
));
467 int pid
, status
, res
, s
;
470 /* These flags go for compress and gzip. */
477 if ((pid
= fork()) < 0) error(FATAL
, "cannot fork", "", "");
481 /* Error codes 0 and 2 are ok, assume others mean disk is full. */
482 res
= (status
== 0 || status
== NO_SAVINGS
? 0 : OUT_OF_SPACE
);
485 /* Child must execute compress. */
487 s
= open(targ
, O_RDWR
);
488 if (s
< 0) error(FATAL
, "cannot write on ", "targ", "");
489 execle("/usr/bin/gzip", "gzip", fbuf
, src
, (char *)0, environ
);
490 execle("/usr/local/bin/gzip", "gzip", fbuf
, src
, (char *)0, environ
);
491 execle("/bin/compress", "compress", fbuf
, src
, (char *)0, environ
);
492 execle("/usr/bin/compress", "compress", fbuf
, src
, (char *)0, environ
);
493 error(FATAL
, "cannot exec gzip or compress", "", "");
499 void copydir(dir1
, dir2
, namep
)
500 char *dir1
, *dir2
, *namep
;
502 /* Copy a directory. */
505 char fbuf
[20], d1buf
[MAX_PATH
], d2buf
[MAX_PATH
];
507 if (nflag
) return; /* backup -n means no directories */
511 /* Handle directory copy by forking off 'backup' ! */
512 if (jflag
|| mflag
|| oflag
|| rflag
|| sflag
|| tflag
|| vflag
|| zflag
)
514 if (jflag
) strcat(fbuf
, "j");
515 if (mflag
) strcat(fbuf
, "m");
516 if (oflag
) strcat(fbuf
, "o");
517 if (rflag
) strcat(fbuf
, "r");
518 if (sflag
) strcat(fbuf
, "s");
519 if (tflag
) strcat(fbuf
, "t");
520 if (vflag
) strcat(fbuf
, "v");
521 if (zflag
) strcat(fbuf
, "z");
522 snprintf(d1buf
, sizeof(d1buf
), "%s/%s", dir1
, namep
);
523 snprintf(d2buf
, sizeof(d2buf
), "%s/%s", dir2
, namep
);
525 if ((pid
= fork()) < 0) error(FATAL
, "cannot fork", "", "");
527 /* Parent waits for child, then returns. */
532 if (fbuf
[0] == '-') {
533 execle(pname
, pname
, fbuf
, d1buf
, d2buf
, (char *) 0, environ
);
534 execle("/bin/backup", "backup", fbuf
, d1buf
, d2buf
, (char *)0,environ
);
535 execle("/usr/bin/backup","backup",fbuf
,d1buf
,d2buf
,(char *)0,environ
);
536 error(FATAL
, "cannot recursively exec backup", "", "");
538 execle(pname
, pname
, d1buf
, d2buf
, (char *) 0, environ
);
539 execle("/bin/backup", "backup", d1buf
, d2buf
, (char *)0,environ
);
540 execle("/usr/bin/backup","backup", d1buf
, d2buf
, (char *)0,environ
);
541 error(FATAL
, "cannot recursively exec backup", "", "");
548 /* Ask for a new diskette. A big problem is that this program does not
549 * know which device is being used and where it is mounted on. As an
550 * emergency solution, fork off a shell and ask the user to do the work.
555 printf("\nDiskette full. Please do the following:\n");
556 printf(" 1. Unmount the diskette using /etc/umount\n");
557 printf(" 2. Physically replace the diskette by the next one.\n");
558 printf(" 3. Mount the new diskette using /etc/mount\n");
559 printf(" 4. Type CTRL-D to return to the backup/restore program\n");
561 if ((pid
= fork()) < 0) error(FATAL
, "cannot fork", "", "");
564 maketarget(dir
); /* make the directory */
566 execle("/bin/sh", "sh", "-i", (char *) 0, environ
);
567 execle("/usr/bin/sh", "sh", "-i", (char *) 0, environ
);
568 error(FATAL
, "cannot execute shell to ask for new diskette", "", "");
574 fprintf(stderr
, "Usage: %s [-djmnorstvz] dir1 dir2\n", pname
);
579 void error(type
, s1
, s2
, s3
)
583 fprintf(stderr
, "%s: %s%s%s\n", pname
, s1
, s2
, s3
);
585 if (type
== NONFATAL
)