1139969Simp/*- 21556Srgrimes * Copyright (c) 1989, 1993, 1994 31556Srgrimes * The Regents of the University of California. All rights reserved. 41556Srgrimes * 51556Srgrimes * This code is derived from software contributed to Berkeley by 61556Srgrimes * Ken Smith of The State University of New York at Buffalo. 71556Srgrimes * 81556Srgrimes * Redistribution and use in source and binary forms, with or without 91556Srgrimes * modification, are permitted provided that the following conditions 101556Srgrimes * are met: 111556Srgrimes * 1. Redistributions of source code must retain the above copyright 121556Srgrimes * notice, this list of conditions and the following disclaimer. 131556Srgrimes * 2. Redistributions in binary form must reproduce the above copyright 141556Srgrimes * notice, this list of conditions and the following disclaimer in the 151556Srgrimes * documentation and/or other materials provided with the distribution. 161556Srgrimes * 4. Neither the name of the University nor the names of its contributors 171556Srgrimes * may be used to endorse or promote products derived from this software 181556Srgrimes * without specific prior written permission. 191556Srgrimes * 201556Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 211556Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 221556Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 231556Srgrimes * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 241556Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 251556Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 261556Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 271556Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 281556Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 291556Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 301556Srgrimes * SUCH DAMAGE. 311556Srgrimes */ 321556Srgrimes 33114433Sobrien#if 0 341556Srgrimes#ifndef lint 3520420Sstevestatic char const copyright[] = 361556Srgrimes"@(#) Copyright (c) 1989, 1993, 1994\n\ 371556Srgrimes The Regents of the University of California. All rights reserved.\n"; 381556Srgrimes#endif /* not lint */ 391556Srgrimes 401556Srgrimes#ifndef lint 4136049Scharnierstatic char sccsid[] = "@(#)mv.c 8.2 (Berkeley) 4/2/94"; 42114433Sobrien#endif /* not lint */ 4336049Scharnier#endif 4492974Sobrien#include <sys/cdefs.h> 4592974Sobrien__FBSDID("$FreeBSD$"); 461556Srgrimes 47149790Scsjp#include <sys/types.h> 48149790Scsjp#include <sys/acl.h> 491556Srgrimes#include <sys/param.h> 501556Srgrimes#include <sys/time.h> 511556Srgrimes#include <sys/wait.h> 521556Srgrimes#include <sys/stat.h> 5331664Seivind#include <sys/mount.h> 541556Srgrimes 551556Srgrimes#include <err.h> 561556Srgrimes#include <errno.h> 571556Srgrimes#include <fcntl.h> 5890644Simp#include <grp.h> 5977409Simp#include <limits.h> 6096806Sjmallett#include <paths.h> 6190644Simp#include <pwd.h> 621556Srgrimes#include <stdio.h> 631556Srgrimes#include <stdlib.h> 641556Srgrimes#include <string.h> 6550544Smharo#include <sysexits.h> 661556Srgrimes#include <unistd.h> 671556Srgrimes 68174935Sdds/* Exit code for a failed exec. */ 69174935Sdds#define EXEC_FAILED 127 70174935Sdds 71241320Sjhbstatic int fflg, hflg, iflg, nflg, vflg; 721556Srgrimes 73180604Sdelphijstatic int copy(const char *, const char *); 74180604Sdelphijstatic int do_move(const char *, const char *); 75180604Sdelphijstatic int fastcopy(const char *, const char *, struct stat *); 76180604Sdelphijstatic void usage(void); 77196841Straszstatic void preserve_fd_acls(int source_fd, int dest_fd, const char *source_path, 78196841Strasz const char *dest_path); 791556Srgrimes 801556Srgrimesint 8190110Simpmain(int argc, char *argv[]) 821556Srgrimes{ 8391085Smarkm size_t baselen, len; 8491085Smarkm int rval; 8590114Simp char *p, *endp; 861556Srgrimes struct stat sb; 871556Srgrimes int ch; 8877409Simp char path[PATH_MAX]; 891556Srgrimes 90241320Sjhb while ((ch = getopt(argc, argv, "fhinv")) != -1) 911556Srgrimes switch (ch) { 92241320Sjhb case 'h': 93241320Sjhb hflg = 1; 94241320Sjhb break; 951556Srgrimes case 'i': 9614154Swosch iflg = 1; 9792935Sobrien fflg = nflg = 0; 981556Srgrimes break; 991556Srgrimes case 'f': 1001556Srgrimes fflg = 1; 10192935Sobrien iflg = nflg = 0; 1021556Srgrimes break; 10392935Sobrien case 'n': 10492935Sobrien nflg = 1; 10592935Sobrien fflg = iflg = 0; 10692935Sobrien break; 10750544Smharo case 'v': 10850544Smharo vflg = 1; 10950544Smharo break; 1101556Srgrimes default: 1111556Srgrimes usage(); 1121556Srgrimes } 11314305Swosch argc -= optind; 1141556Srgrimes argv += optind; 1151556Srgrimes 1161556Srgrimes if (argc < 2) 1171556Srgrimes usage(); 1181556Srgrimes 1191556Srgrimes /* 1201556Srgrimes * If the stat on the target fails or the target isn't a directory, 1211556Srgrimes * try the move. More than 2 arguments is an error in this case. 1221556Srgrimes */ 1231556Srgrimes if (stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) { 1241556Srgrimes if (argc > 2) 1251556Srgrimes usage(); 1261556Srgrimes exit(do_move(argv[0], argv[1])); 1271556Srgrimes } 1281556Srgrimes 129241320Sjhb /* 130241320Sjhb * If -h was specified, treat the target as a symlink instead of 131241320Sjhb * directory. 132241320Sjhb */ 133241320Sjhb if (hflg) { 134241320Sjhb if (argc > 2) 135241320Sjhb usage(); 136241320Sjhb if (lstat(argv[1], &sb) == 0 && S_ISLNK(sb.st_mode)) 137241320Sjhb exit(do_move(argv[0], argv[1])); 138241320Sjhb } 139241320Sjhb 1401556Srgrimes /* It's a directory, move each file into it. */ 14136785Simp if (strlen(argv[argc - 1]) > sizeof(path) - 1) 14236785Simp errx(1, "%s: destination pathname too long", *argv); 1431556Srgrimes (void)strcpy(path, argv[argc - 1]); 1441556Srgrimes baselen = strlen(path); 1451556Srgrimes endp = &path[baselen]; 14636383Ssteve if (!baselen || *(endp - 1) != '/') { 14736383Ssteve *endp++ = '/'; 14836383Ssteve ++baselen; 14936383Ssteve } 1501556Srgrimes for (rval = 0; --argc; ++argv) { 15111298Sbde /* 15211298Sbde * Find the last component of the source pathname. It 15311298Sbde * may have trailing slashes. 15411298Sbde */ 15511298Sbde p = *argv + strlen(*argv); 15611298Sbde while (p != *argv && p[-1] == '/') 15711298Sbde --p; 15811298Sbde while (p != *argv && p[-1] != '/') 15911298Sbde --p; 16011298Sbde 16177409Simp if ((baselen + (len = strlen(p))) >= PATH_MAX) { 1621556Srgrimes warnx("%s: destination pathname too long", *argv); 1631556Srgrimes rval = 1; 1641556Srgrimes } else { 16576878Skris memmove(endp, p, (size_t)len + 1); 1661556Srgrimes if (do_move(*argv, path)) 1671556Srgrimes rval = 1; 1681556Srgrimes } 1691556Srgrimes } 1701556Srgrimes exit(rval); 1711556Srgrimes} 1721556Srgrimes 173180604Sdelphijstatic int 174180604Sdelphijdo_move(const char *from, const char *to) 1751556Srgrimes{ 1761556Srgrimes struct stat sb; 17729933Swosch int ask, ch, first; 1781556Srgrimes char modep[15]; 1791556Srgrimes 1801556Srgrimes /* 1811556Srgrimes * Check access. If interactive and file exists, ask user if it 1821556Srgrimes * should be replaced. Otherwise if file exists but isn't writable 1831556Srgrimes * make sure the user wants to clobber it. 1841556Srgrimes */ 1851556Srgrimes if (!fflg && !access(to, F_OK)) { 18614166Swosch 18714166Swosch /* prompt only if source exist */ 18814166Swosch if (lstat(from, &sb) == -1) { 18914305Swosch warn("%s", from); 19014305Swosch return (1); 19114166Swosch } 19230106Swosch 19330106Swosch#define YESNO "(y/n [n]) " 1941556Srgrimes ask = 0; 19592935Sobrien if (nflg) { 19692935Sobrien if (vflg) 19792935Sobrien printf("%s not overwritten\n", to); 19892935Sobrien return (0); 19992935Sobrien } else if (iflg) { 20030106Swosch (void)fprintf(stderr, "overwrite %s? %s", to, YESNO); 2011556Srgrimes ask = 1; 202243557Seadler } else if (access(to, W_OK) && !stat(to, &sb) && isatty(STDIN_FILENO)) { 2031556Srgrimes strmode(sb.st_mode, modep); 20430106Swosch (void)fprintf(stderr, "override %s%s%s/%s for %s? %s", 2051556Srgrimes modep + 1, modep[9] == ' ' ? "" : " ", 20676878Skris user_from_uid((unsigned long)sb.st_uid, 0), 20776878Skris group_from_gid((unsigned long)sb.st_gid, 0), to, YESNO); 2081556Srgrimes ask = 1; 2091556Srgrimes } 2101556Srgrimes if (ask) { 21129933Swosch first = ch = getchar(); 21229933Swosch while (ch != '\n' && ch != EOF) 21329933Swosch ch = getchar(); 21430106Swosch if (first != 'y' && first != 'Y') { 21530106Swosch (void)fprintf(stderr, "not overwritten\n"); 2161556Srgrimes return (0); 21730106Swosch } 2181556Srgrimes } 2191556Srgrimes } 220174935Sdds /* 221174935Sdds * Rename on FreeBSD will fail with EISDIR and ENOTDIR, before failing 222174935Sdds * with EXDEV. Therefore, copy() doesn't have to perform the checks 223174935Sdds * specified in the Step 3 of the POSIX mv specification. 224174935Sdds */ 22550544Smharo if (!rename(from, to)) { 22650544Smharo if (vflg) 22750544Smharo printf("%s -> %s\n", from, to); 2281556Srgrimes return (0); 22950544Smharo } 2301556Srgrimes 23131664Seivind if (errno == EXDEV) { 23231664Seivind struct statfs sfs; 23377409Simp char path[PATH_MAX]; 23431664Seivind 235127272Spjd /* 236127272Spjd * If the source is a symbolic link and is on another 237127272Spjd * filesystem, it can be recreated at the destination. 238127272Spjd */ 239127272Spjd if (lstat(from, &sb) == -1) { 240127272Spjd warn("%s", from); 24131664Seivind return (1); 24231664Seivind } 243127272Spjd if (!S_ISLNK(sb.st_mode)) { 244127272Spjd /* Can't mv(1) a mount point. */ 245127272Spjd if (realpath(from, path) == NULL) { 246174935Sdds warn("cannot resolve %s: %s", from, path); 247127272Spjd return (1); 248127272Spjd } 249127272Spjd if (!statfs(path, &sfs) && 250127272Spjd !strcmp(path, sfs.f_mntonname)) { 251127272Spjd warnx("cannot rename a mount point"); 252127272Spjd return (1); 253127272Spjd } 25431664Seivind } 25531664Seivind } else { 2561556Srgrimes warn("rename %s to %s", from, to); 2571556Srgrimes return (1); 2581556Srgrimes } 2591556Srgrimes 2601556Srgrimes /* 2611556Srgrimes * If rename fails because we're trying to cross devices, and 2621556Srgrimes * it's a regular file, do the copy internally; otherwise, use 2631556Srgrimes * cp and rm. 2641556Srgrimes */ 26562963Sdwmalone if (lstat(from, &sb)) { 2661556Srgrimes warn("%s", from); 2671556Srgrimes return (1); 2681556Srgrimes } 2691556Srgrimes return (S_ISREG(sb.st_mode) ? 2701556Srgrimes fastcopy(from, to, &sb) : copy(from, to)); 2711556Srgrimes} 2721556Srgrimes 273180604Sdelphijstatic int 274180604Sdelphijfastcopy(const char *from, const char *to, struct stat *sbp) 2751556Srgrimes{ 2761556Srgrimes struct timeval tval[2]; 2771556Srgrimes static u_int blen; 2781556Srgrimes static char *bp; 27923525Sguido mode_t oldmode; 28090114Simp int nread, from_fd, to_fd; 2811556Srgrimes 2821556Srgrimes if ((from_fd = open(from, O_RDONLY, 0)) < 0) { 2831556Srgrimes warn("%s", from); 2841556Srgrimes return (1); 2851556Srgrimes } 28623525Sguido if (blen < sbp->st_blksize) { 28723525Sguido if (bp != NULL) 28823525Sguido free(bp); 28976878Skris if ((bp = malloc((size_t)sbp->st_blksize)) == NULL) { 29023525Sguido blen = 0; 29123525Sguido warnx("malloc failed"); 29223525Sguido return (1); 29323525Sguido } 29423525Sguido blen = sbp->st_blksize; 29523525Sguido } 29623525Sguido while ((to_fd = 29723525Sguido open(to, O_CREAT | O_EXCL | O_TRUNC | O_WRONLY, 0)) < 0) { 29823525Sguido if (errno == EEXIST && unlink(to) == 0) 29923525Sguido continue; 3001556Srgrimes warn("%s", to); 3011556Srgrimes (void)close(from_fd); 3021556Srgrimes return (1); 3031556Srgrimes } 30476878Skris while ((nread = read(from_fd, bp, (size_t)blen)) > 0) 30576878Skris if (write(to_fd, bp, (size_t)nread) != nread) { 3061556Srgrimes warn("%s", to); 3071556Srgrimes goto err; 3081556Srgrimes } 3091556Srgrimes if (nread < 0) { 3101556Srgrimes warn("%s", from); 3111556Srgrimeserr: if (unlink(to)) 3121556Srgrimes warn("%s: remove", to); 3131556Srgrimes (void)close(from_fd); 3141556Srgrimes (void)close(to_fd); 3151556Srgrimes return (1); 3161556Srgrimes } 3171556Srgrimes 31823525Sguido oldmode = sbp->st_mode & ALLPERMS; 31923525Sguido if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) { 32037245Sbde warn("%s: set owner/group (was: %lu/%lu)", to, 32137245Sbde (u_long)sbp->st_uid, (u_long)sbp->st_gid); 32223525Sguido if (oldmode & (S_ISUID | S_ISGID)) { 32323525Sguido warnx( 32423525Sguido"%s: owner/group changed; clearing suid/sgid (mode was 0%03o)", 32523525Sguido to, oldmode); 32623525Sguido sbp->st_mode &= ~(S_ISUID | S_ISGID); 32723525Sguido } 32823525Sguido } 329196841Strasz if (fchmod(to_fd, sbp->st_mode)) 330196841Strasz warn("%s: set mode (was: 0%03o)", to, oldmode); 331149790Scsjp /* 332149790Scsjp * POSIX 1003.2c states that if _POSIX_ACL_EXTENDED is in effect 333174935Sdds * for dest_file, then its ACLs shall reflect the ACLs of the 334149790Scsjp * source_file. 335149790Scsjp */ 336196841Strasz preserve_fd_acls(from_fd, to_fd, from, to); 337149790Scsjp (void)close(from_fd); 33863680Ssada /* 33963680Ssada * XXX 34063680Ssada * NFS doesn't support chflags; ignore errors unless there's reason 34163680Ssada * to believe we're losing bits. (Note, this still won't be right 34263680Ssada * if the server supports flags and we were trying to *remove* flags 34363680Ssada * on a file that we copied, i.e., that we didn't create.) 34463680Ssada */ 34563680Ssada errno = 0; 34676878Skris if (fchflags(to_fd, (u_long)sbp->st_flags)) 34763680Ssada if (errno != EOPNOTSUPP || sbp->st_flags != 0) 34863680Ssada warn("%s: set flags (was: 0%07o)", to, sbp->st_flags); 3491556Srgrimes 3501556Srgrimes tval[0].tv_sec = sbp->st_atime; 3511556Srgrimes tval[1].tv_sec = sbp->st_mtime; 3521556Srgrimes tval[0].tv_usec = tval[1].tv_usec = 0; 3531556Srgrimes if (utimes(to, tval)) 3541556Srgrimes warn("%s: set times", to); 3551556Srgrimes 3561556Srgrimes if (close(to_fd)) { 3571556Srgrimes warn("%s", to); 3581556Srgrimes return (1); 3591556Srgrimes } 3601556Srgrimes 3611556Srgrimes if (unlink(from)) { 3621556Srgrimes warn("%s: remove", from); 3631556Srgrimes return (1); 3641556Srgrimes } 36550544Smharo if (vflg) 36650544Smharo printf("%s -> %s\n", from, to); 3671556Srgrimes return (0); 3681556Srgrimes} 3691556Srgrimes 370180604Sdelphijstatic int 371180604Sdelphijcopy(const char *from, const char *to) 3721556Srgrimes{ 373174664Sdds struct stat sb; 374174667Sdds int pid, status; 3751556Srgrimes 376174935Sdds if (lstat(to, &sb) == 0) { 377174935Sdds /* Destination path exists. */ 378174935Sdds if (S_ISDIR(sb.st_mode)) { 379174935Sdds if (rmdir(to) != 0) { 380174935Sdds warn("rmdir %s", to); 381174935Sdds return (1); 382174935Sdds } 383174935Sdds } else { 384174935Sdds if (unlink(to) != 0) { 385174935Sdds warn("unlink %s", to); 386174935Sdds return (1); 387174935Sdds } 388174664Sdds } 389174935Sdds } else if (errno != ENOENT) { 390174935Sdds warn("%s", to); 391174935Sdds return (1); 392174664Sdds } 393174935Sdds 394174664Sdds /* Copy source to destination. */ 395174935Sdds if (!(pid = vfork())) { 39698280Stjr execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", "--", from, to, 39779452Sbrian (char *)NULL); 398174935Sdds _exit(EXEC_FAILED); 3991556Srgrimes } 4001556Srgrimes if (waitpid(pid, &status, 0) == -1) { 401174935Sdds warn("%s %s %s: waitpid", _PATH_CP, from, to); 402174935Sdds return (1); 4031556Srgrimes } 4041556Srgrimes if (!WIFEXITED(status)) { 405174935Sdds warnx("%s %s %s: did not terminate normally", 406174935Sdds _PATH_CP, from, to); 407174935Sdds return (1); 4081556Srgrimes } 409174935Sdds switch (WEXITSTATUS(status)) { 410174935Sdds case 0: 411174935Sdds break; 412174935Sdds case EXEC_FAILED: 413174935Sdds warnx("%s %s %s: exec failed", _PATH_CP, from, to); 414174935Sdds return (1); 415174935Sdds default: 416174935Sdds warnx("%s %s %s: terminated with %d (non-zero) status", 417174935Sdds _PATH_CP, from, to, WEXITSTATUS(status)); 418174935Sdds return (1); 4191556Srgrimes } 420174935Sdds 421174935Sdds /* Delete the source. */ 422174935Sdds if (!(pid = vfork())) { 423174935Sdds execl(_PATH_RM, "mv", "-rf", "--", from, (char *)NULL); 424174935Sdds _exit(EXEC_FAILED); 4251556Srgrimes } 426174935Sdds if (waitpid(pid, &status, 0) == -1) { 427174935Sdds warn("%s %s: waitpid", _PATH_RM, from); 428174935Sdds return (1); 429174935Sdds } 430174935Sdds if (!WIFEXITED(status)) { 431174935Sdds warnx("%s %s: did not terminate normally", _PATH_RM, from); 432174935Sdds return (1); 433174935Sdds } 434174935Sdds switch (WEXITSTATUS(status)) { 435174935Sdds case 0: 436174935Sdds break; 437174935Sdds case EXEC_FAILED: 438174935Sdds warnx("%s %s: exec failed", _PATH_RM, from); 439174935Sdds return (1); 440174935Sdds default: 441174935Sdds warnx("%s %s: terminated with %d (non-zero) status", 442174935Sdds _PATH_RM, from, WEXITSTATUS(status)); 443174935Sdds return (1); 444174935Sdds } 445174935Sdds return (0); 4461556Srgrimes} 4471556Srgrimes 448180604Sdelphijstatic void 449196841Straszpreserve_fd_acls(int source_fd, int dest_fd, const char *source_path, 450196841Strasz const char *dest_path) 451196841Strasz{ 452196841Strasz acl_t acl; 453196841Strasz acl_type_t acl_type; 454196841Strasz int acl_supported = 0, ret, trivial; 455196841Strasz 456196841Strasz ret = fpathconf(source_fd, _PC_ACL_NFS4); 457196841Strasz if (ret > 0 ) { 458196841Strasz acl_supported = 1; 459196841Strasz acl_type = ACL_TYPE_NFS4; 460196841Strasz } else if (ret < 0 && errno != EINVAL) { 461196841Strasz warn("fpathconf(..., _PC_ACL_NFS4) failed for %s", 462196841Strasz source_path); 463196841Strasz return; 464196841Strasz } 465196841Strasz if (acl_supported == 0) { 466196841Strasz ret = fpathconf(source_fd, _PC_ACL_EXTENDED); 467196841Strasz if (ret > 0 ) { 468196841Strasz acl_supported = 1; 469196841Strasz acl_type = ACL_TYPE_ACCESS; 470196841Strasz } else if (ret < 0 && errno != EINVAL) { 471196841Strasz warn("fpathconf(..., _PC_ACL_EXTENDED) failed for %s", 472196841Strasz source_path); 473196841Strasz return; 474196841Strasz } 475196841Strasz } 476196841Strasz if (acl_supported == 0) 477196841Strasz return; 478196841Strasz 479196841Strasz acl = acl_get_fd_np(source_fd, acl_type); 480196841Strasz if (acl == NULL) { 481196841Strasz warn("failed to get acl entries for %s", source_path); 482196841Strasz return; 483196841Strasz } 484196841Strasz if (acl_is_trivial_np(acl, &trivial)) { 485196841Strasz warn("acl_is_trivial() failed for %s", source_path); 486196841Strasz acl_free(acl); 487196841Strasz return; 488196841Strasz } 489196841Strasz if (trivial) { 490196841Strasz acl_free(acl); 491196841Strasz return; 492196841Strasz } 493196841Strasz if (acl_set_fd_np(dest_fd, acl, acl_type) < 0) { 494196841Strasz warn("failed to set acl entries for %s", dest_path); 495196841Strasz acl_free(acl); 496196841Strasz return; 497196841Strasz } 498196841Strasz acl_free(acl); 499196841Strasz} 500196841Strasz 501196841Straszstatic void 50290110Simpusage(void) 5031556Srgrimes{ 50450544Smharo 50514305Swosch (void)fprintf(stderr, "%s\n%s\n", 506241320Sjhb "usage: mv [-f | -i | -n] [-hv] source target", 50799678Sjohan " mv [-f | -i | -n] [-v] source ... directory"); 50850544Smharo exit(EX_USAGE); 5091556Srgrimes} 510