mv.c revision 180604
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: head/bin/mv/mv.c 180604 2008-07-19 00:13:26Z delphij $"); 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 7192935Sobrienint fflg, 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); 771556Srgrimes 781556Srgrimesint 7990110Simpmain(int argc, char *argv[]) 801556Srgrimes{ 8191085Smarkm size_t baselen, len; 8291085Smarkm int rval; 8390114Simp char *p, *endp; 841556Srgrimes struct stat sb; 851556Srgrimes int ch; 8677409Simp char path[PATH_MAX]; 871556Srgrimes 8892935Sobrien while ((ch = getopt(argc, argv, "finv")) != -1) 891556Srgrimes switch (ch) { 901556Srgrimes case 'i': 9114154Swosch iflg = 1; 9292935Sobrien fflg = nflg = 0; 931556Srgrimes break; 941556Srgrimes case 'f': 951556Srgrimes fflg = 1; 9692935Sobrien iflg = nflg = 0; 971556Srgrimes break; 9892935Sobrien case 'n': 9992935Sobrien nflg = 1; 10092935Sobrien fflg = iflg = 0; 10192935Sobrien break; 10250544Smharo case 'v': 10350544Smharo vflg = 1; 10450544Smharo break; 1051556Srgrimes default: 1061556Srgrimes usage(); 1071556Srgrimes } 10814305Swosch argc -= optind; 1091556Srgrimes argv += optind; 1101556Srgrimes 1111556Srgrimes if (argc < 2) 1121556Srgrimes usage(); 1131556Srgrimes 1141556Srgrimes /* 1151556Srgrimes * If the stat on the target fails or the target isn't a directory, 1161556Srgrimes * try the move. More than 2 arguments is an error in this case. 1171556Srgrimes */ 1181556Srgrimes if (stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) { 1191556Srgrimes if (argc > 2) 1201556Srgrimes usage(); 1211556Srgrimes exit(do_move(argv[0], argv[1])); 1221556Srgrimes } 1231556Srgrimes 1241556Srgrimes /* It's a directory, move each file into it. */ 12536785Simp if (strlen(argv[argc - 1]) > sizeof(path) - 1) 12636785Simp errx(1, "%s: destination pathname too long", *argv); 1271556Srgrimes (void)strcpy(path, argv[argc - 1]); 1281556Srgrimes baselen = strlen(path); 1291556Srgrimes endp = &path[baselen]; 13036383Ssteve if (!baselen || *(endp - 1) != '/') { 13136383Ssteve *endp++ = '/'; 13236383Ssteve ++baselen; 13336383Ssteve } 1341556Srgrimes for (rval = 0; --argc; ++argv) { 13511298Sbde /* 13611298Sbde * Find the last component of the source pathname. It 13711298Sbde * may have trailing slashes. 13811298Sbde */ 13911298Sbde p = *argv + strlen(*argv); 14011298Sbde while (p != *argv && p[-1] == '/') 14111298Sbde --p; 14211298Sbde while (p != *argv && p[-1] != '/') 14311298Sbde --p; 14411298Sbde 14577409Simp if ((baselen + (len = strlen(p))) >= PATH_MAX) { 1461556Srgrimes warnx("%s: destination pathname too long", *argv); 1471556Srgrimes rval = 1; 1481556Srgrimes } else { 14976878Skris memmove(endp, p, (size_t)len + 1); 1501556Srgrimes if (do_move(*argv, path)) 1511556Srgrimes rval = 1; 1521556Srgrimes } 1531556Srgrimes } 1541556Srgrimes exit(rval); 1551556Srgrimes} 1561556Srgrimes 157180604Sdelphijstatic int 158180604Sdelphijdo_move(const char *from, const char *to) 1591556Srgrimes{ 1601556Srgrimes struct stat sb; 16129933Swosch int ask, ch, first; 1621556Srgrimes char modep[15]; 1631556Srgrimes 1641556Srgrimes /* 1651556Srgrimes * Check access. If interactive and file exists, ask user if it 1661556Srgrimes * should be replaced. Otherwise if file exists but isn't writable 1671556Srgrimes * make sure the user wants to clobber it. 1681556Srgrimes */ 1691556Srgrimes if (!fflg && !access(to, F_OK)) { 17014166Swosch 17114166Swosch /* prompt only if source exist */ 17214166Swosch if (lstat(from, &sb) == -1) { 17314305Swosch warn("%s", from); 17414305Swosch return (1); 17514166Swosch } 17630106Swosch 17730106Swosch#define YESNO "(y/n [n]) " 1781556Srgrimes ask = 0; 17992935Sobrien if (nflg) { 18092935Sobrien if (vflg) 18192935Sobrien printf("%s not overwritten\n", to); 18292935Sobrien return (0); 18392935Sobrien } else if (iflg) { 18430106Swosch (void)fprintf(stderr, "overwrite %s? %s", to, YESNO); 1851556Srgrimes ask = 1; 1861556Srgrimes } else if (access(to, W_OK) && !stat(to, &sb)) { 1871556Srgrimes strmode(sb.st_mode, modep); 18830106Swosch (void)fprintf(stderr, "override %s%s%s/%s for %s? %s", 1891556Srgrimes modep + 1, modep[9] == ' ' ? "" : " ", 19076878Skris user_from_uid((unsigned long)sb.st_uid, 0), 19176878Skris group_from_gid((unsigned long)sb.st_gid, 0), to, YESNO); 1921556Srgrimes ask = 1; 1931556Srgrimes } 1941556Srgrimes if (ask) { 19529933Swosch first = ch = getchar(); 19629933Swosch while (ch != '\n' && ch != EOF) 19729933Swosch ch = getchar(); 19830106Swosch if (first != 'y' && first != 'Y') { 19930106Swosch (void)fprintf(stderr, "not overwritten\n"); 2001556Srgrimes return (0); 20130106Swosch } 2021556Srgrimes } 2031556Srgrimes } 204174935Sdds /* 205174935Sdds * Rename on FreeBSD will fail with EISDIR and ENOTDIR, before failing 206174935Sdds * with EXDEV. Therefore, copy() doesn't have to perform the checks 207174935Sdds * specified in the Step 3 of the POSIX mv specification. 208174935Sdds */ 20950544Smharo if (!rename(from, to)) { 21050544Smharo if (vflg) 21150544Smharo printf("%s -> %s\n", from, to); 2121556Srgrimes return (0); 21350544Smharo } 2141556Srgrimes 21531664Seivind if (errno == EXDEV) { 21631664Seivind struct statfs sfs; 21777409Simp char path[PATH_MAX]; 21831664Seivind 219127272Spjd /* 220127272Spjd * If the source is a symbolic link and is on another 221127272Spjd * filesystem, it can be recreated at the destination. 222127272Spjd */ 223127272Spjd if (lstat(from, &sb) == -1) { 224127272Spjd warn("%s", from); 22531664Seivind return (1); 22631664Seivind } 227127272Spjd if (!S_ISLNK(sb.st_mode)) { 228127272Spjd /* Can't mv(1) a mount point. */ 229127272Spjd if (realpath(from, path) == NULL) { 230174935Sdds warn("cannot resolve %s: %s", from, path); 231127272Spjd return (1); 232127272Spjd } 233127272Spjd if (!statfs(path, &sfs) && 234127272Spjd !strcmp(path, sfs.f_mntonname)) { 235127272Spjd warnx("cannot rename a mount point"); 236127272Spjd return (1); 237127272Spjd } 23831664Seivind } 23931664Seivind } else { 2401556Srgrimes warn("rename %s to %s", from, to); 2411556Srgrimes return (1); 2421556Srgrimes } 2431556Srgrimes 2441556Srgrimes /* 2451556Srgrimes * If rename fails because we're trying to cross devices, and 2461556Srgrimes * it's a regular file, do the copy internally; otherwise, use 2471556Srgrimes * cp and rm. 2481556Srgrimes */ 24962963Sdwmalone if (lstat(from, &sb)) { 2501556Srgrimes warn("%s", from); 2511556Srgrimes return (1); 2521556Srgrimes } 2531556Srgrimes return (S_ISREG(sb.st_mode) ? 2541556Srgrimes fastcopy(from, to, &sb) : copy(from, to)); 2551556Srgrimes} 2561556Srgrimes 257180604Sdelphijstatic int 258180604Sdelphijfastcopy(const char *from, const char *to, struct stat *sbp) 2591556Srgrimes{ 2601556Srgrimes struct timeval tval[2]; 2611556Srgrimes static u_int blen; 2621556Srgrimes static char *bp; 263174935Sdds acl_t acl; 26423525Sguido mode_t oldmode; 26590114Simp int nread, from_fd, to_fd; 2661556Srgrimes 2671556Srgrimes if ((from_fd = open(from, O_RDONLY, 0)) < 0) { 2681556Srgrimes warn("%s", from); 2691556Srgrimes return (1); 2701556Srgrimes } 27123525Sguido if (blen < sbp->st_blksize) { 27223525Sguido if (bp != NULL) 27323525Sguido free(bp); 27476878Skris if ((bp = malloc((size_t)sbp->st_blksize)) == NULL) { 27523525Sguido blen = 0; 27623525Sguido warnx("malloc failed"); 27723525Sguido return (1); 27823525Sguido } 27923525Sguido blen = sbp->st_blksize; 28023525Sguido } 28123525Sguido while ((to_fd = 28223525Sguido open(to, O_CREAT | O_EXCL | O_TRUNC | O_WRONLY, 0)) < 0) { 28323525Sguido if (errno == EEXIST && unlink(to) == 0) 28423525Sguido continue; 2851556Srgrimes warn("%s", to); 2861556Srgrimes (void)close(from_fd); 2871556Srgrimes return (1); 2881556Srgrimes } 28976878Skris while ((nread = read(from_fd, bp, (size_t)blen)) > 0) 29076878Skris if (write(to_fd, bp, (size_t)nread) != nread) { 2911556Srgrimes warn("%s", to); 2921556Srgrimes goto err; 2931556Srgrimes } 2941556Srgrimes if (nread < 0) { 2951556Srgrimes warn("%s", from); 2961556Srgrimeserr: if (unlink(to)) 2971556Srgrimes warn("%s: remove", to); 2981556Srgrimes (void)close(from_fd); 2991556Srgrimes (void)close(to_fd); 3001556Srgrimes return (1); 3011556Srgrimes } 3021556Srgrimes 30323525Sguido oldmode = sbp->st_mode & ALLPERMS; 30423525Sguido if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) { 30537245Sbde warn("%s: set owner/group (was: %lu/%lu)", to, 30637245Sbde (u_long)sbp->st_uid, (u_long)sbp->st_gid); 30723525Sguido if (oldmode & (S_ISUID | S_ISGID)) { 30823525Sguido warnx( 30923525Sguido"%s: owner/group changed; clearing suid/sgid (mode was 0%03o)", 31023525Sguido to, oldmode); 31123525Sguido sbp->st_mode &= ~(S_ISUID | S_ISGID); 31223525Sguido } 31323525Sguido } 314149790Scsjp /* 315149790Scsjp * POSIX 1003.2c states that if _POSIX_ACL_EXTENDED is in effect 316174935Sdds * for dest_file, then its ACLs shall reflect the ACLs of the 317149790Scsjp * source_file. 318149790Scsjp */ 319149790Scsjp if (fpathconf(to_fd, _PC_ACL_EXTENDED) == 1 && 320149790Scsjp fpathconf(from_fd, _PC_ACL_EXTENDED) == 1) { 321149790Scsjp acl = acl_get_fd(from_fd); 322149790Scsjp if (acl == NULL) 323149790Scsjp warn("failed to get acl entries while setting %s", 324149790Scsjp from); 325149790Scsjp else if (acl_set_fd(to_fd, acl) < 0) 326149790Scsjp warn("failed to set acl entries for %s", to); 327149790Scsjp } 328149790Scsjp (void)close(from_fd); 3291556Srgrimes if (fchmod(to_fd, sbp->st_mode)) 33023525Sguido warn("%s: set mode (was: 0%03o)", to, oldmode); 33163680Ssada /* 33263680Ssada * XXX 33363680Ssada * NFS doesn't support chflags; ignore errors unless there's reason 33463680Ssada * to believe we're losing bits. (Note, this still won't be right 33563680Ssada * if the server supports flags and we were trying to *remove* flags 33663680Ssada * on a file that we copied, i.e., that we didn't create.) 33763680Ssada */ 33863680Ssada errno = 0; 33976878Skris if (fchflags(to_fd, (u_long)sbp->st_flags)) 34063680Ssada if (errno != EOPNOTSUPP || sbp->st_flags != 0) 34163680Ssada warn("%s: set flags (was: 0%07o)", to, sbp->st_flags); 3421556Srgrimes 3431556Srgrimes tval[0].tv_sec = sbp->st_atime; 3441556Srgrimes tval[1].tv_sec = sbp->st_mtime; 3451556Srgrimes tval[0].tv_usec = tval[1].tv_usec = 0; 3461556Srgrimes if (utimes(to, tval)) 3471556Srgrimes warn("%s: set times", to); 3481556Srgrimes 3491556Srgrimes if (close(to_fd)) { 3501556Srgrimes warn("%s", to); 3511556Srgrimes return (1); 3521556Srgrimes } 3531556Srgrimes 3541556Srgrimes if (unlink(from)) { 3551556Srgrimes warn("%s: remove", from); 3561556Srgrimes return (1); 3571556Srgrimes } 35850544Smharo if (vflg) 35950544Smharo printf("%s -> %s\n", from, to); 3601556Srgrimes return (0); 3611556Srgrimes} 3621556Srgrimes 363180604Sdelphijstatic int 364180604Sdelphijcopy(const char *from, const char *to) 3651556Srgrimes{ 366174664Sdds struct stat sb; 367174667Sdds int pid, status; 3681556Srgrimes 369174935Sdds if (lstat(to, &sb) == 0) { 370174935Sdds /* Destination path exists. */ 371174935Sdds if (S_ISDIR(sb.st_mode)) { 372174935Sdds if (rmdir(to) != 0) { 373174935Sdds warn("rmdir %s", to); 374174935Sdds return (1); 375174935Sdds } 376174935Sdds } else { 377174935Sdds if (unlink(to) != 0) { 378174935Sdds warn("unlink %s", to); 379174935Sdds return (1); 380174935Sdds } 381174664Sdds } 382174935Sdds } else if (errno != ENOENT) { 383174935Sdds warn("%s", to); 384174935Sdds return (1); 385174664Sdds } 386174935Sdds 387174664Sdds /* Copy source to destination. */ 388174935Sdds if (!(pid = vfork())) { 38998280Stjr execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", "--", from, to, 39079452Sbrian (char *)NULL); 391174935Sdds _exit(EXEC_FAILED); 3921556Srgrimes } 3931556Srgrimes if (waitpid(pid, &status, 0) == -1) { 394174935Sdds warn("%s %s %s: waitpid", _PATH_CP, from, to); 395174935Sdds return (1); 3961556Srgrimes } 3971556Srgrimes if (!WIFEXITED(status)) { 398174935Sdds warnx("%s %s %s: did not terminate normally", 399174935Sdds _PATH_CP, from, to); 400174935Sdds return (1); 4011556Srgrimes } 402174935Sdds switch (WEXITSTATUS(status)) { 403174935Sdds case 0: 404174935Sdds break; 405174935Sdds case EXEC_FAILED: 406174935Sdds warnx("%s %s %s: exec failed", _PATH_CP, from, to); 407174935Sdds return (1); 408174935Sdds default: 409174935Sdds warnx("%s %s %s: terminated with %d (non-zero) status", 410174935Sdds _PATH_CP, from, to, WEXITSTATUS(status)); 411174935Sdds return (1); 4121556Srgrimes } 413174935Sdds 414174935Sdds /* Delete the source. */ 415174935Sdds if (!(pid = vfork())) { 416174935Sdds execl(_PATH_RM, "mv", "-rf", "--", from, (char *)NULL); 417174935Sdds _exit(EXEC_FAILED); 4181556Srgrimes } 419174935Sdds if (waitpid(pid, &status, 0) == -1) { 420174935Sdds warn("%s %s: waitpid", _PATH_RM, from); 421174935Sdds return (1); 422174935Sdds } 423174935Sdds if (!WIFEXITED(status)) { 424174935Sdds warnx("%s %s: did not terminate normally", _PATH_RM, from); 425174935Sdds return (1); 426174935Sdds } 427174935Sdds switch (WEXITSTATUS(status)) { 428174935Sdds case 0: 429174935Sdds break; 430174935Sdds case EXEC_FAILED: 431174935Sdds warnx("%s %s: exec failed", _PATH_RM, from); 432174935Sdds return (1); 433174935Sdds default: 434174935Sdds warnx("%s %s: terminated with %d (non-zero) status", 435174935Sdds _PATH_RM, from, WEXITSTATUS(status)); 436174935Sdds return (1); 437174935Sdds } 438174935Sdds return (0); 4391556Srgrimes} 4401556Srgrimes 441180604Sdelphijstatic void 44290110Simpusage(void) 4431556Srgrimes{ 44450544Smharo 44514305Swosch (void)fprintf(stderr, "%s\n%s\n", 44699678Sjohan "usage: mv [-f | -i | -n] [-v] source target", 44799678Sjohan " mv [-f | -i | -n] [-v] source ... directory"); 44850544Smharo exit(EX_USAGE); 4491556Srgrimes} 450