mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-06 13:25:18 +00:00
Merge pull request #3413 from libgit2/cmn/follow-symlink
filebuf: follow symlinks when creating a lock file
This commit is contained in:
commit
e1d27bcaaf
@ -7,6 +7,9 @@ v0.23 + 1
|
||||
example `filter=*`. Consumers should examine the attributes parameter
|
||||
of the `check` function for details.
|
||||
|
||||
* Symlinks are now followed when locking a file, which can be
|
||||
necessary when multiple worktrees share a base repository.
|
||||
|
||||
### API additions
|
||||
|
||||
* `git_config_lock()` has been added, which allow for
|
||||
|
@ -191,6 +191,81 @@ static int write_deflate(git_filebuf *file, void *source, size_t len)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define MAX_SYMLINK_DEPTH 5
|
||||
|
||||
static int resolve_symlink(git_buf *out, const char *path)
|
||||
{
|
||||
int i, error, root;
|
||||
ssize_t ret;
|
||||
struct stat st;
|
||||
git_buf curpath = GIT_BUF_INIT, target = GIT_BUF_INIT;
|
||||
|
||||
if ((error = git_buf_grow(&target, GIT_PATH_MAX + 1)) < 0 ||
|
||||
(error = git_buf_puts(&curpath, path)) < 0)
|
||||
return error;
|
||||
|
||||
for (i = 0; i < MAX_SYMLINK_DEPTH; i++) {
|
||||
error = p_lstat(curpath.ptr, &st);
|
||||
if (error < 0 && errno == ENOENT) {
|
||||
error = git_buf_puts(out, curpath.ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (error < 0) {
|
||||
giterr_set(GITERR_OS, "failed to stat '%s'", curpath.ptr);
|
||||
error = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!S_ISLNK(st.st_mode)) {
|
||||
error = git_buf_puts(out, curpath.ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = p_readlink(curpath.ptr, target.ptr, GIT_PATH_MAX);
|
||||
if (ret < 0) {
|
||||
giterr_set(GITERR_OS, "failed to read symlink '%s'", curpath.ptr);
|
||||
error = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (ret == GIT_PATH_MAX) {
|
||||
giterr_set(GITERR_INVALID, "symlink target too long");
|
||||
error = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* readlink(2) won't NUL-terminate for us */
|
||||
target.ptr[ret] = '\0';
|
||||
target.size = ret;
|
||||
|
||||
root = git_path_root(target.ptr);
|
||||
if (root >= 0) {
|
||||
if ((error = git_buf_puts(&curpath, target.ptr)) < 0)
|
||||
goto cleanup;
|
||||
} else {
|
||||
git_buf dir = GIT_BUF_INIT;
|
||||
|
||||
if ((error = git_path_dirname_r(&dir, curpath.ptr)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
git_buf_swap(&curpath, &dir);
|
||||
git_buf_free(&dir);
|
||||
|
||||
if ((error = git_path_apply_relative(&curpath, target.ptr)) < 0)
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
giterr_set(GITERR_INVALID, "maximum symlink depth reached");
|
||||
error = -1;
|
||||
|
||||
cleanup:
|
||||
git_buf_free(&curpath);
|
||||
git_buf_free(&target);
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode)
|
||||
{
|
||||
int compression, error = -1;
|
||||
@ -265,11 +340,14 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode
|
||||
file->path_lock = git_buf_detach(&tmp_path);
|
||||
GITERR_CHECK_ALLOC(file->path_lock);
|
||||
} else {
|
||||
path_len = strlen(path);
|
||||
git_buf resolved_path = GIT_BUF_INIT;
|
||||
|
||||
if ((error = resolve_symlink(&resolved_path, path)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
/* Save the original path of the file */
|
||||
file->path_original = git__strdup(path);
|
||||
GITERR_CHECK_ALLOC(file->path_original);
|
||||
path_len = resolved_path.size;
|
||||
file->path_original = git_buf_detach(&resolved_path);
|
||||
|
||||
/* create the locking path by appending ".lock" to the original */
|
||||
GITERR_CHECK_ALLOC_ADD(&alloc_len, path_len, GIT_FILELOCK_EXTLENGTH);
|
||||
|
@ -151,3 +151,56 @@ void test_core_filebuf__rename_error(void)
|
||||
|
||||
cl_assert_equal_i(false, git_path_exists(test_lock));
|
||||
}
|
||||
|
||||
void test_core_filebuf__symlink_follow(void)
|
||||
{
|
||||
git_filebuf file = GIT_FILEBUF_INIT;
|
||||
const char *dir = "linkdir", *source = "linkdir/link";
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
cl_skip();
|
||||
#endif
|
||||
|
||||
cl_git_pass(p_mkdir(dir, 0777));
|
||||
cl_git_pass(p_symlink("target", source));
|
||||
|
||||
cl_git_pass(git_filebuf_open(&file, source, 0, 0666));
|
||||
cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks"));
|
||||
|
||||
cl_assert_equal_i(true, git_path_exists("linkdir/target.lock"));
|
||||
|
||||
cl_git_pass(git_filebuf_commit(&file));
|
||||
cl_assert_equal_i(true, git_path_exists("linkdir/target"));
|
||||
|
||||
git_filebuf_cleanup(&file);
|
||||
|
||||
/* The second time around, the target file does exist */
|
||||
cl_git_pass(git_filebuf_open(&file, source, 0, 0666));
|
||||
cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks"));
|
||||
|
||||
cl_assert_equal_i(true, git_path_exists("linkdir/target.lock"));
|
||||
|
||||
cl_git_pass(git_filebuf_commit(&file));
|
||||
cl_assert_equal_i(true, git_path_exists("linkdir/target"));
|
||||
|
||||
git_filebuf_cleanup(&file);
|
||||
cl_git_pass(git_futils_rmdir_r(dir, NULL, GIT_RMDIR_REMOVE_FILES));
|
||||
}
|
||||
|
||||
void test_core_filebuf__symlink_depth(void)
|
||||
{
|
||||
git_filebuf file = GIT_FILEBUF_INIT;
|
||||
const char *dir = "linkdir", *source = "linkdir/link";
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
cl_skip();
|
||||
#endif
|
||||
|
||||
cl_git_pass(p_mkdir(dir, 0777));
|
||||
/* Endless loop */
|
||||
cl_git_pass(p_symlink("link", source));
|
||||
|
||||
cl_git_fail(git_filebuf_open(&file, source, 0, 0666));
|
||||
|
||||
cl_git_pass(git_futils_rmdir_r(dir, NULL, GIT_RMDIR_REMOVE_FILES));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user