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