diff --git a/cli/tests/unit/stat_test.ts b/cli/tests/unit/stat_test.ts index 50149cae66..572e54e257 100644 --- a/cli/tests/unit/stat_test.ts +++ b/cli/tests/unit/stat_test.ts @@ -291,22 +291,22 @@ Deno.test( ignore: Deno.build.os !== "windows", permissions: { read: true, write: true }, }, - function statNoUnixFields() { + function statUnixFieldsOnWindows() { const enc = new TextEncoder(); const data = enc.encode("Hello"); const tempDir = Deno.makeTempDirSync(); const filename = tempDir + "/test.txt"; Deno.writeFileSync(filename, data, { mode: 0o666 }); const s = Deno.statSync(filename); - assert(s.dev === null); - assert(s.ino === null); - assert(s.mode === null); - assert(s.nlink === null); - assert(s.uid === null); - assert(s.gid === null); - assert(s.rdev === null); - assert(s.blksize === null); - assert(s.blocks === null); + assert(s.dev !== 0); + assert(s.ino === 0); + assert(s.mode === 0); + assert(s.nlink === 0); + assert(s.uid === 0); + assert(s.gid === 0); + assert(s.rdev === 0); + assert(s.blksize === 0); + assert(s.blocks === 0); }, ); diff --git a/cli/tsc/dts/lib.deno.ns.d.ts b/cli/tsc/dts/lib.deno.ns.d.ts index 1ad67ac88a..472c147d01 100644 --- a/cli/tsc/dts/lib.deno.ns.d.ts +++ b/cli/tsc/dts/lib.deno.ns.d.ts @@ -3078,43 +3078,41 @@ declare namespace Deno { * field from `stat` on Mac/BSD and `ftCreationTime` on Windows. This may * not be available on all platforms. */ birthtime: Date | null; - /** ID of the device containing the file. - * - * _Linux/Mac OS only._ */ - dev: number | null; + /** ID of the device containing the file. */ + dev: number; /** Inode number. * - * _Linux/Mac OS only._ */ - ino: number | null; + * _Linux/Mac OS only, always returns 0 on Windows_ */ + ino: number; /** **UNSTABLE**: Match behavior with Go on Windows for `mode`. * * The underlying raw `st_mode` bits that contain the standard Unix * permissions for this file/directory. */ - mode: number | null; + mode: number; /** Number of hard links pointing to this file. * - * _Linux/Mac OS only._ */ - nlink: number | null; + * _Linux/Mac OS only, always returns 0 on Windows_ */ + nlink: number; /** User ID of the owner of this file. * - * _Linux/Mac OS only._ */ - uid: number | null; + * _Linux/Mac OS only, always returns 0 on Windows_ */ + uid: number; /** Group ID of the owner of this file. * - * _Linux/Mac OS only._ */ - gid: number | null; + * _Linux/Mac OS only, always returns 0 on Windows_ */ + gid: number; /** Device ID of this file. * - * _Linux/Mac OS only._ */ - rdev: number | null; + * _Linux/Mac OS only, always returns 0 on Windows_ */ + rdev: number; /** Blocksize for filesystem I/O. * - * _Linux/Mac OS only._ */ - blksize: number | null; + * _Linux/Mac OS only, always returns 0 on Windows_ */ + blksize: number; /** Number of blocks allocated to the file, in 512-byte units. * - * _Linux/Mac OS only._ */ - blocks: number | null; + * _Linux/Mac OS only, always returns 0 on Windows_ */ + blocks: number; } /** Resolves to the absolute normalized path, with symbolic links resolved. diff --git a/ext/fs/30_fs.js b/ext/fs/30_fs.js index 075707ef23..72123f6307 100644 --- a/ext/fs/30_fs.js +++ b/ext/fs/30_fs.js @@ -211,31 +211,16 @@ async function rename(oldpath, newpath) { // 3. u64 // offset += 2 // high u32 | low u32 -// -// 4. ?u64 converts a zero u64 value to JS null on Windows. function createByteStruct(types) { // types can be "date", "bool" or "u64". - // `?` prefix means optional on windows. let offset = 0; - let str = - 'const unix = Deno.build.os === "darwin" || Deno.build.os === "linux"; return {'; + let str = "return {"; const typeEntries = ObjectEntries(types); for (let i = 0; i < typeEntries.length; ++i) { - let { 0: name, 1: type } = typeEntries[i]; - - const optional = type.startsWith("?"); - if (optional) type = type.slice(1); + const { 0: name, 1: type } = typeEntries[i]; if (type == "u64") { - if (!optional) { - str += `${name}: view[${offset}] + view[${offset + 1}] * 2**32,`; - } else { - str += `${name}: (unix ? (view[${offset}] + view[${ - offset + 1 - }] * 2**32) : (view[${offset}] + view[${ - offset + 1 - }] * 2**32) || null),`; - } + str += `${name}: view[${offset}] + view[${offset + 1}] * 2**32,`; } else if (type == "date") { str += `${name}: view[${offset}] === 0 ? null : new Date(view[${ offset + 2 @@ -259,19 +244,18 @@ const { 0: statStruct, 1: statBuf } = createByteStruct({ mtime: "date", atime: "date", birthtime: "date", - dev: "?u64", - ino: "?u64", - mode: "?u64", - nlink: "?u64", - uid: "?u64", - gid: "?u64", - rdev: "?u64", - blksize: "?u64", - blocks: "?u64", + dev: "u64", + ino: "u64", + mode: "u64", + nlink: "u64", + uid: "u64", + gid: "u64", + rdev: "u64", + blksize: "u64", + blocks: "u64", }); function parseFileInfo(response) { - const unix = core.build.os === "darwin" || core.build.os === "linux"; return { isFile: response.isFile, isDirectory: response.isDirectory, @@ -282,16 +266,15 @@ function parseFileInfo(response) { birthtime: response.birthtimeSet !== null ? new Date(response.birthtime) : null, - // Only non-null if on Unix - dev: unix ? response.dev : null, - ino: unix ? response.ino : null, - mode: unix ? response.mode : null, - nlink: unix ? response.nlink : null, - uid: unix ? response.uid : null, - gid: unix ? response.gid : null, - rdev: unix ? response.rdev : null, - blksize: unix ? response.blksize : null, - blocks: unix ? response.blocks : null, + dev: response.dev, + ino: response.ino, + mode: response.mode, + nlink: response.nlink, + uid: response.uid, + gid: response.gid, + rdev: response.rdev, + blksize: response.blksize, + blocks: response.blocks, }; } diff --git a/ext/fs/lib.rs b/ext/fs/lib.rs index 522dea3fb7..746d40dffb 100644 --- a/ext/fs/lib.rs +++ b/ext/fs/lib.rs @@ -1244,6 +1244,68 @@ fn get_stat(metadata: std::fs::Metadata) -> FsStat { } } +#[cfg(windows)] +#[inline(always)] +fn get_stat2(metadata: std::fs::Metadata, dev: u64) -> FsStat { + let (mtime, mtime_set) = to_msec(metadata.modified()); + let (atime, atime_set) = to_msec(metadata.accessed()); + let (birthtime, birthtime_set) = to_msec(metadata.created()); + + FsStat { + is_file: metadata.is_file(), + is_directory: metadata.is_dir(), + is_symlink: metadata.file_type().is_symlink(), + size: metadata.len(), + mtime_set, + mtime, + atime_set, + atime, + birthtime_set, + birthtime, + dev, + ino: 0, + mode: 0, + nlink: 0, + uid: 0, + gid: 0, + rdev: 0, + blksize: 0, + blocks: 0, + } +} + +#[cfg(not(windows))] +#[inline(always)] +fn get_stat2(metadata: std::fs::Metadata) -> FsStat { + #[cfg(unix)] + use std::os::unix::fs::MetadataExt; + let (mtime, mtime_set) = to_msec(metadata.modified()); + let (atime, atime_set) = to_msec(metadata.accessed()); + let (birthtime, birthtime_set) = to_msec(metadata.created()); + + FsStat { + is_file: metadata.is_file(), + is_directory: metadata.is_dir(), + is_symlink: metadata.file_type().is_symlink(), + size: metadata.len(), + mtime_set, + mtime, + atime_set, + atime, + birthtime_set, + birthtime, + dev: metadata.dev(), + ino: metadata.ino(), + mode: metadata.mode(), + nlink: metadata.nlink(), + uid: metadata.uid(), + gid: metadata.gid(), + rdev: metadata.rdev(), + blksize: metadata.blksize(), + blocks: metadata.blocks(), + } +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct StatArgs { @@ -1251,6 +1313,97 @@ pub struct StatArgs { lstat: bool, } +#[cfg(not(windows))] +fn do_stat(path: PathBuf, lstat: bool) -> Result { + let err_mapper = + |err| default_err_mapper(err, format!("stat '{}'", path.display())); + let metadata = if lstat { + std::fs::symlink_metadata(&path).map_err(err_mapper)? + } else { + std::fs::metadata(&path).map_err(err_mapper)? + }; + + Ok(get_stat2(metadata)) +} + +#[cfg(windows)] +fn do_stat(path: PathBuf, lstat: bool) -> Result { + use std::os::windows::prelude::OsStrExt; + + use winapi::um::fileapi::CreateFileW; + use winapi::um::fileapi::OPEN_EXISTING; + use winapi::um::handleapi::CloseHandle; + use winapi::um::handleapi::INVALID_HANDLE_VALUE; + use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS; + use winapi::um::winbase::FILE_FLAG_OPEN_REPARSE_POINT; + use winapi::um::winnt::FILE_SHARE_DELETE; + use winapi::um::winnt::FILE_SHARE_READ; + use winapi::um::winnt::FILE_SHARE_WRITE; + + let err_mapper = + |err| default_err_mapper(err, format!("stat '{}'", path.display())); + let metadata = if lstat { + std::fs::symlink_metadata(&path).map_err(err_mapper)? + } else { + std::fs::metadata(&path).map_err(err_mapper)? + }; + + let (p, file_flags) = if lstat { + ( + path, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, + ) + } else { + (path.canonicalize()?, FILE_FLAG_BACKUP_SEMANTICS) + }; + unsafe { + let mut path: Vec<_> = p.as_os_str().encode_wide().collect(); + path.push(0); + let file_handle = CreateFileW( + path.as_ptr(), + 0, + FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, + std::ptr::null_mut(), + OPEN_EXISTING, + file_flags, + std::ptr::null_mut(), + ); + if file_handle == INVALID_HANDLE_VALUE { + return Err(std::io::Error::last_os_error().into()); + } + + let result = get_dev(file_handle); + CloseHandle(file_handle); + let dev = result?; + + Ok(get_stat2(metadata, dev)) + } +} + +#[cfg(windows)] +use winapi::um::fileapi::GetFileInformationByHandle; +#[cfg(windows)] +use winapi::um::fileapi::BY_HANDLE_FILE_INFORMATION; + +#[cfg(windows)] +unsafe fn get_dev( + handle: winapi::shared::ntdef::HANDLE, +) -> std::io::Result { + use winapi::shared::minwindef::FALSE; + + let info = { + let mut info = + std::mem::MaybeUninit::::zeroed(); + if GetFileInformationByHandle(handle, info.as_mut_ptr()) == FALSE { + return Err(std::io::Error::last_os_error()); + } + + info.assume_init() + }; + + Ok(info.dwVolumeSerialNumber as u64) +} + #[op] fn op_stat_sync

( state: &mut OpState, @@ -1265,15 +1418,8 @@ where state .borrow_mut::

() .check_read(&path, "Deno.statSync()")?; - let err_mapper = - |err| default_err_mapper(err, format!("stat '{}'", path.display())); - let metadata = if lstat { - std::fs::symlink_metadata(&path).map_err(err_mapper)? - } else { - std::fs::metadata(&path).map_err(err_mapper)? - }; - let stat = get_stat(metadata); + let stat = do_stat(path, lstat)?; stat.write(out_buf); Ok(()) @@ -1297,14 +1443,7 @@ where tokio::task::spawn_blocking(move || { debug!("op_stat_async {} {}", path.display(), lstat); - let err_mapper = - |err| default_err_mapper(err, format!("stat '{}'", path.display())); - let metadata = if lstat { - std::fs::symlink_metadata(&path).map_err(err_mapper)? - } else { - std::fs::metadata(&path).map_err(err_mapper)? - }; - Ok(get_stat(metadata)) + do_stat(path, lstat) }) .await .unwrap()