V8/usr/src/cmd/rcp.c

Compare this file to the similar file:
Show the results in this format:


/*
 *	cp source dest
 *
 *	     or
 *
 *	cp source1 ... sourcen dest
 *
 *	In the first case, dest must not be a directory (else we have a
 *	degenerate version of the second case). In the second case,
 *	dest must be a directory; the resulting objects will be
 *	named dest/source1 ... dest/sourcen.
 *
 *	If any source is a directory, the tree structure under it
 *	will be (recursively) copied; this process will attempt to
 *	preserve any links that exist within the hierarchy.
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/dir.h>

#define BLKSIZE BUFSIZ

/* Maximum file tree nesting before we start closing and re-opening */
#define MAXLEVEL 8

/* Non-zero if stat buffer b refers to a directory, zero otherwise */
#define isdir(b) (((b).st_mode & S_IFMT) == S_IFDIR)

char *malloc(), *realloc(), *strcpy(), *strcat();
int iamsu;

/* Final component of the path name by which we were invoked */
char *pgmname;

main (argc, argv)
	int argc;
	char **argv;
{
	int retcode;
	char *dest, *destdir, *p, *q;
	struct stat *argbufs, destbuf, statbuf, rootbuf;
	int i;

	umask(0);
	pgmname = argv[0];

	/*
	 *	Start validity checking
	 */

	/* Check for too few arguments given */
	if (argc < 3) {
		fprintf (stderr, "usage:  %s f1 f2  or  %s f1 ... fn d\n",
				pgmname, pgmname);
		return 1;
	}

	/*
	 *	If the last argument is a non-directory, we require
	 *	argc == 3 (in the case of: cp f1 f2)
	 */
	dest = argv[argc-1];
	destbuf.st_mode = 0;	/* Force isdir(destbuf) false */
	if ((stat (dest, &destbuf) < 0 || !isdir(destbuf)) && argc != 3) {
		scream ("%s not a directory", dest);
		return 1;
	}

	/*
	 *	Allocate storage for a stat buffer for each argument
	 *	but the last. This will be used to check for trying to
	 *	copy something into a subdirectory of itself.
	 */
	argbufs = (struct stat *)
		malloc ((unsigned) (sizeof (struct stat) * (argc-2)));
	if (argbufs == NULL) {
		scream ("insufficient storage", "");
		return 1;
	}
	iamsu = (getuid() == 0);

	for (i = 0; i < argc-2; i++) {
		if (stat (argv[i+1], &argbufs[i]) < 0) {
			scream ("cannot access %s", argv[i+1]);
			return 1;
		}
	}
	
	/*
	 *	Now run back from the destination to the root directory,
	 *	checking along the way for any matches with anything in
	 *	the argbufs array. If we find any, we tried a forbidden
	 *	operation. If the destination is not already a directory,
	 *	we must start from its parent. Note that even in this case,
	 *	we can't elide the test, because otherwise we wouldn't get
	 *
	 *		cp . x
	 *
	 *	This code also handles pathological but legal destinations
	 *	properly; in particular note that the null string is a
	 *	valid name for the current directory. Sigh.
	 *
	 *	First figure out the name of the directory to check.
	 */
	destdir = malloc ((unsigned) (strlen (dest) + 2));
	if (destdir == NULL) {
		scream ("insufficient storage", "");
		return 1;
	}
	strcpy (destdir, dest);
	if (!isdir (destbuf)) {
		p = q = destdir;
		while (*q) {
			if (*q++ == '/')
				p = q - 1;
		}
		if (p == destdir)
			strcpy (destdir, ".");
		else
			*p = '\0';
	}

	/* Now stat the root directory for later use */
	if (stat ("/", &rootbuf) < 0) {
		scream ("cannot access root directory", "");
		return 1;
	}

	/* Now run back from destdir to the root, checking against argbufs */
	do {
		if (stat (destdir, &statbuf) < 0) {
			scream ("cannot stat %s", destdir);
			return 1;
		}
		for (i = 0; i < argc-2; i++)
			if (argbufs[i].st_dev == statbuf.st_dev
			 && argbufs[i].st_ino == statbuf.st_ino) {
				fprintf (stderr, "%s: %s contains %s\n",
					pgmname, argv[i+1], dest);
				return 1;
			}
		destdir = realloc (destdir, (unsigned) (strlen(destdir) + 4));
		if (destdir == NULL) {
			scream ("insufficient storage", "");
			return 1;
		}
		strcat (destdir, "/..");
	} while (statbuf.st_dev != rootbuf.st_dev
	      || statbuf.st_ino != rootbuf.st_ino);
	free (destdir);

	/*
	 *	End of validity checking
	 */

	if (isdir (destbuf)) {
		retcode = 0;
		for (i=1; i<argc-1; i++) {

			char *p, *q;
			char *xdest;

			p = q = argv[i];
			while (*p)
				if (*p++ == '/')
					q = p;
			xdest = malloc ((unsigned)
				(strlen(dest) + strlen (q) + 2));
			if (xdest != NULL) {
				strcpy (xdest, dest);
				strcat (xdest, "/");
				strcat (xdest, q);
				retcode |= copy (argv[i], &argbufs[i-1], xdest);
				free (xdest);
			} else {
				scream ("no storage", "");
				retcode = 1;
			}
		}
	} else
		retcode = copy (argv[1], &argbufs[0], dest);

	return retcode;
}

