Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions benchmark/internal/benchmark_ffi/pkg.generated.mbti
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
package "moonbitlang/x/benchmark/internal/benchmark_ffi"

// Values
fn instant_elapsed_as_secs_f64(Instant) -> Double
fn instant_elapsed_as_secs_f64(Date) -> Double

fn instant_now() -> Instant
fn instant_now() -> Date

// Errors

// Types and methods
type Instant
type Date

// Type aliases

Expand Down
35 changes: 35 additions & 0 deletions fs/blackbox_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,38 @@ test "create_dir_and_remove_dir" {
@fs.remove_dir(path)
assert_false(@fs.path_exists(path))
}

///|
test "stat" {
let path = "./fs/stat_test.txt"
// Ensure cleanup if previous run failed
if @fs.path_exists(path) {
@fs.remove_file(path)
}
let content = "12345"
@fs.write_string_to_file(path, content)
let stats = @fs.stats(path)
// println(stats)

// Basic Type and Size
inspect(stats.size, content="5")

// Time fields check (sanity check: should be non-zero)
assert_true(stats.atime_sec > 0L)
assert_true(stats.mtime_sec > 0L)
assert_true(stats.ctime_sec > 0L)

// Metadata checks
assert_true(stats.nlink >= 1UL)

// Mode check: Verify it's a regular file (S_IFREG is 0o100000)
// We mask with S_IFMT (0o170000) to get the file type bits
let is_reg = (stats.mode & 0o170000U) == 0o100000U
assert_true(is_reg)

// UID/GID should be readable (printing them to ensure they are valid numbers)
// Note: specific values depend on the environment, so we just check they exist
inspect(stats.uid >= 0U, content="true")
inspect(stats.gid >= 0U, content="true")
@fs.remove_file(path)
}
97 changes: 97 additions & 0 deletions fs/fs.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,103 @@ pub fn is_file(path : String) -> Bool raise IOError {
is_file_internal(path)
}

///|
pub struct Stats {
dev : UInt64
mode : UInt
nlink : UInt64
ino : UInt64
uid : UInt
gid : UInt
rdev : UInt64
atime_sec : Int64
atime_nsec : Int64
mtime_sec : Int64
mtime_nsec : Int64
ctime_sec : Int64
ctime_nsec : Int64
birthtime_sec : Int64
birthtime_nsec : Int64
size : Int64
blocks : UInt64
blksize : UInt
flags : UInt
gen : UInt
lspare : Int
qspare : (Int64, Int64)
} derive(Show, Eq)

///|
fn decode_stat(b : Bytes) -> Stats raise IOError {
match b {
[
u64le(dev),
u32le(mode),
u32le(_),
u64le(nlink),
u64le(ino),
u32le(uid),
u32le(gid),
u64le(rdev),
i64le(atime_sec),
i64le(atime_nsec),
i64le(mtime_sec),
i64le(mtime_nsec),
i64le(ctime_sec),
i64le(ctime_nsec),
i64le(birthtime_sec),
i64le(birthtime_nsec),
i64le(size),
u64le(blocks),
u32le(blksize),
u32le(flags),
u32le(gen),
i32le(lspare),
i64le(qspare_0),
i64le(qspare_1),
] =>
{
dev,
mode,
nlink,
ino,
uid,
gid,
rdev,
atime_sec,
atime_nsec,
mtime_sec,
mtime_nsec,
ctime_sec,
ctime_nsec,
birthtime_sec,
birthtime_nsec,
size,
blocks,
blksize,
flags,
gen,
lspare,
qspare: (qspare_0, qspare_1),
}
_ => raise IOError("invalid stat buffer")
}
}

///|
/// Returns the statistics of the file specified by the given path.
///
/// # Parameters
///
/// - `path` : The path to the file.
///
/// # Returns
///
/// - A `Stats` structure containing file information.
pub fn stats(path : String) -> Stats raise IOError {
stat_internal(path)
}

///|
/// Removes a directory at the specified path.
///
Expand Down
62 changes: 62 additions & 0 deletions fs/fs_js.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,65 @@ fn remove_file_internal(path : String) -> Unit raise IOError {
raise IOError(get_error_message_ffi())
}
}

///|

