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