summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
authorƁukasz Czerniawski <33061335+lczerniawski@users.noreply.github.com>2024-11-13 05:35:04 +0100
committerGitHub <noreply@github.com>2024-11-13 13:35:04 +0900
commit7becd83a3828b35331d0fcb82c64146e520f154b (patch)
treeedab8904c24802a3163427d8df9b41aa3cc4c3d1 /ext
parent43812ee8ff0eb2584c7beb18639da14d96d06817 (diff)
feat(ext/fs): add ctime to Deno.stats and use it in node compat layer (#24801)
This PR fixes #24453, by introducing a ctime (using ctime for UNIX and ChangeTime for Windows) to Deno.stats. Co-authored-by: Yoshiya Hinosawa <stibium121@gmail.com>
Diffstat (limited to 'ext')
-rw-r--r--ext/fs/30_fs.js6
-rw-r--r--ext/fs/in_memory_fs.rs2
-rw-r--r--ext/fs/ops.rs4
-rw-r--r--ext/fs/std_fs.rs34
-rw-r--r--ext/io/fs.rs12
-rw-r--r--ext/node/polyfills/_fs/_fs_stat.ts10
6 files changed, 57 insertions, 11 deletions
diff --git a/ext/fs/30_fs.js b/ext/fs/30_fs.js
index c8e19ac75..40513e7e0 100644
--- a/ext/fs/30_fs.js
+++ b/ext/fs/30_fs.js
@@ -346,9 +346,10 @@ const { 0: statStruct, 1: statBuf } = createByteStruct({
mtime: "date",
atime: "date",
birthtime: "date",
+ ctime: "date",
dev: "u64",
ino: "?u64",
- mode: "?u64",
+ mode: "u64",
nlink: "?u64",
uid: "?u64",
gid: "?u64",
@@ -377,9 +378,10 @@ function parseFileInfo(response) {
birthtime: response.birthtimeSet === true
? new Date(response.birthtime)
: null,
+ ctime: response.ctimeSet === true ? new Date(response.ctime) : null,
dev: response.dev,
+ mode: response.mode,
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,
diff --git a/ext/fs/in_memory_fs.rs b/ext/fs/in_memory_fs.rs
index e29b9d50c..34b77836d 100644
--- a/ext/fs/in_memory_fs.rs
+++ b/ext/fs/in_memory_fs.rs
@@ -229,6 +229,7 @@ impl FileSystem for InMemoryFs {
mtime: None,
atime: None,
birthtime: None,
+ ctime: None,
dev: 0,
ino: 0,
mode: 0,
@@ -251,6 +252,7 @@ impl FileSystem for InMemoryFs {
mtime: None,
atime: None,
birthtime: None,
+ ctime: None,
dev: 0,
ino: 0,
mode: 0,
diff --git a/ext/fs/ops.rs b/ext/fs/ops.rs
index 9b76b49e6..3d0d96ce6 100644
--- a/ext/fs/ops.rs
+++ b/ext/fs/ops.rs
@@ -1795,6 +1795,8 @@ create_struct_writer! {
atime: u64,
birthtime_set: bool,
birthtime: u64,
+ ctime_set: bool,
+ ctime: u64,
// Following are only valid under Unix.
dev: u64,
ino: u64,
@@ -1826,6 +1828,8 @@ impl From<FsStat> for SerializableStat {
atime: stat.atime.unwrap_or(0),
birthtime_set: stat.birthtime.is_some(),
birthtime: stat.birthtime.unwrap_or(0),
+ ctime_set: stat.ctime.is_some(),
+ ctime: stat.ctime.unwrap_or(0),
dev: stat.dev,
ino: stat.ino,
diff --git a/ext/fs/std_fs.rs b/ext/fs/std_fs.rs
index 1a83c97c5..73439d9ba 100644
--- a/ext/fs/std_fs.rs
+++ b/ext/fs/std_fs.rs
@@ -821,24 +821,46 @@ fn stat_extra(
Ok(info.dwVolumeSerialNumber as u64)
}
+ const WINDOWS_TICK: i64 = 10_000; // 100-nanosecond intervals in a millisecond
+ const SEC_TO_UNIX_EPOCH: i64 = 11_644_473_600; // Seconds between Windows epoch and Unix epoch
+
+ fn windows_time_to_unix_time_msec(windows_time: &i64) -> i64 {
+ let milliseconds_since_windows_epoch = windows_time / WINDOWS_TICK;
+ milliseconds_since_windows_epoch - SEC_TO_UNIX_EPOCH * 1000
+ }
+
use windows_sys::Wdk::Storage::FileSystem::FILE_ALL_INFORMATION;
+ use windows_sys::Win32::Foundation::NTSTATUS;
unsafe fn query_file_information(
handle: winapi::shared::ntdef::HANDLE,
- ) -> std::io::Result<FILE_ALL_INFORMATION> {
+ ) -> Result<FILE_ALL_INFORMATION, NTSTATUS> {
use windows_sys::Wdk::Storage::FileSystem::NtQueryInformationFile;
+ use windows_sys::Win32::Foundation::RtlNtStatusToDosError;
+ use windows_sys::Win32::Foundation::ERROR_MORE_DATA;
+ use windows_sys::Win32::System::IO::IO_STATUS_BLOCK;
let mut info = std::mem::MaybeUninit::<FILE_ALL_INFORMATION>::zeroed();
+ let mut io_status_block =
+ std::mem::MaybeUninit::<IO_STATUS_BLOCK>::zeroed();
let status = NtQueryInformationFile(
handle as _,
- std::ptr::null_mut(),
+ io_status_block.as_mut_ptr(),
info.as_mut_ptr() as *mut _,
std::mem::size_of::<FILE_ALL_INFORMATION>() as _,
18, /* FileAllInformation */
);
if status < 0 {
- return Err(std::io::Error::last_os_error());
+ let converted_status = RtlNtStatusToDosError(status);
+
+ // If error more data is returned, then it means that the buffer is too small to get full filename information
+ // to have that we should retry. However, since we only use BasicInformation and StandardInformation, it is fine to ignore it
+ // since struct is populated with other data anyway.
+ // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntqueryinformationfile#remarksdd
+ if converted_status != ERROR_MORE_DATA {
+ return Err(converted_status as NTSTATUS);
+ }
}
Ok(info.assume_init())
@@ -862,10 +884,13 @@ fn stat_extra(
}
let result = get_dev(file_handle);
- CloseHandle(file_handle);
fsstat.dev = result?;
if let Ok(file_info) = query_file_information(file_handle) {
+ fsstat.ctime = Some(windows_time_to_unix_time_msec(
+ &file_info.BasicInformation.ChangeTime,
+ ) as u64);
+
if file_info.BasicInformation.FileAttributes
& winapi::um::winnt::FILE_ATTRIBUTE_REPARSE_POINT
!= 0
@@ -898,6 +923,7 @@ fn stat_extra(
}
}
+ CloseHandle(file_handle);
Ok(())
}
}
diff --git a/ext/io/fs.rs b/ext/io/fs.rs
index 885426520..7ef02315b 100644
--- a/ext/io/fs.rs
+++ b/ext/io/fs.rs
@@ -94,6 +94,7 @@ pub struct FsStat {
pub mtime: Option<u64>,
pub atime: Option<u64>,
pub birthtime: Option<u64>,
+ pub ctime: Option<u64>,
pub dev: u64,
pub ino: u64,
@@ -153,6 +154,16 @@ impl FsStat {
}
}
+ #[inline(always)]
+ fn get_ctime(ctime_or_0: i64) -> Option<u64> {
+ if ctime_or_0 > 0 {
+ // ctime return seconds since epoch, but we need milliseconds
+ return Some(ctime_or_0 as u64 * 1000);
+ }
+
+ None
+ }
+
Self {
is_file: metadata.is_file(),
is_directory: metadata.is_dir(),
@@ -162,6 +173,7 @@ impl FsStat {
mtime: to_msec(metadata.modified()),
atime: to_msec(metadata.accessed()),
birthtime: to_msec(metadata.created()),
+ ctime: get_ctime(unix_or_zero!(ctime)),
dev: unix_or_zero!(dev),
ino: unix_or_zero!(ino),
diff --git a/ext/node/polyfills/_fs/_fs_stat.ts b/ext/node/polyfills/_fs/_fs_stat.ts
index d00c81ffb..507cb05ea 100644
--- a/ext/node/polyfills/_fs/_fs_stat.ts
+++ b/ext/node/polyfills/_fs/_fs_stat.ts
@@ -290,8 +290,8 @@ export function convertFileInfoToStats(origin: Deno.FileInfo): Stats {
isFIFO: () => false,
isCharacterDevice: () => false,
isSocket: () => false,
- ctime: origin.mtime,
- ctimeMs: origin.mtime?.getTime() || null,
+ ctime: origin.ctime,
+ ctimeMs: origin.ctime?.getTime() || null,
});
return stats;
@@ -336,9 +336,9 @@ export function convertFileInfoToBigIntStats(
isFIFO: () => false,
isCharacterDevice: () => false,
isSocket: () => false,
- ctime: origin.mtime,
- ctimeMs: origin.mtime ? BigInt(origin.mtime.getTime()) : null,
- ctimeNs: origin.mtime ? BigInt(origin.mtime.getTime()) * 1000000n : null,
+ ctime: origin.ctime,
+ ctimeMs: origin.ctime ? BigInt(origin.ctime.getTime()) : null,
+ ctimeNs: origin.ctime ? BigInt(origin.ctime.getTime()) * 1000000n : null,
});
return stats;
}