///|
extern "js" fn stat_details_ffi(path : String, out_buf : Bytes) -> Int =
#| function(path, out_buf) {
#| var fs = require('fs');
#| try {
#| const s = fs.statSync(path, {bigint: true});
#| const view = new DataView(out_buf.buffer, out_buf.byteOffset, out_buf.byteLength);
#|
#| view.setBigUint64(0, s.dev, true);
#| view.setUint32(8, Number(s.mode), true);
#| view.setUint32(12, 0, true); // padding
#| view.setBigUint64(16, s.nlink, true);
#| view.setBigUint64(24, s.ino, true);
#| view.setUint32(32, Number(s.uid), true);
#| view.setUint32(36, Number(s.gid), true);
#| view.setBigUint64(40, s.rdev, true);
#|
#| const splitTime = (ns, offset) => {
#| if (ns === undefined) {
#| view.setBigInt64(offset, 0n, true);
#| view.setBigInt64(offset + 8, 0n, true);
#| return;
#| }
#| const sec = ns / 1000000000n;
#| const nsec = ns % 1000000000n;
#| view.setBigInt64(offset, sec, true);
#| view.setBigInt64(offset + 8, nsec, true);
#| };
#|
#| splitTime(s.atimeNs, 48);
#| splitTime(s.mtimeNs, 64);
#| splitTime(s.ctimeNs, 80);
#| splitTime(s.birthtimeNs, 96);
#|
#| view.setBigInt64(112, s.size, true);
#| view.setBigUint64(120, s.blocks || 0n, true);
#| view.setUint32(128, Number(s.blksize || 0), true);
#| view.setUint32(132, 0, true); // flags
#| view.setUint32(136, 0, true); // gen
#| view.setInt32(140, 0, true); // lspare
#| view.setBigInt64(144, 0n, true); // qspare
#| view.setBigInt64(152, 0n, true); // qspare
#|
#| return 0;
#| } catch (error) {
#| globalThis.errorMessage = error.message;
#| return -1;
#| }
#| }

