commit - 377624f7f3328486605f0c0ca78abc398440bdbe
commit + 8ba819a3547825c0e0d657a7e41610da16f6cd4f
blob - 54c7093a4d0bc68cfd3ef1939b7c80f71106e777
blob + b0dceed9f15dbec807d84c8744cbe9fce22faa76
--- include/got_object.h
+++ include/got_object.h
const struct got_error *got_object_blob_read_block(size_t *,
struct got_blob_object *);
+/* Rewind an open blob's data stream back to the beginning. */
+void got_object_blob_rewind(struct got_blob_object *);
+
/*
* Read the entire content of a blob and write it to the specified file.
* Flush and rewind the file as well. Indicate the amount of bytes
blob - 9a3beff5204483df08335c4ff3dfe9f49015cf50
blob + 0138ca96e984ed90c59ea99cf3e066a60af6138b
--- lib/object.c
+++ lib/object.c
return err;
}
+void
+got_object_blob_rewind(struct got_blob_object *blob)
+{
+ if (blob->f)
+ rewind(blob->f);
+}
+
char *
got_object_blob_id_str(struct got_blob_object *blob, char *buf, size_t size)
{
blob - 985b0d77e0e47f32bdbe60268d8184e7df1a8e47
blob + 1f4dfd3657f769e5b82c587069c4f33f57427be0
--- lib/worktree.c
+++ lib/worktree.c
return (st_mode & ~(S_IXUSR | S_IXGRP | S_IXOTH));
}
+/* forward declaration */
static const struct got_error *
install_blob(struct got_worktree *worktree, const char *ondisk_path,
const char *path, mode_t te_mode, mode_t st_mode,
struct got_blob_object *blob, int restoring_missing_file,
int reverting_versioned_file, struct got_repository *repo,
+ got_worktree_checkout_cb progress_cb, void *progress_arg);
+
+static const struct got_error *
+install_symlink(struct got_worktree *worktree, const char *ondisk_path,
+ const char *path, mode_t te_mode, mode_t st_mode,
+ struct got_blob_object *blob, int restoring_missing_file,
+ int reverting_versioned_file, struct got_repository *repo,
got_worktree_checkout_cb progress_cb, void *progress_arg)
{
const struct got_error *err = NULL;
+ char target_path[PATH_MAX];
+ size_t len, target_len = 0;
+ char *resolved_path = NULL, *abspath = NULL;
+ const uint8_t *buf = got_object_blob_get_read_buf(blob);
+ size_t hdrlen = got_object_blob_get_hdrlen(blob);
+
+ /*
+ * Blob object content specifies the target path of the link.
+ * If a symbolic link cannot be installed we instead create
+ * a regular file which contains the link target path stored
+ * in the blob object.
+ */
+ do {
+ err = got_object_blob_read_block(&len, blob);
+ if (len + target_len >= sizeof(target_path)) {
+ /* Path too long; install as a regular file. */
+ got_object_blob_rewind(blob);
+ return install_blob(worktree, ondisk_path, path,
+ GOT_DEFAULT_FILE_MODE, st_mode, blob,
+ restoring_missing_file, reverting_versioned_file,
+ repo, progress_cb, progress_arg);
+ }
+ if (len > 0) {
+ /* Skip blob object header first time around. */
+ memcpy(target_path + target_len, buf + hdrlen,
+ len - hdrlen);
+ target_len += len - hdrlen;
+ hdrlen = 0;
+ }
+ } while (len != 0);
+ target_path[target_len] = '\0';
+
+ /*
+ * 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 *parent = dirname(ondisk_path);
+ if (asprintf(&abspath, "%s/%s", parent, target_path) == -1) {
+ 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)
+ return got_error_from_errno2("realpath", target_path);
+ }
+
+ /* Only allow symlinks pointing at paths within the work tree. */
+ if (!got_path_is_child(resolved_path ? resolved_path : target_path,
+ worktree->root_path, strlen(worktree->root_path))) {
+ /* install as a regular file */
+ got_object_blob_rewind(blob);
+ err = install_blob(worktree, ondisk_path, path,
+ GOT_DEFAULT_FILE_MODE, st_mode, blob,
+ restoring_missing_file, reverting_versioned_file,
+ repo, progress_cb, progress_arg);
+ goto done;
+ }
+
+ if (symlink(target_path, ondisk_path) == -1) {
+ if (errno == ENOENT) {
+ char *parent = dirname(ondisk_path);
+ if (parent == NULL) {
+ err = got_error_from_errno2("dirname",
+ ondisk_path);
+ goto done;
+ }
+ err = add_dir_on_disk(worktree, parent);
+ if (err)
+ goto done;
+ /*
+ * Retry, and fall through to error handling
+ * below if this second attempt fails.
+ */
+ if (symlink(target_path, ondisk_path) != -1) {
+ err = NULL; /* success */
+ goto done;
+ }
+ }
+
+ /* Handle errors from first or second creation attempt. */
+ if (errno == EEXIST) {
+ struct stat sb;
+ ssize_t elen;
+ char etarget[PATH_MAX];
+ if (lstat(ondisk_path, &sb) == -1) {
+ err = got_error_from_errno2("lstat",
+ ondisk_path);
+ goto done;
+ }
+ if (!S_ISLNK(sb.st_mode)) {
+ err = got_error_path(ondisk_path,
+ GOT_ERR_FILE_OBSTRUCTED);
+ goto done;
+ }
+ elen = readlink(ondisk_path, etarget, sizeof(etarget));
+ if (elen == -1) {
+ err = got_error_from_errno2("readlink",
+ ondisk_path);
+ goto done;
+ }
+ if (elen == target_len &&
+ memcmp(etarget, target_path, target_len) == 0)
+ err = NULL;
+ else
+ err = got_error_path(ondisk_path,
+ GOT_ERR_FILE_OBSTRUCTED);
+ } else if (errno == ENAMETOOLONG) {
+ /* bad target path; install as a regular file */
+ got_object_blob_rewind(blob);
+ err = install_blob(worktree, ondisk_path, path,
+ GOT_DEFAULT_FILE_MODE, st_mode, blob,
+ restoring_missing_file, reverting_versioned_file,
+ repo, progress_cb, progress_arg);
+ } else if (errno == ENOTDIR) {
+ err = got_error_path(ondisk_path,
+ GOT_ERR_FILE_OBSTRUCTED);
+ } else {
+ err = got_error_from_errno3("symlink",
+ target_path, ondisk_path);
+ }
+ }
+done:
+ free(resolved_path);
+ free(abspath);
+ return err;
+}
+
+static const struct got_error *
+install_blob(struct got_worktree *worktree, const char *ondisk_path,
+ const char *path, mode_t te_mode, mode_t st_mode,
+ struct got_blob_object *blob, int restoring_missing_file,
+ int reverting_versioned_file, struct got_repository *repo,
+ got_worktree_checkout_cb progress_cb, void *progress_arg)
+{
+ const struct got_error *err = NULL;
int fd = -1;
size_t len, hdrlen;
int update = 0;
char *tmppath = NULL;
+
+ if (S_ISLNK(te_mode))
+ return install_symlink(worktree, ondisk_path, path, te_mode,
+ st_mode, blob, restoring_missing_file,
+ reverting_versioned_file, repo, progress_cb, progress_arg);
fd = open(ondisk_path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW,
GOT_DEFAULT_FILE_MODE);
blob - 68a5558798cac114d8be26ae680a0efced072896
blob + 8bb0b0d52dfb1516850a25bf3e9b77204b3ae314
--- regress/cmdline/checkout.sh
+++ regress/cmdline/checkout.sh
echo 'M alpha' > $testroot/stdout.expected
(cd $testroot/wt && got status > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ fi
+ test_done "$testroot" "$ret"
+}
+
+function test_checkout_symlink {
+ local testroot=`test_init 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 && git add .)
+ git_commit $testroot/repo -m "add a symlink"
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ if ! [ -h $testroot/wt/alpha.link ]; then
+ echo "alpha.link is not a symlink"
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ readlink $testroot/wt/alpha.link > $testroot/stdout
+ echo "alpha" > $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
+ if ! [ -h $testroot/wt/epsilon.link ]; then
+ echo "epsilon.link is not a symlink"
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ readlink $testroot/wt/epsilon.link > $testroot/stdout
+ echo "epsilon" > $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
+
+ if [ -h $testroot/wt/passwd.link ]; then
+ echo -n "passwd.link symlink points outside of work tree: " >&2
+ readlink $testroot/wt/passwd.link >&2
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ echo -n "/etc/passwd" > $testroot/content.expected
+ cp $testroot/wt/passwd.link $testroot/content
+
+ cmp -s $testroot/content.expected $testroot/content
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/content.expected $testroot/content
+ fi
test_done "$testroot" "$ret"
+
}
run_test test_checkout_basic
run_test test_checkout_ignores_submodules
run_test test_checkout_read_only
run_test test_checkout_into_nonempty_dir
+run_test test_checkout_symlink