| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 1 | /* vi: set sw=4 ts=4: |
| 2 | * |
| 3 | * cp.c - Copy files. |
| 4 | * |
| 5 | * Copyright 2008 Rob Landley <rob@landley.net> |
| 6 | * |
| 7 | * See http://www.opengroup.org/onlinepubs/009695399/utilities/cp.html |
| 8 | * |
| 9 | * "R+ra+d+p+r" |
| Rob Landley | de69527 | 2008-02-24 03:48:06 -0600 | [diff] [blame^] | 10 | USE_CP(NEWTOY(cp, "<2slrR+rdpa+d+p+rHLPif", TOYFLAG_BIN)) |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 11 | |
| 12 | config CP |
| 13 | bool "cp" |
| 14 | default n |
| 15 | help |
| Rob Landley | de69527 | 2008-02-24 03:48:06 -0600 | [diff] [blame^] | 16 | usage: cp -fpr SOURCE... DEST |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 17 | |
| 18 | Copy files from SOURCE to DEST. If more than one SOURCE, DEST must |
| 19 | be a directory. |
| 20 | |
| 21 | -f force copy by deleting destination file |
| 22 | -i interactive, prompt before overwriting existing DEST |
| 23 | -p preserve timestamps, ownership, and permissions |
| 24 | -r recurse into subdirectories (DEST must be a directory) |
| 25 | */ |
| 26 | |
| 27 | #include "toys.h" |
| 28 | |
| 29 | #define FLAG_f 1 |
| Rob Landley | de69527 | 2008-02-24 03:48:06 -0600 | [diff] [blame^] | 30 | #define FLAG_i 2 // todo |
| 31 | #define FLAG_P 4 // todo |
| 32 | #define FLAG_L 8 // todo |
| 33 | #define FLAG_H 16 // todo |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 34 | #define FLAG_a 32 |
| 35 | #define FLAG_p 64 |
| Rob Landley | de69527 | 2008-02-24 03:48:06 -0600 | [diff] [blame^] | 36 | #define FLAG_d 128 // todo |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 37 | #define FLAG_R 256 |
| 38 | #define FLAG_r 512 |
| Rob Landley | de69527 | 2008-02-24 03:48:06 -0600 | [diff] [blame^] | 39 | #define FLAG_s 1024 // todo |
| 40 | #define FLAG_l 2048 // todo |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 41 | |
| 42 | DEFINE_GLOBALS( |
| 43 | char *destname; |
| 44 | int destisdir; |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 45 | int destisnew; |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 46 | ) |
| 47 | |
| 48 | #define TT this.cp |
| 49 | |
| 50 | // Copy an individual file or directory to target. |
| 51 | |
| Rob Landley | de69527 | 2008-02-24 03:48:06 -0600 | [diff] [blame^] | 52 | void cp_file(char *src, struct stat *srcst, int depth) |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 53 | { |
| 54 | char *s = NULL; |
| Rob Landley | de69527 | 2008-02-24 03:48:06 -0600 | [diff] [blame^] | 55 | int fdout = -1; |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 56 | |
| 57 | // Trim path from name if necessary. |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 58 | |
| Rob Landley | de69527 | 2008-02-24 03:48:06 -0600 | [diff] [blame^] | 59 | if (!depth) s = strrchr(src, '/'); |
| 60 | if (s) s++; |
| 61 | else s=src; |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 62 | |
| 63 | // Determine location to create new file/directory at. |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 64 | |
| Rob Landley | de69527 | 2008-02-24 03:48:06 -0600 | [diff] [blame^] | 65 | if (TT.destisdir || depth) s = xmsprintf("%s/%s", TT.destname, s); |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 66 | else s = xstrdup(TT.destname); |
| 67 | |
| 68 | // Copy directory or file to destination. |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 69 | |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 70 | if (S_ISDIR(srcst->st_mode)) { |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 71 | struct stat st2; |
| 72 | |
| 73 | // Always make directory writeable to us, so we can create files in it. |
| 74 | // |
| 75 | // Yes, there's a race window between mkdir() and open() so it's |
| 76 | // possible that -p can be made to chown a directory other than the one |
| 77 | // we created. The closest we can do to closing this is make sure |
| 78 | // that what we open _is_ a directory rather than something else. |
| 79 | |
| 80 | if (mkdir(s, srcst->st_mode | 0200) || 0>(fdout=open(s, 0)) |
| 81 | || fstat(fdout, &st2) || !S_ISDIR(st2.st_mode)) |
| 82 | { |
| 83 | perror_exit("mkdir '%s'", s); |
| 84 | } |
| Rob Landley | de69527 | 2008-02-24 03:48:06 -0600 | [diff] [blame^] | 85 | } else if ((depth || (toys.optflags & FLAG_d)) && S_ISLNK(srcst->st_mode)) { |
| 86 | struct stat st2; |
| 87 | char *link = xreadlink(src); |
| 88 | |
| 89 | // Note: -p currently has no effect on symlinks. How do you get a |
| 90 | // filehandle to them? O_NOFOLLOW causes the open to fail. |
| 91 | if (!link || symlink(link, s)) perror_msg("link '%s'",s); |
| 92 | free(link); |
| 93 | } else if (toys.optflags & FLAG_l) { |
| 94 | if (link(src, s)) perror_msg("link '%s'"); |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 95 | } else { |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 96 | int fdin, i; |
| 97 | |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 98 | fdin = xopen(src, O_RDONLY); |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 99 | for (i=2 ; i; i--) { |
| 100 | fdout = open(s, O_RDWR|O_CREAT|O_TRUNC, srcst->st_mode); |
| 101 | if (fdout>=0 || !(toys.optflags & FLAG_f)) break; |
| 102 | unlink(s); |
| 103 | } |
| 104 | if (fdout<0) perror_exit("%s", s); |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 105 | xsendfile(fdin, fdout); |
| 106 | close(fdin); |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 107 | } |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 108 | |
| 109 | // Inability to set these isn't fatal, some require root access. |
| 110 | // Can't do fchmod() etc here because -p works on mkdir, too. |
| 111 | |
| 112 | if (toys.optflags & FLAG_p) { |
| 113 | int mask = umask(0); |
| 114 | struct utimbuf ut; |
| 115 | |
| 116 | fchown(fdout,srcst->st_uid, srcst->st_gid); |
| 117 | ut.actime = srcst->st_atime; |
| 118 | ut.modtime = srcst->st_mtime; |
| 119 | utime(s, &ut); |
| 120 | umask(mask); |
| 121 | } |
| 122 | xclose(fdout); |
| 123 | free(s); |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 124 | } |
| 125 | |
| 126 | // Callback from dirtree_read() for each file/directory under a source dir. |
| 127 | |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 128 | int cp_node(char *path, struct dirtree *node) |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 129 | { |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 130 | char *s = path+strlen(path); |
| 131 | struct dirtree *n = node; |
| Rob Landley | de69527 | 2008-02-24 03:48:06 -0600 | [diff] [blame^] | 132 | int depth = 0; |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 133 | |
| 134 | // Find appropriate chunk of path for destination. |
| 135 | |
| 136 | for (;;) { |
| 137 | if (*(--s) == '/') { |
| Rob Landley | de69527 | 2008-02-24 03:48:06 -0600 | [diff] [blame^] | 138 | depth++; |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 139 | if (!n->parent) break; |
| 140 | n = n->parent; |
| 141 | } |
| 142 | } |
| 143 | s++; |
| 144 | |
| Rob Landley | de69527 | 2008-02-24 03:48:06 -0600 | [diff] [blame^] | 145 | cp_file(s, &(node->st), depth); |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 146 | return 0; |
| 147 | } |
| 148 | |
| 149 | void cp_main(void) |
| 150 | { |
| 151 | struct stat st; |
| 152 | int i; |
| 153 | |
| 154 | // Grab target argument. (Guaranteed to be there due to "<2" above.) |
| 155 | |
| 156 | TT.destname = toys.optargs[--toys.optc]; |
| 157 | |
| 158 | // If destination doesn't exist, are we ok with that? |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 159 | |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 160 | if (stat(TT.destname, &st)) { |
| 161 | if (toys.optc>1) goto error_notdir; |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 162 | TT.destisnew++; |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 163 | |
| 164 | // If destination exists... |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 165 | |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 166 | } else { |
| 167 | if (S_ISDIR(st.st_mode)) TT.destisdir++; |
| 168 | else if (toys.optc > 1) goto error_notdir; |
| 169 | } |
| 170 | |
| 171 | // Handle sources |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 172 | |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 173 | for (i=0; i<toys.optc; i++) { |
| 174 | char *src = toys.optargs[i]; |
| 175 | |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 176 | // Skip nonexistent sources, or src==dest. |
| 177 | |
| 178 | if (!strcmp(src, TT.destname)) continue; |
| 179 | if ((toys.optflags & FLAG_d) ? lstat(src, &st) : stat(src, &st)) |
| 180 | { |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 181 | perror_msg("'%s'", src); |
| 182 | toys.exitval = 1; |
| 183 | continue; |
| 184 | } |
| 185 | |
| 186 | // Copy directory or file. |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 187 | |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 188 | if (S_ISDIR(st.st_mode)) { |
| 189 | if (toys.optflags & FLAG_r) { |
| Rob Landley | de69527 | 2008-02-24 03:48:06 -0600 | [diff] [blame^] | 190 | cp_file(src, &st, 0); |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 191 | strncpy(toybuf, src, sizeof(toybuf)-1); |
| 192 | toybuf[sizeof(toybuf)-1]=0; |
| 193 | dirtree_read(toybuf, NULL, cp_node); |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 194 | } else error_msg("Skipped dir '%s'", src); |
| Rob Landley | de69527 | 2008-02-24 03:48:06 -0600 | [diff] [blame^] | 195 | } else cp_file(src, &st, 0); |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 196 | } |
| Rob Landley | 7f184fa | 2008-02-21 04:44:42 -0600 | [diff] [blame] | 197 | |
| Rob Landley | 6e6871c | 2008-02-20 01:47:56 -0600 | [diff] [blame] | 198 | return; |
| 199 | |
| 200 | error_notdir: |
| 201 | error_exit("'%s' isn't a directory", TT.destname); |
| 202 | } |