diff --git a/src/win/fs.c b/src/win/fs.c index 7a1818c56..33bc9da30 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -43,8 +43,6 @@ #define UV_FS_FREE_PTR 0x0008 #define UV_FS_CLEANEDUP 0x0010 -static const int uv__fs_dirent_slide = 0x20; - #define QUEUE_FS_TP_JOB(loop, req) \ do { \ @@ -788,123 +786,203 @@ void fs__mkdtemp(uv_fs_t* req) { void fs__scandir(uv_fs_t* req) { - WCHAR* pathw = req->pathw; - size_t len = wcslen(pathw); - int result; - WCHAR* name; - HANDLE dir; - WIN32_FIND_DATAW ent = { 0 }; - WCHAR* path2; - const WCHAR* fmt; - uv__dirent_t** dents; - int dent_size; + static const size_t dirents_initial_size = 32; - if (len == 0) { - fmt = L"./*"; - } else if (pathw[len - 1] == L'/' || pathw[len - 1] == L'\\') { - fmt = L"%ls*"; - } else { - fmt = L"%ls\\*"; - } + HANDLE dir_handle = INVALID_HANDLE_VALUE; - /* Figure out whether path is a file or a directory. */ - if (!(GetFileAttributesW(pathw) & FILE_ATTRIBUTE_DIRECTORY)) { - req->result = UV_ENOTDIR; - req->sys_errno_ = ERROR_SUCCESS; - return; - } + uv__dirent_t** dirents = NULL; + size_t dirents_size = 0; + size_t dirents_used = 0; - path2 = (WCHAR*)malloc(sizeof(WCHAR) * (len + 4)); - if (!path2) { - SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); - return; - } + IO_STATUS_BLOCK iosb; + NTSTATUS status; - _snwprintf(path2, len + 3, fmt, pathw); - dir = FindFirstFileW(path2, &ent); - free(path2); + /* Buffer to hold directory entries returned by NtQueryDirectoryFile. + * It's important that this buffer can hold at least one entry, regardless + * of the length of the file names present in the enumerated directory. + * A file name is at most 256 WCHARs long. + * According to MSDN, the buffer must be aligned at an 8-byte boundary. + */ + __declspec(align(8)) char buffer[8192]; - if(dir == INVALID_HANDLE_VALUE) { - SET_REQ_WIN32_ERROR(req, GetLastError()); - return; - } + STATIC_ASSERT(sizeof buffer >= + sizeof(FILE_DIRECTORY_INFORMATION) + 256 * sizeof(WCHAR)); - result = 0; - dents = NULL; - dent_size = 0; + /* Open the directory. */ + dir_handle = + CreateFileW(req->pathw, + FILE_LIST_DIRECTORY | SYNCHRONIZE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (dir_handle == INVALID_HANDLE_VALUE) + goto win32_error; - do { - uv__dirent_t* dent; - int utf8_len; + /* Read the first chunk. */ + status = pNtQueryDirectoryFile(dir_handle, + NULL, + NULL, + NULL, + &iosb, + &buffer, + sizeof buffer, + FileDirectoryInformation, + FALSE, + NULL, + TRUE); - name = ent.cFileName; + /* If the handle is not a directory, we'll get STATUS_INVALID_PARAMETER. + * This should be reported back as UV_ENOTDIR. + */ + if (status == STATUS_INVALID_PARAMETER) + goto not_a_directory_error; - if (!(name[0] != L'.' || (name[1] && (name[1] != L'.' || name[2])))) - continue; + while (NT_SUCCESS(status)) { + char* position = buffer; + size_t next_entry_offset = 0; - /* Grow dents buffer, if needed */ - if (result >= dent_size) { - uv__dirent_t** tmp; + do { + FILE_DIRECTORY_INFORMATION* info; + uv__dirent_t* dirent; - dent_size += uv__fs_dirent_slide; - tmp = realloc(dents, dent_size * sizeof(*dents)); - if (tmp == NULL) { - SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); - goto fatal; + size_t wchar_len; + size_t utf8_len; + + /* Obtain a pointer to the current directory entry. */ + position += next_entry_offset; + info = (FILE_DIRECTORY_INFORMATION*) position; + + /* Fetch the offset to the next directory entry. */ + next_entry_offset = info->NextEntryOffset; + + /* Compute the length of the filename in WCHARs. */ + wchar_len = info->FileNameLength / sizeof info->FileName[0]; + + /* Skip over '.' and '..' entries. */ + if (wchar_len == 1 && info->FileName[0] == L'.') + continue; + if (wchar_len == 2 && info->FileName[0] == L'.' && + info->FileName[1] == L'.') + continue; + + /* Compute the space required to store the filename as UTF-8. */ + utf8_len = WideCharToMultiByte( + CP_UTF8, 0, &info->FileName[0], wchar_len, NULL, 0, NULL, NULL); + if (utf8_len == 0) + goto win32_error; + + /* Resize the dirent array if needed. */ + if (dirents_used >= dirents_size) { + size_t new_dirents_size = + dirents_size == 0 ? dirents_initial_size : dirents_size << 1; + uv__dirent_t** new_dirents = + realloc(dirents, new_dirents_size * sizeof *dirents); + + if (new_dirents == NULL) + goto out_of_memory_error; + + dirents_size = new_dirents_size; + dirents = new_dirents; } - dents = tmp; - } - /* Allocate enough space to fit utf8 encoding of file name */ - len = wcslen(name); - utf8_len = uv_utf16_to_utf8(name, len, NULL, 0); - if (!utf8_len) { - SET_REQ_WIN32_ERROR(req, GetLastError()); - goto fatal; - } + /* Allocate space for the uv dirent structure. The dirent structure + * includes room for the first character of the filename, but `utf8_len` + * doesn't count the NULL terminator at this point. + */ + dirent = malloc(sizeof *dirent + utf8_len); + if (dirent == NULL) + goto out_of_memory_error; - dent = malloc(sizeof(*dent) + utf8_len + 1); - if (dent == NULL) { - SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); - goto fatal; - } + dirents[dirents_used++] = dirent; - /* Copy file name */ - utf8_len = uv_utf16_to_utf8(name, len, dent->d_name, utf8_len); - if (!utf8_len) { - free(dent); - SET_REQ_WIN32_ERROR(req, GetLastError()); - goto fatal; - } - dent->d_name[utf8_len] = '\0'; + /* Convert file name to UTF-8. */ + if (WideCharToMultiByte(CP_UTF8, + 0, + &info->FileName[0], + wchar_len, + &dirent->d_name[0], + utf8_len, + NULL, + NULL) == 0) + goto win32_error; - /* Copy file type */ - if ((ent.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) - dent->d_type = UV__DT_DIR; - else if ((ent.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) - dent->d_type = UV__DT_LINK; - else - dent->d_type = UV__DT_FILE; + /* Add a null terminator to the filename. */ + dirent->d_name[utf8_len] = '\0'; - dents[result++] = dent; - } while(FindNextFileW(dir, &ent)); + /* Fill out the type field. */ + if (info->FileAttributes & FILE_ATTRIBUTE_DEVICE) + dirent->d_type = UV__DT_CHAR; + else if (info->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + dirent->d_type = UV__DT_LINK; + else if (info->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) + dirent->d_type = UV__DT_DIR; + else + dirent->d_type = UV__DT_FILE; + } while (next_entry_offset != 0); - FindClose(dir); + /* Read the next chunk. */ + status = pNtQueryDirectoryFile(dir_handle, + NULL, + NULL, + NULL, + &iosb, + &buffer, + sizeof buffer, + FileDirectoryInformation, + FALSE, + NULL, + FALSE); - if (dents != NULL) + /* After the first pNtQueryDirectoryFile call, the function may return + * STATUS_SUCCESS even if the buffer was too small to hold at least one + * directory entry. + */ + if (status == STATUS_SUCCESS && iosb.Information == 0) + status = STATUS_BUFFER_OVERFLOW; + } + + if (status != STATUS_NO_MORE_FILES) + goto nt_error; + + CloseHandle(dir_handle); + + /* Store the result in the request object. */ + req->ptr = dirents; + if (dirents != NULL) req->flags |= UV_FS_FREE_PTR; - /* NOTE: nbufs will be used as index */ + SET_REQ_RESULT(req, dirents_used); + + /* `nbufs` will be used as index by uv_fs_scandir_next. */ req->nbufs = 0; - req->ptr = dents; - SET_REQ_RESULT(req, result); + return; -fatal: - /* Deallocate dents */ - for (result--; result >= 0; result--) - free(dents[result]); - free(dents); +nt_error: + SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(status)); + goto cleanup; + +win32_error: + SET_REQ_WIN32_ERROR(req, GetLastError()); + goto cleanup; + +not_a_directory_error: + SET_REQ_UV_ERROR(req, UV_ENOTDIR, ERROR_DIRECTORY); + goto cleanup; + +out_of_memory_error: + SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); + goto cleanup; + +cleanup: + if (dir_handle != INVALID_HANDLE_VALUE) + CloseHandle(dir_handle); + while (dirents_used > 0) + free(dirents[--dirents_used]); + if (dirents != NULL) + free(dirents); }