1/*
2 * Copyright (c) 1989, 1993, 1994
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Ken Smith of The State University of New York at Buffalo.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 *    must display the following acknowledgement:
18 *	This product includes software developed by the University of
19 *	California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 *    may be used to endorse or promote products derived from this software
22 *    without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37#include <sys/cdefs.h>
38#ifndef lint
39__used static char const copyright[] =
40"@(#) Copyright (c) 1989, 1993, 1994\n\
41	The Regents of the University of California.  All rights reserved.\n";
42#endif /* not lint */
43
44#ifndef lint
45#if 0
46static char sccsid[] = "@(#)mv.c	8.2 (Berkeley) 4/2/94";
47#endif
48#endif /* not lint */
49#include <sys/cdefs.h>
50__RCSID("$FreeBSD: src/bin/mv/mv.c,v 1.39 2002/07/09 17:45:13 johan Exp $");
51
52#include <sys/param.h>
53#include <sys/time.h>
54#include <sys/wait.h>
55#include <sys/stat.h>
56#include <sys/mount.h>
57
58#include <err.h>
59#include <errno.h>
60#include <fcntl.h>
61#include <grp.h>
62#include <limits.h>
63#include <paths.h>
64#include <pwd.h>
65#include <stdio.h>
66#include <stdlib.h>
67#include <string.h>
68#include <sysexits.h>
69#include <unistd.h>
70
71#ifdef __APPLE__
72#include <copyfile.h>
73#include <sys/mount.h>
74#endif
75
76#ifdef __APPLE__
77#include <get_compat.h>
78#else
79#define COMPAT_MODE(a,b) (1)
80#endif /* __APPLE__ */
81
82#include "pathnames.h"
83
84int fflg, iflg, nflg, vflg;
85
86int	copy(char *, char *);
87int	do_move(char *, char *);
88int	fastcopy(char *, char *, struct stat *);
89void	usage(void);
90
91int
92main(int argc, char *argv[])
93{
94	size_t baselen, len;
95	int rval;
96	char *p, *endp;
97	struct stat sb;
98#ifdef __APPLE__
99	struct stat fsb, tsb;
100#endif /* __APPLE__ */
101	int ch;
102	char path[PATH_MAX];
103
104	while ((ch = getopt(argc, argv, "finv")) != -1)
105		switch (ch) {
106		case 'i':
107			iflg = 1;
108			fflg = nflg = 0;
109			break;
110		case 'f':
111			fflg = 1;
112			iflg = nflg = 0;
113			break;
114		case 'n':
115			nflg = 1;
116			fflg = iflg = 0;
117			break;
118		case 'v':
119			vflg = 1;
120			break;
121		default:
122			usage();
123		}
124	argc -= optind;
125	argv += optind;
126
127	if (argc < 2)
128		usage();
129
130	/*
131	 * If the stat on the target fails or the target isn't a directory,
132	 * try the move.  More than 2 arguments is an error in this case.
133	 */
134	if (stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) {
135		if (argc > 2)
136			usage();
137		exit(do_move(argv[0], argv[1]));
138	}
139
140#ifdef __APPLE__
141	if (argc == 2 && !lstat(argv[0], &fsb) && !lstat(argv[1], &tsb) &&
142		fsb.st_ino == tsb.st_ino && fsb.st_dev == tsb.st_dev &&
143		fsb.st_gen == tsb.st_gen) {
144		/*
145		 * We appear to be trying to move a directory into itself,
146		 * but it may be that the filesystem is case insensitive and
147		 * we are trying to rename the directory to a case-variant.
148		 * Ignoring trailing slashes, we look for any difference in
149		 * the directory names.  If there is a difference we do
150		 * the rename, otherwise we fall-thru to the traditional
151		 * error.  Note the lstat calls above (rather than stat)
152		 * permit the renaming of symlinks to case-variants.
153		 */
154		char *q;
155
156		for (p = argv[0] + strlen(argv[0]); p != argv[0]; ) {
157			p--;
158			if (*p != '/')
159				break;
160		}
161		for (q = argv[1] + strlen(argv[1]); q != argv[1]; ) {
162			q--;
163			if (*q != '/')
164				break;
165		}
166		for ( ; ; p--, q--) {
167			if (*p != *q)
168				exit(do_move(argv[0], argv[1]));
169			if (*p == '/')
170				break;
171			if (p == argv[0]) {
172				if (q == argv[1] || *(q-1) == '/')
173					break;
174				exit(do_move(argv[0], argv[1]));
175			}
176			if (q == argv[1]) {
177				if (p == argv[0] || *(p-1) == '/')
178					break;
179				exit(do_move(argv[0], argv[1]));
180			}
181		}
182	}
183#endif /* __APPLE__ */
184
185	/* It's a directory, move each file into it. */
186	if (strlen(argv[argc - 1]) > sizeof(path) - 1)
187		errx(1, "%s: destination pathname too long", *argv);
188	(void)strcpy(path, argv[argc - 1]);
189	baselen = strlen(path);
190	endp = &path[baselen];
191	if (!baselen || *(endp - 1) != '/') {
192		*endp++ = '/';
193		++baselen;
194	}
195	for (rval = 0; --argc; ++argv) {
196		/*
197		 * Find the last component of the source pathname.  It
198		 * may have trailing slashes.
199		 */
200		p = *argv + strlen(*argv);
201		while (p != *argv && p[-1] == '/')
202			--p;
203		while (p != *argv && p[-1] != '/')
204			--p;
205
206		if ((baselen + (len = strlen(p))) >= PATH_MAX) {
207			warnx("%s: destination pathname too long", *argv);
208			rval = 1;
209		} else {
210			memmove(endp, p, (size_t)len + 1);
211			if (COMPAT_MODE("bin/mv", "unix2003")) {
212				/*
213				 * For Unix 2003 compatibility, check if old and new are
214				 * same file, and produce an error * (like on Sun) that
215				 * conformance test 66 in mv.ex expects.
216				 */
217				if (!stat(*argv, &fsb) && !stat(path, &tsb) &&
218					fsb.st_ino == tsb.st_ino &&
219					fsb.st_dev == tsb.st_dev &&
220					fsb.st_gen == tsb.st_gen) {
221					(void)fprintf(stderr, "mv: %s and %s are identical\n",
222								*argv, path);
223					rval = 2; /* Like the Sun */
224				} else {
225					if (do_move(*argv, path))
226						rval = 1;
227				}
228			} else {
229				if (do_move(*argv, path))
230					rval = 1;
231			}
232		}
233	}
234	exit(rval);
235}
236
237int
238do_move(char *from, char *to)
239{
240	struct stat sb;
241	int ask, ch, first;
242	char modep[15];
243
244	/*
245	 * Check access.  If interactive and file exists, ask user if it
246	 * should be replaced.  Otherwise if file exists but isn't writable
247	 * make sure the user wants to clobber it.
248	 */
249	if (!fflg && !access(to, F_OK)) {
250
251		/* prompt only if source exist */
252	        if (lstat(from, &sb) == -1) {
253			warn("%s", from);
254			return (1);
255		}
256
257#define YESNO "(y/n [n]) "
258		ask = 0;
259		if (nflg) {
260			if (vflg)
261				printf("%s not overwritten\n", to);
262			return (0);
263		} else if (iflg) {
264			(void)fprintf(stderr, "overwrite %s? %s", to, YESNO);
265			ask = 1;
266		} else if (access(to, W_OK) && !stat(to, &sb)) {
267			strmode(sb.st_mode, modep);
268			(void)fprintf(stderr, "override %s%s%s/%s for %s? %s",
269			    modep + 1, modep[9] == ' ' ? "" : " ",
270			    user_from_uid(sb.st_uid, 0),
271			    group_from_gid(sb.st_gid, 0), to, YESNO);
272			ask = 1;
273		}
274		if (ask) {
275			first = ch = getchar();
276			while (ch != '\n' && ch != EOF)
277				ch = getchar();
278			if (first != 'y' && first != 'Y') {
279				(void)fprintf(stderr, "not overwritten\n");
280				return (0);
281			}
282		}
283	}
284	if (!rename(from, to)) {
285		if (vflg)
286			printf("%s -> %s\n", from, to);
287		return (0);
288	}
289
290	if (errno == EXDEV) {
291		struct statfs sfs;
292		char path[PATH_MAX];
293
294		/* Can't mv(1) a mount point. */
295		if (realpath(from, path) == NULL) {
296			warnx("cannot resolve %s: %s", from, path);
297			return (1);
298		}
299		if (!statfs(path, &sfs) && !strcmp(path, sfs.f_mntonname)) {
300			warnx("cannot rename a mount point");
301			return (1);
302		}
303	} else {
304		warn("rename %s to %s", from, to);
305		return (1);
306	}
307
308	/*
309	 * If rename fails because we're trying to cross devices, and
310	 * it's a regular file, do the copy internally; otherwise, use
311	 * cp and rm.
312	 */
313	if (lstat(from, &sb)) {
314		warn("%s", from);
315		return (1);
316	}
317	return (S_ISREG(sb.st_mode) ?
318	    fastcopy(from, to, &sb) : copy(from, to));
319}
320
321int
322fastcopy(char *from, char *to, struct stat *sbp)
323{
324	struct timeval tval[2];
325	static u_int blen;
326	static char *bp;
327	mode_t oldmode;
328	ssize_t nread;
329	int from_fd, to_fd;
330
331	if ((from_fd = open(from, O_RDONLY, 0)) < 0) {
332		warn("%s", from);
333		return (1);
334	}
335	if (blen < sbp->st_blksize) {
336		if (bp != NULL)
337			free(bp);
338		if ((bp = malloc((size_t)sbp->st_blksize)) == NULL) {
339			blen = 0;
340			warnx("malloc failed");
341			return (1);
342		}
343		blen = sbp->st_blksize;
344	}
345	while ((to_fd =
346	    open(to, O_CREAT | O_EXCL | O_TRUNC | O_WRONLY, 0)) < 0) {
347		if (errno == EEXIST && unlink(to) == 0)
348			continue;
349		warn("%s", to);
350		(void)close(from_fd);
351		return (1);
352	}
353#ifdef __APPLE__
354       {
355               struct statfs sfs;
356
357               /*
358                * Pre-allocate blocks for the destination file if it
359                * resides on Xsan.
360                */
361               if (fstatfs(to_fd, &sfs) == 0 &&
362                   strcmp(sfs.f_fstypename, "acfs") == 0) {
363                       fstore_t fst;
364
365                       fst.fst_flags = 0;
366                       fst.fst_posmode = F_PEOFPOSMODE;
367                       fst.fst_offset = 0;
368                       fst.fst_length = sbp->st_size;
369
370                       (void) fcntl(to_fd, F_PREALLOCATE, &fst);
371               }
372       }
373#endif /* __APPLE__ */
374	while ((nread = read(from_fd, bp, (size_t)blen)) > 0)
375		if (write(to_fd, bp, (size_t)nread) != nread) {
376			warn("%s", to);
377			goto err;
378		}
379	if (nread < 0) {
380		warn("%s", from);
381err:		if (unlink(to))
382			warn("%s: remove", to);
383		(void)close(from_fd);
384		(void)close(to_fd);
385		return (1);
386	}
387#ifdef __APPLE__
388	/* XATTR can fail if to_fd has mode 000 */
389	if (fcopyfile(from_fd, to_fd, NULL, COPYFILE_ACL | COPYFILE_XATTR) < 0) {
390		warn("%s: unable to move extended attributes and ACL from %s",
391		     to, from);
392	}
393#endif
394	(void)close(from_fd);
395
396	oldmode = sbp->st_mode & ALLPERMS;
397	if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) {
398		warn("%s: set owner/group (was: %lu/%lu)", to,
399		    (u_long)sbp->st_uid, (u_long)sbp->st_gid);
400		if (oldmode & (S_ISUID | S_ISGID)) {
401			warnx(
402"%s: owner/group changed; clearing suid/sgid (mode was 0%03o)",
403			    to, oldmode);
404			sbp->st_mode &= ~(S_ISUID | S_ISGID);
405		}
406	}
407	if (fchmod(to_fd, sbp->st_mode))
408		warn("%s: set mode (was: 0%03o)", to, oldmode);
409	/*
410	 * XXX
411	 * NFS doesn't support chflags; ignore errors unless there's reason
412	 * to believe we're losing bits.  (Note, this still won't be right
413	 * if the server supports flags and we were trying to *remove* flags
414	 * on a file that we copied, i.e., that we didn't create.)
415	 */
416	errno = 0;
417	if (fchflags(to_fd, (u_int)sbp->st_flags))
418		if (errno != ENOTSUP || sbp->st_flags != 0)
419			warn("%s: set flags (was: 0%07o)", to, sbp->st_flags);
420
421	tval[0].tv_sec = sbp->st_atime;
422	tval[1].tv_sec = sbp->st_mtime;
423	tval[0].tv_usec = tval[1].tv_usec = 0;
424	if (utimes(to, tval))
425		warn("%s: set times", to);
426
427	if (close(to_fd)) {
428		warn("%s", to);
429		return (1);
430	}
431
432	if (unlink(from)) {
433		warn("%s: remove", from);
434		return (1);
435	}
436	if (vflg)
437		printf("%s -> %s\n", from, to);
438	return (0);
439}
440
441int
442copy(char *from, char *to)
443{
444	int pid, status;
445
446	/* posix_spawn mv from to && rm from */
447
448	if ((pid = fork()) == 0) {
449		execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", "--", from, to,
450		    (char *)NULL);
451		warn("%s", _PATH_CP);
452		_exit(1);
453	}
454	if (waitpid(pid, &status, 0) == -1) {
455		warn("%s: waitpid", _PATH_CP);
456		return (1);
457	}
458	if (!WIFEXITED(status)) {
459		warnx("%s: did not terminate normally", _PATH_CP);
460		return (1);
461	}
462	if (WEXITSTATUS(status)) {
463		warnx("%s: terminated with %d (non-zero) status",
464		    _PATH_CP, WEXITSTATUS(status));
465		return (1);
466	}
467	if (!(pid = vfork())) {
468		execl(_PATH_RM, "mv", "-rf", "--", from, (char *)NULL);
469		warn("%s", _PATH_RM);
470		_exit(1);
471	}
472	if (waitpid(pid, &status, 0) == -1) {
473		warn("%s: waitpid", _PATH_RM);
474		return (1);
475	}
476	if (!WIFEXITED(status)) {
477		warnx("%s: did not terminate normally", _PATH_RM);
478		return (1);
479	}
480	if (WEXITSTATUS(status)) {
481		warnx("%s: terminated with %d (non-zero) status",
482		    _PATH_RM, WEXITSTATUS(status));
483		return (1);
484	}
485	return (0);
486}
487
488void
489usage(void)
490{
491
492	(void)fprintf(stderr, "%s\n%s\n",
493		      "usage: mv [-f | -i | -n] [-v] source target",
494		      "       mv [-f | -i | -n] [-v] source ... directory");
495	exit(EX_USAGE);
496}
497