win: handle win32 namespaces
This commit introduces a comprehensive path parser that handles:
- Win32 file namespace: \?\C:\path
- Win32 device namespace: \.\C:
- NT namespace: \??\C:\path
- UNC paths: \server\share and \?\UNC\server\share
- Volume GUID paths: \?\Volume{...}
- Special paths: \?\GLOBALROOT\...
The implementation also adds validation for:
- NULL and empty input strings
- Path length limits (32767 characters for Win32 namespace)
- Forward slash rejection in Win32 namespaces (which don't support normalization)
- Explicit rejection of GLOBALROOT paths
This commit is contained in:
parent
a944c422cc
commit
62c8aadc4f
@ -706,7 +706,8 @@ if(LIBUV_BUILD_TESTS)
|
||||
test/test-udp-reuseport.c
|
||||
test/test-uname.c
|
||||
test/test-walk-handles.c
|
||||
test/test-watcher-cross-stop.c)
|
||||
test/test-watcher-cross-stop.c
|
||||
test/test-win32-namespaces.c)
|
||||
|
||||
add_executable(uv_run_tests ${uv_test_sources} uv_win_longpath.manifest)
|
||||
target_compile_definitions(uv_run_tests
|
||||
|
||||
@ -331,7 +331,8 @@ test_run_tests_SOURCES = test/blackhole-server.c \
|
||||
test/test-udp-reuseport.c \
|
||||
test/test-uname.c \
|
||||
test/test-walk-handles.c \
|
||||
test/test-watcher-cross-stop.c
|
||||
test/test-watcher-cross-stop.c \
|
||||
test/test-win32-namespaces.c
|
||||
test_run_tests_LDADD = libuv.la
|
||||
|
||||
if WINNT
|
||||
|
||||
164
src/win/util.c
164
src/win/util.c
@ -207,17 +207,157 @@ int uv_cwd(char* buffer, size_t* size) {
|
||||
}
|
||||
|
||||
|
||||
/* Helper function to check if path contains forward slashes */
|
||||
static int uv__path_has_forward_slash(const WCHAR* path, size_t len) {
|
||||
size_t i;
|
||||
for (i = 0; i < len; i++) {
|
||||
if (path[i] == L'/')
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Helper function to check if path uses Win32 namespace prefix */
|
||||
static int uv__is_win32_namespace(const WCHAR* path, size_t len) {
|
||||
return len >= 4 &&
|
||||
path[0] == L'\\' &&
|
||||
path[1] == L'\\' &&
|
||||
(path[2] == L'?' || path[2] == L'.') &&
|
||||
path[3] == L'\\';
|
||||
}
|
||||
|
||||
|
||||
/* Helper function to check if path uses NT namespace prefix */
|
||||
static int uv__is_nt_namespace(const WCHAR* path, size_t len) {
|
||||
return len >= 4 &&
|
||||
path[0] == L'\\' &&
|
||||
path[1] == L'?' &&
|
||||
path[2] == L'?' &&
|
||||
path[3] == L'\\';
|
||||
}
|
||||
|
||||
|
||||
/* Helper function to check if path is UNC after namespace prefix */
|
||||
static int uv__is_unc_after_prefix(const WCHAR* path, size_t len) {
|
||||
/* Need "UNC\" pattern: at least 4 chars (UNC + backslash) */
|
||||
if (len < 4)
|
||||
return 0;
|
||||
|
||||
/* Check if starts with "UNC" (case-insensitive) followed by backslash */
|
||||
return _wcsnicmp(path, L"UNC", 3) == 0 && path[3] == L'\\';
|
||||
}
|
||||
|
||||
|
||||
/* Helper function to extract drive letter from various path formats */
|
||||
static WCHAR uv__extract_drive_letter(const WCHAR* path, size_t len) {
|
||||
const WCHAR* scan = path;
|
||||
size_t scan_len = len;
|
||||
|
||||
/* Handle Win32 namespace: \\?\ or \\.\ */
|
||||
if (uv__is_win32_namespace(scan, scan_len)) {
|
||||
scan += 4;
|
||||
scan_len -= 4;
|
||||
|
||||
/* Check for UNC path: \\?\UNC\server\share */
|
||||
if (uv__is_unc_after_prefix(scan, scan_len))
|
||||
return 0; /* No drive letter in UNC paths */
|
||||
|
||||
/* Check for device paths: \\.\COM1, \\.\PhysicalDrive0 */
|
||||
if (path[2] == L'.') {
|
||||
/* Device namespace paths don't have drive letters we care about */
|
||||
if (scan_len < 2 || scan[1] != L':')
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check for Volume GUID: \\?\Volume{...} */
|
||||
if (scan_len >= 7 &&
|
||||
_wcsnicmp(scan, L"VOLUME", 6) == 0 &&
|
||||
scan[6] == L'{')
|
||||
return 0; /* Volume GUIDs don't have traditional drive letters */
|
||||
|
||||
/* Check for GLOBALROOT and other special paths */
|
||||
if (scan_len >= 10 &&
|
||||
_wcsnicmp(scan, L"GLOBALROOT", 10) == 0)
|
||||
return 0;
|
||||
}
|
||||
/* Handle NT namespace: \??\ */
|
||||
else if (uv__is_nt_namespace(scan, scan_len)) {
|
||||
scan += 4;
|
||||
scan_len -= 4;
|
||||
|
||||
/* Check for UNC: \??\UNC\server\share */
|
||||
if (uv__is_unc_after_prefix(scan, scan_len))
|
||||
return 0;
|
||||
}
|
||||
/* Handle regular UNC: \\server\share */
|
||||
else if (scan_len >= 2 &&
|
||||
scan[0] == L'\\' &&
|
||||
scan[1] == L'\\') {
|
||||
return 0; /* UNC path */
|
||||
}
|
||||
|
||||
/* Now check for drive letter: X: */
|
||||
if (scan_len >= 2 && scan[1] == L':') {
|
||||
WCHAR letter = scan[0];
|
||||
|
||||
/* Validate and normalize to uppercase */
|
||||
if (letter >= L'A' && letter <= L'Z')
|
||||
return letter;
|
||||
else if (letter >= L'a' && letter <= L'z')
|
||||
return letter - L'a' + L'A';
|
||||
}
|
||||
|
||||
return 0; /* No valid drive letter found */
|
||||
}
|
||||
|
||||
|
||||
int uv_chdir(const char* dir) {
|
||||
WCHAR *utf16_buffer;
|
||||
DWORD utf16_len;
|
||||
WCHAR drive_letter, env_var[4];
|
||||
int r;
|
||||
|
||||
if (dir == NULL || dir[0] == '\0')
|
||||
return UV_EINVAL;
|
||||
|
||||
/* Convert to UTF-16 */
|
||||
r = uv__convert_utf8_to_utf16(dir, &utf16_buffer);
|
||||
if (r)
|
||||
return r;
|
||||
|
||||
/* Validate path length (32767 is the maximum for Win32 namespace) */
|
||||
utf16_len = wcslen(utf16_buffer);
|
||||
if (utf16_len > 32767) {
|
||||
uv__free(utf16_buffer);
|
||||
return UV_ENAMETOOLONG;
|
||||
}
|
||||
|
||||
/* Win32 namespace paths (\\?\ and \\.\) do not support forward slashes.
|
||||
* They require exact path strings and do not perform any normalization.
|
||||
* Regular paths and UNC paths can handle forward slashes as Windows
|
||||
* normalizes them to backslashes.
|
||||
*/
|
||||
if (uv__is_win32_namespace(utf16_buffer, utf16_len)) {
|
||||
/* Skip prefix for further checks */
|
||||
const WCHAR* check_path = utf16_buffer + 4;
|
||||
size_t check_len = utf16_len - 4;
|
||||
|
||||
/* Explicitly reject GLOBALROOT paths */
|
||||
if (check_len >= 11 &&
|
||||
_wcsnicmp(check_path, L"GLOBALROOT", 10) == 0 &&
|
||||
check_path[10] == L'\\') {
|
||||
uv__free(utf16_buffer);
|
||||
return UV_EINVAL;
|
||||
}
|
||||
|
||||
/* Forward slash check */
|
||||
if (uv__path_has_forward_slash(utf16_buffer + 4, utf16_len - 4)) {
|
||||
uv__free(utf16_buffer);
|
||||
return UV_EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SetCurrentDirectoryW(utf16_buffer)) {
|
||||
uv__free(utf16_buffer);
|
||||
return uv_translate_sys_error(GetLastError());
|
||||
@ -241,19 +381,17 @@ int uv_chdir(const char* dir) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if (utf16_len < 2 || utf16_buffer[1] != L':') {
|
||||
/* Doesn't look like a drive letter could be there - probably an UNC path.
|
||||
* TODO: Need to handle win32 namespaces like \\?\C:\ ? */
|
||||
drive_letter = 0;
|
||||
} else if (utf16_buffer[0] >= L'A' && utf16_buffer[0] <= L'Z') {
|
||||
drive_letter = utf16_buffer[0];
|
||||
} else if (utf16_buffer[0] >= L'a' && utf16_buffer[0] <= L'z') {
|
||||
/* Convert to uppercase. */
|
||||
drive_letter = utf16_buffer[0] - L'a' + L'A';
|
||||
} else {
|
||||
/* Not valid. */
|
||||
drive_letter = 0;
|
||||
}
|
||||
/* Extract drive letter from the current working directory path.
|
||||
* This handles various Windows namespace formats:
|
||||
* - Regular paths: C:\Windows
|
||||
* - Win32 file namespace: \\?\C:\LongPath
|
||||
* - Win32 device namespace: \\.\C:
|
||||
* - NT namespace: \??\C:\Path
|
||||
* - UNC paths: \\server\share (no drive letter)
|
||||
* - Volume GUIDs: \\?\Volume{...} (no drive letter)
|
||||
* - Special paths: \\?\GLOBALROOT\... (no drive letter)
|
||||
*/
|
||||
drive_letter = uv__extract_drive_letter(utf16_buffer, utf16_len);
|
||||
|
||||
if (drive_letter != 0) {
|
||||
/* Construct the environment variable name and set it. */
|
||||
|
||||
@ -593,6 +593,19 @@ TEST_DECLARE (metrics_idle_time)
|
||||
TEST_DECLARE (metrics_idle_time_thread)
|
||||
TEST_DECLARE (metrics_idle_time_zero)
|
||||
|
||||
#ifdef _WIN32
|
||||
TEST_DECLARE(chdir_win32_namespace)
|
||||
TEST_DECLARE(chdir_unc_paths)
|
||||
TEST_DECLARE(chdir_forward_slash_rejection)
|
||||
TEST_DECLARE(chdir_path_too_long)
|
||||
TEST_DECLARE(chdir_volume_guid_path)
|
||||
TEST_DECLARE(chdir_globalroot_path)
|
||||
TEST_DECLARE(chdir_nt_namespace)
|
||||
TEST_DECLARE(chdir_case_insensitive)
|
||||
TEST_DECLARE(chdir_drive_env_variable_update)
|
||||
TEST_DECLARE(chdir_device_paths)
|
||||
#endif
|
||||
|
||||
TASK_LIST_START
|
||||
TEST_ENTRY_CUSTOM (platform_output, 0, 1, 5000)
|
||||
|
||||
@ -1265,6 +1278,19 @@ TASK_LIST_START
|
||||
TEST_ENTRY (metrics_idle_time_thread)
|
||||
TEST_ENTRY (metrics_idle_time_zero)
|
||||
|
||||
#ifdef _WIN32
|
||||
TEST_ENTRY(chdir_win32_namespace)
|
||||
TEST_ENTRY(chdir_unc_paths)
|
||||
TEST_ENTRY(chdir_forward_slash_rejection)
|
||||
TEST_ENTRY(chdir_path_too_long)
|
||||
TEST_ENTRY(chdir_volume_guid_path)
|
||||
TEST_ENTRY(chdir_globalroot_path)
|
||||
TEST_ENTRY(chdir_nt_namespace)
|
||||
TEST_ENTRY(chdir_case_insensitive)
|
||||
TEST_ENTRY(chdir_drive_env_variable_update)
|
||||
TEST_ENTRY(chdir_device_paths)
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
/* These are for testing the test runner. */
|
||||
TEST_ENTRY (fail_always)
|
||||
|
||||
430
test/test-win32-namespaces.c
Normal file
430
test/test-win32-namespaces.c
Normal file
@ -0,0 +1,430 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "uv.h"
|
||||
#include "task.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Helper to get current drive letter */
|
||||
static char get_current_drive(void) {
|
||||
char cwd[MAX_PATH];
|
||||
size_t cwd_size = sizeof(cwd);
|
||||
if (uv_cwd(cwd, &cwd_size) != 0)
|
||||
return 0;
|
||||
if (cwd[0] >= 'A' && cwd[0] <= 'Z')
|
||||
return cwd[0];
|
||||
if (cwd[0] >= 'a' && cwd[0] <= 'z')
|
||||
return cwd[0] - 'a' + 'A';
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Helper to check if a path exists and is accessible */
|
||||
static int path_exists(const char* path) {
|
||||
WCHAR wpath[32768]; /* Max path length for Win32 namespace */
|
||||
DWORD attrs;
|
||||
int len;
|
||||
|
||||
/* Convert UTF-8 to UTF-16 using Windows API */
|
||||
len = MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, 32768);
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
attrs = GetFileAttributesW(wpath);
|
||||
|
||||
return attrs != INVALID_FILE_ATTRIBUTES &&
|
||||
(attrs & FILE_ATTRIBUTE_DIRECTORY);
|
||||
}
|
||||
|
||||
|
||||
/* Helper to get environment variable value (drive-local path) */
|
||||
static int get_drive_env(char drive, char* buf, size_t buflen) {
|
||||
char env_name[4];
|
||||
env_name[0] = '=';
|
||||
env_name[1] = drive;
|
||||
env_name[2] = ':';
|
||||
env_name[3] = '\0';
|
||||
|
||||
DWORD result = GetEnvironmentVariableA(env_name, buf, (DWORD)buflen);
|
||||
return result > 0 && result < buflen;
|
||||
}
|
||||
|
||||
|
||||
TEST_IMPL(chdir_win32_namespace) {
|
||||
char original_cwd[1024];
|
||||
size_t original_cwd_size = sizeof(original_cwd);
|
||||
char test_path[1024];
|
||||
char expected_path[1024];
|
||||
char env_value[1024];
|
||||
char drive;
|
||||
int r;
|
||||
|
||||
/* Save original working directory */
|
||||
r = uv_cwd(original_cwd, &original_cwd_size);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
drive = get_current_drive();
|
||||
ASSERT_NE(drive, 0);
|
||||
|
||||
/* Test 1: Win32 namespace path \\?\C:\Windows */
|
||||
snprintf(test_path, sizeof(test_path), "\\\\?\\%c:\\Windows", drive);
|
||||
if (path_exists(test_path)) {
|
||||
r = uv_chdir(test_path);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
/* Verify the drive-local environment variable is set */
|
||||
ASSERT_EQ(get_drive_env(drive, env_value, sizeof(env_value)), 1);
|
||||
|
||||
/* The env value should contain the current path (check substring) */
|
||||
snprintf(expected_path, sizeof(expected_path), "\\\\?\\%c:\\Windows", drive);
|
||||
ASSERT(strstr(env_value, "Windows") != NULL);
|
||||
}
|
||||
|
||||
/* Test 2: Win32 namespace with lowercase drive letter */
|
||||
snprintf(test_path, sizeof(test_path), "\\\\?\\%c:\\Windows", drive + 32);
|
||||
if (path_exists(test_path)) {
|
||||
r = uv_chdir(test_path);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
/* Environment variable should use uppercase drive letter */
|
||||
ASSERT_EQ(get_drive_env(drive, env_value, sizeof(env_value)), 1);
|
||||
}
|
||||
|
||||
/* Test 3: Device namespace \\.\C: (if accessible) */
|
||||
snprintf(test_path, sizeof(test_path), "\\\\.\\%c:", drive);
|
||||
r = uv_chdir(test_path);
|
||||
/* May fail with access denied, which is OK */
|
||||
if (r == 0) {
|
||||
ASSERT_EQ(get_drive_env(drive, env_value, sizeof(env_value)), 1);
|
||||
}
|
||||
|
||||
/* Restore original directory */
|
||||
r = uv_chdir(original_cwd);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
TEST_IMPL(chdir_unc_paths) {
|
||||
char original_cwd[1024];
|
||||
size_t original_cwd_size = sizeof(original_cwd);
|
||||
char test_path[1024];
|
||||
char drive;
|
||||
int r;
|
||||
|
||||
/* Save original working directory */
|
||||
r = uv_cwd(original_cwd, &original_cwd_size);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
drive = get_current_drive();
|
||||
ASSERT_NE(drive, 0);
|
||||
|
||||
char old_env[1024];
|
||||
int had_env = get_drive_env(drive, old_env, sizeof(old_env));
|
||||
|
||||
/* Test 1: UNC path with Win32 namespace \\?\UNC\localhost\C$ */
|
||||
snprintf(test_path, sizeof(test_path), "\\\\?\\UNC\\localhost\\%c$", drive);
|
||||
r = uv_chdir(test_path);
|
||||
|
||||
if (r == 0) {
|
||||
char new_env[1024];
|
||||
int has_env = get_drive_env(drive, new_env, sizeof(new_env));
|
||||
|
||||
if (had_env && has_env) {
|
||||
ASSERT_STR_EQ(old_env, new_env);
|
||||
}
|
||||
|
||||
char cwd[1024];
|
||||
size_t cwd_size = sizeof(cwd);
|
||||
r = uv_cwd(cwd, &cwd_size);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
/* Verify we're actually in a UNC path */
|
||||
ASSERT(strncmp(cwd, "\\\\", 2) == 0 || strncmp(cwd, "//", 2) == 0);
|
||||
} else {
|
||||
/* Access denied or path not found is acceptable for UNC paths */
|
||||
ASSERT(r == UV_EACCES || r == UV_ENOENT || r == UV_EINVAL);
|
||||
}
|
||||
|
||||
/* Test 2: Regular UNC path \\localhost\C$ */
|
||||
snprintf(test_path, sizeof(test_path), "\\\\localhost\\%c$", drive);
|
||||
r = uv_chdir(test_path);
|
||||
/* May fail, which is acceptable */
|
||||
|
||||
/* Restore original directory */
|
||||
r = uv_chdir(original_cwd);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
TEST_IMPL(chdir_forward_slash_rejection) {
|
||||
char test_path[256];
|
||||
char drive;
|
||||
int r;
|
||||
|
||||
drive = get_current_drive();
|
||||
ASSERT_NE(drive, 0);
|
||||
|
||||
/* Test 1: Win32 namespace with forward slash should fail */
|
||||
snprintf(test_path, sizeof(test_path), "\\\\?\\%c:/Windows", drive);
|
||||
r = uv_chdir(test_path);
|
||||
ASSERT_EQ(r, UV_EINVAL);
|
||||
|
||||
/* Test 2: Win32 namespace with mixed slashes should fail */
|
||||
snprintf(test_path, sizeof(test_path), "\\\\?\\%c:\\Windows/System32", drive);
|
||||
r = uv_chdir(test_path);
|
||||
ASSERT_EQ(r, UV_EINVAL);
|
||||
|
||||
/* Test 3: Device namespace with forward slash should fail */
|
||||
snprintf(test_path, sizeof(test_path), "\\\\.\\%c:/", drive);
|
||||
r = uv_chdir(test_path);
|
||||
ASSERT_EQ(r, UV_EINVAL);
|
||||
|
||||
/* Test 4: Regular path with forward slash should succeed (Windows normalizes) */
|
||||
snprintf(test_path, sizeof(test_path), "%c:/Windows", drive);
|
||||
r = uv_chdir(test_path);
|
||||
/* Should succeed if path exists, or fail with ENOENT, but NOT EINVAL */
|
||||
ASSERT_NE(r, UV_EINVAL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
TEST_IMPL(chdir_path_too_long) {
|
||||
char* long_path;
|
||||
int r;
|
||||
size_t i;
|
||||
char drive = get_current_drive();
|
||||
ASSERT_NE(drive, 0);
|
||||
|
||||
/* Create a path longer than 32767 characters */
|
||||
long_path = malloc(35000);
|
||||
ASSERT(long_path != NULL);
|
||||
|
||||
/* Build: \\?\C:\ + many repetitions of "LongDirectory\" */
|
||||
snprintf(long_path, 100, "\\\\?\\%c:\\", drive);
|
||||
size_t pos = strlen(long_path);
|
||||
|
||||
for (i = 0; i < 2500; i++) {
|
||||
strcpy(long_path + pos, "LongDirectory\\");
|
||||
pos += 14;
|
||||
if (pos > 33000) break;
|
||||
}
|
||||
long_path[pos] = '\0';
|
||||
|
||||
/* This should fail with UV_ENAMETOOLONG */
|
||||
r = uv_chdir(long_path);
|
||||
ASSERT_EQ(r, UV_ENAMETOOLONG);
|
||||
|
||||
free(long_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
TEST_IMPL(chdir_volume_guid_path) {
|
||||
/* Note: This test may not work on all systems as volume GUIDs are dynamic.
|
||||
* We're testing that the code handles them correctly without crashing. */
|
||||
char original_cwd[1024];
|
||||
size_t original_cwd_size = sizeof(original_cwd);
|
||||
int r;
|
||||
|
||||
/* Save original working directory */
|
||||
r = uv_cwd(original_cwd, &original_cwd_size);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
/* Try a Volume GUID path (will likely fail with ENOENT, which is fine) */
|
||||
r = uv_chdir("\\\\?\\Volume{12345678-1234-1234-1234-123456789012}\\");
|
||||
|
||||
/* Should fail with ENOENT or EINVAL, but should NOT crash */
|
||||
ASSERT(r == UV_ENOENT || r == UV_EINVAL || r == UV_EACCES);
|
||||
|
||||
/* Verify we're still in the original directory */
|
||||
char current_cwd[1024];
|
||||
size_t current_cwd_size = sizeof(current_cwd);
|
||||
r = uv_cwd(current_cwd, ¤t_cwd_size);
|
||||
ASSERT_EQ(r, 0);
|
||||
ASSERT_STR_EQ(current_cwd, original_cwd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
TEST_IMPL(chdir_globalroot_path) {
|
||||
char original_cwd[1024];
|
||||
size_t original_cwd_size = sizeof(original_cwd);
|
||||
int r;
|
||||
|
||||
/* Save original working directory */
|
||||
r = uv_cwd(original_cwd, &original_cwd_size);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
/* Try a GLOBALROOT path (will likely fail, which is fine) */
|
||||
r = uv_chdir("\\\\?\\GLOBALROOT\\Device\\HarddiskVolume1");
|
||||
|
||||
/* Should fail with ENOENT or EINVAL or EACCES, but should NOT crash */
|
||||
ASSERT(r == UV_ENOENT || r == UV_EINVAL || r == UV_EACCES);
|
||||
|
||||
/* Verify we're still in the original directory */
|
||||
char current_cwd[1024];
|
||||
size_t current_cwd_size = sizeof(current_cwd);
|
||||
r = uv_cwd(current_cwd, ¤t_cwd_size);
|
||||
ASSERT_EQ(r, 0);
|
||||
ASSERT_STR_EQ(current_cwd, original_cwd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
TEST_IMPL(chdir_nt_namespace) {
|
||||
char original_cwd[1024];
|
||||
size_t original_cwd_size = sizeof(original_cwd);
|
||||
char test_path[256];
|
||||
char env_value[1024];
|
||||
char drive;
|
||||
int r;
|
||||
|
||||
/* Save original working directory */
|
||||
r = uv_cwd(original_cwd, &original_cwd_size);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
drive = get_current_drive();
|
||||
ASSERT_NE(drive, 0);
|
||||
|
||||
/* Test NT namespace path \??\C:\Windows */
|
||||
snprintf(test_path, sizeof(test_path), "\\??\\%c:\\Windows", drive);
|
||||
r = uv_chdir(test_path);
|
||||
|
||||
if (r == 0) {
|
||||
/* Verify the drive-local environment variable is set */
|
||||
ASSERT_EQ(get_drive_env(drive, env_value, sizeof(env_value)), 1);
|
||||
ASSERT(strstr(env_value, "Windows") != NULL);
|
||||
} else {
|
||||
/* May fail with access issues, which is acceptable */
|
||||
ASSERT(r == UV_ENOENT || r == UV_EINVAL || r == UV_EACCES);
|
||||
}
|
||||
|
||||
/* Restore original directory */
|
||||
r = uv_chdir(original_cwd);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
TEST_IMPL(chdir_case_insensitive) {
|
||||
char original_cwd[1024];
|
||||
size_t original_cwd_size = sizeof(original_cwd);
|
||||
char test_path_upper[256];
|
||||
char test_path_lower[256];
|
||||
char test_path_mixed[256];
|
||||
char env_value[1024];
|
||||
char drive;
|
||||
int r;
|
||||
|
||||
/* Save original working directory */
|
||||
r = uv_cwd(original_cwd, &original_cwd_size);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
drive = get_current_drive();
|
||||
ASSERT_NE(drive, 0);
|
||||
|
||||
/* Test uppercase drive */
|
||||
snprintf(test_path_upper, sizeof(test_path_upper), "%c:\\Windows", drive);
|
||||
if (path_exists(test_path_upper)) {
|
||||
r = uv_chdir(test_path_upper);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
/* Environment variable should always use uppercase */
|
||||
ASSERT_EQ(get_drive_env(drive, env_value, sizeof(env_value)), 1);
|
||||
}
|
||||
|
||||
/* Test lowercase drive */
|
||||
snprintf(test_path_lower, sizeof(test_path_lower), "%c:\\Windows", drive + 32);
|
||||
if (path_exists(test_path_lower)) {
|
||||
r = uv_chdir(test_path_lower);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
/* Environment variable should STILL use uppercase */
|
||||
ASSERT_EQ(get_drive_env(drive, env_value, sizeof(env_value)), 1);
|
||||
}
|
||||
|
||||
/* Test with Win32 namespace and lowercase */
|
||||
snprintf(test_path_mixed, sizeof(test_path_mixed), "\\\\?\\%c:\\Windows",
|
||||
drive + 32);
|
||||
if (path_exists(test_path_mixed)) {
|
||||
r = uv_chdir(test_path_mixed);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
/* Environment variable should use uppercase */
|
||||
ASSERT_EQ(get_drive_env(drive, env_value, sizeof(env_value)), 1);
|
||||
}
|
||||
|
||||
/* Restore original directory */
|
||||
r = uv_chdir(original_cwd);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
TEST_IMPL(chdir_drive_env_variable_update) {
|
||||
char original_cwd[1024];
|
||||
size_t original_cwd_size = sizeof(original_cwd);
|
||||
char test_path_1[256];
|
||||
char test_path_2[256];
|
||||
char env_value_1[1024];
|
||||
char env_value_2[1024];
|
||||
char drive;
|
||||
int r;
|
||||
|
||||
/* Save original working directory */
|
||||
r = uv_cwd(original_cwd, &original_cwd_size);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
drive = get_current_drive();
|
||||
ASSERT_NE(drive, 0);
|
||||
|
||||
/* Change to Windows directory */
|
||||
snprintf(test_path_1, sizeof(test_path_1), "%c:\\Windows", drive);
|
||||
if (path_exists(test_path_1)) {
|
||||
r = uv_chdir(test_path_1);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
/* Get environment variable value */
|
||||
ASSERT_EQ(get_drive_env(drive, env_value_1, sizeof(env_value_1)), 1);
|
||||
ASSERT(strstr(env_value_1, "Windows") != NULL);
|
||||
|
||||
/* Change to System32 subdirectory */
|
||||
snprintf(test_path_2, sizeof(test_path_2), "%c:\\Windows\\System32", drive);
|
||||
if (path_exists(test_path_2)) {
|
||||
r = uv_chdir(test_path_2);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
/* Environment variable should be updated */
|
||||
ASSERT_EQ(get_drive_env(drive, env_value_2, sizeof(env_value_2)), 1);
|
||||
ASSERT(strstr(env_value_2, "System32") != NULL);
|
||||
|
||||
/* Values should be different */
|
||||
ASSERT_STR_NE(env_value_1, env_value_2);
|
||||
}
|
||||
}
|
||||
|
||||
/* Restore original directory */
|
||||
r = uv_chdir(original_cwd);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEST_IMPL(chdir_device_paths) {
|
||||
/* These should fail gracefully, not crash */
|
||||
ASSERT_NE(uv_chdir("\\\\.\\COM1"), 0);
|
||||
ASSERT_NE(uv_chdir("\\\\.\\PhysicalDrive0"), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
Loading…
Reference in New Issue
Block a user