file_cmds-116.10.tar.gz
[apple/file_cmds.git] / cp / utils.c
1 /*-
2 * Copyright (c) 1991, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34 #ifndef lint
35 #if 0
36 static char sccsid[] = "@(#)utils.c 8.3 (Berkeley) 4/1/94";
37 #endif
38 #endif /* not lint */
39 #include <sys/cdefs.h>
40 __RCSID("$FreeBSD: src/bin/cp/utils.c,v 1.38 2002/07/31 16:52:16 markm Exp $");
41
42 #include <sys/param.h>
43 #include <sys/time.h>
44 #include <sys/stat.h>
45 #ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
46 #include <sys/mman.h>
47 #endif
48
49 #include <err.h>
50 #include <errno.h>
51 #include <fcntl.h>
52 #include <fts.h>
53 #include <limits.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <sysexits.h>
57 #include <unistd.h>
58
59 #ifdef __APPLE__
60 #include <copyfile.h>
61 #include <string.h>
62 #include <sys/mount.h>
63 #endif
64
65 #include "extern.h"
66
67 int
68 copy_file(FTSENT *entp, int dne)
69 {
70 static char buf[MAXBSIZE];
71 struct stat *fs;
72 int ch, checkch, from_fd, rcount, rval, to_fd;
73 ssize_t wcount;
74 size_t wresid;
75 char *bufp;
76 #ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
77 char *p;
78 #endif
79
80 if ((from_fd = open(entp->fts_path, O_RDONLY, 0)) == -1) {
81 warn("%s", entp->fts_path);
82 return (1);
83 }
84
85 fs = entp->fts_statp;
86
87 /*
88 * If the file exists and we're interactive, verify with the user.
89 * If the file DNE, set the mode to be the from file, minus setuid
90 * bits, modified by the umask; arguably wrong, but it makes copying
91 * executables work right and it's been that way forever. (The
92 * other choice is 666 or'ed with the execute bits on the from file
93 * modified by the umask.)
94 */
95 if (!dne) {
96 #define YESNO "(y/n [n]) "
97 if (nflag) {
98 if (vflag)
99 printf("%s not overwritten\n", to.p_path);
100 close(from_fd);
101 return (0);
102 } else if (iflag) {
103 (void)fprintf(stderr, "overwrite %s? %s",
104 to.p_path, YESNO);
105 checkch = ch = getchar();
106 while (ch != '\n' && ch != EOF)
107 ch = getchar();
108 if (checkch != 'y' && checkch != 'Y') {
109 (void)close(from_fd);
110 (void)fprintf(stderr, "not overwritten\n");
111 return (1);
112 }
113 }
114
115 if (fflag) {
116 /* remove existing destination file name,
117 * create a new file */
118 (void)unlink(to.p_path);
119 to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
120 fs->st_mode & ~(S_ISUID | S_ISGID));
121 } else
122 /* overwrite existing destination file name */
123 to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0);
124 } else
125 to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
126 fs->st_mode & ~(S_ISUID | S_ISGID));
127
128 if (to_fd == -1) {
129 warn("%s", to.p_path);
130 (void)close(from_fd);
131 return (1);
132 }
133
134 rval = 0;
135
136 #ifdef __APPLE__
137 if (S_ISREG(fs->st_mode)) {
138 struct statfs sfs;
139
140 /*
141 * Pre-allocate blocks for the destination file if it
142 * resides on Xsan.
143 */
144 if (fstatfs(to_fd, &sfs) == 0 &&
145 strcmp(sfs.f_fstypename, "acfs") == 0) {
146 fstore_t fst;
147
148 fst.fst_flags = 0;
149 fst.fst_posmode = F_PEOFPOSMODE;
150 fst.fst_offset = 0;
151 fst.fst_length = fs->st_size;
152
153 (void) fcntl(to_fd, F_PREALLOCATE, &fst);
154 }
155 }
156 #endif /* __APPLE__ */
157
158 /*
159 * Mmap and write if less than 8M (the limit is so we don't totally
160 * trash memory on big files. This is really a minor hack, but it
161 * wins some CPU back.
162 */
163 #ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
164 if (S_ISREG(fs->st_mode) && fs->st_size <= 8 * 1048576) {
165 if ((p = mmap(NULL, (size_t)fs->st_size, PROT_READ,
166 MAP_SHARED, from_fd, (off_t)0)) == MAP_FAILED) {
167 warn("%s", entp->fts_path);
168 rval = 1;
169 } else {
170 for (bufp = p, wresid = fs->st_size; ;
171 bufp += wcount, wresid -= (size_t)wcount) {
172 wcount = write(to_fd, bufp, wresid);
173 if (wcount >= (ssize_t)wresid || wcount <= 0)
174 break;
175 }
176 if (wcount != (ssize_t)wresid) {
177 warn("%s", to.p_path);
178 rval = 1;
179 }
180 /* Some systems don't unmap on close(2). */
181 if (munmap(p, fs->st_size) < 0) {
182 warn("%s", entp->fts_path);
183 rval = 1;
184 }
185 }
186 } else
187 #endif
188 {
189 while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
190 for (bufp = buf, wresid = rcount; ;
191 bufp += wcount, wresid -= wcount) {
192 wcount = write(to_fd, bufp, wresid);
193 if (wcount >= (ssize_t)wresid || wcount <= 0)
194 break;
195 }
196 if (wcount != (ssize_t)wresid) {
197 warn("%s", to.p_path);
198 rval = 1;
199 break;
200 }
201 }
202 if (rcount < 0) {
203 warn("%s", entp->fts_path);
204 rval = 1;
205 }
206 }
207
208 #ifdef __APPLE__
209 if (pflag)
210 copyfile(entp->fts_path, to.p_path, 0, COPYFILE_XATTR | COPYFILE_ACL);
211 else
212 copyfile(entp->fts_path, to.p_path, 0, COPYFILE_XATTR);
213 #endif
214 /*
215 * Don't remove the target even after an error. The target might
216 * not be a regular file, or its attributes might be important,
217 * or its contents might be irreplaceable. It would only be safe
218 * to remove it if we created it and its length is 0.
219 */
220
221 if (pflag && setfile(fs, to_fd))
222 rval = 1;
223 (void)close(from_fd);
224 if (close(to_fd)) {
225 warn("%s", to.p_path);
226 rval = 1;
227 }
228 return (rval);
229 }
230
231 int
232 copy_link(FTSENT *p, int exists)
233 {
234 int len;
235 char llink[PATH_MAX];
236
237 if ((len = readlink(p->fts_path, llink, sizeof(llink) - 1)) == -1) {
238 warn("readlink: %s", p->fts_path);
239 return (1);
240 }
241 llink[len] = '\0';
242 if (exists && unlink(to.p_path)) {
243 warn("unlink: %s", to.p_path);
244 return (1);
245 }
246 if (symlink(llink, to.p_path)) {
247 warn("symlink: %s", llink);
248 return (1);
249 }
250 #ifdef __APPLE__
251 copyfile(p->fts_path, to.p_path, 0, COPYFILE_XATTR | COPYFILE_NOFOLLOW_SRC);
252 #endif
253 return (0);
254 }
255
256 int
257 copy_fifo(struct stat *from_stat, int exists)
258 {
259 if (exists && unlink(to.p_path)) {
260 warn("unlink: %s", to.p_path);
261 return (1);
262 }
263 if (mkfifo(to.p_path, from_stat->st_mode)) {
264 warn("mkfifo: %s", to.p_path);
265 return (1);
266 }
267 return (pflag ? setfile(from_stat, 0) : 0);
268 }
269
270 int
271 copy_special(struct stat *from_stat, int exists)
272 {
273 if (exists && unlink(to.p_path)) {
274 warn("unlink: %s", to.p_path);
275 return (1);
276 }
277 if (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) {
278 warn("mknod: %s", to.p_path);
279 return (1);
280 }
281 return (pflag ? setfile(from_stat, 0) : 0);
282 }
283
284 int
285 setfile(struct stat *fs, int fd)
286 {
287 static struct timeval tv[2];
288 struct stat ts;
289 int rval;
290 int gotstat;
291
292 rval = 0;
293 fs->st_mode &= S_ISUID | S_ISGID | S_ISVTX |
294 S_IRWXU | S_IRWXG | S_IRWXO;
295
296 TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
297 TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
298 if (utimes(to.p_path, tv)) {
299 warn("utimes: %s", to.p_path);
300 rval = 1;
301 }
302 if (fd ? fstat(fd, &ts) : stat(to.p_path, &ts))
303 gotstat = 0;
304 else {
305 gotstat = 1;
306 ts.st_mode &= S_ISUID | S_ISGID | S_ISVTX |
307 S_IRWXU | S_IRWXG | S_IRWXO;
308 }
309 /*
310 * Changing the ownership probably won't succeed, unless we're root
311 * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting
312 * the mode; current BSD behavior is to remove all setuid bits on
313 * chown. If chown fails, lose setuid/setgid bits.
314 */
315 if (!gotstat || fs->st_uid != ts.st_uid || fs->st_gid != ts.st_gid)
316 if (fd ? fchown(fd, fs->st_uid, fs->st_gid) :
317 chown(to.p_path, fs->st_uid, fs->st_gid)) {
318 if (errno != EPERM) {
319 warn("chown: %s", to.p_path);
320 rval = 1;
321 }
322 fs->st_mode &= ~(S_ISUID | S_ISGID);
323 }
324
325 if (!gotstat || fs->st_mode != ts.st_mode)
326 if (fd ? fchmod(fd, fs->st_mode) : chmod(to.p_path, fs->st_mode)) {
327 warn("chmod: %s", to.p_path);
328 rval = 1;
329 }
330
331 if (!gotstat || fs->st_flags != ts.st_flags)
332 if (fd ?
333 fchflags(fd, fs->st_flags) : chflags(to.p_path, fs->st_flags)) {
334 warn("chflags: %s", to.p_path);
335 rval = 1;
336 }
337
338 return (rval);
339 }
340
341 void
342 usage(void)
343 {
344
345 (void)fprintf(stderr, "%s\n%s\n",
346 "usage: cp [-R [-H | -L | -P]] [-f | -i | -n] [-pv] src target",
347 " cp [-R [-H | -L | -P]] [-f | -i | -n] [-pv] src1 ... srcN directory");
348 exit(EX_USAGE);
349 }