From da7e50bbd843a9278c57fc1f90cb4cbbd5fba169 Mon Sep 17 00:00:00 2001 From: Bartosz Sosnowski Date: Tue, 28 Apr 2020 18:06:04 +0200 Subject: [PATCH] win: remove MAX_PATH limitations Since Windows 10 1607 some WinApi functions no longer have a MAX_PATH limit on the filenames length. This removes this hard-coded path length limit from various places in libuv, switching to dynamically allocating string buffers. Fixes: https://github.com/libuv/libuv/issues/2331 PR-URL: https://github.com/libuv/libuv/pull/2788 Reviewed-By: Colin Ihrig --- src/win/fs-event.c | 33 +++++++++--- src/win/util.c | 126 +++++++++++++++++++++++++++++++-------------- 2 files changed, 114 insertions(+), 45 deletions(-) diff --git a/src/win/fs-event.c b/src/win/fs-event.c index acf8e110..0126c5ed 100644 --- a/src/win/fs-event.c +++ b/src/win/fs-event.c @@ -83,6 +83,7 @@ static void uv_relative_path(const WCHAR* filename, static int uv_split_path(const WCHAR* filename, WCHAR** dir, WCHAR** file) { size_t len, i; + DWORD dir_len; if (filename == NULL) { if (dir != NULL) @@ -97,12 +98,16 @@ static int uv_split_path(const WCHAR* filename, WCHAR** dir, if (i == 0) { if (dir) { - *dir = (WCHAR*)uv__malloc((MAX_PATH + 1) * sizeof(WCHAR)); + dir_len = GetCurrentDirectoryW(0, NULL); + if (dir_len == 0) { + return -1; + } + *dir = (WCHAR*)uv__malloc(dir_len * sizeof(WCHAR)); if (!*dir) { uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc"); } - if (!GetCurrentDirectoryW(MAX_PATH, *dir)) { + if (!GetCurrentDirectoryW(dir_len, *dir)) { uv__free(*dir); *dir = NULL; return -1; @@ -155,9 +160,11 @@ int uv_fs_event_start(uv_fs_event_t* handle, int name_size, is_path_dir, size; DWORD attr, last_error; WCHAR* dir = NULL, *dir_to_watch, *pathw = NULL; - WCHAR short_path_buffer[MAX_PATH]; + DWORD short_path_buffer_len; + WCHAR *short_path_buffer; WCHAR* short_path, *long_path; + short_path = NULL; if (uv__is_active(handle)) return UV_EINVAL; @@ -230,13 +237,23 @@ int uv_fs_event_start(uv_fs_event_t* handle, */ /* Convert to short path. */ + short_path_buffer = NULL; + short_path_buffer_len = GetShortPathNameW(pathw, NULL, 0); + if (short_path_buffer_len == 0) { + goto short_path_done; + } + short_path_buffer = uv__malloc(short_path_buffer_len * sizeof(WCHAR)); + if (short_path_buffer == NULL) { + goto short_path_done; + } if (GetShortPathNameW(pathw, short_path_buffer, - ARRAY_SIZE(short_path_buffer))) { - short_path = short_path_buffer; - } else { - short_path = NULL; + short_path_buffer_len) == 0) { + uv__free(short_path_buffer); + short_path_buffer = NULL; } +short_path_done: + short_path = short_path_buffer; if (uv_split_path(pathw, &dir, &handle->filew) != 0) { last_error = GetLastError(); @@ -346,6 +363,8 @@ error: if (uv__is_active(handle)) uv__handle_stop(handle); + uv__free(short_path); + return uv_translate_sys_error(last_error); } diff --git a/src/win/util.c b/src/win/util.c index ff8a28b4..ef5e1ccb 100644 --- a/src/win/util.c +++ b/src/win/util.c @@ -154,20 +154,26 @@ int uv_exepath(char* buffer, size_t* size_ptr) { int uv_cwd(char* buffer, size_t* size) { DWORD utf16_len; - WCHAR utf16_buffer[MAX_PATH]; + WCHAR *utf16_buffer; int r; if (buffer == NULL || size == NULL) { return UV_EINVAL; } - utf16_len = GetCurrentDirectoryW(MAX_PATH, utf16_buffer); + utf16_len = GetCurrentDirectoryW(0, NULL); if (utf16_len == 0) { return uv_translate_sys_error(GetLastError()); - } else if (utf16_len > MAX_PATH) { - /* This should be impossible; however the CRT has a code path to deal with - * this scenario, so I added a check anyway. */ - return UV_EIO; + } + utf16_buffer = uv__malloc(utf16_len * sizeof(WCHAR)); + if (utf16_buffer == NULL) { + return UV_ENOMEM; + } + + utf16_len = GetCurrentDirectoryW(utf16_len, utf16_buffer); + if (utf16_len == 0) { + uv__free(utf16_buffer); + return uv_translate_sys_error(GetLastError()); } /* utf16_len contains the length, *not* including the terminating null. */ @@ -191,8 +197,10 @@ int uv_cwd(char* buffer, size_t* size) { NULL, NULL); if (r == 0) { + uv__free(utf16_buffer); return uv_translate_sys_error(GetLastError()); } else if (r > (int) *size) { + uv__free(utf16_buffer); *size = r; return UV_ENOBUFS; } @@ -206,6 +214,8 @@ int uv_cwd(char* buffer, size_t* size) { *size > INT_MAX ? INT_MAX : (int) *size, NULL, NULL); + uv__free(utf16_buffer); + if (r == 0) { return uv_translate_sys_error(GetLastError()); } @@ -216,43 +226,61 @@ int uv_cwd(char* buffer, size_t* size) { int uv_chdir(const char* dir) { - WCHAR utf16_buffer[MAX_PATH]; - size_t utf16_len; + WCHAR *utf16_buffer; + size_t utf16_len, new_utf16_len; WCHAR drive_letter, env_var[4]; if (dir == NULL) { return UV_EINVAL; } + utf16_len = MultiByteToWideChar(CP_UTF8, + 0, + dir, + -1, + NULL, + 0); + if (utf16_len == 0) { + return uv_translate_sys_error(GetLastError()); + } + utf16_buffer = uv__malloc(utf16_len * sizeof(WCHAR)); + if (utf16_buffer == NULL) { + return UV_ENOMEM; + } + if (MultiByteToWideChar(CP_UTF8, 0, dir, -1, utf16_buffer, - MAX_PATH) == 0) { - DWORD error = GetLastError(); - /* The maximum length of the current working directory is 260 chars, - * including terminating null. If it doesn't fit, the path name must be too - * long. */ - if (error == ERROR_INSUFFICIENT_BUFFER) { - return UV_ENAMETOOLONG; - } else { - return uv_translate_sys_error(error); - } + utf16_len) == 0) { + uv__free(utf16_buffer); + return uv_translate_sys_error(GetLastError()); } if (!SetCurrentDirectoryW(utf16_buffer)) { + uv__free(utf16_buffer); return uv_translate_sys_error(GetLastError()); } /* 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. */ - utf16_len = GetCurrentDirectoryW(MAX_PATH, utf16_buffer); + new_utf16_len = GetCurrentDirectoryW(utf16_len, utf16_buffer); + if (new_utf16_len > utf16_len ) { + uv__free(utf16_buffer); + utf16_buffer = uv__malloc(new_utf16_len * sizeof(WCHAR)); + if (utf16_buffer == NULL) { + /* 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; + } + new_utf16_len = GetCurrentDirectoryW(new_utf16_len, utf16_buffer); + } if (utf16_len == 0) { - return uv_translate_sys_error(GetLastError()); - } else if (utf16_len > MAX_PATH) { - return UV_EIO; + uv__free(utf16_buffer); + return 0; } /* The returned directory should not have a trailing slash, unless it points @@ -284,11 +312,10 @@ int uv_chdir(const char* dir) { env_var[2] = L':'; env_var[3] = L'\0'; - if (!SetEnvironmentVariableW(env_var, utf16_buffer)) { - return uv_translate_sys_error(GetLastError()); - } + SetEnvironmentVariableW(env_var, utf16_buffer); } + uv__free(utf16_buffer); return 0; } @@ -1167,20 +1194,29 @@ int uv_os_homedir(char* buffer, size_t* size) { int uv_os_tmpdir(char* buffer, size_t* size) { - wchar_t path[MAX_PATH + 2]; + wchar_t *path; DWORD bufsize; size_t len; if (buffer == NULL || size == NULL || *size == 0) return UV_EINVAL; - len = GetTempPathW(ARRAY_SIZE(path), path); - + len = 0; + len = GetTempPathW(0, NULL); if (len == 0) { return uv_translate_sys_error(GetLastError()); - } else if (len > ARRAY_SIZE(path)) { - /* This should not be possible */ - return UV_EIO; + } + /* Include space for terminating null char. */ + len += 1; + path = uv__malloc(len * sizeof(wchar_t)); + if (path == NULL) { + return UV_ENOMEM; + } + len = GetTempPathW(len, path); + + if (len == 0) { + uv__free(path); + return uv_translate_sys_error(GetLastError()); } /* The returned directory should not have a trailing slash, unless it points @@ -1195,8 +1231,10 @@ int uv_os_tmpdir(char* buffer, size_t* size) { bufsize = WideCharToMultiByte(CP_UTF8, 0, path, -1, NULL, 0, NULL, NULL); if (bufsize == 0) { + uv__free(path); return uv_translate_sys_error(GetLastError()); } else if (bufsize > *size) { + uv__free(path); *size = bufsize; return UV_ENOBUFS; } @@ -1210,6 +1248,7 @@ int uv_os_tmpdir(char* buffer, size_t* size) { *size, NULL, NULL); + uv__free(path); if (bufsize == 0) return uv_translate_sys_error(GetLastError()); @@ -1329,7 +1368,7 @@ int uv__convert_utf8_to_utf16(const char* utf8, int utf8len, WCHAR** utf16) { int uv__getpwuid_r(uv_passwd_t* pwd) { HANDLE token; wchar_t username[UNLEN + 1]; - wchar_t path[MAX_PATH]; + wchar_t *path; DWORD bufsize; int r; @@ -1340,15 +1379,24 @@ int uv__getpwuid_r(uv_passwd_t* pwd) { if (OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &token) == 0) return uv_translate_sys_error(GetLastError()); - bufsize = ARRAY_SIZE(path); + bufsize = 0; + GetUserProfileDirectoryW(token, NULL, &bufsize); + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + r = GetLastError(); + CloseHandle(token); + return uv_translate_sys_error(r); + } + + path = uv__malloc(bufsize * sizeof(wchar_t)); + if (path == NULL) { + CloseHandle(token); + return UV_ENOMEM; + } + if (!GetUserProfileDirectoryW(token, path, &bufsize)) { r = GetLastError(); CloseHandle(token); - - /* This should not be possible */ - if (r == ERROR_INSUFFICIENT_BUFFER) - return UV_ENOMEM; - + uv__free(path); return uv_translate_sys_error(r); } @@ -1358,6 +1406,7 @@ int uv__getpwuid_r(uv_passwd_t* pwd) { bufsize = ARRAY_SIZE(username); if (!GetUserNameW(username, &bufsize)) { r = GetLastError(); + uv__free(path); /* This should not be possible */ if (r == ERROR_INSUFFICIENT_BUFFER) @@ -1368,6 +1417,7 @@ int uv__getpwuid_r(uv_passwd_t* pwd) { pwd->homedir = NULL; r = uv__convert_utf16_to_utf8(path, -1, &pwd->homedir); + uv__free(path); if (r != 0) return r;