mv.c revision 63680
1117845Ssam/* 2117845Ssam * Copyright (c) 1989, 1993, 1994 3117845Ssam * The Regents of the University of California. All rights reserved. 4117845Ssam * 5117845Ssam * This code is derived from software contributed to Berkeley by 6117845Ssam * Ken Smith of The State University of New York at Buffalo. 7117845Ssam * 8117845Ssam * Redistribution and use in source and binary forms, with or without 9117845Ssam * modification, are permitted provided that the following conditions 10117845Ssam * are met: 11117845Ssam * 1. Redistributions of source code must retain the above copyright 12117845Ssam * notice, this list of conditions and the following disclaimer. 13117845Ssam * 2. Redistributions in binary form must reproduce the above copyright 14117845Ssam * notice, this list of conditions and the following disclaimer in the 15117845Ssam * documentation and/or other materials provided with the distribution. 16117845Ssam * 3. All advertising materials mentioning features or use of this software 17117845Ssam * must display the following acknowledgement: 18117845Ssam * This product includes software developed by the University of 19117845Ssam * California, Berkeley and its contributors. 20117845Ssam * 4. Neither the name of the University nor the names of its contributors 21117845Ssam * may be used to endorse or promote products derived from this software 22117845Ssam * without specific prior written permission. 23117845Ssam * 24117845Ssam * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25117845Ssam * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26117845Ssam * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27117845Ssam * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28117845Ssam * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29117845Ssam * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30117845Ssam * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31117845Ssam * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32117845Ssam * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33117845Ssam * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34117845Ssam * SUCH DAMAGE. 35117845Ssam */ 36117845Ssam 37117845Ssam#ifndef lint 38117845Ssamstatic char const copyright[] = 39117845Ssam"@(#) Copyright (c) 1989, 1993, 1994\n\ 40117845Ssam The Regents of the University of California. All rights reserved.\n"; 41117845Ssam#endif /* not lint */ 42117845Ssam 43129879Sphk#ifndef lint 44117845Ssam#if 0 45117845Ssamstatic char sccsid[] = "@(#)mv.c 8.2 (Berkeley) 4/2/94"; 46117845Ssam#endif 47117845Ssamstatic const char rcsid[] = 48117845Ssam "$FreeBSD: head/bin/mv/mv.c 63680 2000-07-20 18:30:00Z sada $"; 49117845Ssam#endif /* not lint */ 50117845Ssam 51117845Ssam#include <sys/param.h> 52117845Ssam#include <sys/time.h> 53117845Ssam#include <sys/wait.h> 54117845Ssam#include <sys/stat.h> 55117845Ssam#include <sys/mount.h> 56117845Ssam 57117845Ssam#include <err.h> 58117845Ssam#include <errno.h> 59117845Ssam#include <fcntl.h> 60117845Ssam#include <stdio.h> 61117845Ssam#include <stdlib.h> 62117845Ssam#include <string.h> 63119287Simp#include <sysexits.h> 64119287Simp#include <unistd.h> 65117845Ssam 66117845Ssam#include "pathnames.h" 67117845Ssam 68117845Ssamint fflg, iflg, vflg; 69117845Ssam 70117845Ssamint copy __P((char *, char *)); 71117845Ssamint do_move __P((char *, char *)); 72117845Ssamint fastcopy __P((char *, char *, struct stat *)); 73117845Ssamvoid usage __P((void)); 74117845Ssam 75117845Ssamint 76117845Ssammain(argc, argv) 77117845Ssam int argc; 78117845Ssam char *argv[]; 79117845Ssam{ 80117845Ssam register int baselen, len, rval; 81117845Ssam register char *p, *endp; 82117845Ssam struct stat sb; 83117845Ssam int ch; 84117845Ssam char path[MAXPATHLEN]; 85117845Ssam 86117845Ssam while ((ch = getopt(argc, argv, "fiv")) != -1) 87117845Ssam switch (ch) { 88117845Ssam case 'i': 89117845Ssam iflg = 1; 90117845Ssam fflg = 0; 91117845Ssam break; 92117845Ssam case 'f': 93117845Ssam fflg = 1; 94117845Ssam iflg = 0; 95117845Ssam break; 96117845Ssam case 'v': 97117845Ssam vflg = 1; 98117845Ssam break; 99117845Ssam default: 100117845Ssam usage(); 101117845Ssam } 102117845Ssam argc -= optind; 103117845Ssam argv += optind; 104117845Ssam 105117845Ssam if (argc < 2) 106117845Ssam usage(); 107117845Ssam 108117845Ssam /* 109117845Ssam * If the stat on the target fails or the target isn't a directory, 110117845Ssam * try the move. More than 2 arguments is an error in this case. 111117845Ssam */ 112117845Ssam if (stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) { 113117845Ssam if (argc > 2) 114117845Ssam usage(); 115117845Ssam exit(do_move(argv[0], argv[1])); 116117845Ssam } 117117845Ssam 118117845Ssam /* It's a directory, move each file into it. */ 119117845Ssam if (strlen(argv[argc - 1]) > sizeof(path) - 1) 120117845Ssam errx(1, "%s: destination pathname too long", *argv); 121117845Ssam (void)strcpy(path, argv[argc - 1]); 122117845Ssam baselen = strlen(path); 123117845Ssam endp = &path[baselen]; 124117845Ssam if (!baselen || *(endp - 1) != '/') { 125117845Ssam *endp++ = '/'; 126117845Ssam ++baselen; 127117845Ssam } 128117845Ssam for (rval = 0; --argc; ++argv) { 129117845Ssam /* 130117845Ssam * Find the last component of the source pathname. It 131117845Ssam * may have trailing slashes. 132117845Ssam */ 133117845Ssam p = *argv + strlen(*argv); 134117845Ssam while (p != *argv && p[-1] == '/') 135117845Ssam --p; 136117845Ssam while (p != *argv && p[-1] != '/') 137117845Ssam --p; 138117845Ssam 139117845Ssam if ((baselen + (len = strlen(p))) >= MAXPATHLEN) { 140117845Ssam warnx("%s: destination pathname too long", *argv); 141117845Ssam rval = 1; 142117845Ssam } else { 143117845Ssam memmove(endp, p, len + 1); 144117845Ssam if (do_move(*argv, path)) 145117845Ssam rval = 1; 146117845Ssam } 147117845Ssam } 148117845Ssam exit(rval); 149117845Ssam} 150117845Ssam 151117845Ssamint 152117845Ssamdo_move(from, to) 153117845Ssam char *from, *to; 154117845Ssam{ 155117845Ssam struct stat sb; 156117845Ssam int ask, ch, first; 157117845Ssam char modep[15]; 158117845Ssam 159117845Ssam /* 160117845Ssam * Check access. If interactive and file exists, ask user if it 161117845Ssam * should be replaced. Otherwise if file exists but isn't writable 162117845Ssam * make sure the user wants to clobber it. 163117845Ssam */ 164117845Ssam if (!fflg && !access(to, F_OK)) { 165117845Ssam 166117845Ssam /* prompt only if source exist */ 167117845Ssam if (lstat(from, &sb) == -1) { 168117845Ssam warn("%s", from); 169117845Ssam return (1); 170117845Ssam } 171117845Ssam 172117845Ssam#define YESNO "(y/n [n]) " 173117845Ssam ask = 0; 174117845Ssam if (iflg) { 175117845Ssam (void)fprintf(stderr, "overwrite %s? %s", to, YESNO); 176117845Ssam ask = 1; 177117845Ssam } else if (access(to, W_OK) && !stat(to, &sb)) { 178117845Ssam strmode(sb.st_mode, modep); 179117845Ssam (void)fprintf(stderr, "override %s%s%s/%s for %s? %s", 180117845Ssam modep + 1, modep[9] == ' ' ? "" : " ", 181117845Ssam user_from_uid(sb.st_uid, 0), 182117845Ssam group_from_gid(sb.st_gid, 0), to, YESNO); 183117845Ssam ask = 1; 184117845Ssam } 185117845Ssam if (ask) { 186142890Simp first = ch = getchar(); 187117845Ssam while (ch != '\n' && ch != EOF) 188117845Ssam ch = getchar(); 189117845Ssam if (first != 'y' && first != 'Y') { 190117845Ssam (void)fprintf(stderr, "not overwritten\n"); 191117845Ssam return (0); 192117845Ssam } 193117845Ssam } 194117845Ssam } 195117845Ssam if (!rename(from, to)) { 196117845Ssam if (vflg) 197117845Ssam printf("%s -> %s\n", from, to); 198117845Ssam return (0); 199117845Ssam } 200117845Ssam 201117845Ssam if (errno == EXDEV) { 202117845Ssam struct statfs sfs; 203117845Ssam char path[MAXPATHLEN]; 204117845Ssam 205117845Ssam /* Can't mv(1) a mount point. */ 206117845Ssam if (realpath(from, path) == NULL) { 207117845Ssam warnx("cannot resolve %s: %s", from, path); 208117845Ssam return (1); 209117845Ssam } 210117845Ssam if (!statfs(path, &sfs) && !strcmp(path, sfs.f_mntonname)) { 211117845Ssam warnx("cannot rename a mount point"); 212117845Ssam return (1); 213117845Ssam } 214117845Ssam } else { 215117845Ssam warn("rename %s to %s", from, to); 216117845Ssam return (1); 217117845Ssam } 218117845Ssam 219117845Ssam /* 220117845Ssam * If rename fails because we're trying to cross devices, and 221117845Ssam * it's a regular file, do the copy internally; otherwise, use 222117845Ssam * cp and rm. 223117845Ssam */ 224117845Ssam if (lstat(from, &sb)) { 225117845Ssam warn("%s", from); 226117845Ssam return (1); 227117845Ssam } 228117845Ssam return (S_ISREG(sb.st_mode) ? 229117845Ssam fastcopy(from, to, &sb) : copy(from, to)); 230117845Ssam} 231117845Ssam 232117845Ssamint 233117845Ssamfastcopy(from, to, sbp) 234117845Ssam char *from, *to; 235117845Ssam struct stat *sbp; 236117845Ssam{ 237117845Ssam struct timeval tval[2]; 238117845Ssam static u_int blen; 239117845Ssam static char *bp; 240117845Ssam mode_t oldmode; 241117845Ssam register int nread, from_fd, to_fd; 242117845Ssam 243117845Ssam if ((from_fd = open(from, O_RDONLY, 0)) < 0) { 244127135Snjl warn("%s", from); 245127135Snjl return (1); 246117845Ssam } 247117845Ssam if (blen < sbp->st_blksize) { 248117845Ssam if (bp != NULL) 249117845Ssam free(bp); 250117845Ssam if ((bp = malloc(sbp->st_blksize)) == NULL) { 251117845Ssam blen = 0; 252117845Ssam warnx("malloc failed"); 253117845Ssam return (1); 254117845Ssam } 255117845Ssam blen = sbp->st_blksize; 256117845Ssam } 257127135Snjl while ((to_fd = 258127135Snjl open(to, O_CREAT | O_EXCL | O_TRUNC | O_WRONLY, 0)) < 0) { 259117845Ssam if (errno == EEXIST && unlink(to) == 0) 260117845Ssam continue; 261117845Ssam warn("%s", to); 262117845Ssam (void)close(from_fd); 263117845Ssam return (1); 264117845Ssam } 265117845Ssam while ((nread = read(from_fd, bp, blen)) > 0) 266117845Ssam if (write(to_fd, bp, nread) != nread) { 267117845Ssam warn("%s", to); 268117845Ssam goto err; 269117845Ssam } 270117845Ssam if (nread < 0) { 271117845Ssam warn("%s", from); 272117845Ssamerr: if (unlink(to)) 273117845Ssam warn("%s: remove", to); 274117845Ssam (void)close(from_fd); 275117845Ssam (void)close(to_fd); 276117845Ssam return (1); 277117845Ssam } 278117845Ssam (void)close(from_fd); 279117845Ssam 280117845Ssam oldmode = sbp->st_mode & ALLPERMS; 281117845Ssam if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) { 282117845Ssam warn("%s: set owner/group (was: %lu/%lu)", to, 283117845Ssam (u_long)sbp->st_uid, (u_long)sbp->st_gid); 284117845Ssam if (oldmode & (S_ISUID | S_ISGID)) { 285117845Ssam warnx( 286117845Ssam"%s: owner/group changed; clearing suid/sgid (mode was 0%03o)", 287117845Ssam to, oldmode); 288117845Ssam sbp->st_mode &= ~(S_ISUID | S_ISGID); 289117845Ssam } 290117845Ssam } 291117845Ssam if (fchmod(to_fd, sbp->st_mode)) 292117845Ssam warn("%s: set mode (was: 0%03o)", to, oldmode); 293117845Ssam /* 294117845Ssam * XXX 295117845Ssam * NFS doesn't support chflags; ignore errors unless there's reason 296117845Ssam * to believe we're losing bits. (Note, this still won't be right 297117845Ssam * if the server supports flags and we were trying to *remove* flags 298117845Ssam * on a file that we copied, i.e., that we didn't create.) 299117845Ssam */ 300117845Ssam errno = 0; 301117845Ssam if (fchflags(to_fd, sbp->st_flags)) 302117845Ssam if (errno != EOPNOTSUPP || sbp->st_flags != 0) 303117845Ssam warn("%s: set flags (was: 0%07o)", to, sbp->st_flags); 304117845Ssam 305117845Ssam tval[0].tv_sec = sbp->st_atime; 306117845Ssam tval[1].tv_sec = sbp->st_mtime; 307117845Ssam tval[0].tv_usec = tval[1].tv_usec = 0; 308117845Ssam if (utimes(to, tval)) 309117845Ssam warn("%s: set times", to); 310117845Ssam 311117845Ssam if (close(to_fd)) { 312117845Ssam warn("%s", to); 313117845Ssam return (1); 314117845Ssam } 315117845Ssam 316117845Ssam if (unlink(from)) { 317117845Ssam warn("%s: remove", from); 318117845Ssam return (1); 319117845Ssam } 320117845Ssam if (vflg) 321117845Ssam printf("%s -> %s\n", from, to); 322117845Ssam return (0); 323117845Ssam} 324117845Ssam 325117845Ssamint 326117845Ssamcopy(from, to) 327117845Ssam char *from, *to; 328117845Ssam{ 329117845Ssam int pid, status; 330117845Ssam 331117845Ssam if ((pid = fork()) == 0) { 332117845Ssam execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", from, to, NULL); 333117845Ssam warn("%s", _PATH_CP); 334117845Ssam _exit(1); 335117845Ssam } 336117845Ssam if (waitpid(pid, &status, 0) == -1) { 337117845Ssam warn("%s: waitpid", _PATH_CP); 338117845Ssam return (1); 339117845Ssam } 340117845Ssam if (!WIFEXITED(status)) { 341117845Ssam warn("%s: did not terminate normally", _PATH_CP); 342117845Ssam return (1); 343117845Ssam } 344117845Ssam if (WEXITSTATUS(status)) { 345117845Ssam warn("%s: terminated with %d (non-zero) status", 346117845Ssam _PATH_CP, WEXITSTATUS(status)); 347117845Ssam return (1); 348117845Ssam } 349117845Ssam if (!(pid = vfork())) { 350117845Ssam execl(_PATH_RM, "mv", "-rf", from, NULL); 351117845Ssam warn("%s", _PATH_RM); 352117845Ssam _exit(1); 353117845Ssam } 354117845Ssam if (waitpid(pid, &status, 0) == -1) { 355117845Ssam warn("%s: waitpid", _PATH_RM); 356117845Ssam return (1); 357117845Ssam } 358117845Ssam if (!WIFEXITED(status)) { 359117845Ssam warn("%s: did not terminate normally", _PATH_RM); 360117845Ssam return (1); 361117845Ssam } 362117845Ssam if (WEXITSTATUS(status)) { 363117845Ssam warn("%s: terminated with %d (non-zero) status", 364117845Ssam _PATH_RM, WEXITSTATUS(status)); 365117845Ssam return (1); 366117845Ssam } 367117845Ssam return (0); 368117845Ssam} 369117845Ssam 370117845Ssamvoid 371117845Ssamusage() 372117845Ssam{ 373117845Ssam 374117845Ssam (void)fprintf(stderr, "%s\n%s\n", 375117845Ssam "usage: mv [-f | -i] [-v] source target", 376117845Ssam " mv [-f | -i] [-v] source ... directory"); 377117845Ssam exit(EX_USAGE); 378117845Ssam} 379117845Ssam