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