Add first pass at cp, totally untested, unlikely to work yet. :)
authorRob Landley <rob@landley.net>
Wed, 20 Feb 2008 07:47:56 +0000 (01:47 -0600)
committerRob Landley <rob@landley.net>
Wed, 20 Feb 2008 07:47:56 +0000 (01:47 -0600)
lib/dirtree.c
lib/lib.h
scripts/test/cp.test
toys/cp.c [new file with mode: 0644]

index 71f6449..a2647ee 100644 (file)
@@ -50,7 +50,7 @@ struct dirtree *dirtree_add_node(char *path)
 // structures after use, and return NULL.
 
 struct dirtree *dirtree_read(char *path, struct dirtree *parent,
-                                       int (*callback)(struct dirtree *node))
+                                       int (*callback)(struct dirtree *node, int after))
 {
        struct dirtree *dt = NULL, **ddt = &dt;
        DIR *dir;
@@ -72,9 +72,10 @@ struct dirtree *dirtree_read(char *path, struct dirtree *parent,
                *ddt = dirtree_add_node(path);
                if (!*ddt) continue;
                (*ddt)->parent = parent;
-               if (callback) callback(*ddt);
+               if (callback) callback(*ddt, 0);
                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;
index ee073e0..21ef98e 100644 (file)
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -41,7 +41,7 @@ struct dirtree {
 
 struct dirtree *dirtree_add_node(char *path);
 struct dirtree *dirtree_read(char *path, struct dirtree *parent,
-                    int (*callback)(struct dirtree *node));
+                    int (*callback)(struct dirtree *node, int after));
 
 // lib.c
 void xstrcpy(char *dest, char *src, size_t size);
index 5e67d3f..858a382 100755 (executable)
@@ -72,5 +72,7 @@ testing "cp file1 file2 dir" \
 rm one two random
 rm -rf dir
 
+# cp -r ../source destdir
+
 # cp file1 file2 dir
 # cp file1 missing file2 -> dir
diff --git a/toys/cp.c b/toys/cp.c
new file mode 100644 (file)
index 0000000..c04a2bf
--- /dev/null
+++ b/toys/cp.c
@@ -0,0 +1,146 @@
+/* vi: set sw=4 ts=4:
+ *
+ * cp.c - Copy files.
+ *
+ * Copyright 2008 Rob Landley <rob@landley.net>
+ *
+ * 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))
+
+config CP
+       bool "cp"
+       default n
+       help
+         usage: cp -f SOURCE... DEST
+
+         Copy files from SOURCE to DEST.  If more than one SOURCE, DEST must
+         be a directory.
+
+               -f      force copy by deleting destination file
+               -i      interactive, prompt before overwriting existing DEST
+               -p      preserve timestamps, ownership, and permissions
+               -r      recurse into subdirectories (DEST must be a directory)
+*/
+
+#include "toys.h"
+
+#define FLAG_f 1
+#define FLAG_i 2
+#define FLAG_P 4
+#define FLAG_L 8
+#define FLAG_H 16
+#define FLAG_a 32
+#define FLAG_p 64
+#define FLAG_d 128
+#define FLAG_R 256
+#define FLAG_r 512
+
+DEFINE_GLOBALS(
+       char *destname;
+       int destisdir;
+)
+
+#define TT this.cp
+
+// Copy an individual file or directory to target.
+
+void cp_file(char *src, struct stat *srcst, int topdir, int again)
+{
+       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;
+       }
+
+       // 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);
+       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);
+       } else {
+               int fdin, fdout;
+               fdin = xopen(src, O_RDONLY);
+               fdout = xcreate(s, O_CREAT|O_TRUNC, mode);
+               xsendfile(fdin, fdout);
+               close(fdin);
+               xclose(fdout);
+       }
+}
+
+// Callback from dirtree_read() for each file/directory under a source dir.
+
+int cp_node(struct dirtree *node, int after)
+{
+       cp_file(node->name, &(node->st), 0, after);
+       return 0;
+}
+
+void cp_main(void)
+{
+       struct stat st;
+       int i;
+
+       // Grab target argument.  (Guaranteed to be there due to "<2" above.)
+
+       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;
+
+       // 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))) {
+                       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);
+                       } else error_msg("Skipped dir '%s'", src);
+               } else {
+                       cp_file(src, &st, 1, 0);
+                       cp_file(src, &st, 1, 1);
+               }
+       }
+       return;
+
+error_notdir:
+       error_exit("'%s' isn't a directory", TT.destname);
+}