First pass on cp --preserve
diff --git a/toys/posix/cp.c b/toys/posix/cp.c
index 803bff8..d5e92f2 100644
--- a/toys/posix/cp.c
+++ b/toys/posix/cp.c
@@ -7,7 +7,7 @@
 // options shared between mv/cp must be in same order (right to left)
 // for FLAG macros to work out right in shared infrastructure.
 
-USE_CP(NEWTOY(cp, "<2RHLPp"USE_CP_MORE("rdaslvnF(remove-destination)")"fi[-HLP"USE_CP_MORE("d")"]"USE_CP_MORE("[-ni]"), TOYFLAG_BIN))
+USE_CP(NEWTOY(cp, "<2"USE_CP_PRESERVE("(preserve):;")"RHLPp"USE_CP_MORE("rdaslvnF(remove-destination)")"fi[-HLP"USE_CP_MORE("d")"]"USE_CP_MORE("[-ni]"), TOYFLAG_BIN))
 USE_MV(NEWTOY(mv, "<2"USE_CP_MORE("vnF")"fi"USE_CP_MORE("[-ni]"), TOYFLAG_BIN))
 USE_INSTALL(NEWTOY(install, "<1cdDpsvm:o:g:", TOYFLAG_USR|TOYFLAG_BIN))
 
@@ -44,6 +44,21 @@
     -s	symlink instead of copy
     -v	verbose
 
+config CP_PRESERVE
+  bool "cp --preserve support"
+  default y
+  depends on CP_MORE
+  help
+    usage: cp [--preserve=mota]
+
+    --preserve takes either a comma separated list of attributes, or the first
+    letter(s) of:
+
+            mode - permissions (ignore umask for rwx, copy suid and sticky bit)
+       ownership - user and group
+      timestamps - file creation, modification, and access times.
+             all - all of the above
+
 config MV
   bool "mv"
   default y
@@ -87,16 +102,24 @@
 #include "toys.h"
 
 GLOBALS(
-  // install's options
-  char *group;
-  char *user;
-  char *mode;
+  union {
+    struct {
+      // install's options
+      char *group;
+      char *user;
+      char *mode;
+    } i;
+    struct {
+      char *preserve;
+    } c;
+  };
 
   char *destname;
   struct stat top;
   int (*callback)(struct dirtree *try);
   uid_t uid;
   gid_t gid;
+  int pflags;
 )
 
 // Callback from dirtree_read() for each file/directory under a source dir.
@@ -245,28 +268,27 @@
         if (fdin < 0) {
           catch = try->name;
           break;
-        } else {
-          fdout = openat(cfd, catch, O_RDWR|O_CREAT|O_TRUNC, try->st.st_mode);
-          if (fdout >= 0) {
-            xsendfile(fdin, fdout);
-            err = 0;
-          }
-          close(fdin);
         }
+        fdout = openat(cfd, catch, O_RDWR|O_CREAT|O_TRUNC, try->st.st_mode);
+        if (fdout >= 0) {
+          xsendfile(fdin, fdout);
+          err = 0;
+        }
+        close(fdin);
       }
     } while (err && (flags & (FLAG_f|FLAG_n)) && !unlinkat(cfd, catch, 0));
   }
 
