mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 19:58:18 +00:00
Add no_std fs
This commit is contained in:
parent
28420e90ad
commit
9234427f30
6 changed files with 693 additions and 40 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -941,6 +941,13 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"widestring",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_at"
|
||||
version = "0.1.10"
|
||||
|
@ -4551,6 +4558,12 @@ version = "0.25.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
|
102
Cargo.toml
102
Cargo.toml
|
@ -1,40 +1,41 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"crates/compiler/*",
|
||||
"crates/vendor/*",
|
||||
"crates/glue",
|
||||
"crates/cli",
|
||||
"crates/cli_utils",
|
||||
"crates/highlight",
|
||||
"crates/error_macros",
|
||||
"crates/reporting",
|
||||
"crates/packaging",
|
||||
"crates/repl_cli",
|
||||
"crates/repl_eval",
|
||||
"crates/repl_test",
|
||||
"crates/repl_ui",
|
||||
"crates/repl_wasm",
|
||||
"crates/repl_expect",
|
||||
"crates/roc_std",
|
||||
"crates/test_utils",
|
||||
"crates/test_utils_dir",
|
||||
"crates/valgrind",
|
||||
"crates/tracing",
|
||||
"crates/utils/*",
|
||||
"crates/docs",
|
||||
"crates/docs_cli",
|
||||
"crates/linker",
|
||||
"crates/wasi-libc-sys",
|
||||
"crates/wasm_module",
|
||||
"crates/wasm_interp",
|
||||
"crates/language_server",
|
||||
"crates/compiler/*",
|
||||
"crates/vendor/*",
|
||||
"crates/fs",
|
||||
"crates/glue",
|
||||
"crates/cli",
|
||||
"crates/cli_utils",
|
||||
"crates/highlight",
|
||||
"crates/error_macros",
|
||||
"crates/reporting",
|
||||
"crates/packaging",
|
||||
"crates/repl_cli",
|
||||
"crates/repl_eval",
|
||||
"crates/repl_test",
|
||||
"crates/repl_ui",
|
||||
"crates/repl_wasm",
|
||||
"crates/repl_expect",
|
||||
"crates/roc_std",
|
||||
"crates/test_utils",
|
||||
"crates/test_utils_dir",
|
||||
"crates/valgrind",
|
||||
"crates/tracing",
|
||||
"crates/utils/*",
|
||||
"crates/docs",
|
||||
"crates/docs_cli",
|
||||
"crates/linker",
|
||||
"crates/wasi-libc-sys",
|
||||
"crates/wasm_module",
|
||||
"crates/wasm_interp",
|
||||
"crates/language_server",
|
||||
]
|
||||
|
||||
exclude = [
|
||||
"ci/benchmarks/bench-runner",
|
||||
"ci/repl_basic_test",
|
||||
# Examples sometimes have Rust hosts in their platforms. The compiler should ignore those.
|
||||
"examples",
|
||||
"ci/benchmarks/bench-runner",
|
||||
"ci/repl_basic_test",
|
||||
# Examples sometimes have Rust hosts in their platforms. The compiler should ignore those.
|
||||
"examples",
|
||||
]
|
||||
# Needed to be able to run `cargo run -p roc_cli --no-default-features` -
|
||||
# see www/build.sh for more.
|
||||
|
@ -69,7 +70,9 @@ version = "0.0.1"
|
|||
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
|
||||
# This way, GitHub Actions works and nobody's builds get broken.
|
||||
# TODO: Switch this back to roc-lang/inkwell once it is updated
|
||||
inkwell = { git = "https://github.com/roc-lang/inkwell", branch = "inkwell-llvm-16", features = ["llvm16-0"] }
|
||||
inkwell = { git = "https://github.com/roc-lang/inkwell", branch = "inkwell-llvm-16", features = [
|
||||
"llvm16-0",
|
||||
] }
|
||||
|
||||
arrayvec = "0.7.2" # update roc_std/Cargo.toml on change
|
||||
backtrace = "0.3.67"
|
||||
|
@ -78,18 +81,27 @@ bincode = "1.3.3"
|
|||
bitflags = "1.3.2"
|
||||
bitvec = "1.0.1"
|
||||
blake3 = "1.3.3"
|
||||
brotli = "3.3.4" # used for decompressing tarballs over HTTPS, if the server supports brotli
|
||||
brotli = "3.3.4" # used for decompressing tarballs over HTTPS, if the server supports brotli
|
||||
bumpalo = { version = "3.12.0", features = ["collections"] }
|
||||
bytemuck = { version = "1.13.1", features = ["derive"] }
|
||||
capstone = { version = "0.11.0", default-features = false }
|
||||
cgmath = "0.18.0"
|
||||
chrono = "0.4.26"
|
||||
clap = { version = "4.2.7", default-features = false, features = ["std", "color", "suggestions", "help", "usage", "error-context"] }
|
||||
clap = { version = "4.2.7", default-features = false, features = [
|
||||
"std",
|
||||
"color",
|
||||
"suggestions",
|
||||
"help",
|
||||
"usage",
|
||||
"error-context",
|
||||
] }
|
||||
colored = "2.0.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
const_format = { version = "0.2.30", features = ["const_generics"] }
|
||||
copypasta = "0.8.2"
|
||||
criterion = { git = "https://github.com/Anton-4/criterion.rs", features = ["html_reports"], rev = "30ea0c5" }
|
||||
criterion = { git = "https://github.com/Anton-4/criterion.rs", features = [
|
||||
"html_reports",
|
||||
], rev = "30ea0c5" }
|
||||
criterion-perf-events = { git = "https://github.com/Anton-4/criterion-perf-events", rev = "0f38c3e" }
|
||||
crossbeam = "0.8.2"
|
||||
dircpy = "0.3.14"
|
||||
|
@ -102,7 +114,12 @@ fs_extra = "1.3.0"
|
|||
futures = "0.3.26"
|
||||
glyph_brush = "0.7.7"
|
||||
hashbrown = { version = "0.14.3" }
|
||||
iced-x86 = { version = "1.18.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] }
|
||||
iced-x86 = { version = "1.18.0", default-features = false, features = [
|
||||
"std",
|
||||
"decoder",
|
||||
"op_code_info",
|
||||
"instr_info",
|
||||
] }
|
||||
im = "15.1.0"
|
||||
im-rc = "15.1.0"
|
||||
indexmap = "2.1.0"
|
||||
|
@ -138,12 +155,17 @@ quote = "1.0.23"
|
|||
rand = "0.8.5"
|
||||
regex = "1.7.1"
|
||||
remove_dir_all = "0.8.1"
|
||||
reqwest = { version = "0.11.23", default-features = false, features = ["blocking", "rustls-tls"] } # default-features=false removes libopenssl as a dependency on Linux, which might not be available!
|
||||
reqwest = { version = "0.11.23", default-features = false, features = [
|
||||
"blocking",
|
||||
"rustls-tls",
|
||||
] } # default-features=false removes libopenssl as a dependency on Linux, which might not be available!
|
||||
rlimit = "0.9.1"
|
||||
rustyline = { git = "https://github.com/roc-lang/rustyline", rev = "e74333c" }
|
||||
rustyline-derive = { git = "https://github.com/roc-lang/rustyline", rev = "e74333c" }
|
||||
schemars = "0.8.12"
|
||||
serde = { version = "1.0.153", features = ["derive"] } # update roc_std/Cargo.toml on change
|
||||
serde = { version = "1.0.153", features = [
|
||||
"derive",
|
||||
] } # update roc_std/Cargo.toml on change
|
||||
serde-xml-rs = "0.6.0"
|
||||
serde_json = "1.0.94" # update roc_std/Cargo.toml on change
|
||||
serial_test = "1.0.0"
|
||||
|
@ -191,7 +213,7 @@ debug = true
|
|||
|
||||
[profile.release-with-lto]
|
||||
inherits = "release"
|
||||
lto = "thin" # TODO: We could consider full here since this is only used for packaged release on github.
|
||||
lto = "thin" # TODO: We could consider full here since this is only used for packaged release on github.
|
||||
|
||||
[profile.debug-full]
|
||||
inherits = "dev"
|
||||
|
|
11
crates/fs/Cargo.toml
Normal file
11
crates/fs/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "fs"
|
||||
description = "no_std filesystem access. These operations all use native OS strings and do not allocate."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
widestring = { version = "1.1.0", default-features = false }
|
559
crates/fs/src/file.rs
Normal file
559
crates/fs/src/file.rs
Normal file
|
@ -0,0 +1,559 @@
|
|||
use crate::native_path::NativePath;
|
||||
use core::{ffi::CStr, fmt, mem::MaybeUninit};
|
||||
|
||||
#[cfg(windows)]
|
||||
use widestring::U16CString;
|
||||
|
||||
#[cfg(unix)]
|
||||
use core::ffi::{c_char, c_int};
|
||||
|
||||
#[cfg(unix)]
|
||||
pub struct File {
|
||||
fd: i32,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub struct File {
|
||||
handle: isize,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
extern "C" {
|
||||
fn open(pathname: *const c_char, flags: c_int, ...) -> c_int;
|
||||
fn close(fd: c_int) -> c_int;
|
||||
fn read(fd: c_int, buf: *mut MaybeUninit<u8>, count: usize) -> isize;
|
||||
fn write(fd: c_int, buf: *const u8, count: usize) -> isize;
|
||||
fn mkstemp(template: *mut c_char) -> c_int;
|
||||
fn unlink(pathname: *const c_char) -> c_int;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl Drop for File {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
close(self.fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
extern "system" {
|
||||
fn CreateFileW(
|
||||
lpFileName: *const u16,
|
||||
dwDesiredAccess: u32,
|
||||
dwShareMode: u32,
|
||||
lpSecurityAttributes: *mut core::ffi::c_void,
|
||||
dwCreationDisposition: u32,
|
||||
dwFlagsAndAttributes: u32,
|
||||
hTemplateFile: isize,
|
||||
) -> isize;
|
||||
fn CloseHandle(hObject: isize) -> i32;
|
||||
fn ReadFile(
|
||||
hFile: isize,
|
||||
lpBuffer: *mut MaybeUninit<u8>,
|
||||
nNumberOfBytesToRead: u32,
|
||||
lpNumberOfBytesRead: *mut u32,
|
||||
lpOverlapped: *mut core::ffi::c_void,
|
||||
) -> i32;
|
||||
fn WriteFile(
|
||||
hFile: isize,
|
||||
lpBuffer: *const u8,
|
||||
nNumberOfBytesToWrite: u32,
|
||||
lpNumberOfBytesWritten: *mut u32,
|
||||
lpOverlapped: *mut core::ffi::c_void,
|
||||
) -> i32;
|
||||
fn DeleteFileW(lpFileName: *const u16) -> i32;
|
||||
fn GetTempPathW(nBufferLength: u32, lpBuffer: *mut u16) -> u32;
|
||||
fn CreateDirectoryW(
|
||||
lpPathName: *const u16,
|
||||
lpSecurityAttributes: *mut core::ffi::c_void,
|
||||
) -> i32;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl Drop for File {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CloseHandle(self.handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(unix, repr(i32))]
|
||||
#[cfg_attr(windows, repr(u32))]
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
#[non_exhaustive] // There are tons of other error numbers that functions which return this might return!
|
||||
pub enum FileIoErr {
|
||||
/// "File not found" is the same code (namely, 2) on both UNIX and Windows:
|
||||
/// ENOENT on UNIX: https://www.man7.org/linux/man-pages/man3/errno.3.html
|
||||
/// ERROR_FILE_NOT_FOUND on Windows: // https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
|
||||
NotFound = 2,
|
||||
|
||||
#[cfg(unix)]
|
||||
/// EACCES: https://www.man7.org/linux/man-pages/man3/errno.3.html
|
||||
AccessDenied = 13,
|
||||
|
||||
#[cfg(windows)]
|
||||
/// ERROR_ACCESS_DENIED: https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
|
||||
AccessDenied = 5,
|
||||
}
|
||||
|
||||
impl fmt::Debug for FileIoErr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
FileIoErr::NotFound => write!(f, "NotFound ({})", *self as i32),
|
||||
#[cfg(unix)]
|
||||
FileIoErr::AccessDenied => write!(f, "AccessDenied ({})", *self as i32),
|
||||
#[cfg(windows)]
|
||||
FileIoErr::AccessDenied => write!(f, "AccessDenied ({})", *self as i32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileIoErr {
|
||||
#[cfg(target_os = "macos")]
|
||||
fn most_recent() -> FileIoErr {
|
||||
extern "C" {
|
||||
fn __error() -> *mut FileIoErr;
|
||||
}
|
||||
|
||||
unsafe { *__error() }
|
||||
}
|
||||
|
||||
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
|
||||
fn most_recent() -> FileIoErr {
|
||||
extern "C" {
|
||||
fn __errno_location() -> *mut FileIoErr;
|
||||
}
|
||||
|
||||
unsafe { errno = *__errno_location() }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn most_recent() -> FileIoErr {
|
||||
extern "system" {
|
||||
fn GetLastError() -> u32;
|
||||
}
|
||||
|
||||
unsafe { FileIoErr(GetLastError()) }
|
||||
}
|
||||
}
|
||||
|
||||
//// OPEN FILE ////
|
||||
|
||||
#[cfg(unix)]
|
||||
impl File {
|
||||
const O_WRONLY: c_int = 1;
|
||||
const O_RDWR: c_int = 2;
|
||||
|
||||
/// source: https://github.com/apple-open-source/macos/blob/8038f956fee603c486e75adbf93cac7a66064f02/Libc/exclave/sys/fcntl.h#L104
|
||||
#[cfg(target_os = "macos")]
|
||||
const O_CREAT: c_int = 512;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
const O_CREAT: c_int = 64;
|
||||
|
||||
pub fn open(path: &NativePath) -> Result<Self, FileIoErr> {
|
||||
let fd = unsafe { open(path.inner.as_ptr(), Self::O_RDWR) };
|
||||
|
||||
if fd != -1 {
|
||||
Ok(File { fd })
|
||||
} else {
|
||||
Err(FileIoErr::most_recent())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(path: &NativePath) -> Result<Self, FileIoErr> {
|
||||
let fd = unsafe {
|
||||
open(
|
||||
path.inner.as_ptr(),
|
||||
Self::O_CREAT | Self::O_WRONLY,
|
||||
0o644, // read/write mode
|
||||
)
|
||||
};
|
||||
|
||||
if fd != -1 {
|
||||
Ok(File { fd })
|
||||
} else {
|
||||
Err(FileIoErr::most_recent())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl File {
|
||||
const GENERIC_WRITE: u32 = 0x40000000;
|
||||
const CREATE_ALWAYS: u32 = 2;
|
||||
const FILE_SHARE_WRITE: u32 = 0x00000002;
|
||||
|
||||
pub fn open(path: &NativePath) -> Result<Self, FileIoErr> {
|
||||
let handle = unsafe {
|
||||
CreateFileW(
|
||||
path.inner.as_ptr(),
|
||||
Self::GENERIC_READ | Self::GENERIC_WRITE,
|
||||
Self::FILE_SHARE_READ | Self::FILE_SHARE_WRITE,
|
||||
core::ptr::null_mut(),
|
||||
Self::OPEN_EXISTING,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
if handle != -1 {
|
||||
Ok(File { handle })
|
||||
} else {
|
||||
Err(FileIoErr::errno())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(path: &NativePath) -> Result<Self, FileIoErr> {
|
||||
let handle = unsafe {
|
||||
CreateFileW(
|
||||
path.inner.as_ptr(),
|
||||
Self::GENERIC_READ | Self::GENERIC_WRITE,
|
||||
Self::FILE_SHARE_READ | Self::FILE_SHARE_WRITE,
|
||||
core::ptr::null_mut(),
|
||||
Self::CREATE_ALWAYS,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
if handle != -1 {
|
||||
Ok(File { handle })
|
||||
} else {
|
||||
Err(FileIoErr::errno())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//// REMOVE ////
|
||||
|
||||
#[cfg(unix)]
|
||||
impl File {
|
||||
/// Returns whether it succeeded.
|
||||
pub fn remove(path: &NativePath) -> bool {
|
||||
unsafe { unlink(path.inner.as_ptr()) == 0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl File {
|
||||
/// Returns whether it succeeded.
|
||||
pub fn remove(path: &NativePath) -> bool {
|
||||
unsafe { DeleteFileW(path.inner.as_ptr()) != 0 }
|
||||
}
|
||||
}
|
||||
|
||||
//// TEMPFILE ////
|
||||
|
||||
#[cfg(unix)]
|
||||
impl File {
|
||||
/// Create a tempfile, open it as a File, pass that File and its generated path
|
||||
/// to the given function, and then delete it after the function returns.
|
||||
pub fn with_tempfile<T>(
|
||||
run: impl FnOnce(Result<(&NativePath, &mut Self), FileIoErr>) -> T,
|
||||
) -> T {
|
||||
const TEMPLATE: &[u8] = b"/tmp/roc_tempfile_XXXXXX\0";
|
||||
|
||||
let mut template = [0; TEMPLATE.len()];
|
||||
template.copy_from_slice(TEMPLATE);
|
||||
|
||||
// mkstemp replaces the Xs in the template with chars that result in a unique path.
|
||||
let fd = unsafe { mkstemp(template.as_mut_ptr().cast()) };
|
||||
|
||||
if fd != -1 {
|
||||
let path_cstr = unsafe { CStr::from_bytes_with_nul_unchecked(&template) };
|
||||
let native_path: &NativePath = path_cstr.into();
|
||||
let mut file = File { fd };
|
||||
|
||||
// Since we pass an owned File, it will get closed automatically once dropped.
|
||||
// This in turn will result in the file getting deleted, since we already unlinked it.
|
||||
let answer = run(Ok((native_path, &mut file)));
|
||||
|
||||
// Unlink the file now that we're done with it.
|
||||
unsafe {
|
||||
unlink(template.as_ptr().cast());
|
||||
}
|
||||
|
||||
answer
|
||||
} else {
|
||||
// Here we assume that since mkstemp errored out, the file was not created
|
||||
// and we shouldn't attempt to unlink it.
|
||||
run(Err(FileIoErr::most_recent()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
/// Create a tempfile, open it as a File, pass that File and its generated path
|
||||
/// to the given function, and then delete it after the function returns.
|
||||
pub fn with_tempfile<T>(run: impl FnOnce(Result<(&NativePath, Self), FileIoErr>) -> T) -> T {
|
||||
let mut temp_path_buf = [0u16; 261];
|
||||
let temp_path_len =
|
||||
unsafe { GetTempPathW(temp_path_buf.len() as u32, temp_path_buf.as_mut_ptr()) };
|
||||
|
||||
if temp_path_len == 0 || temp_path_len > temp_path_buf.len() as u32 {
|
||||
let native_path =
|
||||
NativePath::new(U16CString::from_vec_with_nul(temp_path_buf.to_vec()).unwrap());
|
||||
return run(&native_path, None);
|
||||
}
|
||||
|
||||
let mut template = Vec::from(&temp_path_buf[..temp_path_len as usize]);
|
||||
template.extend_from_slice(&[
|
||||
b't' as u16,
|
||||
b'e' as u16,
|
||||
b'm' as u16,
|
||||
b'p' as u16,
|
||||
b'f' as u16,
|
||||
b'i' as u16,
|
||||
b'l' as u16,
|
||||
b'e' as u16,
|
||||
b'.' as u16,
|
||||
b't' as u16,
|
||||
b'm' as u16,
|
||||
b'p' as u16,
|
||||
0,
|
||||
]);
|
||||
|
||||
let mut temp_file_name = [0u16; 261];
|
||||
let result = unsafe {
|
||||
GetTempFileNameW(
|
||||
temp_path_buf.as_ptr(),
|
||||
U16CString::from_str("tmp").unwrap().as_ptr(),
|
||||
0,
|
||||
temp_file_name.as_mut_ptr(),
|
||||
)
|
||||
};
|
||||
|
||||
if result == 0 {
|
||||
let native_path =
|
||||
NativePath::new(U16CString::from_vec_with_nul(temp_file_name.to_vec()).unwrap());
|
||||
return run(&native_path, None);
|
||||
}
|
||||
|
||||
let handle = unsafe {
|
||||
CreateFileW(
|
||||
temp_file_name.as_ptr(),
|
||||
0x40000000, // GENERIC_WRITE
|
||||
0,
|
||||
core::ptr::null_mut(),
|
||||
1, // CREATE_ALWAYS
|
||||
0x80, // FILE_ATTRIBUTE_TEMPORARY
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
if handle != -1 {
|
||||
// Since we pass an owned File, it will get closed automatically once dropped.
|
||||
// This in turn will result in the file getting deleted, since Windows sets
|
||||
// tempfiles to delete once the handle is closed.
|
||||
run(Some(File { handle }))
|
||||
} else {
|
||||
// Here we assume that since CreateFileW errored out, the file was not created
|
||||
// and we shouldn't attempt to delete it.
|
||||
run(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//// READ FILE ////
|
||||
|
||||
impl File {
|
||||
#[cfg(unix)]
|
||||
pub fn read_into<'buf>(
|
||||
&mut self,
|
||||
buf: &'buf mut [MaybeUninit<u8>],
|
||||
) -> Result<&'buf mut [u8], FileIoErr> {
|
||||
let bytes_read = unsafe { read(self.fd, buf.as_mut_ptr(), buf.len()) };
|
||||
|
||||
if bytes_read >= 0 {
|
||||
// Return the subset of the buf that we actually initialized.
|
||||
let buf_ptr = buf.as_mut_ptr();
|
||||
let len = bytes_read as usize;
|
||||
|
||||
Ok(unsafe { core::slice::from_raw_parts_mut(buf_ptr.cast(), len) })
|
||||
} else {
|
||||
Err(FileIoErr::most_recent())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn read_into<'buf>(
|
||||
&mut self,
|
||||
buf: &'buf mut [MaybeUninit<u8>],
|
||||
) -> Result<&'buf mut [u8], FileIoErr> {
|
||||
let mut bytes_read: u32 = 0;
|
||||
|
||||
let result = unsafe {
|
||||
ReadFile(
|
||||
self.handle,
|
||||
buf.as_mut_ptr(),
|
||||
buf.len() as u32,
|
||||
&mut bytes_read,
|
||||
core::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
if result >= 0 {
|
||||
let buf_ptr = buf.as_mut_ptr();
|
||||
let len = bytes_read as usize;
|
||||
|
||||
Ok(unsafe { core::slice::from_raw_parts_mut(buf_ptr.cast(), len) })
|
||||
} else {
|
||||
Err(FileIoErr::most_recent())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//// WRITE FILE ////
|
||||
|
||||
impl File {
|
||||
#[cfg(unix)]
|
||||
pub fn write(&mut self, buf: &[u8]) -> Result<usize, FileIoErr> {
|
||||
let bytes_written = unsafe { write(self.fd, buf.as_ptr(), buf.len()) };
|
||||
|
||||
if bytes_written >= 0 {
|
||||
Ok(bytes_written as usize)
|
||||
} else {
|
||||
Err(FileIoErr::most_recent())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn write(&mut self, buf: &[u8]) -> Result<usize, FileIoErr> {
|
||||
let mut bytes_written: u32 = 0;
|
||||
|
||||
let result = unsafe {
|
||||
WriteFile(
|
||||
self.handle,
|
||||
buf.as_ptr(),
|
||||
buf.len() as u32,
|
||||
&mut bytes_written,
|
||||
core::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
if result != 0 {
|
||||
Ok(bytes_written as usize)
|
||||
} else {
|
||||
Err(FileIoErr::most_recent())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{File, FileIoErr};
|
||||
use crate::native_path::NativePath;
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
#[cfg(unix)]
|
||||
use core::ffi::CStr;
|
||||
|
||||
#[cfg(windows)]
|
||||
use widestring::U16CString;
|
||||
|
||||
#[cfg(unix)]
|
||||
fn str_to_cstr(s: &str) -> &CStr {
|
||||
CStr::from_bytes_with_nul(s.as_bytes()).expect("CStr conversion failed")
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn str_to_u16cstr(s: &str) -> &widestring::U16CStr {
|
||||
widestring::U16CString::from_str(s)
|
||||
.expect("U16CStr conversion failed")
|
||||
.as_ref()
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn mock_path(path: &str) -> &NativePath {
|
||||
str_to_cstr(path).into()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn mock_path(path: &str) -> &NativePath {
|
||||
str_to_u16cstr(path).into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_not_found() {
|
||||
let file_result = File::open(mock_path("test_file_that_should_not_exist\0"));
|
||||
|
||||
assert_eq!(
|
||||
file_result.map(|_| ()),
|
||||
Err(FileIoErr::NotFound),
|
||||
"File should not exist: test_file_that_should_not_exist"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_write_read_delete() {
|
||||
let path = mock_path("roc_test_read_file\0");
|
||||
let mut file = File::create(&path).unwrap();
|
||||
|
||||
// Write some data to the file
|
||||
let write_data: &[u8] = &[42u8; 10];
|
||||
let write_result = file.write(write_data);
|
||||
assert_eq!(
|
||||
write_result,
|
||||
Ok(write_data.len()),
|
||||
"Failed to write all data to the file: roc_test_read_file"
|
||||
);
|
||||
|
||||
// Close the file by dropping the file descriptor
|
||||
drop(file);
|
||||
|
||||
// Reopen the file
|
||||
let mut file = File::open(path).unwrap();
|
||||
let mut input_buf = [MaybeUninit::uninit(); 10];
|
||||
let result = file.read_into(&mut input_buf);
|
||||
assert_eq!(
|
||||
result.map(|mut_slice| &*mut_slice),
|
||||
Ok(write_data),
|
||||
"Failed to read all the data from the file: roc_test_read_file"
|
||||
);
|
||||
|
||||
// Close the file by dropping the file descriptor
|
||||
drop(file);
|
||||
|
||||
// Remove the file
|
||||
let result = File::remove(&path);
|
||||
assert!(result, "Failed to remove the file: roc_test_read_file");
|
||||
|
||||
// Verify that the file no longer exists
|
||||
let file_result = File::open(path);
|
||||
|
||||
assert_eq!(
|
||||
file_result.map(|_| ()),
|
||||
Err(FileIoErr::NotFound),
|
||||
"File should not exist after removal: roc_test_read_file"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tempfile() {
|
||||
const DATA: &[u8] = &[42; 10];
|
||||
let buf = &mut [MaybeUninit::uninit(); DATA.len()];
|
||||
|
||||
let answer = File::with_tempfile(|result| {
|
||||
let (native_path, file) = result.unwrap();
|
||||
|
||||
// Write some data to the tempfile
|
||||
assert_eq!(
|
||||
Ok(DATA.len()),
|
||||
file.write(DATA),
|
||||
"Failed to write all the bytes to the tempfile"
|
||||
);
|
||||
|
||||
// Reopen the tempfile with a fresh file descriptor, and read the data back in from it
|
||||
File::open(native_path).unwrap().read_into(buf)
|
||||
});
|
||||
|
||||
// The data read should match the data written
|
||||
assert_eq!(
|
||||
Ok(DATA),
|
||||
answer.map(|mut_slice| &*mut_slice),
|
||||
"Data read from the file does not match data written to the file"
|
||||
);
|
||||
}
|
||||
}
|
7
crates/fs/src/lib.rs
Normal file
7
crates/fs/src/lib.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
#![cfg_attr(not(any(debug_assertions, test)), no_std)]
|
||||
|
||||
mod file;
|
||||
mod native_path;
|
||||
|
||||
pub use file::*;
|
||||
pub use native_path::*;
|
41
crates/fs/src/native_path.rs
Normal file
41
crates/fs/src/native_path.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
use core::{fmt, mem};
|
||||
|
||||
#[cfg(unix)]
|
||||
use core::ffi::CStr;
|
||||
|
||||
#[cfg(windows)]
|
||||
use widestring::U16CStr;
|
||||
|
||||
#[cfg(unix)]
|
||||
#[repr(transparent)]
|
||||
pub struct NativePath {
|
||||
pub(crate) inner: CStr,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[repr(transparent)]
|
||||
pub struct NativePath {
|
||||
pub(crate) inner: U16CStr,
|
||||
}
|
||||
|
||||
impl fmt::Debug for NativePath {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.inner.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl<'a> From<&'a CStr> for &'a NativePath {
|
||||
fn from(c_str: &'a CStr) -> Self {
|
||||
// Safety: Self is repr(transparent)
|
||||
unsafe { mem::transmute(c_str) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl<'a> From<&'a U16CStr> for &'a NativePath {
|
||||
fn from(inner: &'a U16CStr) -> Self {
|
||||
// Safety: Self is repr(transparent)
|
||||
unsafe { mem::transmute(c_str) }
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue