Skip to content

Commit

Permalink
fix(ext/node): Add fs.lutimes / fs.lutimesSync (#23172)
Browse files Browse the repository at this point in the history
Part of #18218


- Adds `fs.lutimes` and `fs.lutimesSync` to our node polyfills. To do
this I added methods to the `FileSystem` trait   ops to expose the
functionality to JS.
- Exports `fs._toUnixTimestamp`. Node exposes an internal util
`toUnixTimestamp` from the fs module to be used by unit tests (so we
need it for the unit test to pass unmodified). It's weird because it's
only supposed to be used internally but it's still publicly accessible
- Matches up error handling and timestamp handling for fs.futimes and
fs.utimes with node
- Enables the node_compat utimes test - this exercises futimes, lutimes,
and utimes.
  • Loading branch information
nathanwhit committed Jul 3, 2024
1 parent 3324d72 commit dadc606
Show file tree
Hide file tree
Showing 17 changed files with 511 additions and 19 deletions.
25 changes: 25 additions & 0 deletions cli/standalone/file_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,4 349,29 @@ impl FileSystem for DenoCompileFileSystem {
.utime_async(path, atime_secs, atime_nanos, mtime_secs, mtime_nanos)
.await
}

fn lutime_sync(
&self,
path: &Path,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()> {
self.error_if_in_vfs(path)?;
RealFs.lutime_sync(path, atime_secs, atime_nanos, mtime_secs, mtime_nanos)
}
async fn lutime_async(
&self,
path: PathBuf,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()> {
self.error_if_in_vfs(&path)?;
RealFs
.lutime_async(path, atime_secs, atime_nanos, mtime_secs, mtime_nanos)
.await
}
}
4 changes: 2 additions & 2 deletions ext/fs/30_fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -473,8 473,8 @@ function toUnixTimeFromEpoch(value) {
];
}

const seconds = value;
const nanoseconds = 0;
const seconds = MathTrunc(value);
const nanoseconds = MathTrunc((value * 1e3) - (seconds * 1e3)) * 1e6;

return [
seconds,
Expand Down
21 changes: 21 additions & 0 deletions ext/fs/in_memory_fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 350,27 @@ impl FileSystem for InMemoryFs {
self.utime_sync(&path, atime_secs, atime_nanos, mtime_secs, mtime_nanos)
}

fn lutime_sync(
&self,
_path: &Path,
_atime_secs: i64,
_atime_nanos: u32,
_mtime_secs: i64,
_mtime_nanos: u32,
) -> FsResult<()> {
Err(FsError::NotSupported)
}
async fn lutime_async(
&self,
path: PathBuf,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()> {
self.lutime_sync(&path, atime_secs, atime_nanos, mtime_secs, mtime_nanos)
}

fn write_file_sync(
&self,
path: &Path,
Expand Down
17 changes: 17 additions & 0 deletions ext/fs/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 221,23 @@ pub trait FileSystem: std::fmt::Debug MaybeSend MaybeSync {
mtime_nanos: u32,
) -> FsResult<()>;

fn lutime_sync(
&self,
path: &Path,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()>;
async fn lutime_async(
&self,
path: PathBuf,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()>;

fn write_file_sync(
&self,
path: &Path,
Expand Down
48 changes: 45 additions & 3 deletions ext/fs/std_fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 274,35 @@ impl FileSystem for RealFs {
.await?
}

fn lutime_sync(
&self,
path: &Path,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()> {
let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
filetime::set_symlink_file_times(path, atime, mtime).map_err(Into::into)
}

async fn lutime_async(
&self,
path: PathBuf,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()> {
let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
spawn_blocking(move || {
filetime::set_symlink_file_times(path, atime, mtime).map_err(Into::into)
})
.await?
}

fn write_file_sync(
&self,
path: &Path,
Expand Down Expand Up @@ -927,9 956,14 @@ fn open_with_access_check(
};
(*access_check)(true, &path, &options)?;

// For windows
#[allow(unused_mut)]
let mut opts: fs::OpenOptions = open_options(options);
#[cfg(windows)]
{
// allow opening directories
use std::os::windows::fs::OpenOptionsExt;
opts.custom_flags(winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS);
}

#[cfg(unix)]
{
// Don't follow symlinks on open -- we must always pass fully-resolved files
Expand All @@ -943,7 977,15 @@ fn open_with_access_check(

Ok(opts.open(&path)?)
} else {
let opts = open_options(options);
// for unix
#[allow(unused_mut)]
let mut opts = open_options(options);
#[cfg(windows)]
{
// allow opening directories
use std::os::windows::fs::OpenOptionsExt;
opts.custom_flags(winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS);
}
Ok(opts.open(path)?)
}
}
3 changes: 3 additions & 0 deletions ext/node/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 315,8 @@ deno_core::extension!(deno_node,
ops::fs::op_node_fs_exists_sync<P>,
ops::fs::op_node_cp_sync<P>,
ops::fs::op_node_cp<P>,
ops::fs::op_node_lutimes_sync<P>,
ops::fs::op_node_lutimes<P>,
ops::fs::op_node_statfs<P>,
ops::winerror::op_node_sys_to_uv_error,
ops::v8::op_v8_cached_data_version_tag,
Expand Down Expand Up @@ -426,6 428,7 @@ deno_core::extension!(deno_node,
"_fs/_fs_futimes.ts",
"_fs/_fs_link.ts",
"_fs/_fs_lstat.ts",
"_fs/_fs_lutimes.ts",
"_fs/_fs_mkdir.ts",
"_fs/_fs_mkdtemp.ts",
"_fs/_fs_open.ts",
Expand Down
51 changes: 51 additions & 0 deletions ext/node/ops/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 222,54 @@ where
Err(anyhow!("Unsupported platform."))
}
}

#[op2(fast)]
pub fn op_node_lutimes_sync<P>(
state: &mut OpState,
#[string] path: &str,
#[number] atime_secs: i64,
#[smi] atime_nanos: u32,
#[number] mtime_secs: i64,
#[smi] mtime_nanos: u32,
) -> Result<(), AnyError>
where
P: NodePermissions 'static,
{
let path = Path::new(path);

state
.borrow_mut::<P>()
.check_write_with_api_name(path, Some("node:fs.lutimes"))?;

let fs = state.borrow::<FileSystemRc>();
fs.lutime_sync(path, atime_secs, atime_nanos, mtime_secs, mtime_nanos)?;
Ok(())
}

#[op2(async)]
pub async fn op_node_lutimes<P>(
state: Rc<RefCell<OpState>>,
#[string] path: String,
#[number] atime_secs: i64,
#[smi] atime_nanos: u32,
#[number] mtime_secs: i64,
#[smi] mtime_nanos: u32,
) -> Result<(), AnyError>
where
P: NodePermissions 'static,
{
let path = PathBuf::from(path);

let fs = {
let mut state = state.borrow_mut();
state
.borrow_mut::<P>()
.check_write_with_api_name(&path, Some("node:fs.lutimesSync"))?;
state.borrow::<FileSystemRc>().clone()
};

fs.lutime_async(path, atime_secs, atime_nanos, mtime_secs, mtime_nanos)
.await?;

Ok(())
}
16 changes: 15 additions & 1 deletion ext/node/polyfills/_fs/_fs_futimes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 5,9 @@

import type { CallbackWithError } from "ext:deno_node/_fs/_fs_common.ts";
import { FsFile } from "ext:deno_fs/30_fs.js";
import { validateInteger } from "ext:deno_node/internal/validators.mjs";
import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts";
import { toUnixTimestamp } from "ext:deno_node/internal/fs/utils.mjs";

function getValidTime(
time: number | string | Date,
Expand All @@ -23,7 26,7 @@ function getValidTime(
);
}

return time;
return toUnixTimestamp(time);
}

export function futimes(
Expand All @@ -35,6 38,11 @@ export function futimes(
if (!callback) {
throw new Deno.errors.InvalidData("No callback function supplied");
}
if (typeof fd !== "number") {
throw new ERR_INVALID_ARG_TYPE("fd", "number", fd);
}

validateInteger(fd, "fd", 0, 2147483647);

atime = getValidTime(atime, "atime");
mtime = getValidTime(mtime, "mtime");
Expand All @@ -51,6 59,12 @@ export function futimesSync(
atime: number | string | Date,
mtime: number | string | Date,
) {
if (typeof fd !== "number") {
throw new ERR_INVALID_ARG_TYPE("fd", "number", fd);
}

validateInteger(fd, "fd", 0, 2147483647);

atime = getValidTime(atime, "atime");
mtime = getValidTime(mtime, "mtime");

Expand Down
85 changes: 85 additions & 0 deletions ext/node/polyfills/_fs/_fs_lutimes.ts
Original file line number Diff line number Diff line change
@@ -0,0 1,85 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

// deno-lint-ignore-file prefer-primordials

import type { CallbackWithError } from "ext:deno_node/_fs/_fs_common.ts";
import { type Buffer } from "node:buffer";
import { primordials } from "ext:core/mod.js";
import { op_node_lutimes, op_node_lutimes_sync } from "ext:core/ops";
import { promisify } from "ext:deno_node/internal/util.mjs";
import {
getValidatedPath,
toUnixTimestamp,
} from "ext:deno_node/internal/fs/utils.mjs";

const { MathTrunc } = primordials;

type TimeLike = number | string | Date;
type PathLike = string | Buffer | URL;

function getValidUnixTime(
value: TimeLike,
name: string,
): [number, number] {
if (typeof value === "string") {
value = Number(value);
}

if (
typeof value === "number" &&
(Number.isNaN(value) || !Number.isFinite(value))
) {
throw new Deno.errors.InvalidData(
`invalid ${name}, must not be infinity or NaN`,
);
}

const unixSeconds = toUnixTimestamp(value);

const seconds = MathTrunc(unixSeconds);
const nanoseconds = MathTrunc((unixSeconds * 1e3) - (seconds * 1e3)) * 1e6;

return [
seconds,
nanoseconds,
];
}

export function lutimes(
path: PathLike,
atime: TimeLike,
mtime: TimeLike,
callback: CallbackWithError,
): void {
if (!callback) {
throw new Error("No callback function supplied");
}
const [atimeSecs, atimeNanos] = getValidUnixTime(atime, "atime");
const [mtimeSecs, mtimeNanos] = getValidUnixTime(mtime, "mtime");

path = getValidatedPath(path).toString();

op_node_lutimes(path, atimeSecs, atimeNanos, mtimeSecs, mtimeNanos).then(
() => callback(null),
callback,
);
}

export function lutimesSync(
path: PathLike,
atime: TimeLike,
mtime: TimeLike,
): void {
const { 0: atimeSecs, 1: atimeNanos } = getValidUnixTime(atime, "atime");
const { 0: mtimeSecs, 1: mtimeNanos } = getValidUnixTime(mtime, "mtime");

path = getValidatedPath(path).toString();

op_node_lutimes_sync(path, atimeSecs, atimeNanos, mtimeSecs, mtimeNanos);
}

export const lutimesPromise = promisify(lutimes) as (
path: PathLike,
atime: TimeLike,
mtime: TimeLike,
) => Promise<void>;
Loading

0 comments on commit dadc606

Please sign in to comment.