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