file_cmds-264.1.1.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 #include <sys/cdefs.h>
38 #ifndef lint
39 __used static char const copyright[] =
40 "@(#) Copyright (c) 1989, 1993, 1994\n\
41 The Regents of the University of California. All rights reserved.\n";
42 #endif /* not lint */
43
44 #ifndef lint
45 #if 0
46 static char sccsid[] = "@(#)mv.c 8.2 (Berkeley) 4/2/94";
47 #endif
48 #endif /* not lint */
49 #include <sys/cdefs.h>
50 __RCSID("$FreeBSD: src/bin/mv/mv.c,v 1.39 2002/07/09 17:45:13 johan Exp $");
51
52 #include <sys/param.h>
53 #include <sys/time.h>
54 #include <sys/wait.h>
55 #include <sys/stat.h>
56 #include <sys/mount.h>
57
58 #include <err.h>
59 #include <errno.h>
60 #include <fcntl.h>
61 #include <grp.h>
62 #include <limits.h>
63 #include <paths.h>
64 #include <pwd.h>
65 #include <stdio.h>
66 #include <stdlib.h>
67 #include <string.h>
68 #include <sysexits.h>
69 #include <unistd.h>
70
71 #ifdef __APPLE__
72 #include <copyfile.h>
73 #include <sys/mount.h>
74 #endif
75
76 #ifdef __APPLE__
77 #include <get_compat.h>
78 #else
79 #define COMPAT_MODE(a,b) (1)
80 #endif /* __APPLE__ */
81
82 #include "pathnames.h"
83
84 int fflg, iflg, nflg, vflg;
85
86 int copy(char *, char *);
87 int do_move(char *, char *);
88 int fastcopy(char *, char *, struct stat *);
89 void usage(void);
90
91 int
92 main(int argc, char *argv[])
93 {
94 size_t baselen, len;
95 int rval;
96 char *p, *endp;
97 struct stat sb;
98 #ifdef __APPLE__
99 struct stat fsb, tsb;
100 #endif /* __APPLE__ */
101 int ch;
102 char path[PATH_MAX];
103
104 while ((ch = getopt(argc, argv, "finv")) != -1)
105 switch (ch) {
106 case 'i':
107 iflg = 1;
108 fflg = nflg = 0;
109 break;
110 case 'f':
111 fflg = 1;
112 iflg = nflg = 0;
113 break;
114 case 'n':
115 nflg = 1;
116 fflg = iflg = 0;
117 break;
118 case 'v':
119 vflg = 1;
120 break;
121 default:
122 usage();
123 }
124 argc -= optind;
125 argv += optind;
126
127 if (argc < 2)
128 usage();
129
130 /*
131 * If the stat on the target fails or the target isn't a directory,
132 * try the move. More than 2 arguments is an error in this case.
133 */
134 if (stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) {
135 if (argc > 2)
136 usage();
137 exit(do_move(argv[0], argv[1]));
138 }
139
140 #ifdef __APPLE__
141 if (argc == 2 && !lstat(argv[0], &fsb) && !lstat(argv[1], &tsb) &&
142 fsb.st_ino == tsb.st_ino && fsb.st_dev == tsb.st_dev &&
143 fsb.st_gen == tsb.st_gen) {
144 /*
145 * We appear to be trying to move a directory into itself,
146 * but it may be that the filesystem is case insensitive and
147 * we are trying to rename the directory to a case-variant.
148 * Ignoring trailing slashes, we look for any difference in
149 * the directory names. If there is a difference we do
150 * the rename, otherwise we fall-thru to the traditional
151 * error. Note the lstat calls above (rather than stat)
152 * permit the renaming of symlinks to case-variants.
153 */
154 char *q;
155
156 for (p = argv[0] + strlen(argv[0]); p != argv[0]; ) {
157 p--;
158 if (*p != '/')
159 break;
160 }
161 for (q = argv[1] + strlen(argv[1]); q != argv[1]; ) {
162 q--;
163 if (*q != '/')
164 break;
165 }
166 for ( ; ; p--, q--) {
167 if (*p != *q)
168 exit(do_move(argv[0], argv[1]));
169 if (*p == '/')
170 break;
171 if (p == argv[0]) {
172 if (q == argv[1] || *(q-1) == '/')
173 break;
174 exit(do_move(argv[0], argv[1]));
175 }
176 if (q == argv[1]) {
177 if (p == argv[0] || *(p-1) == '/')
178 break;
179 exit(do_move(argv[0], argv[1]));
180 }
181 }
182 }
183 #endif /* __APPLE__ */
184
185 /* It's a directory, move each file into it. */
186 if (strlen(argv[argc - 1]) > sizeof(path) - 1)
187 errx(1, "%s: destination pathname too long", *argv);
188 (void)strcpy(path, argv[argc - 1]);
189 baselen = strlen(path);
190 endp = &path[baselen];
191 if (!baselen || *(endp - 1) != '/') {
192 *endp++ = '/';
193 ++baselen;
194 }
195 for (rval = 0; --argc; ++argv) {
196 /*
197 * Find the last component of the source pathname. It
198 * may have trailing slashes.
199 */
200 p = *argv + strlen(*argv);
201 while (p != *argv && p[-1] == '/')
202 --p;
203 while (p != *argv && p[-1] != '/')
204 --p;
205
206 if ((baselen + (len = strlen(p))) >= PATH_MAX) {
207 warnx("%s: destination pathname too long", *argv);
208 rval = 1;
209 } else {
210 memmove(endp, p, (size_t)len + 1);
211 if (COMPAT_MODE("bin/mv", "unix2003")) {
212 /*
213 * For Unix 2003 compatibility, check if old and new are
214 * same file, and produce an error * (like on Sun) that
215 * conformance test 66 in mv.ex expects.
216 */
217 if (!stat(*argv, &fsb) && !stat(path, &tsb) &&
218 fsb.st_ino == tsb.st_ino &&
219 fsb.st_dev == tsb.st_dev &&
220 fsb.st_gen == tsb.st_gen) {
221 (void)fprintf(stderr, "mv: %s and %s are identical\n",
222 *argv, path);
223 rval = 2; /* Like the Sun */
224 } else {
225 if (do_move(*argv, path))
226 rval = 1;
227 }
228 } else {
229 if (do_move(*argv, path))
230 rval = 1;
231 }
232 }
233 }
234 exit(rval);
235 }
236
237 int
238 do_move(char *from, char *to)
239 {
240 struct stat sb;
241 int ask, ch, first;
242 char modep[15];
243
244 /*
245 * Check access. If interactive and file exists, ask user if it
246 * should be replaced. Otherwise if file exists but isn't writable
247 * make sure the user wants to clobber it.
248 */
249 if (!fflg && !access(to, F_OK)) {
250
251 /* prompt only if source exist */
252 if (lstat(from, &sb) == -1) {
253 warn("%s", from);
254 return (1);
255 }
256
257 #define YESNO "(y/n [n]) "
258 ask = 0;
259 if (nflg) {
260 if (vflg)
261 printf("%s not overwritten\n", to);
262 return (0);
263 } else if (iflg) {
264 (void)fprintf(stderr, "overwrite %s? %s", to, YESNO);
265 ask = 1;
266 } else if (access(to, W_OK) && !stat(to, &sb)) {
267 strmode(sb.st_mode, modep);
268 (void)fprintf(stderr, "override %s%s%s/%s for %s? %s",
269 modep + 1, modep[9] == ' ' ? "" : " ",
270 user_from_uid(sb.st_uid, 0),
271 group_from_gid(sb.st_gid, 0), to, YESNO);
272 ask = 1;
273 }
274 if (ask) {
275 first = ch = getchar();
276 while (ch != '\n' && ch != EOF)
277 ch = getchar();
278 if (first != 'y' && first != 'Y') {
279 (void)fprintf(stderr, "not overwritten\n");
280 return (0);
281 }
282 }
283 }
284 if (!rename(from, to)) {
285 if (vflg)
286 printf("%s -> %s\n", from, to);
287 return (0);
288 }
289
290 if (errno == EXDEV) {
291 struct statfs sfs;
292 char path[PATH_MAX];
293
294 /* Can't mv(1) a mount point. */
295 if (realpath(from, path) == NULL) {
296 warnx("cannot resolve %s: %s", from, path);
297 return (1);
298 }
299 if (!statfs(path, &sfs) && !strcmp(path, sfs.f_mntonname)) {
300 warnx("cannot rename a mount point");
301 return (1);
302 }
303 } else {
304 warn("rename %s to %s", from, to);
305 return (1);
306 }
307
308 /*
309 * If rename fails because we're trying to cross devices, and
310 * it's a regular file, do the copy internally; otherwise, use
311 * cp and rm.
312 */
313 if (lstat(from, &sb)) {
314 warn("%s", from);
315 return (1);
316 }
317 return (S_ISREG(sb.st_mode) ?
318 fastcopy(from, to, &sb) : copy(from, to));
319 }
320
321 int
322 fastcopy(char *from, char *to, struct stat *sbp)
323 {
324 struct timeval tval[2];
325 static u_int blen;
326 static char *bp;
327 mode_t oldmode;
328 ssize_t nread;
329 int from_fd, to_fd;
330
331 if ((from_fd = open(from, O_RDONLY, 0)) < 0) {
332 warn("%s", from);
333 return (1);
334 }
335 if (blen < sbp->st_blksize) {
336 if (bp != NULL)
337 free(bp);
338 if ((bp = malloc((size_t)sbp->st_blksize)) == NULL) {
339 blen = 0;
340 warnx("malloc failed");
341 return (1);
342 }
343 blen = sbp->st_blksize;
344 }
345 while ((to_fd =
346 open(to, O_CREAT | O_EXCL | O_TRUNC | O_WRONLY, 0)) < 0) {
347 if (errno == EEXIST && unlink(to) == 0)
348 continue;
349 warn("%s", to);
350 (void)close(from_fd);
351 return (1);
352 }
353 #ifdef __APPLE__
354 {
355 struct statfs sfs;
356
357 /*
358 * Pre-allocate blocks for the destination file if it
359 * resides on Xsan.
360 */
361 if (fstatfs(to_fd, &sfs) == 0 &&
362 strcmp(sfs.f_fstypename, "acfs") == 0) {
363 fstore_t fst;
364
365 fst.fst_flags = 0;
366 fst.fst_posmode = F_PEOFPOSMODE;
367 fst.fst_offset = 0;
368 fst.fst_length = sbp->st_size;
369
370 (void) fcntl(to_fd, F_PREALLOCATE, &fst);
371 }
372 }
373 #endif /* __APPLE__ */
374 while ((nread = read(from_fd, bp, (size_t)blen)) > 0)
375 if (write(to_fd, bp, (size_t)nread) != nread) {
376 warn("%s", to);
377 goto err;
378 }
379 if (nread < 0) {
380 warn("%s", from);
381 err: if (unlink(to))
382 warn("%s: remove", to);
383 (void)close(from_fd);
384 (void)close(to_fd);
385 return (1);
386 }
387 #ifdef __APPLE__
388 /* XATTR can fail if to_fd has mode 000 */
389 if (fcopyfile(from_fd, to_fd, NULL, COPYFILE_ACL | COPYFILE_XATTR) < 0) {
390 warn("%s: unable to move extended attributes and ACL from %s",
391 to, from);
392 }
393 #endif
394 (void)close(from_fd);
395
396 oldmode = sbp->st_mode & ALLPERMS;
397 if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) {
398 warn("%s: set owner/group (was: %lu/%lu)", to,
399 (u_long)sbp->st_uid, (u_long)sbp->st_gid);
400 if (oldmode & (S_ISUID | S_ISGID)) {
401 warnx(
402 "%s: owner/group changed; clearing suid/sgid (mode was 0%03o)",
403 to, oldmode);
404 sbp->st_mode &= ~(S_ISUID | S_ISGID);
405 }
406 }
407 if (fchmod(to_fd, sbp->st_mode))
408 warn("%s: set mode (was: 0%03o)", to, oldmode);
409 /*
410 * XXX
411 * NFS doesn't support chflags; ignore errors unless there's reason
412 * to believe we're losing bits. (Note, this still won't be right
413 * if the server supports flags and we were trying to *remove* flags
414 * on a file that we copied, i.e., that we didn't create.)
415 */
416 errno = 0;
417 if (fchflags(to_fd, (u_int)sbp->st_flags))
418 if (errno != ENOTSUP || sbp->st_flags != 0)
419 warn("%s: set flags (was: 0%07o)", to, sbp->st_flags);
420
421 tval[0].tv_sec = sbp->st_atime;
422 tval[1].tv_sec = sbp->st_mtime;
423 tval[0].tv_usec = tval[1].tv_usec = 0;
424 if (utimes(to, tval))
425 warn("%s: set times", to);
426
427 if (close(to_fd)) {
428 warn("%s", to);
429 return (1);
430 }
431
432 if (unlink(from)) {
433 warn("%s: remove", from);
434 return (1);
435 }
436 if (vflg)
437 printf("%s -> %s\n", from, to);
438 return (0);
439 }
440
441 int
442 copy(char *from, char *to)
443 {
444 int pid, status;
445
446 /* posix_spawn cp from to && rm from */
447
448 if ((pid = fork()) == 0) {
449 execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", "--", from, to,
450 (char *)NULL);
451 warn("%s", _PATH_CP);
452 _exit(1);
453 }
454 if (waitpid(pid, &status, 0) == -1) {
455 warn("%s: waitpid", _PATH_CP);
456 return (1);
457 }
458 if (!WIFEXITED(status)) {
459 warnx("%s: did not terminate normally", _PATH_CP);
460 return (1);
461 }
462 if (WEXITSTATUS(status)) {
463 warnx("%s: terminated with %d (non-zero) status",
464 _PATH_CP, WEXITSTATUS(status));
465 return (1);
466 }
467 if (!(pid = vfork())) {
468 execl(_PATH_RM, "mv", "-rf", "--", from, (char *)NULL);
469 warn("%s", _PATH_RM);
470 _exit(1);
471 }
472 if (waitpid(pid, &status, 0) == -1) {
473 warn("%s: waitpid", _PATH_RM);
474 return (1);
475 }
476 if (!WIFEXITED(status)) {
477 warnx("%s: did not terminate normally", _PATH_RM);
478 return (1);
479 }
480 if (WEXITSTATUS(status)) {
481 warnx("%s: terminated with %d (non-zero) status",
482 _PATH_RM, WEXITSTATUS(status));
483 return (1);
484 }
485 return (0);
486 }
487
488 void
489 usage(void)
490 {
491
492 (void)fprintf(stderr, "%s\n%s\n",
493 "usage: mv [-f | -i | -n] [-v] source target",
494 " mv [-f | -i | -n] [-v] source ... directory");
495 exit(EX_USAGE);
496 }