1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-11 00:21:05 -05:00

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>
This commit is contained in:
Bartek Iwańczuk 2023-03-15 21:35:13 -04:00 committed by GitHub
parent 92c3ac3034
commit 48a0b7f98f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 203 additions and 83 deletions

View file

@ -291,22 +291,22 @@ Deno.test(
ignore: Deno.build.os !== "windows", ignore: Deno.build.os !== "windows",
permissions: { read: true, write: true }, permissions: { read: true, write: true },
}, },
function statNoUnixFields() { function statUnixFieldsOnWindows() {
const enc = new TextEncoder(); const enc = new TextEncoder();
const data = enc.encode("Hello"); const data = enc.encode("Hello");
const tempDir = Deno.makeTempDirSync(); const tempDir = Deno.makeTempDirSync();
const filename = tempDir + "/test.txt"; const filename = tempDir + "/test.txt";
Deno.writeFileSync(filename, data, { mode: 0o666 }); Deno.writeFileSync(filename, data, { mode: 0o666 });
const s = Deno.statSync(filename); const s = Deno.statSync(filename);
assert(s.dev === null); assert(s.dev !== 0);
assert(s.ino === null); assert(s.ino === 0);
assert(s.mode === null); assert(s.mode === 0);
assert(s.nlink === null); assert(s.nlink === 0);
assert(s.uid === null); assert(s.uid === 0);
assert(s.gid === null); assert(s.gid === 0);
assert(s.rdev === null); assert(s.rdev === 0);
assert(s.blksize === null); assert(s.blksize === 0);
assert(s.blocks === null); assert(s.blocks === 0);
}, },
); );

View file

