mv.c revision 77409
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 371556Srgrimes#ifndef lint 3820420Sstevestatic char const copyright[] = 391556Srgrimes"@(#) Copyright (c) 1989, 1993, 1994\n\ 401556Srgrimes The Regents of the University of California. All rights reserved.\n"; 411556Srgrimes#endif /* not lint */ 421556Srgrimes 431556Srgrimes#ifndef lint 4436049Scharnier#if 0 4536049Scharnierstatic char sccsid[] = "@(#)mv.c 8.2 (Berkeley) 4/2/94"; 4636049Scharnier#endif 4736049Scharnierstatic const char rcsid[] = 4850471Speter "$FreeBSD: head/bin/mv/mv.c 77409 2001-05-29 18:20:36Z imp $"; 491556Srgrimes#endif /* not lint */ 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> 6077409Simp#include <limits.h> 611556Srgrimes#include <stdio.h> 621556Srgrimes#include <stdlib.h> 631556Srgrimes#include <string.h> 6450544Smharo#include <sysexits.h> 651556Srgrimes#include <unistd.h> 661556Srgrimes 671556Srgrimes#include "pathnames.h" 681556Srgrimes 6950544Smharoint fflg, iflg, vflg; 701556Srgrimes 711556Srgrimesint copy __P((char *, char *)); 721556Srgrimesint do_move __P((char *, char *)); 731556Srgrimesint fastcopy __P((char *, char *, struct stat *)); 7476878Skrisint main __P((int, char *[])); 751556Srgrimesvoid usage __P((void)); 761556Srgrimes 771556Srgrimesint 781556Srgrimesmain(argc, argv) 791556Srgrimes int argc; 801556Srgrimes char *argv[]; 811556Srgrimes{ 821556Srgrimes register int baselen, len, rval; 831556Srgrimes register char *p, *endp; 841556Srgrimes struct stat sb; 851556Srgrimes int ch; 8677409Simp char path[PATH_MAX]; 871556Srgrimes 8850544Smharo while ((ch = getopt(argc, argv, "fiv")) != -1) 891556Srgrimes switch (ch) { 901556Srgrimes case 'i': 9114154Swosch iflg = 1; 9214166Swosch fflg = 0; 931556Srgrimes break; 941556Srgrimes case 'f': 951556Srgrimes fflg = 1; 9614166Swosch iflg = 0; 971556Srgrimes break; 9850544Smharo case 'v': 9950544Smharo vflg = 1; 10050544Smharo break; 1011556Srgrimes default: 1021556Srgrimes usage(); 1031556Srgrimes } 10414305Swosch argc -= optind; 1051556Srgrimes argv += optind; 1061556Srgrimes 1071556Srgrimes if (argc < 2) 1081556Srgrimes usage(); 1091556Srgrimes 1101556Srgrimes /* 1111556Srgrimes * If the stat on the target fails or the target isn't a directory, 1121556Srgrimes * try the move. More than 2 arguments is an error in this case. 1131556Srgrimes */ 1141556Srgrimes if (stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) { 1151556Srgrimes if (argc > 2) 1161556Srgrimes usage(); 1171556Srgrimes exit(do_move(argv[0], argv[1])); 1181556Srgrimes } 1191556Srgrimes 1201556Srgrimes /* It's a directory, move each file into it. */ 12136785Simp if (strlen(argv[argc - 1]) > sizeof(path) - 1) 12236785Simp errx(1, "%s: destination pathname too long", *argv); 1231556Srgrimes (void)strcpy(path, argv[argc - 1]); 1241556Srgrimes baselen = strlen(path); 1251556Srgrimes endp = &path[baselen]; 12636383Ssteve if (!baselen || *(endp - 1) != '/') { 12736383Ssteve *endp++ = '/'; 12836383Ssteve ++baselen; 12936383Ssteve } 1301556Srgrimes for (rval = 0; --argc; ++argv) { 13111298Sbde /* 13211298Sbde * Find the last component of the source pathname. It 13311298Sbde * may have trailing slashes. 13411298Sbde */ 13511298Sbde p = *argv + strlen(*argv); 13611298Sbde while (p != *argv && p[-1] == '/') 13711298Sbde --p; 13811298Sbde while (p != *argv && p[-1] != '/') 13911298Sbde --p; 14011298Sbde 14177409Simp if ((baselen + (len = strlen(p))) >= PATH_MAX) { 1421556Srgrimes warnx("%s: destination pathname too long", *argv); 1431556Srgrimes rval = 1; 1441556Srgrimes } else { 14576878Skris memmove(endp, p, (size_t)len + 1); 1461556Srgrimes if (do_move(*argv, path)) 1471556Srgrimes rval = 1; 1481556Srgrimes } 1491556Srgrimes } 1501556Srgrimes exit(rval); 1511556Srgrimes} 1521556Srgrimes 1531556Srgrimesint 1541556Srgrimesdo_move(from, to) 1551556Srgrimes char *from, *to; 1561556Srgrimes{ 1571556Srgrimes struct stat sb; 15829933Swosch int ask, ch, first; 1591556Srgrimes char modep[15]; 1601556Srgrimes 1611556Srgrimes /* 1621556Srgrimes * Check access. If interactive and file exists, ask user if it 1631556Srgrimes * should be replaced. Otherwise if file exists but isn't writable 1641556Srgrimes * make sure the user wants to clobber it. 1651556Srgrimes */ 1661556Srgrimes if (!fflg && !access(to, F_OK)) { 16714166Swosch 16814166Swosch /* prompt only if source exist */ 16914166Swosch if (lstat(from, &sb) == -1) { 17014305Swosch warn("%s", from); 17114305Swosch return (1); 17214166Swosch } 17330106Swosch 17430106Swosch#define YESNO "(y/n [n]) " 1751556Srgrimes ask = 0; 1761556Srgrimes if (iflg) { 17730106Swosch (void)fprintf(stderr, "overwrite %s? %s", to, YESNO); 1781556Srgrimes ask = 1; 1791556Srgrimes } else if (access(to, W_OK) && !stat(to, &sb)) { 1801556Srgrimes strmode(sb.st_mode, modep); 18130106Swosch (void)fprintf(stderr, "override %s%s%s/%s for %s? %s", 1821556Srgrimes modep + 1, modep[9] == ' ' ? "" : " ", 18376878Skris user_from_uid((unsigned long)sb.st_uid, 0), 18476878Skris group_from_gid((unsigned long)sb.st_gid, 0), to, YESNO); 1851556Srgrimes ask = 1; 1861556Srgrimes } 1871556Srgrimes if (ask) { 18829933Swosch first = ch = getchar(); 18929933Swosch while (ch != '\n' && ch != EOF) 19029933Swosch ch = getchar(); 19130106Swosch if (first != 'y' && first != 'Y') { 19230106Swosch (void)fprintf(stderr, "not overwritten\n"); 1931556Srgrimes return (0); 19430106Swosch } 1951556Srgrimes } 1961556Srgrimes } 19750544Smharo if (!rename(from, to)) { 19850544Smharo if (vflg) 19950544Smharo printf("%s -> %s\n", from, to); 2001556Srgrimes return (0); 20150544Smharo } 2021556Srgrimes 20331664Seivind if (errno == EXDEV) { 20431664Seivind struct statfs sfs; 20577409Simp char path[PATH_MAX]; 20631664Seivind 20731664Seivind /* Can't mv(1) a mount point. */ 20831664Seivind if (realpath(from, path) == NULL) { 20931664Seivind warnx("cannot resolve %s: %s", from, path); 21031664Seivind return (1); 21131664Seivind } 21231664Seivind if (!statfs(path, &sfs) && !strcmp(path, sfs.f_mntonname)) { 21331664Seivind warnx("cannot rename a mount point"); 21431664Seivind return (1); 21531664Seivind } 21631664Seivind } else { 2171556Srgrimes warn("rename %s to %s", from, to); 2181556Srgrimes return (1); 2191556Srgrimes } 2201556Srgrimes 2211556Srgrimes /* 2221556Srgrimes * If rename fails because we're trying to cross devices, and 2231556Srgrimes * it's a regular file, do the copy internally; otherwise, use 2241556Srgrimes * cp and rm. 2251556Srgrimes */ 22662963Sdwmalone if (lstat(from, &sb)) { 2271556Srgrimes warn("%s", from); 2281556Srgrimes return (1); 2291556Srgrimes } 2301556Srgrimes return (S_ISREG(sb.st_mode) ? 2311556Srgrimes fastcopy(from, to, &sb) : copy(from, to)); 2321556Srgrimes} 2331556Srgrimes 2341556Srgrimesint 2351556Srgrimesfastcopy(from, to, sbp) 2361556Srgrimes char *from, *to; 2371556Srgrimes struct stat *sbp; 2381556Srgrimes{ 2391556Srgrimes struct timeval tval[2]; 2401556Srgrimes static u_int blen; 2411556Srgrimes static char *bp; 24223525Sguido mode_t oldmode; 2431556Srgrimes register int nread, from_fd, to_fd; 2441556Srgrimes 2451556Srgrimes if ((from_fd = open(from, O_RDONLY, 0)) < 0) { 2461556Srgrimes warn("%s", from); 2471556Srgrimes return (1); 2481556Srgrimes } 24923525Sguido if (blen < sbp->st_blksize) { 25023525Sguido if (bp != NULL) 25123525Sguido free(bp); 25276878Skris if ((bp = malloc((size_t)sbp->st_blksize)) == NULL) { 25323525Sguido blen = 0; 25423525Sguido warnx("malloc failed"); 25523525Sguido return (1); 25623525Sguido } 25723525Sguido blen = sbp->st_blksize; 25823525Sguido } 25923525Sguido while ((to_fd = 26023525Sguido open(to, O_CREAT | O_EXCL | O_TRUNC | O_WRONLY, 0)) < 0) { 26123525Sguido if (errno == EEXIST && unlink(to) == 0) 26223525Sguido continue; 2631556Srgrimes warn("%s", to); 2641556Srgrimes (void)close(from_fd); 2651556Srgrimes return (1); 2661556Srgrimes } 26776878Skris while ((nread = read(from_fd, bp, (size_t)blen)) > 0) 26876878Skris if (write(to_fd, bp, (size_t)nread) != nread) { 2691556Srgrimes warn("%s", to); 2701556Srgrimes goto err; 2711556Srgrimes } 2721556Srgrimes if (nread < 0) { 2731556Srgrimes warn("%s", from); 2741556Srgrimeserr: if (unlink(to)) 2751556Srgrimes warn("%s: remove", to); 2761556Srgrimes (void)close(from_fd); 2771556Srgrimes (void)close(to_fd); 2781556Srgrimes return (1); 2791556Srgrimes } 2801556Srgrimes (void)close(from_fd); 2811556Srgrimes 28223525Sguido oldmode = sbp->st_mode & ALLPERMS; 28323525Sguido if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) { 28437245Sbde warn("%s: set owner/group (was: %lu/%lu)", to, 28537245Sbde (u_long)sbp->st_uid, (u_long)sbp->st_gid); 28623525Sguido if (oldmode & (S_ISUID | S_ISGID)) { 28723525Sguido warnx( 28823525Sguido"%s: owner/group changed; clearing suid/sgid (mode was 0%03o)", 28923525Sguido to, oldmode); 29023525Sguido sbp->st_mode &= ~(S_ISUID | S_ISGID); 29123525Sguido } 29223525Sguido } 2931556Srgrimes if (fchmod(to_fd, sbp->st_mode)) 29423525Sguido warn("%s: set mode (was: 0%03o)", to, oldmode); 29563680Ssada /* 29663680Ssada * XXX 29763680Ssada * NFS doesn't support chflags; ignore errors unless there's reason 29863680Ssada * to believe we're losing bits. (Note, this still won't be right 29963680Ssada * if the server supports flags and we were trying to *remove* flags 30063680Ssada * on a file that we copied, i.e., that we didn't create.) 30163680Ssada */ 30263680Ssada errno = 0; 30376878Skris if (fchflags(to_fd, (u_long)sbp->st_flags)) 30463680Ssada if (errno != EOPNOTSUPP || sbp->st_flags != 0) 30563680Ssada warn("%s: set flags (was: 0%07o)", to, sbp->st_flags); 3061556Srgrimes 3071556Srgrimes tval[0].tv_sec = sbp->st_atime; 3081556Srgrimes tval[1].tv_sec = sbp->st_mtime; 3091556Srgrimes tval[0].tv_usec = tval[1].tv_usec = 0; 3101556Srgrimes if (utimes(to, tval)) 3111556Srgrimes warn("%s: set times", to); 3121556Srgrimes 3131556Srgrimes if (close(to_fd)) { 3141556Srgrimes warn("%s", to); 3151556Srgrimes return (1); 3161556Srgrimes } 3171556Srgrimes 3181556Srgrimes if (unlink(from)) { 3191556Srgrimes warn("%s: remove", from); 3201556Srgrimes return (1); 3211556Srgrimes } 32250544Smharo if (vflg) 32350544Smharo printf("%s -> %s\n", from, to); 3241556Srgrimes return (0); 3251556Srgrimes} 3261556Srgrimes 3271556Srgrimesint 3281556Srgrimescopy(from, to) 3291556Srgrimes char *from, *to; 3301556Srgrimes{ 3311556Srgrimes int pid, status; 3321556Srgrimes 33340301Sdes if ((pid = fork()) == 0) { 33450544Smharo execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", from, to, NULL); 3351556Srgrimes warn("%s", _PATH_CP); 3361556Srgrimes _exit(1); 3371556Srgrimes } 3381556Srgrimes if (waitpid(pid, &status, 0) == -1) { 3391556Srgrimes warn("%s: waitpid", _PATH_CP); 3401556Srgrimes return (1); 3411556Srgrimes } 3421556Srgrimes if (!WIFEXITED(status)) { 3431556Srgrimes warn("%s: did not terminate normally", _PATH_CP); 3441556Srgrimes return (1); 3451556Srgrimes } 3461556Srgrimes if (WEXITSTATUS(status)) { 3471556Srgrimes warn("%s: terminated with %d (non-zero) status", 3481556Srgrimes _PATH_CP, WEXITSTATUS(status)); 3491556Srgrimes return (1); 3501556Srgrimes } 3511556Srgrimes if (!(pid = vfork())) { 3521556Srgrimes execl(_PATH_RM, "mv", "-rf", from, NULL); 3531556Srgrimes warn("%s", _PATH_RM); 3541556Srgrimes _exit(1); 3551556Srgrimes } 3561556Srgrimes if (waitpid(pid, &status, 0) == -1) { 3571556Srgrimes warn("%s: waitpid", _PATH_RM); 3581556Srgrimes return (1); 3591556Srgrimes } 3601556Srgrimes if (!WIFEXITED(status)) { 3611556Srgrimes warn("%s: did not terminate normally", _PATH_RM); 3621556Srgrimes return (1); 3631556Srgrimes } 3641556Srgrimes if (WEXITSTATUS(status)) { 3651556Srgrimes warn("%s: terminated with %d (non-zero) status", 3661556Srgrimes _PATH_RM, WEXITSTATUS(status)); 3671556Srgrimes return (1); 3681556Srgrimes } 3691556Srgrimes return (0); 3701556Srgrimes} 3711556Srgrimes 3721556Srgrimesvoid 3731556Srgrimesusage() 3741556Srgrimes{ 37550544Smharo 37614305Swosch (void)fprintf(stderr, "%s\n%s\n", 37750544Smharo "usage: mv [-f | -i] [-v] source target", 37850544Smharo " mv [-f | -i] [-v] source ... directory"); 37950544Smharo exit(EX_USAGE); 3801556Srgrimes} 381