diff --git a/src/win/util.c b/src/win/util.c index 2aa181e29..fc14e4f8b 100644 --- a/src/win/util.c +++ b/src/win/util.c @@ -207,7 +207,6 @@ int uv_cwd(char* buffer, size_t* size) { } -/* 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'\\' && @@ -217,7 +216,6 @@ static int uv__is_win32_namespace(const WCHAR* path, size_t len) { } -/* 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'\\' && @@ -227,80 +225,75 @@ static int uv__is_nt_namespace(const WCHAR* path, size_t len) { } -/* 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 */ + return 0; /* 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 */ + return 0; - /* 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 */ + return 0; } - /* 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 */ + return 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) + */ int uv_chdir(const char* dir) { WCHAR *utf16_buffer; DWORD utf16_len; @@ -310,7 +303,6 @@ int uv_chdir(const char* dir) { 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; @@ -320,38 +312,20 @@ int uv_chdir(const char* dir) { return uv_translate_sys_error(GetLastError()); } - /* uv__cwd() will return a new buffer. */ uv__free(utf16_buffer); utf16_buffer = NULL; - /* Windows stores the drive-local path in an "hidden" environment variable, - * which has the form "=C:=C:\Windows". SetCurrentDirectory does not update - * this, so we'll have to do it. */ r = uv__cwd(&utf16_buffer, &utf16_len); if (r == UV_ENOMEM) { - /* When updating the environment variable fails, return UV_OK anyway. - * We did successfully change current working directory, only updating - * hidden env variable failed. */ return 0; } if (r < 0) { return r; } - /* 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. */ env_var[0] = L'='; env_var[1] = drive_letter; env_var[2] = L':'; diff --git a/test/test-win32-namespaces.c b/test/test-win32-namespaces.c index 64e66e045..fd1ea799a 100644 --- a/test/test-win32-namespaces.c +++ b/test/test-win32-namespaces.c @@ -6,7 +6,6 @@ #include #include -/* Helper to get current drive letter */ static char get_current_drive(void) { char cwd[MAX_PATH]; size_t cwd_size = sizeof(cwd); @@ -20,13 +19,11 @@ static char get_current_drive(void) { } -/* 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 */ + WCHAR wpath[32768]; 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; @@ -38,7 +35,6 @@ static int path_exists(const char* path) { } -/* 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] = '='; @@ -60,46 +56,37 @@ TEST_IMPL(chdir_win32_namespace) { 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); @@ -114,7 +101,6 @@ TEST_IMPL(chdir_unc_paths) { char drive; int r; - /* Save original working directory */ r = uv_cwd(original_cwd, &original_cwd_size); ASSERT_EQ(r, 0); @@ -124,7 +110,6 @@ TEST_IMPL(chdir_unc_paths) { 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); @@ -141,19 +126,14 @@ TEST_IMPL(chdir_unc_paths) { 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); @@ -169,13 +149,10 @@ TEST_IMPL(chdir_volume_guid_path) { drive = get_current_drive(); ASSERT_NE(drive, 0); - /* Capture current drive environment variable */ had_env = get_drive_env(drive, old_env, sizeof(old_env)); - /* Try a Volume GUID path - result doesn't matter */ uv_chdir("\\\\?\\Volume{12345678-1234-1234-1234-123456789012}\\"); - /* The ONLY thing that matters: drive env variable must be unchanged */ char new_env[1024]; int has_env = get_drive_env(drive, new_env, sizeof(new_env)); @@ -199,13 +176,10 @@ TEST_IMPL(chdir_globalroot_path) { drive = get_current_drive(); ASSERT_NE(drive, 0); - /* Capture current drive environment variable */ had_env = get_drive_env(drive, old_env, sizeof(old_env)); - /* Try a GLOBALROOT path - result doesn't matter */ uv_chdir("\\\\?\\GLOBALROOT\\Device\\HarddiskVolume1"); - /* The ONLY thing that matters: drive env variable must be unchanged */ char new_env[1024]; int has_env = get_drive_env(drive, new_env, sizeof(new_env)); @@ -229,27 +203,22 @@ TEST_IMPL(chdir_nt_namespace) { 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); @@ -267,45 +236,37 @@ TEST_IMPL(chdir_case_insensitive) { 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); @@ -323,39 +284,32 @@ TEST_IMPL(chdir_drive_env_variable_update) { 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);