1/* $NetBSD: mv.c,v 1.46 2020/06/24 16:58:12 riastradh Exp $ */
2
3/*
4 * Copyright (c) 1989, 1993, 1994
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * Ken Smith of The State University of New York at Buffalo.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the University nor the names of its contributors
19 *    may be used to endorse or promote products derived from this software
20 *    without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 */
34
35#include <sys/cdefs.h>
36#ifndef lint
37__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\
38 The Regents of the University of California.  All rights reserved.");
39#endif /* not lint */
40
41#ifndef lint
42#if 0
43static char sccsid[] = "@(#)mv.c	8.2 (Berkeley) 4/2/94";
44#else
45__RCSID("$NetBSD: mv.c,v 1.46 2020/06/24 16:58:12 riastradh Exp $");
46#endif
47#endif /* not lint */
48
49#include <sys/param.h>
50#include <sys/time.h>
51#include <sys/wait.h>
52#include <sys/stat.h>
53#include <sys/extattr.h>
54
55#include <err.h>
56#include <errno.h>
57#include <fcntl.h>
58#include <grp.h>
59#include <locale.h>
60#include <pwd.h>
61#include <stdio.h>
62#include <stdlib.h>
63#include <string.h>
64#include <unistd.h>
65
66#include "pathnames.h"
67
68static int fflg, hflg, iflg, vflg;
69static int stdin_ok;
70static sig_atomic_t pinfo;
71
72static int	copy(char *, char *);
73static int	do_move(char *, char *);
74static int	fastcopy(char *, char *, struct stat *);
75__dead static void	usage(void);
76
77static void
78progress(int sig __unused)
79{
80
81	pinfo++;
82}
83
84int
85main(int argc, char *argv[])
86{
87	int ch, len, rval;
88	char *p, *endp;
89	struct stat sb;
90	char path[MAXPATHLEN + 1];
91	size_t baselen;
92
93	setprogname(argv[0]);
94	(void)setlocale(LC_ALL, "");
95
96	while ((ch = getopt(argc, argv, "hifv")) != -1)
97		switch (ch) {
98		case 'h':
99			hflg = 1;
100			break;
101		case 'i':
102			fflg = 0;
103			iflg = 1;
104			break;
105		case 'f':
106			iflg = 0;
107			fflg = 1;
108			break;
109		case 'v':
110			vflg = 1;
111			break;
112		default:
113			usage();
114		}
115	argc -= optind;
116	argv += optind;
117
118	if (argc < 2)
119		usage();
120
121	stdin_ok = isatty(STDIN_FILENO);
122
123	(void)signal(SIGINFO, progress);
124
125	/*
126	 * If the stat on the target fails or the target isn't a directory,
127	 * try the move.  More than 2 arguments is an error in this case.
128	 */
129	if (hflg || stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) {
130		if (argc > 2)
131			usage();
132		exit(do_move(argv[0], argv[1]));
133	}
134
135	/* It's a directory, move each file into it. */
136	baselen = strlcpy(path, argv[argc - 1], sizeof(path));
137	if (baselen >= sizeof(path))
138		errx(1, "%s: destination pathname too long", argv[argc - 1]);
139	endp = &path[baselen];
140	if (!baselen || *(endp - 1) != '/') {
141		*endp++ = '/';
142		++baselen;
143	}
144	for (rval = 0; --argc; ++argv) {
145		p = *argv + strlen(*argv) - 1;
146		while (*p == '/' && p != *argv)
147			*p-- = '\0';
148		if ((p = strrchr(*argv, '/')) == NULL)
149			p = *argv;
150		else
151			++p;
152
153		if ((baselen + (len = strlen(p))) >= MAXPATHLEN) {
154			warnx("%s: destination pathname too long", *argv);
155			rval = 1;
156		} else {
157			memmove(endp, p, len + 1);
158			if (do_move(*argv, path))
159				rval = 1;
160		}
161	}
162	exit(rval);
163	/* NOTREACHED */
164}
165
166static int
167do_move(char *from, char *to)
168{
169	struct stat sb;
170	char modep[15];
171
172	/*
173	 * (1)	If the destination path exists, the -f option is not specified
174	 *	and either of the following conditions are true:
175	 *
176	 *	(a) The permissions of the destination path do not permit
177	 *	    writing and the standard input is a terminal.
178	 *	(b) The -i option is specified.
179	 *
180	 *	the mv utility shall write a prompt to standard error and
181	 *	read a line from standard input.  If the response is not
182	 *	affirmative, mv shall do nothing more with the current
183	 *	source file...
184	 */
185	if (!fflg && !access(to, F_OK)) {
186		int ask = 1;
187		int ch;
188
189		if (iflg) {
190			if (access(from, F_OK)) {
191				warn("rename %s", from);
192				return (1);
193			}
194			(void)fprintf(stderr, "overwrite %s? ", to);
195		} else if (stdin_ok && access(to, W_OK) && !stat(to, &sb)) {
196			if (access(from, F_OK)) {
197				warn("rename %s", from);
198				return (1);
199			}
200			strmode(sb.st_mode, modep);
201			(void)fprintf(stderr, "override %s%s%s/%s for %s? ",
202			    modep + 1, modep[9] == ' ' ? "" : " ",
203			    user_from_uid(sb.st_uid, 0),
204			    group_from_gid(sb.st_gid, 0), to);
205		} else
206			ask = 0;
207		if (ask) {
208			if ((ch = getchar()) != EOF && ch != '\n') {
209				int ch2;
210				while ((ch2 = getchar()) != EOF && ch2 != '\n')
211					continue;
212			}
213			if (ch != 'y' && ch != 'Y')
214				return (0);
215		}
216	}
217
218	/*
219	 * (2)	If rename() succeeds, mv shall do nothing more with the
220	 *	current source file.  If it fails for any other reason than
221	 *	EXDEV, mv shall write a diagnostic message to the standard
222	 *	error and do nothing more with the current source file.
223	 *
224	 * (3)	If the destination path exists, and it is a file of type
225	 *	directory and source_file is not a file of type directory,
226	 *	or it is a file not of type directory, and source file is
227	 *	a file of type directory, mv shall write a diagnostic
228	 *	message to standard error, and do nothing more with the
229	 *	current source file...
230	 */
231	if (!rename(from, to)) {
232		if (vflg)
233			printf("%s -> %s\n", from, to);
234		return (0);
235	}
236
237	if (errno != EXDEV) {
238		warn("rename %s to %s", from, to);
239		return (1);
240	}
241
242	/*
243	 * (4)	If the destination path exists, mv shall attempt to remove it.
244	 *	If this fails for any reason, mv shall write a diagnostic
245	 *	message to the standard error and do nothing more with the
246	 *	current source file...
247	 */
248	if (!lstat(to, &sb)) {
249		if ((S_ISDIR(sb.st_mode)) ? rmdir(to) : unlink(to)) {
250			warn("can't remove %s", to);
251			return (1);
252		}
253	}
254
255	/*
256	 * (5)	The file hierarchy rooted in source_file shall be duplicated
257	 *	as a file hierarchy rooted in the destination path...
258	 */
259	if (lstat(from, &sb)) {
260		warn("%s", from);
261		return (1);
262	}
263
264	return (S_ISREG(sb.st_mode) ?
265	    fastcopy(from, to, &sb) : copy(from, to));
266}
267
268static int
269fastcopy(char *from, char *to, struct stat *sbp)
270{
271#if defined(__NetBSD__)
272	struct timespec ts[2];
273#else
274	struct timeval tval[2];
275#endif
276	static blksize_t blen;
277	static char *bp;
278	int from_fd, to_fd;
279	ssize_t nread;
280	off_t total = 0;
281
282	if ((from_fd = open(from, O_RDONLY, 0)) < 0) {
283		warn("%s", from);
284		return (1);
285	}
286	if ((to_fd =
287	    open(to, O_CREAT | O_TRUNC | O_WRONLY, sbp->st_mode)) < 0) {
288		warn("%s", to);
289		(void)close(from_fd);
290		return (1);
291	}
292	if (!blen && !(bp = malloc(blen = sbp->st_blksize))) {
293		warn(NULL);
294		blen = 0;
295		(void)close(from_fd);
296		(void)close(to_fd);
297		return (1);
298	}
299	while ((nread = read(from_fd, bp, blen)) > 0) {
300		if (write(to_fd, bp, nread) != nread) {
301			warn("%s", to);
302			goto err;
303		}
304		total += nread;
305		if (pinfo) {
306			int pcent = (int)((100.0 * total) / sbp->st_size);
307
308			pinfo = 0;
309			(void)fprintf(stderr, "%s => %s %llu/%llu bytes %d%% "
310			    "written\n", from, to, (unsigned long long)total,
311			    (unsigned long long)sbp->st_size, pcent);
312		}
313	}
314	if (nread < 0) {
315		warn("%s", from);
316err:		if (unlink(to))
317			warn("%s: remove", to);
318		(void)close(from_fd);
319		(void)close(to_fd);
320		return (1);
321	}
322
323	if (fcpxattr(from_fd, to_fd) == -1)
324		warn("%s: error copying extended attributes", to);
325
326	(void)close(from_fd);
327#ifdef BSD4_4
328#if defined(__NetBSD__)
329	ts[0] = sbp->st_atimespec;
330	ts[1] = sbp->st_mtimespec;
331#else
332	TIMESPEC_TO_TIMEVAL(&tval[0], &sbp->st_atimespec);
333	TIMESPEC_TO_TIMEVAL(&tval[1], &sbp->st_mtimespec);
334#endif
335#else
336	tval[0].tv_sec = sbp->st_atime;
337	tval[1].tv_sec = sbp->st_mtime;
338	tval[0].tv_usec = 0;
339	tval[1].tv_usec = 0;
340#endif
341#ifdef __SVR4
342	if (utimes(to, tval))
343#else
344#if defined(__NetBSD__)
345	if (futimens(to_fd, ts))
346#else
347	if (futimes(to_fd, tval))
348#endif
349#endif
350		warn("%s: set times", to);
351	if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) {
352		if (errno != EPERM)
353			warn("%s: set owner/group", to);
354		sbp->st_mode &= ~(S_ISUID | S_ISGID);
355	}
356	if (fchmod(to_fd, sbp->st_mode))
357		warn("%s: set mode", to);
358	if (fchflags(to_fd, sbp->st_flags) && (errno != EOPNOTSUPP))
359		warn("%s: set flags (was: 0%07o)", to, sbp->st_flags);
360
361	if (close(to_fd)) {
362		warn("%s", to);
363		return (1);
364	}
365
366	if (unlink(from)) {
367		warn("%s: remove", from);
368		return (1);
369	}
370
371	if (vflg)
372		printf("%s -> %s\n", from, to);
373
374	return (0);
375}
376
377static int
378copy(char *from, char *to)
379{
380	pid_t pid;
381	int status;
382
383	if ((pid = vfork()) == 0) {
384		execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", "--", from, to, NULL);
385		warn("%s", _PATH_CP);
386		_exit(1);
387	}
388	if (waitpid(pid, &status, 0) == -1) {
389		warn("%s: waitpid", _PATH_CP);
390		return (1);
391	}
392	if (!WIFEXITED(status)) {
393		warnx("%s: did not terminate normally", _PATH_CP);
394		return (1);
395	}
396	if (WEXITSTATUS(status)) {
397		warnx("%s: terminated with %d (non-zero) status",
398		    _PATH_CP, WEXITSTATUS(status));
399		return (1);
400	}
401	if (!(pid = vfork())) {
402		execl(_PATH_RM, "mv", "-rf", "--", from, NULL);
403		warn("%s", _PATH_RM);
404		_exit(1);
405	}
406	if (waitpid(pid, &status, 0) == -1) {
407		warn("%s: waitpid", _PATH_RM);
408		return (1);
409	}
410	if (!WIFEXITED(status)) {
411		warnx("%s: did not terminate normally", _PATH_RM);
412		return (1);
413	}
414	if (WEXITSTATUS(status)) {
415		warnx("%s: terminated with %d (non-zero) status",
416		    _PATH_RM, WEXITSTATUS(status));
417		return (1);
418	}
419	return (0);
420}
421
422static void
423usage(void)
424{
425	(void)fprintf(stderr, "usage: %s [-fhiv] source target\n"
426	    "       %s [-fiv] source ... directory\n", getprogname(),
427	    getprogname());
428	exit(1);
429	/* NOTREACHED */
430}
431