diff options
author | zeertzjq <zeertzjq@outlook.com> | 2024-05-17 18:39:01 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-17 18:39:01 +0800 |
commit | 42aa69b076cb338e20b5b4656771f1873e8930d8 (patch) | |
tree | cc1b82eb2e91c93c78af88a9feb62889833848b8 | |
parent | 10f917351906ca2d3c61a6b9bd8b8ae88a26ed8f (diff) | |
download | rneovim-42aa69b076cb338e20b5b4656771f1873e8930d8.tar.gz rneovim-42aa69b076cb338e20b5b4656771f1873e8930d8.tar.bz2 rneovim-42aa69b076cb338e20b5b4656771f1873e8930d8.zip |
fix(path): avoid chdir() when resolving path (#28799)
Use uv_fs_realpath() instead.
It seems that uv_fs_realpath() has some problems on non-Linux platforms:
- macOS and other BSDs: this function will fail with UV_ELOOP if more
than 32 symlinks are found while resolving the given path. This limit
is hardcoded and cannot be sidestepped.
- Windows: while this function works in the common case, there are a
number of corner cases where it doesn't:
- Paths in ramdisk volumes created by tools which sidestep the Volume
Manager (such as ImDisk) cannot be resolved.
- Inconsistent casing when using drive letters.
- Resolved path bypasses subst'd drives.
Ref: https://docs.libuv.org/en/v1.x/fs.html#c.uv_fs_realpath
I don't know if the old implementation that uses uv_chdir() and uv_cwd()
also suffers from the same problems.
- For the ELOOP case, chdir() seems to have the same limitations.
- On Windows, Vim doesn't use anything like chdir() either. It uses
_wfullpath(), while libuv uses GetFinalPathNameByHandleW().
-rw-r--r-- | src/nvim/eval/funcs.c | 4 | ||||
-rw-r--r-- | src/nvim/os/env.c | 14 | ||||
-rw-r--r-- | src/nvim/os/fs.c | 14 | ||||
-rw-r--r-- | src/nvim/path.c | 43 | ||||
-rw-r--r-- | test/unit/path_spec.lua | 60 |
5 files changed, 72 insertions, 63 deletions
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index b1ee33929c..1fc80e88c4 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -6479,7 +6479,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) char *v = os_resolve_shortcut(fname); if (v == NULL) { if (os_is_reparse_point_include(fname)) { - v = os_realpath(fname, v); + v = os_realpath(fname, NULL, MAXPATHL + 1); } } rettv->vval.v_string = (v == NULL ? xstrdup(fname) : v); @@ -6631,7 +6631,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) xfree(buf); } # else - char *v = os_realpath(fname, NULL); + char *v = os_realpath(fname, NULL, MAXPATHL + 1); rettv->vval.v_string = v == NULL ? xstrdup(fname) : v; # endif #endif diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index fbc3a243a6..5a79004c41 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -474,17 +474,9 @@ void init_homedir(void) var = os_homedir(); } - if (var != NULL) { - // Change to the directory and get the actual path. This resolves - // links. Don't do it when we can't return. - if (os_dirname(os_buf, MAXPATHL) == OK && os_chdir(os_buf) == 0) { - if (!os_chdir(var) && os_dirname(IObuff, IOSIZE) == OK) { - var = IObuff; - } - if (os_chdir(os_buf) != 0) { - emsg(_(e_prev_dir)); - } - } + // Get the actual path. This resolves links. + if (var != NULL && os_realpath(var, IObuff, IOSIZE) != NULL) { + var = IObuff; } // Fall back to current working directory if home is not found diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 2eca906d4e..19bdf30311 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -1320,22 +1320,22 @@ bool os_fileid_equal_fileinfo(const FileID *file_id, const FileInfo *file_info) /// Return the canonicalized absolute pathname. /// /// @param[in] name Filename to be canonicalized. -/// @param[out] buf Buffer to store the canonicalized values. A minimum length -// of MAXPATHL+1 is required. If it is NULL, memory is -// allocated. In that case, the caller should deallocate this -// buffer. +/// @param[out] buf Buffer to store the canonicalized values. +/// If it is NULL, memory is allocated. In that case, the caller +/// should deallocate this buffer. +/// @param[in] len The length of the buffer. /// /// @return pointer to the buf on success, or NULL. -char *os_realpath(const char *name, char *buf) +char *os_realpath(const char *name, char *buf, size_t len) FUNC_ATTR_NONNULL_ARG(1) { uv_fs_t request; int result = uv_fs_realpath(NULL, &request, name, NULL); if (result == kLibuvSuccess) { if (buf == NULL) { - buf = xmallocz(MAXPATHL); + buf = xmalloc(len); } - xstrlcpy(buf, request.ptr, MAXPATHL + 1); + xstrlcpy(buf, request.ptr, len); } uv_fs_req_cleanup(&request); return result == kLibuvSuccess ? buf : NULL; diff --git a/src/nvim/path.c b/src/nvim/path.c index f7e8b2b65c..96330e000b 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -2273,13 +2273,20 @@ bool match_suffix(char *fname) /// @return `FAIL` for failure, `OK` for success. int path_full_dir_name(char *directory, char *buffer, size_t len) { - int SUCCESS = 0; - int retval = OK; - if (strlen(directory) == 0) { return os_dirname(buffer, len); } + if (os_realpath(directory, buffer, len) != NULL) { + return OK; + } + + // Path does not exist (yet). For a full path fail, will use the path as-is. + if (path_is_absolute(directory)) { + return FAIL; + } + // For a relative path use the current directory and append the file name. + char old_dir[MAXPATHL]; // Get current directory name. @@ -2287,36 +2294,12 @@ int path_full_dir_name(char *directory, char *buffer, size_t len) return FAIL; } - // We have to get back to the current dir at the end, check if that works. - if (os_chdir(old_dir) != SUCCESS) { + xstrlcpy(buffer, old_dir, len); + if (append_path(buffer, directory, len) == FAIL) { return FAIL; } - if (os_chdir(directory) != SUCCESS) { - // Path does not exist (yet). For a full path fail, - // will use the path as-is. For a relative path use - // the current directory and append the file name. - if (path_is_absolute(directory)) { - // Do not return immediately since we may be in the wrong directory. - retval = FAIL; - } else { - xstrlcpy(buffer, old_dir, len); - if (append_path(buffer, directory, len) == FAIL) { - retval = FAIL; - } - } - } else if (os_dirname(buffer, len) == FAIL) { - // Do not return immediately since we are in the wrong directory. - retval = FAIL; - } - - if (os_chdir(old_dir) != SUCCESS) { - // That shouldn't happen, since we've tested if it works. - retval = FAIL; - emsg(_(e_prev_dir)); - } - - return retval; + return OK; } // Append to_append to path with a slash in between. diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua index 44919368bf..6f6a80f44e 100644 --- a/test/unit/path_spec.lua +++ b/test/unit/path_spec.lua @@ -22,11 +22,16 @@ local buffer = nil describe('path.c', function() describe('path_full_dir_name', function() + local old_dir + setup(function() + old_dir = uv.cwd() mkdir('unit-test-directory') + uv.fs_symlink(old_dir .. '/unit-test-directory', 'unit-test-symlink') end) teardown(function() + uv.fs_unlink('unit-test-symlink') uv.fs_rmdir('unit-test-directory') end) @@ -37,35 +42,64 @@ describe('path.c', function() before_each(function() -- Create empty string buffer which will contain the resulting path. - length = string.len(uv.cwd()) + 22 + length = string.len(old_dir) + 22 buffer = cstr(length, '') end) + after_each(function() + uv.chdir(old_dir) + end) + itp('returns the absolute directory name of a given relative one', function() - local result = path_full_dir_name('..', buffer, length) - eq(OK, result) - local old_dir = uv.cwd() + eq(OK, path_full_dir_name('..', buffer, length)) uv.chdir('..') local expected = uv.cwd() uv.chdir(old_dir) - eq(expected, (ffi.string(buffer))) + eq(expected, ffi.string(buffer)) end) itp('returns the current directory name if the given string is empty', function() - eq(OK, (path_full_dir_name('', buffer, length))) - eq(uv.cwd(), (ffi.string(buffer))) + eq(OK, path_full_dir_name('', buffer, length)) + eq(old_dir, ffi.string(buffer)) + end) + + local function test_full_dir_absolute() + itp('works with a normal absolute dir', function() + eq(OK, path_full_dir_name(old_dir .. '/unit-test-directory', buffer, length)) + eq(old_dir .. '/unit-test-directory', ffi.string(buffer)) + end) + + itp('works with a symlinked absolute dir', function() + eq(OK, path_full_dir_name(old_dir .. '/unit-test-symlink', buffer, length)) + eq(old_dir .. '/unit-test-directory', ffi.string(buffer)) + end) + end + + test_full_dir_absolute() + + describe('when cwd does not exist #28786', function() + before_each(function() + mkdir('dir-to-remove') + uv.chdir('dir-to-remove') + uv.fs_rmdir(old_dir .. '/dir-to-remove') + end) + + test_full_dir_absolute() end) itp('works with a normal relative dir', function() - local result = path_full_dir_name('unit-test-directory', buffer, length) - eq(uv.cwd() .. '/unit-test-directory', (ffi.string(buffer))) - eq(OK, result) + eq(OK, path_full_dir_name('unit-test-directory', buffer, length)) + eq(old_dir .. '/unit-test-directory', ffi.string(buffer)) + end) + + itp('works with a symlinked relative dir', function() + eq(OK, path_full_dir_name('unit-test-symlink', buffer, length)) + eq(old_dir .. '/unit-test-directory', ffi.string(buffer)) end) itp('works with a non-existing relative dir', function() - local result = path_full_dir_name('does-not-exist', buffer, length) - eq(uv.cwd() .. '/does-not-exist', (ffi.string(buffer))) - eq(OK, result) + eq(OK, path_full_dir_name('does-not-exist', buffer, length)) + eq(old_dir .. '/does-not-exist', ffi.string(buffer)) end) itp('fails with a non-existing absolute dir', function() |