changeset 263:7c53152a483b

Make cp pass most of its test suite. Still need to add symlink support.
author Rob Landley <rob@landley.net>
date Thu, 21 Feb 2008 04:44:42 -0600
parents 70f36d9c5387
children 4b4a6979d228
files lib/dirtree.c lib/lib.h toys/cp.c
diffstat 3 files changed, 81 insertions(+), 40 deletions(-) [+]
line wrap: on
line diff
--- a/lib/dirtree.c	Wed Feb 20 01:47:56 2008 -0600
+++ b/lib/dirtree.c	Thu Feb 21 04:44:42 2008 -0600
@@ -50,7 +50,7 @@
 // structures after use, and return NULL.
 
 struct dirtree *dirtree_read(char *path, struct dirtree *parent,
-					int (*callback)(struct dirtree *node, int after))
+					int (*callback)(char *path, struct dirtree *node))
 {
 	struct dirtree *dt = NULL, **ddt = &dt;
 	DIR *dir;
@@ -72,10 +72,9 @@
 		*ddt = dirtree_add_node(path);
 		if (!*ddt) continue;
 		(*ddt)->parent = parent;
-		if (callback) callback(*ddt, 0);
+		if (callback) callback(path, *ddt);
 		if (entry->d_type == DT_DIR)
 			(*ddt)->child = dirtree_read(path, *ddt, callback);
-		if (callback) callback(*ddt, 1);
 		if (callback) free(*ddt);
 		else ddt = &((*ddt)->next);
 		path[len]=0;
--- a/lib/lib.h	Wed Feb 20 01:47:56 2008 -0600
+++ b/lib/lib.h	Thu Feb 21 04:44:42 2008 -0600
@@ -41,7 +41,7 @@
 
 struct dirtree *dirtree_add_node(char *path);
 struct dirtree *dirtree_read(char *path, struct dirtree *parent,
-                    int (*callback)(struct dirtree *node, int after));
+                    int (*callback)(char *path, struct dirtree *node));
 
 // lib.c
 void xstrcpy(char *dest, char *src, size_t size);
--- a/toys/cp.c	Wed Feb 20 01:47:56 2008 -0600
+++ b/toys/cp.c	Thu Feb 21 04:44:42 2008 -0600
@@ -7,7 +7,7 @@
  * See http://www.opengroup.org/onlinepubs/009695399/utilities/cp.html
  *
  * "R+ra+d+p+r"
-USE_HELLO(NEWTOY(hello, "<2rR+rdpa+d+p+rHLPif", TOYFLAG_BIN|TOYFLAG_UMASK))
+USE_CP(NEWTOY(cp, "<2rR+rdpa+d+p+rHLPif", TOYFLAG_BIN))
 
 config CP
 	bool "cp"
@@ -40,60 +40,95 @@
 DEFINE_GLOBALS(
 	char *destname;
 	int destisdir;
+	int destisnew;
 )
 
 #define TT this.cp
 
 // Copy an individual file or directory to target.
 
