<sys/syslimits.h>
[minix3.git] / commands / cp / cp.c
blob470a94bd75e2471d7c1fb3e4190d75e3559a6d05
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 <sys/syslimits.h>
19 #include <utime.h>
20 #include <dirent.h>
21 #include <errno.h>
22 #ifndef DEBUG
23 #define DEBUG 0
24 #define NDEBUG 1
25 #endif
26 #include <assert.h>
28 /* Copy files in this size chunks: */
29 #if __minix && !__minix_vmd
30 #define CHUNK (8192 * sizeof(char *))
31 #else
32 #define CHUNK (1024 << (sizeof(int) + sizeof(char *)))
33 #endif
36 #ifndef CONFORMING
37 #define CONFORMING 1 /* Precisely POSIX conforming. */
38 #endif
41 #define arraysize(a) (sizeof(a) / sizeof((a)[0]))
42 #define arraylimit(a) ((a) + arraysize(a))
44 char *prog_name; /* Call name of this program. */
45 int ex_code= 0; /* Final exit code. */
47 typedef enum identity { CP, MV, RM, LN, CPDIR, CLONE } identity_t;
48 typedef enum action { COPY, MOVE, REMOVE, LINK } action_t;
50 identity_t identity; /* How did the user call me? */
51 action_t action; /* Copying, moving, or linking. */
52 int pflag= 0; /* -p/-s: Make orginal and copy the same. */
53 int iflag= 0; /* -i: Interactive overwriting/deleting. */
54 int fflag= 0; /* -f: Force. */
55 int sflag= 0; /* -s: Make a symbolic link (ln/clone). */
56 int Sflag= 0; /* -S: Make a symlink if across devices. */
57 int mflag= 0; /* -m: Merge trees, no target dir trickery. */
58 int rflag= 0; /* -r/-R: Recursively copy a tree. */
59 int vflag= 0; /* -v: Verbose. */
60 int xflag= 0; /* -x: Don't traverse past mount points. */
61 int xdev= 0; /* Set when moving or linking cross-device. */
62 int expand= 0; /* Expand symlinks, ignore links. */
63 int conforming= CONFORMING; /* Sometimes standards are a pain. */
65 int fc_mask; /* File creation mask. */
66 int uid, gid; /* Effective uid & gid. */
67 int istty; /* Can have terminal input. */
69 #ifndef S_ISLNK
70 /* There were no symlinks in medieval times. */
71 #define S_ISLNK(mode) (0)
72 #define lstat stat
73 #define symlink(path1, path2) (errno= ENOSYS, -1)
74 #define readlink(path, buf, len) (errno= ENOSYS, -1)
75 #endif
77 void report(const char *label)
79 if (action == REMOVE && fflag) return;
80 fprintf(stderr, "%s: %s: %s\n", prog_name, label, strerror(errno));
81 ex_code= 1;
84 void fatal(const char *label)
86 report(label);
87 exit(1);
90 void report2(const char *src, const char *dst)
92 fprintf(stderr, "%s %s %s: %s\n", prog_name, src, dst, strerror(errno));
93 ex_code= 1;
96 #if DEBUG
97 size_t nchunks= 0; /* Number of allocated cells. */
98 #endif
100 void *allocate(void *mem, size_t size)
101 /* Like realloc, but with checking of the return value. */
103 #if DEBUG
104 if (mem == nil) nchunks++;
105 #endif
106 if ((mem= mem == nil ? malloc(size) : realloc(mem, size)) == nil)
107 fatal("malloc()");
108 return mem;
111 void deallocate(void *mem)
112 /* Release a chunk of memory. */
114 if (mem != nil) {
115 #if DEBUG
116 nchunks--;
117 #endif
118 free(mem);
122 typedef struct pathname {
123 char *path; /* The actual pathname. */
124 size_t idx; /* Index for the terminating null byte. */
125 size_t lim; /* Actual length of the path array. */
126 } pathname_t;
128 void path_init(pathname_t *pp)
129 /* Initialize a pathname to the null string. */
131 pp->path= allocate(nil, pp->lim= NAME_MAX + 2);
132 pp->path[pp->idx= 0]= 0;
135 void path_add(pathname_t *pp, const char *name)
136 /* Add a component to a pathname. */
138 size_t lim;
139 char *p;
141 lim= pp->idx + strlen(name) + 2;
143 if (lim > pp->lim) {
144 pp->lim= lim += lim/2; /* add an extra 50% growing space. */
146 pp->path= allocate(pp->path, lim);
149 p= pp->path + pp->idx;
150 if (p > pp->path && p[-1] != '/') *p++ = '/';
152 while (*name != 0) {
153 if (*name != '/' || p == pp->path || p[-1] != '/') *p++ = *name;
154 name++;
156 *p = 0;
157 pp->idx= p - pp->path;
160 void path_trunc(pathname_t *pp, size_t didx)
161 /* Delete part of a pathname to a remembered length. */
163 pp->path[pp->idx= didx]= 0;
166 #if DEBUG
167 const char *path_name(const pathname_t *pp)
168 /* Return the actual name as a C string. */
170 return pp->path;
173 size_t path_length(const pathname_t *pp)
174 /* The length of the pathname. */
176 return pp->idx;
179 void path_drop(pathname_t *pp)
180 /* Release the storage occupied by the pathname. */
182 deallocate(pp->path);
185 #else /* !DEBUG */
186 #define path_name(pp) ((const char *) (pp)->path)
187 #define path_length(pp) ((pp)->idx)
188 #define path_drop(pp) deallocate((void *) (pp)->path)
189 #endif /* !DEBUG */
191 char *basename(const char *path)
192 /* Return the last component of a pathname. (Note: declassifies a const
193 * char * just like strchr.
196 const char *p= path;
198 for (;;) {
199 while (*p == '/') p++; /* Trailing slashes? */
201 if (*p == 0) break;
203 path= p;
204 while (*p != 0 && *p != '/') p++; /* Skip component. */
206 return (char *) path;
209 int affirmative(void)
210 /* Get a yes/no answer from the suspecting user. */
212 int c;
213 int ok;
215 fflush(stdout);
216 fflush(stderr);
218 while ((c= getchar()) == ' ') {}
219 ok= (c == 'y' || c == 'Y');
220 while (c != EOF && c != '\n') c= getchar();
222 return ok;
225 int writable(const struct stat *stp)
226 /* True iff the file with the given attributes allows writing. (And we have
227 * a terminal to ask if ok to overwrite.)
230 if (!istty || uid == 0) return 1;
231 if (stp->st_uid == uid) return stp->st_mode & S_IWUSR;
232 if (stp->st_gid == gid) return stp->st_mode & S_IWGRP;
233 return stp->st_mode & S_IWOTH;
236 #ifndef PATH_MAX
237 #define PATH_MAX 1024
238 #endif
240 static char *link_islink(const struct stat *stp, const char *file)
242 /* Tell if a file, which stat(2) information in '*stp', has been seen
243 * earlier by this function under a different name. If not return a
244 * null pointer with errno set to ENOENT, otherwise return the name of
245 * the link. Return a null pointer with an error code in errno for any
246 * error, using E2BIG for a too long file name.
248 * Use link_islink(nil, nil) to reset all bookkeeping.
250 * Call for a file twice to delete it from the store.
253 typedef struct link { /* In-memory link store. */
254 struct link *next; /* Hash chain on inode number. */
255 ino_t ino; /* File's inode number. */
256 off_t off; /* Offset to more info in temp file. */
257 } link_t;
258 typedef struct dlink { /* On-disk link store. */
259 dev_t dev; /* Device number. */
260 char file[PATH_MAX]; /* Name of earlier seen link. */
261 } dlink_t;
262 static link_t *links[256]; /* Hash list of known links. */
263 static int tfd= -1; /* Temp file for file name storage. */
264 static dlink_t dlink;
265 link_t *lp, **plp;
266 size_t len;
267 off_t off;
269 if (file == nil) {
270 /* Reset everything. */
271 for (plp= links; plp < arraylimit(links); plp++) {
272 while ((lp= *plp) != nil) {
273 *plp= lp->next;
274 free(lp);
277 if (tfd != -1) close(tfd);
278 tfd= -1;
279 return nil;
282 /* The file must be a non-directory with more than one link. */
283 if (S_ISDIR(stp->st_mode) || stp->st_nlink <= 1) {
284 errno= ENOENT;
285 return nil;
288 plp= &links[stp->st_ino % arraysize(links)];
290 while ((lp= *plp) != nil) {
291 if (lp->ino == stp->st_ino) {
292 /* May have seen this link before. Get it and check. */
293 if (lseek(tfd, lp->off, SEEK_SET) == -1) return nil;
294 if (read(tfd, &dlink, sizeof(dlink)) < 0) return nil;
296 /* Only need to check the device number. */
297 if (dlink.dev == stp->st_dev) {
298 if (strcmp(file, dlink.file) == 0) {
299 /* Called twice. Forget about this link. */
300 *plp= lp->next;
301 free(lp);
302 errno= ENOENT;
303 return nil;
306 /* Return the name of the earlier link. */
307 return dlink.file;
310 plp= &lp->next;
313 /* First time I see this link. Add it to the store. */
314 if (tfd == -1) {
315 for (;;) {
316 char *tmp;
318 tmp= tmpnam(nil);
319 tfd= open(tmp, O_RDWR|O_CREAT|O_EXCL, 0600);
320 if (tfd < 0) {
321 if (errno != EEXIST) return nil;
322 } else {
323 (void) unlink(tmp);
324 break;
328 if ((len= strlen(file)) >= PATH_MAX) {
329 errno= E2BIG;
330 return nil;
333 dlink.dev= stp->st_dev;
334 strcpy(dlink.file, file);
335 len += offsetof(dlink_t, file) + 1;
336 if ((off= lseek(tfd, 0, SEEK_END)) == -1) return nil;
337 if (write(tfd, &dlink, len) != len) return nil;
339 if ((lp= malloc(sizeof(*lp))) == nil) return nil;
340 lp->next= nil;
341 lp->ino= stp->st_ino;
342 lp->off= off;
343 *plp= lp;
344 errno= ENOENT;
345 return nil;
348 int trylink(const char *src, const char *dst, const struct stat *srcst,
349 const struct stat *dstst)
350 /* Keep the link structure intact if src has been seen before. */
352 char *olddst;
353 int linked;
355 if (action == COPY && expand) return 0;
357 if ((olddst= link_islink(srcst, dst)) == nil) {
358 /* if (errno != ENOENT) ... */
359 return 0;
362 /* Try to link the file copied earlier to the new file. */
363 if (dstst->st_ino != 0) (void) unlink(dst);
365 if ((linked= (link(olddst, dst) == 0)) && vflag)
366 printf("ln %s ..\n", olddst);
368 return linked;
371 int copy(const char *src, const char *dst, struct stat *srcst,
372 struct stat *dstst)
373 /* Copy one file to another and copy (some of) the attributes. */
375 char buf[CHUNK];
376 int srcfd, dstfd;
377 ssize_t n;
379 assert(srcst->st_ino != 0);
381 if (dstst->st_ino == 0) {
382 /* The file doesn't exist yet. */
384 if (!S_ISREG(srcst->st_mode)) {
385 /* Making a new mode 666 regular file. */
386 srcst->st_mode= (S_IFREG | 0666) & fc_mask;
387 } else
388 if (!pflag && conforming) {
389 /* Making a new file copying mode with umask applied. */
390 srcst->st_mode &= fc_mask;
392 } else {
393 /* File exists, ask if ok to overwrite if '-i'. */
395 if (iflag || (action == MOVE && !fflag && !writable(dstst))) {
396 fprintf(stderr, "Overwrite %s? (mode = %03o) ",
397 dst, dstst->st_mode & 07777);
398 if (!affirmative()) return 0;
401 if (action == MOVE) {
402 /* Don't overwrite, remove first. */
403 if (unlink(dst) < 0 && errno != ENOENT) {
404 report(dst);
405 return 0;
407 } else {
408 /* Overwrite. */
409 if (!pflag) {
410 /* Keep the existing mode and ownership. */
411 srcst->st_mode= dstst->st_mode;
412 srcst->st_uid= dstst->st_uid;
413 srcst->st_gid= dstst->st_gid;
418 /* Keep the link structure if possible. */
419 if (trylink(src, dst, srcst, dstst)) return 1;
421 if ((srcfd= open(src, O_RDONLY)) < 0) {
422 report(src);
423 return 0;
426 dstfd= open(dst, O_WRONLY|O_CREAT|O_TRUNC, srcst->st_mode & 0777);
427 if (dstfd < 0 && fflag && errno == EACCES) {
428 /* Retry adding a "w" bit. */
429 (void) chmod(dst, dstst->st_mode | S_IWUSR);
430 dstfd= open(dst, O_WRONLY|O_CREAT|O_TRUNC, 0);
432 if (dstfd < 0 && fflag && errno == EACCES) {
433 /* Retry after trying to delete. */
434 (void) unlink(dst);
435 dstfd= open(dst, O_WRONLY|O_CREAT|O_TRUNC, 0);
437 if (dstfd < 0) {
438 report(dst);
439 close(srcfd);
440 return 0;
443 /* Get current parameters. */
444 if (fstat(dstfd, dstst) < 0) {
445 report(dst);
446 close(srcfd);
447 close(dstfd);
448 return 0;
451 /* Copy the little bytes themselves. */
452 while ((n= read(srcfd, buf, sizeof(buf))) > 0) {
453 char *bp = buf;
454 ssize_t r;
456 while (n > 0 && (r= write(dstfd, bp, n)) > 0) {
457 bp += r;
458 n -= r;
460 if (r <= 0) {
461 if (r == 0) {
462 fprintf(stderr,
463 "%s: Warning: EOF writing to %s\n",
464 prog_name, dst);
465 break;
467 fatal(dst);
471 if (n < 0) {
472 report(src);
473 close(srcfd);
474 close(dstfd);
475 return 0;
478 close(srcfd);
479 close(dstfd);
481 /* Copy the ownership. */
482 if ((pflag || !conforming)
483 && S_ISREG(dstst->st_mode)
484 && (dstst->st_uid != srcst->st_uid
485 || dstst->st_gid != srcst->st_gid)
487 if (chmod(dst, 0) == 0) dstst->st_mode&= ~07777;
488 if (chown(dst, srcst->st_uid, srcst->st_gid) < 0) {
489 if (errno != EPERM) {
490 report(dst);
491 return 0;
493 } else {
494 dstst->st_uid= srcst->st_uid;
495 dstst->st_gid= srcst->st_gid;
499 if (conforming && S_ISREG(dstst->st_mode)
500 && (dstst->st_uid != srcst->st_uid
501 || dstst->st_gid != srcst->st_gid)
503 /* Suid bits must be cleared in the holy name of
504 * security (and the assumed user stupidity).
506 srcst->st_mode&= ~06000;
509 /* Copy the mode. */
510 if (S_ISREG(dstst->st_mode) && dstst->st_mode != srcst->st_mode) {
511 if (chmod(dst, srcst->st_mode) < 0) {
512 if (errno != EPERM) {
513 report(dst);
514 return 0;
516 fprintf(stderr, "%s: Can't change the mode of %s\n",
517 prog_name, dst);
521 /* Copy the file modification time. */
522 if ((pflag || !conforming) && S_ISREG(dstst->st_mode)) {
523 struct utimbuf ut;
525 ut.actime= action == MOVE ? srcst->st_atime : time(nil);
526 ut.modtime= srcst->st_mtime;
527 if (utime(dst, &ut) < 0) {
528 if (errno != EPERM) {
529 report(dst);
530 return 0;
532 if (pflag) {
533 fprintf(stderr,
534 "%s: Can't set the time of %s\n",
535 prog_name, dst);
539 if (vflag) {
540 printf(action == COPY ? "cp %s ..\n" : "mv %s ..\n", src);
542 return 1;
545 void copy1(const char *src, const char *dst, struct stat *srcst,
546 struct stat *dstst)
547 /* Inspect the source file and then copy it. Treatment of symlinks and
548 * special files is a bit complicated. The filetype and link-structure are
549 * ignored if (expand && !rflag), symlinks and link-structure are ignored
550 * if (expand && rflag), everything is copied precisely if !expand.
553 int r, linked;
555 assert(srcst->st_ino != 0);
557 if (srcst->st_ino == dstst->st_ino && srcst->st_dev == dstst->st_dev) {
558 fprintf(stderr, "%s: can't copy %s onto itself\n",
559 prog_name, src);
560 ex_code= 1;
561 return;
564 /* You can forget it if the destination is a directory. */
565 if (dstst->st_ino != 0 && S_ISDIR(dstst->st_mode)) {
566 errno= EISDIR;
567 report(dst);
568 return;
571 if (S_ISREG(srcst->st_mode) || (expand && !rflag)) {
572 if (!copy(src, dst, srcst, dstst)) return;
574 if (action == MOVE && unlink(src) < 0) {
575 report(src);
576 return;
578 return;
581 if (dstst->st_ino != 0) {
582 if (iflag || (action == MOVE && !fflag && !writable(dstst))) {
583 fprintf(stderr, "Replace %s? (mode = %03o) ",
584 dst, dstst->st_mode & 07777);
585 if (!affirmative()) return;
587 if (unlink(dst) < 0) {
588 report(dst);
589 return;
591 dstst->st_ino= 0;
594 /* Apply the file creation mask if so required. */
595 if (!pflag && conforming) srcst->st_mode &= fc_mask;
597 linked= 0;
599 if (S_ISLNK(srcst->st_mode)) {
600 char buf[1024+1];
602 if ((r= readlink(src, buf, sizeof(buf)-1)) < 0) {
603 report(src);
604 return;
606 buf[r]= 0;
607 r= symlink(buf, dst);
608 if (vflag && r == 0)
609 printf("ln -s %s %s\n", buf, dst);
610 } else
611 if (trylink(src, dst, srcst, dstst)) {
612 linked= 1;
613 r= 0;
614 } else
615 if (S_ISFIFO(srcst->st_mode)) {
616 r= mkfifo(dst, srcst->st_mode);
617 if (vflag && r == 0)
618 printf("mkfifo %s\n", dst);
619 } else
620 if (S_ISBLK(srcst->st_mode) || S_ISCHR(srcst->st_mode)) {
621 r= mknod(dst, srcst->st_mode, srcst->st_rdev);
622 if (vflag && r == 0) {
623 printf("mknod %s %c %d %d\n",
624 dst,
625 S_ISBLK(srcst->st_mode) ? 'b' : 'c',
626 (srcst->st_rdev >> 8) & 0xFF,
627 (srcst->st_rdev >> 0) & 0xFF);
629 } else {
630 fprintf(stderr, "%s: %s: odd filetype %5o (not copied)\n",
631 prog_name, src, srcst->st_mode);
632 ex_code= 1;
633 return;
636 if (r < 0 || lstat(dst, dstst) < 0) {
637 report(dst);
638 return;
641 if (action == MOVE && unlink(src) < 0) {
642 report(src);
643 (void) unlink(dst); /* Don't want it twice. */
644 return;
647 if (linked) return;
649 if (S_ISLNK(srcst->st_mode)) return;
651 /* Copy the ownership. */
652 if ((pflag || !conforming)
653 && (dstst->st_uid != srcst->st_uid
654 || dstst->st_gid != srcst->st_gid)
656 if (chown(dst, srcst->st_uid, srcst->st_gid) < 0) {
657 if (errno != EPERM) {
658 report(dst);
659 return;
664 /* Copy the file modification time. */
665 if (pflag || !conforming) {
666 struct utimbuf ut;
668 ut.actime= action == MOVE ? srcst->st_atime : time(nil);
669 ut.modtime= srcst->st_mtime;
670 if (utime(dst, &ut) < 0) {
671 if (errno != EPERM) {
672 report(dst);
673 return;
675 fprintf(stderr, "%s: Can't set the time of %s\n",
676 prog_name, dst);
681 void remove1(const char *src, const struct stat *srcst)
683 if (iflag || (!fflag && !writable(srcst))) {
684 fprintf(stderr, "Remove %s? (mode = %03o) ", src,
685 srcst->st_mode & 07777);
686 if (!affirmative()) return;
688 if (unlink(src) < 0) {
689 report(src);
690 } else {
691 if (vflag) printf("rm %s\n", src);
695 void link1(const char *src, const char *dst, const struct stat *srcst,
696 const struct stat *dstst)
698 pathname_t sym;
699 const char *p;
701 if (dstst->st_ino != 0 && (iflag || fflag)) {
702 if (srcst->st_ino == dstst->st_ino) {
703 if (fflag) return;
704 fprintf(stderr, "%s: Can't link %s onto itself\n",
705 prog_name, src);
706 ex_code= 1;
707 return;
709 if (iflag) {
710 fprintf(stderr, "Remove %s? ", dst);
711 if (!affirmative()) return;
713 errno= EISDIR;
714 if (S_ISDIR(dstst->st_mode) || unlink(dst) < 0) {
715 report(dst);
716 return;
720 if (!sflag && !(rflag && S_ISLNK(srcst->st_mode)) && !(Sflag && xdev)) {
721 /* A normal link. */
722 if (link(src, dst) < 0) {
723 if (!Sflag || errno != EXDEV) {
724 report2(src, dst);
725 return;
727 /* Can't do a cross-device link, we have to symlink. */
728 xdev= 1;
729 } else {
730 if (vflag) printf("ln %s..\n", src);
731 return;
735 /* Do a symlink. */
736 if (!rflag && !Sflag) {
737 /* We can get away with a "don't care if it works" symlink. */
738 if (symlink(src, dst) < 0) {
739 report(dst);
740 return;
742 if (vflag) printf("ln -s %s %s\n", src, dst);
743 return;
746 /* If the source is a symlink then it is simply copied. */
747 if (S_ISLNK(srcst->st_mode)) {
748 int r;
749 char buf[1024+1];
751 if ((r= readlink(src, buf, sizeof(buf)-1)) < 0) {
752 report(src);
753 return;
755 buf[r]= 0;
756 if (symlink(buf, dst) < 0) {
757 report(dst);
758 return;
760 if (vflag) printf("ln -s %s %s\n", buf, dst);
761 return;
764 /* Make a symlink that has to work, i.e. we must be able to access the
765 * source now, and the link must work.
767 if (dst[0] == '/' && src[0] != '/') {
768 /* ln -[rsS] relative/path /full/path. */
769 fprintf(stderr,
770 "%s: Symlinking %s to %s is too difficult for me to figure out\n",
771 prog_name, src, dst);
772 exit(1);
775 /* Count the number of subdirectories in the destination file and
776 * add one '..' for each.
778 path_init(&sym);
779 if (src[0] != '/') {
780 p= dst;
781 while (*p != 0) {
782 if (p[0] == '.') {
783 if (p[1] == '/' || p[1] == 0) {
784 /* A "." component; skip. */
785 do p++; while (*p == '/');
786 continue;
787 } else
788 if (p[1] == '.' && (p[2] == '/' || p[2] == 0)) {
789 /* A ".." component; oops. */
790 switch (path_length(&sym)) {
791 case 0:
792 fprintf(stderr,
793 "%s: Symlinking %s to %s is too difficult for me to figure out\n",
794 prog_name, src, dst);
795 exit(1);
796 case 2:
797 path_trunc(&sym, 0);
798 break;
799 default:
800 path_trunc(&sym, path_length(&sym) - 3);
802 p++;
803 do p++; while (*p == '/');
804 continue;
807 while (*p != 0 && *p != '/') p++;
808 while (*p == '/') p++;
809 if (*p == 0) break;
810 path_add(&sym, "..");
813 path_add(&sym, src);
815 if (symlink(path_name(&sym), dst) < 0) {
816 report(dst);
817 } else {
818 if (vflag) printf("ln -s %s %s\n", path_name(&sym), dst);
820 path_drop(&sym);
823 typedef struct entrylist {
824 struct entrylist *next;
825 char *name;
826 } entrylist_t;
828 int eat_dir(const char *dir, entrylist_t **dlist)
829 /* Make a linked list of all the names in a directory. */
831 DIR *dp;
832 struct dirent *entry;
834 if ((dp= opendir(dir)) == nil) return 0;
836 while ((entry= readdir(dp)) != nil) {
837 if (strcmp(entry->d_name, ".") == 0) continue;
838 if (strcmp(entry->d_name, "..") == 0) continue;
840 *dlist= allocate(nil, sizeof(**dlist));
841 (*dlist)->name= allocate(nil, strlen(entry->d_name)+1);
842 strcpy((*dlist)->name, entry->d_name);
843 dlist= &(*dlist)->next;
845 closedir(dp);
846 *dlist= nil;
847 return 1;
850 void chop_dlist(entrylist_t **dlist)
851 /* Chop an entry of a name list. */
853 entrylist_t *junk= *dlist;
855 *dlist= junk->next;
856 deallocate(junk->name);
857 deallocate(junk);
860 void drop_dlist(entrylist_t *dlist)
861 /* Get rid of a whole list. */
863 while (dlist != nil) chop_dlist(&dlist);
866 void do1(pathname_t *src, pathname_t *dst, int depth)
867 /* Perform the appropriate action on a source and destination file. */
869 size_t slashsrc, slashdst;
870 struct stat srcst, dstst;
871 entrylist_t *dlist;
872 static ino_t topdst_ino;
873 static dev_t topdst_dev;
874 static dev_t topsrc_dev;
876 #if DEBUG
877 if (vflag && depth == 0) {
878 char flags[100], *pf= flags;
880 if (pflag) *pf++= 'p';
881 if (iflag) *pf++= 'i';
882 if (fflag) *pf++= 'f';
883 if (sflag) *pf++= 's';
884 if (Sflag) *pf++= 'S';
885 if (mflag) *pf++= 'm';
886 if (rflag) *pf++= 'r';
887 if (vflag) *pf++= 'v';
888 if (xflag) *pf++= 'x';
889 if (expand) *pf++= 'L';
890 if (conforming) *pf++= 'C';
891 *pf= 0;
892 printf(": %s -%s %s %s\n", prog_name, flags,
893 path_name(src), path_name(dst));
895 #endif
897 /* st_ino == 0 if not stat()'ed yet, or nonexistent. */
898 srcst.st_ino= 0;
899 dstst.st_ino= 0;
901 if (action != LINK || !sflag || rflag) {
902 /* Source must exist unless symlinking. */
903 if ((expand ? stat : lstat)(path_name(src), &srcst) < 0) {
904 report(path_name(src));
905 return;
909 if (depth == 0) {
910 /* First call: Not cross-device yet, first dst not seen yet,
911 * remember top device number.
913 xdev= 0;
914 topdst_ino= 0;
915 topsrc_dev= srcst.st_dev;
918 /* Inspect the intended destination unless removing. */
919 if (action != REMOVE) {
920 if ((expand ? stat : lstat)(path_name(dst), &dstst) < 0) {
921 if (errno != ENOENT) {
922 report(path_name(dst));
923 return;
928 if (action == MOVE && !xdev) {
929 if (dstst.st_ino != 0 && srcst.st_dev != dstst.st_dev) {
930 /* It's a cross-device rename, i.e. copy and remove. */
931 xdev= 1;
932 } else
933 if (!mflag || dstst.st_ino == 0 || !S_ISDIR(dstst.st_mode)) {
934 /* Try to simply rename the file (not merging trees). */
936 if (srcst.st_ino == dstst.st_ino) {
937 fprintf(stderr,
938 "%s: Can't move %s onto itself\n",
939 prog_name, path_name(src));
940 ex_code= 1;
941 return;
944 if (dstst.st_ino != 0) {
945 if (iflag || (!fflag && !writable(&dstst))) {
946 fprintf(stderr,
947 "Replace %s? (mode = %03o) ",
948 path_name(dst),
949 dstst.st_mode & 07777);
950 if (!affirmative()) return;
952 if (!S_ISDIR(dstst.st_mode))
953 (void) unlink(path_name(dst));
956 if (rename(path_name(src), path_name(dst)) == 0) {
957 /* Success. */
958 if (vflag) {
959 printf("mv %s %s\n", path_name(src),
960 path_name(dst));
962 return;
964 if (errno == EXDEV) {
965 xdev= 1;
966 } else {
967 report2(path_name(src), path_name(dst));
968 return;
973 if (srcst.st_ino == 0 || !S_ISDIR(srcst.st_mode)) {
974 /* Copy/move/remove/link a single file. */
975 switch (action) {
976 case COPY:
977 case MOVE:
978 copy1(path_name(src), path_name(dst), &srcst, &dstst);
979 break;
980 case REMOVE:
981 remove1(path_name(src), &srcst);
982 break;
983 case LINK:
984 link1(path_name(src), path_name(dst), &srcst, &dstst);
985 break;
987 return;
990 /* Recursively copy/move/remove/link a directory if -r or -R. */
991 if (!rflag) {
992 errno= EISDIR;
993 report(path_name(src));
994 return;
997 /* Ok to remove contents of dir? */
998 if (action == REMOVE) {
999 if (xflag && topsrc_dev != srcst.st_dev) {
1000 /* Don't recurse past a mount point. */
1001 return;
1003 if (iflag) {
1004 fprintf(stderr, "Remove contents of %s? ", path_name(src));
1005 if (!affirmative()) return;
1009 /* Gather the names in the source directory. */
1010 if (!eat_dir(path_name(src), &dlist)) {
1011 report(path_name(src));
1012 return;
1015 /* Check/create the target directory. */
1016 if (action != REMOVE && dstst.st_ino != 0 && !S_ISDIR(dstst.st_mode)) {
1017 if (action != MOVE && !fflag) {
1018 errno= ENOTDIR;
1019 report(path_name(dst));
1020 return;
1022 if (iflag) {
1023 fprintf(stderr, "Replace %s? ", path_name(dst));
1024 if (!affirmative()) {
1025 drop_dlist(dlist);
1026 return;
1029 if (unlink(path_name(dst)) < 0) {
1030 report(path_name(dst));
1031 drop_dlist(dlist);
1032 return;
1034 dstst.st_ino= 0;
1037 if (action != REMOVE) {
1038 if (dstst.st_ino == 0) {
1039 /* Create a new target directory. */
1040 if (!pflag && conforming) srcst.st_mode&= fc_mask;
1042 if (mkdir(path_name(dst), srcst.st_mode | S_IRWXU) < 0
1043 || stat(path_name(dst), &dstst) < 0) {
1044 report(path_name(dst));
1045 drop_dlist(dlist);
1046 return;
1048 if (vflag) printf("mkdir %s\n", path_name(dst));
1049 } else {
1050 /* Target directory already exists. */
1051 if (action == MOVE && !mflag) {
1052 errno= EEXIST;
1053 report(path_name(dst));
1054 drop_dlist(dlist);
1055 return;
1057 if (!pflag) {
1058 /* Keep the existing attributes. */
1059 srcst.st_mode= dstst.st_mode;
1060 srcst.st_uid= dstst.st_uid;
1061 srcst.st_gid= dstst.st_gid;
1062 srcst.st_mtime= dstst.st_mtime;
1066 if (topdst_ino == 0) {
1067 /* Remember the top destination. */
1068 topdst_dev= dstst.st_dev;
1069 topdst_ino= dstst.st_ino;
1072 if (srcst.st_ino == topdst_ino && srcst.st_dev == topdst_dev) {
1073 /* E.g. cp -r /shallow /shallow/deep. */
1074 fprintf(stderr,
1075 "%s%s %s/ %s/: infinite recursion avoided\n",
1076 prog_name, action != MOVE ? " -r" : "",
1077 path_name(src), path_name(dst));
1078 drop_dlist(dlist);
1079 return;
1082 if (xflag && topsrc_dev != srcst.st_dev) {
1083 /* Don't recurse past a mount point. */
1084 drop_dlist(dlist);
1085 return;
1089 /* Go down. */
1090 slashsrc= path_length(src);
1091 slashdst= path_length(dst);
1093 while (dlist != nil) {
1094 path_add(src, dlist->name);
1095 if (action != REMOVE) path_add(dst, dlist->name);
1097 do1(src, dst, depth+1);
1099 path_trunc(src, slashsrc);
1100 path_trunc(dst, slashdst);
1101 chop_dlist(&dlist);
1104 if (action == MOVE || action == REMOVE) {
1105 /* The contents of the source directory should have
1106 * been (re)moved above. Get rid of the empty dir.
1108 if (action == REMOVE && iflag) {
1109 fprintf(stderr, "Remove directory %s? ",
1110 path_name(src));
1111 if (!affirmative()) return;
1113 if (rmdir(path_name(src)) < 0) {
1114 if (errno != ENOTEMPTY) report(path_name(src));
1115 return;
1117 if (vflag) printf("rmdir %s\n", path_name(src));
1120 if (action != REMOVE) {
1121 /* Set the attributes of a new directory. */
1122 struct utimbuf ut;
1124 /* Copy the ownership. */
1125 if ((pflag || !conforming)
1126 && (dstst.st_uid != srcst.st_uid
1127 || dstst.st_gid != srcst.st_gid)
1129 if (chown(path_name(dst), srcst.st_uid,
1130 srcst.st_gid) < 0) {
1131 if (errno != EPERM) {
1132 report(path_name(dst));
1133 return;
1138 /* Copy the mode. */
1139 if (dstst.st_mode != srcst.st_mode) {
1140 if (chmod(path_name(dst), srcst.st_mode) < 0) {
1141 report(path_name(dst));
1142 return;
1146 /* Copy the file modification time. */
1147 if (dstst.st_mtime != srcst.st_mtime) {
1148 ut.actime= action == MOVE ? srcst.st_atime : time(nil);
1149 ut.modtime= srcst.st_mtime;
1150 if (utime(path_name(dst), &ut) < 0) {
1151 if (errno != EPERM) {
1152 report(path_name(dst));
1153 return;
1155 fprintf(stderr,
1156 "%s: Can't set the time of %s\n",
1157 prog_name, path_name(dst));
1163 void usage(void)
1165 char *flags1, *flags2;
1167 switch (identity) {
1168 case CP:
1169 flags1= "pifsmrRvx";
1170 flags2= "pifsrRvx";
1171 break;
1172 case MV:
1173 flags1= "ifsmvx";
1174 flags2= "ifsvx";
1175 break;
1176 case RM:
1177 fprintf(stderr, "Usage: rm [-ifrRvx] file ...\n");
1178 exit(1);
1179 case LN:
1180 flags1= "ifsSmrRvx";
1181 flags2= "ifsSrRvx";
1182 break;
1183 case CPDIR:
1184 flags1= "ifvx";
1185 flags2= nil;
1186 break;
1187 case CLONE:
1188 flags1= "ifsSvx";
1189 flags2= nil;
1190 break;
1192 fprintf(stderr, "Usage: %s [-%s] file1 file2\n", prog_name, flags1);
1193 if (flags2 != nil)
1194 fprintf(stderr, " %s [-%s] file ... dir\n", prog_name, flags2);
1195 exit(1);
1198 int main(int argc, char **argv)
1200 int i;
1201 char *flags;
1202 struct stat st;
1203 pathname_t src, dst;
1204 size_t slash;
1206 #if DEBUG >= 3
1207 /* The first argument is the call name while debugging. */
1208 if (argc < 2) exit(-1);
1209 argv++;
1210 argc--;
1211 #endif
1212 #if DEBUG
1213 vflag= isatty(1);
1214 #endif
1216 /* Call name of this program. */
1217 prog_name= basename(argv[0]);
1219 /* Required action. */
1220 if (strcmp(prog_name, "cp") == 0) {
1221 identity= CP;
1222 action= COPY;
1223 flags= "pifsmrRvx";
1224 expand= 1;
1225 } else
1226 if (strcmp(prog_name, "mv") == 0) {
1227 identity= MV;
1228 action= MOVE;
1229 flags= "ifsmvx";
1230 rflag= pflag= 1;
1231 } else
1232 if (strcmp(prog_name, "rm") == 0) {
1233 identity= RM;
1234 action= REMOVE;
1235 flags= "ifrRvx";
1236 } else
1237 if (strcmp(prog_name, "ln") == 0) {
1238 identity= LN;
1239 action= LINK;
1240 flags= "ifsSmrRvx";
1241 } else
1242 if (strcmp(prog_name, "cpdir") == 0) {
1243 identity= CPDIR;
1244 action= COPY;
1245 flags= "pifsmrRvx";
1246 rflag= mflag= pflag= 1;
1247 conforming= 0;
1248 } else
1249 if (strcmp(prog_name, "clone") == 0) {
1250 identity= CLONE;
1251 action= LINK;
1252 flags= "ifsSmrRvx";
1253 rflag= mflag= fflag= 1;
1254 } else {
1255 fprintf(stderr,
1256 "%s: Identity crisis, not called cp, mv, rm, ln, cpdir, or clone\n",
1257 prog_name);
1258 exit(1);
1261 /* Who am I?, where am I?, how protective am I? */
1262 uid= geteuid();
1263 gid= getegid();
1264 istty= isatty(0);
1265 fc_mask= ~umask(0);
1267 /* Gather flags. */
1268 i= 1;
1269 while (i < argc && argv[i][0] == '-') {
1270 char *opt= argv[i++] + 1;
1272 if (opt[0] == '-' && opt[1] == 0) break; /* -- */
1274 while (*opt != 0) {
1275 /* Flag supported? */
1276 if (strchr(flags, *opt) == nil) usage();
1278 switch (*opt++) {
1279 case 'p':
1280 pflag= 1;
1281 break;
1282 case 'i':
1283 iflag= 1;
1284 if (action == MOVE) fflag= 0;
1285 break;
1286 case 'f':
1287 fflag= 1;
1288 if (action == MOVE) iflag= 0;
1289 break;
1290 case 's':
1291 if (action == LINK) {
1292 sflag= 1;
1293 } else {
1294 /* Forget about POSIX, do it right. */
1295 conforming= 0;
1297 break;
1298 case 'S':
1299 Sflag= 1;
1300 break;
1301 case 'm':
1302 mflag= 1;
1303 break;
1304 case 'r':
1305 expand= 0;
1306 /*FALL THROUGH*/
1307 case 'R':
1308 rflag= 1;
1309 break;
1310 case 'v':
1311 vflag= 1;
1312 break;
1313 case 'x':
1314 xflag= 1;
1315 break;
1316 default:
1317 assert(0);
1322 switch (action) {
1323 case REMOVE:
1324 if (i == argc) {
1325 if (fflag)
1326 exit(0);
1327 usage();
1329 break;
1330 case LINK:
1331 /* 'ln dir/file' is to be read as 'ln dir/file .'. */
1332 if ((argc - i) == 1 && action == LINK) argv[argc++]= ".";
1333 /*FALL THROUGH*/
1334 default:
1335 if ((argc - i) < 2) usage();
1338 path_init(&src);
1339 path_init(&dst);
1341 if (action != REMOVE && !mflag
1342 && stat(argv[argc-1], &st) >= 0 && S_ISDIR(st.st_mode)
1344 /* The last argument is a directory, this means we have to
1345 * throw the whole lot into this directory. This is the
1346 * Right Thing unless you use -r.
1348 path_add(&dst, argv[argc-1]);
1349 slash= path_length(&dst);
1351 do {
1352 path_add(&src, argv[i]);
1353 path_add(&dst, basename(argv[i]));
1355 do1(&src, &dst, 0);
1357 path_trunc(&src, 0);
1358 path_trunc(&dst, slash);
1359 } while (++i < argc-1);
1360 } else
1361 if (action == REMOVE || (argc - i) == 2) {
1362 /* Just two files (or many files for rm). */
1363 do {
1364 path_add(&src, argv[i]);
1365 if (action != REMOVE) path_add(&dst, argv[i+1]);
1367 do1(&src, &dst, 0);
1368 path_trunc(&src, 0);
1369 } while (action == REMOVE && ++i < argc);
1370 } else {
1371 usage();
1373 path_drop(&src);
1374 path_drop(&dst);
1376 #if DEBUG
1377 if (nchunks != 0) {
1378 fprintf(stderr, "(%ld chunks of memory not freed)\n",
1379 (long) nchunks);
1381 #endif
1382 exit(ex_code);
1383 return ex_code;