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:
parent
92c3ac3034
commit
48a0b7f98f
4 changed files with 203 additions and 83 deletions
|
@ -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);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
36
cli/tsc/dts/lib.deno.ns.d.ts
vendored
36
cli/tsc/dts/lib.deno.ns.d.ts
vendored
|
@ -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.
|
||||||
|
|
|
@ -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,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
171
ext/fs/lib.rs
171
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)]
|
#[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()
|
||||||
|
|
Loading…
Reference in a new issue