/*
 *	Copy a file or directory. The source file name is in "source",
 *	and the destination file name is in "dest". "srcbuf" points to
 *	a stat buffer for the source.
 */
int
copy (source, srcbuf, dest)
	char *source, *dest;
	struct stat *srcbuf;
{
	int inf, outf, n;
	char buffer[BLKSIZE];
	struct stat newbuf, destbuf;
	static int level = 0;
	static short firstyell = 1;
	struct {
		long actime, modtime;
	} utimbuf;
	long time();
	static struct linknode {
		dev_t ln_dev;
		ino_t ln_ino;
		struct linknode *ln_next;
		char *ln_name;		/* variable */
	} *ln_head = NULL, *lnp;

	if (isdir (*srcbuf)) {
		
		char *srcname, *destname;
		FILE *dirfile;
		struct direct dir;
		long dirloc;
		int retcode = 0;

		/*
		 *	Copy from a directory. This will have to copy the
		 *	tree structure recursively. Thus, we will create
		 *	a new directory of an appropriate name, then
		 *	copy everything in source to elements of dest.
		 */
		if (stat (dest, &destbuf) < 0) {
			if (mkdir (dest)) {
				scream ("cannot create directory %s", dest);
				return 1;
			}
		} else {
			scream ("%s already exists", dest);
			return 1;
		}

		if ((dirfile = fopen (source, "r")) == NULL) {
			scream ("cannot open %s", source);
			return 1;
		}

		srcname = malloc ((unsigned) (strlen(source) + DIRSIZ + 2));
		destname = malloc ((unsigned) (strlen(dest) + DIRSIZ + 2));
		if (srcname == NULL || destname == NULL) {
			scream ("insufficient storage", "");
			fclose(dirfile);
			return 1;
		}

		while (fread ((char *) & dir, sizeof (struct direct),
			1, dirfile) == 1)
			if (dir.d_ino != 0 &&
			    strncmp (dir.d_name, ".", DIRSIZ) &&
			    strncmp (dir.d_name, "..", DIRSIZ)) {
				strcpy (srcname, source);
				strcat (srcname, "/");
				strncat (srcname, dir.d_name, DIRSIZ);
				strcpy (destname, dest);
				strcat (destname, "/");
				strncat (destname, dir.d_name, DIRSIZ);

				if (level > MAXLEVEL) {
					dirloc = ftell (dirfile);
					fclose (dirfile);
					dirfile = NULL;
				}

				if (lstat (srcname, &newbuf) < 0) {
					scream ("cannot stat %s", srcname);
					if(dirfile != NULL)
						fclose(dirfile);
					return 1;
				}

				level++;
				retcode |= copy (srcname, &newbuf, destname);
				level--;

				if (level > MAXLEVEL) {
					if ((dirfile = fopen (source, "r"))
					    == NULL) {
						scream ("cannot reopen %s",
						    source);
						return 1;
					}
					if(fseek(dirfile, dirloc, 0) == EOF) {
						scream("can't seek on %s",
							source);
						fclose(dirfile);
						return(1);
					}
				}
		}

		free (srcname);
		free (destname);
		if(iamsu)
			chown(dest, srcbuf->st_uid, srcbuf->st_gid);

		utimbuf.actime = time ((long *) NULL);
		utimbuf.modtime = srcbuf->st_mtime;
		utime (dest, &utimbuf);

		if (chmod (dest, srcbuf->st_mode & 07777) < 0) {
			scream ("cannot chmod for %s", dest);
			retcode = 1;
		}
		fclose(dirfile);
		return retcode;
	}

