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:
Bert Belder 2015-01-05 01:06:10 +01:00
parent 2af8294895
commit 0729ce8b39

View File

@ -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);
}