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