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",
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);
},
);

View file

@ -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.

View file

@ -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,
};
}

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)]
#[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()