diff --git a/bin/cp/cp.c b/bin/cp/cp.c --- a/bin/cp/cp.c +++ b/bin/cp/cp.c @@ -292,6 +292,7 @@ static int copy(char *argv[], enum op type, int fts_options, struct stat *root_stat) { + char rootname[NAME_MAX]; struct stat created_root_stat, to_stat; FTS *ftsp; FTSENT *curr; @@ -328,45 +329,15 @@ ; } - if (curr->fts_info == FTS_D && type != FILE_TO_FILE && - root_stat != NULL && - root_stat->st_dev == curr->fts_statp->st_dev && - root_stat->st_ino == curr->fts_statp->st_ino) { - assert(recurse_path == NULL); - if (curr->fts_level > FTS_ROOTLEVEL) { - /* - * If the recursion isn't at the immediate - * level, we can just not traverse into this - * directory. - */ - fts_set(ftsp, curr, FTS_SKIP); - continue; - } else { - const char *slash; - - /* - * Grab the last path component and double it, - * to make life easier later and ensure that - * we work even with fts_level == 0 is a couple - * of components deep in fts_path. No path - * separators are fine and expected in the - * common case, though. - */ - slash = strrchr(curr->fts_path, '/'); - if (slash != NULL) - slash++; - else - slash = curr->fts_path; - if (asprintf(&recurse_path, "%s/%s", - curr->fts_path, slash) == -1) - err(1, "asprintf"); - } - } - - if (recurse_path != NULL && - strcmp(curr->fts_path, recurse_path) == 0) { - fts_set(ftsp, curr, FTS_SKIP); - continue; + /* + * Stash the root basename off for detecting recursion later. + * + * This will be essential if the root is a symlink and we're + * rolling with -L or -H. The later bits will need this bit in + * particular. + */ + if (curr->fts_level == FTS_ROOTLEVEL) { + strlcpy(rootname, curr->fts_name, sizeof(rootname)); } /* @@ -422,6 +393,41 @@ to.p_end = target_mid + nlen; *to.p_end = 0; STRIP_TRAILING_SLASH(to); + + /* + * We're on the verge of recursing on ourselves. Either + * we need to stop right here (we knowingly just created + * it), or we will in an immediate descendant. Record + * the path of the immediate descendant to make our + * lives a little less complicated looking. + */ + if (curr->fts_info == FTS_D && root_stat != NULL && + root_stat->st_dev == curr->fts_statp->st_dev && + root_stat->st_ino == curr->fts_statp->st_ino) { + assert(recurse_path == NULL); + + if (root_stat == &created_root_stat) { + /* + * This directory didn't exist when we + * started, we created it as part of + * traversal. Stop right here before we + * do something silly. + */ + fts_set(ftsp, curr, FTS_SKIP); + continue; + } + + + if (asprintf(&recurse_path, "%s/%s", to.p_path, + rootname) == -1) + err(1, "asprintf"); + } + + if (recurse_path != NULL && + strcmp(to.p_path, recurse_path) == 0) { + fts_set(ftsp, curr, FTS_SKIP); + continue; + } } if (curr->fts_info == FTS_DP) { diff --git a/bin/cp/tests/cp_test.sh b/bin/cp/tests/cp_test.sh --- a/bin/cp/tests/cp_test.sh +++ b/bin/cp/tests/cp_test.sh @@ -95,16 +95,25 @@ # Let's do the same thing, except we'll try to recursively copy foo into # one of its subdirectories. mkdir foo + ln -s foo coo echo "qux" > foo/bar - mkdir foo/loo mkdir foo/moo - mkdir foo/roo + touch foo/moo/roo cp foo/bar foo/zoo atf_check cp -R foo foo/moo + atf_check cp -RH coo foo/moo atf_check -o inline:"qux\n" cat foo/moo/foo/bar + atf_check -o inline:"qux\n" cat foo/moo/coo/bar atf_check -o inline:"qux\n" cat foo/moo/foo/zoo - atf_check -e not-empty -s not-exit:0 stat foo/moo/foo/moo + atf_check -o inline:"qux\n" cat foo/moo/coo/zoo + + # We should have copied the contents of foo/moo before foo, coo started + # getting copied in. + atf_check -o not-empty stat foo/moo/foo/moo/roo + atf_check -o not-empty stat foo/moo/coo/moo/roo + atf_check -e not-empty -s not-exit:0 stat foo/moo/foo/moo/foo + atf_check -e not-empty -s not-exit:0 stat foo/moo/coo/moo/coo } atf_test_case matching_srctgt_link