diff --git a/docs/src/fs.rst b/docs/src/fs.rst index fb8105fb..deaf0b13 100644 --- a/docs/src/fs.rst +++ b/docs/src/fs.rst @@ -102,6 +102,24 @@ Data types UV_FS_CLOSEDIR } uv_fs_type; +.. c:type:: uv_statfs_t + + Reduced cross platform equivalent of ``struct statfs``. + Used in :c:func:`uv_fs_statfs`. + + :: + + typedef struct uv_statfs_s { + uint64_t f_type; + uint64_t f_bsize; + uint64_t f_blocks; + uint64_t f_bfree; + uint64_t f_bavail; + uint64_t f_files; + uint64_t f_ffree; + uint64_t f_spare[4]; + } uv_statfs_t; + .. c:type:: uv_dirent_t Cross platform (reduced) equivalent of ``struct dirent``. @@ -290,6 +308,17 @@ API Equivalent to :man:`stat(2)`, :man:`fstat(2)` and :man:`lstat(2)` respectively. +.. c:function:: int uv_fs_statfs(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) + + Equivalent to :man:`statfs(2)`. On success, a `uv_statfs_t` is allocated + and returned via `req->ptr`. This memory is freed by `uv_fs_req_cleanup()`. + + .. note:: + Any fields in the resulting `uv_statfs_t` that are not supported by the + underlying operating system are set to zero. + + .. versionadded:: 1.31.0 + .. c:function:: int uv_fs_rename(uv_loop_t* loop, uv_fs_t* req, const char* path, const char* new_path, uv_fs_cb cb) Equivalent to :man:`rename(2)`. diff --git a/include/uv.h b/include/uv.h index f97801ce..9157d69a 100644 --- a/include/uv.h +++ b/include/uv.h @@ -236,6 +236,7 @@ typedef struct uv_interface_address_s uv_interface_address_t; typedef struct uv_dirent_s uv_dirent_t; typedef struct uv_passwd_s uv_passwd_t; typedef struct uv_utsname_s uv_utsname_t; +typedef struct uv_statfs_s uv_statfs_t; typedef enum { UV_LOOP_BLOCK_SIGNAL @@ -1070,6 +1071,17 @@ struct uv_utsname_s { to as meaningless in the docs. */ }; +struct uv_statfs_s { + uint64_t f_type; + uint64_t f_bsize; + uint64_t f_blocks; + uint64_t f_bfree; + uint64_t f_bavail; + uint64_t f_files; + uint64_t f_ffree; + uint64_t f_spare[4]; +}; + typedef enum { UV_DIRENT_UNKNOWN, UV_DIRENT_FILE, @@ -1205,7 +1217,8 @@ typedef enum { UV_FS_LCHOWN, UV_FS_OPENDIR, UV_FS_READDIR, - UV_FS_CLOSEDIR + UV_FS_CLOSEDIR, + UV_FS_STATFS } uv_fs_type; struct uv_dir_s { @@ -1433,6 +1446,10 @@ UV_EXTERN int uv_fs_lchown(uv_loop_t* loop, uv_uid_t uid, uv_gid_t gid, uv_fs_cb cb); +UV_EXTERN int uv_fs_statfs(uv_loop_t* loop, + uv_fs_t* req, + const char* path, + uv_fs_cb cb); enum uv_fs_event { diff --git a/src/unix/fs.c b/src/unix/fs.c index 5138c619..5190fd40 100644 --- a/src/unix/fs.c +++ b/src/unix/fs.c @@ -70,6 +70,20 @@ # include #endif +#if defined(__APPLE__) || \ + defined(__DragonFly__) || \ + defined(__FreeBSD__) || \ + defined(__FreeBSD_kernel__) || \ + defined(__OpenBSD__) || \ + defined(__NetBSD__) +# include +# include +#elif defined(__sun) || defined(__MVS__) +# include +#else +# include +#endif + #if defined(_AIX) && _XOPEN_SOURCE <= 600 extern char *mkdtemp(char *template); /* See issue #740 on AIX < 7 */ #endif @@ -519,6 +533,40 @@ static int uv__fs_closedir(uv_fs_t* req) { return 0; } +static int uv__fs_statfs(uv_fs_t* req) { + uv_statfs_t* stat_fs; +#if defined(__sun) || defined(__MVS__) + struct statvfs buf; + + if (0 != statvfs(req->path, &buf)) +#else + struct statfs buf; + + if (0 != statfs(req->path, &buf)) +#endif /* defined(__sun) */ + return -1; + + stat_fs = uv__malloc(sizeof(*stat_fs)); + if (stat_fs == NULL) { + errno = ENOMEM; + return -1; + } + +#if defined(__sun) || defined(__MVS__) + stat_fs->f_type = 0; /* f_type is not supported. */ +#else + stat_fs->f_type = buf.f_type; +#endif + stat_fs->f_bsize = buf.f_bsize; + stat_fs->f_blocks = buf.f_blocks; + stat_fs->f_bfree = buf.f_bfree; + stat_fs->f_bavail = buf.f_bavail; + stat_fs->f_files = buf.f_files; + stat_fs->f_ffree = buf.f_ffree; + req->ptr = stat_fs; + return 0; +} + static ssize_t uv__fs_pathmax_size(const char* path) { ssize_t pathmax; @@ -1386,6 +1434,7 @@ static void uv__fs_work(struct uv__work* w) { X(RMDIR, rmdir(req->path)); X(SENDFILE, uv__fs_sendfile(req)); X(STAT, uv__fs_stat(req->path, &req->statbuf)); + X(STATFS, uv__fs_statfs(req)); X(SYMLINK, symlink(req->path, req->new_path)); X(UNLINK, unlink(req->path)); X(UTIME, uv__fs_utime(req)); @@ -1858,3 +1907,13 @@ int uv_fs_copyfile(uv_loop_t* loop, req->flags = flags; POST; } + + +int uv_fs_statfs(uv_loop_t* loop, + uv_fs_t* req, + const char* path, + uv_fs_cb cb) { + INIT(STATFS); + PATH; + POST; +} diff --git a/src/win/fs.c b/src/win/fs.c index 1f6f343c..83dfdca9 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -2548,6 +2548,41 @@ static void fs__lchown(uv_fs_t* req) { req->result = 0; } + +static void fs__statfs(uv_fs_t* req) { + uv_statfs_t* stat_fs; + DWORD sectors_per_cluster; + DWORD bytes_per_sector; + DWORD free_clusters; + DWORD total_clusters; + + if (0 == GetDiskFreeSpaceW(req->file.pathw, + §ors_per_cluster, + &bytes_per_sector, + &free_clusters, + &total_clusters)) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + return; + } + + stat_fs = uv__malloc(sizeof(*stat_fs)); + if (stat_fs == NULL) { + SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); + return; + } + + stat_fs->f_type = 0; + stat_fs->f_bsize = bytes_per_sector * sectors_per_cluster; + stat_fs->f_blocks = total_clusters; + stat_fs->f_bfree = free_clusters; + stat_fs->f_bavail = free_clusters; + stat_fs->f_files = 0; + stat_fs->f_ffree = 0; + req->ptr = stat_fs; + SET_REQ_RESULT(req, 0); +} + + static void uv__fs_work(struct uv__work* w) { uv_fs_t* req; @@ -2589,6 +2624,7 @@ static void uv__fs_work(struct uv__work* w) { XX(CHOWN, chown) XX(FCHOWN, fchown) XX(LCHOWN, lchown) + XX(STATFS, statfs) default: assert(!"bad uv_fs_type"); } @@ -3100,3 +3136,18 @@ int uv_fs_futime(uv_loop_t* loop, uv_fs_t* req, uv_file fd, double atime, req->fs.time.mtime = mtime; POST; } + + +int uv_fs_statfs(uv_loop_t* loop, + uv_fs_t* req, + const char* path, + uv_fs_cb cb) { + int err; + + INIT(UV_FS_STATFS); + err = fs__capture_path(req, path, NULL, cb != NULL); + if (err) + return uv_translate_sys_error(err); + + POST; +} diff --git a/test/test-fs.c b/test/test-fs.c index 95f6b5e9..0d92b0d3 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -94,6 +94,7 @@ static int readlink_cb_count; static int realpath_cb_count; static int utime_cb_count; static int futime_cb_count; +static int statfs_cb_count; static uv_loop_t* loop; @@ -330,6 +331,38 @@ static void fstat_cb(uv_fs_t* req) { } +static void statfs_cb(uv_fs_t* req) { + uv_statfs_t* stats; + + ASSERT(req->fs_type == UV_FS_STATFS); + ASSERT(req->result == 0); + ASSERT(req->ptr != NULL); + stats = req->ptr; + +#if defined(_WIN32) || defined(__sun) || defined(_AIX) || defined(__MVS__) + ASSERT(stats->f_type == 0); +#else + ASSERT(stats->f_type > 0); +#endif + + ASSERT(stats->f_bsize > 0); + ASSERT(stats->f_blocks > 0); + ASSERT(stats->f_bfree <= stats->f_blocks); + ASSERT(stats->f_bavail <= stats->f_bfree); + +#ifdef _WIN32 + ASSERT(stats->f_files == 0); + ASSERT(stats->f_ffree == 0); +#else + ASSERT(stats->f_files > 0); + ASSERT(stats->f_ffree <= stats->f_files); +#endif + uv_fs_req_cleanup(req); + ASSERT(req->ptr == NULL); + statfs_cb_count++; +} + + static void close_cb(uv_fs_t* req) { int r; ASSERT(req == &close_req); @@ -3817,6 +3850,9 @@ TEST_IMPL(fs_null_req) { r = uv_fs_futime(NULL, NULL, 0, 0.0, 0.0, NULL); ASSERT(r == UV_EINVAL); + r = uv_fs_statfs(NULL, NULL, NULL, NULL); + ASSERT(r == UV_EINVAL); + /* This should be a no-op. */ uv_fs_req_cleanup(NULL); @@ -4073,3 +4109,24 @@ TEST_IMPL(fs_invalid_mkdir_name) { return 0; } #endif + +TEST_IMPL(fs_statfs) { + uv_fs_t req; + int r; + + loop = uv_default_loop(); + + /* Test the synchronous version. */ + r = uv_fs_statfs(NULL, &req, ".", NULL); + ASSERT(r == 0); + statfs_cb(&req); + ASSERT(statfs_cb_count == 1); + + /* Test the asynchronous version. */ + r = uv_fs_statfs(loop, &req, ".", statfs_cb); + ASSERT(r == 0); + uv_run(loop, UV_RUN_DEFAULT); + ASSERT(statfs_cb_count == 2); + + return 0; +} diff --git a/test/test-list.h b/test/test-list.h index ffa7e545..3f94360f 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -329,6 +329,7 @@ TEST_DECLARE (fs_fd_hash) TEST_DECLARE (fs_utime) TEST_DECLARE (fs_futime) TEST_DECLARE (fs_file_open_append) +TEST_DECLARE (fs_statfs) TEST_DECLARE (fs_stat_missing_path) TEST_DECLARE (fs_read_bufs) TEST_DECLARE (fs_read_file_eof) @@ -924,6 +925,7 @@ TASK_LIST_START #if defined(_WIN32) && !defined(USING_UV_SHARED) TEST_ENTRY (fs_fd_hash) #endif + TEST_ENTRY (fs_statfs) TEST_ENTRY (fs_stat_missing_path) TEST_ENTRY (fs_read_bufs) TEST_ENTRY (fs_read_file_eof)