win: use NtQueryDirectoryFile to implement uv_fs_scandir
PR-URL: https://github.com/libuv/libuv/pull/105 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Saúl Ibarra Corretgé <saghul@gmail.com>
This commit is contained in:
parent
2af8294895
commit
0729ce8b39
268
src/win/fs.c
268
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user