Initial import from FreeBSD RELENG_4:
[dragonfly.git] / bin / 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 static const char rcsid[] =
39   "$FreeBSD: src/bin/cp/utils.c,v 1.27.2.6 2002/08/10 13:20:19 johan Exp $";
40 #endif /* not lint */
41
42 #include <sys/param.h>
43 #include <sys/stat.h>
44 #include <sys/time.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 #include "extern.h"
60
61 int
62 copy_file(entp, dne)
63         FTSENT *entp;
64         int dne;
65 {
66         static char buf[MAXBSIZE];
67         struct stat *fs;
68         int ch, checkch, from_fd, rcount, rval, to_fd, wcount, wresid;
69         char *bufp;
70 #ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
71         char *p;
72 #endif
73
74         if ((from_fd = open(entp->fts_path, O_RDONLY, 0)) == -1) {
75                 warn("%s", entp->fts_path);
76                 return (1);
77         }
78
79         fs = entp->fts_statp;
80
81         /*
82          * If the file exists and we're interactive, verify with the user.
83          * If the file DNE, set the mode to be the from file, minus setuid
84          * bits, modified by the umask; arguably wrong, but it makes copying
85          * executables work right and it's been that way forever.  (The
86          * other choice is 666 or'ed with the execute bits on the from file
87          * modified by the umask.)
88          */
89         if (!dne) {
90 #define YESNO "(y/n [n]) "
91                 if (nflag) {
92                         if (vflag)
93                                 printf("%s not overwritten\n", to.p_path);
94                         return (0);
95                 } else if (iflag) {
96                         (void)fprintf(stderr, "overwrite %s? %s", 
97                                         to.p_path, YESNO);
98                         checkch = ch = getchar();
99                         while (ch != '\n' && ch != EOF)
100                                 ch = getchar();
101                         if (checkch != 'y' && checkch != 'Y') {
102                                 (void)close(from_fd);
103                                 (void)fprintf(stderr, "not overwritten\n");
104                                 return (1);
105                         }
106                 }
107                 
108                 if (fflag) {
109                     /* remove existing destination file name, 
110                      * create a new file  */
111                     (void)unlink(to.p_path);
112                     to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
113                                  fs->st_mode & ~(S_ISUID | S_ISGID));
114                 } else 
115                     /* overwrite existing destination file name */
116                     to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0);
117         } else
118                 to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
119                     fs->st_mode & ~(S_ISUID | S_ISGID));
120
121         if (to_fd == -1) {
122                 warn("%s", to.p_path);
123                 (void)close(from_fd);
124                 return (1);;
125         }
126
127         rval = 0;
128
129         /*
130          * Mmap and write if less than 8M (the limit is so we don't totally
131          * trash memory on big files.  This is really a minor hack, but it
132          * wins some CPU back.
133          */
134 #ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
135         if (S_ISREG(fs->st_mode) && fs->st_size <= 8 * 1048576) {
136                 if ((p = mmap(NULL, (size_t)fs->st_size, PROT_READ,
137                     MAP_SHARED, from_fd, (off_t)0)) == MAP_FAILED) {
138                         warn("%s", entp->fts_path);
139                         rval = 1;
140                 } else {
141                         for (bufp = p, wresid = fs->st_size; ;
142                             bufp += wcount, wresid -= wcount) {
143                                 wcount = write(to_fd, bufp, wresid);
144                                 if (wcount >= wresid || wcount <= 0)
145                                         break;
146                         }
147                         if (wcount != wresid) {
148                                 warn("%s", to.p_path);
149                                 rval = 1;
150                         }
151                         /* Some systems don't unmap on close(2). */
152                         if (munmap(p, fs->st_size) < 0) {
153                                 warn("%s", entp->fts_path);
154                                 rval = 1;
155                         }
156                 }
157         } else
158 #endif
159         {
160                 while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
161                         for (bufp = buf, wresid = rcount; ;
162                             bufp += wcount, wresid -= wcount) {
163                                 wcount = write(to_fd, bufp, wresid);
164                                 if (wcount >= wresid || wcount <= 0)
165                                         break;
166                         }
167                         if (wcount != wresid) {
168                                 warn("%s", to.p_path);
169                                 rval = 1;
170                                 break;
171                         }
172                 }
173                 if (rcount < 0) {
174                         warn("%s", entp->fts_path);
175                         rval = 1;
176                 }
177         }
178
179         /*
180          * Don't remove the target even after an error.  The target might
181          * not be a regular file, or its attributes might be important,
182          * or its contents might be irreplaceable.  It would only be safe
183          * to remove it if we created it and its length is 0.
184          */
185
186         if (pflag && setfile(fs, to_fd))
187                 rval = 1;
188         (void)close(from_fd);
189         if (close(to_fd)) {
190                 warn("%s", to.p_path);
191                 rval = 1;
192         }
193         return (rval);
194 }
195
196 int
197 copy_link(p, exists)
198         FTSENT *p;
199         int exists;
200 {
201         int len;
202         char link[PATH_MAX];
203
204         if ((len = readlink(p->fts_path, link, sizeof(link) - 1)) == -1) {
205                 warn("readlink: %s", p->fts_path);
206                 return (1);
207         }
208         link[len] = '\0';
209         if (exists && unlink(to.p_path)) {
210                 warn("unlink: %s", to.p_path);
211                 return (1);
212         }
213         if (symlink(link, to.p_path)) {
214                 warn("symlink: %s", link);
215                 return (1);
216         }
217         return (0);
218 }
219
220 int
221 copy_fifo(from_stat, exists)
222         struct stat *from_stat;
223         int exists;
224 {
225         if (exists && unlink(to.p_path)) {
226                 warn("unlink: %s", to.p_path);
227                 return (1);
228         }
229         if (mkfifo(to.p_path, from_stat->st_mode)) {
230                 warn("mkfifo: %s", to.p_path);
231                 return (1);
232         }
233         return (pflag ? setfile(from_stat, 0) : 0);
234 }
235
236 int
237 copy_special(from_stat, exists)
238         struct stat *from_stat;
239         int exists;
240 {
241         if (exists && unlink(to.p_path)) {
242                 warn("unlink: %s", to.p_path);
243                 return (1);
244         }
245         if (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) {
246                 warn("mknod: %s", to.p_path);
247                 return (1);
248         }
249         return (pflag ? setfile(from_stat, 0) : 0);
250 }
251
252 #define RETAINBITS \
253         (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
254
255 int
256 setfile(fs, fd)
257         register struct stat *fs;
258         int fd;
259 {
260         static struct timeval tv[2];
261         struct stat ts;
262         int rval;
263         int gotstat;
264
265         rval = 0;
266         fs->st_mode &= S_ISUID | S_ISGID | S_ISVTX |
267                        S_IRWXU | S_IRWXG | S_IRWXO;
268
269         TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
270         TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
271         if (utimes(to.p_path, tv)) {
272                 warn("utimes: %s", to.p_path);
273                 rval = 1;
274         }
275         if (fd ? fstat(fd, &ts) : stat(to.p_path, &ts))
276                 gotstat = 0;
277         else {
278                 gotstat = 1;
279                 ts.st_mode &= S_ISUID | S_ISGID | S_ISVTX |
280                               S_IRWXU | S_IRWXG | S_IRWXO;
281         }
282         /*
283          * Changing the ownership probably won't succeed, unless we're root
284          * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
285          * the mode; current BSD behavior is to remove all setuid bits on
286          * chown.  If chown fails, lose setuid/setgid bits.
287          */
288         if (!gotstat || fs->st_uid != ts.st_uid || fs->st_gid != ts.st_gid)
289                 if (fd ? fchown(fd, fs->st_uid, fs->st_gid) :
290                     chown(to.p_path, fs->st_uid, fs->st_gid)) {
291                         if (errno != EPERM) {
292                                 warn("chown: %s", to.p_path);
293                                 rval = 1;
294                         }
295                         fs->st_mode &= ~(S_ISUID | S_ISGID);
296                 }
297
298         if (!gotstat || fs->st_mode != ts.st_mode)
299                 if (fd ? fchmod(fd, fs->st_mode) : chmod(to.p_path, fs->st_mode)) {
300                         warn("chmod: %s", to.p_path);
301                         rval = 1;
302                 }
303
304         if (!gotstat || fs->st_flags != ts.st_flags)
305                 if (fd ?
306                     fchflags(fd, fs->st_flags) : chflags(to.p_path, fs->st_flags)) {
307                         warn("chflags: %s", to.p_path);
308                         rval = 1;
309                 }
310
311         return (rval);
312 }
313
314 void
315 usage()
316 {
317
318         (void)fprintf(stderr, "%s\n%s\n",
319 "usage: cp [-R [-H | -L | -P]] [-f | -i | -n] [-pv] src target",
320 "       cp [-R [-H | -L | -P]] [-f | -i | -n] [-pv] src1 ... srcN directory");
321         exit(EX_USAGE);
322 }