-void cp_file(char *src, struct stat *srcst, int topdir, int again)
+void cp_file(char *src, struct stat *srcst, int topdir)
 {
 	char *s = NULL;
-	int mode = (toys.optflags & FLAG_p) ? 0700 : 0777;
-
-	// The second time we're called, chmod data.  We can't do this on
-	// the first pass because we may copy files into a read-only directory.
-	if (again) {
-		if (toys.optflags & FLAG_p) {
-			struct utimbuf ut;
-
-			// Inability to set these isn't fatal, some require root access.
-			// Can't do fchmod() etc here because -p works on mkdir, too.
-			chown(s, srcst->st_uid, srcst->st_gid);
-			chmod(s, srcst->st_mode);
-			ut.actime = srcst->st_atime;
-			ut.modtime = srcst->st_mtime;
-			utime(s, &ut);
-		}
-		return;
-	}
+	int fdout;
 
 	// Trim path from name if necessary.
+
+
 	if (topdir) s = strrchr(src, '/');
 	if (!s) s=src;
 
 	// Determine location to create new file/directory at.
-	if (TT.destisdir) s = xmsprintf(toybuf, "%s/%s", TT.destname, s);
+
+	if (TT.destisdir || !topdir) s = xmsprintf("%s/%s", TT.destname, s);
 	else s = xstrdup(TT.destname);
 
 	// Copy directory or file to destination.
+
 	if (S_ISDIR(srcst->st_mode)) {
-		if (mkdir(s, mode)) perror_exit("mkdir '%s'", s);
+		struct stat st2;
+
+		// Always make directory writeable to us, so we can create files in it.
+		//
+		// Yes, there's a race window between mkdir() and open() so it's
+		// possible that -p can be made to chown a directory other than the one
+		// we created.  The closest we can do to closing this is make sure
+		// that what we open _is_ a directory rather than something else.
+
+		if (mkdir(s, srcst->st_mode | 0200) || 0>(fdout=open(s, 0))
+			|| fstat(fdout, &st2) || !S_ISDIR(st2.st_mode))
+		{
+			perror_exit("mkdir '%s'", s);
+		}
 	} else {
-		int fdin, fdout;
+		int fdin, i;
+
 		fdin = xopen(src, O_RDONLY);
-		fdout = xcreate(s, O_CREAT|O_TRUNC, mode);
+		for (i=2 ; i; i--) {
+			fdout = open(s, O_RDWR|O_CREAT|O_TRUNC, srcst->st_mode);
+			if (fdout>=0 || !(toys.optflags & FLAG_f)) break;
+			unlink(s);
+		}
+		if (fdout<0) perror_exit("%s", s);
 		xsendfile(fdin, fdout);
 		close(fdin);
-		xclose(fdout);
 	}
+
+	// Inability to set these isn't fatal, some require root access.
+	// Can't do fchmod() etc here because -p works on mkdir, too.
+
+	if (toys.optflags & FLAG_p) {
+		int mask = umask(0);
+		struct utimbuf ut;
+
+		fchown(fdout,srcst->st_uid, srcst->st_gid);
+		ut.actime = srcst->st_atime;
+		ut.modtime = srcst->st_mtime;
+		utime(s, &ut);
+		umask(mask);
+	}
+	xclose(fdout);
+	free(s);
 }
 
 // Callback from dirtree_read() for each file/directory under a source dir.
 
-int cp_node(struct dirtree *node, int after)
+int cp_node(char *path, struct dirtree *node)
 {
-	cp_file(node->name, &(node->st), 0, after);
+	char *s = path+strlen(path);
+	struct dirtree *n = node;
+
+	// Find appropriate chunk of path for destination.
+
+	for (;;) {
+		if (*(--s) == '/') {
+			if (!n->parent) break;
+			n = n->parent;
+		}
+	}
+	s++;
+		
+	cp_file(s, &(node->st), 0);
 	return 0;
 }
 
@@ -107,38 +142,45 @@
 	TT.destname = toys.optargs[--toys.optc];
 
 	// If destination doesn't exist, are we ok with that?
+
 	if (stat(TT.destname, &st)) {
 		if (toys.optc>1) goto error_notdir;
+		TT.destisnew++;
 
 	// If destination exists...
+
 	} else {
 		if (S_ISDIR(st.st_mode)) TT.destisdir++;
 		else if (toys.optc > 1) goto error_notdir;
 	}
 
 	// Handle sources
+
 	for (i=0; i<toys.optc; i++) {
 		char *src = toys.optargs[i];
 
-		// Skip nonexistent sources...
-		if (!((toys.optflags & FLAG_d) ? lstat(src, &st) : stat(src, &st))) {
+		// Skip nonexistent sources, or src==dest.
+
+		if (!strcmp(src, TT.destname)) continue;
+		if ((toys.optflags & FLAG_d) ? lstat(src, &st) : stat(src, &st))
+		{
 			perror_msg("'%s'", src);
 			toys.exitval = 1;
 			continue;
 		}
 
 		// Copy directory or file.
+
 		if (S_ISDIR(st.st_mode)) {
 			if (toys.optflags & FLAG_r) {
-				cp_file(src, &st, 1, 0);
-				dirtree_read(src, NULL, cp_node);
-				cp_file(src, &st, 1, 1);
+				cp_file(src, &st, 1);
+				strncpy(toybuf, src, sizeof(toybuf)-1);
+				toybuf[sizeof(toybuf)-1]=0;
+				dirtree_read(toybuf, NULL, cp_node);
 			} else error_msg("Skipped dir '%s'", src);
-		} else {
-			cp_file(src, &st, 1, 0);
-			cp_file(src, &st, 1, 1);
-		}
+		} else cp_file(src, &st, 1);
 	}
+
 	return;
 
 error_notdir: