19ddb49cbSWarner Losh /*-
28a16b7a1SPedro F. Giffuni * SPDX-License-Identifier: BSD-3-Clause
38a16b7a1SPedro F. Giffuni *
44b88c807SRodney W. Grimes * Copyright (c) 1988, 1993, 1994
54b88c807SRodney W. Grimes * The Regents of the University of California. All rights reserved.
64b88c807SRodney W. Grimes *
74b88c807SRodney W. Grimes * This code is derived from software contributed to Berkeley by
84b88c807SRodney W. Grimes * David Hitz of Auspex Systems Inc.
94b88c807SRodney W. Grimes *
104b88c807SRodney W. Grimes * Redistribution and use in source and binary forms, with or without
114b88c807SRodney W. Grimes * modification, are permitted provided that the following conditions
124b88c807SRodney W. Grimes * are met:
134b88c807SRodney W. Grimes * 1. Redistributions of source code must retain the above copyright
144b88c807SRodney W. Grimes * notice, this list of conditions and the following disclaimer.
154b88c807SRodney W. Grimes * 2. Redistributions in binary form must reproduce the above copyright
164b88c807SRodney W. Grimes * notice, this list of conditions and the following disclaimer in the
174b88c807SRodney W. Grimes * documentation and/or other materials provided with the distribution.
18fbbd9655SWarner Losh * 3. Neither the name of the University nor the names of its contributors
194b88c807SRodney W. Grimes * may be used to endorse or promote products derived from this software
204b88c807SRodney W. Grimes * without specific prior written permission.
214b88c807SRodney W. Grimes *
224b88c807SRodney W. Grimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
234b88c807SRodney W. Grimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
244b88c807SRodney W. Grimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
254b88c807SRodney W. Grimes * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
264b88c807SRodney W. Grimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
274b88c807SRodney W. Grimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
284b88c807SRodney W. Grimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
294b88c807SRodney W. Grimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
304b88c807SRodney W. Grimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
314b88c807SRodney W. Grimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
324b88c807SRodney W. Grimes * SUCH DAMAGE.
334b88c807SRodney W. Grimes */
344b88c807SRodney W. Grimes
354b88c807SRodney W. Grimes /*
364b88c807SRodney W. Grimes * Cp copies source files to target files.
374b88c807SRodney W. Grimes *
384b88c807SRodney W. Grimes * The global PATH_T structure "to" always contains the path to the
394b88c807SRodney W. Grimes * current target file. Since fts(3) does not change directories,
40890acb95SSteve Price * this path can be either absolute or dot-relative.
414b88c807SRodney W. Grimes *
424b88c807SRodney W. Grimes * The basic algorithm is to initialize "to" and use fts(3) to traverse
434b88c807SRodney W. Grimes * the file hierarchy rooted in the argument list. A trivial case is the
444b88c807SRodney W. Grimes * case of 'cp file1 file2'. The more interesting case is the case of
454b88c807SRodney W. Grimes * 'cp file1 file2 ... fileN dir' where the hierarchy is traversed and the
464b88c807SRodney W. Grimes * path (relative to the root of the traversal) is appended to dir (stored
474b88c807SRodney W. Grimes * in "to") to form the final target path.
484b88c807SRodney W. Grimes */
494b88c807SRodney W. Grimes
5082fdc5e6SBruce Evans #include <sys/types.h>
514b88c807SRodney W. Grimes #include <sys/stat.h>
524b88c807SRodney W. Grimes
53848263aaSKyle Evans #include <assert.h>
544b88c807SRodney W. Grimes #include <err.h>
554b88c807SRodney W. Grimes #include <errno.h>
564b88c807SRodney W. Grimes #include <fts.h>
5734f9c106SWarner Losh #include <limits.h>
5882fdc5e6SBruce Evans #include <signal.h>
594506e907SMichael Haro #include <stdio.h>
6026f6b0fbSDag-Erling Smørgrav #include <stdlib.h>
614b88c807SRodney W. Grimes #include <string.h>
624b88c807SRodney W. Grimes #include <unistd.h>
634b88c807SRodney W. Grimes
644b88c807SRodney W. Grimes #include "extern.h"
654b88c807SRodney W. Grimes
664b88c807SRodney W. Grimes #define STRIP_TRAILING_SLASH(p) { \
677658fd20SBruce Evans while ((p).p_end > (p).p_path + 1 && (p).p_end[-1] == '/') \
684b88c807SRodney W. Grimes *--(p).p_end = 0; \
694b88c807SRodney W. Grimes }
704b88c807SRodney W. Grimes
718bd08b5fSMark Murray static char emptystring[] = "";
724b88c807SRodney W. Grimes
738bd08b5fSMark Murray PATH_T to = { to.p_path, emptystring, "" };
748bd08b5fSMark Murray
755a52e3d0SWarner Losh int Nflag, fflag, iflag, lflag, nflag, pflag, sflag, vflag;
760f4467ceSDag-Erling Smørgrav static int Hflag, Lflag, Pflag, Rflag, rflag;
77947193d9SMatthew N. Dodd volatile sig_atomic_t info;
7800d321a2SMatthew N. Dodd
794b88c807SRodney W. Grimes enum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE };
804b88c807SRodney W. Grimes
81848263aaSKyle Evans static int copy(char *[], enum op, int, struct stat *);
824901f51bSBruce Evans static void siginfo(int __unused);
834b88c807SRodney W. Grimes
844b88c807SRodney W. Grimes int
main(int argc,char * argv[])855dce647cSWarner Losh main(int argc, char *argv[])
864b88c807SRodney W. Grimes {
874b88c807SRodney W. Grimes struct stat to_stat, tmp_stat;
884b88c807SRodney W. Grimes enum op type;
890f4467ceSDag-Erling Smørgrav int ch, fts_options, r, have_trailing_slash;
90486d0387SAndrey A. Chernov char *target;
914b88c807SRodney W. Grimes
92f815125fSGavin Atkinson fts_options = FTS_NOCHDIR | FTS_PHYSICAL;
930f4467ceSDag-Erling Smørgrav while ((ch = getopt(argc, argv, "HLPRafilNnprsvx")) != -1)
944b88c807SRodney W. Grimes switch (ch) {
954b88c807SRodney W. Grimes case 'H':
964b88c807SRodney W. Grimes Hflag = 1;
9797e13037SCameron Katri Lflag = Pflag = 0;
984b88c807SRodney W. Grimes break;
994b88c807SRodney W. Grimes case 'L':
1004b88c807SRodney W. Grimes Lflag = 1;
10197e13037SCameron Katri Hflag = Pflag = 0;
1024b88c807SRodney W. Grimes break;
1034b88c807SRodney W. Grimes case 'P':
10497e13037SCameron Katri Pflag = 1;
1054b88c807SRodney W. Grimes Hflag = Lflag = 0;
1064b88c807SRodney W. Grimes break;
1074b88c807SRodney W. Grimes case 'R':
1084b88c807SRodney W. Grimes Rflag = 1;
1094b88c807SRodney W. Grimes break;
110d140946cSJohn Baldwin case 'a':
111d140946cSJohn Baldwin pflag = 1;
112d140946cSJohn Baldwin Rflag = 1;
11397e13037SCameron Katri Pflag = 1;
114d140946cSJohn Baldwin Hflag = Lflag = 0;
115d140946cSJohn Baldwin break;
1164b88c807SRodney W. Grimes case 'f':
1176add522fSWolfram Schneider fflag = 1;
118786c276fSJohan Karlsson iflag = nflag = 0;
1194b88c807SRodney W. Grimes break;
1204b88c807SRodney W. Grimes case 'i':
1216add522fSWolfram Schneider iflag = 1;
122786c276fSJohan Karlsson fflag = nflag = 0;
123786c276fSJohan Karlsson break;
12464941e9dSRuslan Ermilov case 'l':
12564941e9dSRuslan Ermilov lflag = 1;
12664941e9dSRuslan Ermilov break;
1270f4467ceSDag-Erling Smørgrav case 'N':
1280f4467ceSDag-Erling Smørgrav Nflag = 1;
1290f4467ceSDag-Erling Smørgrav break;
130786c276fSJohan Karlsson case 'n':
131786c276fSJohan Karlsson nflag = 1;
132786c276fSJohan Karlsson fflag = iflag = 0;
1334b88c807SRodney W. Grimes break;
1344b88c807SRodney W. Grimes case 'p':
1354b88c807SRodney W. Grimes pflag = 1;
1364b88c807SRodney W. Grimes break;
1374b88c807SRodney W. Grimes case 'r':
138ba3fe6d1STom Rhodes rflag = Lflag = 1;
13997e13037SCameron Katri Hflag = Pflag = 0;
1404b88c807SRodney W. Grimes break;
1417ea2450fSBryan Drewery case 's':
1427ea2450fSBryan Drewery sflag = 1;
1437ea2450fSBryan Drewery break;
1444506e907SMichael Haro case 'v':
1454506e907SMichael Haro vflag = 1;
1464506e907SMichael Haro break;
147f815125fSGavin Atkinson case 'x':
148f815125fSGavin Atkinson fts_options |= FTS_XDEV;
149f815125fSGavin Atkinson break;
1504b88c807SRodney W. Grimes default:
1514b88c807SRodney W. Grimes usage();
1524b88c807SRodney W. Grimes }
1534b88c807SRodney W. Grimes argc -= optind;
1544b88c807SRodney W. Grimes argv += optind;
1554b88c807SRodney W. Grimes
1564b88c807SRodney W. Grimes if (argc < 2)
1574b88c807SRodney W. Grimes usage();
1584b88c807SRodney W. Grimes
15943d8847eSTom Rhodes if (Rflag && rflag)
16043d8847eSTom Rhodes errx(1, "the -R and -r options may not be specified together");
1617ea2450fSBryan Drewery if (lflag && sflag)
1627ea2450fSBryan Drewery errx(1, "the -l and -s options may not be specified together");
16343d8847eSTom Rhodes if (rflag)
164ba3fe6d1STom Rhodes Rflag = 1;
1654b88c807SRodney W. Grimes if (Rflag) {
1664b88c807SRodney W. Grimes if (Hflag)
1674b88c807SRodney W. Grimes fts_options |= FTS_COMFOLLOW;
1684b88c807SRodney W. Grimes if (Lflag) {
1694b88c807SRodney W. Grimes fts_options &= ~FTS_PHYSICAL;
1704b88c807SRodney W. Grimes fts_options |= FTS_LOGICAL;
1714b88c807SRodney W. Grimes }
17297e13037SCameron Katri } else if (!Pflag) {
1734b88c807SRodney W. Grimes fts_options &= ~FTS_PHYSICAL;
174f9dc2a8bSTim J. Robbins fts_options |= FTS_LOGICAL | FTS_COMFOLLOW;
1754b88c807SRodney W. Grimes }
17600d321a2SMatthew N. Dodd (void)signal(SIGINFO, siginfo);
1774b88c807SRodney W. Grimes
1784b88c807SRodney W. Grimes /* Save the target base in "to". */
1794b88c807SRodney W. Grimes target = argv[--argc];
18034f9c106SWarner Losh if (strlcpy(to.p_path, target, sizeof(to.p_path)) >= sizeof(to.p_path))
1814b88c807SRodney W. Grimes errx(1, "%s: name too long", target);
1824b88c807SRodney W. Grimes to.p_end = to.p_path + strlen(to.p_path);
1834b88c807SRodney W. Grimes if (to.p_path == to.p_end) {
1844b88c807SRodney W. Grimes *to.p_end++ = '.';
1854b88c807SRodney W. Grimes *to.p_end = 0;
1864b88c807SRodney W. Grimes }
187486d0387SAndrey A. Chernov have_trailing_slash = (to.p_end[-1] == '/');
188486d0387SAndrey A. Chernov if (have_trailing_slash)
1894b88c807SRodney W. Grimes STRIP_TRAILING_SLASH(to);
1904b88c807SRodney W. Grimes to.target_end = to.p_end;
1914b88c807SRodney W. Grimes
1924b88c807SRodney W. Grimes /* Set end of argument list for fts(3). */
1934b88c807SRodney W. Grimes argv[argc] = NULL;
1944b88c807SRodney W. Grimes
1954b88c807SRodney W. Grimes /*
1964b88c807SRodney W. Grimes * Cp has two distinct cases:
1974b88c807SRodney W. Grimes *
1984b88c807SRodney W. Grimes * cp [-R] source target
1994b88c807SRodney W. Grimes * cp [-R] source1 ... sourceN directory
2004b88c807SRodney W. Grimes *
2014b88c807SRodney W. Grimes * In both cases, source can be either a file or a directory.
2024b88c807SRodney W. Grimes *
2034b88c807SRodney W. Grimes * In (1), the target becomes a copy of the source. That is, if the
2044b88c807SRodney W. Grimes * source is a file, the target will be a file, and likewise for
2054b88c807SRodney W. Grimes * directories.
2064b88c807SRodney W. Grimes *
2074b88c807SRodney W. Grimes * In (2), the real target is not directory, but "directory/source".
2084b88c807SRodney W. Grimes */
2094b88c807SRodney W. Grimes r = stat(to.p_path, &to_stat);
2104b88c807SRodney W. Grimes if (r == -1 && errno != ENOENT)
2114b88c807SRodney W. Grimes err(1, "%s", to.p_path);
2124b88c807SRodney W. Grimes if (r == -1 || !S_ISDIR(to_stat.st_mode)) {
2134b88c807SRodney W. Grimes /*
2144b88c807SRodney W. Grimes * Case (1). Target is not a directory.
2154b88c807SRodney W. Grimes */
216a76b9b69SEdwin Groothuis if (argc > 1)
217a76b9b69SEdwin Groothuis errx(1, "%s is not a directory", to.p_path);
218a76b9b69SEdwin Groothuis
2194b88c807SRodney W. Grimes /*
2204b88c807SRodney W. Grimes * Need to detect the case:
2214b88c807SRodney W. Grimes * cp -R dir foo
2224b88c807SRodney W. Grimes * Where dir is a directory and foo does not exist, where
2234b88c807SRodney W. Grimes * we want pathname concatenations turned on but not for
2244b88c807SRodney W. Grimes * the initial mkdir().
2254b88c807SRodney W. Grimes */
2264b88c807SRodney W. Grimes if (r == -1) {
227ba3fe6d1STom Rhodes if (Rflag && (Lflag || Hflag))
2284b88c807SRodney W. Grimes stat(*argv, &tmp_stat);
2294b88c807SRodney W. Grimes else
2304b88c807SRodney W. Grimes lstat(*argv, &tmp_stat);
2314b88c807SRodney W. Grimes
23243d8847eSTom Rhodes if (S_ISDIR(tmp_stat.st_mode) && Rflag)
2334b88c807SRodney W. Grimes type = DIR_TO_DNE;
2344b88c807SRodney W. Grimes else
2354b88c807SRodney W. Grimes type = FILE_TO_FILE;
2364b88c807SRodney W. Grimes } else
2374b88c807SRodney W. Grimes type = FILE_TO_FILE;
23827d3ae35SAndrey A. Chernov
23927d3ae35SAndrey A. Chernov if (have_trailing_slash && type == FILE_TO_FILE) {
240724fd448SBryan Drewery if (r == -1) {
24127d3ae35SAndrey A. Chernov errx(1, "directory %s does not exist",
24227d3ae35SAndrey A. Chernov to.p_path);
243724fd448SBryan Drewery } else
24427d3ae35SAndrey A. Chernov errx(1, "%s is not a directory", to.p_path);
24527d3ae35SAndrey A. Chernov }
2464b88c807SRodney W. Grimes } else
2474b88c807SRodney W. Grimes /*
2484b88c807SRodney W. Grimes * Case (2). Target is a directory.
2494b88c807SRodney W. Grimes */
2504b88c807SRodney W. Grimes type = FILE_TO_DIR;
2514b88c807SRodney W. Grimes
252848263aaSKyle Evans /*
253848263aaSKyle Evans * For DIR_TO_DNE, we could provide copy() with the to_stat we've
254848263aaSKyle Evans * already allocated on the stack here that isn't being used for
255848263aaSKyle Evans * anything. Not doing so, though, simplifies later logic a little bit
256848263aaSKyle Evans * as we need to skip checking root_stat on the first iteration and
257848263aaSKyle Evans * ensure that we set it with the first mkdir().
258848263aaSKyle Evans */
259848263aaSKyle Evans exit (copy(argv, type, fts_options, (type == DIR_TO_DNE ? NULL :
260848263aaSKyle Evans &to_stat)));
2614b88c807SRodney W. Grimes }
2624b88c807SRodney W. Grimes
263ba8acd9dSMark Murray static int
copy(char * argv[],enum op type,int fts_options,struct stat * root_stat)264848263aaSKyle Evans copy(char *argv[], enum op type, int fts_options, struct stat *root_stat)
2654b88c807SRodney W. Grimes {
266f00f8b4fSKyle Evans char rootname[NAME_MAX];
267848263aaSKyle Evans struct stat created_root_stat, to_stat;
2684b88c807SRodney W. Grimes FTS *ftsp;
2694b88c807SRodney W. Grimes FTSENT *curr;
2708bd08b5fSMark Murray int base = 0, dne, badcp, rval;
2718bd08b5fSMark Murray size_t nlen;
272848263aaSKyle Evans char *p, *recurse_path, *target_mid;
27362de071bSStephen McKay mode_t mask, mode;
2744a5db7acSStephen McKay
2754a5db7acSStephen McKay /*
2764a5db7acSStephen McKay * Keep an inverted copy of the umask, for use in correcting
2774a5db7acSStephen McKay * permissions on created directories when not using -p.
2784a5db7acSStephen McKay */
2794a5db7acSStephen McKay mask = ~umask(0777);
2804a5db7acSStephen McKay umask(~mask);
2814b88c807SRodney W. Grimes
282848263aaSKyle Evans recurse_path = NULL;
283c633f8dcSJilles Tjoelker if ((ftsp = fts_open(argv, fts_options, NULL)) == NULL)
2845ad9e45fSMatthew Dillon err(1, "fts_open");
285cb96a0efSDag-Erling Smørgrav for (badcp = rval = 0; (curr = fts_read(ftsp)) != NULL; badcp = 0) {
2864b88c807SRodney W. Grimes switch (curr->fts_info) {
2874b88c807SRodney W. Grimes case FTS_NS:
288f6fa1b35SDmitrij Tejblum case FTS_DNR:
2894b88c807SRodney W. Grimes case FTS_ERR:
290f070188cSDag-Erling Smørgrav warnc(curr->fts_errno, "%s", curr->fts_path);
2910efa2040SMichael Haro badcp = rval = 1;
2924b88c807SRodney W. Grimes continue;
2934b88c807SRodney W. Grimes case FTS_DC: /* Warn, continue. */
2944b88c807SRodney W. Grimes warnx("%s: directory causes a cycle", curr->fts_path);
2950efa2040SMichael Haro badcp = rval = 1;
2964b88c807SRodney W. Grimes continue;
2978bd08b5fSMark Murray default:
29864baebf9SAlfred Perlstein ;
2994b88c807SRodney W. Grimes }
3004b88c807SRodney W. Grimes
301848263aaSKyle Evans /*
302f00f8b4fSKyle Evans * Stash the root basename off for detecting recursion later.
303f00f8b4fSKyle Evans *
304f00f8b4fSKyle Evans * This will be essential if the root is a symlink and we're
305f00f8b4fSKyle Evans * rolling with -L or -H. The later bits will need this bit in
306f00f8b4fSKyle Evans * particular.
307848263aaSKyle Evans */
308f00f8b4fSKyle Evans if (curr->fts_level == FTS_ROOTLEVEL) {
309f00f8b4fSKyle Evans strlcpy(rootname, curr->fts_name, sizeof(rootname));
310848263aaSKyle Evans }
311848263aaSKyle Evans
3124b88c807SRodney W. Grimes /*
3134b88c807SRodney W. Grimes * If we are in case (2) or (3) above, we need to append the
3144b88c807SRodney W. Grimes * source name to the target name.
3154b88c807SRodney W. Grimes */
3164b88c807SRodney W. Grimes if (type != FILE_TO_FILE) {
3174b88c807SRodney W. Grimes /*
3184b88c807SRodney W. Grimes * Need to remember the roots of traversals to create
3194b88c807SRodney W. Grimes * correct pathnames. If there's a directory being
3204b88c807SRodney W. Grimes * copied to a non-existent directory, e.g.
3214b88c807SRodney W. Grimes * cp -R a/dir noexist
3224b88c807SRodney W. Grimes * the resulting path name should be noexist/foo, not
3234b88c807SRodney W. Grimes * noexist/dir/foo (where foo is a file in dir), which
3244b88c807SRodney W. Grimes * is the case where the target exists.
3254b88c807SRodney W. Grimes *
3265aaef5a6SDag-Erling Smørgrav * Also, check for "..". This is for correct path
32746be34b9SKris Kennaway * concatenation for paths ending in "..", e.g.
3284b88c807SRodney W. Grimes * cp -R .. /tmp
3294b88c807SRodney W. Grimes * Paths ending in ".." are changed to ".". This is
3304b88c807SRodney W. Grimes * tricky, but seems the easiest way to fix the problem.
3314b88c807SRodney W. Grimes *
3324b88c807SRodney W. Grimes * XXX
3334b88c807SRodney W. Grimes * Since the first level MUST be FTS_ROOTLEVEL, base
3344b88c807SRodney W. Grimes * is always initialized.
3354b88c807SRodney W. Grimes */
336426e9c1dSWarner Losh if (curr->fts_level == FTS_ROOTLEVEL) {
3374b88c807SRodney W. Grimes if (type != DIR_TO_DNE) {
3384b88c807SRodney W. Grimes p = strrchr(curr->fts_path, '/');
3394b88c807SRodney W. Grimes base = (p == NULL) ? 0 :
3404b88c807SRodney W. Grimes (int)(p - curr->fts_path + 1);
3414b88c807SRodney W. Grimes
3424b88c807SRodney W. Grimes if (!strcmp(&curr->fts_path[base],
3434b88c807SRodney W. Grimes ".."))
3444b88c807SRodney W. Grimes base += 1;
3454b88c807SRodney W. Grimes } else
3464b88c807SRodney W. Grimes base = curr->fts_pathlen;
347426e9c1dSWarner Losh }
3484b88c807SRodney W. Grimes
3494b88c807SRodney W. Grimes p = &curr->fts_path[base];
3504b88c807SRodney W. Grimes nlen = curr->fts_pathlen - base;
3517658fd20SBruce Evans target_mid = to.target_end;
3527658fd20SBruce Evans if (*p != '/' && target_mid[-1] != '/')
3537658fd20SBruce Evans *target_mid++ = '/';
3547658fd20SBruce Evans *target_mid = 0;
35534f9c106SWarner Losh if (target_mid - to.p_path + nlen >= PATH_MAX) {
3567658fd20SBruce Evans warnx("%s%s: name too long (not copied)",
3577658fd20SBruce Evans to.p_path, p);
3580efa2040SMichael Haro badcp = rval = 1;
3597658fd20SBruce Evans continue;
3607658fd20SBruce Evans }
3617658fd20SBruce Evans (void)strncat(target_mid, p, nlen);
3627658fd20SBruce Evans to.p_end = target_mid + nlen;
3634b88c807SRodney W. Grimes *to.p_end = 0;
3644b88c807SRodney W. Grimes STRIP_TRAILING_SLASH(to);
365f00f8b4fSKyle Evans
366f00f8b4fSKyle Evans /*
367f00f8b4fSKyle Evans * We're on the verge of recursing on ourselves. Either
368f00f8b4fSKyle Evans * we need to stop right here (we knowingly just created
369f00f8b4fSKyle Evans * it), or we will in an immediate descendant. Record
370f00f8b4fSKyle Evans * the path of the immediate descendant to make our
371f00f8b4fSKyle Evans * lives a little less complicated looking.
372f00f8b4fSKyle Evans */
373f00f8b4fSKyle Evans if (curr->fts_info == FTS_D && root_stat != NULL &&
374f00f8b4fSKyle Evans root_stat->st_dev == curr->fts_statp->st_dev &&
375f00f8b4fSKyle Evans root_stat->st_ino == curr->fts_statp->st_ino) {
376f00f8b4fSKyle Evans assert(recurse_path == NULL);
377f00f8b4fSKyle Evans
378f00f8b4fSKyle Evans if (root_stat == &created_root_stat) {
379f00f8b4fSKyle Evans /*
380f00f8b4fSKyle Evans * This directory didn't exist when we
381f00f8b4fSKyle Evans * started, we created it as part of
382f00f8b4fSKyle Evans * traversal. Stop right here before we
383f00f8b4fSKyle Evans * do something silly.
384f00f8b4fSKyle Evans */
385f00f8b4fSKyle Evans fts_set(ftsp, curr, FTS_SKIP);
386f00f8b4fSKyle Evans continue;
387f00f8b4fSKyle Evans }
388f00f8b4fSKyle Evans
389f00f8b4fSKyle Evans if (asprintf(&recurse_path, "%s/%s", to.p_path,
390f00f8b4fSKyle Evans rootname) == -1)
391f00f8b4fSKyle Evans err(1, "asprintf");
392f00f8b4fSKyle Evans }
393f00f8b4fSKyle Evans
394f00f8b4fSKyle Evans if (recurse_path != NULL &&
395f00f8b4fSKyle Evans strcmp(to.p_path, recurse_path) == 0) {
396f00f8b4fSKyle Evans fts_set(ftsp, curr, FTS_SKIP);
397f00f8b4fSKyle Evans continue;
398f00f8b4fSKyle Evans }
3994b88c807SRodney W. Grimes }
4004b88c807SRodney W. Grimes
4014a5db7acSStephen McKay if (curr->fts_info == FTS_DP) {
4024a5db7acSStephen McKay /*
40316ef4ac3SStephen McKay * We are nearly finished with this directory. If we
40416ef4ac3SStephen McKay * didn't actually copy it, or otherwise don't need to
40516ef4ac3SStephen McKay * change its attributes, then we are done.
40662de071bSStephen McKay */
40762de071bSStephen McKay if (!curr->fts_number)
40862de071bSStephen McKay continue;
40962de071bSStephen McKay /*
41062de071bSStephen McKay * If -p is in effect, set all the attributes.
41162de071bSStephen McKay * Otherwise, set the correct permissions, limited
41216ef4ac3SStephen McKay * by the umask. Optimise by avoiding a chmod()
41316ef4ac3SStephen McKay * if possible (which is usually the case if we
41416ef4ac3SStephen McKay * made the directory). Note that mkdir() does not
41516ef4ac3SStephen McKay * honour setuid, setgid and sticky bits, but we
41616ef4ac3SStephen McKay * normally want to preserve them on directories.
4174a5db7acSStephen McKay */
418eedc99e7SStephen McKay if (pflag) {
419529a7167SJohn-Mark Gurney if (setfile(curr->fts_statp, -1))
420eedc99e7SStephen McKay rval = 1;
4219b4261c9SChristian S.J. Peron if (preserve_dir_acls(curr->fts_statp,
4229b4261c9SChristian S.J. Peron curr->fts_accpath, to.p_path) != 0)
4239b4261c9SChristian S.J. Peron rval = 1;
424eedc99e7SStephen McKay } else {
42562de071bSStephen McKay mode = curr->fts_statp->st_mode;
42662de071bSStephen McKay if ((mode & (S_ISUID | S_ISGID | S_ISTXT)) ||
42762de071bSStephen McKay ((mode | S_IRWXU) & mask) != (mode & mask))
428724fd448SBryan Drewery if (chmod(to.p_path, mode & mask) !=
429724fd448SBryan Drewery 0) {
4304a5db7acSStephen McKay warn("chmod: %s", to.p_path);
4314a5db7acSStephen McKay rval = 1;
4324a5db7acSStephen McKay }
4334a5db7acSStephen McKay }
4344a5db7acSStephen McKay continue;
4354a5db7acSStephen McKay }
4364a5db7acSStephen McKay
4370729d1e8SDag-Erling Smørgrav /* Check if source and destination are identical. */
4380729d1e8SDag-Erling Smørgrav if (stat(to.p_path, &to_stat) == 0 &&
4390729d1e8SDag-Erling Smørgrav to_stat.st_dev == curr->fts_statp->st_dev &&
4404b88c807SRodney W. Grimes to_stat.st_ino == curr->fts_statp->st_ino) {
4414b88c807SRodney W. Grimes warnx("%s and %s are identical (not copied).",
4424b88c807SRodney W. Grimes to.p_path, curr->fts_path);
4430efa2040SMichael Haro badcp = rval = 1;
4444b88c807SRodney W. Grimes if (S_ISDIR(curr->fts_statp->st_mode))
4454b88c807SRodney W. Grimes (void)fts_set(ftsp, curr, FTS_SKIP);
4464b88c807SRodney W. Grimes continue;
4474b88c807SRodney W. Grimes }
4480729d1e8SDag-Erling Smørgrav
4490729d1e8SDag-Erling Smørgrav /* Not an error but need to remember it happened. */
4500729d1e8SDag-Erling Smørgrav dne = lstat(to.p_path, &to_stat) != 0;
4514b88c807SRodney W. Grimes
4524b88c807SRodney W. Grimes switch (curr->fts_statp->st_mode & S_IFMT) {
4534b88c807SRodney W. Grimes case S_IFLNK:
454f9dc2a8bSTim J. Robbins if ((fts_options & FTS_LOGICAL) ||
455f9dc2a8bSTim J. Robbins ((fts_options & FTS_COMFOLLOW) &&
456f9dc2a8bSTim J. Robbins curr->fts_level == 0)) {
45764d6925dSDag-Erling Smørgrav /*
45864d6925dSDag-Erling Smørgrav * We asked FTS to follow links but got
45964d6925dSDag-Erling Smørgrav * here anyway, which means the target is
46064d6925dSDag-Erling Smørgrav * nonexistent or inaccessible. Let
46164d6925dSDag-Erling Smørgrav * copy_file() deal with the error.
46264d6925dSDag-Erling Smørgrav */
463f9dc2a8bSTim J. Robbins if (copy_file(curr, dne))
464f9dc2a8bSTim J. Robbins badcp = rval = 1;
465f9dc2a8bSTim J. Robbins } else {
46664d6925dSDag-Erling Smørgrav /* Copy the link. */
4674b88c807SRodney W. Grimes if (copy_link(curr, !dne))
4680efa2040SMichael Haro badcp = rval = 1;
469f9dc2a8bSTim J. Robbins }
4704b88c807SRodney W. Grimes break;
4714b88c807SRodney W. Grimes case S_IFDIR:
472ba3fe6d1STom Rhodes if (!Rflag) {
4734b88c807SRodney W. Grimes warnx("%s is a directory (not copied).",
4744b88c807SRodney W. Grimes curr->fts_path);
4754b88c807SRodney W. Grimes (void)fts_set(ftsp, curr, FTS_SKIP);
4760efa2040SMichael Haro badcp = rval = 1;
4774b88c807SRodney W. Grimes break;
4784b88c807SRodney W. Grimes }
4794b88c807SRodney W. Grimes /*
4804b88c807SRodney W. Grimes * If the directory doesn't exist, create the new
4814b88c807SRodney W. Grimes * one with the from file mode plus owner RWX bits,
4824b88c807SRodney W. Grimes * modified by the umask. Trade-off between being
4834b88c807SRodney W. Grimes * able to write the directory (if from directory is
4844b88c807SRodney W. Grimes * 555) and not causing a permissions race. If the
485724fd448SBryan Drewery * umask blocks owner writes, we fail.
4864b88c807SRodney W. Grimes */
4874b88c807SRodney W. Grimes if (dne) {
488dd286b0dSDag-Erling Smørgrav mode = curr->fts_statp->st_mode | S_IRWXU;
489dd286b0dSDag-Erling Smørgrav if (mkdir(to.p_path, mode) != 0) {
490dd286b0dSDag-Erling Smørgrav warn("%s", to.p_path);
491dd286b0dSDag-Erling Smørgrav (void)fts_set(ftsp, curr, FTS_SKIP);
492dd286b0dSDag-Erling Smørgrav badcp = rval = 1;
493dd286b0dSDag-Erling Smørgrav break;
494dd286b0dSDag-Erling Smørgrav }
495848263aaSKyle Evans /*
496848263aaSKyle Evans * First DNE with a NULL root_stat is the root
497848263aaSKyle Evans * path, so set root_stat. We can't really
498848263aaSKyle Evans * tell in all cases if the target path is
499848263aaSKyle Evans * within the src path, so we just stat() the
500848263aaSKyle Evans * first directory we created and use that.
501848263aaSKyle Evans */
502848263aaSKyle Evans if (root_stat == NULL &&
503dd286b0dSDag-Erling Smørgrav stat(to.p_path, &created_root_stat) != 0) {
504dd286b0dSDag-Erling Smørgrav warn("%s", to.p_path);
505dd286b0dSDag-Erling Smørgrav (void)fts_set(ftsp, curr, FTS_SKIP);
506dd286b0dSDag-Erling Smørgrav badcp = rval = 1;
507dd286b0dSDag-Erling Smørgrav break;
508848263aaSKyle Evans }
509dd286b0dSDag-Erling Smørgrav if (root_stat == NULL)
510dd286b0dSDag-Erling Smørgrav root_stat = &created_root_stat;
5114b88c807SRodney W. Grimes } else if (!S_ISDIR(to_stat.st_mode)) {
512dd286b0dSDag-Erling Smørgrav warnc(ENOTDIR, "%s", to.p_path);
513dd286b0dSDag-Erling Smørgrav (void)fts_set(ftsp, curr, FTS_SKIP);
514dd286b0dSDag-Erling Smørgrav badcp = rval = 1;
515dd286b0dSDag-Erling Smørgrav break;
5164b88c807SRodney W. Grimes }
5174b88c807SRodney W. Grimes /*
51862de071bSStephen McKay * Arrange to correct directory attributes later
5194a5db7acSStephen McKay * (in the post-order phase) if this is a new
52062de071bSStephen McKay * directory, or if the -p flag is in effect.
5214b88c807SRodney W. Grimes */
52262de071bSStephen McKay curr->fts_number = pflag || dne;
5234b88c807SRodney W. Grimes break;
5244b88c807SRodney W. Grimes case S_IFBLK:
5254b88c807SRodney W. Grimes case S_IFCHR:
5267ea2450fSBryan Drewery if (Rflag && !sflag) {
5274b88c807SRodney W. Grimes if (copy_special(curr->fts_statp, !dne))
5280efa2040SMichael Haro badcp = rval = 1;
529439b2b1eSBruce Evans } else {
5304b88c807SRodney W. Grimes if (copy_file(curr, dne))
5310efa2040SMichael Haro badcp = rval = 1;
532439b2b1eSBruce Evans }
5334b88c807SRodney W. Grimes break;
5346fa36377SJulian Elischer case S_IFSOCK:
5356fa36377SJulian Elischer warnx("%s is a socket (not copied).",
5366fa36377SJulian Elischer curr->fts_path);
5379fa5f90fSEdward Tomasz Napierala break;
5384b88c807SRodney W. Grimes case S_IFIFO:
5397ea2450fSBryan Drewery if (Rflag && !sflag) {
5404b88c807SRodney W. Grimes if (copy_fifo(curr->fts_statp, !dne))
5410efa2040SMichael Haro badcp = rval = 1;
542439b2b1eSBruce Evans } else {
5434b88c807SRodney W. Grimes if (copy_file(curr, dne))
5440efa2040SMichael Haro badcp = rval = 1;
545439b2b1eSBruce Evans }
5464b88c807SRodney W. Grimes break;
5474b88c807SRodney W. Grimes default:
5484b88c807SRodney W. Grimes if (copy_file(curr, dne))
5490efa2040SMichael Haro badcp = rval = 1;
5504b88c807SRodney W. Grimes break;
5514b88c807SRodney W. Grimes }
5520efa2040SMichael Haro if (vflag && !badcp)
553fcb2f1b3SMichael Haro (void)printf("%s -> %s\n", curr->fts_path, to.p_path);
5544b88c807SRodney W. Grimes }
5554b88c807SRodney W. Grimes if (errno)
5564b88c807SRodney W. Grimes err(1, "fts_read");
557867e6cafSMaxim Konovalov fts_close(ftsp);
558848263aaSKyle Evans free(recurse_path);
5594b88c807SRodney W. Grimes return (rval);
5604b88c807SRodney W. Grimes }
5614b88c807SRodney W. Grimes
56200d321a2SMatthew N. Dodd static void
siginfo(int sig __unused)5634901f51bSBruce Evans siginfo(int sig __unused)
56400d321a2SMatthew N. Dodd {
56500d321a2SMatthew N. Dodd
56600d321a2SMatthew N. Dodd info = 1;
56700d321a2SMatthew N. Dodd }
568