source: trunk/minix/commands/simple/cp.c@ 20

Last change on this file since 20 was 9, checked in by Mattia Monga, 14 years ago

Minix 3.1.2a

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