mv.c revision 1.27
1146011Snyan/* $NetBSD: mv.c,v 1.27 2001/09/16 21:53:55 wiz Exp $ */
243561Skato
343561Skato/*
443561Skato * Copyright (c) 1989, 1993, 1994
543561Skato *	The Regents of the University of California.  All rights reserved.
643561Skato *
743561Skato * This code is derived from software contributed to Berkeley by
843561Skato * Ken Smith of The State University of New York at Buffalo.
943561Skato *
1043561Skato * Redistribution and use in source and binary forms, with or without
1143561Skato * modification, are permitted provided that the following conditions
1243561Skato * are met:
1343561Skato * 1. Redistributions of source code must retain the above copyright
1443561Skato *    notice, this list of conditions and the following disclaimer.
1543561Skato * 2. Redistributions in binary form must reproduce the above copyright
1643561Skato *    notice, this list of conditions and the following disclaimer in the
1743561Skato *    documentation and/or other materials provided with the distribution.
1843561Skato * 3. All advertising materials mentioning features or use of this software
1943561Skato *    must display the following acknowledgement:
2043561Skato *	This product includes software developed by the University of
2143561Skato *	California, Berkeley and its contributors.
2243561Skato * 4. Neither the name of the University nor the names of its contributors
2343561Skato *    may be used to endorse or promote products derived from this software
2443561Skato *    without specific prior written permission.
2543561Skato *
2643561Skato * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27119880Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2843561Skato * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2943561Skato * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30119880Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31119880Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32119880Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
3343561Skato * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3443561Skato * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3543561Skato * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3643561Skato * SUCH DAMAGE.
3768358Snyan */
3843561Skato
3943561Skato#include <sys/cdefs.h>
4043561Skato#ifndef lint
4143561Skato__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\n\
4243561Skato	The Regents of the University of California.  All rights reserved.\n");
4343561Skato#endif /* not lint */
4443561Skato
4543561Skato#ifndef lint
4643561Skato#if 0
4743561Skatostatic char sccsid[] = "@(#)mv.c	8.2 (Berkeley) 4/2/94";
4843561Skato#else
4943561Skato__RCSID("$NetBSD: mv.c,v 1.27 2001/09/16 21:53:55 wiz Exp $");
5043561Skato#endif
5143561Skato#endif /* not lint */
5243561Skato
5385063Snyan#include <sys/param.h>
5485063Snyan#include <sys/time.h>
5585063Snyan#include <sys/wait.h>
5685063Snyan#include <sys/stat.h>
5768358Snyan
5843561Skato#include <err.h>
5943561Skato#include <errno.h>
6043561Skato#include <fcntl.h>
6143561Skato#include <grp.h>
6243561Skato#include <locale.h>
6343561Skato#include <pwd.h>
6443561Skato#include <stdio.h>
6543561Skato#include <stdlib.h>
6643561Skato#include <string.h>
6743561Skato#include <unistd.h>
6885063Snyan
6985063Snyan#include "pathnames.h"
7043561Skato
7143561Skatoint fflg, iflg;
7243561Skatoint stdin_ok;
7343561Skato
7443561Skatoint copy(char *, char *);
7543561Skatoint do_move(char *, char *);
7685065Snyanint fastcopy(char *, char *, struct stat *);
7785065Snyanvoid usage(void);
7885065Snyanint main(int, char *[]);
7985065Snyan
8085065Snyanint
8185065Snyanmain(int argc, char *argv[])
8285065Snyan{
8385065Snyan	int baselen, ch, len, rval;
8485065Snyan	char *p, *endp;
8585065Snyan	struct stat sb;
8685065Snyan	char path[MAXPATHLEN + 1];
8785065Snyan
8885065Snyan	setprogname(argv[0]);
8985065Snyan	(void)setlocale(LC_ALL, "");
9085065Snyan
9185065Snyan	while ((ch = getopt(argc, argv, "if")) != -1)
9285065Snyan		switch (ch) {
9343561Skato		case 'i':
9485065Snyan			fflg = 0;
9585065Snyan			iflg = 1;
9685065Snyan			break;
9785065Snyan		case 'f':
9885065Snyan			iflg = 0;
9985065Snyan			fflg = 1;
10085065Snyan			break;
10185065Snyan		case '?':
10285065Snyan		default:
10385065Snyan			usage();
10485065Snyan		}
10585065Snyan	argc -= optind;
10685065Snyan	argv += optind;
10785065Snyan
10885065Snyan	if (argc < 2)
10985065Snyan		usage();
11085065Snyan
11185065Snyan	stdin_ok = isatty(STDIN_FILENO);
11285065Snyan
11385065Snyan	/*
11443561Skato	 * If the stat on the target fails or the target isn't a directory,
11543561Skato	 * try the move.  More than 2 arguments is an error in this case.
11643561Skato	 */
11743561Skato	if (stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) {
11843561Skato		if (argc > 2)
11943561Skato			usage();
12043561Skato		exit(do_move(argv[0], argv[1]));
12143561Skato	}
12243561Skato
12343561Skato	/* It's a directory, move each file into it. */
12443561Skato	(void)strcpy(path, argv[argc - 1]);
12543561Skato	baselen = strlen(path);
12643561Skato	endp = &path[baselen];
12743561Skato	*endp++ = '/';
12843561Skato	++baselen;
12943561Skato	for (rval = 0; --argc; ++argv) {
13043561Skato		p = *argv + strlen(*argv) - 1;
13143561Skato		while (*p == '/' && p != *argv)
13243561Skato			*p-- = '\0';
13343561Skato		if ((p = strrchr(*argv, '/')) == NULL)
13443561Skato			p = *argv;
13543561Skato		else
13643561Skato			++p;
13743561Skato
13843561Skato		if ((baselen + (len = strlen(p))) >= MAXPATHLEN) {
13943561Skato			warnx("%s: destination pathname too long", *argv);
14043561Skato			rval = 1;
14143561Skato		} else {
14243561Skato			memmove(endp, p, len + 1);
14343561Skato			if (do_move(*argv, path))
14443561Skato				rval = 1;
145146011Snyan		}
14643561Skato	}
14743561Skato	exit(rval);
14885061Snyan	/* NOTREACHED */
14943561Skato}
15043561Skato
15185061Snyanint
15285061Snyando_move(char *from, char *to)
15343561Skato{
15485061Snyan	struct stat sb;
15585061Snyan	char modep[15];
15643561Skato
15743561Skato	/*
15843561Skato	 * (1)	If the destination path exists, the -f option is not specified
15943561Skato	 *	and either of the following conditions are true:
16043561Skato	 *
16143561Skato	 *	(a) The permissions of the destination path do not permit
16243561Skato	 *	    writing and the standard input is a terminal.
16343561Skato	 *	(b) The -i option is specified.
16443561Skato	 *
16543561Skato	 *	the mv utility shall write a prompt to standard error and
16685061Snyan	 *	read a line from standard input.  If the response is not
16785063Snyan	 *	affirmative, mv shall do nothing more with the current
16885063Snyan	 *	source file...
16943561Skato	 */
17085061Snyan	if (!fflg && !access(to, F_OK)) {
17185061Snyan		int ask = 1;
17285061Snyan		int ch;
17343561Skato
17443561Skato		if (iflg) {
17543561Skato			if (access(from, F_OK)) {
17654086Snyan				warn("rename %s", from);
17754086Snyan				return (1);
17885061Snyan			}
17954086Snyan			(void)fprintf(stderr, "overwrite %s? ", to);
18054086Snyan		} else if (stdin_ok && access(to, W_OK) && !stat(to, &sb)) {
18154086Snyan			if (access(from, F_OK)) {
18254086Snyan				warn("rename %s", from);
18354086Snyan				return (1);
18454086Snyan			}
18554086Snyan			strmode(sb.st_mode, modep);
18643561Skato			(void)fprintf(stderr, "override %s%s%s/%s for %s? ",
18743561Skato			    modep + 1, modep[9] == ' ' ? "" : " ",
18843561Skato			    user_from_uid(sb.st_uid, 0),
18943561Skato			    group_from_gid(sb.st_gid, 0), to);
19043561Skato		} else
19143561Skato			ask = 0;
19243561Skato		if (ask) {
19343561Skato			if ((ch = getchar()) != EOF && ch != '\n')
19443561Skato				while (getchar() != '\n');
19543561Skato			if (ch != 'y' && ch != 'Y')
19643561Skato				return (0);
19743561Skato		}
19843561Skato	}
19943561Skato
20043561Skato	/*
20143561Skato	 * (2)	If rename() succeeds, mv shall do nothing more with the
20243561Skato	 *	current source file.  If it fails for any other reason than
20343561Skato	 *	EXDEV, mv shall write a diagnostic message to the standard
20443561Skato	 *	error and do nothing more with the current source file.
20543561Skato	 *
20643561Skato	 * (3)	If the destination path exists, and it is a file of type
20743561Skato	 *	directory and source_file is not a file of type directory,
20843561Skato	 *	or it is a file not of type directory, and source file is
20943561Skato	 *	a file of type directory, mv shall write a diagnostic
21043561Skato	 *	message to standard error, and do nothing more with the
21143561Skato	 *	current source file...
21243561Skato	 */
21343561Skato	if (!rename(from, to))
21485061Snyan		return (0);
21543561Skato
21643561Skato	if (errno != EXDEV) {
21743561Skato		warn("rename %s to %s", from, to);
21843561Skato		return (1);
21943561Skato	}
22043561Skato
22143561Skato	/*
22243561Skato	 * (4)	If the destination path exists, mv shall attempt to remove it.
22385061Snyan	 *	If this fails for any reason, mv shall write a diagnostic
22443561Skato	 *	message to the standard error and do nothing more with the
22543561Skato	 *	current source file...
22643561Skato	 */
22743561Skato	if (!lstat(to, &sb)) {
22843561Skato		if ((S_ISDIR(sb.st_mode)) ? rmdir(to) : unlink(to)) {
22954086Snyan			warn("can't remove %s", to);
23043561Skato			return (1);
23143561Skato		}
23243561Skato	}
23343561Skato
23443561Skato	/*
23543561Skato	 * (5)	The file hierarchy rooted in source_file shall be duplicated
23685061Snyan	 *	as a file hierarchy rooted in the destination path...
23743561Skato	 */
23843561Skato	if (lstat(from, &sb)) {
23943561Skato		warn("%s", from);
24043561Skato		return (1);
24143561Skato	}
24243561Skato	return (S_ISREG(sb.st_mode) ?
24343561Skato	    fastcopy(from, to, &sb) : copy(from, to));
24485061Snyan}
24585061Snyan
24643561Skatoint
24785061Snyanfastcopy(char *from, char *to, struct stat *sbp)
24885061Snyan{
24985061Snyan	struct timeval tval[2];
25043561Skato	static u_int blen;
25185061Snyan	static char *bp;
25285061Snyan	int nread, from_fd, to_fd;
25385061Snyan
25485061Snyan	if ((from_fd = open(from, O_RDONLY, 0)) < 0) {
25585061Snyan		warn("%s", from);
25685061Snyan		return (1);
25785061Snyan	}
25885061Snyan	if ((to_fd =
25943561Skato	    open(to, O_CREAT | O_TRUNC | O_WRONLY, sbp->st_mode)) < 0) {
26085061Snyan		warn("%s", to);
26185061Snyan		(void)close(from_fd);
26285061Snyan		return (1);
26385061Snyan	}
26443561Skato	if (!blen && !(bp = malloc(blen = sbp->st_blksize))) {
26585061Snyan		warn(NULL);
26685061Snyan		return (1);
26743561Skato	}
26885061Snyan	while ((nread = read(from_fd, bp, blen)) > 0)
26985061Snyan		if (write(to_fd, bp, nread) != nread) {
27085061Snyan			warn("%s", to);
27185061Snyan			goto err;
27285061Snyan		}
27385061Snyan	if (nread < 0) {
27485061Snyan		warn("%s", from);
27585061Snyanerr:		if (unlink(to))
27685061Snyan			warn("%s: remove", to);
27785061Snyan		(void)close(from_fd);
27885061Snyan		(void)close(to_fd);
27943561Skato		return (1);
28085061Snyan	}
28143561Skato	(void)close(from_fd);
28243561Skato#ifdef BSD4_4
28343561Skato	TIMESPEC_TO_TIMEVAL(&tval[0], &sbp->st_atimespec);
28443561Skato	TIMESPEC_TO_TIMEVAL(&tval[1], &sbp->st_mtimespec);
28543561Skato#else
28643561Skato	tval[0].tv_sec = sbp->st_atime;
28743561Skato	tval[1].tv_sec = sbp->st_mtime;
28843561Skato	tval[0].tv_usec = 0;
28943561Skato	tval[1].tv_usec = 0;
29043561Skato#endif
29143561Skato#ifdef __SVR4
29243561Skato	if (utimes(to, tval))
29385061Snyan#else
29443561Skato	if (futimes(to_fd, tval))
29543561Skato#endif
29643561Skato		warn("%s: set times", to);
29743561Skato	if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) {
29843561Skato		if (errno != EPERM)
29943561Skato			warn("%s: set owner/group", to);
30043561Skato		sbp->st_mode &= ~(S_ISUID | S_ISGID);
30143561Skato	}
30243561Skato	if (fchmod(to_fd, sbp->st_mode))
30385061Snyan		warn("%s: set mode", to);
30485061Snyan	if (fchflags(to_fd, sbp->st_flags) && (errno != EOPNOTSUPP))
30543561Skato		warn("%s: set flags (was: 0%07o)", to, sbp->st_flags);
30643561Skato
30743561Skato	if (close(to_fd)) {
30843561Skato		warn("%s", to);
30943561Skato		return (1);
31043561Skato	}
31185061Snyan
31285061Snyan	if (unlink(from)) {
31385063Snyan		warn("%s: remove", from);
31485061Snyan		return (1);
31585061Snyan	}
31643561Skato	return (0);
31743561Skato}
31843561Skato
31943561Skatoint
32043561Skatocopy(char *from, char *to)
32143561Skato{
32243561Skato	int pid, status;
32343561Skato
32468358Snyan	if ((pid = vfork()) == 0) {
32543561Skato		execl(_PATH_CP, "mv", "-PRp", from, to, NULL);
32685061Snyan		warn("%s", _PATH_CP);
32785061Snyan		_exit(1);
32843561Skato	}
32985061Snyan	if (waitpid(pid, &status, 0) == -1) {
33085061Snyan		warn("%s: waitpid", _PATH_CP);
33185061Snyan		return (1);
33285061Snyan	}
33385061Snyan	if (!WIFEXITED(status)) {
33485061Snyan		warn("%s: did not terminate normally", _PATH_CP);
33585061Snyan		return (1);
33685061Snyan	}
33785061Snyan	if (WEXITSTATUS(status)) {
33885061Snyan		warn("%s: terminated with %d (non-zero) status",
33985061Snyan		    _PATH_CP, WEXITSTATUS(status));
34043561Skato		return (1);
34143561Skato	}
34243561Skato	if (!(pid = vfork())) {
34343561Skato		execl(_PATH_RM, "mv", "-rf", from, NULL);
34468358Snyan		warn("%s", _PATH_RM);
34543561Skato		_exit(1);
34685061Snyan	}
347124647Snyan	if (waitpid(pid, &status, 0) == -1) {
34885061Snyan		warn("%s: waitpid", _PATH_RM);
34943561Skato		return (1);
35043561Skato	}
35143561Skato	if (!WIFEXITED(status)) {
35243561Skato		warn("%s: did not terminate normally", _PATH_RM);
35343561Skato		return (1);
35443561Skato	}
35543561Skato	if (WEXITSTATUS(status)) {
35643561Skato		warn("%s: terminated with %d (non-zero) status",
35743561Skato		    _PATH_RM, WEXITSTATUS(status));
35843561Skato		return (1);
35943561Skato	}
36043561Skato	return (0);
36143561Skato}
36285063Snyan
36385061Snyanvoid
36443561Skatousage(void)
36585063Snyan{
36685063Snyan	(void)fprintf(stderr, "usage: %s [-fi] source target\n"
36785063Snyan	    "       %s [-fi] source ... directory\n", getprogname(),
36843561Skato	    getprogname());
36985063Snyan	exit(1);
37043561Skato	/* NOTREACHED */
37143561Skato}
37243561Skato