+  // Did we make a thing?
   if (fdout != -1) {
-    if (flags & (FLAG_a|FLAG_p)) {
-      struct timespec times[2];
-      int rc;
+    int rc;
 
-      // Inability to set these isn't fatal, some require root access.
+    // Inability to set --preserve isn't fatal, some require root access.
 
-      times[0] = try->st.st_atim;
-      times[1] = try->st.st_mtim;
+    // ownership
+    if (TT.pflags & 2) {
 
+      // permission bits already correct for mknod and don't apply to symlink
       // If we can't get a filehandle to the actual object, use racy functions
       if (fdout == AT_FDCWD)
         rc = fchownat(cfd, catch, try->st.st_uid, try->st.st_gid,
@@ -278,16 +300,21 @@
         perror_msg("chown '%s'", pp = dirtree_path(try, 0));
         free(pp);
       }
-
-      // permission bits already correct for mknod and don't apply to symlink
-      if (fdout == AT_FDCWD) utimensat(cfd, catch, times, AT_SYMLINK_NOFOLLOW);
-      else {
-        futimens(fdout, times);
-        fchmod(fdout, try->st.st_mode);
-      }
     }
 
-    if (fdout != AT_FDCWD) xclose(fdout);
+    // timestamp
+    if (TT.pflags & 4) {
+      struct timespec times[] = {try->st.st_atim, try->st.st_mtim};
+
+      if (fdout == AT_FDCWD) utimensat(cfd, catch, times, AT_SYMLINK_NOFOLLOW);
+      else futimens(fdout, times);
+    }
+
+    // mode comes last because other syscalls can strip suid bit
+    if (fdout != AT_FDCWD) {
+      if (TT.pflags & 1) fchmod(fdout, try->st.st_mode);
+      xclose(fdout);
+    }
 
     if (CFG_MV && toys.which->name[0] == 'm')
       if (unlinkat(tfd, try->name, S_ISDIR(try->st.st_mode) ? AT_REMOVEDIR :0))
@@ -300,17 +327,41 @@
 
 void cp_main(void)
 {
-  char *destname = toys.optargs[--toys.optc];
+  char *destname = toys.optargs[--toys.optc],
+       *preserve[] = {"mode", "ownership", "timestamps"};
   int i, destdir = !stat(destname, &TT.top) && S_ISDIR(TT.top.st_mode);
 
   if (toys.optc>1 && !destdir) error_exit("'%s' not directory", destname);
-  if (toys.which->name[0] == 'm') toys.optflags |= FLAG_d|FLAG_p|FLAG_R;
-  if (toys.optflags & (FLAG_a|FLAG_p)) umask(0);
 
+  if (toys.optflags & (FLAG_a|FLAG_p)) {
+    TT.pflags = 7; // preserve=mot
+    umask(0);
+  }
+  if (CFG_CP_PRESERVE && TT.c.preserve) {
+    char *pre = xstrdup(TT.c.preserve), *s;
+
+    if (comma_scan(pre, "all", 1)) TT.pflags = ~0;
+    for (i=0; i<ARRAY_LEN(preserve); i++)
+      if (comma_scan(pre, preserve[i], 1)) TT.pflags |= 1<<i;
+    if (*pre) {
+
+      // Try to interpret as letters, commas won't set anything this doesn't.
+      for (s = TT.c.preserve; *s; s++) {
+        for (i=0; i<ARRAY_LEN(preserve); i++)
+          if (*s == *preserve[i]) TT.pflags |= 1<<i;
+        if (i == ARRAY_LEN(preserve)) {
+          if (*s == 'a') TT.pflags = ~0;
+          else break;
+        }
+      }
+
+      if (*s) error_exit("bad --preserve=%s", pre);
+    }
+    free(pre);
+  }
   if (!TT.callback) TT.callback = cp_node;
 
   // Loop through sources
-
   for (i=0; i<toys.optc; i++) {
     struct dirtree *new;
     char *src = toys.optargs[i];
@@ -351,6 +402,8 @@
 
 void mv_main(void)
 {
+  toys.optflags |= FLAG_d|FLAG_p|FLAG_R;
+
   cp_main();
 }
 
@@ -360,9 +413,9 @@
 
 static int install_node(struct dirtree *try)
 {
-  if (TT.mode) try->st.st_mode = string_to_mode(TT.mode, try->st.st_mode);
-  if (TT.group) try->st.st_gid = TT.gid;
-  if (TT.user) try->st.st_uid = TT.uid;
+  if (TT.i.mode) try->st.st_mode = string_to_mode(TT.i.mode, try->st.st_mode);
+  if (TT.i.group) try->st.st_gid = TT.gid;
+  if (TT.i.user) try->st.st_uid = TT.uid;
 
   // Always returns 0 because no -r
   cp_node(try);
@@ -401,8 +454,8 @@
   if (flags & FLAG_v) toys.optflags |= 8; // cp's FLAG_v
   if (flags & (FLAG_p|FLAG_o|FLAG_g)) toys.optflags |= 512; // cp's FLAG_p
 
-  if (TT.user) TT.uid = xgetpwnamid(TT.user)->pw_uid;
-  if (TT.group) TT.gid = xgetgrnamid(TT.group)->gr_gid;
+  if (TT.i.user) TT.uid = xgetpwnamid(TT.i.user)->pw_uid;
+  if (TT.i.group) TT.gid = xgetgrnamid(TT.i.group)->gr_gid;
 
   TT.callback = install_node;
   cp_main();