	/*
	 *	Here when src is not a directory
	 */
	if (stat (dest, &destbuf) >= 0
	 && srcbuf->st_dev == destbuf.st_dev
	 && srcbuf->st_ino == destbuf.st_ino) {
		scream ("cannot copy %s to itself", source);
		return 1;
	}

	/*
	 *	Check if the source (which is known to be a file) has
	 *	more than one link. If not, all is easy. If so, we may
	 *	be able to create a link rather than copying the source.
	 */
	if (srcbuf->st_nlink > 1) {

		/* Hunt through the table of linked items for a match */
		lnp = ln_head;
		while (lnp != NULL &&
		   (lnp->ln_dev != srcbuf->st_dev
		   ||  lnp->ln_ino != srcbuf->st_ino)) {
			lnp = lnp->ln_next;
		}

		/* If match found, try a link */
		if (lnp != NULL)
			if (link (lnp->ln_name, dest)) {
				scream ("cannot create %s link", dest);
				return 1;
			} else
				return 0;

		/* No match found -- build a table entry */
		lnp = (struct linknode *) malloc (sizeof (struct linknode));

		if (lnp == NULL) {
			if (firstyell) {
				scream ("link table too large", "");
				firstyell = 0;
			}
		} else if ((lnp->ln_name = malloc ((unsigned) (strlen(dest)+1)))
			    == NULL) {
			if (firstyell) {
				scream ("link names too large", "");
				firstyell = 0;
			}
			free ((char *) lnp);
		} else {
			lnp->ln_dev = srcbuf->st_dev;
			lnp->ln_ino = srcbuf->st_ino;
			strcpy (lnp->ln_name, dest);
			lnp->ln_next = ln_head;
			ln_head = lnp;
		}
	}

	/* Now copy the file, whether or not there was a link */
	switch(srcbuf->st_mode & S_IFMT){
	case S_IFLNK:{
		int len;
		char buf[1024];
		len = readlink(source, buf, sizeof buf);
		if(len<=0){
			scream("can't read link for %s", source);
			return 1;
		}
		if(symlink(buf, dest)!=0){
			scream("can't write link for %s", dest);
			return 1;
		}
		break;
	}
	case S_IFCHR:
	case S_IFBLK:
		if(mknod(dest, srcbuf->st_mode, srcbuf->st_rdev)!=0){
			scream("can't mknod %s", dest);
			return 1;
		}
		break;
	default:
		if ((outf = creat (dest, srcbuf->st_mode & 07777)) < 0) {
			scream ("cannot create %s", dest);
			return 1;
		}
		if ((inf = open (source, 0)) < 0) {
			scream ("cannot open %s", source);
			close(outf);
			return 1;
		}
		while ((n = read (inf, buffer, BLKSIZE)) > 0) {
			if (write (outf, buffer, n) != n) {
				scream ("output error on %s", dest);
				close (inf);
				close (outf);
				return 1;
			}
		}
		if (n < 0) {
			scream ("input error on %s", source);
			close (inf);
			close (outf);
			return 1;
		}
		if (close (outf) < 0) {
			scream ("cannot close %s", dest);
			close (inf);
			return 1;
		}
		if (close (inf) < 0) {
			scream ("cannot close %s", source);
			return 1;
		}
		break;
	}
	utimbuf.actime = srcbuf->st_atime;
	utimbuf.modtime = srcbuf->st_mtime;
	utime (dest, &utimbuf);
	if(iamsu)
		chown(dest, srcbuf->st_uid, srcbuf->st_gid);
	return 0;
}

int
mkdir(d)
	char *d;
{
	int status, pid, w;
	/*w = open(".", 0);
	printf("%d\t%s\n", w, d);
	close(w);*/
	switch (pid=fork()) {
	case 0:
		execl ("/bin/mkdir", "mkdir", d, 0);
		/* No break */
	case -1:
		return 1;

	default:
		do w = wait (&status);
		while (w != pid && w > 0);
		if (w == pid)
			return status;
		return w;
	}
}

scream (s1, s2)
	char *s1, *s2;
{
	fprintf (stderr, "%s: ", pgmname);
	fprintf (stderr, s1, s2);
	putc ('\n', stderr);
}