1/* 2 * Copyright (c) 1989, 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * 5 * This code is derived from software contributed to Berkeley by 6 * Ken Smith of The State University of New York at Buffalo. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. All advertising materials mentioning features or use of this software 17 * must display the following acknowledgement: 18 * This product includes software developed by the University of 19 * California, Berkeley and its contributors. 20 * 4. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37#include <sys/cdefs.h> 38#ifndef lint 39__used static char const copyright[] = 40"@(#) Copyright (c) 1989, 1993, 1994\n\ 41 The Regents of the University of California. All rights reserved.\n"; 42#endif /* not lint */ 43 44#ifndef lint 45#if 0 46static char sccsid[] = "@(#)mv.c 8.2 (Berkeley) 4/2/94"; 47#endif 48#endif /* not lint */ 49#include <sys/cdefs.h> 50__RCSID("$FreeBSD: src/bin/mv/mv.c,v 1.39 2002/07/09 17:45:13 johan Exp $"); 51 52#include <sys/param.h> 53#include <sys/time.h> 54#include <sys/wait.h> 55#include <sys/stat.h> 56#include <sys/mount.h> 57 58#include <err.h> 59#include <errno.h> 60#include <fcntl.h> 61#include <grp.h> 62#include <limits.h> 63#include <paths.h> 64#include <pwd.h> 65#include <stdio.h> 66#include <stdlib.h> 67#include <string.h> 68#include <sysexits.h> 69#include <unistd.h> 70 71#ifdef __APPLE__ 72#include <copyfile.h> 73#include <sys/mount.h> 74#endif 75 76#ifdef __APPLE__ 77#include <get_compat.h> 78#else 79#define COMPAT_MODE(a,b) (1) 80#endif /* __APPLE__ */ 81 82#include "pathnames.h" 83 84int fflg, iflg, nflg, vflg; 85 86int copy(char *, char *); 87int do_move(char *, char *); 88int fastcopy(char *, char *, struct stat *); 89void usage(void); 90 91int 92main(int argc, char *argv[]) 93{ 94 size_t baselen, len; 95 int rval; 96 char *p, *endp; 97 struct stat sb; 98#ifdef __APPLE__ 99 struct stat fsb, tsb; 100#endif /* __APPLE__ */ 101 int ch; 102 char path[PATH_MAX]; 103 104 while ((ch = getopt(argc, argv, "finv")) != -1) 105 switch (ch) { 106 case 'i': 107 iflg = 1; 108 fflg = nflg = 0; 109 break; 110 case 'f': 111 fflg = 1; 112 iflg = nflg = 0; 113 break; 114 case 'n': 115 nflg = 1; 116 fflg = iflg = 0; 117 break; 118 case 'v': 119 vflg = 1; 120 break; 121 default: 122 usage(); 123 } 124 argc -= optind; 125 argv += optind; 126 127 if (argc < 2) 128 usage(); 129 130 /* 131 * If the stat on the target fails or the target isn't a directory, 132 * try the move. More than 2 arguments is an error in this case. 133 */ 134 if (stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) { 135 if (argc > 2) 136 usage(); 137 exit(do_move(argv[0], argv[1])); 138 } 139 140#ifdef __APPLE__ 141 if (argc == 2 && !lstat(argv[0], &fsb) && !lstat(argv[1], &tsb) && 142 fsb.st_ino == tsb.st_ino && fsb.st_dev == tsb.st_dev && 143 fsb.st_gen == tsb.st_gen) { 144 /* 145 * We appear to be trying to move a directory into itself, 146 * but it may be that the filesystem is case insensitive and 147 * we are trying to rename the directory to a case-variant. 148 * Ignoring trailing slashes, we look for any difference in 149 * the directory names. If there is a difference we do 150 * the rename, otherwise we fall-thru to the traditional 151 * error. Note the lstat calls above (rather than stat) 152 * permit the renaming of symlinks to case-variants. 153 */ 154 char *q; 155 156 for (p = argv[0] + strlen(argv[0]); p != argv[0]; ) { 157 p--; 158 if (*p != '/') 159 break; 160 } 161 for (q = argv[1] + strlen(argv[1]); q != argv[1]; ) { 162 q--; 163 if (*q != '/') 164 break; 165 } 166 for ( ; ; p--, q--) { 167 if (*p != *q) 168 exit(do_move(argv[0], argv[1])); 169 if (*p == '/') 170 break; 171 if (p == argv[0]) { 172 if (q == argv[1] || *(q-1) == '/') 173 break; 174 exit(do_move(argv[0], argv[1])); 175 } 176 if (q == argv[1]) { 177 if (p == argv[0] || *(p-1) == '/') 178 break; 179 exit(do_move(argv[0], argv[1])); 180 } 181 } 182 } 183#endif /* __APPLE__ */ 184 185 /* It's a directory, move each file into it. */ 186 if (strlen(argv[argc - 1]) > sizeof(path) - 1) 187 errx(1, "%s: destination pathname too long", *argv); 188 (void)strcpy(path, argv[argc - 1]); 189 baselen = strlen(path); 190 endp = &path[baselen]; 191 if (!baselen || *(endp - 1) != '/') { 192 *endp++ = '/'; 193 ++baselen; 194 } 195 for (rval = 0; --argc; ++argv) { 196 /* 197 * Find the last component of the source pathname. It 198 * may have trailing slashes. 199 */ 200 p = *argv + strlen(*argv); 201 while (p != *argv && p[-1] == '/') 202 --p; 203 while (p != *argv && p[-1] != '/') 204 --p; 205 206 if ((baselen + (len = strlen(p))) >= PATH_MAX) { 207 warnx("%s: destination pathname too long", *argv); 208 rval = 1; 209 } else { 210 memmove(endp, p, (size_t)len + 1); 211 if (COMPAT_MODE("bin/mv", "unix2003")) { 212 /* 213 * For Unix 2003 compatibility, check if old and new are 214 * same file, and produce an error * (like on Sun) that 215 * conformance test 66 in mv.ex expects. 216 */ 217 if (!stat(*argv, &fsb) && !stat(path, &tsb) && 218 fsb.st_ino == tsb.st_ino && 219 fsb.st_dev == tsb.st_dev && 220 fsb.st_gen == tsb.st_gen) { 221 (void)fprintf(stderr, "mv: %s and %s are identical\n", 222 *argv, path); 223 rval = 2; /* Like the Sun */ 224 } else { 225 if (do_move(*argv, path)) 226 rval = 1; 227 } 228 } else { 229 if (do_move(*argv, path)) 230 rval = 1; 231 } 232 } 233 } 234 exit(rval); 235} 236 237int 238do_move(char *from, char *to) 239{ 240 struct stat sb; 241 int ask, ch, first; 242 char modep[15]; 243 244 /* 245 * Check access. If interactive and file exists, ask user if it 246 * should be replaced. Otherwise if file exists but isn't writable 247 * make sure the user wants to clobber it. 248 */ 249 if (!fflg && !access(to, F_OK)) { 250 251 /* prompt only if source exist */ 252 if (lstat(from, &sb) == -1) { 253 warn("%s", from); 254 return (1); 255 } 256 257#define YESNO "(y/n [n]) " 258 ask = 0; 259 if (nflg) { 260 if (vflg) 261 printf("%s not overwritten\n", to); 262 return (0); 263 } else if (iflg) { 264 (void)fprintf(stderr, "overwrite %s? %s", to, YESNO); 265 ask = 1; 266 } else if (access(to, W_OK) && !stat(to, &sb)) { 267 strmode(sb.st_mode, modep); 268 (void)fprintf(stderr, "override %s%s%s/%s for %s? %s", 269 modep + 1, modep[9] == ' ' ? "" : " ", 270 user_from_uid(sb.st_uid, 0), 271 group_from_gid(sb.st_gid, 0), to, YESNO); 272 ask = 1; 273 } 274 if (ask) { 275 first = ch = getchar(); 276 while (ch != '\n' && ch != EOF) 277 ch = getchar(); 278 if (first != 'y' && first != 'Y') { 279 (void)fprintf(stderr, "not overwritten\n"); 280 return (0); 281 } 282 } 283 } 284 if (!rename(from, to)) { 285 if (vflg) 286 printf("%s -> %s\n", from, to); 287 return (0); 288 } 289 290 if (errno == EXDEV) { 291 struct statfs sfs; 292 char path[PATH_MAX]; 293 294 /* Can't mv(1) a mount point. */ 295 if (realpath(from, path) == NULL) { 296 warnx("cannot resolve %s: %s", from, path); 297 return (1); 298 } 299 if (!statfs(path, &sfs) && !strcmp(path, sfs.f_mntonname)) { 300 warnx("cannot rename a mount point"); 301 return (1); 302 } 303 } else { 304 warn("rename %s to %s", from, to); 305 return (1); 306 } 307 308 /* 309 * If rename fails because we're trying to cross devices, and 310 * it's a regular file, do the copy internally; otherwise, use 311 * cp and rm. 312 */ 313 if (lstat(from, &sb)) { 314 warn("%s", from); 315 return (1); 316 } 317 return (S_ISREG(sb.st_mode) ? 318 fastcopy(from, to, &sb) : copy(from, to)); 319} 320 321int 322fastcopy(char *from, char *to, struct stat *sbp) 323{ 324 struct timeval tval[2]; 325 static u_int blen; 326 static char *bp; 327 mode_t oldmode; 328 ssize_t nread; 329 int from_fd, to_fd; 330 331 if ((from_fd = open(from, O_RDONLY, 0)) < 0) { 332 warn("%s", from); 333 return (1); 334 } 335 if (blen < sbp->st_blksize) { 336 if (bp != NULL) 337 free(bp); 338 if ((bp = malloc((size_t)sbp->st_blksize)) == NULL) { 339 blen = 0; 340 warnx("malloc failed"); 341 return (1); 342 } 343 blen = sbp->st_blksize; 344 } 345 while ((to_fd = 346 open(to, O_CREAT | O_EXCL | O_TRUNC | O_WRONLY, 0)) < 0) { 347 if (errno == EEXIST && unlink(to) == 0) 348 continue; 349 warn("%s", to); 350 (void)close(from_fd); 351 return (1); 352 } 353#ifdef __APPLE__ 354 { 355 struct statfs sfs; 356 357 /* 358 * Pre-allocate blocks for the destination file if it 359 * resides on Xsan. 360 */ 361 if (fstatfs(to_fd, &sfs) == 0 && 362 strcmp(sfs.f_fstypename, "acfs") == 0) { 363 fstore_t fst; 364 365 fst.fst_flags = 0; 366 fst.fst_posmode = F_PEOFPOSMODE; 367 fst.fst_offset = 0; 368 fst.fst_length = sbp->st_size; 369 370 (void) fcntl(to_fd, F_PREALLOCATE, &fst); 371 } 372 } 373#endif /* __APPLE__ */ 374 while ((nread = read(from_fd, bp, (size_t)blen)) > 0) 375 if (write(to_fd, bp, (size_t)nread) != nread) { 376 warn("%s", to); 377 goto err; 378 } 379 if (nread < 0) { 380 warn("%s", from); 381err: if (unlink(to)) 382 warn("%s: remove", to); 383 (void)close(from_fd); 384 (void)close(to_fd); 385 return (1); 386 } 387#ifdef __APPLE__ 388 /* XATTR can fail if to_fd has mode 000 */ 389 if (fcopyfile(from_fd, to_fd, NULL, COPYFILE_ACL | COPYFILE_XATTR) < 0) { 390 warn("%s: unable to move extended attributes and ACL from %s", 391 to, from); 392 } 393#endif 394 (void)close(from_fd); 395 396 oldmode = sbp->st_mode & ALLPERMS; 397 if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) { 398 warn("%s: set owner/group (was: %lu/%lu)", to, 399 (u_long)sbp->st_uid, (u_long)sbp->st_gid); 400 if (oldmode & (S_ISUID | S_ISGID)) { 401 warnx( 402"%s: owner/group changed; clearing suid/sgid (mode was 0%03o)", 403 to, oldmode); 404 sbp->st_mode &= ~(S_ISUID | S_ISGID); 405 } 406 } 407 if (fchmod(to_fd, sbp->st_mode)) 408 warn("%s: set mode (was: 0%03o)", to, oldmode); 409 /* 410 * XXX 411 * NFS doesn't support chflags; ignore errors unless there's reason 412 * to believe we're losing bits. (Note, this still won't be right 413 * if the server supports flags and we were trying to *remove* flags 414 * on a file that we copied, i.e., that we didn't create.) 415 */ 416 errno = 0; 417 if (fchflags(to_fd, (u_int)sbp->st_flags)) 418 if (errno != ENOTSUP || sbp->st_flags != 0) 419 warn("%s: set flags (was: 0%07o)", to, sbp->st_flags); 420 421 tval[0].tv_sec = sbp->st_atime; 422 tval[1].tv_sec = sbp->st_mtime; 423 tval[0].tv_usec = tval[1].tv_usec = 0; 424 if (utimes(to, tval)) 425 warn("%s: set times", to); 426 427 if (close(to_fd)) { 428 warn("%s", to); 429 return (1); 430 } 431 432 if (unlink(from)) { 433 warn("%s: remove", from); 434 return (1); 435 } 436 if (vflg) 437 printf("%s -> %s\n", from, to); 438 return (0); 439} 440 441int 442copy(char *from, char *to) 443{ 444 int pid, status; 445 446 /* posix_spawn mv from to && rm from */ 447 448 if ((pid = fork()) == 0) { 449 execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", "--", from, to, 450 (char *)NULL); 451 warn("%s", _PATH_CP); 452 _exit(1); 453 } 454 if (waitpid(pid, &status, 0) == -1) { 455 warn("%s: waitpid", _PATH_CP); 456 return (1); 457 } 458 if (!WIFEXITED(status)) { 459 warnx("%s: did not terminate normally", _PATH_CP); 460 return (1); 461 } 462 if (WEXITSTATUS(status)) { 463 warnx("%s: terminated with %d (non-zero) status", 464 _PATH_CP, WEXITSTATUS(status)); 465 return (1); 466 } 467 if (!(pid = vfork())) { 468 execl(_PATH_RM, "mv", "-rf", "--", from, (char *)NULL); 469 warn("%s", _PATH_RM); 470 _exit(1); 471 } 472 if (waitpid(pid, &status, 0) == -1) { 473 warn("%s: waitpid", _PATH_RM); 474 return (1); 475 } 476 if (!WIFEXITED(status)) { 477 warnx("%s: did not terminate normally", _PATH_RM); 478 return (1); 479 } 480 if (WEXITSTATUS(status)) { 481 warnx("%s: terminated with %d (non-zero) status", 482 _PATH_RM, WEXITSTATUS(status)); 483 return (1); 484 } 485 return (0); 486} 487 488void 489usage(void) 490{ 491 492 (void)fprintf(stderr, "%s\n%s\n", 493 "usage: mv [-f | -i | -n] [-v] source target", 494 " mv [-f | -i | -n] [-v] source ... directory"); 495 exit(EX_USAGE); 496} 497