cp.c revision 5879
1199482Srdivacky/* 2199482Srdivacky * Copyright (c) 1988, 1993, 1994 3199482Srdivacky * The Regents of the University of California. All rights reserved. 4199482Srdivacky * 5199482Srdivacky * This code is derived from software contributed to Berkeley by 6199482Srdivacky * David Hitz of Auspex Systems Inc. 7199482Srdivacky * 8199482Srdivacky * Redistribution and use in source and binary forms, with or without 9199482Srdivacky * modification, are permitted provided that the following conditions 10199482Srdivacky * are met: 11199482Srdivacky * 1. Redistributions of source code must retain the above copyright 12234353Sdim * notice, this list of conditions and the following disclaimer. 13210299Sed * 2. Redistributions in binary form must reproduce the above copyright 14199482Srdivacky * notice, this list of conditions and the following disclaimer in the 15199482Srdivacky * documentation and/or other materials provided with the distribution. 16199482Srdivacky * 3. All advertising materials mentioning features or use of this software 17199482Srdivacky * must display the following acknowledgement: 18199482Srdivacky * This product includes software developed by the University of 19199482Srdivacky * California, Berkeley and its contributors. 20199482Srdivacky * 4. Neither the name of the University nor the names of its contributors 21199482Srdivacky * may be used to endorse or promote products derived from this software 22212904Sdim * without specific prior written permission. 23207619Srdivacky * 24234353Sdim * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25212904Sdim * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26199482Srdivacky * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27226633Sdim * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28226633Sdim * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29199482Srdivacky * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30199482Srdivacky * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31199482Srdivacky * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32205408Srdivacky * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33205408Srdivacky * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34205408Srdivacky * SUCH DAMAGE. 35205408Srdivacky * 36226633Sdim * $Id: cp.c,v 1.3 1994/12/30 13:12:12 bde Exp $ 37205408Srdivacky */ 38205408Srdivacky 39205408Srdivacky#ifndef lint 40205408Srdivackystatic char copyright[] = 41205408Srdivacky"@(#) Copyright (c) 1988, 1993, 1994\n\ 42205408Srdivacky The Regents of the University of California. All rights reserved.\n"; 43205408Srdivacky#endif /* not lint */ 44199482Srdivacky 45199482Srdivacky#ifndef lint 46199482Srdivackystatic char sccsid[] = "@(#)cp.c 8.2 (Berkeley) 4/1/94"; 47199482Srdivacky#endif /* not lint */ 48226633Sdim 49226633Sdim/* 50239462Sdim * Cp copies source files to target files. 51200583Srdivacky * 52199482Srdivacky * The global PATH_T structure "to" always contains the path to the 53199482Srdivacky * current target file. Since fts(3) does not change directories, 54199482Srdivacky * this path can be either absolute or dot-realative. 55226633Sdim * 56239462Sdim * The basic algorithm is to initialize "to" and use fts(3) to traverse 57199482Srdivacky * the file hierarchy rooted in the argument list. A trivial case is the 58199482Srdivacky * case of 'cp file1 file2'. The more interesting case is the case of 59239462Sdim * 'cp file1 file2 ... fileN dir' where the hierarchy is traversed and the 60239462Sdim * path (relative to the root of the traversal) is appended to dir (stored 61239462Sdim * in "to") to form the final target path. 62239462Sdim */ 63239462Sdim 64218893Sdim#include <sys/param.h> 65226633Sdim#include <sys/stat.h> 66226633Sdim#include <sys/mman.h> 67218893Sdim#include <sys/time.h> 68218893Sdim 69218893Sdim#include <dirent.h> 70218893Sdim#include <err.h> 71218893Sdim#include <errno.h> 72218893Sdim#include <fcntl.h> 73218893Sdim#include <fts.h> 74218893Sdim#include <stdio.h> 75199482Srdivacky#include <stdlib.h> 76226633Sdim#include <string.h> 77199482Srdivacky#include <unistd.h> 78199482Srdivacky 79199482Srdivacky#include "extern.h" 80199482Srdivacky 81226633Sdim#define STRIP_TRAILING_SLASH(p) { \ 82199482Srdivacky while ((p).p_end > (p).p_path + 1 && (p).p_end[-1] == '/') \ 83199482Srdivacky *--(p).p_end = 0; \ 84199482Srdivacky} 85199482Srdivacky 86226633SdimPATH_T to = { to.p_path, "" }; 87212904Sdim 88218893Sdimuid_t myuid; 89226633Sdimint Rflag, iflag, pflag, rflag; 90226633Sdimint myumask; 91199482Srdivacky 92212904Sdimenum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE }; 93226633Sdim 94226633Sdimint copy __P((char *[], enum op, int)); 95234353Sdimint mastercmp __P((const FTSENT **, const FTSENT **)); 96212904Sdim 97212904Sdimint 98212904Sdimmain(argc, argv) 99226633Sdim int argc; 100212904Sdim char *argv[]; 101218893Sdim{ 102226633Sdim struct stat to_stat, tmp_stat; 103212904Sdim enum op type; 104212904Sdim int Hflag, Lflag, Pflag, ch, fts_options, r; 105212904Sdim char *target; 106212904Sdim 107199482Srdivacky Hflag = Lflag = Pflag = Rflag = 0; 108199482Srdivacky while ((ch = getopt(argc, argv, "HLPRfipr")) != EOF) 109218893Sdim switch (ch) { 110218893Sdim case 'H': 111226633Sdim Hflag = 1; 112218893Sdim Lflag = Pflag = 0; 113226633Sdim break; 114226633Sdim case 'L': 115200583Srdivacky Lflag = 1; 116212904Sdim Hflag = Pflag = 0; 117200583Srdivacky break; 118218893Sdim case 'P': 119212904Sdim Pflag = 1; 120199482Srdivacky Hflag = Lflag = 0; 121199482Srdivacky break; 122234353Sdim case 'R': 123234353Sdim Rflag = 1; 124234353Sdim break; 125234353Sdim case 'f': 126234353Sdim iflag = 0; 127234353Sdim break; 128234353Sdim case 'i': 129234353Sdim iflag = isatty(fileno(stdin)); 130234353Sdim break; 131234353Sdim case 'p': 132234353Sdim pflag = 1; 133234353Sdim break; 134234353Sdim case 'r': 135234353Sdim rflag = 1; 136234353Sdim break; 137234353Sdim case '?': 138234353Sdim default: 139239462Sdim usage(); 140234353Sdim break; 141234353Sdim } 142234353Sdim argc -= optind; 143234353Sdim argv += optind; 144234353Sdim 145234353Sdim if (argc < 2) 146234353Sdim usage(); 147234353Sdim 148234353Sdim fts_options = FTS_NOCHDIR | FTS_PHYSICAL; 149234353Sdim if (rflag) { 150234353Sdim if (Rflag) 151234353Sdim errx(1, 152234353Sdim "the -R and -r options may not be specified together."); 153234353Sdim if (Hflag || Lflag || Pflag) 154234353Sdim errx(1, 155234353Sdim "the -H, -L, and -P options may not be specified with the -r option."); 156234353Sdim fts_options &= ~FTS_PHYSICAL; 157234353Sdim fts_options |= FTS_LOGICAL; 158234353Sdim } 159234353Sdim if (Rflag) { 160234353Sdim if (Hflag) 161234353Sdim fts_options |= FTS_COMFOLLOW; 162234353Sdim if (Lflag) { 163234353Sdim fts_options &= ~FTS_PHYSICAL; 164234353Sdim fts_options |= FTS_LOGICAL; 165234353Sdim } 166234353Sdim } else { 167234353Sdim fts_options &= ~FTS_PHYSICAL; 168234353Sdim fts_options |= FTS_LOGICAL; 169234353Sdim } 170234353Sdim 171234353Sdim myuid = getuid(); 172234353Sdim 173234353Sdim /* Copy the umask for explicit mode setting. */ 174234353Sdim myumask = umask(0); 175234353Sdim (void)umask(myumask); 176234353Sdim 177234353Sdim /* Save the target base in "to". */ 178234353Sdim target = argv[--argc]; 179234353Sdim if (strlen(target) > MAXPATHLEN) 180234353Sdim errx(1, "%s: name too long", target); 181234353Sdim (void)strcpy(to.p_path, target); 182234353Sdim to.p_end = to.p_path + strlen(to.p_path); 183234353Sdim if (to.p_path == to.p_end) { 184234353Sdim *to.p_end++ = '.'; 185234353Sdim *to.p_end = 0; 186234353Sdim } 187234353Sdim STRIP_TRAILING_SLASH(to); 188234353Sdim to.target_end = to.p_end; 189234353Sdim 190234353Sdim /* Set end of argument list for fts(3). */ 191234353Sdim argv[argc] = NULL; 192234353Sdim 193234353Sdim /* 194234353Sdim * Cp has two distinct cases: 195234353Sdim * 196234353Sdim * cp [-R] source target 197234353Sdim * cp [-R] source1 ... sourceN directory 198234353Sdim * 199234353Sdim * In both cases, source can be either a file or a directory. 200234353Sdim * 201234353Sdim * In (1), the target becomes a copy of the source. That is, if the 202234353Sdim * source is a file, the target will be a file, and likewise for 203234353Sdim * directories. 204234353Sdim * 205234353Sdim * In (2), the real target is not directory, but "directory/source". 206234353Sdim */ 207234353Sdim r = stat(to.p_path, &to_stat); 208234353Sdim if (r == -1 && errno != ENOENT) 209234353Sdim err(1, "%s", to.p_path); 210234353Sdim if (r == -1 || !S_ISDIR(to_stat.st_mode)) { 211234353Sdim /* 212234353Sdim * Case (1). Target is not a directory. 213234353Sdim */ 214234353Sdim if (argc > 1) { 215234353Sdim usage(); 216234353Sdim exit(1); 217234353Sdim } 218234353Sdim /* 219234353Sdim * Need to detect the case: 220234353Sdim * cp -R dir foo 221234353Sdim * Where dir is a directory and foo does not exist, where 222234353Sdim * we want pathname concatenations turned on but not for 223234353Sdim * the initial mkdir(). 224234353Sdim */ 225234353Sdim if (r == -1) { 226234353Sdim if (rflag || (Rflag && (Lflag || Hflag))) 227234353Sdim stat(*argv, &tmp_stat); 228234353Sdim else 229234353Sdim lstat(*argv, &tmp_stat); 230234353Sdim 231234353Sdim if (S_ISDIR(tmp_stat.st_mode) && (Rflag || rflag)) 232234353Sdim type = DIR_TO_DNE; 233234353Sdim else 234234353Sdim type = FILE_TO_FILE; 235234353Sdim } else 236234353Sdim type = FILE_TO_FILE; 237234353Sdim } else 238234353Sdim /* 239234353Sdim * Case (2). Target is a directory. 240234353Sdim */ 241234353Sdim type = FILE_TO_DIR; 242234353Sdim 243234353Sdim exit (copy(argv, type, fts_options)); 244234353Sdim} 245234353Sdim 246234353Sdimint 247234353Sdimcopy(argv, type, fts_options) 248234353Sdim char *argv[]; 249234353Sdim enum op type; 250234353Sdim int fts_options; 251234353Sdim{ 252234353Sdim struct stat to_stat; 253234353Sdim FTS *ftsp; 254234353Sdim FTSENT *curr; 255234353Sdim int base, dne, nlen, rval; 256234353Sdim char *p, *target_mid; 257234353Sdim 258234353Sdim if ((ftsp = fts_open(argv, fts_options, mastercmp)) == NULL) 259234353Sdim err(1, NULL); 260234353Sdim for (rval = 0; (curr = fts_read(ftsp)) != NULL;) { 261234353Sdim switch (curr->fts_info) { 262234353Sdim case FTS_NS: 263234353Sdim case FTS_ERR: 264234353Sdim warnx("%s: %s", 265234353Sdim curr->fts_path, strerror(curr->fts_errno)); 266234353Sdim rval = 1; 267234353Sdim continue; 268234353Sdim case FTS_DC: /* Warn, continue. */ 269234353Sdim warnx("%s: directory causes a cycle", curr->fts_path); 270234353Sdim rval = 1; 271234353Sdim continue; 272234353Sdim case FTS_DP: /* Ignore, continue. */ 273234353Sdim continue; 274234353Sdim } 275234353Sdim 276234353Sdim /* 277234353Sdim * If we are in case (2) or (3) above, we need to append the 278234353Sdim * source name to the target name. 279234353Sdim */ 280234353Sdim if (type != FILE_TO_FILE) { 281234353Sdim /* 282234353Sdim * Need to remember the roots of traversals to create 283234353Sdim * correct pathnames. If there's a directory being 284234353Sdim * copied to a non-existent directory, e.g. 285234353Sdim * cp -R a/dir noexist 286234353Sdim * the resulting path name should be noexist/foo, not 287234353Sdim * noexist/dir/foo (where foo is a file in dir), which 288234353Sdim * is the case where the target exists. 289234353Sdim * 290234353Sdim * Also, check for "..". This is for correct path 291234353Sdim * concatentation for paths ending in "..", e.g. 292234353Sdim * cp -R .. /tmp 293234353Sdim * Paths ending in ".." are changed to ".". This is 294234353Sdim * tricky, but seems the easiest way to fix the problem. 295234353Sdim * 296234353Sdim * XXX 297234353Sdim * Since the first level MUST be FTS_ROOTLEVEL, base 298234353Sdim * is always initialized. 299234353Sdim */ 300234353Sdim if (curr->fts_level == FTS_ROOTLEVEL) 301234353Sdim if (type != DIR_TO_DNE) { 302234353Sdim p = strrchr(curr->fts_path, '/'); 303234353Sdim base = (p == NULL) ? 0 : 304234353Sdim (int)(p - curr->fts_path + 1); 305234353Sdim 306234353Sdim if (!strcmp(&curr->fts_path[base], 307234353Sdim "..")) 308234353Sdim base += 1; 309234353Sdim } else 310234353Sdim base = curr->fts_pathlen; 311234353Sdim 312234353Sdim p = &curr->fts_path[base]; 313234353Sdim nlen = curr->fts_pathlen - base; 314234353Sdim target_mid = to.target_end; 315234353Sdim if (*p != '/' && target_mid[-1] != '/') 316234353Sdim *target_mid++ = '/'; 317234353Sdim *target_mid = 0; 318234353Sdim if (target_mid - to.p_path + nlen > MAXPATHLEN) { 319234353Sdim warnx("%s%s: name too long (not copied)", 320234353Sdim to.p_path, p); 321234353Sdim rval = 1; 322234353Sdim continue; 323234353Sdim } 324234353Sdim (void)strncat(target_mid, p, nlen); 325234353Sdim to.p_end = target_mid + nlen; 326234353Sdim *to.p_end = 0; 327234353Sdim STRIP_TRAILING_SLASH(to); 328234353Sdim } 329234353Sdim 330234353Sdim /* Not an error but need to remember it happened */ 331234353Sdim if (stat(to.p_path, &to_stat) == -1) 332234353Sdim dne = 1; 333234353Sdim else { 334234353Sdim if (to_stat.st_dev == curr->fts_statp->st_dev && 335234353Sdim to_stat.st_ino == curr->fts_statp->st_ino) { 336234353Sdim warnx("%s and %s are identical (not copied).", 337234353Sdim to.p_path, curr->fts_path); 338234353Sdim rval = 1; 339234353Sdim if (S_ISDIR(curr->fts_statp->st_mode)) 340234353Sdim (void)fts_set(ftsp, curr, FTS_SKIP); 341234353Sdim continue; 342234353Sdim } 343234353Sdim dne = 0; 344234353Sdim } 345234353Sdim 346234353Sdim switch (curr->fts_statp->st_mode & S_IFMT) { 347234353Sdim case S_IFLNK: 348234353Sdim if (copy_link(curr, !dne)) 349234353Sdim rval = 1; 350234353Sdim break; 351234353Sdim case S_IFDIR: 352234353Sdim if (!Rflag && !rflag) { 353234353Sdim warnx("%s is a directory (not copied).", 354234353Sdim curr->fts_path); 355234353Sdim (void)fts_set(ftsp, curr, FTS_SKIP); 356234353Sdim rval = 1; 357199482Srdivacky break; 358226633Sdim } 359199482Srdivacky /* 360199482Srdivacky * If the directory doesn't exist, create the new 361199482Srdivacky * one with the from file mode plus owner RWX bits, 362199482Srdivacky * modified by the umask. Trade-off between being 363199482Srdivacky * able to write the directory (if from directory is 364199482Srdivacky * 555) and not causing a permissions race. If the 365199482Srdivacky * umask blocks owner writes, we fail.. 366199482Srdivacky */ 367199482Srdivacky if (dne) { 368199482Srdivacky if (mkdir(to.p_path, 369199482Srdivacky curr->fts_statp->st_mode | S_IRWXU) < 0) 370199482Srdivacky err(1, "%s", to.p_path); 371199990Srdivacky } else if (!S_ISDIR(to_stat.st_mode)) { 372234353Sdim errno = ENOTDIR; 373199482Srdivacky err(1, "%s", to.p_path); 374199482Srdivacky } 375199482Srdivacky /* 376199482Srdivacky * If not -p and directory didn't exist, set it to be 377199482Srdivacky * the same as the from directory, umodified by the 378199482Srdivacky * umask; arguably wrong, but it's been that way 379199990Srdivacky * forever. 380199482Srdivacky */ 381199482Srdivacky if (pflag && setfile(curr->fts_statp, 0)) 382199482Srdivacky rval = 1; 383199482Srdivacky else if (dne) 384199482Srdivacky (void)chmod(to.p_path, 385199482Srdivacky curr->fts_statp->st_mode); 386199482Srdivacky break; 387199482Srdivacky case S_IFBLK: 388207619Srdivacky case S_IFCHR: 389199482Srdivacky if (Rflag) { 390199482Srdivacky if (copy_special(curr->fts_statp, !dne)) 391199482Srdivacky rval = 1; 392199990Srdivacky } else 393199482Srdivacky if (copy_file(curr, dne)) 394199482Srdivacky rval = 1; 395199482Srdivacky break; 396199482Srdivacky case S_IFIFO: 397199482Srdivacky if (Rflag) 398199482Srdivacky if (copy_fifo(curr->fts_statp, !dne)) 399199482Srdivacky rval = 1; 400199482Srdivacky else 401199482Srdivacky if (copy_file(curr, dne)) 402207619Srdivacky rval = 1; 403199482Srdivacky break; 404199482Srdivacky default: 405199482Srdivacky if (copy_file(curr, dne)) 406200583Srdivacky rval = 1; 407200583Srdivacky break; 408199482Srdivacky } 409199482Srdivacky } 410199482Srdivacky if (errno) 411199482Srdivacky err(1, "fts_read"); 412199482Srdivacky return (rval); 413199482Srdivacky} 414210299Sed 415210299Sed/* 416210299Sed * mastercmp -- 417199482Srdivacky * The comparison function for the copy order. The order is to copy 418199482Srdivacky * non-directory files before directory files. The reason for this 419207619Srdivacky * is because files tend to be in the same cylinder group as their 420199482Srdivacky * parent directory, whereas directories tend not to be. Copying the 421199482Srdivacky * files first reduces seeking. 422199482Srdivacky */ 423199482Srdivackyint 424199482Srdivackymastercmp(a, b) 425199482Srdivacky const FTSENT **a, **b; 426199482Srdivacky{ 427226633Sdim int a_info, b_info; 428202379Srdivacky 429226633Sdim a_info = (*a)->fts_info; 430226633Sdim if (a_info == FTS_ERR || a_info == FTS_NS || a_info == FTS_DNR) 431226633Sdim return (0); 432226633Sdim b_info = (*b)->fts_info; 433226633Sdim if (b_info == FTS_ERR || b_info == FTS_NS || b_info == FTS_DNR) 434226633Sdim return (0); 435226633Sdim if (a_info == FTS_D) 436226633Sdim return (-1); 437226633Sdim if (b_info == FTS_D) 438226633Sdim return (1); 439226633Sdim return (0); 440226633Sdim} 441226633Sdim