diff --git a/include/uv-unix.h b/include/uv-unix.h index f04bed43..2a1ead5b 100644 --- a/include/uv-unix.h +++ b/include/uv-unix.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -155,6 +156,10 @@ typedef pthread_barrier_t uv_barrier_t; typedef gid_t uv_gid_t; typedef uid_t uv_uid_t; +typedef struct dirent uv__dirent_t; + +#define UV__DT_DIR DT_DIR + /* Platform-specific definitions for uv_dlopen support. */ #define UV_DYNAMIC /* empty */ diff --git a/include/uv-win.h b/include/uv-win.h index a1be7e15..116d67f6 100644 --- a/include/uv-win.h +++ b/include/uv-win.h @@ -289,6 +289,14 @@ typedef struct uv_once_s { typedef unsigned char uv_uid_t; typedef unsigned char uv_gid_t; +typedef struct uv__dirent_s { + int d_type; + char d_name[1]; +} uv__dirent_t; + +#define UV__DT_DIR UV_DIRENT_DIR +#define UV__DT_FILE UV_DIRENT_FILE + /* Platform-specific definitions for uv_dlopen support. */ #define UV_DYNAMIC FAR WINAPI typedef struct { diff --git a/include/uv.h b/include/uv.h index ff62df9b..221d5d03 100644 --- a/include/uv.h +++ b/include/uv.h @@ -227,6 +227,7 @@ typedef struct uv_work_s uv_work_t; /* None of the above. */ typedef struct uv_cpu_info_s uv_cpu_info_t; typedef struct uv_interface_address_s uv_interface_address_t; +typedef struct uv_dirent_s uv_dirent_t; typedef enum { @@ -1786,6 +1787,16 @@ struct uv_interface_address_s { } netmask; }; +typedef enum { + UV_DIRENT_FILE, + UV_DIRENT_DIR +} uv_dirent_type_t; + +struct uv_dirent_s { + const char* name; + uv_dirent_type_t type; +}; + UV_EXTERN char** uv_setup_args(int argc, char** argv); UV_EXTERN int uv_get_process_title(char* buffer, size_t size); UV_EXTERN int uv_set_process_title(const char* title); @@ -1931,6 +1942,15 @@ UV_EXTERN int uv_fs_rmdir(uv_loop_t* loop, uv_fs_t* req, const char* path, UV_EXTERN int uv_fs_readdir(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, uv_fs_cb cb); +/* + * Call this after `uv_fs_readdir` cb's invocation, this function should be + * called until it returns `UV_EOF`. + * + * The data that is put into `ent` is managed by libuv and should not be + * deallocated by the user. + */ +UV_EXTERN int uv_fs_readdir_next(uv_fs_t* req, uv_dirent_t* ent); + UV_EXTERN int uv_fs_stat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb); diff --git a/src/unix/fs.c b/src/unix/fs.c index 47f66722..2dd0fe97 100644 --- a/src/unix/fs.c +++ b/src/unix/fs.c @@ -38,7 +38,6 @@ #include #include #include -#include #include #include #include @@ -296,9 +295,9 @@ done: #if defined(__OpenBSD__) || (defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_8)) -static int uv__fs_readdir_filter(struct dirent* dent) { +static int uv__fs_readdir_filter(uv__dirent_t* dent) { #else -static int uv__fs_readdir_filter(const struct dirent* dent) { +static int uv__fs_readdir_filter(const uv__dirent_t* dent) { #endif return strcmp(dent->d_name, ".") != 0 && strcmp(dent->d_name, "..") != 0; } @@ -306,12 +305,8 @@ static int uv__fs_readdir_filter(const struct dirent* dent) { /* This should have been called uv__fs_scandir(). */ static ssize_t uv__fs_readdir(uv_fs_t* req) { - struct dirent **dents; + uv__dirent_t **dents; int saved_errno; - size_t off; - size_t len; - char *buf; - int i; int n; dents = NULL; @@ -322,32 +317,17 @@ static ssize_t uv__fs_readdir(uv_fs_t* req) { else if (n == -1) return n; - len = 0; + /* NOTE: We will use nbufs as an index field */ + req->ptr = dents; + req->nbufs = 0; - for (i = 0; i < n; i++) - len += strlen(dents[i]->d_name) + 1; - - buf = malloc(len); - - if (buf == NULL) { - errno = ENOMEM; - n = -1; - goto out; - } - - off = 0; - - for (i = 0; i < n; i++) { - len = strlen(dents[i]->d_name) + 1; - memcpy(buf + off, dents[i]->d_name, len); - off += len; - } - - req->ptr = buf; + return n; out: saved_errno = errno; if (dents != NULL) { + int i; + for (i = 0; i < n; i++) free(dents[i]); free(dents); @@ -1184,6 +1164,9 @@ void uv_fs_req_cleanup(uv_fs_t* req) { req->path = NULL; req->new_path = NULL; + if (req->fs_type == UV_FS_READDIR && req->ptr != NULL) + uv__fs_readdir_cleanup(req); + if (req->ptr != &req->statbuf) free(req->ptr); req->ptr = NULL; diff --git a/src/uv-common.c b/src/uv-common.c index e5475acd..e2307aa0 100644 --- a/src/uv-common.c +++ b/src/uv-common.c @@ -449,3 +449,43 @@ int uv_fs_event_getpath(uv_fs_event_t* handle, char* buf, size_t* len) { return 0; } + + +void uv__fs_readdir_cleanup(uv_fs_t* req) { + uv__dirent_t** dents; + + dents = req->ptr; + if (req->nbufs > 0 && req->nbufs != (unsigned int) req->result) + req->nbufs--; + for (; req->nbufs < (unsigned int) req->result; req->nbufs++) + free(dents[req->nbufs]); +} + + +int uv_fs_readdir_next(uv_fs_t* req, uv_dirent_t* ent) { + uv__dirent_t** dents; + uv__dirent_t* dent; + + dents = req->ptr; + + /* Free previous entity */ + if (req->nbufs > 0) + free(dents[req->nbufs - 1]); + + /* End was already reached */ + if (req->nbufs == (unsigned int) req->result) { + free(dents); + req->ptr = NULL; + return UV_EOF; + } + + dent = dents[req->nbufs++]; + + ent->name = dent->d_name; + if (dent->d_type == UV__DT_DIR) + ent->type = UV_DIRENT_DIR; + else + ent->type = UV_DIRENT_FILE; + + return 0; +} diff --git a/src/uv-common.h b/src/uv-common.h index e7af5e9b..c9bad2f1 100644 --- a/src/uv-common.h +++ b/src/uv-common.h @@ -109,6 +109,8 @@ size_t uv__count_bufs(const uv_buf_t bufs[], unsigned int nbufs); int uv__socket_sockopt(uv_handle_t* handle, int optname, int* value); +void uv__fs_readdir_cleanup(uv_fs_t* req); + #define uv__has_active_reqs(loop) \ (QUEUE_EMPTY(&(loop)->active_reqs) == 0) diff --git a/src/win/fs.c b/src/win/fs.c index 3d3c4fdd..f1835b0b 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -43,6 +43,8 @@ #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 { \ @@ -784,13 +786,14 @@ void fs__mkdtemp(uv_fs_t* req) { void fs__readdir(uv_fs_t* req) { WCHAR* pathw = req->pathw; size_t len = wcslen(pathw); - int result, size; - WCHAR* buf = NULL, *ptr, *name; + int result; + WCHAR* name; HANDLE dir; WIN32_FIND_DATAW ent = { 0 }; - size_t buf_char_len = 4096; WCHAR* path2; const WCHAR* fmt; + uv__dirent_t** dents; + int dent_size; if (len == 0) { fmt = L"./*"; @@ -809,7 +812,8 @@ void fs__readdir(uv_fs_t* req) { path2 = (WCHAR*)malloc(sizeof(WCHAR) * (len + 4)); if (!path2) { - uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); + SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); + return; } _snwprintf(path2, len + 3, fmt, pathw); @@ -822,71 +826,79 @@ void fs__readdir(uv_fs_t* req) { } result = 0; + dents = NULL; + dent_size = 0; do { + uv__dirent_t* dent; + int utf8_len; + name = ent.cFileName; - if (name[0] != L'.' || (name[1] && (name[1] != L'.' || name[2]))) { - len = wcslen(name); + if (!(name[0] != L'.' || (name[1] && (name[1] != L'.' || name[2])))) + continue; - if (!buf) { - buf = (WCHAR*)malloc(buf_char_len * sizeof(WCHAR)); - if (!buf) { - uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); - } + /* Grow dents buffer, if needed */ + if (result >= dent_size) { + uv__dirent_t** tmp; - ptr = buf; + 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; } - - while ((ptr - buf) + len + 1 > buf_char_len) { - buf_char_len *= 2; - path2 = buf; - buf = (WCHAR*)realloc(buf, buf_char_len * sizeof(WCHAR)); - if (!buf) { - uv_fatal_error(ERROR_OUTOFMEMORY, "realloc"); - } - - ptr = buf + (ptr - path2); - } - - wcscpy(ptr, name); - ptr += len + 1; - result++; + 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; + } + + dent = malloc(sizeof(*dent) + utf8_len + 1); + if (dent == NULL) { + SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); + goto fatal; + } + + /* 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'; + + /* Copy file type */ + if ((ent.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + dent->d_type = UV__DT_DIR; + else + dent->d_type = UV__DT_FILE; + + dents[result++] = dent; } while(FindNextFileW(dir, &ent)); FindClose(dir); - if (buf) { - /* Convert result to UTF8. */ - size = uv_utf16_to_utf8(buf, buf_char_len, NULL, 0); - if (!size) { - SET_REQ_WIN32_ERROR(req, GetLastError()); - return; - } - - req->ptr = (char*)malloc(size + 1); - if (!req->ptr) { - uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); - } - - size = uv_utf16_to_utf8(buf, buf_char_len, (char*)req->ptr, size); - if (!size) { - free(buf); - free(req->ptr); - req->ptr = NULL; - SET_REQ_WIN32_ERROR(req, GetLastError()); - return; - } - free(buf); - - ((char*)req->ptr)[size] = '\0'; + if (dents != NULL) req->flags |= UV_FS_FREE_PTR; - } else { - req->ptr = NULL; - } + /* NOTE: nbufs will be used as index */ + 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); } diff --git a/test/test-fs.c b/test/test-fs.c index 4c6ccfab..38bc3e03 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -417,12 +417,16 @@ static void rmdir_cb(uv_fs_t* req) { static void readdir_cb(uv_fs_t* req) { + uv_dirent_t dent; ASSERT(req == &readdir_req); ASSERT(req->fs_type == UV_FS_READDIR); ASSERT(req->result == 2); ASSERT(req->ptr); - ASSERT(memcmp(req->ptr, "file1\0file2\0", 12) == 0 - || memcmp(req->ptr, "file2\0file1\0", 12) == 0); + + while (UV_EOF != uv_fs_readdir_next(req, &dent)) { + ASSERT(strcmp(dent.name, "file1") == 0 || strcmp(dent.name, "file2") == 0); + ASSERT(dent.type == UV_DIRENT_FILE); + } readdir_cb_count++; ASSERT(req->path); ASSERT(memcmp(req->path, "test_dir\0", 9) == 0); @@ -802,6 +806,7 @@ TEST_IMPL(fs_file_write_null_buffer) { TEST_IMPL(fs_async_dir) { int r; + uv_dirent_t dent; /* Setup */ unlink("test_dir/file1"); @@ -844,8 +849,10 @@ TEST_IMPL(fs_async_dir) { ASSERT(r == 2); ASSERT(readdir_req.result == 2); ASSERT(readdir_req.ptr); - ASSERT(memcmp(readdir_req.ptr, "file1\0file2\0", 12) == 0 - || memcmp(readdir_req.ptr, "file2\0file1\0", 12) == 0); + while (UV_EOF != uv_fs_readdir_next(&readdir_req, &dent)) { + ASSERT(strcmp(dent.name, "file1") == 0 || strcmp(dent.name, "file2") == 0); + ASSERT(dent.type == UV_DIRENT_FILE); + } uv_fs_req_cleanup(&readdir_req); ASSERT(!readdir_req.ptr); @@ -1521,6 +1528,7 @@ TEST_IMPL(fs_symlink_dir) { uv_fs_t req; int r; char* test_dir; + uv_dirent_t dent; /* set-up */ unlink("test_dir/file1"); @@ -1597,8 +1605,10 @@ TEST_IMPL(fs_symlink_dir) { ASSERT(r == 2); ASSERT(readdir_req.result == 2); ASSERT(readdir_req.ptr); - ASSERT(memcmp(readdir_req.ptr, "file1\0file2\0", 12) == 0 - || memcmp(readdir_req.ptr, "file2\0file1\0", 12) == 0); + while (UV_EOF != uv_fs_readdir_next(&readdir_req, &dent)) { + ASSERT(strcmp(dent.name, "file1") == 0 || strcmp(dent.name, "file2") == 0); + ASSERT(dent.type == UV_DIRENT_FILE); + } uv_fs_req_cleanup(&readdir_req); ASSERT(!readdir_req.ptr); @@ -1615,8 +1625,10 @@ TEST_IMPL(fs_symlink_dir) { ASSERT(r == 2); ASSERT(readdir_req.result == 2); ASSERT(readdir_req.ptr); - ASSERT(memcmp(readdir_req.ptr, "file1\0file2\0", 12) == 0 - || memcmp(readdir_req.ptr, "file2\0file1\0", 12) == 0); + while (UV_EOF != uv_fs_readdir_next(&readdir_req, &dent)) { + ASSERT(strcmp(dent.name, "file1") == 0 || strcmp(dent.name, "file2") == 0); + ASSERT(dent.type == UV_DIRENT_FILE); + } uv_fs_req_cleanup(&readdir_req); ASSERT(!readdir_req.ptr);