1/* Copyright 2008 Rob Landley <rob@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 18USE_CP(NEWTOY(cp, "<2"USE_CP_PRESERVE("(preserve):;")"D(parents)RHLPprdaslvnF(remove-destination)fi[-HLPd][-ni]", TOYFLAG_BIN)) 19USE_MV(NEWTOY(mv, "<2vnF(remove-destination)fi[-ni]", TOYFLAG_BIN)) 20USE_INSTALL(NEWTOY(install, "<1cdDpsvm:o:g:", TOYFLAG_USR|TOYFLAG_BIN)) 21 22config 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 -D Create leading dirs under DEST (--parents) 32 -f Delete destination files we can't write to 33 -F Delete any existing destination file first (--remove-destination) 34 -i Interactive, prompt before overwriting existing DEST 35 -p Preserve timestamps, ownership, and mode 36 -R Recurse into subdirectories (DEST must be a directory) 37 -H Follow symlinks listed on command line 38 -L Follow all symlinks 39 -P Do not follow symlinks [default] 40 -a Same as -dpr 41 -d Don't dereference symlinks 42 -l Hard link instead of copy 43 -n No clobber (don't overwrite DEST) 44 -r Synonym for -R 45 -s Symlink instead of copy 46 -v Verbose 47 48config CP_PRESERVE 49 bool "cp --preserve support" 50 default y 51 depends on CP 52 help 53 usage: cp [--preserve=motcxa] 54 55 --preserve takes either a comma separated list of attributes, or the first 56 letter(s) of: 57 58 mode - permissions (ignore umask for rwx, copy suid and sticky bit) 59 ownership - user and group 60 timestamps - file creation, modification, and access times. 61 context - security context 62 xattr - extended attributes 63 all - all of the above 64 65config MV 66 bool "mv" 67 default y 68 help 69 usage: mv [-fivn] SOURCE... DEST 70 71 -f Force copy by deleting destination file 72 -i Interactive, prompt before overwriting existing DEST 73 -v Verbose 74 -n No clobber (don't overwrite DEST) 75 76config INSTALL 77 bool "install" 78 default y 79 help 80 usage: install [-dDpsv] [-o USER] [-g GROUP] [-m MODE] [SOURCE...] DEST 81 82 Copy files and set attributes. 83 84 -d Act like mkdir -p 85 -D Create leading directories for DEST 86 -g Make copy belong to GROUP 87 -m Set permissions to MODE 88 -o Make copy belong to USER 89 -p Preserve timestamps 90 -s Call "strip -p" 91 -v Verbose 92*/ 93 94#define FORCE_FLAGS 95#define FOR_cp 96#include "toys.h" 97 98GLOBALS( 99 union { 100 // install's options 101 struct { 102 char *g, *o, *m; 103 } i; 104 // cp's options 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 118struct 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 126static 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 | (DIRTREE_SYMFOLLOW*!!FLAG(L)); 212 213 // Hardlink 214 215 } else if (flags & FLAG_l) { 216 if (!linkat(tfd, try->name, cfd, catch, 0)) err = 0; 217 218 // Copy tree as symlinks. For non-absolute paths this involves 219 // appending the right number of .. entries as you go down the tree. 220 221 } else if (flags & FLAG_s) { 222 char *s; 223 struct dirtree *or; 224 int dotdots = 0; 225 226 s = dirtree_path(try, 0); 227 for (or = try; or->parent; or = or->parent) dotdots++; 228 229 if (*or->name == '/') dotdots = 0; 230 if (dotdots) { 231 char *s2 = xmprintf("%*c%s", 3*dotdots, ' ', s); 232 free(s); 233 s = s2; 234 while(dotdots--) { 235 memcpy(s2, "../", 3); 236 s2 += 3; 237 } 238 } 239 if (!symlinkat(s, cfd, catch)) { 240 err = 0; 241 fdout = AT_FDCWD; 242 } 243 free(s); 244 245 // Do something _other_ than copy contents of a file? 246 } else if (!S_ISREG(try->st.st_mode) 247 && (try->parent || (flags & (FLAG_a|FLAG_r)))) 248 { 249 int i; 250 251 // make symlink, or make block/char/fifo/socket 252 if (S_ISLNK(try->st.st_mode) 253 ? ((i = readlinkat0(tfd, try->name, toybuf, sizeof(toybuf))) && 254 ((!unlinkat(cfd, catch, 0) || ENOENT == errno) && 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 // When copying contents use symlink target's attributes 272 if (S_ISLNK(try->st.st_mode)) fstat(fdin, &try->st); 273 fdout = openat(cfd, catch, O_RDWR|O_CREAT|O_TRUNC, try->st.st_mode); 274 if (fdout >= 0) { 275 xsendfile(fdin, fdout); 276 err = 0; 277 } 278 279 // We only copy xattrs for files because there's no flistxattrat() 280 if (TT.pflags&(_CP_xattr|_CP_context)) { 281 ssize_t listlen = xattr_flist(fdin, 0, 0), len; 282 char *name, *value, *list; 283 284 if (listlen>0) { 285 list = xmalloc(listlen); 286 xattr_flist(fdin, list, listlen); 287 list[listlen-1] = 0; // I do not trust this API. 288 for (name = list; name-list < listlen; name += strlen(name)+1) { 289 if (!(TT.pflags&_CP_xattr) && strncmp(name, "security.", 9)) 290 continue; 291 if ((len = xattr_fget(fdin, name, 0, 0))>0) { 292 value = xmalloc(len); 293 if (len == xattr_fget(fdin, name, value, len)) 294 if (xattr_fset(fdout, name, value, len, 0)) 295 perror_msg("%s setxattr(%s=%s)", catch, name, value); 296 free(value); 297 } 298 } 299 free(list); 300 } 301 } 302 303 close(fdin); 304 } 305 } while (err && (flags & (FLAG_f|FLAG_n)) && !unlinkat(cfd, catch, 0)); 306 } 307 308 // Did we make a thing? 309 if (fdout != -1) { 310 int rc; 311 312 // Inability to set --preserve isn't fatal, some require root access. 313 314 // ownership 315 if (TT.pflags & _CP_ownership) { 316 317 // permission bits already correct for mknod and don't apply to symlink 318 // If we can't get a filehandle to the actual object, use racy functions 319 if (fdout == AT_FDCWD) 320 rc = fchownat(cfd, catch, try->st.st_uid, try->st.st_gid, 321 AT_SYMLINK_NOFOLLOW); 322 else rc = fchown(fdout, try->st.st_uid, try->st.st_gid); 323 if (rc && !geteuid()) { 324 char *pp; 325 326 perror_msg("chown '%s'", pp = dirtree_path(try, 0)); 327 free(pp); 328 } 329 } 330 331 // timestamp 332 if (TT.pflags & _CP_timestamps) { 333 struct timespec times[] = {try->st.st_atim, try->st.st_mtim}; 334 335 if (fdout == AT_FDCWD) utimensat(cfd, catch, times, AT_SYMLINK_NOFOLLOW); 336 else futimens(fdout, times); 337 } 338 339 // mode comes last because other syscalls can strip suid bit 340 if (fdout != AT_FDCWD) { 341 if (TT.pflags & _CP_mode) fchmod(fdout, try->st.st_mode); 342 xclose(fdout); 343 } 344 345 if (CFG_MV && toys.which->name[0] == 'm') 346 if (unlinkat(tfd, try->name, S_ISDIR(try->st.st_mode) ? AT_REMOVEDIR :0)) 347 err = "%s"; 348 } 349 350 if (err) { 351 char *f = 0; 352 353 if (catch == try->name) { 354 f = dirtree_path(try, 0); 355 while (try->parent) try = try->parent; 356 catch = xmprintf("%s%s", TT.destname, f+strlen(try->name)); 357 free(f); 358 f = catch; 359 } 360 perror_msg(err, catch); 361 free(f); 362 } 363 return 0; 364} 365 366void cp_main(void) 367{ 368 char *destname = toys.optargs[--toys.optc]; 369 int i, destdir = !stat(destname, &TT.top) && S_ISDIR(TT.top.st_mode); 370 371 if ((toys.optc>1 || FLAG(D)) && !destdir) 372 error_exit("'%s' not directory", destname); 373 374 if (FLAG(a)||FLAG(p)) TT.pflags = _CP_mode|_CP_ownership|_CP_timestamps; 375 376 // Not using comma_args() (yet?) because interpeting as letters. 377 if (CFG_CP_PRESERVE && FLAG(preserve)) { 378 char *pre = xstrdup(TT.c.preserve ? TT.c.preserve : "mot"), *s; 379 380 if (comma_remove(pre, "all")) TT.pflags = ~0; 381 for (i=0; i<ARRAY_LEN(cp_preserve); i++) 382 while (comma_remove(pre, cp_preserve[i].name)) TT.pflags |= 1<<i; 383 if (*pre) { 384 385 // Try to interpret as letters, commas won't set anything this doesn't. 386 for (s = pre; *s; s++) { 387 for (i=0; i<ARRAY_LEN(cp_preserve); i++) 388 if (*s == *cp_preserve[i].name) break; 389 if (i == ARRAY_LEN(cp_preserve)) { 390 if (*s == 'a') TT.pflags = ~0; 391 else break; 392 } else TT.pflags |= 1<<i; 393 } 394 395 if (*s) error_exit("bad --preserve=%s", pre); 396 } 397 free(pre); 398 } 399 if (TT.pflags & _CP_mode) umask(0); 400 if (!TT.callback) TT.callback = cp_node; 401 402 // Loop through sources 403 for (i=0; i<toys.optc; i++) { 404 char *src = toys.optargs[i], *trail = src; 405 int rc = 1; 406 407 if (CFG_MV && toys.which->name[0] == 'm') { 408 while (*++trail); 409 if (*--trail == '/') *trail = 0; 410 } 411 412 if (destdir) { 413 char *s = FLAG(D) ? src : getbasename(src); 414 415 TT.destname = xmprintf("%s/%s", destname, s); 416 if (FLAG(D)) { 417 if (!(s = fileunderdir(TT.destname, destname))) { 418 error_msg("%s not under %s", TT.destname, destname); 419 continue; 420 } 421 // TODO: .. follows abspath, not links... 422 free(s); 423 mkpath(TT.destname); 424 } 425 } else TT.destname = destname; 426 427 errno = EXDEV; 428 if (CFG_MV && toys.which->name[0] == 'm') { 429 int force = FLAG(f), no_clobber = FLAG(n); 430 431 if (!force || no_clobber) { 432 struct stat st; 433 int exists = !stat(TT.destname, &st); 434 435 // Prompt if -i or file isn't writable. Technically "is writable" is 436 // more complicated (022 is not writeable by the owner, just everybody 437 // _else_) but I don't care. 438 if (exists && (FLAG(i) || !(st.st_mode & 0222))) { 439 fprintf(stderr, "%s: overwrite '%s'", toys.which->name, TT.destname); 440 if (!yesno(1)) rc = 0; 441 else unlink(TT.destname); 442 } 443 // if -n and dest exists, don't try to rename() or copy 444 if (exists && no_clobber) rc = 0; 445 } 446 if (rc) rc = rename(src, TT.destname); 447 if (errno && !*trail) *trail = '/'; 448 } 449 450 // Copy if we didn't mv, skipping nonexistent sources 451 if (rc) { 452 if (errno!=EXDEV || dirtree_flagread(src, DIRTREE_SHUTUP+ 453 DIRTREE_SYMFOLLOW*!!(FLAG(H)||FLAG(L)), TT.callback)) 454 perror_msg("bad '%s'", src); 455 } 456 if (destdir) free(TT.destname); 457 } 458} 459 460void mv_main(void) 461{ 462 toys.optflags |= FLAG_d|FLAG_p|FLAG_R; 463 464 cp_main(); 465} 466 467// Export cp flags into install's flag context. 468 469static inline int cp_flag_F(void) { return FLAG_F; }; 470static inline int cp_flag_p(void) { return FLAG_p; }; 471static inline int cp_flag_v(void) { return FLAG_v; }; 472 473// Switch to install's flag context 474#define CLEANUP_cp 475#define FOR_install 476#include <generated/flags.h> 477 478static int install_node(struct dirtree *try) 479{ 480 try->st.st_mode = TT.i.m ? string_to_mode(TT.i.m, try->st.st_mode) : 0755; 481 if (TT.i.g) try->st.st_gid = TT.gid; 482 if (TT.i.o) try->st.st_uid = TT.uid; 483 484 // Always returns 0 because no -r 485 cp_node(try); 486 487 // No -r so always one level deep, so destname as set by cp_node() is correct 488 if (FLAG(s) && xrun((char *[]){"strip", "-p", TT.destname, 0})) 489 toys.exitval = 1; 490 491 return 0; 492} 493 494void install_main(void) 495{ 496 char **ss; 497 int flags = toys.optflags; 498 499 TT.uid = TT.i.o ? xgetuid(TT.i.o) : -1; 500 TT.gid = TT.i.g ? xgetgid(TT.i.g) : -1; 501 502 if (flags & FLAG_d) { 503 for (ss = toys.optargs; *ss; ss++) { 504 if (mkpathat(AT_FDCWD, *ss, 0777, MKPATHAT_MKLAST | MKPATHAT_MAKE)) perror_msg_raw(*ss); 505 if (flags & (FLAG_g|FLAG_o)) 506 if (lchown(*ss, TT.uid, TT.gid)) perror_msg("chown '%s'", *ss); 507 if (flags & FLAG_v) printf("%s\n", *ss); 508 } 509 510 return; 511 } 512 513 if (FLAG(D)) { 514 TT.destname = toys.optargs[toys.optc-1]; 515 if (mkpathat(AT_FDCWD, TT.destname, 0, MKPATHAT_MAKE)) 516 perror_exit("-D '%s'", TT.destname); 517 if (toys.optc == 1) return; 518 } 519 if (toys.optc < 2) error_exit("needs 2 args"); 520 521 // Translate flags from install to cp 522 toys.optflags = cp_flag_F(); 523 if (flags & FLAG_v) toys.optflags |= cp_flag_v(); 524 if (flags & (FLAG_p|FLAG_o|FLAG_g)) toys.optflags |= cp_flag_p(); 525 526 TT.callback = install_node; 527 cp_main(); 528} 529