1 /* Copyright 2008 Rob Landley <rob (at) landley.net> 2 * 3 * See http://opengroup.org/onlinepubs/9699919799/utilities/cp.html 4 * And http://opengroup.org/onlinepubs/9699919799/utilities/mv.html 5 * And http://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic.html#INSTALL 6 * 7 * Posix says "cp -Rf dir file" shouldn't delete file, but our -f does. 8 * 9 * Deviations from posix: -adlnrsvF, --preserve... about half the 10 * functionality in this cp isn't in posix. Posix is stuck in the 1970's. 11 * 12 * TODO: --preserve=links 13 * TODO: what's this _CP_mode system.posix_acl_ business? We chmod()? 14 15 // options shared between mv/cp must be in same order (right to left) 16 // for FLAG macros to work out right in shared infrastructure. 17 18 USE_CP(NEWTOY(cp, "<2"USE_CP_PRESERVE("(preserve):;")"RHLPprdaslvnF(remove-destination)fi[-HLPd][-ni]", TOYFLAG_BIN)) 19 USE_MV(NEWTOY(mv, "<2vnF(remove-destination)fi[-ni]", TOYFLAG_BIN)) 20 USE_INSTALL(NEWTOY(install, "<1cdDpsvm:o:g:", TOYFLAG_USR|TOYFLAG_BIN)) 21 22 config CP 23 bool "cp" 24 default y 25 help 26 usage: cp [-adlnrsvfipRHLP] SOURCE... DEST 27 28 Copy files from SOURCE to DEST. If more than one SOURCE, DEST must 29 be a directory. 30 31 -f delete destination files we can't write to 32 -F delete any existing destination file first (--remove-destination) 33 -i interactive, prompt before overwriting existing DEST 34 -p preserve timestamps, ownership, and mode 35 -R recurse into subdirectories (DEST must be a directory) 36 -H Follow symlinks listed on command line 37 -L Follow all symlinks 38 -P Do not follow symlinks [default] 39 -a same as -dpr 40 -d don't dereference symlinks 41 -l hard link instead of copy 42 -n no clobber (don't overwrite DEST) 43 -r synonym for -R 44 -s symlink instead of copy 45 -v verbose 46 47 config CP_PRESERVE 48 bool "cp --preserve support" 49 default y 50 depends on CP 51 help 52 usage: cp [--preserve=motcxa] 53 54 --preserve takes either a comma separated list of attributes, or the first 55 letter(s) of: 56 57 mode - permissions (ignore umask for rwx, copy suid and sticky bit) 58 ownership - user and group 59 timestamps - file creation, modification, and access times. 60 context - security context 61 xattr - extended attributes 62 all - all of the above 63 64 config MV 65 bool "mv" 66 default y 67 help 68 usage: mv [-fivn] SOURCE... DEST" 69 70 -f force copy by deleting destination file 71 -i interactive, prompt before overwriting existing DEST 72 -v verbose 73 -n no clobber (don't overwrite DEST) 74 75 config INSTALL 76 bool "install" 77 default y 78 help 79 usage: install [-dDpsv] [-o USER] [-g GROUP] [-m MODE] [SOURCE...] DEST 80 81 Copy files and set attributes. 82 83 -d Act like mkdir -p 84 -D Create leading directories for DEST 85 -g Make copy belong to GROUP 86 -m Set permissions to MODE 87 -o Make copy belong to USER 88 -p Preserve timestamps 89 -s Call "strip -p" 90 -v Verbose 91 */ 92 93 #define FORCE_FLAGS 94 #define FOR_cp 95 #include "toys.h" 96 97 GLOBALS( 98 union { 99 struct { 100 // install's options 101 char *group; 102 char *user; 103 char *mode; 104 } i; 105 struct { 106 char *preserve; 107 } c; 108 }; 109 110 char *destname; 111 struct stat top; 112 int (*callback)(struct dirtree *try); 113 uid_t uid; 114 gid_t gid; 115 int pflags; 116 ) 117 118 struct cp_preserve { 119 char *name; 120 } static const cp_preserve[] = TAGGED_ARRAY(CP, 121 {"mode"}, {"ownership"}, {"timestamps"}, {"context"}, {"xattr"}, 122 ); 123 124 // Callback from dirtree_read() for each file/directory under a source dir. 125 126 static int cp_node(struct dirtree *try) 127 { 128 int fdout = -1, cfd = try->parent ? try->parent->extra : AT_FDCWD, 129 tfd = dirtree_parentfd(try); 130 unsigned flags = toys.optflags; 131 char *catch = try->parent ? try->name : TT.destname, *err = "%s"; 132 struct stat cst; 133 134 if (!dirtree_notdotdot(try)) return 0; 135 136 // If returning from COMEAGAIN, jump straight to -p logic at end. 137 if (S_ISDIR(try->st.st_mode) && try->again) { 138 fdout = try->extra; 139 err = 0; 140 } else { 141 142 // -d is only the same as -r for symlinks, not for directories 143 if (S_ISLNK(try->st.st_mode) && (flags & FLAG_d)) flags |= FLAG_r; 144 145 // Detect recursive copies via repeated top node (cp -R .. .) or 146 // identical source/target (fun with hardlinks). 147 if ((TT.top.st_dev == try->st.st_dev && TT.top.st_ino == try->st.st_ino 148 && (catch = TT.destname)) 149 || (!fstatat(cfd, catch, &cst, 0) && cst.st_dev == try->st.st_dev 150 && cst.st_ino == try->st.st_ino)) 151 { 152 error_msg("'%s' is '%s'", catch, err = dirtree_path(try, 0)); 153 free(err); 154 155 return 0; 156 } 157 158 // Handle -invF 159 160 if (!faccessat(cfd, catch, F_OK, 0) && !S_ISDIR(cst.st_mode)) { 161 char *s; 162 163 if (S_ISDIR(try->st.st_mode)) { 164 error_msg("dir at '%s'", s = dirtree_path(try, 0)); 165 free(s); 166 return 0; 167 } else if ((flags & FLAG_F) && unlinkat(cfd, catch, 0)) { 168 error_msg("unlink '%s'", catch); 169 return 0; 170 } else if (flags & FLAG_n) return 0; 171 else if (flags & FLAG_i) { 172 fprintf(stderr, "%s: overwrite '%s'", toys.which->name, 173 s = dirtree_path(try, 0)); 174 free(s); 175 if (!yesno(1)) return 0; 176 } 177 } 178 179 if (flags & FLAG_v) { 180 char *s = dirtree_path(try, 0); 181 printf("%s '%s'\n", toys.which->name, s); 182 free(s); 183 } 184 185 // Loop for -f retry after unlink 186 do { 187 188 // directory, hardlink, symlink, mknod (char, block, fifo, socket), file 189 190 // Copy directory 191 192 if (S_ISDIR(try->st.st_mode)) { 193 struct stat st2; 194 195 if (!(flags & (FLAG_a|FLAG_r|FLAG_R))) { 196 err = "Skipped dir '%s'"; 197 catch = try->name; 198 break; 199 } 200 201 // Always make directory writeable to us, so we can create files in it. 202 // 203 // Yes, there's a race window between mkdir() and open() so it's 204 // possible that -p can be made to chown a directory other than the one 205 // we created. The closest we can do to closing this is make sure 206 // that what we open _is_ a directory rather than something else. 207 208 if (!mkdirat(cfd, catch, try->st.st_mode | 0200) || errno == EEXIST) 209 if (-1 != (try->extra = openat(cfd, catch, O_NOFOLLOW))) 210 if (!fstat(try->extra, &st2) && S_ISDIR(st2.st_mode)) 211 return DIRTREE_COMEAGAIN 212 | (DIRTREE_SYMFOLLOW*!!(toys.optflags&FLAG_L)); 213 214 // Hardlink 215 216 } else if (flags & FLAG_l) { 217 if (!linkat(tfd, try->name, cfd, catch, 0)) err = 0; 218 219 // Copy tree as symlinks. For non-absolute paths this involves 220 // appending the right number of .. entries as you go down the tree. 221 222 } else if (flags & FLAG_s) { 223 char *s; 224 struct dirtree *or; 225 int dotdots = 0; 226 227 s = dirtree_path(try, 0); 228 for (or = try; or->parent; or = or->parent) dotdots++; 229 230 if (*or->name == '/') dotdots = 0; 231 if (dotdots) { 232 char *s2 = xmprintf("%*c%s", 3*dotdots, ' ', s); 233 free(s); 234 s = s2; 235 while(dotdots--) { 236 memcpy(s2, "../", 3); 237 s2 += 3; 238 } 239 } 240 if (!symlinkat(s, cfd, catch)) { 241 err = 0; 242 fdout = AT_FDCWD; 243 } 244 free(s); 245 246 // Do something _other_ than copy contents of a file? 247 } else if (!S_ISREG(try->st.st_mode) 248 && (try->parent || (flags & (FLAG_a|FLAG_r)))) 249 { 250 int i; 251 252 // make symlink, or make block/char/fifo/socket 253 if (S_ISLNK(try->st.st_mode) 254 ? ((i = readlinkat0(tfd, try->name, toybuf, sizeof(toybuf))) && 255 !symlinkat(toybuf, cfd, catch)) 256 : !mknodat(cfd, catch, try->st.st_mode, try->st.st_rdev)) 257 { 258 err = 0; 259 fdout = AT_FDCWD; 260 } 261 262 // Copy contents of file. 263 } else { 264 int fdin; 265 266 fdin = openat(tfd, try->name, O_RDONLY); 267 if (fdin < 0) { 268 catch = try->name; 269 break; 270 } 271 fdout = openat(cfd, catch, O_RDWR|O_CREAT|O_TRUNC, try->st.st_mode); 272 if (fdout >= 0) { 273 xsendfile(fdin, fdout); 274 err = 0; 275 } 276 277 // We only copy xattrs for files because there's no flistxattrat() 278 if (TT.pflags&(_CP_xattr|_CP_context)) { 279 ssize_t listlen = flistxattr(fdin, 0, 0), len; 280 char *name, *value, *list; 281 282 if (listlen>0) { 283 list = xmalloc(listlen); 284 flistxattr(fdin, list, listlen); 285 list[listlen-1] = 0; // I do not trust this API. 286 for (name = list; name-list < listlen; name += strlen(name)+1) { 287 if (!(TT.pflags&_CP_xattr) && strncmp(name, "security.", 9)) 288 continue; 289 if ((len = fgetxattr(fdin, name, 0, 0))>0) { 290 value = xmalloc(len); 291 if (len == fgetxattr(fdin, name, value, len)) 292 if (fsetxattr(fdout, name, value, len, 0)) 293 perror_msg("%s setxattr(%s=%s)", catch, name, value); 294 free(value); 295 } 296 } 297 free(list); 298 } 299 } 300 301 close(fdin); 302 } 303 } while (err && (flags & (FLAG_f|FLAG_n)) && !unlinkat(cfd, catch, 0)); 304 } 305 306 // Did we make a thing? 307 if (fdout != -1) { 308 int rc; 309 310 // Inability to set --preserve isn't fatal, some require root access. 311 312 // ownership 313 if (TT.pflags & _CP_ownership) { 314 315 // permission bits already correct for mknod and don't apply to symlink 316 // If we can't get a filehandle to the actual object, use racy functions 317 if (fdout == AT_FDCWD) 318 rc = fchownat(cfd, catch, try->st.st_uid, try->st.st_gid, 319 AT_SYMLINK_NOFOLLOW); 320 else rc = fchown(fdout, try->st.st_uid, try->st.st_gid); 321 if (rc && !geteuid()) { 322 char *pp; 323 324 perror_msg("chown '%s'", pp = dirtree_path(try, 0)); 325 free(pp); 326 } 327 } 328 329 // timestamp 330 if (TT.pflags & _CP_timestamps) { 331 struct timespec times[] = {try->st.st_atim, try->st.st_mtim}; 332 333 if (fdout == AT_FDCWD) utimensat(cfd, catch, times, AT_SYMLINK_NOFOLLOW); 334 else futimens(fdout, times); 335 } 336 337 // mode comes last because other syscalls can strip suid bit 338 if (fdout != AT_FDCWD) { 339 if (TT.pflags & _CP_mode) fchmod(fdout, try->st.st_mode); 340 xclose(fdout); 341 } 342 343 if (CFG_MV && toys.which->name[0] == 'm') 344 if (unlinkat(tfd, try->name, S_ISDIR(try->st.st_mode) ? AT_REMOVEDIR :0)) 345 err = "%s"; 346 } 347 348 if (err) { 349 char *f = 0; 350 351 if (catch == try->name) { 352 f = dirtree_path(try, 0); 353 while (try->parent) try = try->parent; 354 catch = xmprintf("%s%s", TT.destname, f+strlen(try->name)); 355 free(f); 356 f = catch; 357 } 358 perror_msg(err, catch); 359 free(f); 360 } 361 return 0; 362 } 363 364 void cp_main(void) 365 { 366 char *destname = toys.optargs[--toys.optc]; 367 int i, destdir = !stat(destname, &TT.top) && S_ISDIR(TT.top.st_mode); 368 369 if (toys.optc>1 && !destdir) error_exit("'%s' not directory", destname); 370 371 if (toys.optflags & (FLAG_a|FLAG_p)) { 372 TT.pflags = CP_mode|CP_ownership|CP_timestamps; 373 umask(0); 374 } 375 // Not using comma_args() (yet?) because interpeting as letters. 376 if (CFG_CP_PRESERVE && (toys.optflags & FLAG_preserve)) { 377 char *pre = xstrdup(TT.c.preserve), *s; 378 379 if (comma_scan(pre, "all", 1)) TT.pflags = ~0; 380 for (i=0; i<ARRAY_LEN(cp_preserve); i++) 381 if (comma_scan(pre, cp_preserve[i].name, 1)) TT.pflags |= 1<<i; 382 if (*pre) { 383 384 // Try to interpret as letters, commas won't set anything this doesn't. 385 for (s = TT.c.preserve; *s; s++) { 386 for (i=0; i<ARRAY_LEN(cp_preserve); i++) 387 if (*s == *cp_preserve[i].name) break; 388 if (i == ARRAY_LEN(cp_preserve)) { 389 if (*s == 'a') TT.pflags = ~0; 390 else break; 391 } else TT.pflags |= 1<<i; 392 } 393 394 if (*s) error_exit("bad --preserve=%s", pre); 395 } 396 free(pre); 397 } 398 if (!TT.callback) TT.callback = cp_node; 399 400 // Loop through sources 401 for (i=0; i<toys.optc; i++) { 402 char *src = toys.optargs[i]; 403 int rc = 1; 404 405 if (destdir) TT.destname = xmprintf("%s/%s", destname, basename(src)); 406 else TT.destname = destname; 407 408 errno = EXDEV; 409 if (CFG_MV && toys.which->name[0] == 'm') { 410 int force = toys.optflags & FLAG_f, no_clobber = toys.optflags & FLAG_n; 411 412 if (!force || no_clobber) { 413 struct stat st; 414 int exists = !stat(TT.destname, &st); 415 416 // Prompt if -i or file isn't writable. Technically "is writable" is 417 // more complicated (022 is not writeable by the owner, just everybody 418 // _else_) but I don't care. 419 if (exists && ((toys.optflags & FLAG_i) || !(st.st_mode & 0222))) { 420 fprintf(stderr, "%s: overwrite '%s'", toys.which->name, TT.destname); 421 if (!yesno(1)) rc = 0; 422 else unlink(TT.destname); 423 } 424 // if -n and dest exists, don't try to rename() or copy 425 if (exists && no_clobber) rc = 0; 426 } 427 if (rc) rc = rename(src, TT.destname); 428 } 429 430 // Copy if we didn't mv, skipping nonexistent sources 431 if (rc) { 432 if (errno!=EXDEV || dirtree_flagread(src, DIRTREE_SHUTUP+ 433 DIRTREE_SYMFOLLOW*!!(toys.optflags&(FLAG_H|FLAG_L)), TT.callback)) 434 perror_msg("bad '%s'", src); 435 } 436 if (destdir) free(TT.destname); 437 } 438 } 439 440 void mv_main(void) 441 { 442 toys.optflags |= FLAG_d|FLAG_p|FLAG_R; 443 444 cp_main(); 445 } 446 447 // Export cp flags into install's flag context. 448 449 static inline int cp_flag_F(void) { return FLAG_F; }; 450 static inline int cp_flag_p(void) { return FLAG_p; }; 451 static inline int cp_flag_v(void) { return FLAG_v; }; 452 453 // Switch to install's flag context 454 #define CLEANUP_cp 455 #define FOR_install 456 #include <generated/flags.h> 457 458 static int install_node(struct dirtree *try) 459 { 460 try->st.st_mode = (TT.i.mode) 461 ? string_to_mode(TT.i.mode, try->st.st_mode) : 0755; 462 if (TT.i.group) try->st.st_gid = TT.gid; 463 if (TT.i.user) try->st.st_uid = TT.uid; 464 465 // Always returns 0 because no -r 466 cp_node(try); 467 468 // No -r so always one level deep, so destname as set by cp_node() is correct 469 if (toys.optflags & FLAG_s) 470 if (xrun((char *[]){"strip", "-p", TT.destname, 0})) toys.exitval = 1; 471 472 return 0; 473 } 474 475 void install_main(void) 476 { 477 char **ss; 478 int flags = toys.optflags; 479 480 if (flags & FLAG_d) { 481 for (ss = toys.optargs; *ss; ss++) { 482 if (mkpathat(AT_FDCWD, *ss, 0777, 3)) perror_msg_raw(*ss); 483 if (flags & FLAG_v) printf("%s\n", *ss); 484 } 485 486 return; 487 } 488 489 if (toys.optflags & FLAG_D) { 490 TT.destname = toys.optargs[toys.optc-1]; 491 if (mkpathat(AT_FDCWD, TT.destname, 0, 2)) 492 perror_exit("-D '%s'", TT.destname); 493 if (toys.optc == 1) return; 494 } 495 if (toys.optc < 2) error_exit("needs 2 args"); 496 497 // Translate flags from install to cp 498 toys.optflags = cp_flag_F(); 499 if (flags & FLAG_v) toys.optflags |= cp_flag_v(); 500 if (flags & (FLAG_p|FLAG_o|FLAG_g)) toys.optflags |= cp_flag_p(); 501 502 if (TT.i.user) TT.uid = xgetuid(TT.i.user); 503 if (TT.i.group) TT.gid = xgetgid(TT.i.group); 504 505 TT.callback = install_node; 506 cp_main(); 507 } 508