mv.c revision 127272
11556Srgrimes/* 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 * 3. All advertising materials mentioning features or use of this software 171556Srgrimes * must display the following acknowledgement: 181556Srgrimes * This product includes software developed by the University of 191556Srgrimes * California, Berkeley and its contributors. 201556Srgrimes * 4. Neither the name of the University nor the names of its contributors 211556Srgrimes * may be used to endorse or promote products derived from this software 221556Srgrimes * without specific prior written permission. 231556Srgrimes * 241556Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 251556Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 261556Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 271556Srgrimes * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 281556Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 291556Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 301556Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 311556Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 321556Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 331556Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 341556Srgrimes * SUCH DAMAGE. 351556Srgrimes */ 361556Srgrimes 37114433Sobrien#if 0 381556Srgrimes#ifndef lint 3920420Sstevestatic char const copyright[] = 401556Srgrimes"@(#) Copyright (c) 1989, 1993, 1994\n\ 411556Srgrimes The Regents of the University of California. All rights reserved.\n"; 421556Srgrimes#endif /* not lint */ 431556Srgrimes 441556Srgrimes#ifndef lint 4536049Scharnierstatic char sccsid[] = "@(#)mv.c 8.2 (Berkeley) 4/2/94"; 46114433Sobrien#endif /* not lint */ 4736049Scharnier#endif 4892974Sobrien#include <sys/cdefs.h> 4992974Sobrien__FBSDID("$FreeBSD: head/bin/mv/mv.c 127272 2004-03-21 13:38:37Z pjd $"); 501556Srgrimes 511556Srgrimes#include <sys/param.h> 521556Srgrimes#include <sys/time.h> 531556Srgrimes#include <sys/wait.h> 541556Srgrimes#include <sys/stat.h> 5531664Seivind#include <sys/mount.h> 561556Srgrimes 571556Srgrimes#include <err.h> 581556Srgrimes#include <errno.h> 591556Srgrimes#include <fcntl.h> 6090644Simp#include <grp.h> 6177409Simp#include <limits.h> 6296806Sjmallett#include <paths.h> 6390644Simp#include <pwd.h> 641556Srgrimes#include <stdio.h> 651556Srgrimes#include <stdlib.h> 661556Srgrimes#include <string.h> 6750544Smharo#include <sysexits.h> 681556Srgrimes#include <unistd.h> 691556Srgrimes 7092935Sobrienint fflg, iflg, nflg, vflg; 711556Srgrimes 7290110Simpint copy(char *, char *); 7390110Simpint do_move(char *, char *); 7490110Simpint fastcopy(char *, char *, struct stat *); 7590110Simpvoid usage(void); 761556Srgrimes 771556Srgrimesint 7890110Simpmain(int argc, char *argv[]) 791556Srgrimes{ 8091085Smarkm size_t baselen, len; 8191085Smarkm int rval; 8290114Simp char *p, *endp; 831556Srgrimes struct stat sb; 841556Srgrimes int ch; 8577409Simp char path[PATH_MAX]; 861556Srgrimes 8792935Sobrien while ((ch = getopt(argc, argv, "finv")) != -1) 881556Srgrimes switch (ch) { 891556Srgrimes case 'i': 9014154Swosch iflg = 1; 9192935Sobrien fflg = nflg = 0; 921556Srgrimes break; 931556Srgrimes case 'f': 941556Srgrimes fflg = 1; 9592935Sobrien iflg = nflg = 0; 961556Srgrimes break; 9792935Sobrien case 'n': 9892935Sobrien nflg = 1; 9992935Sobrien fflg = iflg = 0; 10092935Sobrien break; 10150544Smharo case 'v': 10250544Smharo vflg = 1; 10350544Smharo break; 1041556Srgrimes default: 1051556Srgrimes usage(); 1061556Srgrimes } 10714305Swosch argc -= optind; 1081556Srgrimes argv += optind; 1091556Srgrimes 1101556Srgrimes if (argc < 2) 1111556Srgrimes usage(); 1121556Srgrimes 1131556Srgrimes /* 1141556Srgrimes * If the stat on the target fails or the target isn't a directory, 1151556Srgrimes * try the move. More than 2 arguments is an error in this case. 1161556Srgrimes */ 1171556Srgrimes if (stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) { 1181556Srgrimes if (argc > 2) 1191556Srgrimes usage(); 1201556Srgrimes exit(do_move(argv[0], argv[1])); 1211556Srgrimes } 1221556Srgrimes 1231556Srgrimes /* It's a directory, move each file into it. */ 12436785Simp if (strlen(argv[argc - 1]) > sizeof(path) - 1) 12536785Simp errx(1, "%s: destination pathname too long", *argv); 1261556Srgrimes (void)strcpy(path, argv[argc - 1]); 1271556Srgrimes baselen = strlen(path); 1281556Srgrimes endp = &path[baselen]; 12936383Ssteve if (!baselen || *(endp - 1) != '/') { 13036383Ssteve *endp++ = '/'; 13136383Ssteve ++baselen; 13236383Ssteve } 1331556Srgrimes for (rval = 0; --argc; ++argv) { 13411298Sbde /* 13511298Sbde * Find the last component of the source pathname. It 13611298Sbde * may have trailing slashes. 13711298Sbde */ 13811298Sbde p = *argv + strlen(*argv); 13911298Sbde while (p != *argv && p[-1] == '/') 14011298Sbde --p; 14111298Sbde while (p != *argv && p[-1] != '/') 14211298Sbde --p; 14311298Sbde 14477409Simp if ((baselen + (len = strlen(p))) >= PATH_MAX) { 1451556Srgrimes warnx("%s: destination pathname too long", *argv); 1461556Srgrimes rval = 1; 1471556Srgrimes } else { 14876878Skris memmove(endp, p, (size_t)len + 1); 1491556Srgrimes if (do_move(*argv, path)) 1501556Srgrimes rval = 1; 1511556Srgrimes } 1521556Srgrimes } 1531556Srgrimes exit(rval); 1541556Srgrimes} 1551556Srgrimes 1561556Srgrimesint 15790110Simpdo_move(char *from, char *to) 1581556Srgrimes{ 1591556Srgrimes struct stat sb; 16029933Swosch int ask, ch, first; 1611556Srgrimes char modep[15]; 1621556Srgrimes 1631556Srgrimes /* 1641556Srgrimes * Check access. If interactive and file exists, ask user if it 1651556Srgrimes * should be replaced. Otherwise if file exists but isn't writable 1661556Srgrimes * make sure the user wants to clobber it. 1671556Srgrimes */ 1681556Srgrimes if (!fflg && !access(to, F_OK)) { 16914166Swosch 17014166Swosch /* prompt only if source exist */ 17114166Swosch if (lstat(from, &sb) == -1) { 17214305Swosch warn("%s", from); 17314305Swosch return (1); 17414166Swosch } 17530106Swosch 17630106Swosch#define YESNO "(y/n [n]) " 1771556Srgrimes ask = 0; 17892935Sobrien if (nflg) { 17992935Sobrien if (vflg) 18092935Sobrien printf("%s not overwritten\n", to); 18192935Sobrien return (0); 18292935Sobrien } else if (iflg) { 18330106Swosch (void)fprintf(stderr, "overwrite %s? %s", to, YESNO); 1841556Srgrimes ask = 1; 1851556Srgrimes } else if (access(to, W_OK) && !stat(to, &sb)) { 1861556Srgrimes strmode(sb.st_mode, modep); 18730106Swosch (void)fprintf(stderr, "override %s%s%s/%s for %s? %s", 1881556Srgrimes modep + 1, modep[9] == ' ' ? "" : " ", 18976878Skris user_from_uid((unsigned long)sb.st_uid, 0), 19076878Skris group_from_gid((unsigned long)sb.st_gid, 0), to, YESNO); 1911556Srgrimes ask = 1; 1921556Srgrimes } 1931556Srgrimes if (ask) { 19429933Swosch first = ch = getchar(); 19529933Swosch while (ch != '\n' && ch != EOF) 19629933Swosch ch = getchar(); 19730106Swosch if (first != 'y' && first != 'Y') { 19830106Swosch (void)fprintf(stderr, "not overwritten\n"); 1991556Srgrimes return (0); 20030106Swosch } 2011556Srgrimes } 2021556Srgrimes } 20350544Smharo if (!rename(from, to)) { 20450544Smharo if (vflg) 20550544Smharo printf("%s -> %s\n", from, to); 2061556Srgrimes return (0); 20750544Smharo } 2081556Srgrimes 20931664Seivind if (errno == EXDEV) { 21031664Seivind struct statfs sfs; 21177409Simp char path[PATH_MAX]; 21231664Seivind 213127272Spjd /* 214127272Spjd * If the source is a symbolic link and is on another 215127272Spjd * filesystem, it can be recreated at the destination. 216127272Spjd */ 217127272Spjd if (lstat(from, &sb) == -1) { 218127272Spjd warn("%s", from); 21931664Seivind return (1); 22031664Seivind } 221127272Spjd if (!S_ISLNK(sb.st_mode)) { 222127272Spjd /* Can't mv(1) a mount point. */ 223127272Spjd if (realpath(from, path) == NULL) { 224127272Spjd warnx("cannot resolve %s: %s", from, path); 225127272Spjd return (1); 226127272Spjd } 227127272Spjd if (!statfs(path, &sfs) && 228127272Spjd !strcmp(path, sfs.f_mntonname)) { 229127272Spjd warnx("cannot rename a mount point"); 230127272Spjd return (1); 231127272Spjd } 23231664Seivind } 23331664Seivind } else { 2341556Srgrimes warn("rename %s to %s", from, to); 2351556Srgrimes return (1); 2361556Srgrimes } 2371556Srgrimes 2381556Srgrimes /* 2391556Srgrimes * If rename fails because we're trying to cross devices, and 2401556Srgrimes * it's a regular file, do the copy internally; otherwise, use 2411556Srgrimes * cp and rm. 2421556Srgrimes */ 24362963Sdwmalone if (lstat(from, &sb)) { 2441556Srgrimes warn("%s", from); 2451556Srgrimes return (1); 2461556Srgrimes } 2471556Srgrimes return (S_ISREG(sb.st_mode) ? 2481556Srgrimes fastcopy(from, to, &sb) : copy(from, to)); 2491556Srgrimes} 2501556Srgrimes 2511556Srgrimesint 25290110Simpfastcopy(char *from, char *to, struct stat *sbp) 2531556Srgrimes{ 2541556Srgrimes struct timeval tval[2]; 2551556Srgrimes static u_int blen; 2561556Srgrimes static char *bp; 25723525Sguido mode_t oldmode; 25890114Simp int nread, from_fd, to_fd; 2591556Srgrimes 2601556Srgrimes if ((from_fd = open(from, O_RDONLY, 0)) < 0) { 2611556Srgrimes warn("%s", from); 2621556Srgrimes return (1); 2631556Srgrimes } 26423525Sguido if (blen < sbp->st_blksize) { 26523525Sguido if (bp != NULL) 26623525Sguido free(bp); 26776878Skris if ((bp = malloc((size_t)sbp->st_blksize)) == NULL) { 26823525Sguido blen = 0; 26923525Sguido warnx("malloc failed"); 27023525Sguido return (1); 27123525Sguido } 27223525Sguido blen = sbp->st_blksize; 27323525Sguido } 27423525Sguido while ((to_fd = 27523525Sguido open(to, O_CREAT | O_EXCL | O_TRUNC | O_WRONLY, 0)) < 0) { 27623525Sguido if (errno == EEXIST && unlink(to) == 0) 27723525Sguido continue; 2781556Srgrimes warn("%s", to); 2791556Srgrimes (void)close(from_fd); 2801556Srgrimes return (1); 2811556Srgrimes } 28276878Skris while ((nread = read(from_fd, bp, (size_t)blen)) > 0) 28376878Skris if (write(to_fd, bp, (size_t)nread) != nread) { 2841556Srgrimes warn("%s", to); 2851556Srgrimes goto err; 2861556Srgrimes } 2871556Srgrimes if (nread < 0) { 2881556Srgrimes warn("%s", from); 2891556Srgrimeserr: if (unlink(to)) 2901556Srgrimes warn("%s: remove", to); 2911556Srgrimes (void)close(from_fd); 2921556Srgrimes (void)close(to_fd); 2931556Srgrimes return (1); 2941556Srgrimes } 2951556Srgrimes (void)close(from_fd); 2961556Srgrimes 29723525Sguido oldmode = sbp->st_mode & ALLPERMS; 29823525Sguido if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) { 29937245Sbde warn("%s: set owner/group (was: %lu/%lu)", to, 30037245Sbde (u_long)sbp->st_uid, (u_long)sbp->st_gid); 30123525Sguido if (oldmode & (S_ISUID | S_ISGID)) { 30223525Sguido warnx( 30323525Sguido"%s: owner/group changed; clearing suid/sgid (mode was 0%03o)", 30423525Sguido to, oldmode); 30523525Sguido sbp->st_mode &= ~(S_ISUID | S_ISGID); 30623525Sguido } 30723525Sguido } 3081556Srgrimes if (fchmod(to_fd, sbp->st_mode)) 30923525Sguido warn("%s: set mode (was: 0%03o)", to, oldmode); 31063680Ssada /* 31163680Ssada * XXX 31263680Ssada * NFS doesn't support chflags; ignore errors unless there's reason 31363680Ssada * to believe we're losing bits. (Note, this still won't be right 31463680Ssada * if the server supports flags and we were trying to *remove* flags 31563680Ssada * on a file that we copied, i.e., that we didn't create.) 31663680Ssada */ 31763680Ssada errno = 0; 31876878Skris if (fchflags(to_fd, (u_long)sbp->st_flags)) 31963680Ssada if (errno != EOPNOTSUPP || sbp->st_flags != 0) 32063680Ssada warn("%s: set flags (was: 0%07o)", to, sbp->st_flags); 3211556Srgrimes 3221556Srgrimes tval[0].tv_sec = sbp->st_atime; 3231556Srgrimes tval[1].tv_sec = sbp->st_mtime; 3241556Srgrimes tval[0].tv_usec = tval[1].tv_usec = 0; 3251556Srgrimes if (utimes(to, tval)) 3261556Srgrimes warn("%s: set times", to); 3271556Srgrimes 3281556Srgrimes if (close(to_fd)) { 3291556Srgrimes warn("%s", to); 3301556Srgrimes return (1); 3311556Srgrimes } 3321556Srgrimes 3331556Srgrimes if (unlink(from)) { 3341556Srgrimes warn("%s: remove", from); 3351556Srgrimes return (1); 3361556Srgrimes } 33750544Smharo if (vflg) 33850544Smharo printf("%s -> %s\n", from, to); 3391556Srgrimes return (0); 3401556Srgrimes} 3411556Srgrimes 3421556Srgrimesint 34390110Simpcopy(char *from, char *to) 3441556Srgrimes{ 3451556Srgrimes int pid, status; 3461556Srgrimes 34740301Sdes if ((pid = fork()) == 0) { 34898280Stjr execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", "--", from, to, 34979452Sbrian (char *)NULL); 3501556Srgrimes warn("%s", _PATH_CP); 3511556Srgrimes _exit(1); 3521556Srgrimes } 3531556Srgrimes if (waitpid(pid, &status, 0) == -1) { 3541556Srgrimes warn("%s: waitpid", _PATH_CP); 3551556Srgrimes return (1); 3561556Srgrimes } 3571556Srgrimes if (!WIFEXITED(status)) { 3581556Srgrimes warn("%s: did not terminate normally", _PATH_CP); 3591556Srgrimes return (1); 3601556Srgrimes } 3611556Srgrimes if (WEXITSTATUS(status)) { 3621556Srgrimes warn("%s: terminated with %d (non-zero) status", 3631556Srgrimes _PATH_CP, WEXITSTATUS(status)); 3641556Srgrimes return (1); 3651556Srgrimes } 3661556Srgrimes if (!(pid = vfork())) { 36798280Stjr execl(_PATH_RM, "mv", "-rf", "--", from, (char *)NULL); 3681556Srgrimes warn("%s", _PATH_RM); 3691556Srgrimes _exit(1); 3701556Srgrimes } 3711556Srgrimes if (waitpid(pid, &status, 0) == -1) { 3721556Srgrimes warn("%s: waitpid", _PATH_RM); 3731556Srgrimes return (1); 3741556Srgrimes } 3751556Srgrimes if (!WIFEXITED(status)) { 3761556Srgrimes warn("%s: did not terminate normally", _PATH_RM); 3771556Srgrimes return (1); 3781556Srgrimes } 3791556Srgrimes if (WEXITSTATUS(status)) { 3801556Srgrimes warn("%s: terminated with %d (non-zero) status", 3811556Srgrimes _PATH_RM, WEXITSTATUS(status)); 3821556Srgrimes return (1); 3831556Srgrimes } 3841556Srgrimes return (0); 3851556Srgrimes} 3861556Srgrimes 3871556Srgrimesvoid 38890110Simpusage(void) 3891556Srgrimes{ 39050544Smharo 39114305Swosch (void)fprintf(stderr, "%s\n%s\n", 39299678Sjohan "usage: mv [-f | -i | -n] [-v] source target", 39399678Sjohan " mv [-f | -i | -n] [-v] source ... directory"); 39450544Smharo exit(EX_USAGE); 3951556Srgrimes} 396