unstack, sort: cleanup and improvement
[minix.git] / commands / cp / cp.c
blob75cf43107401e8bb2e81880617bac2729ac4507b
1 /* cp 1.12 - copy files Author: Kees J. Bot
2 * mv - move files 20 Jul 1993
3 * rm - remove files
4 * ln - make a link
5 * cpdir - copy a directory tree (cp -psmr)
6 * clone - make a link farm (ln -fmr)
7 */
8 #define nil 0
9 #include <stdio.h>
10 #include <sys/types.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <stddef.h>
14 #include <unistd.h>
15 #include <fcntl.h>
16 #include <time.h>
17 #include <sys/stat.h>
18 #include <utime.h>
19 #include <dirent.h>
20 #include <errno.h>
21 #ifndef DEBUG
22 #define DEBUG 0
23 #define NDEBUG 1
24 #endif
25 #include <assert.h>
27 /* Copy files in this size chunks: */
28 #if __minix && !__minix_vmd
29 #define CHUNK (8192 * sizeof(char *))
30 #else
31 #define CHUNK (1024 << (sizeof(int) + sizeof(char *)))
32 #endif
35 #ifndef CONFORMING
36 #define CONFORMING 1 /* Precisely POSIX conforming. */
37 #endif
40 #define arraysize(a) (sizeof(a) / sizeof((a)[0]))
41 #define arraylimit(a) ((a) + arraysize(a))
43 char *prog_name; /* Call name of this program. */
44 int ex_code= 0; /* Final exit code. */
46 typedef enum identity { CP, MV, RM, LN, CPDIR, CLONE } identity_t;
47 typedef enum action { COPY, MOVE, REMOVE, LINK } action_t;
49 identity_t identity; /* How did the user call me? */
50 action_t action; /* Copying, moving, or linking. */
51 int pflag= 0; /* -p/-s: Make orginal and copy the same. */
52 int iflag= 0; /* -i: Interactive overwriting/deleting. */
53 int fflag= 0; /* -f: Force. */
54 int sflag= 0; /* -s: Make a symbolic link (ln/clone). */
55 int Sflag= 0; /* -S: Make a symlink if across devices. */
56 int mflag= 0; /* -m: Merge trees, no target dir trickery. */
57 int rflag= 0; /* -r/-R: Recursively copy a tree. */
58 int vflag= 0; /* -v: Verbose. */
59 int xflag= 0; /* -x: Don't traverse past mount points. */
60 int xdev= 0; /* Set when moving or linking cross-device. */
61 int expand= 0; /* Expand symlinks, ignore links. */
62 int conforming= CONFORMING; /* Sometimes standards are a pain. */
64 int fc_mask; /* File creation mask. */
65 int uid, gid; /* Effective uid & gid. */
66 int istty; /* Can have terminal input. */
68 #ifndef S_ISLNK
69 /* There were no symlinks in medieval times. */
70 #define S_ISLNK(mode) (0)
71 #define lstat stat
72 #define symlink(path1, path2) (errno= ENOSYS, -1)
73 #define readlink(path, buf, len) (errno= ENOSYS, -1)
74 #endif
76 void report(const char *label)
78 if (action == REMOVE && fflag) return;
79 fprintf(stderr, "%s: %s: %s\n", prog_name, label, strerror(errno));
80 ex_code= 1;
83 void fatal(const char *label)
85 report(label);
86 exit(1);
89 void report2(const char *src, const char *dst)
91 fprintf(stderr, "%s %s %s: %s\n", prog_name, src, dst, strerror(errno));
92 ex_code= 1;
95 #if DEBUG
96 size_t nchunks= 0; /* Number of allocated cells. */
97 #endif
99 void *allocate(void *mem, size_t size)
100 /* Like realloc, but with checking of the return value. */
102 #if DEBUG
103 if (mem == nil) nchunks++;
104 #endif
105 if ((mem= mem == nil ? malloc(size) : realloc(mem, size)) == nil)
106 fatal("malloc()");
107 return mem;
110 void deallocate(void *mem)
111 /* Release a chunk of memory. */
113 if (mem != nil) {
114 #if DEBUG
115 nchunks--;
116 #endif
117 free(mem);
121 typedef struct pathname {
122 char *path; /* The actual pathname. */
123 size_t idx; /* Index for the terminating null byte. */
124 size_t lim; /* Actual length of the path array. */
125 } pathname_t;
127 void path_init(pathname_t *pp)
128 /* Initialize a pathname to the null string. */
130 pp->path= allocate(nil, pp->lim= NAME_MAX + 2);
131 pp->path[pp->idx= 0]= 0;
134 void path_add(pathname_t *pp, const char *name)
135 /* Add a component to a pathname. */
137 size_t lim;
138 char *p;
140 lim= pp->idx + strlen(name) + 2;
142 if (lim > pp->lim) {
143 pp->lim= lim += lim/2; /* add an extra 50% growing space. */
145 pp->path= allocate(pp->path, lim);
148 p= pp->path + pp->idx;
149 if (p > pp->path && p[-1] != '/') *p++ = '/';
151 while (*name != 0) {
152 if (*name != '/' || p == pp->path || p[-1] != '/') *p++ = *name;
153 name++;
155 *p = 0;
156 pp->idx= p - pp->path;
159 void path_trunc(pathname_t *pp, size_t didx)
160 /* Delete part of a pathname to a remembered length. */
162 pp->path[pp->idx= didx]= 0;
165 #if DEBUG
166 const char *path_name(const pathname_t *pp)
167 /* Return the actual name as a C string. */
169 return pp->path;
172 size_t path_length(const pathname_t *pp)
173 /* The length of the pathname. */
175 return pp->idx;
178 void path_drop(pathname_t *pp)
179 /* Release the storage occupied by the pathname. */
181 deallocate(pp->path);
184 #else /* !DEBUG */
185 #define path_name(pp) ((const char *) (pp)->path)
186 #define path_length(pp) ((pp)->idx)
187 #define path_drop(pp) deallocate((void *) (pp)->path)
188 #endif /* !DEBUG */
190 char *basename(const char *path)
191 /* Return the last component of a pathname. (Note: declassifies a const
192 * char * just like strchr.
195 const char *p= path;
197 for (;;) {
198 while (*p == '/') p++; /* Trailing slashes? */
200 if (*p == 0) break;
202 path= p;
203 while (*p != 0 && *p != '/') p++; /* Skip component. */
205 return (char *) path;
208 int affirmative(void)
209 /* Get a yes/no answer from the suspecting user. */
211 int c;
212 int ok;
214 fflush(stdout);
215 fflush(stderr);
217 while ((c= getchar()) == ' ') {}
218 ok= (c == 'y' || c == 'Y');
219 while (c != EOF && c != '\n') c= getchar();
221 return ok;
224 int writable(const struct stat *stp)
225 /* True iff the file with the given attributes allows writing. (And we have
226 * a terminal to ask if ok to overwrite.)
229 if (!istty || uid == 0) return 1;
230 if (stp->st_uid == uid) return stp->st_mode & S_IWUSR;
231 if (stp->st_gid == gid) return stp->st_mode & S_IWGRP;
232 return stp->st_mode & S_IWOTH;
235 #ifndef PATH_MAX
236 #define PATH_MAX 1024
237 #endif
239 static char *link_islink(const struct stat *stp, const char *file)
241 /* Tell if a file, which stat(2) information in '*stp', has been seen
242 * earlier by this function under a different name. If not return a
243 * null pointer with errno set to ENOENT, otherwise return the name of
244 * the link. Return a null pointer with an error code in errno for any
245 * error, using E2BIG for a too long file name.
247 * Use link_islink(nil, nil) to reset all bookkeeping.
249 * Call for a file twice to delete it from the store.
252 typedef struct link { /* In-memory link store. */
253 struct link *next; /* Hash chain on inode number. */
254 ino_t ino; /* File's inode number. */
255 off_t off; /* Offset to more info in temp file. */
256 } link_t;
257 typedef struct dlink { /* On-disk link store. */
258 dev_t dev; /* Device number. */
259 char file[PATH_MAX]; /* Name of earlier seen link. */
260 } dlink_t;
261 static link_t *links[256]; /* Hash list of known links. */
262 static int tfd= -1; /* Temp file for file name storage. */
263 static dlink_t dlink;
264 link_t *lp, **plp;
265 size_t len;
266 off_t off;
268 if (file == nil) {
269 /* Reset everything. */
270 for (plp= links; plp < arraylimit(links); plp++) {
271 while ((lp= *plp) != nil) {
272 *plp= lp->next;
273 free(lp);
276 if (tfd != -1) close(tfd);
277 tfd= -1;
278 return nil;
281 /* The file must be a non-directory with more than one link. */
282 if (S_ISDIR(stp->st_mode) || stp->st_nlink <= 1) {
283 errno= ENOENT;
284 return nil;
287 plp= &links[stp->st_ino % arraysize(links)];
289 while ((lp= *plp) != nil) {
290 if (lp->ino == stp->st_ino) {
291 /* May have seen this link before. Get it and check. */
292 if (lseek(tfd, lp->off, SEEK_SET) == -1) return nil;
293 if (read(tfd, &dlink, sizeof(dlink)) < 0) return nil;
295 /* Only need to check the device number. */
296 if (dlink.dev == stp->st_dev) {
297 if (strcmp(file, dlink.file) == 0) {
298 /* Called twice. Forget about this link. */
299 *plp= lp->next;
300 free(lp);
301 errno= ENOENT;
302 return nil;
305 /* Return the name of the earlier link. */
306 return dlink.file;
309 plp= &lp->next;
312 /* First time I see this link. Add it to the store. */
313 if (tfd == -1) {
314 for (;;) {
315 char *tmp;
317 tmp= tmpnam(nil);
318 tfd= open(tmp, O_RDWR|O_CREAT|O_EXCL, 0600);
319 if (tfd < 0) {
320 if (errno != EEXIST) return nil;
321 } else {
322 (void) unlink(tmp);
323 break;
327 if ((len= strlen(file)) >= PATH_MAX) {
328 errno= E2BIG;
329 return nil;
332 dlink.dev= stp->st_dev;
333 strcpy(dlink.file, file);
334 len += offsetof(dlink_t, file) + 1;
335 if ((off= lseek(tfd, 0, SEEK_END)) == -1) return nil;
336 if (write(tfd, &dlink, len) != len) return nil;
338 if ((lp= malloc(sizeof(*lp))) == nil) return nil;
339 lp->next= nil;
340 lp->ino= stp->st_ino;
341 lp->off= off;
342 *plp= lp;
343 errno= ENOENT;
344 return nil;
347 int trylink(const char *src, const char *dst, const struct stat *srcst,
348 const struct stat *dstst)
349 /* Keep the link structure intact if src has been seen before. */
351 char *olddst;
352 int linked;
354 if (action == COPY && expand) return 0;
356 if ((olddst= link_islink(srcst, dst)) == nil) {
357 /* if (errno != ENOENT) ... */
358 return 0;
361 /* Try to link the file copied earlier to the new file. */
362 if (dstst->st_ino != 0) (void) unlink(dst);
364 if ((linked= (link(olddst, dst) == 0)) && vflag)
365 printf("ln %s ..\n", olddst);
367 return linked;
370 int copy(const char *src, const char *dst, struct stat *srcst,
371 struct stat *dstst)
372 /* Copy one file to another and copy (some of) the attributes. */
374 char buf[CHUNK];
375 int srcfd, dstfd;
376 ssize_t n;
378 assert(srcst->st_ino != 0);
380 if (dstst->st_ino == 0) {
381 /* The file doesn't exist yet. */
383 if (!S_ISREG(srcst->st_mode)) {
384 /* Making a new mode 666 regular file. */
385 srcst->st_mode= (S_IFREG | 0666) & fc_mask;
386 } else
387 if (!pflag && conforming) {
388 /* Making a new file copying mode with umask applied. */
389 srcst->st_mode &= fc_mask;
391 } else {
392 /* File exists, ask if ok to overwrite if '-i'. */
394 if (iflag || (action == MOVE && !fflag && !writable(dstst))) {
395 fprintf(stderr, "Overwrite %s? (mode = %03o) ",
396 dst, dstst->st_mode & 07777);
397 if (!affirmative()) return 0;
400 if (action == MOVE) {
401 /* Don't overwrite, remove first. */
402 if (unlink(dst) < 0 && errno != ENOENT) {
403 report(dst);
404 return 0;
406 } else {
407 /* Overwrite. */
408 if (!pflag) {
409 /* Keep the existing mode and ownership. */
410 srcst->st_mode= dstst->st_mode;
411 srcst->st_uid= dstst->st_uid;
412 srcst->st_gid= dstst->st_gid;
417 /* Keep the link structure if possible. */
418 if (trylink(src, dst, srcst, dstst)) return 1;
420 if ((srcfd= open(src, O_RDONLY)) < 0) {
421 report(src);
422 return 0;
425 dstfd= open(dst, O_WRONLY|O_CREAT|O_TRUNC, srcst->st_mode & 0777);
426 if (dstfd < 0 && fflag && errno == EACCES) {
427 /* Retry adding a "w" bit. */
428 (void) chmod(dst, dstst->st_mode | S_IWUSR);
429 dstfd= open(dst, O_WRONLY|O_CREAT|O_TRUNC, 0);
431 if (dstfd < 0 && fflag && errno == EACCES) {
432 /* Retry after trying to delete. */
433 (void) unlink(dst);
434 dstfd= open(dst, O_WRONLY|O_CREAT|O_TRUNC, 0);
436 if (dstfd < 0) {
437 report(dst);
438 close(srcfd);
439 return 0;
442 /* Get current parameters. */
443 if (fstat(dstfd, dstst) < 0) {
444 report(dst);
445 close(srcfd);
446 close(dstfd);
447 return 0;
450 /* Copy the little bytes themselves. */
451 while ((n= read(srcfd, buf, sizeof(buf))) > 0) {
452 char *bp = buf;
453 ssize_t r;
455 while (n > 0 && (r= write(dstfd, bp, n)) > 0) {
456 bp += r;
457 n -= r;
459 if (r <= 0) {
460 if (r == 0) {
461 fprintf(stderr,
462 "%s: Warning: EOF writing to %s\n",
463 prog_name, dst);
464 break;
466 fatal(dst);
470 if (n < 0) {
471 report(src);
472 close(srcfd);
473 close(dstfd);
474 return 0;
477 close(srcfd);
478 close(dstfd);
480 /* Copy the ownership. */
481 if ((pflag || !conforming)
482 && S_ISREG(dstst->st_mode)
483 && (dstst->st_uid != srcst->st_uid
484 || dstst->st_gid != srcst->st_gid)
486 if (chmod(dst, 0) == 0) dstst->st_mode&= ~07777;
487 if (chown(dst, srcst->st_uid, srcst->st_gid) < 0) {
488 if (errno != EPERM) {
489 report(dst);
490 return 0;
492 } else {
493 dstst->st_uid= srcst->st_uid;
494 dstst->st_gid= srcst->st_gid;
498 if (conforming && S_ISREG(dstst->st_mode)
499 && (dstst->st_uid != srcst->st_uid
500 || dstst->st_gid != srcst->st_gid)
502 /* Suid bits must be cleared in the holy name of
503 * security (and the assumed user stupidity).
505 srcst->st_mode&= ~06000;
508 /* Copy the mode. */
509 if (S_ISREG(dstst->st_mode) && dstst->st_mode != srcst->st_mode) {
510 if (chmod(dst, srcst->st_mode) < 0) {
511 if (errno != EPERM) {
512 report(dst);
513 return 0;
515 fprintf(stderr, "%s: Can't change the mode of %s\n",
516 prog_name, dst);
520 /* Copy the file modification time. */
521 if ((pflag || !conforming) && S_ISREG(dstst->st_mode)) {
522 struct utimbuf ut;
524 ut.actime= action == MOVE ? srcst->st_atime : time(nil);
525 ut.modtime= srcst->st_mtime;
526 if (utime(dst, &ut) < 0) {
527 if (errno != EPERM) {
528 report(dst);
529 return 0;
531 if (pflag) {
532 fprintf(stderr,
533 "%s: Can't set the time of %s\n",
534 prog_name, dst);
538 if (vflag) {
539 printf(action == COPY ? "cp %s ..\n" : "mv %s ..\n", src);
541 return 1;
544 void copy1(const char *src, const char *dst, struct stat *srcst,
545 struct stat *dstst)
546 /* Inspect the source file and then copy it. Treatment of symlinks and
547 * special files is a bit complicated. The filetype and link-structure are
548 * ignored if (expand && !rflag), symlinks and link-structure are ignored
549 * if (expand && rflag), everything is copied precisely if !expand.
552 int r, linked;
554 assert(srcst->st_ino != 0);
556 if (srcst->st_ino == dstst->st_ino && srcst->st_dev == dstst->st_dev) {
557 fprintf(stderr, "%s: can't copy %s onto itself\n",
558 prog_name, src);
559 ex_code= 1;
560 return;
563 /* You can forget it if the destination is a directory. */
564 if (dstst->st_ino != 0 && S_ISDIR(dstst->st_mode)) {
565 errno= EISDIR;
566 report(dst);
567 return;
570 if (S_ISREG(srcst->st_mode) || (expand && !rflag)) {
571 if (!copy(src, dst, srcst, dstst)) return;
573 if (action == MOVE && unlink(src) < 0) {
574 report(src);
575 return;
577 return;
580 if (dstst->st_ino != 0) {
581 if (iflag || (action == MOVE && !fflag && !writable(dstst))) {
582 fprintf(stderr, "Replace %s? (mode = %03o) ",
583 dst, dstst->st_mode & 07777);
584 if (!affirmative()) return;
586 if (unlink(dst) < 0) {
587 report(dst);
588 return;
590 dstst->st_ino= 0;
593 /* Apply the file creation mask if so required. */
594 if (!pflag && conforming) srcst->st_mode &= fc_mask;
596 linked= 0;
598 if (S_ISLNK(srcst->st_mode)) {
599 char buf[1024+1];
601 if ((r= readlink(src, buf, sizeof(buf)-1)) < 0) {
602 report(src);
603 return;
605 buf[r]= 0;
606 r= symlink(buf, dst);
607 if (vflag && r == 0)
608 printf("ln -s %s %s\n", buf, dst);
609 } else
610 if (trylink(src, dst, srcst, dstst)) {
611 linked= 1;
612 r= 0;
613 } else
614 if (S_ISFIFO(srcst->st_mode)) {
615 r= mkfifo(dst, srcst->st_mode);
616 if (vflag && r == 0)
617 printf("mkfifo %s\n", dst);
618 } else
619 if (S_ISBLK(srcst->st_mode) || S_ISCHR(srcst->st_mode)) {
620 r= mknod(dst, srcst->st_mode, srcst->st_rdev);
621 if (vflag && r == 0) {
622 printf("mknod %s %c %d %d\n",
623 dst,
624 S_ISBLK(srcst->st_mode) ? 'b' : 'c',
625 (srcst->st_rdev >> 8) & 0xFF,
626 (srcst->st_rdev >> 0) & 0xFF);
628 } else {
629 fprintf(stderr, "%s: %s: odd filetype %5o (not copied)\n",
630 prog_name, src, srcst->st_mode);
631 ex_code= 1;
632 return;
635 if (r < 0 || lstat(dst, dstst) < 0) {
636 report(dst);
637 return;
640 if (action == MOVE && unlink(src) < 0) {
641 report(src);
642 (void) unlink(dst); /* Don't want it twice. */
643 return;
646 if (linked) return;
648 if (S_ISLNK(srcst->st_mode)) return;
650 /* Copy the ownership. */
651 if ((pflag || !conforming)
652 && (dstst->st_uid != srcst->st_uid
653 || dstst->st_gid != srcst->st_gid)
655 if (chown(dst, srcst->st_uid, srcst->st_gid) < 0) {
656 if (errno != EPERM) {
657 report(dst);
658 return;
663 /* Copy the file modification time. */
664 if (pflag || !conforming) {
665 struct utimbuf ut;
667 ut.actime= action == MOVE ? srcst->st_atime : time(nil);
668 ut.modtime= srcst->st_mtime;
669 if (utime(dst, &ut) < 0) {
670 if (errno != EPERM) {
671 report(dst);
672 return;
674 fprintf(stderr, "%s: Can't set the time of %s\n",
675 prog_name, dst);
680 void remove1(const char *src, const struct stat *srcst)
682 if (iflag || (!fflag && !writable(srcst))) {
683 fprintf(stderr, "Remove %s? (mode = %03o) ", src,
684 srcst->st_mode & 07777);
685 if (!affirmative()) return;
687 if (unlink(src) < 0) {
688 report(src);
689 } else {
690 if (vflag) printf("rm %s\n", src);
694 void link1(const char *src, const char *dst, const struct stat *srcst,
695 const struct stat *dstst)
697 pathname_t sym;
698 const char *p;
700 if (dstst->st_ino != 0 && (iflag || fflag)) {
701 if (srcst->st_ino == dstst->st_ino) {
702 if (fflag) return;
703 fprintf(stderr, "%s: Can't link %s onto itself\n",
704 prog_name, src);
705 ex_code= 1;
706 return;
708 if (iflag) {
709 fprintf(stderr, "Remove %s? ", dst);
710 if (!affirmative()) return;
712 errno= EISDIR;
713 if (S_ISDIR(dstst->st_mode) || unlink(dst) < 0) {
714 report(dst);
715 return;
719 if (!sflag && !(rflag && S_ISLNK(srcst->st_mode)) && !(Sflag && xdev)) {
720 /* A normal link. */
721 if (link(src, dst) < 0) {
722 if (!Sflag || errno != EXDEV) {
723 report2(src, dst);
724 return;
726 /* Can't do a cross-device link, we have to symlink. */
727 xdev= 1;
728 } else {
729 if (vflag) printf("ln %s..\n", src);
730 return;
734 /* Do a symlink. */
735 if (!rflag && !Sflag) {
736 /* We can get away with a "don't care if it works" symlink. */
737 if (symlink(src, dst) < 0) {
738 report(dst);
739 return;
741 if (vflag) printf("ln -s %s %s\n", src, dst);
742 return;
745 /* If the source is a symlink then it is simply copied. */
746 if (S_ISLNK(srcst->st_mode)) {
747 int r;
748 char buf[1024+1];
750 if ((r= readlink(src, buf, sizeof(buf)-1)) < 0) {
751 report(src);
752 return;
754 buf[r]= 0;
755 if (symlink(buf, dst) < 0) {
756 report(dst);
757 return;
759 if (vflag) printf("ln -s %s %s\n", buf, dst);
760 return;
763 /* Make a symlink that has to work, i.e. we must be able to access the
764 * source now, and the link must work.
766 if (dst[0] == '/' && src[0] != '/') {
767 /* ln -[rsS] relative/path /full/path. */
768 fprintf(stderr,
769 "%s: Symlinking %s to %s is too difficult for me to figure out\n",
770 prog_name, src, dst);
771 exit(1);
774 /* Count the number of subdirectories in the destination file and
775 * add one '..' for each.
777 path_init(&sym);
778 if (src[0] != '/') {
779 p= dst;
780 while (*p != 0) {
781 if (p[0] == '.') {
782 if (p[1] == '/' || p[1] == 0) {
783 /* A "." component; skip. */
784 do p++; while (*p == '/');
785 continue;
786 } else
787 if (p[1] == '.' && (p[2] == '/' || p[2] == 0)) {
788 /* A ".." component; oops. */
789 switch (path_length(&sym)) {
790 case 0:
791 fprintf(stderr,
792 "%s: Symlinking %s to %s is too difficult for me to figure out\n",
793 prog_name, src, dst);
794 exit(1);
795 case 2:
796 path_trunc(&sym, 0);
797 break;
798 default:
799 path_trunc(&sym, path_length(&sym) - 3);
801 p++;
802 do p++; while (*p == '/');
803 continue;
806 while (*p != 0 && *p != '/') p++;
807 while (*p == '/') p++;
808 if (*p == 0) break;
809 path_add(&sym, "..");
812 path_add(&sym, src);
814 if (symlink(path_name(&sym), dst) < 0) {
815 report(dst);
816 } else {
817 if (vflag) printf("ln -s %s %s\n", path_name(&sym), dst);
819 path_drop(&sym);
822 typedef struct entrylist {
823 struct entrylist *next;
824 char *name;
825 } entrylist_t;
827 int eat_dir(const char *dir, entrylist_t **dlist)
828 /* Make a linked list of all the names in a directory. */
830 DIR *dp;
831 struct dirent *entry;
833 if ((dp= opendir(dir)) == nil) return 0;
835 while ((entry= readdir(dp)) != nil) {
836 if (strcmp(entry->d_name, ".") == 0) continue;
837 if (strcmp(entry->d_name, "..") == 0) continue;
839 *dlist= allocate(nil, sizeof(**dlist));
840 (*dlist)->name= allocate(nil, strlen(entry->d_name)+1);
841 strcpy((*dlist)->name, entry->d_name);
842 dlist= &(*dlist)->next;
844 closedir(dp);
845 *dlist= nil;
846 return 1;
849 void chop_dlist(entrylist_t **dlist)
850 /* Chop an entry of a name list. */
852 entrylist_t *junk= *dlist;
854 *dlist= junk->next;
855 deallocate(junk->name);
856 deallocate(junk);
859 void drop_dlist(entrylist_t *dlist)
860 /* Get rid of a whole list. */
862 while (dlist != nil) chop_dlist(&dlist);
865 void do1(pathname_t *src, pathname_t *dst, int depth)
866 /* Perform the appropriate action on a source and destination file. */
868 size_t slashsrc, slashdst;
869 struct stat srcst, dstst;
870 entrylist_t *dlist;
871 static ino_t topdst_ino;
872 static dev_t topdst_dev;
873 static dev_t topsrc_dev;
875 #if DEBUG
876 if (vflag && depth == 0) {
877 char flags[100], *pf= flags;
879 if (pflag) *pf++= 'p';
880 if (iflag) *pf++= 'i';
881 if (fflag) *pf++= 'f';
882 if (sflag) *pf++= 's';
883 if (Sflag) *pf++= 'S';
884 if (mflag) *pf++= 'm';
885 if (rflag) *pf++= 'r';
886 if (vflag) *pf++= 'v';
887 if (xflag) *pf++= 'x';
888 if (expand) *pf++= 'L';
889 if (conforming) *pf++= 'C';
890 *pf= 0;
891 printf(": %s -%s %s %s\n", prog_name, flags,
892 path_name(src), path_name(dst));
894 #endif
896 /* st_ino == 0 if not stat()'ed yet, or nonexistent. */
897 srcst.st_ino= 0;
898 dstst.st_ino= 0;
900 if (action != LINK || !sflag || rflag) {
901 /* Source must exist unless symlinking. */
902 if ((expand ? stat : lstat)(path_name(src), &srcst) < 0) {
903 report(path_name(src));
904 return;
908 if (depth == 0) {
909 /* First call: Not cross-device yet, first dst not seen yet,
910 * remember top device number.
912 xdev= 0;
913 topdst_ino= 0;
914 topsrc_dev= srcst.st_dev;
917 /* Inspect the intended destination unless removing. */
918 if (action != REMOVE) {
919 if ((expand ? stat : lstat)(path_name(dst), &dstst) < 0) {
920 if (errno != ENOENT) {
921 report(path_name(dst));
922 return;
927 if (action == MOVE && !xdev) {
928 if (dstst.st_ino != 0 && srcst.st_dev != dstst.st_dev) {
929 /* It's a cross-device rename, i.e. copy and remove. */
930 xdev= 1;
931 } else
932 if (!mflag || dstst.st_ino == 0 || !S_ISDIR(dstst.st_mode)) {
933 /* Try to simply rename the file (not merging trees). */
935 if (srcst.st_ino == dstst.st_ino) {
936 fprintf(stderr,
937 "%s: Can't move %s onto itself\n",
938 prog_name, path_name(src));
939 ex_code= 1;
940 return;
943 if (dstst.st_ino != 0) {
944 if (iflag || (!fflag && !writable(&dstst))) {
945 fprintf(stderr,
946 "Replace %s? (mode = %03o) ",
947 path_name(dst),
948 dstst.st_mode & 07777);
949 if (!affirmative()) return;
951 if (!S_ISDIR(dstst.st_mode))
952 (void) unlink(path_name(dst));
955 if (rename(path_name(src), path_name(dst)) == 0) {
956 /* Success. */
957 if (vflag) {
958 printf("mv %s %s\n", path_name(src),
959 path_name(dst));
961 return;
963 if (errno == EXDEV) {
964 xdev= 1;
965 } else {
966 report2(path_name(src), path_name(dst));
967 return;
972 if (srcst.st_ino == 0 || !S_ISDIR(srcst.st_mode)) {
973 /* Copy/move/remove/link a single file. */
974 switch (action) {
975 case COPY:
976 case MOVE:
977 copy1(path_name(src), path_name(dst), &srcst, &dstst);
978 break;
979 case REMOVE:
980 remove1(path_name(src), &srcst);
981 break;
982 case LINK:
983 link1(path_name(src), path_name(dst), &srcst, &dstst);
984 break;
986 return;
989 /* Recursively copy/move/remove/link a directory if -r or -R. */
990 if (!rflag) {
991 errno= EISDIR;
992 report(path_name(src));
993 return;
996 /* Ok to remove contents of dir? */
997 if (action == REMOVE) {
998 if (xflag && topsrc_dev != srcst.st_dev) {
999 /* Don't recurse past a mount point. */
1000 return;
1002 if (iflag) {
1003 fprintf(stderr, "Remove contents of %s? ", path_name(src));
1004 if (!affirmative()) return;
1008 /* Gather the names in the source directory. */
1009 if (!eat_dir(path_name(src), &dlist)) {
1010 report(path_name(src));
1011 return;
1014 /* Check/create the target directory. */
1015 if (action != REMOVE && dstst.st_ino != 0 && !S_ISDIR(dstst.st_mode)) {
1016 if (action != MOVE && !fflag) {
1017 errno= ENOTDIR;
1018 report(path_name(dst));
1019 return;
1021 if (iflag) {
1022 fprintf(stderr, "Replace %s? ", path_name(dst));
1023 if (!affirmative()) {
1024 drop_dlist(dlist);
1025 return;
1028 if (unlink(path_name(dst)) < 0) {
1029 report(path_name(dst));
1030 drop_dlist(dlist);
1031 return;
1033 dstst.st_ino= 0;
1036 if (action != REMOVE) {
1037 if (dstst.st_ino == 0) {
1038 /* Create a new target directory. */
1039 if (!pflag && conforming) srcst.st_mode&= fc_mask;
1041 if (mkdir(path_name(dst), srcst.st_mode | S_IRWXU) < 0
1042 || stat(path_name(dst), &dstst) < 0) {
1043 report(path_name(dst));
1044 drop_dlist(dlist);
1045 return;
1047 if (vflag) printf("mkdir %s\n", path_name(dst));
1048 } else {
1049 /* Target directory already exists. */
1050 if (action == MOVE && !mflag) {
1051 errno= EEXIST;
1052 report(path_name(dst));
1053 drop_dlist(dlist);
1054 return;
1056 if (!pflag) {
1057 /* Keep the existing attributes. */
1058 srcst.st_mode= dstst.st_mode;
1059 srcst.st_uid= dstst.st_uid;
1060 srcst.st_gid= dstst.st_gid;
1061 srcst.st_mtime= dstst.st_mtime;
1065 if (topdst_ino == 0) {
1066 /* Remember the top destination. */
1067 topdst_dev= dstst.st_dev;
1068 topdst_ino= dstst.st_ino;
1071 if (srcst.st_ino == topdst_ino && srcst.st_dev == topdst_dev) {
1072 /* E.g. cp -r /shallow /shallow/deep. */
1073 fprintf(stderr,
1074 "%s%s %s/ %s/: infinite recursion avoided\n",
1075 prog_name, action != MOVE ? " -r" : "",
1076 path_name(src), path_name(dst));
1077 drop_dlist(dlist);
1078 return;
1081 if (xflag && topsrc_dev != srcst.st_dev) {
1082 /* Don't recurse past a mount point. */
1083 drop_dlist(dlist);
1084 return;
1088 /* Go down. */
1089 slashsrc= path_length(src);
1090 slashdst= path_length(dst);
1092 while (dlist != nil) {
1093 path_add(src, dlist->name);
1094 if (action != REMOVE) path_add(dst, dlist->name);
1096 do1(src, dst, depth+1);
1098 path_trunc(src, slashsrc);
1099 path_trunc(dst, slashdst);
1100 chop_dlist(&dlist);
1103 if (action == MOVE || action == REMOVE) {
1104 /* The contents of the source directory should have
1105 * been (re)moved above. Get rid of the empty dir.
1107 if (action == REMOVE && iflag) {
1108 fprintf(stderr, "Remove directory %s? ",
1109 path_name(src));
1110 if (!affirmative()) return;
1112 if (rmdir(path_name(src)) < 0) {
1113 if (errno != ENOTEMPTY) report(path_name(src));
1114 return;
1116 if (vflag) printf("rmdir %s\n", path_name(src));
1119 if (action != REMOVE) {
1120 /* Set the attributes of a new directory. */
1121 struct utimbuf ut;
1123 /* Copy the ownership. */
1124 if ((pflag || !conforming)
1125 && (dstst.st_uid != srcst.st_uid
1126 || dstst.st_gid != srcst.st_gid)
1128 if (chown(path_name(dst), srcst.st_uid,
1129 srcst.st_gid) < 0) {
1130 if (errno != EPERM) {
1131 report(path_name(dst));
1132 return;
1137 /* Copy the mode. */
1138 if (dstst.st_mode != srcst.st_mode) {
1139 if (chmod(path_name(dst), srcst.st_mode) < 0) {
1140 report(path_name(dst));
1141 return;
1145 /* Copy the file modification time. */
1146 if (dstst.st_mtime != srcst.st_mtime) {
1147 ut.actime= action == MOVE ? srcst.st_atime : time(nil);
1148 ut.modtime= srcst.st_mtime;
1149 if (utime(path_name(dst), &ut) < 0) {
1150 if (errno != EPERM) {
1151 report(path_name(dst));
1152 return;
1154 fprintf(stderr,
1155 "%s: Can't set the time of %s\n",
1156 prog_name, path_name(dst));
1162 void usage(void)
1164 char *flags1, *flags2;
1166 switch (identity) {
1167 case CP:
1168 flags1= "pifsmrRvx";
1169 flags2= "pifsrRvx";
1170 break;
1171 case MV:
1172 flags1= "ifsmvx";
1173 flags2= "ifsvx";
1174 break;
1175 case RM:
1176 fprintf(stderr, "Usage: rm [-ifrRvx] file ...\n");
1177 exit(1);
1178 case LN:
1179 flags1= "ifsSmrRvx";
1180 flags2= "ifsSrRvx";
1181 break;
1182 case CPDIR:
1183 flags1= "ifvx";
1184 flags2= nil;
1185 break;
1186 case CLONE:
1187 flags1= "ifsSvx";
1188 flags2= nil;
1189 break;
1191 fprintf(stderr, "Usage: %s [-%s] file1 file2\n", prog_name, flags1);
1192 if (flags2 != nil)
1193 fprintf(stderr, " %s [-%s] file ... dir\n", prog_name, flags2);
1194 exit(1);
1197 int main(int argc, char **argv)
1199 int i;
1200 char *flags;
1201 struct stat st;
1202 pathname_t src, dst;
1203 size_t slash;
1205 #if DEBUG >= 3
1206 /* The first argument is the call name while debugging. */
1207 if (argc < 2) exit(-1);
1208 argv++;
1209 argc--;
1210 #endif
1211 #if DEBUG
1212 vflag= isatty(1);
1213 #endif
1215 /* Call name of this program. */
1216 prog_name= basename(argv[0]);
1218 /* Required action. */
1219 if (strcmp(prog_name, "cp") == 0) {
1220 identity= CP;
1221 action= COPY;
1222 flags= "pifsmrRvx";
1223 expand= 1;
1224 } else
1225 if (strcmp(prog_name, "mv") == 0) {
1226 identity= MV;
1227 action= MOVE;
1228 flags= "ifsmvx";
1229 rflag= pflag= 1;
1230 } else
1231 if (strcmp(prog_name, "rm") == 0) {
1232 identity= RM;
1233 action= REMOVE;
1234 flags= "ifrRvx";
1235 } else
1236 if (strcmp(prog_name, "ln") == 0) {
1237 identity= LN;
1238 action= LINK;
1239 flags= "ifsSmrRvx";
1240 } else
1241 if (strcmp(prog_name, "cpdir") == 0) {
1242 identity= CPDIR;
1243 action= COPY;
1244 flags= "pifsmrRvx";
1245 rflag= mflag= pflag= 1;
1246 conforming= 0;
1247 } else
1248 if (strcmp(prog_name, "clone") == 0) {
1249 identity= CLONE;
1250 action= LINK;
1251 flags= "ifsSmrRvx";
1252 rflag= mflag= fflag= 1;
1253 } else {
1254 fprintf(stderr,
1255 "%s: Identity crisis, not called cp, mv, rm, ln, cpdir, or clone\n",
1256 prog_name);
1257 exit(1);
1260 /* Who am I?, where am I?, how protective am I? */
1261 uid= geteuid();
1262 gid= getegid();
1263 istty= isatty(0);
1264 fc_mask= ~umask(0);
1266 /* Gather flags. */
1267 i= 1;
1268 while (i < argc && argv[i][0] == '-') {
1269 char *opt= argv[i++] + 1;
1271 if (opt[0] == '-' && opt[1] == 0) break; /* -- */
1273 while (*opt != 0) {
1274 /* Flag supported? */
1275 if (strchr(flags, *opt) == nil) usage();
1277 switch (*opt++) {
1278 case 'p':
1279 pflag= 1;
1280 break;
1281 case 'i':
1282 iflag= 1;
1283 if (action == MOVE) fflag= 0;
1284 break;
1285 case 'f':
1286 fflag= 1;
1287 if (action == MOVE) iflag= 0;
1288 break;
1289 case 's':
1290 if (action == LINK) {
1291 sflag= 1;
1292 } else {
1293 /* Forget about POSIX, do it right. */
1294 conforming= 0;
1296 break;
1297 case 'S':
1298 Sflag= 1;
1299 break;
1300 case 'm':
1301 mflag= 1;
1302 break;
1303 case 'r':
1304 expand= 0;
1305 /*FALL THROUGH*/
1306 case 'R':
1307 rflag= 1;
1308 break;
1309 case 'v':
1310 vflag= 1;
1311 break;
1312 case 'x':
1313 xflag= 1;
1314 break;
1315 default:
1316 assert(0);
1321 switch (action) {
1322 case REMOVE:
1323 if (i == argc) {
1324 if (fflag)
1325 exit(0);
1326 usage();
1328 break;
1329 case LINK:
1330 /* 'ln dir/file' is to be read as 'ln dir/file .'. */
1331 if ((argc - i) == 1 && action == LINK) argv[argc++]= ".";
1332 /*FALL THROUGH*/
1333 default:
1334 if ((argc - i) < 2) usage();
1337 path_init(&src);
1338 path_init(&dst);
1340 if (action != REMOVE && !mflag
1341 && stat(argv[argc-1], &st) >= 0 && S_ISDIR(st.st_mode)
1343 /* The last argument is a directory, this means we have to
1344 * throw the whole lot into this directory. This is the
1345 * Right Thing unless you use -r.
1347 path_add(&dst, argv[argc-1]);
1348 slash= path_length(&dst);
1350 do {
1351 path_add(&src, argv[i]);
1352 path_add(&dst, basename(argv[i]));
1354 do1(&src, &dst, 0);
1356 path_trunc(&src, 0);
1357 path_trunc(&dst, slash);
1358 } while (++i < argc-1);
1359 } else
1360 if (action == REMOVE || (argc - i) == 2) {
1361 /* Just two files (or many files for rm). */
1362 do {
1363 path_add(&src, argv[i]);
1364 if (action != REMOVE) path_add(&dst, argv[i+1]);
1366 do1(&src, &dst, 0);
1367 path_trunc(&src, 0);
1368 } while (action == REMOVE && ++i < argc);
1369 } else {
1370 usage();
1372 path_drop(&src);
1373 path_drop(&dst);
1375 #if DEBUG
1376 if (nchunks != 0) {
1377 fprintf(stderr, "(%ld chunks of memory not freed)\n",
1378 (long) nchunks);
1380 #endif
1381 exit(ex_code);
1382 return ex_code;