1 /* $NetBSD: utils.c,v 1.38 2011/01/04 10:35:10 wiz Exp $ */ 2 3 /*- 4 * Copyright (c) 1991, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 #ifndef lint 34 #if 0 35 static char sccsid[] = "@(#)utils.c 8.3 (Berkeley) 4/1/94"; 36 #else 37 __RCSID("$NetBSD: utils.c,v 1.38 2011/01/04 10:35:10 wiz Exp $"); 38 #endif 39 #endif /* not lint */ 40 41 #include <sys/mman.h> 42 #include <sys/param.h> 43 #include <sys/stat.h> 44 #include <sys/time.h> 45 46 #include <err.h> 47 #include <errno.h> 48 #include <fcntl.h> 49 #include <fts.h> 50 #include <stdbool.h> 51 #include <stdio.h> 52 #include <stdlib.h> 53 #include <string.h> 54 #include <unistd.h> 55 56 #include "extern.h" 57 58 #define MMAP_MAX_SIZE (8 * 1048576) 59 #define MMAP_MAX_WRITE (64 * 1024) 60 61 int 62 set_utimes(const char *file, struct stat *fs) 63 { 64 static struct timeval tv[2]; 65 66 TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec); 67 TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec); 68 69 if (lutimes(file, tv)) { 70 warn("lutimes: %s", file); 71 return (1); 72 } 73 return (0); 74 } 75 76 int 77 copy_file(FTSENT *entp, int dne) 78 { 79 static char buf[MAXBSIZE]; 80 struct stat to_stat, *fs; 81 int ch, checkch, from_fd, rcount, rval, to_fd, tolnk, wcount; 82 char *p; 83 84 if ((from_fd = open(entp->fts_path, O_RDONLY, 0)) == -1) { 85 warn("%s", entp->fts_path); 86 return (1); 87 } 88 89 to_fd = -1; 90 fs = entp->fts_statp; 91 tolnk = ((Rflag && !(Lflag || Hflag)) || Pflag); 92 93 /* 94 * If the file exists and we're interactive, verify with the user. 95 * If the file DNE, set the mode to be the from file, minus setuid 96 * bits, modified by the umask; arguably wrong, but it makes copying 97 * executables work right and it's been that way forever. (The 98 * other choice is 666 or'ed with the execute bits on the from file 99 * modified by the umask.) 100 */ 101 if (!dne) { 102 struct stat sb; 103 int sval; 104 105 if (iflag) { 106 (void)fprintf(stderr, "overwrite %s? ", to.p_path); 107 checkch = ch = getchar(); 108 while (ch != '\n' && ch != EOF) 109 ch = getchar(); 110 if (checkch != 'y' && checkch != 'Y') { 111 (void)close(from_fd); 112 return (0); 113 } 114 } 115 116 sval = tolnk ? 117 lstat(to.p_path, &sb) : stat(to.p_path, &sb); 118 if (sval == -1) { 119 warn("stat: %s", to.p_path); 120 (void)close(from_fd); 121 return (1); 122 } 123 124 if (!(tolnk && S_ISLNK(sb.st_mode))) 125 to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0); 126 } else 127 to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT, 128 fs->st_mode & ~(S_ISUID | S_ISGID)); 129 130 if (to_fd == -1 && (fflag || tolnk)) { 131 /* 132 * attempt to remove existing destination file name and 133 * create a new file 134 */ 135 (void)unlink(to.p_path); 136 to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT, 137 fs->st_mode & ~(S_ISUID | S_ISGID)); 138 } 139 140 if (to_fd == -1) { 141 warn("%s", to.p_path); 142 (void)close(from_fd); 143 return (1); 144 } 145 146 rval = 0; 147 148 /* 149 * There's no reason to do anything other than close the file 150 * now if it's empty, so let's not bother. 151 */ 152 153 if (fs->st_size > 0) { 154 /* 155 * Mmap and write if less than 8M (the limit is so 156 * we don't totally trash memory on big files). 157 * This is really a minor hack, but it wins some CPU back. 158 */ 159 bool use_read; 160 161 use_read = true; 162 if (fs->st_size <= MMAP_MAX_SIZE) { 163 size_t fsize = (size_t)fs->st_size; 164 p = mmap(NULL, fsize, PROT_READ, MAP_FILE|MAP_SHARED, 165 from_fd, (off_t)0); 166 if (p != MAP_FAILED) { 167 size_t remainder; 168 169 use_read = false; 170 171 (void) madvise(p, (size_t)fs->st_size, 172 MADV_SEQUENTIAL); 173 174 /* 175 * Write out the data in small chunks to 176 * avoid locking the output file for a 177 * long time if the reading the data from 178 * the source is slow. 179 */ 180 remainder = fsize; 181 do { 182 ssize_t chunk; 183 184 chunk = (remainder > MMAP_MAX_WRITE) ? 185 MMAP_MAX_WRITE : remainder; 186 if (write(to_fd, &p[fsize - remainder], 187 chunk) != chunk) { 188 warn("%s", to.p_path); 189 rval = 1; 190 break; 191 } 192 remainder -= chunk; 193 } while (remainder > 0); 194 195 if (munmap(p, fsize) < 0) { 196 warn("%s", entp->fts_path); 197 rval = 1; 198 } 199 } 200 } 201 202 if (use_read) { 203 while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) { 204 wcount = write(to_fd, buf, (size_t)rcount); 205 if (rcount != wcount || wcount == -1) { 206 warn("%s", to.p_path); 207 rval = 1; 208 break; 209 } 210 } 211 if (rcount < 0) { 212 warn("%s", entp->fts_path); 213 rval = 1; 214 } 215 } 216 } 217 218 if (rval == 1) { 219 (void)close(from_fd); 220 (void)close(to_fd); 221 return (1); 222 } 223 224 if (pflag && setfile(fs, to_fd)) 225 rval = 1; 226 /* 227 * If the source was setuid or setgid, lose the bits unless the 228 * copy is owned by the same user and group. 229 */ 230 #define RETAINBITS \ 231 (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO) 232 if (!pflag && dne 233 && fs->st_mode & (S_ISUID | S_ISGID) && fs->st_uid == myuid) { 234 if (fstat(to_fd, &to_stat)) { 235 warn("%s", to.p_path); 236 rval = 1; 237 } else if (fs->st_gid == to_stat.st_gid && 238 fchmod(to_fd, fs->st_mode & RETAINBITS & ~myumask)) { 239 warn("%s", to.p_path); 240 rval = 1; 241 } 242 } 243 (void)close(from_fd); 244 if (close(to_fd)) { 245 warn("%s", to.p_path); 246 rval = 1; 247 } 248 /* set the mod/access times now after close of the fd */ 249 if (pflag && set_utimes(to.p_path, fs)) { 250 rval = 1; 251 } 252 return (rval); 253 } 254 255 int 256 copy_link(FTSENT *p, int exists) 257 { 258 int len; 259 char target[MAXPATHLEN]; 260 261 if ((len = readlink(p->fts_path, target, sizeof(target)-1)) == -1) { 262 warn("readlink: %s", p->fts_path); 263 return (1); 264 } 265 target[len] = '\0'; 266 if (exists && unlink(to.p_path)) { 267 warn("unlink: %s", to.p_path); 268 return (1); 269 } 270 if (symlink(target, to.p_path)) { 271 warn("symlink: %s", target); 272 return (1); 273 } 274 return (pflag ? setfile(p->fts_statp, 0) : 0); 275 } 276 277 int 278 copy_fifo(struct stat *from_stat, int exists) 279 { 280 if (exists && unlink(to.p_path)) { 281 warn("unlink: %s", to.p_path); 282 return (1); 283 } 284 if (mkfifo(to.p_path, from_stat->st_mode)) { 285 warn("mkfifo: %s", to.p_path); 286 return (1); 287 } 288 return (pflag ? setfile(from_stat, 0) : 0); 289 } 290 291 int 292 copy_special(struct stat *from_stat, int exists) 293 { 294 if (exists && unlink(to.p_path)) { 295 warn("unlink: %s", to.p_path); 296 return (1); 297 } 298 if (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) { 299 warn("mknod: %s", to.p_path); 300 return (1); 301 } 302 return (pflag ? setfile(from_stat, 0) : 0); 303 } 304 305 306 /* 307 * Function: setfile 308 * 309 * Purpose: 310 * Set the owner/group/permissions for the "to" file to the information 311 * in the stat structure. If fd is zero, also call set_utimes() to set 312 * the mod/access times. If fd is non-zero, the caller must do a utimes 313 * itself after close(fd). 314 */ 315 int 316 setfile(struct stat *fs, int fd) 317 { 318 int rval, islink; 319 320 rval = 0; 321 islink = S_ISLNK(fs->st_mode); 322 fs->st_mode &= S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO; 323 324 /* 325 * Changing the ownership probably won't succeed, unless we're root 326 * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting 327 * the mode; current BSD behavior is to remove all setuid bits on 328 * chown. If chown fails, lose setuid/setgid bits. 329 */ 330 if (fd ? fchown(fd, fs->st_uid, fs->st_gid) : 331 lchown(to.p_path, fs->st_uid, fs->st_gid)) { 332 if (errno != EPERM) { 333 warn("chown: %s", to.p_path); 334 rval = 1; 335 } 336 fs->st_mode &= ~(S_ISUID | S_ISGID); 337 } 338 if (fd ? fchmod(fd, fs->st_mode) : lchmod(to.p_path, fs->st_mode)) { 339 warn("chmod: %s", to.p_path); 340 rval = 1; 341 } 342 343 if (!islink && !Nflag) { 344 unsigned long fflags = fs->st_flags; 345 /* 346 * XXX 347 * NFS doesn't support chflags; ignore errors unless 348 * there's reason to believe we're losing bits. 349 * (Note, this still won't be right if the server 350 * supports flags and we were trying to *remove* flags 351 * on a file that we copied, i.e., that we didn't create.) 352 */ 353 errno = 0; 354 if ((fd ? fchflags(fd, fflags) : 355 chflags(to.p_path, fflags)) == -1) 356 if (errno != EOPNOTSUPP || fs->st_flags != 0) { 357 warn("chflags: %s", to.p_path); 358 rval = 1; 359 } 360 } 361 /* if fd is non-zero, caller must call set_utimes() after close() */ 362 if (fd == 0 && set_utimes(to.p_path, fs)) 363 rval = 1; 364 return (rval); 365 } 366 367 void 368 usage(void) 369 { 370 (void)fprintf(stderr, 371 "usage: %s [-R [-H | -L | -P]] [-f | -i] [-aNpv] src target\n" 372 " %s [-R [-H | -L | -P]] [-f | -i] [-aNpv] src1 ... srcN directory\n", 373 getprogname(), getprogname()); 374 exit(1); 375 /* NOTREACHED */ 376 } 377