mirror of
https://github.com/neovim/neovim.git
synced 2026-07-09 19:09:39 +00:00
fix(path): open swapfile when using device path #39982
Problem: Can't open swapfile when using a device path starting with `//?/`, because `?` is a reserved char on Widnows. Solution: For device UNC paths, replace `//?/UNC/` and `//./UNC/` with `//`. For other device paths, just strip their prefix (i.e. `//?/`, `//./`). This aligns swapfile naming for device paths with regular UNC and DOS paths. Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
This commit is contained in:
@@ -1425,11 +1425,22 @@ char *make_percent_swname(char *dir, char *dir_end, const char *name)
|
||||
FUNC_ATTR_NONNULL_ARG(1, 2)
|
||||
{
|
||||
String fixed_fname;
|
||||
fixed_fname.data = fix_fname(name != NULL ? name : "");
|
||||
if (fixed_fname.data == NULL) {
|
||||
char *fname = fix_fname(name != NULL ? name : "");
|
||||
if (fname == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
FileInfo file_info;
|
||||
if (!os_fileinfo2(fname, &file_info)) {
|
||||
xfree(fname);
|
||||
return NULL;
|
||||
}
|
||||
fixed_fname.data = fname + file_info.root_off;
|
||||
if (file_info.type == kPathDeviceUNC) {
|
||||
assert(file_info.root_off >= 2);
|
||||
fixed_fname.data -= 2;
|
||||
fixed_fname.data[0] = '/'; // Fixup //?/UNC/server/ path: "C/server/..." -> "//server/..."
|
||||
}
|
||||
char *p;
|
||||
for (p = fixed_fname.data; *p != NUL; MB_PTR_ADV(p)) {
|
||||
if (vim_ispathsep(*p)) {
|
||||
@@ -1442,7 +1453,7 @@ char *make_percent_swname(char *dir, char *dir_end, const char *name)
|
||||
p = &dir_end[-1];
|
||||
*p = NUL;
|
||||
String d = concat_fnames(cbuf_as_string(dir, (size_t)(p - dir)), fixed_fname, true);
|
||||
xfree(fixed_fname.data);
|
||||
xfree(fname);
|
||||
|
||||
return d.data;
|
||||
}
|
||||
|
||||
@@ -1188,6 +1188,40 @@ bool os_fileinfo(const char *path, FileInfo *file_info)
|
||||
return os_stat(path, &(file_info->stat)) == kLibuvSuccess;
|
||||
}
|
||||
|
||||
/// Identify Windows path type and provide root offset for now, examples:
|
||||
/// - "//?/C:/foo" => type is `kPathDevice` root offset is 4
|
||||
/// - "//?/UNC/localhost/C$/foo" => type is `kPathDeviceUNC`, root offset is 8
|
||||
///
|
||||
/// TODO(ntdiary): Could be extended for path.c cleanup and path normalization
|
||||
/// logic. Eventually merge this with `os_fileinfo`
|
||||
///
|
||||
/// TODO(justinmk): return value is meaningless currently.
|
||||
bool os_fileinfo2(const char *path, FileInfo *info)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
CLEAR_POINTER(info);
|
||||
#ifdef MSWIN
|
||||
size_t len = strlen(path);
|
||||
if (len < 4) {
|
||||
return true;
|
||||
}
|
||||
if (vim_ispathsep_nocolon(path[0])
|
||||
&& vim_ispathsep_nocolon(path[1])
|
||||
&& (path[2] == '?' || path[2] == '.')
|
||||
&& vim_ispathsep_nocolon(path[3])) {
|
||||
if (len >= 8 && vim_strnicmp_asc(path + 4, "unc/", 4) == 0) {
|
||||
info->root_off = 8;
|
||||
info->type = kPathDeviceUNC;
|
||||
} else {
|
||||
info->root_off = 4;
|
||||
info->type = kPathDevice;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Get the file information for a given path without following links
|
||||
///
|
||||
/// @param path Path to the file.
|
||||
|
||||
@@ -2,9 +2,24 @@
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
/// Struct which encapsulates stat information.
|
||||
/// Currently supports Windows and is extensible.
|
||||
/// @see https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats
|
||||
typedef enum {
|
||||
kPathUnknown = 0,
|
||||
kPathDOS, ///< C:/foo/bar
|
||||
kPathUNC, ///< //server/share/foo/bar
|
||||
kPathDevice, ///< //?/C:/foo/bar or //?/Volume{xxx}/foo/bar
|
||||
kPathDeviceUNC, ///< //?/UNC/server/share/foo/bar
|
||||
} PathType;
|
||||
|
||||
/// Struct which encapsulates file stat and path information.
|
||||
typedef struct {
|
||||
uv_stat_t stat; ///< @private
|
||||
|
||||
/// Offset of the root component after any path type prefix. Currently
|
||||
/// only used for Widnows device paths. e.g. "//?/" and "//?/UNC/"
|
||||
size_t root_off;
|
||||
PathType type;
|
||||
} FileInfo;
|
||||
|
||||
/// Struct which encapsulates inode/dev_id information.
|
||||
|
||||
@@ -118,6 +118,49 @@ describe('startup', function()
|
||||
command('filetype detect')
|
||||
eq('filetype detection:ON plugin:OFF indent:OFF', exec_capture('filetype'))
|
||||
end)
|
||||
|
||||
it('opens swapfile when using device path #31606', function()
|
||||
t.skip(not is_os('win'), 'N/A: Windows feature')
|
||||
local cwd = vim.fs.normalize(vim.uv.cwd())
|
||||
local drive, path = cwd:sub(1, 1), cwd:sub(3)
|
||||
local swapdir = ('%s/Xtest_swap_dir'):format(cwd)
|
||||
local dos_path = ('%s/file.txt'):format(cwd)
|
||||
local device_quest_path = ('//?/%s'):format(dos_path)
|
||||
clear({
|
||||
args = {
|
||||
'--embed',
|
||||
'--headless',
|
||||
'--cmd',
|
||||
('set directory=%s//'):format(swapdir),
|
||||
'--',
|
||||
device_quest_path,
|
||||
},
|
||||
merge = false,
|
||||
})
|
||||
local escaped_name = ('%s/%s.swp'):format(swapdir, dos_path:gsub('[:/]', '%%'))
|
||||
eq(escaped_name, fn.swapname('%'))
|
||||
|
||||
command('bw!')
|
||||
api.nvim_buf_set_name(0, dos_path)
|
||||
eq(escaped_name, fn.swapname('%'))
|
||||
|
||||
command('bw!')
|
||||
local device_dot_path = ('//./%s'):format(dos_path)
|
||||
api.nvim_buf_set_name(0, device_dot_path)
|
||||
eq(escaped_name, fn.swapname('%'))
|
||||
|
||||
command('bw!')
|
||||
local device_unc_path = ('//?/UNC/localhost/%s$%s/file.txt'):format(drive, path)
|
||||
api.nvim_buf_set_name(0, device_unc_path)
|
||||
eq(('%s/%%%s.swp'):format(swapdir, device_unc_path:sub(8):gsub('/', '%%')), fn.swapname('%'))
|
||||
|
||||
command('bw!')
|
||||
-- Volume GUID form, like \\?\Volume{d4857377-2ac4-45d8-a4cb-9b2e447fd02e}\
|
||||
local guid = vim.fs.normalize(vim.trim(vim.fn.system(('mountvol %s: /L'):format(drive))))
|
||||
local device_guid_path = ('%s%s/file.txt'):format(guid, path:sub(2))
|
||||
api.nvim_buf_set_name(0, device_guid_path)
|
||||
eq(escaped_name, fn.swapname('%'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('startup', function()
|
||||
|
||||
Reference in New Issue
Block a user