From c68ca444e72fc7711eec4e71a8bb8feaffbbefc2 Mon Sep 17 00:00:00 2001 From: Santiago Gimeno Date: Mon, 2 Feb 2026 10:04:36 +0100 Subject: [PATCH] win: refactor to support large statfs blocks (#5016) Accomplish this by replacing `GetDiskFreeSpaceW()` with `NtQueryVolumeInformationFile()` which allows us to represent blocks larger than 2^32 - 1 via `FILE_FS_FULL_SIZE_INFORMATION.TotalAllocationUnits`. Expanded `fs_statfs` test to check that `uv_fs_statfs()` also works with files, meaning https://github.com/libuv/libuv/issues/2683 remains fixed without the need of https://github.com/libuv/libuv/pull/2695. --- src/win/fs.c | 93 +++++++++++++++++--------------------------------- test/test-fs.c | 28 ++++++++++++--- 2 files changed, 55 insertions(+), 66 deletions(-) diff --git a/src/win/fs.c b/src/win/fs.c index cb498608e..4092de0ab 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -3109,67 +3109,35 @@ static void fs__lchown(uv_fs_t* req) { static void fs__statfs(uv_fs_t* req) { + FILE_FS_FULL_SIZE_INFORMATION info; + IO_STATUS_BLOCK io_status; uv_statfs_t* stat_fs; - DWORD sectors_per_cluster; - DWORD bytes_per_sector; - DWORD free_clusters; - DWORD total_clusters; - WCHAR* pathw; + NTSTATUS nt_status; + HANDLE handle; - pathw = req->file.pathw; -retry_get_disk_free_space: - if (0 == GetDiskFreeSpaceW(pathw, - §ors_per_cluster, - &bytes_per_sector, - &free_clusters, - &total_clusters)) { - DWORD err; - WCHAR* fpart; - size_t len; - DWORD ret; - BOOL is_second; + handle = CreateFileW(req->file.pathw, + FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); - err = GetLastError(); - is_second = pathw != req->file.pathw; - if (err != ERROR_DIRECTORY || is_second) { - if (is_second) - uv__free(pathw); - - SET_REQ_WIN32_ERROR(req, err); - return; - } - - len = MAX_PATH + 1; - pathw = uv__malloc(len * sizeof(*pathw)); - if (pathw == NULL) { - SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); - return; - } -retry_get_full_path_name: - ret = GetFullPathNameW(req->file.pathw, - len, - pathw, - &fpart); - if (ret == 0) { - uv__free(pathw); - SET_REQ_WIN32_ERROR(req, err); - return; - } else if (ret > len) { - len = ret; - pathw = uv__reallocf(pathw, len * sizeof(*pathw)); - if (pathw == NULL) { - SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); - return; - } - goto retry_get_full_path_name; - } - if (fpart != 0) - *fpart = L'\0'; - - goto retry_get_disk_free_space; + if (handle == INVALID_HANDLE_VALUE) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + return; } - if (pathw != req->file.pathw) { - uv__free(pathw); + + nt_status = pNtQueryVolumeInformationFile(handle, + &io_status, + &info, + sizeof info, + FileFsFullSizeInformation); + CloseHandle(handle); + + if (NT_ERROR(nt_status)) { + SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(nt_status)); + return; } stat_fs = uv__malloc(sizeof(*stat_fs)); @@ -3179,11 +3147,12 @@ retry_get_full_path_name: } stat_fs->f_type = 0; - stat_fs->f_bsize = bytes_per_sector * sectors_per_cluster; - stat_fs->f_frsize = 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_bsize = (uint64_t)info.SectorsPerAllocationUnit * + info.BytesPerSector; + stat_fs->f_frsize = stat_fs->f_bsize; + stat_fs->f_blocks = info.TotalAllocationUnits.QuadPart; + stat_fs->f_bfree = info.ActualAvailableAllocationUnits.QuadPart; + stat_fs->f_bavail = info.CallerAvailableAllocationUnits.QuadPart; stat_fs->f_files = 0; stat_fs->f_ffree = 0; req->ptr = stat_fs; diff --git a/test/test-fs.c b/test/test-fs.c index 5f3265fe3..817a48464 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -4915,21 +4915,41 @@ TEST_FS_IMPL(fs_invalid_mkdir_name) { TEST_FS_IMPL(fs_statfs) { uv_fs_t req; + uv_fs_t req1; int r; + /* Setup. */ + unlink("test_file"); + + r = uv_fs_open(NULL, &req, "test_file", UV_FS_O_WRONLY | UV_FS_O_CREAT, + S_IRUSR | S_IWUSR, NULL); + ASSERT_GT(r, 0); + + uv_fs_req_cleanup(&req); + + r = uv_fs_close(NULL, &req, req.result, NULL); + ASSERT_OK(r); + + uv_fs_req_cleanup(&req); + loop = uv_default_loop(); - /* Test the synchronous version. */ + /* Test the synchronous version for both a directory and a file. */ r = uv_fs_statfs(NULL, &req, ".", NULL); ASSERT_OK(r); statfs_cb(&req); - ASSERT_EQ(1, statfs_cb_count); + r = uv_fs_statfs(NULL, &req, "test_file", NULL); + ASSERT_OK(r); + statfs_cb(&req); + ASSERT_EQ(2, statfs_cb_count); - /* Test the asynchronous version. */ + /* Test the asynchronous version too. */ r = uv_fs_statfs(loop, &req, ".", statfs_cb); ASSERT_OK(r); + r = uv_fs_statfs(loop, &req1, "test_file", statfs_cb); + ASSERT_OK(r); uv_run(loop, UV_RUN_DEFAULT); - ASSERT_EQ(2, statfs_cb_count); + ASSERT_EQ(4, statfs_cb_count); MAKE_VALGRIND_HAPPY(loop); return 0;