From 7ac0bad920c440c98bb6fedffb7bbc06117c5b28 Mon Sep 17 00:00:00 2001 From: Julien Gilli Date: Fri, 30 Jan 2015 11:04:03 -0800 Subject: [PATCH] fs: uv_fs_{open,read,close}_dir This is the same changes as https://github.com/joyent/libuv/pull/1574. This commit is just the start of getting them to work in libuv/libuv. Failing tests will be fixed asap. Fixes #170. --- Makefile.am | 1 + include/uv-unix.h | 7 + include/uv-win.h | 23 ++ include/uv.h | 27 +++ src/unix/fs.c | 234 ++++++++++++++++++- src/uv-common.c | 41 +++- src/uv-common.h | 2 + src/win/fs.c | 229 ++++++++++++++++++ test/test-fs-readdir.c | 514 +++++++++++++++++++++++++++++++++++++++++ test/test-list.h | 8 + uv.gyp | 1 + 11 files changed, 1072 insertions(+), 15 deletions(-) create mode 100644 test/test-fs-readdir.c diff --git a/Makefile.am b/Makefile.am index 66d92ae869d9..3a63c616a5e4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -157,6 +157,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 a852c40e49ba..eec003828792 100644 --- a/include/uv-unix.h +++ b/include/uv-unix.h @@ -145,6 +145,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 94ef3495243f..867815a7a57a 100644 --- a/include/uv-win.h +++ b/include/uv-win.h @@ -251,6 +251,29 @@ typedef struct uv__dirent_s { char d_name[1]; } uv__dirent_t; +/* + * "handle" is the actual directory handle that is used to perform calls + * to FindFirstFile/FindNextFile. + * + * "find_data" is needed to store the result of the FindFirstFile call that + * happened in uv_fs_opendir so that it can be used in the subsequent + * uv_fs_readdir call. + * + * "need_find_call" is a boolean determining if the next call to uv_fs_readdir + * must call FindNextFile. In uv_fs_opendir, FindFirstFile reads the first + * entry of the directory, and thus the subsequent call to uv_fs_readdir must + * not call FindNextFile, or otherwise it would miss the first directory entry. + * The next uv_fs_readdir calls after the first one will use FindNextFile. + * + * "dirent" is used to hold a buffer large enough for any dirent in the + * directory being read. It avoids allocating for each directory entry. + */ +#define UV_DIR_PRIVATE_FIELDS \ + HANDLE dir_handle; \ + WIN32_FIND_DATAW find_data; \ + BOOL need_find_call; \ + uv__dirent_t* dirent; + #define HAVE_DIRENT_TYPES #define UV__DT_DIR UV_DIRENT_DIR #define UV__DT_FILE UV_DIRENT_FILE diff --git a/include/uv.h b/include/uv.h index cc385d011398..65d99c3f8d2c 100644 --- a/include/uv.h +++ b/include/uv.h @@ -194,6 +194,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; @@ -1092,6 +1093,9 @@ typedef enum { UV_FS_MKDTEMP, UV_FS_RENAME, UV_FS_SCANDIR, + UV_FS_OPENDIR, + UV_FS_READDIR, + UV_FS_CLOSEDIR, UV_FS_LINK, UV_FS_SYMLINK, UV_FS_READLINK, @@ -1100,6 +1104,10 @@ typedef enum { UV_FS_REALPATH } uv_fs_type; +struct uv_dir_s { + UV_DIR_PRIVATE_FIELDS +}; + /* uv_fs_t is a subclass of uv_req_t. */ struct uv_fs_s { UV_REQ_FIELDS @@ -1110,6 +1118,9 @@ struct uv_fs_s { void* ptr; const char* path; uv_stat_t statbuf; /* Stores the result of uv_fs_stat() and uv_fs_fstat(). */ + const uv_dir_t* dir; /* Stores the result of uv_fs_opendir() */ + uv_dirent_t* dirents; + size_t nentries; UV_FS_PRIVATE_FIELDS }; @@ -1162,6 +1173,22 @@ 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); + +UV_EXTERN int uv_fs_opendir(uv_loop_t* loop, + uv_fs_t* req, + const char* path, + uv_fs_cb cb); +UV_EXTERN int uv_fs_readdir(uv_loop_t* loop, + uv_fs_t* req, + const uv_dir_t* dir, + uv_dirent_t dirents[], + size_t nentries, + uv_fs_cb cb); +UV_EXTERN int uv_fs_closedir(uv_loop_t* loop, + uv_fs_t* req, + const uv_dir_t* dir, + uv_fs_cb cb); + UV_EXTERN int uv_fs_stat(uv_loop_t* loop, uv_fs_t* req, const char* path, diff --git a/src/unix/fs.c b/src/unix/fs.c index ac28869c5b88..2b93bf3d6fe8 100644 --- a/src/unix/fs.c +++ b/src/unix/fs.c @@ -136,6 +136,73 @@ static ssize_t uv__fs_fdatasync(uv_fs_t* req) { } +/* + * Computes the required size in bytes for directory entries read from the + * directory identified by the handle "dir". + * + * This code does not trust values of NAME_MAX that are less than + * 255, since some systems (including at least HP-UX) incorrectly + * define it to be a smaller value. + * + * This code was copied from + * http://womble.decadent.org.uk/readdir_r-advisory.html + * and slightly adapted. + */ +static size_t uv__fs_direntry_size(const DIR* dir) { + long name_max; + size_t name_end; + + name_max = -1; + name_end = -1; + +#if defined(HAVE_FPATHCONF) && defined(HAVE_DIRFD) \ + && defined(_PC_NAME_MAX) + name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX); + if (name_max == -1) +# if defined(NAME_MAX) + name_max = (NAME_MAX > 255) ? NAME_MAX : 255; +# else + return (size_t)(-1); +# endif /* defined(NAME_MAX) +#else +# if defined(NAME_MAX) + name_max = (NAME_MAX > 255) ? NAME_MAX : 255; +# else +# error "buffer size for readdir_r cannot be determined" +# endif /* defined(NAME_MAX) */ +#endif + + /* + * Most of the time, struct dirent is defined by operating systems' header + * files as either one of the following possible implementations: + * 1) + * + * struct dirent { + * other stuff; + * char d_name[ NAME_MAX + 1 ]; + * }; + * + * 2) + * + * struct dirent { + * other stuff; + * char d_name[1]; + * }; + * + * In case 1, using sizeof(struct dirent) to compute the size for the buffer + * would account for the path name. In case 2), it would not. + * + * The following test ensures that this function always allocate enough + * memory to hold the longest path names that can exist for a given + * directory, regardless of how system headers implement struct dirent. + */ + name_end = (size_t)offsetof(struct dirent, d_name) + name_max + 1; + if (name_end > sizeof(struct dirent)) + return name_end; + return sizeof(struct dirent); +} + + static ssize_t uv__fs_futime(uv_fs_t* req) { #if defined(__linux__) /* utimesat() has nanosecond resolution but we stick to microseconds @@ -370,10 +437,10 @@ static ssize_t uv__fs_scandir(uv_fs_t* req) { if (dents != NULL) { int i; - /* Memory was allocated using the system allocator, so use free() here. */ + /* Memory was allocated using the system allocator, so use uv__free() here. */ for (i = 0; i < n; i++) - free(dents[i]); - free(dents); + uv__free(dents[i]); + uv__free(dents); } errno = saved_errno; @@ -382,6 +449,116 @@ static ssize_t uv__fs_scandir(uv_fs_t* req) { return n; } +static int uv__fs_opendir(uv_fs_t* req) { + DIR *dir_stream; + uv__dirent_t *dirent; + uv_dir_t *dir; + size_t len; + + assert(req); + + dir_stream = opendir(req->path); + if (dir_stream == NULL) + return -1; + + len = uv__fs_direntry_size(dir_stream); + if (len == (size_t)-1) + return -1; + + /* + * Allocate the space for each directory entry just once instead of + * once per directory entry. + */ + dirent = uv__malloc(len + 1); + if (dirent == NULL) { + errno = ENOMEM; + goto failed; + } + + dir = uv__malloc(sizeof(*dir)); + if (dir == NULL) { + errno = ENOMEM; + goto failed; + } + + dir->dir = dir_stream; + dir->dirent = dirent; + + req->dir = dir; + req->ptr = (void*)req->dir; + + return 0; + +failed: + if (errno == ENOMEM) { + if (dirent != NULL) + uv__free(dirent); + if (dir != NULL) + uv__free(dir); + } + + return -1; +} + +static int uv__fs_readdir(uv_fs_t* req) { + uv_dirent_t *dirents; + struct dirent *res; + int r; + unsigned int dirent_idx; + + assert(req); + + assert(req->dir); + assert(req->dir->dir); + assert(req->dir->dirent); + + assert(req->dirents); + assert(req->nentries > 0); + + dirents = req->dirents; + req->ptr = req->dirents; + + for (dirent_idx = 0; dirent_idx < req->nentries; ++dirent_idx) { + r = readdir_r(req->dir->dir, req->dir->dirent, &res); + + if (r == 0) { + if (res != NULL) { + dirents[dirent_idx].name = uv__strdup(req->dir->dirent->d_name); + if (dirents[dirent_idx].name != NULL) { + dirents[dirent_idx].type = uv__fs_get_dirent_type(req->dir->dirent); + } else { + errno = ENOMEM; + return -1; + } + } else { + return dirent_idx; + } + } + } + + return dirent_idx; +} + +static int uv__fs_closedir(uv_fs_t* req) { + assert(req); + assert(req->dir); + + if (req->dir) { + if (req->dir->dir) { + closedir(req->dir->dir); + ((uv_dir_t*)req->dir)->dir = NULL; + } + + if (req->dir->dirent) { + uv__free(req->dir->dirent); + ((uv_dir_t*)req->dir)->dirent = NULL; + } + } + + req->ptr = NULL; + return 0; +} + static ssize_t uv__fs_pathmax_size(const char* path) { ssize_t pathmax; @@ -924,6 +1101,9 @@ static void uv__fs_work(struct uv__work* w) { X(OPEN, uv__fs_open(req)); X(READ, uv__fs_buf_iter(req, uv__fs_read)); X(SCANDIR, uv__fs_scandir(req)); + X(OPENDIR, uv__fs_opendir(req)); + X(READDIR, uv__fs_readdir(req)); + X(CLOSEDIR, uv__fs_closedir(req)); X(READLINK, uv__fs_readlink(req)); X(REALPATH, uv__fs_realpath(req)); X(RENAME, rename(req->path, req->new_path)); @@ -1185,6 +1365,40 @@ int uv_fs_scandir(uv_loop_t* loop, POST; } +int uv_fs_opendir(uv_loop_t* loop, + uv_fs_t* req, + const char* path, + uv_fs_cb cb) { + + INIT(OPENDIR); + PATH; + POST; +} + +int uv_fs_readdir(uv_loop_t* loop, + uv_fs_t* req, + const uv_dir_t* dir, + uv_dirent_t dirents[], + size_t nentries, + uv_fs_cb cb) { + INIT(READDIR); + + req->dir = dir; + req->dirents = dirents; + req->nentries = nentries; + + POST; +} + +int uv_fs_closedir(uv_loop_t* loop, + uv_fs_t* req, + const uv_dir_t* dir, + uv_fs_cb cb) { + INIT(CLOSEDIR); + req->dir = dir; + + POST; +} int uv_fs_readlink(uv_loop_t* loop, uv_fs_t* req, @@ -1319,7 +1533,7 @@ void uv_fs_req_cleanup(uv_fs_t* req) { * exception to the rule, it always allocates memory. */ if (req->path != NULL && (req->cb != NULL || req->fs_type == UV_FS_MKDTEMP)) - uv__free((void*) req->path); /* Memory is shared with req->new_path. */ + uv__free(req->path); /* Memory is shared with req->new_path. */ req->path = NULL; req->new_path = NULL; @@ -1327,7 +1541,17 @@ 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); + + /* + * Do not free the results of OPENDIR or READDIR requests, + * as they point to data that needs to be reused for subsequent + * calls. + */ + if (req->fs_type != UV_FS_READDIR && + req->fs_type != UV_FS_OPENDIR && + req->ptr != &req->statbuf) uv__free(req->ptr); req->ptr = NULL; } diff --git a/src/uv-common.c b/src/uv-common.c index 434a5029cda7..1f78cce96477 100644 --- a/src/uv-common.c +++ b/src/uv-common.c @@ -531,37 +531,58 @@ int uv_fs_scandir_next(uv_fs_t* req, uv_dirent_t* ent) { dent = dents[(*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) { + unsigned int dirent_idx; + + if (req && req->ptr) { + uv_dirent_t* dirents = req->ptr; + for (dirent_idx = 0; dirent_idx < req->nentries; ++dirent_idx) { + if (dirents[dirent_idx].name) + free((char*)dirents[dirent_idx].name); + dirents[dirent_idx].name = NULL; + } + } } diff --git a/src/uv-common.h b/src/uv-common.h index 041e6bbdcb6d..0fbdc48da387 100644 --- a/src/uv-common.h +++ b/src/uv-common.h @@ -114,6 +114,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/src/win/fs.c b/src/win/fs.c index e1670a082c43..a7fd4941c2a7 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -1031,6 +1031,169 @@ void fs__scandir(uv_fs_t* req) { uv__free(dirents); } +void fs__opendir(uv_fs_t* req) { + WCHAR* pathw = req->pathw; + size_t len = wcslen(pathw); + HANDLE dir_handle; + WIN32_FIND_DATAW ent = { 0 }; + WCHAR* path2; + const WCHAR* fmt; + uv_dir_t* dir = NULL; + + /* 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; + } + + dir = malloc(sizeof(*dir)); + if (dir == NULL) { + uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); + } + memset(dir, 0, sizeof(*dir)); + + if (len == 0) { + fmt = L"./*"; + } else if (pathw[len - 1] == L'/' || pathw[len - 1] == L'\\') { + fmt = L"%s*"; + } else { + fmt = L"%s\\*"; + } + + path2 = (WCHAR*)malloc(sizeof(WCHAR) * (len + 4)); + if (!path2) { + SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); + return; + } + + _snwprintf(path2, len + 3, fmt, pathw); + dir_handle = FindFirstFileW(path2, &dir->find_data); + free(path2); + + if(dir_handle == INVALID_HANDLE_VALUE) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + return; + } + + dir->dir_handle = dir_handle; + dir->need_find_call = FALSE; + + req->dir = dir; + req->ptr = (void*)req->dir; + + req->result = 0; +} + +void fs__readdir(uv_fs_t* req) { + uv__dirent_t* dent = NULL; + uv_dirent_t* dirents = NULL; + const uv_dir_t* dir = NULL; + size_t nb_entries_to_read = 0; + unsigned int dirent_idx = 0; + HANDLE dir_handle; + int utf8_len; + size_t len; + WCHAR* name = NULL; + PWIN32_FIND_DATAW find_data = NULL; + + assert(req); + if (req) + dir = req->dir; + + assert(req->nentries > 0); + nb_entries_to_read = req->nentries; + + assert(req->dirents); + dirents = req->dirents; + memset(dirents, 0, nb_entries_to_read * sizeof(uv_dirent_t)); + + assert(dir); + if (dir) { + dir_handle = dir->dir_handle; + find_data = &((uv_dir_t*)dir)->find_data; + } + + while (dirent_idx < nb_entries_to_read) { + if (dir->need_find_call) { + if (!FindNextFileW(dir_handle, find_data)) { + break; + } + } + + name = find_data->cFileName; + + /* 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 ((find_data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + dent->d_type = UV__DT_DIR; + else if ((find_data->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) + dent->d_type = UV__DT_LINK; + else + dent->d_type = UV__DT_FILE; + + dirents[dirent_idx].name = strdup(dent->d_name); + if (!dirents[dirent_idx].name) { + SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); + goto fatal; + } + + dirents[dirent_idx].type = uv__fs_get_dirent_type(dent); + + ((uv_dir_t*)dir)->need_find_call = TRUE; + if (dent) { + free(dent); + dent = NULL; + } + + ++dirent_idx; + } + + req->result = dirent_idx; + req->ptr = req->dirents; + +fatal: + if (dent) { + free(dent); + dent = NULL; + } + dent = NULL; +} + +void fs__closedir(uv_fs_t* req) { + assert(req); + assert(req->dir); + + if (req && req->dir) { + if (FindClose(req->dir->dir_handle)) { + req->result = 0; + } else { + SET_REQ_WIN32_ERROR(req, GetLastError()); + } + } +} INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf) { FILE_ALL_INFORMATION file_info; @@ -1834,6 +1997,9 @@ static void uv__fs_work(struct uv__work* w) { XX(MKDTEMP, mkdtemp) XX(RENAME, rename) XX(SCANDIR, scandir) + XX(READDIR, readdir) + XX(OPENDIR, opendir) + XX(CLOSEDIR, closedir) XX(LINK, link) XX(SYMLINK, symlink) XX(READLINK, readlink) @@ -1875,6 +2041,9 @@ void uv_fs_req_cleanup(uv_fs_t* req) { uv__free(req->ptr); } + if (req->fs_type == UV_FS_READDIR) + uv__fs_readdir_cleanup(req); + req->path = NULL; req->file.pathw = NULL; req->fs.info.new_pathw = NULL; @@ -2100,6 +2269,66 @@ int uv_fs_scandir(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, } } +int uv_fs_opendir(uv_loop_t* loop, + uv_fs_t* req, + const char* path, + uv_fs_cb cb) { + int err; + + uv_fs_req_init(loop, req, UV_FS_OPENDIR, cb); + + err = fs__capture_path(loop, req, path, NULL, cb != NULL); + if (err) { + return uv_translate_sys_error(err); + } + + if (cb) { + QUEUE_FS_TP_JOB(loop, req); + return 0; + } else { + fs__opendir(req); + return req->result; + } +} + +UV_EXTERN int uv_fs_readdir(uv_loop_t* loop, + uv_fs_t* req, + const uv_dir_t* dir, + uv_dirent_t dirents[], + size_t nentries, + uv_fs_cb cb) { + uv_fs_req_init(loop, req, UV_FS_READDIR, cb); + + req->dir = dir; + req->dirents = dirents; + req->nentries = nentries; + + if (cb) { + QUEUE_FS_TP_JOB(loop, req); + return 0; + } else { + fs__readdir(req); + return req->result; + } +} + +int uv_fs_closedir(uv_loop_t* loop, + uv_fs_t* req, + const uv_dir_t* dir, + uv_fs_cb cb) { + + uv_fs_req_init(loop, req, UV_FS_CLOSEDIR, cb); + + req->dir = dir; + + if (cb) { + QUEUE_FS_TP_JOB(loop, req); + return 0; + } else { + fs__closedir(req); + return req->result; + } +} int uv_fs_link(uv_loop_t* loop, uv_fs_t* req, const char* path, const char* new_path, uv_fs_cb cb) { diff --git a/test/test-fs-readdir.c b/test/test-fs-readdir.c new file mode 100644 index 000000000000..8b3a195b62be --- /dev/null +++ b/test/test-fs-readdir.c @@ -0,0 +1,514 @@ +/* Copyright the libuv project. 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 "uv.h" +#include "task.h" + +/* FIXME we shouldn't need to branch in this file */ +#if defined(__unix__) || defined(__POSIX__) || \ + defined(__APPLE__) || defined(_AIX) +#include /* unlink, rmdir, memset */ +#else +# include +# include +# define unlink _unlink +# define rmdir _rmdir +#endif + +#if defined(__sun) || defined(__linux__) +#include /* memset */ +#endif + +#include +#include + +uv_loop_t* loop; + +uv_fs_t opendir_req; +uv_fs_t readdir_req; +uv_fs_t closedir_req; + +uv_dirent_t dirents[1]; + +static int empty_opendir_cb_count; +static int empty_readdir_cb_count; +static int empty_closedir_cb_count; + +static void empty_closedir_cb(uv_fs_t* req) { + ASSERT(req == &closedir_req); + ++empty_closedir_cb_count; +} + +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 0 when reading + an emptry dir. Instead, it returns "." and ".." entries in sequence. + Should this be changed to mimic uv_fs_scandir's behavior? */ + if (req->result != 0) { + ASSERT(req->result == 1); + ASSERT(req->ptr == &dirents); + +#ifdef HAVE_DIRENT_TYPES + // In an empty directory, all entries are directories ("." and "..") + ASSERT(dirents[0].type == UV_DIRENT_DIR); +#else + ASSERT(dirents[0].type == UV_DIRENT_UNKNOWN); +#endif /* HAVE_DIRENT_TYPES */ + + ++empty_readdir_cb_count; + + uv_fs_readdir(uv_default_loop(), + req, + req->dir, + dirents, + sizeof(dirents) / sizeof(dirents[0]), + empty_readdir_cb); + } else { + ASSERT(empty_readdir_cb_count == 2); + uv_fs_closedir(loop, &closedir_req, req->dir, empty_closedir_cb); + + uv_fs_req_cleanup(req); + } +} + +static void empty_opendir_cb(uv_fs_t* req) { + ASSERT(req == &opendir_req); + ASSERT(req->fs_type == UV_FS_OPENDIR); + ASSERT(req->result == 0); + ASSERT(req->dir != NULL); + ASSERT(0 == uv_fs_readdir(uv_default_loop(), + &readdir_req, + req->dir, + dirents, + sizeof(dirents) / sizeof(dirents[0]), + 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_fs_closedir sequence work + * as expected when processing an empty directory. + */ +TEST_IMPL(fs_readdir_empty_dir) { + const char* path; + uv_fs_t mkdir_req; + uv_fs_t rmdir_req; + int r; + int nb_entries_read; + int entry_idx; + size_t entries_count; + const uv_dir_t* dir; + + 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(&opendir_req, 0xdb, sizeof(opendir_req)); + + /* Testing the synchronous flavor */ + r = uv_fs_opendir(loop, + &opendir_req, + path, + NULL); + ASSERT(r == 0); + ASSERT(opendir_req.fs_type == UV_FS_OPENDIR); + ASSERT(opendir_req.result == 0); + ASSERT(opendir_req.ptr != NULL); + + dir = opendir_req.ptr; + uv_fs_req_cleanup(&opendir_req); + + /* Fill the req to ensure that required fields are cleaned up */ + memset(&readdir_req, 0xdb, sizeof(readdir_req)); + entries_count = 0; + nb_entries_read = uv_fs_readdir(loop, + &readdir_req, + dir, + dirents, + sizeof(dirents) / sizeof(dirents[0]), + NULL); + while (0 != nb_entries_read) { + entry_idx = 0; + while (entry_idx < nb_entries_read) { +#ifdef HAVE_DIRENT_TYPES + // In an empty directory, all entries are directories ("." and "..") + ASSERT(dirents[entry_idx].type == UV_DIRENT_DIR); +#else + ASSERT(dirents[entry_idx].type == UV_DIRENT_UNKNOWN); +#endif /* HAVE_DIRENT_TYPES */ + ++entry_idx; + ++entries_count; + } + + nb_entries_read = uv_fs_readdir(loop, + &readdir_req, + dir, + dirents, + sizeof(dirents) / sizeof(dirents[0]), + NULL); + } + + /* + * 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 changed to mimic uv_fs_scandir's behavior? + */ + ASSERT(entries_count == 2); + + uv_fs_req_cleanup(&readdir_req); + + /* Fill the req to ensure that required fields are cleaned up */ + memset(&closedir_req, 0xdb, sizeof(closedir_req)); + uv_fs_closedir(loop, &closedir_req, dir, NULL); + ASSERT(closedir_req.result == 0); + + /* Testing the asynchronous flavor */ + + /* Fill the req to ensure that required fields are cleaned up */ + memset(&opendir_req, 0xdb, sizeof(opendir_req)); + memset(&readdir_req, 0xdb, sizeof(readdir_req)); + memset(&closedir_req, 0xdb, sizeof(closedir_req)); + + r = uv_fs_opendir(loop, + &opendir_req, + path, + empty_opendir_cb); + ASSERT(r == 0); + + ASSERT(empty_opendir_cb_count == 0); + ASSERT(empty_closedir_cb_count == 0); + + uv_run(loop, UV_RUN_DEFAULT); + + ASSERT(empty_opendir_cb_count == 1); + ASSERT(empty_closedir_cb_count == 1); + + uv_fs_rmdir(loop, &rmdir_req, path, NULL); + uv_fs_req_cleanup(&rmdir_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 == &opendir_req); + ASSERT(req->fs_type == UV_FS_OPENDIR); + ASSERT(req->result == UV_ENOENT); + ASSERT(req->ptr == NULL); + + uv_fs_req_cleanup(req); + ++non_existing_opendir_cb_count; +} + +TEST_IMPL(fs_readdir_non_existing_dir) { + 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(&opendir_req, 0xdb, sizeof(opendir_req)); + + /* Testing the synchronous flavor */ + r = uv_fs_opendir(loop, &opendir_req, path, NULL); + + ASSERT(r == UV_ENOENT); + ASSERT(opendir_req.fs_type == UV_FS_OPENDIR); + ASSERT(opendir_req.result == UV_ENOENT); + ASSERT(opendir_req.ptr == NULL); + + uv_fs_req_cleanup(&opendir_req); + + /* Fill the req to ensure that required fields are cleaned up */ + memset(&opendir_req, 0xdb, sizeof(opendir_req)); + + /* Testing the async flavor */ + r = uv_fs_opendir(loop, &opendir_req, path, 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(&opendir_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 == &opendir_req); + ASSERT(req->fs_type == UV_FS_OPENDIR); + ASSERT(req->result == UV_ENOTDIR); + ASSERT(req->ptr == NULL); + + uv_fs_req_cleanup(req); + ++file_opendir_cb_count; +} + +TEST_IMPL(fs_readdir_file) { + 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(&opendir_req, 0xdb, sizeof(opendir_req)); + + /* Testing the synchronous flavor */ + r = uv_fs_opendir(loop, &opendir_req, path, NULL); + + ASSERT(r == UV_ENOTDIR); + ASSERT(opendir_req.fs_type == UV_FS_OPENDIR); + ASSERT(opendir_req.result == UV_ENOTDIR); + ASSERT(opendir_req.ptr == NULL); + + uv_fs_req_cleanup(&opendir_req); + + /* Fill the req to ensure that required fields are cleaned up */ + memset(&opendir_req, 0xdb, sizeof(opendir_req)); + + /* Testing the async flavor */ + r = uv_fs_opendir(loop, &opendir_req, path, 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(&opendir_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 int non_empty_closedir_cb_count; + +static void non_empty_closedir_cb(uv_fs_t* req) { + ASSERT(req == &closedir_req); + ASSERT(req->result == 0); + + ++non_empty_closedir_cb_count; +} + +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 == 0) { + ASSERT(non_empty_readdir_cb_count == 5); + uv_fs_closedir(loop, &closedir_req, req->dir, non_empty_closedir_cb); + } else { + ASSERT(req->result == 1); + ASSERT(req->ptr == dirents); + +#ifdef HAVE_DIRENT_TYPES + if (!strcmp(dirents[0].name, "test_subdir") || + !strcmp(dirents[0].name, ".") || + !strcmp(dirents[0].name, "..")) { + ASSERT(dirents[0].type == UV_DIRENT_DIR); + } else { + ASSERT(dirents[0].type == UV_DIRENT_FILE); + } +#else + ASSERT(dirents[0].type == UV_DIRENT_UNKNOWN); +#endif /* HAVE_DIRENT_TYPES */ + + ++non_empty_readdir_cb_count; + + uv_fs_readdir(uv_default_loop(), + &readdir_req, + req->dir, + dirents, + sizeof(dirents) / sizeof(dirents[0]), + non_empty_readdir_cb); + } + + uv_fs_req_cleanup(req); +} + +static void non_empty_opendir_cb(uv_fs_t* req) { + ASSERT(req == &opendir_req); + ASSERT(req->fs_type == UV_FS_OPENDIR); + ASSERT(req->result == 0); + ASSERT(req->ptr == req->dir); + ASSERT(req->dir != NULL); + ASSERT(0 == uv_fs_readdir(uv_default_loop(), + &readdir_req, + req->ptr, + dirents, + sizeof(dirents) / sizeof(dirents[0]), + non_empty_readdir_cb)); + uv_fs_req_cleanup(req); + ++non_empty_opendir_cb_count; +} + +TEST_IMPL(fs_readdir_non_empty_dir) { + int r; + size_t entries_count; + + uv_fs_t mkdir_req; + uv_fs_t rmdir_req; + uv_fs_t create_req; + uv_fs_t close_req; + + const uv_dir_t* dir; + + loop = uv_default_loop(); + + /* Setup */ + unlink("test_dir/file1"); + unlink("test_dir/file2"); + rmdir("test_dir/test_subdir"); + rmdir("test_dir"); + + r = uv_fs_mkdir(loop, &mkdir_req, "test_dir", 0755, NULL); + ASSERT(r == 0); + + /* Create 2 files synchronously. */ + r = uv_fs_open(loop, &create_req, "test_dir/file1", O_WRONLY | O_CREAT, + S_IWUSR | S_IRUSR, NULL); + ASSERT(r >= 0); + uv_fs_req_cleanup(&create_req); + r = uv_fs_close(loop, &close_req, create_req.result, NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&close_req); + + r = uv_fs_open(loop, &create_req, "test_dir/file2", O_WRONLY | O_CREAT, + S_IWUSR | S_IRUSR, NULL); + ASSERT(r >= 0); + uv_fs_req_cleanup(&create_req); + r = uv_fs_close(loop, &close_req, create_req.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(&opendir_req, 0xdb, sizeof(opendir_req)); + + /* Testing the synchronous flavor */ + r = uv_fs_opendir(loop, &opendir_req, "test_dir", NULL); + + ASSERT(r == 0); + ASSERT(opendir_req.fs_type == UV_FS_OPENDIR); + ASSERT(opendir_req.result == 0); + ASSERT(opendir_req.ptr != NULL); + // 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 changed to mimic uv_fs_scandir's behavior? + entries_count = 0; + dir = opendir_req.ptr; + while (uv_fs_readdir(loop, + &readdir_req, + dir, + dirents, + sizeof(dirents) / sizeof(dirents[0]), + NULL) != 0) { +#ifdef HAVE_DIRENT_TYPES + if (!strcmp(dirents[0].name, "test_subdir") || + !strcmp(dirents[0].name, ".") || + !strcmp(dirents[0].name, "..")) { + ASSERT(dirents[0].type == UV_DIRENT_DIR); + } else { + ASSERT(dirents[0].type == UV_DIRENT_FILE); + } +#else + ASSERT(dirents[0].type == UV_DIRENT_UNKNOWN); +#endif /* HAVE_DIRENT_TYPES */ + ++entries_count; + } + + ASSERT(entries_count == 5); + uv_fs_req_cleanup(&readdir_req); + + /* Fill the req to ensure that required fields are cleaned up */ + memset(&closedir_req, 0xdb, sizeof(closedir_req)); + uv_fs_closedir(loop, &closedir_req, dir, NULL); + ASSERT(closedir_req.result == 0); + + /* Testing the asynchronous flavor */ + + /* Fill the req to ensure that required fields are cleaned up */ + memset(&opendir_req, 0xdb, sizeof(opendir_req)); + + r = uv_fs_opendir(loop, &opendir_req, "test_dir", non_empty_opendir_cb); + ASSERT(r == 0); + + ASSERT(non_empty_opendir_cb_count == 0); + ASSERT(non_empty_closedir_cb_count == 0); + + uv_run(loop, UV_RUN_DEFAULT); + + ASSERT(non_empty_opendir_cb_count == 1); + ASSERT(non_empty_closedir_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 41ed64c6999b..8e010f91150d 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -292,6 +292,10 @@ TEST_DECLARE (fs_event_getpath) TEST_DECLARE (fs_scandir_empty_dir) TEST_DECLARE (fs_scandir_file) TEST_DECLARE (fs_open_dir) +TEST_DECLARE (fs_readdir_empty_dir) +TEST_DECLARE (fs_readdir_non_existing_dir) +TEST_DECLARE (fs_readdir_file) +TEST_DECLARE (fs_readdir_non_empty_dir) TEST_DECLARE (fs_rename_to_existing_file) TEST_DECLARE (fs_write_multiple_bufs) TEST_DECLARE (fs_read_write_null_arguments) @@ -742,6 +746,10 @@ TASK_LIST_START TEST_ENTRY (fs_scandir_empty_dir) TEST_ENTRY (fs_scandir_file) TEST_ENTRY (fs_open_dir) + TEST_ENTRY (fs_readdir_empty_dir) + TEST_ENTRY (fs_readdir_non_existing_dir) + TEST_ENTRY (fs_readdir_file) + TEST_ENTRY (fs_readdir_non_empty_dir) TEST_ENTRY (fs_rename_to_existing_file) TEST_ENTRY (fs_write_multiple_bufs) TEST_ENTRY (fs_write_alotof_bufs) diff --git a/uv.gyp b/uv.gyp index 12a7ebde4f41..52465f34a2ec 100644 --- a/uv.gyp +++ b/uv.gyp @@ -304,6 +304,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-get-passwd.c',