summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2023-03-15 21:35:13 -0400
committerGitHub <noreply@github.com>2023-03-16 02:35:13 +0100
commit48a0b7f98f568bb5c3a15b487459569e38e4c671 (patch)
treed7a7aba5da5a019874b1c8cd534eb2b22b86b1a9
parent92c3ac30346fddc138a5d83cb67c87ac23e69dae (diff)
feat(fs): support FileInfo.dev on Windows (#18073)
This commit adds support for retrieving `dev` information when stating files on Windows. Additionally `Deno.FileInfo` interfaces was changed to always return 0 for fields that we don't retrieve information for on Windows. Closes https://github.com/denoland/deno/issues/18053 --------- Co-authored-by: David Sherret <dsherret@gmail.com>
-rw-r--r--cli/tests/unit/stat_test.ts20
-rw-r--r--cli/tsc/dts/lib.deno.ns.d.ts36
-rw-r--r--ext/fs/30_fs.js59
-rw-r--r--ext/fs/lib.rs171
4 files changed, 203 insertions, 83 deletions
diff --git a/cli/tests/unit/stat_test.ts b/cli/tests/unit/stat_test.ts
index 50149cae6..572e54e25 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 1ad67ac88..472c147d0 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 075707ef2..72123f630 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 522dea3fb..746d40dff 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<FsStat, AnyError> {
+ 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<FsStat, AnyError> {
+ 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<u64> {
+ use winapi::shared::minwindef::FALSE;
+
+ let info = {
+ let mut info =
+ std::mem::MaybeUninit::<BY_HANDLE_FILE_INFORMATION>::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<P>(
state: &mut OpState,
@@ -1265,15 +1418,8 @@ where
state
.borrow_mut::<P>()
.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()