]>
Commit | Line | Data |
---|---|---|
44a7a5ab A |
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 | ||
00337e45 | 37 | #include <sys/cdefs.h> |
44a7a5ab | 38 | #ifndef lint |
00337e45 | 39 | __used static char const copyright[] = |
6c780a1f A |
40 | "@(#) Copyright (c) 1989, 1993, 1994\n\ |
41 | The Regents of the University of California. All rights reserved.\n"; | |
44a7a5ab A |
42 | #endif /* not lint */ |
43 | ||
44 | #ifndef lint | |
45 | #if 0 | |
46 | static char sccsid[] = "@(#)mv.c 8.2 (Berkeley) 4/2/94"; | |
44a7a5ab A |
47 | #endif |
48 | #endif /* not lint */ | |
6c780a1f A |
49 | #include <sys/cdefs.h> |
50 | __RCSID("$FreeBSD: src/bin/mv/mv.c,v 1.39 2002/07/09 17:45:13 johan Exp $"); | |
44a7a5ab A |
51 | |
52 | #include <sys/param.h> | |
53 | #include <sys/time.h> | |
54 | #include <sys/wait.h> | |
55 | #include <sys/stat.h> | |
6c780a1f | 56 | #include <sys/mount.h> |
44a7a5ab A |
57 | |
58 | #include <err.h> | |
59 | #include <errno.h> | |
60 | #include <fcntl.h> | |
6c780a1f A |
61 | #include <grp.h> |
62 | #include <limits.h> | |
63 | #include <paths.h> | |
64 | #include <pwd.h> | |
44a7a5ab A |
65 | #include <stdio.h> |
66 | #include <stdlib.h> | |
67 | #include <string.h> | |
6c780a1f | 68 | #include <sysexits.h> |
44a7a5ab | 69 | #include <unistd.h> |
44a7a5ab | 70 | |
c59d3020 A |
71 | #ifdef __APPLE__ |
72 | #include <copyfile.h> | |
52f52064 | 73 | #include <sys/mount.h> |
c59d3020 A |
74 | #endif |
75 | ||
864a4b6e A |
76 | #ifdef __APPLE__ |
77 | #include <get_compat.h> | |
78 | #else | |
79 | #define COMPAT_MODE(a,b) (1) | |
80 | #endif /* __APPLE__ */ | |
81 | ||
44a7a5ab A |
82 | #include "pathnames.h" |
83 | ||
6c780a1f | 84 | int fflg, iflg, nflg, vflg; |
44a7a5ab | 85 | |
6c780a1f A |
86 | int copy(char *, char *); |
87 | int do_move(char *, char *); | |
88 | int fastcopy(char *, char *, struct stat *); | |
89 | void usage(void); | |
44a7a5ab A |
90 | |
91 | int | |
6c780a1f | 92 | main(int argc, char *argv[]) |
44a7a5ab | 93 | { |
6c780a1f A |
94 | size_t baselen, len; |
95 | int rval; | |
44a7a5ab A |
96 | char *p, *endp; |
97 | struct stat sb; | |
6c780a1f A |
98 | #ifdef __APPLE__ |
99 | struct stat fsb, tsb; | |
100 | #endif /* __APPLE__ */ | |
44a7a5ab | 101 | int ch; |
6c780a1f | 102 | char path[PATH_MAX]; |
44a7a5ab | 103 | |
6c780a1f | 104 | while ((ch = getopt(argc, argv, "finv")) != -1) |
44a7a5ab A |
105 | switch (ch) { |
106 | case 'i': | |
44a7a5ab | 107 | iflg = 1; |
6c780a1f | 108 | fflg = nflg = 0; |
44a7a5ab A |
109 | break; |
110 | case 'f': | |
44a7a5ab | 111 | fflg = 1; |
6c780a1f A |
112 | iflg = nflg = 0; |
113 | break; | |
114 | case 'n': | |
115 | nflg = 1; | |
116 | fflg = iflg = 0; | |
117 | break; | |
118 | case 'v': | |
119 | vflg = 1; | |
44a7a5ab | 120 | break; |
44a7a5ab A |
121 | default: |
122 | usage(); | |
123 | } | |
124 | argc -= optind; | |
125 | argv += optind; | |
126 | ||
127 | if (argc < 2) | |
128 | usage(); | |
129 | ||
44a7a5ab A |
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 | } | |
6c780a1f A |
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__ */ | |
44a7a5ab A |
184 | |
185 | /* It's a directory, move each file into it. */ | |
6c780a1f A |
186 | if (strlen(argv[argc - 1]) > sizeof(path) - 1) |
187 | errx(1, "%s: destination pathname too long", *argv); | |
44a7a5ab A |
188 | (void)strcpy(path, argv[argc - 1]); |
189 | baselen = strlen(path); | |
190 | endp = &path[baselen]; | |
6c780a1f A |
191 | if (!baselen || *(endp - 1) != '/') { |
192 | *endp++ = '/'; | |
193 | ++baselen; | |
194 | } | |
44a7a5ab | 195 | for (rval = 0; --argc; ++argv) { |
6c780a1f A |
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) { | |
44a7a5ab A |
207 | warnx("%s: destination pathname too long", *argv); |
208 | rval = 1; | |
209 | } else { | |
6c780a1f | 210 | memmove(endp, p, (size_t)len + 1); |
864a4b6e A |
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 | } | |
44a7a5ab A |
232 | } |
233 | } | |
234 | exit(rval); | |
44a7a5ab A |
235 | } |
236 | ||
237 | int | |
6c780a1f | 238 | do_move(char *from, char *to) |
44a7a5ab A |
239 | { |
240 | struct stat sb; | |
6c780a1f | 241 | int ask, ch, first; |
44a7a5ab A |
242 | char modep[15]; |
243 | ||
244 | /* | |
6c780a1f A |
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. | |
44a7a5ab A |
248 | */ |
249 | if (!fflg && !access(to, F_OK)) { | |
44a7a5ab | 250 | |
6c780a1f A |
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)) { | |
44a7a5ab | 267 | strmode(sb.st_mode, modep); |
6c780a1f | 268 | (void)fprintf(stderr, "override %s%s%s/%s for %s? %s", |
44a7a5ab | 269 | modep + 1, modep[9] == ' ' ? "" : " ", |
4d0bb651 A |
270 | user_from_uid(sb.st_uid, 0), |
271 | group_from_gid(sb.st_gid, 0), to, YESNO); | |
6c780a1f A |
272 | ask = 1; |
273 | } | |
44a7a5ab | 274 | if (ask) { |
6c780a1f A |
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"); | |
44a7a5ab | 280 | return (0); |
6c780a1f | 281 | } |
44a7a5ab A |
282 | } |
283 | } | |
6c780a1f A |
284 | if (!rename(from, to)) { |
285 | if (vflg) | |
286 | printf("%s -> %s\n", from, to); | |
44a7a5ab | 287 | return (0); |
44a7a5ab A |
288 | } |
289 | ||
6c780a1f A |
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); | |
44a7a5ab A |
297 | return (1); |
298 | } | |
6c780a1f A |
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); | |
44a7a5ab A |
306 | } |
307 | ||
308 | /* | |
6c780a1f A |
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. | |
44a7a5ab A |
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 | |
6c780a1f | 322 | fastcopy(char *from, char *to, struct stat *sbp) |
44a7a5ab A |
323 | { |
324 | struct timeval tval[2]; | |
325 | static u_int blen; | |
326 | static char *bp; | |
6c780a1f | 327 | mode_t oldmode; |
4d0bb651 A |
328 | ssize_t nread; |
329 | int from_fd, to_fd; | |
44a7a5ab A |
330 | |
331 | if ((from_fd = open(from, O_RDONLY, 0)) < 0) { | |
332 | warn("%s", from); | |
333 | return (1); | |
334 | } | |
6c780a1f A |
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; | |
44a7a5ab A |
349 | warn("%s", to); |
350 | (void)close(from_fd); | |
351 | return (1); | |
352 | } | |
52f52064 | 353 | #ifdef __APPLE__ |
864a4b6e A |
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 | } | |
52f52064 | 373 | #endif /* __APPLE__ */ |
6c780a1f A |
374 | while ((nread = read(from_fd, bp, (size_t)blen)) > 0) |
375 | if (write(to_fd, bp, (size_t)nread) != nread) { | |
44a7a5ab A |
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 | } | |
c59d3020 | 387 | #ifdef __APPLE__ |
864a4b6e A |
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 | } | |
c59d3020 | 393 | #endif |
44a7a5ab A |
394 | (void)close(from_fd); |
395 | ||
6c780a1f | 396 | oldmode = sbp->st_mode & ALLPERMS; |
44a7a5ab | 397 | if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) { |
6c780a1f A |
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 | } | |
44a7a5ab A |
406 | } |
407 | if (fchmod(to_fd, sbp->st_mode)) | |
6c780a1f A |
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; | |
4d0bb651 | 417 | if (fchflags(to_fd, (u_int)sbp->st_flags)) |
864a4b6e | 418 | if (errno != ENOTSUP || sbp->st_flags != 0) |
6c780a1f A |
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); | |
44a7a5ab A |
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 | } | |
6c780a1f A |
436 | if (vflg) |
437 | printf("%s -> %s\n", from, to); | |
44a7a5ab A |
438 | return (0); |
439 | } | |
440 | ||
441 | int | |
6c780a1f | 442 | copy(char *from, char *to) |
44a7a5ab A |
443 | { |
444 | int pid, status; | |
00337e45 | 445 | |
e0055cbe | 446 | /* posix_spawn cp from to && rm from */ |
44a7a5ab | 447 | |
6c780a1f A |
448 | if ((pid = fork()) == 0) { |
449 | execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", "--", from, to, | |
450 | (char *)NULL); | |
44a7a5ab A |
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)) { | |
40bf83fe | 459 | warnx("%s: did not terminate normally", _PATH_CP); |
44a7a5ab A |
460 | return (1); |
461 | } | |
462 | if (WEXITSTATUS(status)) { | |
40bf83fe | 463 | warnx("%s: terminated with %d (non-zero) status", |
44a7a5ab A |
464 | _PATH_CP, WEXITSTATUS(status)); |
465 | return (1); | |
466 | } | |
467 | if (!(pid = vfork())) { | |
6c780a1f | 468 | execl(_PATH_RM, "mv", "-rf", "--", from, (char *)NULL); |
44a7a5ab A |
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)) { | |
40bf83fe | 477 | warnx("%s: did not terminate normally", _PATH_RM); |
44a7a5ab A |
478 | return (1); |
479 | } | |
480 | if (WEXITSTATUS(status)) { | |
40bf83fe | 481 | warnx("%s: terminated with %d (non-zero) status", |
44a7a5ab A |
482 | _PATH_RM, WEXITSTATUS(status)); |
483 | return (1); | |
484 | } | |
485 | return (0); | |
486 | } | |
487 | ||
488 | void | |
6c780a1f | 489 | usage(void) |
44a7a5ab A |
490 | { |
491 | ||
6c780a1f A |
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); | |
44a7a5ab | 496 | } |