///|
fn stat_internal(path : String) -> Stats raise IOError {
let buf = Bytes::make(160, 0)
let res = stat_details_ffi(path, buf)
if res == -1 {
raise IOError(get_error_message_ffi())
}
decode_stat(buf)
}
126 changes: 126 additions & 0 deletions fs/fs_native.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ extern "C" {
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <stdint.h>

#ifdef _WIN32
#include <direct.h>
Expand Down Expand Up @@ -253,6 +254,131 @@ moonbitlang_x_fs_read_dir_ffi(moonbit_bytes_t path) {
#endif
}

MOONBIT_FFI_EXPORT int moonbitlang_x_fs_stat_details_ffi(moonbit_bytes_t path,
moonbit_bytes_t out_buf) {
struct stat s;
if (stat((const char *)path, &s) != 0) {
return -1;
}

int32_t type = 3; // Unknown
#ifdef _WIN32
if (s.st_mode & _S_IFREG)
type = 0;
else if (s.st_mode & _S_IFDIR)
type = 1;
#else
if (S_ISREG(s.st_mode))
type = 0;
else if (S_ISDIR(s.st_mode))
type = 1;
else if (S_ISLNK(s.st_mode))
type = 2;
#endif

uint64_t dev = (uint64_t)s.st_dev;
uint32_t mode = (uint32_t)s.st_mode;
uint64_t nlink = (uint64_t)s.st_nlink;
uint64_t ino = (uint64_t)s.st_ino;
uint32_t uid = (uint32_t)s.st_uid;
uint32_t gid = (uint32_t)s.st_gid;
uint64_t rdev = (uint64_t)s.st_rdev;
int64_t size = (int64_t)s.st_size;

#if defined(__APPLE__)
int64_t atime_sec = s.st_atimespec.tv_sec;
int64_t atime_nsec = s.st_atimespec.tv_nsec;
int64_t mtime_sec = s.st_mtimespec.tv_sec;
int64_t mtime_nsec = s.st_mtimespec.tv_nsec;
int64_t ctime_sec = s.st_ctimespec.tv_sec;
int64_t ctime_nsec = s.st_ctimespec.tv_nsec;
int64_t birthtime_sec = s.st_birthtimespec.tv_sec;
int64_t birthtime_nsec = s.st_birthtimespec.tv_nsec;
#elif defined(_WIN32)
int64_t atime_sec = s.st_atime;
int64_t atime_nsec = 0;
int64_t mtime_sec = s.st_mtime;
int64_t mtime_nsec = 0;
int64_t ctime_sec = s.st_ctime;
int64_t ctime_nsec = 0;
int64_t birthtime_sec = 0;
int64_t birthtime_nsec = 0;
#else
// Linux and others
int64_t atime_sec = s.st_atim.tv_sec;
int64_t atime_nsec = s.st_atim.tv_nsec;
int64_t mtime_sec = s.st_mtim.tv_sec;
int64_t mtime_nsec = s.st_mtim.tv_nsec;
int64_t ctime_sec = s.st_ctim.tv_sec;
int64_t ctime_nsec = s.st_ctim.tv_nsec;
int64_t birthtime_sec = 0;
int64_t birthtime_nsec = 0;
#endif

#ifdef _WIN32
uint64_t blocks = 0;
uint32_t blksize = 0;
uint32_t flags = 0;
uint32_t gen = 0;
#else
uint64_t blocks = (uint64_t)s.st_blocks;
uint32_t blksize = (uint32_t)s.st_blksize;
#if defined(__APPLE__)
uint32_t flags = (uint32_t)s.st_flags;
uint32_t gen = (uint32_t)s.st_gen;
#else
uint32_t flags = 0;
uint32_t gen = 0;
#endif
#endif

uint8_t *buf = (uint8_t *)out_buf;
// dev (8 bytes) at offset 0
memcpy(buf, &dev, 8);
// mode (4 bytes) at offset 8
memcpy(buf + 8, &mode, 4);
// padding (4 bytes) at offset 12
memset(buf + 12, 0, 4);
// nlink (8 bytes) at offset 16
memcpy(buf + 16, &nlink, 8);
// ino (8 bytes) at offset 24
memcpy(buf + 24, &ino, 8);
// uid (4 bytes) at offset 32
memcpy(buf + 32, &uid, 4);
// gid (4 bytes) at offset 36
memcpy(buf + 36, &gid, 4);
// rdev (8 bytes) at offset 40
memcpy(buf + 40, &rdev, 8);
// atime (sec, nsec) at offset 48, 56
memcpy(buf + 48, &atime_sec, 8);
memcpy(buf + 56, &atime_nsec, 8);
// mtime (sec, nsec) at offset 64, 72
memcpy(buf + 64, &mtime_sec, 8);
memcpy(buf + 72, &mtime_nsec, 8);
// ctime (sec, nsec) at offset 80, 88
memcpy(buf + 80, &ctime_sec, 8);
memcpy(buf + 88, &ctime_nsec, 8);
// birthtime (sec, nsec) at offset 96, 104
memcpy(buf + 96, &birthtime_sec, 8);
memcpy(buf + 104, &birthtime_nsec, 8);
// size (8 bytes) at offset 112
memcpy(buf + 112, &size, 8);
// blocks (8 bytes) at offset 120
memcpy(buf + 120, &blocks, 8);
// blksize (4 bytes) at offset 128
memcpy(buf + 128, &blksize, 4);
// flags (4 bytes) at offset 132
memcpy(buf + 132, &flags, 4);
// gen (4 bytes) at offset 136
memcpy(buf + 136, &gen, 4);
// lspare (4 bytes) at offset 140 -> 0
memset(buf + 140, 0, 4);
// qspare (16 bytes) at offset 144 -> 0
memset(buf + 144, 0, 16);

return 0;
}

#ifdef __cplusplus
}
#endif
12 changes: 12 additions & 0 deletions fs/fs_native.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,15 @@ fn remove_file_internal(path : String) -> Unit raise IOError {
raise IOError(get_error_message())
}
}

///|
#borrow(path, out_buf)
extern "C" fn stat_details_ffi(path : Bytes, out_buf : Bytes) -> Int = "moonbitlang_x_fs_stat_details_ffi"

///|
fn stat_internal(path : String) -> Stats raise IOError {
let buf = Bytes::make(160, 0)
let res = stat_details_ffi(@ffi.mbt_string_to_utf8_bytes(path, true), buf)
guard res == 0 else { raise IOError(get_error_message()) }
decode_stat(buf)
}
Loading
Loading