#![allow(unsafe_code)]
use crate::config::{RuntimeConfig, RUNTIME_KEYS};
use crate::demo::{Demo, DEMO_KEYS};
use crate::device::MemDevice;
use crate::error::{Error, Result};
use crate::fs::map::json_string;
use crate::fs::Fs;
use std::cell::RefCell;
use std::fmt::Write as _;
thread_local! {
static VOLUME: RefCell<Option<Fs<MemDevice>>> = const { RefCell::new(None) };
static OUTPUT: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
}
fn quote(text: &str) -> String {
let mut out = String::new();
json_string(&mut out, text);
out
}
fn finish(result: Result<Vec<u8>>) -> i32 {
let (bytes, code) = match result {
Ok(bytes) => (bytes, 0),
Err(error) => (error.to_string().into_bytes(), -1),
};
OUTPUT.with(|output| *output.borrow_mut() = bytes);
code
}
fn with_volume(run: impl FnOnce(&mut Fs<MemDevice>) -> Result<Vec<u8>>) -> i32 {
finish(VOLUME.with(|volume| {
volume.borrow_mut().as_mut().map_or_else(
|| Err(Error::invalid("no volume: build or open one first")),
run,
)
}))
}
unsafe fn input(ptr: *const u8, len: usize) -> Vec<u8> {
if ptr.is_null() || len == 0 {
return Vec::new();
}
unsafe { std::slice::from_raw_parts(ptr, len) }.to_vec()
}
fn text(bytes: &[u8]) -> Result<String> {
String::from_utf8(bytes.to_vec()).map_err(|_| Error::invalid("input is not UTF-8"))
}
fn summary(fs: &Fs<MemDevice>, note: &str) -> Vec<u8> {
let stats = fs.statfs();
let sb = fs.superblock();
format!(
"{{\"image_len\":{},\"block_size\":{},\"blocks\":{},\"free\":{},\"files\":{},\"links\":{},\"layout\":{},\"label\":{},\"note\":{},\"config\":{}}}",
fs.image_len(),
stats.block_size,
stats.blocks,
stats.free,
stats.files,
stats.links,
quote(sb.layout.name()),
quote(&sb.label),
quote(note),
quote(&fs.config().to_lino()),
)
.into_bytes()
}
#[no_mangle]
pub extern "C" fn lfs_alloc(len: usize) -> *mut u8 {
let mut buffer = vec![0u8; len.max(1)].into_boxed_slice();
let ptr = buffer.as_mut_ptr();
std::mem::forget(buffer);
ptr
}
#[no_mangle]
pub unsafe extern "C" fn lfs_free(ptr: *mut u8, len: usize) {
if !ptr.is_null() {
drop(unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr, len.max(1))) });
}
}
#[no_mangle]
pub extern "C" fn lfs_output_ptr() -> *const u8 {
OUTPUT.with(|output| output.borrow().as_ptr())
}
#[no_mangle]
pub extern "C" fn lfs_output_len() -> usize {
OUTPUT.with(|output| output.borrow().len())
}
#[no_mangle]
pub extern "C" fn lfs_set_time(ns: i64) {
crate::fs::set_wasm_clock(ns);
}
#[no_mangle]
pub extern "C" fn lfs_keys() -> i32 {
let list = |keys: &[(&str, &str)]| {
keys.iter()
.map(|(key, help)| format!("[{},{}]", quote(key), quote(help)))
.collect::<Vec<_>>()
.join(",")
};
let mut defaults = Demo::default().to_text();
for (key, value) in RuntimeConfig::default().pairs() {
let _ = write!(defaults, "\n{key}={value}");
}
finish(Ok(format!(
"{{\"demo\":[{}],\"runtime\":[{}],\"defaults\":{}}}",
list(DEMO_KEYS),
list(RUNTIME_KEYS),
quote(&defaults)
)
.into_bytes()))
}
#[no_mangle]
pub unsafe extern "C" fn lfs_demo(ptr: *const u8, len: usize) -> i32 {
let params = unsafe { input(ptr, len) };
finish(text(¶ms).and_then(|params| {
let fs = Demo::parse(¶ms)?.build()?;
let out = summary(&fs, "demo volume");
VOLUME.with(|volume| *volume.borrow_mut() = Some(fs));
Ok(out)
}))
}
#[no_mangle]
pub unsafe extern "C" fn lfs_open(ptr: *const u8, len: usize) -> i32 {
let bytes = unsafe { input(ptr, len) };
let opened = match Fs::open(MemDevice::from_bytes(bytes.clone()), false, &[]) {
Err(Error::Unsupported(_)) => Fs::open(MemDevice::from_bytes(bytes), true, &[])
.map(|fs| (fs, "image (journal replayed in memory)")),
other => other.map(|fs| (fs, "image")),
};
finish(opened.map(|(fs, note)| {
let out = summary(&fs, note);
VOLUME.with(|volume| *volume.borrow_mut() = Some(fs));
out
}))
}
#[no_mangle]
pub extern "C" fn lfs_map() -> i32 {
with_volume(|fs| Ok(fs.map_json().into_bytes()))
}
#[no_mangle]
pub unsafe extern "C" fn lfs_node(ptr: *const u8, len: usize, depth: u32) -> i32 {
let path = unsafe { input(ptr, len) };
with_volume(|fs| {
let path = parse_path(&text(&path)?)?;
Ok(fs
.blueprint_at(&path, depth as usize)?
.to_json()
.into_bytes())
})
}
#[no_mangle]
pub extern "C" fn lfs_locate(offset: u64) -> i32 {
with_volume(|fs| {
let path = fs.blueprint_locate(offset)?;
let items: Vec<String> = path.iter().map(ToString::to_string).collect();
Ok(format!("[{}]", items.join(",")).into_bytes())
})
}
#[no_mangle]
pub extern "C" fn lfs_bytes(offset: u64, len: u64) -> i32 {
with_volume(|fs| {
let end = offset.saturating_add(len).min(fs.image_len());
fs.read_image(offset, end.saturating_sub(offset))
})
}
#[no_mangle]
pub extern "C" fn lfs_image() -> i32 {
with_volume(|fs| fs.read_image(0, fs.image_len()))
}
fn parse_path(text: &str) -> Result<Vec<usize>> {
text.split('/')
.map(str::trim)
.filter(|part| !part.is_empty())
.map(|part| {
part.parse()
.map_err(|_| Error::invalid(format!("bad path component {part:?}")))
})
.collect()
}