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