diff --git a/Makefile.am b/Makefile.am index 3c6fcb595a..4e9508b222 100644 --- a/Makefile.am +++ b/Makefile.am @@ -140,6 +140,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-fs-event.c \ test/test-fs-poll.c \ test/test-fs.c \ + test/test-fs-readdir.c \ test/test-get-currentexe.c \ test/test-get-loadavg.c \ test/test-get-memory.c \ diff --git a/include/uv-unix.h b/include/uv-unix.h index e72492564d..e5865bb0a4 100644 --- a/include/uv-unix.h +++ b/include/uv-unix.h @@ -158,6 +158,13 @@ typedef gid_t uv_gid_t; typedef uid_t uv_uid_t; typedef struct dirent uv__dirent_t; +/* + * "dirent" is used to hold a buffer large enough for any dirent in the + * directory being read. Avoids allocating for each directory entry. + */ +#define UV_DIR_PRIVATE_FIELDS \ + uv__dirent_t* dirent; \ + DIR* dir; #if defined(DT_UNKNOWN) # define HAVE_DIRENT_TYPES diff --git a/include/uv-win.h b/include/uv-win.h index 293b41d08c..25debe158f 100644 --- a/include/uv-win.h +++ b/include/uv-win.h @@ -302,6 +302,8 @@ typedef struct uv__dirent_s { #define UV__DT_CHAR UV_DIRENT_CHAR #define UV__DT_BLOCK UV_DIRENT_BLOCK +#define UV_DIR_PRIVATE_FIELDS + /* 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 42c6100a1d..601a7f16d1 100644 --- a/include/uv.h +++ b/include/uv.h @@ -156,6 +156,7 @@ extern "C" { XX(TTY, tty) \ XX(UDP, udp) \ XX(SIGNAL, signal) \ + XX(DIR, dir) \ #define UV_REQ_TYPE_MAP(XX) \ XX(REQ, req) \ @@ -197,6 +198,7 @@ typedef enum { /* Handle types. */ typedef struct uv_loop_s uv_loop_t; typedef struct uv_handle_s uv_handle_t; +typedef struct uv_dir_s uv_dir_t; typedef struct uv_stream_s uv_stream_t; typedef struct uv_tcp_s uv_tcp_t; typedef struct uv_udp_s uv_udp_t; @@ -1032,6 +1034,8 @@ typedef enum { UV_FS_MKDTEMP, UV_FS_RENAME, UV_FS_SCANDIR, + UV_FS_OPENDIR, + UV_FS_READDIR, UV_FS_LINK, UV_FS_SYMLINK, UV_FS_READLINK, @@ -1039,6 +1043,11 @@ typedef enum { UV_FS_FCHOWN } uv_fs_type; +struct uv_dir_s { + UV_HANDLE_FIELDS + UV_DIR_PRIVATE_FIELDS +}; + /* uv_fs_t is a subclass of uv_req_t. */ struct uv_fs_s { UV_REQ_FIELDS @@ -1049,6 +1058,8 @@ struct uv_fs_s { void* ptr; const char* path; uv_stat_t statbuf; /* Stores the result of uv_fs_stat() and uv_fs_fstat(). */ + uv_dir_t* dir_handle; /* Stores the result of uv_fs_opendir() */ + uv_dirent_t* dir_entry; /* Stores the result of uv_fs_readdir() */ UV_FS_PRIVATE_FIELDS }; @@ -1101,6 +1112,30 @@ UV_EXTERN int uv_fs_scandir(uv_loop_t* loop, uv_fs_cb cb); UV_EXTERN int uv_fs_scandir_next(uv_fs_t* req, uv_dirent_t* ent); + +/* + * In the future, this flag could be used to specify a custom filter to be + * applied each time a directory entry is read. It removes the need for the + * user to iterate through all directory entries again to filter out the ones + * that they are interested in. + */ +#define UV_DIR_FLAGS_NONE 0 + +/* uv_fs_{open,readdir} are not yet implemented on Windows */ +#ifndef _WIN32 +UV_EXTERN int uv_fs_opendir(uv_loop_t* loop, + uv_fs_t* req, + uv_dir_t* dirh, + const char* path, + int flags, + uv_fs_cb cb); +UV_EXTERN int uv_fs_readdir(uv_loop_t* loop, + uv_fs_t* req, + uv_dir_t* dirh, + uv_dirent_t* dirent, + uv_fs_cb cb); +#endif /* _WIN32 */ + UV_EXTERN int uv_fs_stat(uv_loop_t* loop, uv_fs_t* req, const char* path, @@ -1390,7 +1425,6 @@ struct uv_loop_s { UV_LOOP_PRIVATE_FIELDS }; - /* Don't export the private CPP symbols. */ #undef UV_HANDLE_TYPE_PRIVATE #undef UV_REQ_TYPE_PRIVATE diff --git a/src/unix/core.c b/src/unix/core.c index 9dcc3935dc..716a353ca5 100644 --- a/src/unix/core.c +++ b/src/unix/core.c @@ -156,6 +156,10 @@ void uv_close(uv_handle_t* handle, uv_close_cb close_cb) { /* itself close uv__make_close_pending whenever appropriate. */ return; + case UV_DIR: + uv__dir_close((uv_dir_t*)handle); + break; + default: assert(0); } @@ -221,6 +225,7 @@ static void uv__finish_close(uv_handle_t* handle) { case UV_FS_POLL: case UV_POLL: case UV_SIGNAL: + case UV_DIR: break; case UV_NAMED_PIPE: diff --git a/src/unix/fs.c b/src/unix/fs.c index cb8741b08e..2687cdbdb8 100644 --- a/src/unix/fs.c +++ b/src/unix/fs.c @@ -136,6 +136,40 @@ static ssize_t uv__fs_fdatasync(uv_fs_t* req) { } +static ssize_t uv__fs_clamp_max_path_len(ssize_t len) { + if (len == -1) { + #if defined(PATH_MAX) + len = PATH_MAX; + #else + len = 4096; + #endif + } + + return len; +} + +static ssize_t uv__fs_max_path_len_from_fd(int fd) { + ssize_t len; + + assert(fd >= 0); + + len = fpathconf(fd, _PC_PATH_MAX); + len = uv__fs_clamp_max_path_len(len); + + return len; +} + +static ssize_t uv__fs_max_path_len(const char* path) { + ssize_t len; + + assert(path); + + len = pathconf(path, _PC_PATH_MAX); + len = uv__fs_clamp_max_path_len(len); + + return len; +} + static ssize_t uv__fs_futime(uv_fs_t* req) { #if defined(__linux__) /* utimesat() has nanosecond resolution but we stick to microseconds @@ -339,21 +373,78 @@ static ssize_t uv__fs_scandir(uv_fs_t* req) { return n; } +static int uv__fs_opendir(uv_fs_t* req) { + DIR *dir; + ssize_t max_path_len; + size_t len; -static ssize_t uv__fs_readlink(uv_fs_t* req) { - ssize_t len; - char* buf; + assert(req && req->dir_handle); - len = pathconf(req->path, _PC_PATH_MAX); + dir = opendir(req->path); + if (dir == NULL) + return -1; - if (len == -1) { -#if defined(PATH_MAX) - len = PATH_MAX; -#else - len = 4096; -#endif + /* + * Compute max path len from the file descriptor of the open directory + * instead of from the path to avoid race conditions. + */ + max_path_len = uv__fs_max_path_len_from_fd(dirfd(dir)); + len = max_path_len + offsetof(struct dirent, d_name); + + /* + * Allocate the space for each directory entry just once instead of + * once per directory entry. + */ + req->dir_handle->dirent = malloc(len + 1); + if (req->dir_handle->dirent == NULL) { + errno = ENOMEM; + return -1; } + req->dir_handle->dir = dir; + req->ptr = req->dir_handle; + + return 0; +} + +static int uv__fs_readdir(uv_fs_t* req) { + struct dirent *de; + int r; + + assert(req); + assert(req->dir_handle); + assert(req->dir_handle->dirent); + assert(req->dir_entry); + + req->ptr = req->dir_entry; + + de = NULL; + + r = readdir_r(req->dir_handle->dir, req->dir_handle->dirent, &de); + + if (r == 0) { + if (de != NULL) { + req->dir_entry->name = strdup(de->d_name); + if (req->dir_entry->name != NULL) { + req->dir_entry->type = uv__fs_get_dirent_type(de); + r = 1; + } else { + errno = ENOMEM; + r = -1; + } + } else { + r = UV_EOF; + } + } + + return r; +} + +static ssize_t uv__fs_readlink(uv_fs_t* req) { + ssize_t len; + char* buf; + + len = uv__fs_max_path_len(req->path); buf = malloc(len + 1); if (buf == NULL) { @@ -779,6 +870,8 @@ static void uv__fs_work(struct uv__work* w) { X(MKDTEMP, uv__fs_mkdtemp(req)); X(READ, uv__fs_read(req)); X(SCANDIR, uv__fs_scandir(req)); + X(OPENDIR, uv__fs_opendir(req)); + X(READDIR, uv__fs_readdir(req)); X(READLINK, uv__fs_readlink(req)); X(RENAME, rename(req->path, req->new_path)); X(RMDIR, rmdir(req->path)); @@ -1050,6 +1143,53 @@ int uv_fs_scandir(uv_loop_t* loop, POST; } +int uv_fs_opendir(uv_loop_t* loop, + uv_fs_t* req, + uv_dir_t* dirh, + const char* path, + int flags, + uv_fs_cb cb) { + + INIT(OPENDIR); + PATH; + + uv__handle_init(loop, dirh, UV_DIR); + + req->flags = flags; + + dirh->dir = NULL; + req->dir_handle = dirh; + + POST; +} + +void uv__dir_close(uv_dir_t* dirh) { + if (!uv__is_active(dirh)) + return; + + if (dirh->dir) { + closedir(dirh->dir); + dirh->dir = NULL; + } + + if (dirh->dirent) { + free(dirh->dirent); + dirh->dirent = NULL; + } +} + +int uv_fs_readdir(uv_loop_t* loop, + uv_fs_t* req, + uv_dir_t* dirh, + uv_dirent_t* dirent, + uv_fs_cb cb) { + INIT(READDIR); + + req->dir_handle = dirh; + req->dir_entry = dirent; + + POST; +} int uv_fs_readlink(uv_loop_t* loop, uv_fs_t* req, @@ -1169,7 +1309,12 @@ void uv_fs_req_cleanup(uv_fs_t* req) { if (req->fs_type == UV_FS_SCANDIR && req->ptr != NULL) uv__fs_scandir_cleanup(req); - if (req->ptr != &req->statbuf) + if (req->fs_type == UV_FS_READDIR) + uv__fs_readdir_cleanup(req); + + if (req->fs_type != UV_FS_READDIR && + req->fs_type != UV_FS_OPENDIR && + req->ptr != &req->statbuf) free(req->ptr); req->ptr = NULL; } diff --git a/src/unix/internal.h b/src/unix/internal.h index d5bc3109f0..623e87d412 100644 --- a/src/unix/internal.h +++ b/src/unix/internal.h @@ -237,6 +237,7 @@ void uv__tcp_close(uv_tcp_t* handle); void uv__timer_close(uv_timer_t* handle); void uv__udp_close(uv_udp_t* handle); void uv__udp_finish_close(uv_udp_t* handle); +void uv__dir_close(uv_dir_t* handle); uv_handle_type uv__handle_type(int fd); #if defined(__APPLE__) diff --git a/src/uv-common.c b/src/uv-common.c index 97727baa54..8cf10c6738 100644 --- a/src/uv-common.c +++ b/src/uv-common.c @@ -468,35 +468,53 @@ int uv_fs_scandir_next(uv_fs_t* req, uv_dirent_t* ent) { dent = dents[req->nbufs++]; ent->name = dent->d_name; + ent->type = uv__fs_get_dirent_type(dent); + + return 0; +} + +uv_dirent_type_t uv__fs_get_dirent_type(uv__dirent_t* dent) { + uv_dirent_type_t type; + #ifdef HAVE_DIRENT_TYPES switch (dent->d_type) { case UV__DT_DIR: - ent->type = UV_DIRENT_DIR; + type = UV_DIRENT_DIR; break; case UV__DT_FILE: - ent->type = UV_DIRENT_FILE; + type = UV_DIRENT_FILE; break; case UV__DT_LINK: - ent->type = UV_DIRENT_LINK; + type = UV_DIRENT_LINK; break; case UV__DT_FIFO: - ent->type = UV_DIRENT_FIFO; + type = UV_DIRENT_FIFO; break; case UV__DT_SOCKET: - ent->type = UV_DIRENT_SOCKET; + type = UV_DIRENT_SOCKET; break; case UV__DT_CHAR: - ent->type = UV_DIRENT_CHAR; + type = UV_DIRENT_CHAR; break; case UV__DT_BLOCK: - ent->type = UV_DIRENT_BLOCK; + type = UV_DIRENT_BLOCK; break; default: - ent->type = UV_DIRENT_UNKNOWN; + type = UV_DIRENT_UNKNOWN; } #else - ent->type = UV_DIRENT_UNKNOWN; + type = UV_DIRENT_UNKNOWN; #endif - return 0; + return type; +} + +void uv__fs_readdir_cleanup(uv_fs_t* req) { + if (req && req->ptr) { + uv_dirent_t* dirent = req->ptr; + + if (dirent->name) + free((char*)dirent->name); + dirent->name = NULL; + } } diff --git a/src/uv-common.h b/src/uv-common.h index e06606c19b..18f6da44d0 100644 --- a/src/uv-common.h +++ b/src/uv-common.h @@ -110,6 +110,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_scandir_cleanup(uv_fs_t* req); +void uv__fs_readdir_cleanup(uv_fs_t* req); +uv_dirent_type_t uv__fs_get_dirent_type(uv__dirent_t* dent); #define uv__has_active_reqs(loop) \ (QUEUE_EMPTY(&(loop)->active_reqs) == 0) diff --git a/test/test-fs-readdir.c b/test/test-fs-readdir.c new file mode 100644 index 0000000000..7b7d7e8e6d --- /dev/null +++ b/test/test-fs-readdir.c @@ -0,0 +1,492 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include /* unlink, rmdir, memset */ + +#if defined(__sun) || defined(__linux__) +#include /* memset */ +#endif + +#include "uv.h" +#include "task.h" + +static int empty_opendir_cb_count; +static int empty_readdir_cb_count; + +uv_fs_t readdir_req; +uv_dir_t dir_handle_sync; +uv_dir_t dir_handle_async; +uv_dirent_t dent; + +static void empty_closedir_cb(uv_handle_t* handle) { + ASSERT(handle == (uv_handle_t*)&dir_handle_async); +} + +static void empty_readdir_cb(uv_fs_t* req) { + ASSERT(req == &readdir_req); + ASSERT(req->fs_type == UV_FS_READDIR); + + // TODO jgilli: by default, uv_fs_readdir doesn't return UV_EOF when reading + // an emptry dir. Instead, it returns "." and ".." entries in sequence. + // Should this be fixed to mimic uv_fs_scandir's behavior? + if (req->result == UV_EOF) { + ASSERT(empty_readdir_cb_count == 2); + uv_close((uv_handle_t*)req->dir_handle, empty_closedir_cb); + } else { + ASSERT(req->result == 1); + ASSERT(req->ptr == &dent); + ASSERT(req->dir_handle == &dir_handle_async); + +#ifdef HAVE_DIRENT_TYPES + // In an empty directory, all entries are directories ("." and "..") + ASSERT(((uv_dirent_t*)req->ptr)->type == UV_DIRENT_DIR); +#else + ASSERT(((uv_dirent_t*)req->ptr)->type == UV_DIRENT_UNKNOWN); +#endif /* HAVE_DIRENT_TYPES */ + + ++empty_readdir_cb_count; + + uv_fs_readdir(uv_default_loop(), + req, + req->dir_handle, + &dent, + empty_readdir_cb); + } + + uv_fs_req_cleanup(req); +} + +static void empty_opendir_cb(uv_fs_t* req) { + ASSERT(req == &readdir_req); + ASSERT(req->fs_type == UV_FS_OPENDIR); + ASSERT(req->result == 0); + ASSERT(req->ptr == &dir_handle_async); + ASSERT(req->dir_handle == &dir_handle_async); + ASSERT(0 == uv_fs_readdir(uv_default_loop(), + req, + req->ptr, + &dent, + empty_readdir_cb)); + uv_fs_req_cleanup(req); + ++empty_opendir_cb_count; +} + +/* + * This test makes sure that both synchronous and asynchronous flavors + * of the uv_fs_opendir -> uv_fs_readdir -> uv_close sequence work + * as expected when processing an empty directory. + */ +TEST_IMPL(fs_readdir_empty_dir) { + uv_loop_t* loop; + const char* path; + uv_fs_t mkdir_req; + int r; + size_t entries_count; + + path = "./empty_dir/"; + loop = uv_default_loop(); + + uv_fs_mkdir(loop, &mkdir_req, path, 0777, NULL); + uv_fs_req_cleanup(&mkdir_req); + + /* Fill the req to ensure that required fields are cleaned up */ + memset(&readdir_req, 0xdb, sizeof(readdir_req)); + + /* Testing the synchronous flavor */ + r = uv_fs_opendir(loop, + &readdir_req, + &dir_handle_sync, + path, + UV_DIR_FLAGS_NONE, + NULL); + ASSERT(r == 0); + ASSERT(readdir_req.fs_type == UV_FS_OPENDIR); + ASSERT(readdir_req.result == 0); + ASSERT(readdir_req.ptr == &dir_handle_sync); + ASSERT(readdir_req.dir_handle == &dir_handle_sync); + /* + * TODO jgilli: by default, uv_fs_readdir doesn't return UV_EOF when reading + * an emptry dir. Instead, it returns "." and ".." entries in sequence. + * Should this be fixed to mimic uv_fs_scandir's behavior? + */ + entries_count = 0; + while (UV_EOF != uv_fs_readdir(loop, + &readdir_req, + readdir_req.dir_handle, + &dent, + NULL)) { +#ifdef HAVE_DIRENT_TYPES + // In an empty directory, all entries are directories ("." and "..") + ASSERT(((uv_dirent_t*)readdir_req.ptr)->type == UV_DIRENT_DIR); +#else + ASSERT(((uv_dirent_t*)readdir_req.ptr)->type == UV_DIRENT_UNKNOWN); +#endif /* HAVE_DIRENT_TYPES */ + + ++entries_count; + } + + ASSERT(entries_count == 2); + + uv_fs_req_cleanup(&readdir_req); + + uv_close((uv_handle_t*)&dir_handle_sync, NULL); + + /* Testing the asynchronous flavor */ + + /* Fill the req to ensure that required fields are cleaned up */ + memset(&readdir_req, 0xdb, sizeof(readdir_req)); + + r = uv_fs_opendir(loop, + &readdir_req, + &dir_handle_async, + path, + UV_DIR_FLAGS_NONE, + empty_opendir_cb); + ASSERT(r == 0); + + ASSERT(empty_opendir_cb_count == 0); + uv_run(loop, UV_RUN_DEFAULT); + ASSERT(empty_opendir_cb_count == 1); + + uv_fs_rmdir(loop, &readdir_req, path, NULL); + uv_fs_req_cleanup(&readdir_req); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +/* + * This test makes sure that reading a non-existing directory with + * uv_fs_{open,read}_dir returns proper error codes. + */ + +static int non_existing_opendir_cb_count; + +static void non_existing_opendir_cb(uv_fs_t* req) { + ASSERT(req == &readdir_req); + ASSERT(req->fs_type == UV_FS_OPENDIR); + ASSERT(req->result == UV_ENOENT); + ASSERT(req->ptr == NULL); + ASSERT(req->dir_handle == &dir_handle_async); + ASSERT(((uv_dir_t*)req->dir_handle)->dir == NULL); + uv_fs_req_cleanup(req); + ++non_existing_opendir_cb_count; +} + +TEST_IMPL(fs_readdir_non_existing_dir) { + uv_loop_t* loop; + const char* path; + int r; + + path = "./non-existing-dir/"; + loop = uv_default_loop(); + + /* Fill the req to ensure that required fields are cleaned up */ + memset(&readdir_req, 0xdb, sizeof(readdir_req)); + + /* Testing the synchronous flavor */ + r = uv_fs_opendir(loop, + &readdir_req, + &dir_handle_sync, + path, + UV_DIR_FLAGS_NONE, + NULL); + ASSERT(r == UV_ENOENT); + ASSERT(readdir_req.fs_type == UV_FS_OPENDIR); + ASSERT(readdir_req.result == UV_ENOENT); + ASSERT(readdir_req.ptr == NULL); + ASSERT(readdir_req.dir_handle == &dir_handle_sync); + ASSERT(readdir_req.dir_handle->dir == NULL); + + uv_fs_req_cleanup(&readdir_req); + + /* Fill the req to ensure that required fields are cleaned up */ + memset(&readdir_req, 0xdb, sizeof(readdir_req)); + + /* Testing the async flavor */ + r = uv_fs_opendir(loop, + &readdir_req, + &dir_handle_async, + path, + UV_DIR_FLAGS_NONE, + non_existing_opendir_cb); + ASSERT(r == 0); + + ASSERT(non_existing_opendir_cb_count == 0); + uv_run(loop, UV_RUN_DEFAULT); + ASSERT(non_existing_opendir_cb_count == 1); + + uv_fs_req_cleanup(&readdir_req); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +/* + * This test makes sure that reading a file as a directory reports correct + * error codes. + */ + +static int file_opendir_cb_count; + +static void file_opendir_cb(uv_fs_t* req) { + ASSERT(req == &readdir_req); + ASSERT(req->fs_type == UV_FS_OPENDIR); + ASSERT(req->result == UV_ENOTDIR); + ASSERT(req->ptr == NULL); + ASSERT(req->dir_handle == &dir_handle_async); + ASSERT(((uv_dir_t*)req->dir_handle)->dir == NULL); + uv_fs_req_cleanup(req); + ++file_opendir_cb_count; +} + +TEST_IMPL(fs_readdir_file) { + uv_loop_t* loop; + const char* path; + int r; + + path = "test/fixtures/empty_file"; + loop = uv_default_loop(); + + /* Fill the req to ensure that required fields are cleaned up */ + memset(&readdir_req, 0xdb, sizeof(readdir_req)); + + /* Testing the synchronous flavor */ + r = uv_fs_opendir(loop, + &readdir_req, + &dir_handle_sync, + path, + UV_DIR_FLAGS_NONE, + NULL); + printf("r: %d\n", r); + ASSERT(r == UV_ENOTDIR); + ASSERT(readdir_req.fs_type == UV_FS_OPENDIR); + ASSERT(readdir_req.result == UV_ENOTDIR); + ASSERT(readdir_req.ptr == NULL); + ASSERT(readdir_req.dir_handle == &dir_handle_sync); + ASSERT(readdir_req.dir_handle->dir == NULL); + + uv_fs_req_cleanup(&readdir_req); + + /* Fill the req to ensure that required fields are cleaned up */ + memset(&readdir_req, 0xdb, sizeof(readdir_req)); + + /* Testing the async flavor */ + r = uv_fs_opendir(loop, + &readdir_req, + &dir_handle_async, + path, + UV_DIR_FLAGS_NONE, + file_opendir_cb); + ASSERT(r == 0); + + ASSERT(file_opendir_cb_count == 0); + uv_run(loop, UV_RUN_DEFAULT); + ASSERT(file_opendir_cb_count == 1); + + uv_fs_req_cleanup(&readdir_req); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +/* + * This test makes sure that reading a non-empty directory with + * uv_fs_{open,read}_dir returns proper directory entries, including the + * correct entry types. + */ + +static int non_empty_opendir_cb_count; +static int non_empty_readdir_cb_count; + +static void non_empty_closedir_cb(uv_handle_t* handle) { + ASSERT(handle == (uv_handle_t*)&dir_handle_async); +} + +static void non_empty_readdir_cb(uv_fs_t* req) { + ASSERT(req == &readdir_req); + ASSERT(req->fs_type == UV_FS_READDIR); + + // TODO jgilli: by default, uv_fs_readdir doesn't return UV_EOF when reading + // an emptry dir. Instead, it returns "." and ".." entries in sequence. + // Should this be fixed to mimic uv_fs_scandir's behavior? + if (req->result == UV_EOF) { + ASSERT(non_empty_readdir_cb_count == 5); + uv_close((uv_handle_t*)req->dir_handle, non_empty_closedir_cb); + } else { + ASSERT(req->result == 1); + ASSERT(req->ptr == &dent); + ASSERT(req->dir_handle == &dir_handle_async); + +#ifdef HAVE_DIRENT_TYPES + if (!strcmp(dent.name, "test_subdir") || + !strcmp(dent.name, ".") || + !strcmp(dent.name, "..")) { + ASSERT(dent.type == UV_DIRENT_DIR); + } else { + ASSERT(dent.type == UV_DIRENT_FILE); + } +#else + ASSERT(dent.type == UV_DIRENT_UNKNOWN); +#endif /* HAVE_DIRENT_TYPES */ + + ++non_empty_readdir_cb_count; + + uv_fs_readdir(uv_default_loop(), + req, + req->dir_handle, + &dent, + non_empty_readdir_cb); + } + + uv_fs_req_cleanup(req); +} + +static void non_empty_opendir_cb(uv_fs_t* req) { + ASSERT(req == &readdir_req); + ASSERT(req->fs_type == UV_FS_OPENDIR); + ASSERT(req->result == 0); + ASSERT(req->ptr == &dir_handle_async); + ASSERT(req->dir_handle == &dir_handle_async); + ASSERT(0 == uv_fs_readdir(uv_default_loop(), + req, + req->ptr, + &dent, + non_empty_readdir_cb)); + uv_fs_req_cleanup(req); + ++non_empty_opendir_cb_count; +} + +TEST_IMPL(fs_readdir_non_empty_dir) { + uv_loop_t* loop; + const char* path; + int r; + size_t entries_count; + uv_fs_t mkdir_req; + uv_fs_t open_req1; + uv_fs_t close_req; + uv_fs_t rmdir_req; + + loop = uv_default_loop(); + + /* Setup */ + unlink("test_dir/file1"); + unlink("test_dir/file2"); + rmdir("test_dir/test_subdir"); + rmdir("test_dir"); + + loop = uv_default_loop(); + + r = uv_fs_mkdir(loop, &mkdir_req, "test_dir", 0755, NULL); + ASSERT(r == 0); + + /* Create 2 files synchronously. */ + r = uv_fs_open(loop, &open_req1, "test_dir/file1", O_WRONLY | O_CREAT, + S_IWUSR | S_IRUSR, NULL); + ASSERT(r >= 0); + uv_fs_req_cleanup(&open_req1); + r = uv_fs_close(loop, &close_req, open_req1.result, NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&close_req); + + r = uv_fs_open(loop, &open_req1, "test_dir/file2", O_WRONLY | O_CREAT, + S_IWUSR | S_IRUSR, NULL); + ASSERT(r >= 0); + uv_fs_req_cleanup(&open_req1); + r = uv_fs_close(loop, &close_req, open_req1.result, NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&close_req); + + r = uv_fs_mkdir(loop, &mkdir_req, "test_dir/test_subdir", 0755, NULL); + ASSERT(r == 0); + + /* Fill the req to ensure that required fields are cleaned up */ + memset(&readdir_req, 0xdb, sizeof(readdir_req)); + + /* Testing the synchronous flavor */ + r = uv_fs_opendir(loop, + &readdir_req, + &dir_handle_sync, + "test_dir", + UV_DIR_FLAGS_NONE, + NULL); + ASSERT(r == 0); + ASSERT(readdir_req.fs_type == UV_FS_OPENDIR); + ASSERT(readdir_req.result == 0); + ASSERT(readdir_req.ptr == &dir_handle_sync); + ASSERT(readdir_req.dir_handle == &dir_handle_sync); + // TODO jgilli: by default, uv_fs_readdir doesn't return UV_EOF when reading + // an emptry dir. Instead, it returns "." and ".." entries in sequence. + // Should this be fixed to mimic uv_fs_scandir's behavior? + entries_count = 0; + while (uv_fs_readdir(loop, + &readdir_req, + readdir_req.dir_handle, + &dent, + NULL) != UV_EOF) { +#ifdef HAVE_DIRENT_TYPES + if (!strcmp(dent.name, "test_subdir") || + !strcmp(dent.name, ".") || + !strcmp(dent.name, "..")) { + ASSERT(dent.type == UV_DIRENT_DIR); + } else { + ASSERT(dent.type == UV_DIRENT_FILE); + } +#else + ASSERT(dent.type == UV_DIRENT_UNKNOWN); +#endif /* HAVE_DIRENT_TYPES */ + ++entries_count; + } + + ASSERT(entries_count == 5); + uv_fs_req_cleanup(&readdir_req); + + uv_close((uv_handle_t*)&dir_handle_sync, NULL); + + /* Testing the asynchronous flavor */ + + /* Fill the req to ensure that required fields are cleaned up */ + memset(&readdir_req, 0xdb, sizeof(readdir_req)); + + r = uv_fs_opendir(loop, + &readdir_req, + &dir_handle_async, + "test_dir", + UV_DIR_FLAGS_NONE, + non_empty_opendir_cb); + ASSERT(r == 0); + + ASSERT(non_empty_opendir_cb_count == 0); + uv_run(loop, UV_RUN_DEFAULT); + ASSERT(non_empty_opendir_cb_count == 1); + + uv_fs_rmdir(loop, &rmdir_req, "test_subdir", NULL); + uv_fs_req_cleanup(&rmdir_req); + + /* Cleanup */ + unlink("test_dir/file1"); + unlink("test_dir/file2"); + rmdir("test_dir/test_subdir"); + rmdir("test_dir"); + + MAKE_VALGRIND_HAPPY(); + return 0; + } diff --git a/test/test-list.h b/test/test-list.h index 90c0442b8f..2f1e37ce9b 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -244,6 +244,12 @@ TEST_DECLARE (fs_event_getpath) TEST_DECLARE (fs_scandir_empty_dir) TEST_DECLARE (fs_scandir_file) TEST_DECLARE (fs_open_dir) +#ifndef _WIN32 +TEST_DECLARE (fs_readdir_empty_dir) +TEST_DECLARE (fs_readdir_non_existing_dir) +TEST_DECLARE (fs_readdir_non_empty_dir) +TEST_DECLARE (fs_readdir_file) +#endif /* _WIN32 */ TEST_DECLARE (fs_rename_to_existing_file) TEST_DECLARE (threadpool_queue_work_simple) TEST_DECLARE (threadpool_queue_work_einval) @@ -615,6 +621,12 @@ TASK_LIST_START TEST_ENTRY (fs_scandir_empty_dir) TEST_ENTRY (fs_scandir_file) TEST_ENTRY (fs_open_dir) +#ifndef _WIN32 + TEST_ENTRY (fs_readdir_empty_dir) + TEST_ENTRY (fs_readdir_non_existing_dir) + TEST_ENTRY (fs_readdir_non_empty_dir) + TEST_ENTRY (fs_readdir_file) +#endif /* _WIN32 */ TEST_ENTRY (fs_rename_to_existing_file) TEST_ENTRY (threadpool_queue_work_simple) TEST_ENTRY (threadpool_queue_work_einval) diff --git a/uv.gyp b/uv.gyp index 444182b62d..bb997fee27 100644 --- a/uv.gyp +++ b/uv.gyp @@ -342,6 +342,7 @@ 'test/test-fail-always.c', 'test/test-fs.c', 'test/test-fs-event.c', + 'test/test-fs-readdir.c', 'test/test-get-currentexe.c', 'test/test-get-memory.c', 'test/test-getaddrinfo.c',