stop using realpath(3) to resolve a symlink target in install_symlink() We should not resolve a symlink target path recursively when installing a symlink in the work tree. We want to handle this symlink's target, not the end result of following a chain of symlinks in case such links already exist.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
diff --git a/lib/worktree.c b/lib/worktree.c
index 2a2d317..cec70da 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -1255,8 +1255,8 @@ install_symlink(int *is_bad_symlink, struct got_worktree *worktree,
{
const struct got_error *err = NULL;
char target_path[PATH_MAX];
+ char canonpath[PATH_MAX];
size_t len, target_len = 0;
- char *resolved_path = NULL, *abspath = NULL;
char *path_got = NULL;
const uint8_t *buf = got_object_blob_get_read_buf(blob);
size_t hdrlen = got_object_blob_get_hdrlen(blob);
@@ -1292,10 +1292,14 @@ install_symlink(int *is_bad_symlink, struct got_worktree *worktree,
target_path[target_len] = '\0';
/*
+ * We do not use realpath(3) to resolve the symlink's target path
+ * because we don't want to resolve symlinks recursively. Instead
+ * we make the path absolute and then canonicalize it.
* Relative symlink target lookup should begin at the directory
* in which the blob object is being installed.
*/
if (!got_path_is_absolute(target_path)) {
+ char *abspath;
char *parent = dirname(ondisk_path);
if (parent == NULL) {
err = got_error_from_errno2("dirname", ondisk_path);
@@ -1305,24 +1309,18 @@ install_symlink(int *is_bad_symlink, struct got_worktree *worktree,
err = got_error_from_errno("asprintf");
goto done;
}
- }
-
- /*
- * unveil(2) restricts our view of paths in the filesystem.
- * ENOENT will occur if a link target path does not exist or
- * if it points outside our unveiled path space.
- */
- resolved_path = realpath(abspath ? abspath : target_path, NULL);
- if (resolved_path == NULL) {
- if (errno != ENOENT) {
- err = got_error_from_errno2("realpath", target_path);
+ err = got_canonpath(abspath, canonpath, sizeof(canonpath));
+ free(abspath);
+ if (err)
+ goto done;
+ } else {
+ err = got_canonpath(target_path, canonpath, sizeof(canonpath));
+ if (err)
goto done;
- }
}
/* Only allow symlinks pointing at paths within the work tree. */
- if (!got_path_is_child(resolved_path ? resolved_path : (abspath ?
- abspath : target_path), worktree->root_path,
+ if (!got_path_is_child(canonpath, worktree->root_path,
strlen(worktree->root_path))) {
/* install as a regular file */
*is_bad_symlink = 1;
@@ -1340,8 +1338,7 @@ install_symlink(int *is_bad_symlink, struct got_worktree *worktree,
err = got_error_from_errno("asprintf");
goto done;
}
- if (got_path_is_child(resolved_path ? resolved_path : (abspath ?
- abspath : target_path), path_got, strlen(path_got))) {
+ if (got_path_is_child(canonpath, path_got, strlen(path_got))) {
/* install as a regular file */
*is_bad_symlink = 1;
got_object_blob_rewind(blob);
@@ -1413,8 +1410,6 @@ install_symlink(int *is_bad_symlink, struct got_worktree *worktree,
err = (*progress_cb)(progress_arg, reverting_versioned_file ?
GOT_STATUS_REVERT : GOT_STATUS_ADD, path);
done:
- free(resolved_path);
- free(abspath);
free(path_got);
return err;
}
diff --git a/regress/cmdline/checkout.sh b/regress/cmdline/checkout.sh
index 4ed5d46..b0bd8db 100755
--- a/regress/cmdline/checkout.sh
+++ b/regress/cmdline/checkout.sh
@@ -509,15 +509,38 @@ function test_checkout_symlink {
(cd $testroot/repo && ln -s alpha alpha.link)
(cd $testroot/repo && ln -s epsilon epsilon.link)
(cd $testroot/repo && ln -s /etc/passwd passwd.link)
+ (cd $testroot/repo && ln -s passwd.link passwd2.link)
(cd $testroot/repo && ln -s ../beta epsilon/beta.link)
(cd $testroot/repo && ln -s nonexistent nonexistent.link)
(cd $testroot/repo && ln -s .got/foo dotgotfoo.link)
(cd $testroot/repo && git add .)
git_commit $testroot/repo -m "add symlinks"
- got checkout $testroot/repo $testroot/wt > /dev/null
+ got checkout $testroot/repo $testroot/wt > $testroot/stdout
ret="$?"
if [ "$ret" != "0" ]; then
+ echo "got checkout failed unexpectedly" >&2
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "A $testroot/wt/alpha" > $testroot/stdout.expected
+ echo "A $testroot/wt/alpha.link" >> $testroot/stdout.expected
+ echo "A $testroot/wt/beta" >> $testroot/stdout.expected
+ echo "A $testroot/wt/dotgotfoo.link" >> $testroot/stdout.expected
+ echo "A $testroot/wt/epsilon/beta.link" >> $testroot/stdout.expected
+ echo "A $testroot/wt/epsilon/zeta" >> $testroot/stdout.expected
+ echo "A $testroot/wt/epsilon.link" >> $testroot/stdout.expected
+ echo "A $testroot/wt/gamma/delta" >> $testroot/stdout.expected
+ echo "A $testroot/wt/nonexistent.link" >> $testroot/stdout.expected
+ echo "A $testroot/wt/passwd.link" >> $testroot/stdout.expected
+ echo "A $testroot/wt/passwd2.link" >> $testroot/stdout.expected
+ echo "Now shut up and hack" >> $testroot/stdout.expected
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
test_done "$testroot" "$ret"
return 1
fi
@@ -572,6 +595,22 @@ function test_checkout_symlink {
return 1
fi
+ if ! [ -h $testroot/wt/passwd2.link ]; then
+ echo "passwd2.link is not a symlink"
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ readlink $testroot/wt/passwd2.link > $testroot/stdout
+ echo "passwd.link" > $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
readlink $testroot/wt/epsilon/beta.link > $testroot/stdout
echo "../beta" > $testroot/stdout.expected
cmp -s $testroot/stdout.expected $testroot/stdout