From 20a8e58adb2a3fc2e92ceb6e1ceb6f05d7e0d439 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Sat, 24 Aug 2013 15:46:58 +0200 Subject: [PATCH] windows: reimplement uv_fs_stat using NT syscalls This improves the output of uv_fs_stat: * `st_ctime` now contains the change time, not the creation time. * `st_ino` is now filled in with an fs-specific unique number. * `st_dev` is set to the serial number of the containing file system. * `st_blocks` now gets set. * `st_blksize` is no longer zero, but set to a reasonable default. --- src/win/fs.c | 129 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 95 insertions(+), 34 deletions(-) diff --git a/src/win/fs.c b/src/win/fs.c index e18c43c0..8c35cabe 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -241,7 +241,7 @@ static int is_path_dir(const WCHAR* path) { INLINE static int fs__readlink_handle(HANDLE handle, char** target_ptr, - int64_t* target_len_ptr) { + uint64_t* target_len_ptr) { char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; REPARSE_DATA_BUFFER* reparse_data = (REPARSE_DATA_BUFFER*) buffer; WCHAR *w_target; @@ -809,56 +809,117 @@ void fs__readdir(uv_fs_t* req) { INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf) { - BY_HANDLE_FILE_INFORMATION info; + FILE_ALL_INFORMATION file_info; + FILE_FS_VOLUME_INFORMATION volume_info; + NTSTATUS nt_status; + IO_STATUS_BLOCK io_status; - if (!GetFileInformationByHandle(handle, &info)) { + nt_status = pNtQueryInformationFile(handle, + &io_status, + &file_info, + sizeof file_info, + FileAllInformation); + + /* Buffer overflow (a warning status code) is expected here. */ + if (NT_ERROR(nt_status)) { + SetLastError(pRtlNtStatusToDosError(nt_status)); return -1; } - /* TODO: set st_dev, st_rdev and st_ino to something meaningful. */ - statbuf->st_ino = 0; - statbuf->st_dev = 0; - statbuf->st_rdev = 0; + nt_status = pNtQueryVolumeInformationFile(handle, + &io_status, + &volume_info, + sizeof volume_info, + FileFsVolumeInformation); - statbuf->st_gid = 0; - statbuf->st_uid = 0; + /* Buffer overflow (a warning status code) is expected here. */ + if (NT_ERROR(nt_status)) { + SetLastError(pRtlNtStatusToDosError(nt_status)); + return -1; + } + /* Todo: st_mode should probably always be 0666 for everyone. We might also + * want to report 0777 if the file is a .exe or a directory. + * + * Currently it's based on whether the 'readonly' attribute is set, which + * makes little sense because the semantics are so different: the 'read-only' + * flag is just a way for a user to protect against accidental deleteion, and + * serves no security purpose. Windows uses ACLs for that. + * + * Also people now use uv_fs_chmod() to take away the writable bit for good + * reasons. Windows however just makes the file read-only, which makes it + * impossible to delete the file afterwards, since read-only files can't be + * deleted. + * + * IOW it's all just a clusterfuck and we should think of something that + * makes slighty more sense. + * + * And uv_fs_chmod should probably just fail on windows or be a total no-op. + * There's nothing sensible it can do anyway. + */ statbuf->st_mode = 0; - statbuf->st_blksize = 0; - statbuf->st_blocks = 0; - - statbuf->st_flags = 0; - statbuf->st_gen = 0; - - if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { - if (fs__readlink_handle(handle, NULL, &statbuf->st_size) != 0) { - return -1; - } + if (file_info.BasicInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { statbuf->st_mode |= S_IFLNK; - } else if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if (fs__readlink_handle(handle, NULL, &statbuf->st_size) != 0) + return -1; + + } else if (file_info.BasicInformation.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { statbuf->st_mode |= _S_IFDIR; statbuf->st_size = 0; + } else { statbuf->st_mode |= _S_IFREG; - statbuf->st_size = ((int64_t) info.nFileSizeHigh << 32) + - (int64_t) info.nFileSizeLow; + statbuf->st_size = file_info.StandardInformation.EndOfFile.QuadPart; } - if (info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) { - statbuf->st_mode |= (_S_IREAD + (_S_IREAD >> 3) + (_S_IREAD >> 6)); - } else { - statbuf->st_mode |= ((_S_IREAD|_S_IWRITE) + ((_S_IREAD|_S_IWRITE) >> 3) + - ((_S_IREAD|_S_IWRITE) >> 6)); - } + if (file_info.BasicInformation.FileAttributes & FILE_ATTRIBUTE_READONLY) + statbuf->st_mode |= _S_IREAD | (_S_IREAD >> 3) | (_S_IREAD >> 6); + else + statbuf->st_mode |= (_S_IREAD | _S_IWRITE) | ((_S_IREAD | _S_IWRITE) >> 3) | + ((_S_IREAD | _S_IWRITE) >> 6); - FILETIME_TO_TIMESPEC(statbuf->st_mtim, info.ftLastWriteTime); - FILETIME_TO_TIMESPEC(statbuf->st_atim, info.ftLastAccessTime); - FILETIME_TO_TIMESPEC(statbuf->st_ctim, info.ftCreationTime); - FILETIME_TO_TIMESPEC(statbuf->st_birthtim, info.ftCreationTime); + FILETIME_TO_TIMESPEC(statbuf->st_atim, file_info.BasicInformation.LastAccessTime); + FILETIME_TO_TIMESPEC(statbuf->st_ctim, file_info.BasicInformation.ChangeTime); + FILETIME_TO_TIMESPEC(statbuf->st_mtim, file_info.BasicInformation.LastWriteTime); + FILETIME_TO_TIMESPEC(statbuf->st_birthtim, file_info.BasicInformation.CreationTime); - statbuf->st_nlink = (info.nNumberOfLinks <= SHRT_MAX) ? - (short) info.nNumberOfLinks : SHRT_MAX; + statbuf->st_ino = file_info.InternalInformation.IndexNumber.QuadPart; + + /* st_blocks contains the on-disk allocation size in 512-byte units. */ + statbuf->st_blocks = + file_info.StandardInformation.AllocationSize.QuadPart >> 9ULL; + + statbuf->st_nlink = file_info.StandardInformation.NumberOfLinks; + + statbuf->st_dev = volume_info.VolumeSerialNumber; + + /* The st_blksize is supposed to be the 'optimal' number of bytes for reading + * and writing to the disk. That is, for any definition of 'optimal' - it's + * supposed to at least avoid read-update-write behavior when writing to the + * disk. + * + * However nobody knows this and even fewer people actually use this value, + * and in order to fill it out we'd have to make another syscall to query the + * volume for FILE_FS_SECTOR_SIZE_INFORMATION. + * + * Therefore we'll just report a sensible value that's quite commonly okay + * on modern hardware. + */ + statbuf->st_blksize = 2048; + + /* Todo: set st_flags to something meaningful. Also provide a wrapper for + * chattr(2). + */ + statbuf->st_flags = 0; + + /* Windows has nothing sensible to say about these values, so they'll just + * remain empty. + */ + statbuf->st_gid = 0; + statbuf->st_uid = 0; + statbuf->st_rdev = 0; + statbuf->st_gen = 0; return 0; }