summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/filebuf.c84
1 files changed, 81 insertions, 3 deletions
diff --git a/src/filebuf.c b/src/filebuf.c
index 838f4b4d2..2bbc210ba 100644
--- a/src/filebuf.c
+++ b/src/filebuf.c
@@ -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);