@ -3078,43 +3078,41 @@ declare namespace Deno {
* field from `stat` on Mac/BSD and `ftCreationTime` on Windows. This may * field from `stat` on Mac/BSD and `ftCreationTime` on Windows. This may
* not be available on all platforms. */ * not be available on all platforms. */
birthtime: Date | null; birthtime: Date | null;
/** ID of the device containing the file. /** ID of the device containing the file. */
* dev: number;
* _Linux/Mac OS only._ */
dev: number | null;
/** Inode number. /** Inode number.
* *
* _Linux/Mac OS only._ */ * _Linux/Mac OS only, always returns 0 on Windows_ */
ino: number | null; ino: number;
/** **UNSTABLE**: Match behavior with Go on Windows for `mode`. /** **UNSTABLE**: Match behavior with Go on Windows for `mode`.
* *
* The underlying raw `st_mode` bits that contain the standard Unix * The underlying raw `st_mode` bits that contain the standard Unix
* permissions for this file/directory. */ * permissions for this file/directory. */
mode: number | null; mode: number;
/** Number of hard links pointing to this file. /** Number of hard links pointing to this file.
* *
* _Linux/Mac OS only._ */ * _Linux/Mac OS only, always returns 0 on Windows_ */
nlink: number | null; nlink: number;
/** User ID of the owner of this file. /** User ID of the owner of this file.
* *
* _Linux/Mac OS only._ */ * _Linux/Mac OS only, always returns 0 on Windows_ */
uid: number | null; uid: number;
/** Group ID of the owner of this file. /** Group ID of the owner of this file.
* *
* _Linux/Mac OS only._ */ * _Linux/Mac OS only, always returns 0 on Windows_ */
gid: number | null; gid: number;
/** Device ID of this file. /** Device ID of this file.
* *
* _Linux/Mac OS only._ */ * _Linux/Mac OS only, always returns 0 on Windows_ */
rdev: number | null; rdev: number;
/** Blocksize for filesystem I/O. /** Blocksize for filesystem I/O.
* *
* _Linux/Mac OS only._ */ * _Linux/Mac OS only, always returns 0 on Windows_ */
blksize: number | null; blksize: number;
/** Number of blocks allocated to the file, in 512-byte units. /** Number of blocks allocated to the file, in 512-byte units.
* *
* _Linux/Mac OS only._ */ * _Linux/Mac OS only, always returns 0 on Windows_ */
blocks: number | null; blocks: number;
} }
/** Resolves to the absolute normalized path, with symbolic links resolved. /** Resolves to the absolute normalized path, with symbolic links resolved.

View file

@ -211,31 +211,16 @@ async function rename(oldpath, newpath) {
// 3. u64 // 3. u64
// offset += 2 // offset += 2
// high u32 | low u32 // high u32 | low u32
//
// 4. ?u64 converts a zero u64 value to JS null on Windows.
function createByteStruct(types) { function createByteStruct(types) {
// types can be "date", "bool" or "u64". // types can be "date", "bool" or "u64".
// `?` prefix means optional on windows.
let offset = 0; let offset = 0;
let str = let str = "return {";
'const unix = Deno.build.os === "darwin" || Deno.build.os === "linux"; return {';
const typeEntries = ObjectEntries(types); const typeEntries = ObjectEntries(types);
for (let i = 0; i < typeEntries.length; ++i) { for (let i = 0; i < typeEntries.length; ++i) {
let { 0: name, 1: type } = typeEntries[i]; const { 0: name, 1: type } = typeEntries[i];
const optional = type.startsWith("?");
if (optional) type = type.slice(1);
if (type == "u64") { if (type == "u64") {
if (!optional) { str += `${name}: view[${offset}] + view[${offset + 1}] * 2**32,`;
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),`;
}
} else if (type == "date") { } else if (type == "date") {
str += `${name}: view[${offset}] === 0 ? null : new Date(view[${ str += `${name}: view[${offset}] === 0 ? null : new Date(view[${
offset + 2 offset + 2
@ -259,19 +244,18 @@ const { 0: statStruct, 1: statBuf } = createByteStruct({
mtime: "date", mtime: "date",
atime: "date", atime: "date",
birthtime: "date", birthtime: "date",
dev: "?u64", dev: "u64",
ino: "?u64", ino: "u64",
mode: "?u64", mode: "u64",
nlink: "?u64", nlink: "u64",
uid: "?u64", uid: "u64",
gid: "?u64", gid: "u64",
rdev: "?u64", rdev: "u64",
blksize: "?u64", blksize: "u64",
blocks: "?u64", blocks: "u64",
}); });
function parseFileInfo(response) { function parseFileInfo(response) {
const unix = core.build.os === "darwin" || core.build.os === "linux";
return { return {
isFile: response.isFile, isFile: response.isFile,
isDirectory: response.isDirectory, isDirectory: response.isDirectory,
@ -282,16 +266,15 @@ function parseFileInfo(response) {
birthtime: response.birthtimeSet !== null birthtime: response.birthtimeSet !== null
? new Date(response.birthtime) ? new Date(response.birthtime)
: null, : null,
// Only non-null if on Unix dev: response.dev,
dev: unix ? response.dev : null, ino: response.ino,
ino: unix ? response.ino : null, mode: response.mode,
mode: unix ? response.mode : null, nlink: response.nlink,
nlink: unix ? response.nlink : null, uid: response.uid,
uid: unix ? response.uid : null, gid: response.gid,
gid: unix ? response.gid : null, rdev: response.rdev,
rdev: unix ? response.rdev : null, blksize: response.blksize,
blksize: unix ? response.blksize : null, blocks: response.blocks,
blocks: unix ? response.blocks : null,
}; };
} }

View file

@ -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)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct StatArgs { pub struct StatArgs {
@ -1251,6 +1313,97 @@ pub struct StatArgs {
lstat: bool, 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] #[op]
fn op_stat_sync<P>( fn op_stat_sync<P>(
state: &mut OpState, state: &mut OpState,
@ -1265,15 +1418,8 @@ where
state state
.borrow_mut::<P>() .borrow_mut::<P>()
.check_read(&path, "Deno.statSync()")?; .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); stat.write(out_buf);
Ok(()) Ok(())
@ -1297,14 +1443,7 @@ where
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
debug!("op_stat_async {} {}", path.display(), lstat); debug!("op_stat_async {} {}", path.display(), lstat);
let err_mapper = do_stat(path, lstat)
|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))
}) })
.await .await
.unwrap() .unwrap()