mv.c revision 62963
1220422Sgabor/* 2220422Sgabor * Copyright (c) 1989, 1993, 1994 3210389Sgabor * The Regents of the University of California. All rights reserved. 4210389Sgabor * 5210389Sgabor * This code is derived from software contributed to Berkeley by 6211496Sdes * Ken Smith of The State University of New York at Buffalo. 7210389Sgabor * 8210389Sgabor * Redistribution and use in source and binary forms, with or without 9210389Sgabor * modification, are permitted provided that the following conditions 10210389Sgabor * are met: 11210389Sgabor * 1. Redistributions of source code must retain the above copyright 12210389Sgabor * notice, this list of conditions and the following disclaimer. 13210389Sgabor * 2. Redistributions in binary form must reproduce the above copyright 14210389Sgabor * notice, this list of conditions and the following disclaimer in the 15210389Sgabor * documentation and/or other materials provided with the distribution. 16210389Sgabor * 3. All advertising materials mentioning features or use of this software 17210389Sgabor * must display the following acknowledgement: 18210389Sgabor * This product includes software developed by the University of 19210389Sgabor * California, Berkeley and its contributors. 20210389Sgabor * 4. Neither the name of the University nor the names of its contributors 21210389Sgabor * may be used to endorse or promote products derived from this software 22210389Sgabor * without specific prior written permission. 23210389Sgabor * 24210389Sgabor * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25210389Sgabor * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26210389Sgabor * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27210389Sgabor * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28210389Sgabor * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29210389Sgabor * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30210389Sgabor * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31210389Sgabor * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32210389Sgabor * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33210389Sgabor * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34210389Sgabor * SUCH DAMAGE. 35210389Sgabor */ 36210389Sgabor 37210389Sgabor#ifndef lint 38210389Sgaborstatic char const copyright[] = 39210389Sgabor"@(#) Copyright (c) 1989, 1993, 1994\n\ 40210389Sgabor The Regents of the University of California. All rights reserved.\n"; 41210389Sgabor#endif /* not lint */ 42210389Sgabor 43210389Sgabor#ifndef lint 44210578Sgabor#if 0 45210389Sgaborstatic char sccsid[] = "@(#)mv.c 8.2 (Berkeley) 4/2/94"; 46210389Sgabor#endif 47210389Sgaborstatic const char rcsid[] = 48210389Sgabor "$FreeBSD: head/bin/mv/mv.c 62963 2000-07-11 18:01:42Z dwmalone $"; 49210389Sgabor#endif /* not lint */ 50210389Sgabor 51210389Sgabor#include <sys/param.h> 52226035Sgabor#include <sys/time.h> 53210389Sgabor#include <sys/wait.h> 54210389Sgabor#include <sys/stat.h> 55210389Sgabor#include <sys/mount.h> 56210389Sgabor 57210389Sgabor#include <err.h> 58210578Sgabor#include <errno.h> 59210578Sgabor#include <fcntl.h> 60210578Sgabor#include <stdio.h> 61220421Sgabor#include <stdlib.h> 62210578Sgabor#include <string.h> 63210578Sgabor#include <sysexits.h> 64210578Sgabor#include <unistd.h> 65220421Sgabor 66210578Sgabor#include "pathnames.h" 67210578Sgabor 68220421Sgaborint fflg, iflg, vflg; 69220421Sgabor 70210578Sgaborint copy __P((char *, char *)); 71210578Sgaborint do_move __P((char *, char *)); 72210578Sgaborint fastcopy __P((char *, char *, struct stat *)); 73210578Sgaborvoid usage __P((void)); 74210578Sgabor 75210578Sgaborint 76210578Sgabormain(argc, argv) 77210578Sgabor int argc; 78210578Sgabor char *argv[]; 79211364Sgabor{ 80210578Sgabor register int baselen, len, rval; 81210578Sgabor register char *p, *endp; 82210578Sgabor struct stat sb; 83210578Sgabor int ch; 84210578Sgabor char path[MAXPATHLEN]; 85210578Sgabor 86210578Sgabor while ((ch = getopt(argc, argv, "fiv")) != -1) 87210578Sgabor switch (ch) { 88224938Sgabor case 'i': 89210578Sgabor iflg = 1; 90210578Sgabor fflg = 0; 91210578Sgabor break; 92210578Sgabor case 'f': 93210578Sgabor fflg = 1; 94210578Sgabor iflg = 0; 95210578Sgabor break; 96210578Sgabor case 'v': 97210578Sgabor vflg = 1; 98210389Sgabor break; 99210389Sgabor default: 100210389Sgabor usage(); 101210389Sgabor } 102210389Sgabor argc -= optind; 103210389Sgabor argv += optind; 104210389Sgabor 105210389Sgabor if (argc < 2) 106210389Sgabor usage(); 107210389Sgabor 108210389Sgabor /* 109210389Sgabor * If the stat on the target fails or the target isn't a directory, 110210389Sgabor * try the move. More than 2 arguments is an error in this case. 111210389Sgabor */ 112210389Sgabor if (stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) { 113210389Sgabor if (argc > 2) 114210389Sgabor usage(); 115210389Sgabor exit(do_move(argv[0], argv[1])); 116210389Sgabor } 117210389Sgabor 118210389Sgabor /* It's a directory, move each file into it. */ 119210389Sgabor if (strlen(argv[argc - 1]) > sizeof(path) - 1) 120210389Sgabor errx(1, "%s: destination pathname too long", *argv); 121210389Sgabor (void)strcpy(path, argv[argc - 1]); 122210389Sgabor baselen = strlen(path); 123210389Sgabor endp = &path[baselen]; 124210389Sgabor if (!baselen || *(endp - 1) != '/') { 125210389Sgabor *endp++ = '/'; 126210389Sgabor ++baselen; 127210430Sdelphij } 128210389Sgabor for (rval = 0; --argc; ++argv) { 129210389Sgabor /* 130210389Sgabor * Find the last component of the source pathname. It 131210389Sgabor * may have trailing slashes. 132210389Sgabor */ 133228319Sgabor p = *argv + strlen(*argv); 134228097Sgabor while (p != *argv && p[-1] == '/') 135228097Sgabor --p; 136210389Sgabor while (p != *argv && p[-1] != '/') 137210389Sgabor --p; 138210389Sgabor 139210389Sgabor if ((baselen + (len = strlen(p))) >= MAXPATHLEN) { 140224938Sgabor warnx("%s: destination pathname too long", *argv); 141224938Sgabor rval = 1; 142224938Sgabor } else { 143224938Sgabor memmove(endp, p, len + 1); 144210389Sgabor if (do_move(*argv, path)) 145210389Sgabor rval = 1; 146210389Sgabor } 147210389Sgabor } 148210389Sgabor exit(rval); 149210389Sgabor} 150210389Sgabor 151210389Sgaborint 152210389Sgabordo_move(from, to) 153210578Sgabor char *from, *to; 154210578Sgabor{ 155210389Sgabor struct stat sb; 156210389Sgabor int ask, ch, first; 157210389Sgabor char modep[15]; 158210389Sgabor 159210389Sgabor /* 160210389Sgabor * Check access. If interactive and file exists, ask user if it 161210389Sgabor * should be replaced. Otherwise if file exists but isn't writable 162210430Sdelphij * make sure the user wants to clobber it. 163210389Sgabor */ 164210389Sgabor if (!fflg && !access(to, F_OK)) { 165210389Sgabor 166210389Sgabor /* prompt only if source exist */ 167210389Sgabor if (lstat(from, &sb) == -1) { 168210389Sgabor warn("%s", from); 169210389Sgabor return (1); 170210389Sgabor } 171210389Sgabor 172210389Sgabor#define YESNO "(y/n [n]) " 173210389Sgabor ask = 0; 174210389Sgabor if (iflg) { 175210389Sgabor (void)fprintf(stderr, "overwrite %s? %s", to, YESNO); 176210389Sgabor ask = 1; 177210389Sgabor } else if (access(to, W_OK) && !stat(to, &sb)) { 178210389Sgabor strmode(sb.st_mode, modep); 179210389Sgabor (void)fprintf(stderr, "override %s%s%s/%s for %s? %s", 180210389Sgabor modep + 1, modep[9] == ' ' ? "" : " ", 181210389Sgabor user_from_uid(sb.st_uid, 0), 182210389Sgabor group_from_gid(sb.st_gid, 0), to, YESNO); 183210389Sgabor ask = 1; 184211463Sgabor } 185210389Sgabor if (ask) { 186210389Sgabor first = ch = getchar(); 187210389Sgabor while (ch != '\n' && ch != EOF) 188210389Sgabor ch = getchar(); 189210389Sgabor if (first != 'y' && first != 'Y') { 190210389Sgabor (void)fprintf(stderr, "not overwritten\n"); 191210389Sgabor return (0); 192210389Sgabor } 193210389Sgabor } 194210389Sgabor } 195210389Sgabor if (!rename(from, to)) { 196210389Sgabor if (vflg) 197210389Sgabor printf("%s -> %s\n", from, to); 198228319Sgabor return (0); 199210389Sgabor } 200210389Sgabor 201210389Sgabor if (errno == EXDEV) { 202210389Sgabor struct statfs sfs; 203210389Sgabor char path[MAXPATHLEN]; 204210389Sgabor 205210389Sgabor /* Can't mv(1) a mount point. */ 206210389Sgabor if (realpath(from, path) == NULL) { 207210389Sgabor warnx("cannot resolve %s: %s", from, path); 208210389Sgabor return (1); 209210389Sgabor } 210210389Sgabor if (!statfs(path, &sfs) && !strcmp(path, sfs.f_mntonname)) { 211210389Sgabor warnx("cannot rename a mount point"); 212210389Sgabor return (1); 213210389Sgabor } 214211463Sgabor } else { 215210389Sgabor warn("rename %s to %s", from, to); 216210389Sgabor return (1); 217210389Sgabor } 218210389Sgabor 219210389Sgabor /* 220210389Sgabor * If rename fails because we're trying to cross devices, and 221210389Sgabor * it's a regular file, do the copy internally; otherwise, use 222210389Sgabor * cp and rm. 223210389Sgabor */ 224210389Sgabor if (lstat(from, &sb)) { 225210389Sgabor warn("%s", from); 226210389Sgabor return (1); 227210430Sdelphij } 228210389Sgabor return (S_ISREG(sb.st_mode) ? 229210389Sgabor fastcopy(from, to, &sb) : copy(from, to)); 230210389Sgabor} 231210389Sgabor 232210389Sgaborint 233210389Sgaborfastcopy(from, to, sbp) 234210389Sgabor char *from, *to; 235210389Sgabor struct stat *sbp; 236210389Sgabor{ 237226273Sgabor struct timeval tval[2]; 238226035Sgabor static u_int blen; 239210389Sgabor static char *bp; 240210389Sgabor mode_t oldmode; 241210389Sgabor register int nread, from_fd, to_fd; 242210389Sgabor 243210389Sgabor if ((from_fd = open(from, O_RDONLY, 0)) < 0) { 244210389Sgabor warn("%s", from); 245210389Sgabor return (1); 246210389Sgabor } 247210389Sgabor if (blen < sbp->st_blksize) { 248210389Sgabor if (bp != NULL) 249210461Sgabor free(bp); 250228093Sgabor if ((bp = malloc(sbp->st_blksize)) == NULL) { 251210461Sgabor blen = 0; 252228093Sgabor warnx("malloc failed"); 253210389Sgabor return (1); 254210389Sgabor } 255210622Sgabor blen = sbp->st_blksize; 256210389Sgabor } 257210430Sdelphij while ((to_fd = 258210389Sgabor open(to, O_CREAT | O_EXCL | O_TRUNC | O_WRONLY, 0)) < 0) { 259210389Sgabor if (errno == EEXIST && unlink(to) == 0) 260210389Sgabor continue; 261210389Sgabor warn("%s", to); 262210389Sgabor (void)close(from_fd); 263210389Sgabor return (1); 264210389Sgabor } 265210389Sgabor while ((nread = read(from_fd, bp, blen)) > 0) 266210389Sgabor if (write(to_fd, bp, nread) != nread) { 267210389Sgabor warn("%s", to); 268210389Sgabor goto err; 269210389Sgabor } 270210389Sgabor if (nread < 0) { 271220421Sgabor warn("%s", from); 272210389Sgaborerr: if (unlink(to)) 273210389Sgabor warn("%s: remove", to); 274210389Sgabor (void)close(from_fd); 275210389Sgabor (void)close(to_fd); 276210389Sgabor return (1); 277210389Sgabor } 278210389Sgabor (void)close(from_fd); 279210389Sgabor 280226035Sgabor oldmode = sbp->st_mode & ALLPERMS; 281226035Sgabor if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) { 282226035Sgabor warn("%s: set owner/group (was: %lu/%lu)", to, 283226035Sgabor (u_long)sbp->st_uid, (u_long)sbp->st_gid); 284210389Sgabor if (oldmode & (S_ISUID | S_ISGID)) { 285226035Sgabor warnx( 286226035Sgabor"%s: owner/group changed; clearing suid/sgid (mode was 0%03o)", 287226035Sgabor to, oldmode); 288226035Sgabor sbp->st_mode &= ~(S_ISUID | S_ISGID); 289226035Sgabor } 290226035Sgabor } 291226035Sgabor if (fchmod(to_fd, sbp->st_mode)) 292226035Sgabor warn("%s: set mode (was: 0%03o)", to, oldmode); 293226035Sgabor 294226035Sgabor tval[0].tv_sec = sbp->st_atime; 295226035Sgabor tval[1].tv_sec = sbp->st_mtime; 296226035Sgabor tval[0].tv_usec = tval[1].tv_usec = 0; 297226035Sgabor if (utimes(to, tval)) 298226035Sgabor warn("%s: set times", to); 299226035Sgabor 300226035Sgabor if (close(to_fd)) { 301226035Sgabor warn("%s", to); 302226035Sgabor return (1); 303226035Sgabor } 304226035Sgabor 305226035Sgabor if (unlink(from)) { 306226035Sgabor warn("%s: remove", from); 307210389Sgabor return (1); 308226035Sgabor } 309226035Sgabor if (vflg) 310226035Sgabor printf("%s -> %s\n", from, to); 311226035Sgabor return (0); 312226035Sgabor} 313226035Sgabor 314226035Sgaborint 315226035Sgaborcopy(from, to) 316226035Sgabor char *from, *to; 317226035Sgabor{ 318226035Sgabor int pid, status; 319226035Sgabor 320226035Sgabor if ((pid = fork()) == 0) { 321210389Sgabor execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", from, to, NULL); 322226035Sgabor warn("%s", _PATH_CP); 323226035Sgabor _exit(1); 324226035Sgabor } 325226035Sgabor if (waitpid(pid, &status, 0) == -1) { 326226035Sgabor warn("%s: waitpid", _PATH_CP); 327226035Sgabor return (1); 328226035Sgabor } 329226035Sgabor if (!WIFEXITED(status)) { 330226035Sgabor warn("%s: did not terminate normally", _PATH_CP); 331210389Sgabor return (1); 332226035Sgabor } 333210389Sgabor if (WEXITSTATUS(status)) { 334226035Sgabor warn("%s: terminated with %d (non-zero) status", 335226035Sgabor _PATH_CP, WEXITSTATUS(status)); 336226035Sgabor return (1); 337210389Sgabor } 338210389Sgabor if (!(pid = vfork())) { 339226035Sgabor execl(_PATH_RM, "mv", "-rf", from, NULL); 340226035Sgabor warn("%s", _PATH_RM); 341226035Sgabor _exit(1); 342226035Sgabor } 343226035Sgabor if (waitpid(pid, &status, 0) == -1) { 344226035Sgabor warn("%s: waitpid", _PATH_RM); 345226035Sgabor return (1); 346226035Sgabor } 347226035Sgabor if (!WIFEXITED(status)) { 348226035Sgabor warn("%s: did not terminate normally", _PATH_RM); 349226035Sgabor return (1); 350226035Sgabor } 351226035Sgabor if (WEXITSTATUS(status)) { 352210389Sgabor warn("%s: terminated with %d (non-zero) status", 353210389Sgabor _PATH_RM, WEXITSTATUS(status)); 354210389Sgabor return (1); 355210389Sgabor } 356210479Sgabor return (0); 357210389Sgabor} 358210389Sgabor 359210389Sgaborvoid 360210389Sgaborusage() 361210389Sgabor{ 362210389Sgabor 363210389Sgabor (void)fprintf(stderr, "%s\n%s\n", 364210389Sgabor "usage: mv [-f | -i] [-v] source target", 365210389Sgabor " mv [-f | -i] [-v] source ... directory"); 366210389Sgabor exit(EX_USAGE); 367210389Sgabor} 368210389Sgabor