file_cmds-202.2.tar.gz
[apple/file_cmds.git] / mv / mv.c
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
38 static 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
45 static 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
83 int fflg, iflg, nflg, vflg;
84
85 int copy(char *, char *);
86 int do_move(char *, char *);
87 int fastcopy(char *, char *, struct stat *);
88 void usage(void);
89
90 int
91 main(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
236 int
237 do_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((unsigned long)sb.st_uid, 0),
270 group_from_gid((unsigned long)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
320 int
321 fastcopy(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 int nread, from_fd, to_fd;
328
329 if ((from_fd = open(from, O_RDONLY, 0)) < 0) {
330 warn("%s", from);
331 return (1);
332 }
333 if (blen < sbp->st_blksize) {
334 if (bp != NULL)
335 free(bp);
336 if ((bp = malloc((size_t)sbp->st_blksize)) == NULL) {
337 blen = 0;
338 warnx("malloc failed");
339 return (1);
340 }
341 blen = sbp->st_blksize;
342 }
343 while ((to_fd =
344 open(to, O_CREAT | O_EXCL | O_TRUNC | O_WRONLY, 0)) < 0) {
345 if (errno == EEXIST && unlink(to) == 0)
346 continue;
347 warn("%s", to);
348 (void)close(from_fd);
349 return (1);
350 }
351 #ifdef __APPLE__
352 {
353 struct statfs sfs;
354
355 /*
356 * Pre-allocate blocks for the destination file if it
357 * resides on Xsan.
358 */
359 if (fstatfs(to_fd, &sfs) == 0 &&
360 strcmp(sfs.f_fstypename, "acfs") == 0) {
361 fstore_t fst;
362
363 fst.fst_flags = 0;
364 fst.fst_posmode = F_PEOFPOSMODE;
365 fst.fst_offset = 0;
366 fst.fst_length = sbp->st_size;
367
368 (void) fcntl(to_fd, F_PREALLOCATE, &fst);
369 }
370 }
371 #endif /* __APPLE__ */
372 while ((nread = read(from_fd, bp, (size_t)blen)) > 0)
373 if (write(to_fd, bp, (size_t)nread) != nread) {
374 warn("%s", to);
375 goto err;
376 }
377 if (nread < 0) {
378 warn("%s", from);
379 err: if (unlink(to))
380 warn("%s: remove", to);
381 (void)close(from_fd);
382 (void)close(to_fd);
383 return (1);
384 }
385 #ifdef __APPLE__
386 /* XATTR can fail if to_fd has mode 000 */
387 if (fcopyfile(from_fd, to_fd, NULL, COPYFILE_ACL | COPYFILE_XATTR) < 0) {
388 warn("%s: unable to move extended attributes and ACL from %s",
389 to, from);
390 }
391 #endif
392 (void)close(from_fd);
393
394 oldmode = sbp->st_mode & ALLPERMS;
395 if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) {
396 warn("%s: set owner/group (was: %lu/%lu)", to,
397 (u_long)sbp->st_uid, (u_long)sbp->st_gid);
398 if (oldmode & (S_ISUID | S_ISGID)) {
399 warnx(
400 "%s: owner/group changed; clearing suid/sgid (mode was 0%03o)",
401 to, oldmode);
402 sbp->st_mode &= ~(S_ISUID | S_ISGID);
403 }
404 }
405 if (fchmod(to_fd, sbp->st_mode))
406 warn("%s: set mode (was: 0%03o)", to, oldmode);
407 /*
408 * XXX
409 * NFS doesn't support chflags; ignore errors unless there's reason
410 * to believe we're losing bits. (Note, this still won't be right
411 * if the server supports flags and we were trying to *remove* flags
412 * on a file that we copied, i.e., that we didn't create.)
413 */
414 errno = 0;
415 if (fchflags(to_fd, (u_long)sbp->st_flags))
416 if (errno != ENOTSUP || sbp->st_flags != 0)
417 warn("%s: set flags (was: 0%07o)", to, sbp->st_flags);
418
419 tval[0].tv_sec = sbp->st_atime;
420 tval[1].tv_sec = sbp->st_mtime;
421 tval[0].tv_usec = tval[1].tv_usec = 0;
422 if (utimes(to, tval))
423 warn("%s: set times", to);
424
425 if (close(to_fd)) {
426 warn("%s", to);
427 return (1);
428 }
429
430 if (unlink(from)) {
431 warn("%s: remove", from);
432 return (1);
433 }
434 if (vflg)
435 printf("%s -> %s\n", from, to);
436 return (0);
437 }
438
439 int
440 copy(char *from, char *to)
441 {
442 int pid, status;
443
444 if ((pid = fork()) == 0) {
445 execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", "--", from, to,
446 (char *)NULL);
447 warn("%s", _PATH_CP);
448 _exit(1);
449 }
450 if (waitpid(pid, &status, 0) == -1) {
451 warn("%s: waitpid", _PATH_CP);
452 return (1);
453 }
454 if (!WIFEXITED(status)) {
455 warnx("%s: did not terminate normally", _PATH_CP);
456 return (1);
457 }
458 if (WEXITSTATUS(status)) {
459 warnx("%s: terminated with %d (non-zero) status",
460 _PATH_CP, WEXITSTATUS(status));
461 return (1);
462 }
463 if (!(pid = vfork())) {
464 execl(_PATH_RM, "mv", "-rf", "--", from, (char *)NULL);
465 warn("%s", _PATH_RM);
466 _exit(1);
467 }
468 if (waitpid(pid, &status, 0) == -1) {
469 warn("%s: waitpid", _PATH_RM);
470 return (1);
471 }
472 if (!WIFEXITED(status)) {
473 warnx("%s: did not terminate normally", _PATH_RM);
474 return (1);
475 }
476 if (WEXITSTATUS(status)) {
477 warnx("%s: terminated with %d (non-zero) status",
478 _PATH_RM, WEXITSTATUS(status));
479 return (1);
480 }
481 return (0);
482 }
483
484 void
485 usage(void)
486 {
487
488 (void)fprintf(stderr, "%s\n%s\n",
489 "usage: mv [-f | -i | -n] [-v] source target",
490 " mv [-f | -i | -n] [-v] source ... directory");
491 exit(EX_USAGE);
492 }