xref: /original-bsd/bin/mv/mv.c (revision 80a2c5b1)
1 #ifndef lint
2 static	char *sccsid = "@(#)mv.c	4.12 (Berkeley) 83/03/21";
3 #endif
4 
5 /*
6  * mv file1 file2
7  */
8 #include <sys/param.h>
9 #include <sys/stat.h>
10 
11 #include <stdio.h>
12 #include <dir.h>
13 #include <errno.h>
14 #include <signal.h>
15 
16 #define	DELIM	'/'
17 #define MODEBITS 07777
18 
19 #define	ISDIR(st)	(((st).st_mode&S_IFMT) == S_IFDIR)
20 #define	ISLNK(st)	(((st).st_mode&S_IFMT) == S_IFLNK)
21 #define	ISREG(st)	(((st).st_mode&S_IFMT) == S_IFREG)
22 #define	ISDEV(st) \
23 	(((st).st_mode&S_IFMT) == S_IFCHR || ((st).st_mode&S_IFMT) == S_IFBLK)
24 
25 char	*sprintf();
26 char	*dname();
27 struct	stat s1, s2;
28 int	iflag = 0;	/* interactive mode */
29 int	fflag = 0;	/* force overwriting */
30 extern	unsigned errno;
31 
32 main(argc, argv)
33 	register char *argv[];
34 {
35 	register i, r;
36 	register char *arg;
37 	char *dest;
38 
39 	if (argc < 2)
40 		goto usage;
41 	while (argc > 1 && *argv[1] == '-') {
42 		argc--;
43 		arg = *++argv;
44 
45 		/*
46 		 * all files following a null option
47 		 * are considered file names
48 		 */
49 		if (*(arg+1) == '\0')
50 			break;
51 		while (*++arg != '\0') switch (*arg) {
52 
53 		case 'i':
54 			iflag++;
55 			break;
56 
57 		case 'f':
58 			fflag++;
59 			break;
60 
61 		default:
62 			goto usage;
63 		}
64 	}
65 	if (argc < 3)
66 		goto usage;
67 	dest = argv[argc-1];
68 	if (stat(dest, &s2) >= 0 && ISDIR(s2)) {
69 		r = 0;
70 		for (i = 1; i < argc-1; i++)
71 			r |= movewithshortname(argv[i], dest);
72 		exit(r);
73 	}
74 	if (argc > 3)
75 		goto usage;
76 	r = move(argv[1], argv[2]);
77 	exit(r);
78 	/*NOTREACHED*/
79 usage:
80 	fprintf(stderr,
81 "usage: mv [-if] f1 f2 or mv [-if] f1 ... fn d1 (`fn' is a file or directory)\n");
82 	return (1);
83 }
84 
85 movewithshortname(src, dest)
86 	char *src, *dest;
87 {
88 	register char *shortname;
89 	char target[MAXPATHLEN + 1];
90 
91 	shortname = dname(src);
92 	if (strlen(dest) + strlen(shortname) > MAXPATHLEN - 1) {
93 		error("%s/%s: pathname too long", dest,
94 			shortname);
95 		return (1);
96 	}
97 	sprintf(target, "%s/%s", dest, shortname);
98 	return (move(src, target));
99 }
100 
101 move(source, target)
102 	char *source, *target;
103 {
104 	int targetexists;
105 
106 	if (lstat(source, &s1) < 0) {
107 		error("cannot access %s", source);
108 		return (1);
109 	}
110 	/*
111 	 * First, try to rename source to destination.
112 	 * The only reason we continue on failure is if
113 	 * the move is on a nondirectory and not across
114 	 * file systems.
115 	 */
116 	targetexists = lstat(target, &s2) >= 0;
117 	if (targetexists) {
118 		if (iflag && !fflag && query("remove %s? ", target) == 0)
119 			return (1);
120 		if (s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino) {
121 			error("%s and %s are identical", source, target);
122 			return (1);
123 		}
124 		if (access(target, 2) < 0 && !fflag && isatty(fileno(stdin))) {
125 			if (query("override protection %o for %s? ",
126 			  s2.st_mode & MODEBITS, target) == 0)
127 				return (1);
128 		}
129 	}
130 	if (rename(source, target) >= 0)
131 		return (0);
132 	if (errno != EXDEV) {
133 		Perror2(source, "rename");
134 		return (1);
135 	}
136 	if (ISDIR(s1)) {
137 		error("can't mv directories across file systems");
138 		return (1);
139 	}
140 	if (targetexists && unlink(target) < 0) {
141 		error("cannot unlink %s", target);
142 		return (1);
143 	}
144 	/*
145 	 * File can't be renamed, try to recreate the symbolic
146 	 * link or special device, or copy the file wholesale
147 	 * between file systems.
148 	 */
149 	if (ISLNK(s1)) {
150 		register m;
151 		char symln[MAXPATHLEN];
152 
153 		if (readlink(source, symln, sizeof (symln)) < 0) {
154 			Perror(source);
155 			return (1);
156 		}
157 		m = umask(~(s1.st_mode & MODEBITS));
158 		if (symlink(symln, target) < 0) {
159 			Perror(target);
160 			return (1);
161 		}
162 		(void) umask(m);
163 		goto cleanup;
164 	}
165 	if (ISDEV(s1)) {
166 		time_t tv[2];
167 
168 		if (mknod(target, s1.st_mode, s1.st_rdev) < 0) {
169 			Perror(target);
170 			return (1);
171 		}
172 		/* kludge prior to utimes */
173 		tv[0] = s1.st_atime;
174 		tv[1] = s1.st_mtime;
175 		(void) utime(target, tv);
176 		goto cleanup;
177 	}
178 	if (ISREG(s1)) {
179 		int i, c, status;
180 		time_t tv[2];
181 
182 		i = fork();
183 		if (i == -1) {
184 			error("try again");
185 			return (1);
186 		}
187 		if (i == 0) {
188 			execl("/bin/cp", "cp", source, target, 0);
189 			error("cannot exec /bin/cp");
190 			exit(1);
191 		}
192 		while ((c = wait(&status)) != i && c != -1)
193 			;
194 		if (status != 0)
195 			return (1);
196 		/* kludge prior to utimes */
197 		tv[0] = s1.st_atime;
198 		tv[1] = s1.st_mtime;
199 		(void) utime(target, tv);
200 		goto cleanup;
201 	}
202 	error("%s: unknown file type %o", source, s1.st_mode);
203 	return (1);
204 
205 cleanup:
206 	if (unlink(source) < 0) {
207 		error("cannot unlink %s", source);
208 		return (1);
209 	}
210 	return (0);
211 }
212 
213 /*VARARGS*/
214 query(prompt, a1, a2)
215 	char *a1;
216 {
217 	register char i, c;
218 
219 	fprintf(stderr, prompt, a1, a2);
220 	i = c = getchar();
221 	while (c != '\n' && c != EOF)
222 		c = getchar();
223 	return (i == 'y');
224 }
225 
226 char *
227 dname(name)
228 	register char *name;
229 {
230 	register char *p;
231 
232 	p = name;
233 	while (*p)
234 		if (*p++ == DELIM && *p)
235 			name = p;
236 	return name;
237 }
238 
239 /*VARARGS*/
240 error(fmt, a1, a2)
241 	char *fmt;
242 {
243 
244 	fprintf(stderr, "mv: ");
245 	fprintf(stderr, fmt, a1, a2);
246 	fprintf(stderr, "\n");
247 }
248 
249 Perror(s)
250 	char *s;
251 {
252 	char buf[MAXPATHLEN + 10];
253 
254 	sprintf(buf, "mv: %s", s);
255 	perror(buf);
256 }
257 
258 Perror2(s1, s2)
259 	char *s1, *s2;
260 {
261 	char buf[MAXPATHLEN + 20];
262 
263 	sprintf(buf, "mv: %s: %s", s1, s2);
264 	perror(buf);
265 }
266