diff --git a/Cargo.lock b/Cargo.lock index 4a159c4..be32482 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -688,7 +688,9 @@ dependencies = [ name = "megaton" version = "0.0.0" dependencies = [ + "hermit-abi", "megaton-macros", + "static_assertions", ] [[package]] @@ -1053,6 +1055,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" diff --git a/Cargo.toml b/Cargo.toml index 1b0a74e..142af4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,13 @@ package = "pistonite-cu" version = "0.7.3" # path = "../../../cu/packages/copper" +[workspace.dependencies.pm] +package = "pistonite-pm" +version = "0.2.6" + [workspace.dependencies] +static_assertions = "1.1.0" +hermit-abi = "0.5.2" ignore = "0.4.25" flate2 = "1.1.2" tar = "0.4.44" diff --git a/Taskfile.yml b/Taskfile.yml index 9a6d7dd..83ccee2 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -33,6 +33,7 @@ tasks: check-non-dkp: - task: cli:check - lisensor + - task: lib:check-rs check-dkp: - task: lib:configure - task: lib:check-c @@ -43,9 +44,10 @@ tasks: build-non-dkp: - task: cli:build - task: docs:build + - task: lib:build-rs build-dkp: - task: lib:configure - - task: lib:build + - task: lib:build-c license: desc: check or fix license notices (-- -f to fix) diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 89d6ccd..97747f4 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -25,11 +25,8 @@ regex = "1.12.2" cargo_metadata = "0.23.1" semver = { version = "1.0.28", features = ["serde"] } -[build-dependencies.cu] -workspace = true -features = ["process"] - [build-dependencies] +cu = { workspace = true, features = ["process", "toml"] } ignore.workspace = true flate2.workspace = true tar.workspace = true diff --git a/packages/cli/build.rs b/packages/cli/build.rs index 1b090e0..f8aae14 100644 --- a/packages/cli/build.rs +++ b/packages/cli/build.rs @@ -85,6 +85,59 @@ resolver = 2 members = [ "macros" ] "##, ); + let mut cargo_toml = toml::parse::(&cargo_toml)?; + // patch [dependencies] .workspace = true to be .version = + let workspace = cu::fs::read_string(packages_path.parent_abs()?.join("Cargo.toml"))?; + let workspace = toml::parse::(&workspace)?; + + // collect deps to inherit + let mut workspace_dep_names = vec![]; + let deps = cu::check!( + cargo_toml.get("dependencies").and_then(|x| x.as_table()), + "didn't find megaton lib dependencies or is not table" + )?; + for (dep_name, dep_data) in deps { + let Some(dep_data) = dep_data.as_table() else { + continue; + }; + let is_workspace = dep_data + .get("workspace") + .and_then(|x| x.as_bool()) + .unwrap_or(false); + if is_workspace { + workspace_dep_names.push(dep_name.clone()); + } + } + + // copy from workspace toml + let workspace_deps = cu::check!( + workspace + .get("workspace") + .and_then(|x| x.as_table()) + .and_then(|x| x.get("dependencies")) + .and_then(|x| x.as_table()), + "didn't find workspace dependencies or is not table" + )?; + let mut new_workspace_deps = toml::Table::new(); + for dep_name in workspace_dep_names { + let workspace_dep_data = cu::check!( + workspace_deps.get(&dep_name), + "did not find dependency '{dep_name}' in workspace" + )?; + if let Some(data) = workspace_dep_data.as_table() { + if data.get("path").is_some() { + cu::bail!("workspace dep cannot have path when packing library: {dep_name}"); + } + } + new_workspace_deps.insert(dep_name, workspace_dep_data.clone()); + } + // unwrap: we added it as string above + cargo_toml["workspace"] + .as_table_mut() + .unwrap() + .insert("dependencies".to_string(), new_workspace_deps.into()); + + let cargo_toml = toml::stringify_pretty(&cargo_toml)?; let bytes = cargo_toml.as_bytes(); let mut header = tar::Header::new_gnu(); header.set_path("Cargo.toml")?; diff --git a/packages/lib/Cargo.toml b/packages/lib/Cargo.toml index 1588582..c881eb6 100644 --- a/packages/lib/Cargo.toml +++ b/packages/lib/Cargo.toml @@ -2,6 +2,11 @@ name = "megaton" version = "0.0.0" edition = "2024" +license = "GPL-3.0-or-later" +description = "Megaton Library that works with the build tool. Automatically unpacked by the build tool" # for the library unpacked by build tool +publish = false [dependencies] +static_assertions.workspace = true +hermit-abi.workspace = true megaton-macros.path = "macros" diff --git a/packages/lib/Taskfile.yml b/packages/lib/Taskfile.yml index e83c9f9..29d6298 100644 --- a/packages/lib/Taskfile.yml +++ b/packages/lib/Taskfile.yml @@ -16,15 +16,22 @@ tasks: cmds: - cmake -G Ninja -B build -S . - build: - desc: Execute incremental build + build-c: cmds: - ninja -C build + build-rs: + cmds: + - cargo build + clean: desc: Clean build directory cmds: - - rm -rf build + - rm -rf build target + + check: + - task: check-rs + - task: check-c check-c: cmds: @@ -38,8 +45,16 @@ tasks: HEADER: ^include/ ADD_CHECKS: > -bugprone-easily-swappable-parameters + + check-rs: + cmds: + - task: cargo:clippy-all + - task: cargo:fmt-check fix: cmds: - task: ccpp:fmt-fix vars: SOURCE: src/megaton src/nximpl + - task: cargo:fmt-fix + + diff --git a/packages/lib/include/megaton/main.h b/packages/lib/include/megaton/main.h new file mode 100644 index 0000000..63874fe --- /dev/null +++ b/packages/lib/include/megaton/main.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2026 Megaton contributors + +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Rust entry point, generated by megaton::main macro in Rust. + * Use megaton::rust_main instead of this function!! + */ +// NOLINTNEXTLINE(bugprone-reserved-identifier) FIXME +void __megaton_rs_main(); + +#ifdef __cplusplus +} +#endif + +#ifdef __cplusplus +namespace megaton { +/** + * Call the rust entry point (the function annotated with #[megaton::main]) + */ +inline_always_ void rust_main() { + __megaton_rs_main(); +} +} +#endif diff --git a/packages/lib/include/megaton/module.h b/packages/lib/include/megaton/module.h index 12b726d..c95f48d 100644 --- a/packages/lib/include/megaton/module.h +++ b/packages/lib/include/megaton/module.h @@ -13,13 +13,13 @@ extern "C" { #endif -// NOLINTNEXTLINE(bugprone-reserved-identifier) +// NOLINTNEXTLINE(bugprone-reserved-identifier) FIXME extern const char* __megaton_module_name(void); -// NOLINTNEXTLINE(bugprone-reserved-identifier) +// NOLINTNEXTLINE(bugprone-reserved-identifier) FIXME extern usize __megaton_module_name_len(void); -// NOLINTNEXTLINE(bugprone-reserved-identifier) +// NOLINTNEXTLINE(bugprone-reserved-identifier) FIXME extern const char* __megaton_title_id_hex(void); -// NOLINTNEXTLINE(bugprone-reserved-identifier) +// NOLINTNEXTLINE(bugprone-reserved-identifier) FIXME extern u64 __megaton_title_id(void); #ifdef __cplusplus @@ -29,24 +29,16 @@ extern u64 __megaton_title_id(void); #ifdef __cplusplus namespace megaton { -/** - * Get the module name. - */ +/** Get the module name. */ inline_always_ const char* module_name() { return __megaton_module_name(); } -/** - * Get the module name length. - */ +/** Get the module name length. */ inline_always_ usize module_name_len() { return __megaton_module_name_len(); } -/** - * Get the title ID in hexadecimal, without the 0x prefix - */ +/** Get the title ID in hexadecimal, without the 0x prefix */ inline_always_ const char* title_id_hex() { return __megaton_title_id_hex(); } -/** - * Get the title ID as a 64-bit integer. - */ +/** Get the title ID as a 64-bit integer. */ inline_always_ u64 title_id() { return __megaton_title_id(); } } // namespace megaton diff --git a/packages/lib/include/megaton/prelude.h b/packages/lib/include/megaton/prelude.h index 90ae6c8..3a40eab 100644 --- a/packages/lib/include/megaton/prelude.h +++ b/packages/lib/include/megaton/prelude.h @@ -1,13 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Copyright (c) 2025-2026 Megaton contributors -// * * * * * -// This file was taken from the exlaunch project and modified. -// See original license information below -// -// SPDX-License-Identifier: GPL-2.0-or-later -// Copyright (c) shadowninja -// SPDX-License-Identifier: ISC -// Copyright (c) libnx Authors /** * prelude.h should be included by all source files. @@ -15,48 +7,6 @@ * This includes common primitive types, macros, and attributes */ #pragma once - -#include -#include -#include - -typedef uint8_t u8; ///< 8-bit unsigned integer. -typedef uint16_t u16; ///< 16-bit unsigned integer. -typedef uint32_t u32; ///< 32-bit unsigned integer. -typedef uint64_t u64; ///< 64-bit unsigned integer. -typedef __uint128_t u128; ///< 128-bit unsigned integer. - -typedef int8_t s8; ///< 8-bit signed integer. -typedef int16_t s16; ///< 16-bit signed integer. -typedef int32_t s32; ///< 32-bit signed integer. -typedef int64_t s64; ///< 64-bit signed integer. -typedef __int128_t s128; ///< 128-bit unsigned integer. - -// rust-ish types -typedef s8 i8; -typedef s16 i16; -typedef s32 i32; -typedef s64 i64; -typedef float f32; -typedef double f64; - -typedef volatile u8 vu8; ///< 8-bit volatile unsigned integer. -typedef volatile u16 vu16; ///< 16-bit volatile unsigned integer. -typedef volatile u32 vu32; ///< 32-bit volatile unsigned integer. -typedef volatile u64 vu64; ///< 64-bit volatile unsigned integer. -typedef volatile u128 vu128; ///< 128-bit volatile unsigned integer. - -typedef volatile s8 vs8; ///< 8-bit volatile signed integer. -typedef volatile s16 vs16; ///< 16-bit volatile signed integer. -typedef volatile s32 vs32; ///< 32-bit volatile signed integer. -typedef volatile s64 vs64; ///< 64-bit volatile signed integer. -typedef volatile s128 vs128; ///< 128-bit volatile signed integer. - -typedef size_t usize; - - -// prelude headers - #include #include #include diff --git a/packages/lib/macros/Cargo.toml b/packages/lib/macros/Cargo.toml index 5bcd51f..7994cdd 100644 --- a/packages/lib/macros/Cargo.toml +++ b/packages/lib/macros/Cargo.toml @@ -3,10 +3,8 @@ name = "megaton-macros" version = "0.0.0" edition = "2024" -[dependencies.pm] -package = "pistonite-pm" -version = "0.2.6" -features = ["full"] +[dependencies] +pm = { workspace = true, features = ["full"] } [lib] proc-macro = true diff --git a/packages/lib/src/fs/fs_helpers.rs b/packages/lib/src/fs/binding.rs similarity index 55% rename from packages/lib/src/fs/fs_helpers.rs rename to packages/lib/src/fs/binding.rs index 11f58b8..ca805f5 100644 --- a/packages/lib/src/fs/fs_helpers.rs +++ b/packages/lib/src/fs/binding.rs @@ -13,11 +13,11 @@ pub struct NNResult { #[repr(C)] pub enum FileDescriptorType { #[allow(dead_code)] - FILE, + File, #[allow(dead_code)] - DIR, + Directory, #[allow(dead_code)] - TCP, + Tcp, } #[repr(C)] @@ -52,13 +52,6 @@ pub struct GetSizeResult { pub size: i64, } -#[repr(C)] -#[allow(dead_code)] -pub struct StatResult { - pub result: NNResult, - pub stat_val: stat, -} - unsafe extern "C" { #[link_name = "__megaton_lib_fs_write_file"] pub unsafe fn write_file(nn_fd: u64, buf: *const u8, size: usize, position: u64) -> NNResult; @@ -94,120 +87,61 @@ unsafe extern "C" { pub unsafe fn write_stderr(buf: *const u8, len: u64); } -#[allow(dead_code)] +#[allow(dead_code)] // FIXME // https://github.com/hermit-os/hermit-rs/blob/82146cf059bf3894eea1e96beed9da72b99b9d5a/hermit-abi/src/lib.rs#L44 pub const STDIN_FILENO: usize = 0; -#[allow(dead_code)] +#[allow(dead_code)] // FIXME pub const STDOUT_FILENO: usize = 1; pub const STDERR_FILENO: usize = 2; +#[allow(dead_code)] // FIXME pub const LOG_FILENO: usize = 3; pub const O_RDONLY: i32 = 0o0; -#[allow(dead_code)] +#[allow(dead_code)] // FIXME pub const O_WRONLY: i32 = 0o1; +#[allow(dead_code)] // FIXME pub const O_RDWR: i32 = 0o2; +#[allow(dead_code)] // FIXME pub const O_CREAT: i32 = 0o100; -#[allow(dead_code)] +#[allow(dead_code)] // FIXME pub const O_EXCL: i32 = 0o200; -#[allow(dead_code)] +#[allow(dead_code)] // FIXME pub const O_TRUNC: i32 = 0o1000; -#[allow(dead_code)] +#[allow(dead_code)] // FIXME pub const O_APPEND: i32 = 0o2000; -#[allow(dead_code)] +#[allow(dead_code)] // FIXME pub const O_NONBLOCK: i32 = 0o4000; -#[allow(dead_code)] +#[allow(dead_code)] // FIXME pub const O_DIRECTORY: i32 = 0o200000; -#[allow(dead_code)] +#[allow(dead_code)] // FIXME pub const FS_ERR_MODULE: i32 = 2; // all fs errors will have module = 2 -#[allow(dead_code)] +#[allow(dead_code)] // FIXME pub const PATH_NOT_FOUND: i32 = 1; -#[allow(dead_code)] +#[allow(dead_code)] // FIXME pub const PATH_ALREADY_EXISTS: i32 = 2; -#[allow(dead_code)] +#[allow(dead_code)] // FIXME pub const TARGET_LOCKED: i32 = 7; -#[allow(dead_code)] +#[allow(dead_code)] // FIXME pub const DIRECTORY_NOT_EMPTY: i32 = 8; -// https://github.com/hermit-os/hermit-rs/blob/82146cf059bf3894eea1e96beed9da72b99b9d5a/hermit-abi/src/lib.rs#L169 -#[allow(non_camel_case_types, dead_code)] -type time_t = i64; - -// https://github.com/hermit-os/hermit-rs/blob/82146cf059bf3894eea1e96beed9da72b99b9d5a/hermit-abi/src/lib.rs#L284 -#[repr(C)] -#[derive(Debug, Default, Copy, Clone)] -pub struct stat { - pub st_dev: u64, - pub st_ino: u64, - pub st_nlink: u64, - /// access permissions - pub st_mode: u32, - /// user id - pub st_uid: u32, - /// group id - pub st_gid: u32, - /// device id - pub st_rdev: u64, - /// size in bytes - pub st_size: i64, - /// block size - pub st_blksize: i64, - /// size in blocks - pub st_blocks: i64, - /// time of last access - pub st_atim: timespec, - /// time of last modification - pub st_mtim: timespec, - /// time of last status change - pub st_ctim: timespec, -} - -// https://github.com/hermit-os/kernel/blob/884cdccf6a5ca532b5aad102a530e2d6e7cf5b25/src/time.rs -/// Represent the number of seconds and nanoseconds since -/// the Epoch (1970-01-01 00:00:00 +0000 (UTC)) -#[derive(Copy, Clone, Debug, Default)] -#[repr(C)] -pub struct timespec { - /// seconds - pub tv_sec: time_t, - /// nanoseconds - pub tv_nsec: i32, -} - -#[allow(dead_code)] -impl timespec { - pub fn from_usec(microseconds: i64) -> Self { - Self { - tv_sec: (microseconds / 1_000_000), - tv_nsec: ((microseconds % 1_000_000) * 1000) as i32, - } - } - - pub fn into_usec(&self) -> Option { - self.tv_sec - .checked_mul(1_000_000) - .and_then(|usec| usec.checked_add((self.tv_nsec / 1000).into())) - } -} - /* Kinds of entries within a directory. */ -#[allow(non_camel_case_types)] #[repr(C)] pub enum DirectoryEntryType { - #[allow(non_camel_case_types, dead_code)] - DirectoryEntryType_Directory, - #[allow(non_camel_case_types, dead_code)] - DirectoryEntryType_File, + #[allow(dead_code)] // FIXME + Directory, + #[allow(dead_code)] // FIXME + File, } /* Bitfield to define the kinds of entries to open from a directory. */ #[repr(C)] -#[allow(dead_code)] +#[allow(dead_code)] // FIXME enum OpenDirectoryMode { - #[allow(non_camel_case_types, dead_code)] - OpenDirectoryMode_Directory = 1 << 0, - #[allow(non_camel_case_types, dead_code)] - OpenDirectoryMode_File = 1 << 1, - #[allow(non_camel_case_types, dead_code)] - OpenDirectoryMode_All = (1 << 0) | (1 << 1), + #[allow(dead_code)] // FIXME + Directory = 1 << 0, + #[allow(dead_code)] // FIXME + File = 1 << 1, + #[allow(dead_code)] // FIXME + All = (1 << 0) | (1 << 1), } diff --git a/packages/lib/src/fs/mod.rs b/packages/lib/src/fs/mod.rs index 916bde8..568302d 100644 --- a/packages/lib/src/fs/mod.rs +++ b/packages/lib/src/fs/mod.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Copyright (c) 2025-2026 Megaton contributors -pub mod fs; -mod fs_helpers; +mod binding; +mod syscall; diff --git a/packages/lib/src/fs/fs.rs b/packages/lib/src/fs/syscall.rs similarity index 74% rename from packages/lib/src/fs/fs.rs rename to packages/lib/src/fs/syscall.rs index 8bce4a6..e214203 100644 --- a/packages/lib/src/fs/fs.rs +++ b/packages/lib/src/fs/syscall.rs @@ -6,7 +6,9 @@ const MIN_FD: usize = 100; // fds other than stdio will be returned as their ind const NUM_FDS: usize = 1000; const GENERIC_ERRNO: i32 = -1; -use crate::fs::fs_helpers::{ +use hermit_abi::EINVAL; + +use crate::fs::binding::{ self, FileDescriptor, FileDescriptorType, GetEntryTypeResult, NNResult, O_RDONLY, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO, write_stderr, write_stdout, }; @@ -58,9 +60,9 @@ fn set_fd_entry(fd: usize, fd_entry: Option) { } fn megaton_log(msg: &str) { - let len = (&msg).len(); + let len = msg.len(); let msg = CString::new(msg).unwrap(); - unsafe { fs_helpers::megaton_log(msg.as_c_str().as_ptr() as *const u8, len as u64) }; + unsafe { binding::megaton_log(msg.as_c_str().as_ptr() as *const u8, len as u64) }; } fn log_error(msg: &str, result: &NNResult) { @@ -84,7 +86,7 @@ pub extern "C" fn sys_open(name: *const i8, flags: i32, mode: i32) -> i32 { ) .as_str(), ); - let open_result = unsafe { fs_helpers::open(name, flags, mode) }; + let open_result = unsafe { binding::open(name, flags, mode) }; if open_result.result.success { match insert_into_fd_list(open_result.fd) { Some(fd_index) => fd_index as i32, @@ -117,15 +119,18 @@ pub fn sys_write(fd: i32, buf: *const u8, len: usize) -> isize { match fd_entry { None => GENERIC_ERRNO as isize, Some(fd) => match fd.kind { - FileDescriptorType::FILE => try_write_file(fd, fd_index, buf, len), - FileDescriptorType::DIR => GENERIC_ERRNO as isize, - FileDescriptorType::TCP => todo!(), + FileDescriptorType::File => try_write_file(fd, fd_index, buf, len), + FileDescriptorType::Directory => GENERIC_ERRNO as isize, + FileDescriptorType::Tcp => { + // FIXME + panic!("todo: tcp not implemented"); + } }, } } fn try_write_file(mut fd: FileDescriptor, fd_index: usize, buf: *const u8, len: usize) -> isize { - let write_result = unsafe { fs_helpers::write_file(fd.inner, buf, len, fd.seek_offset) }; + let write_result = unsafe { binding::write_file(fd.inner, buf, len, fd.seek_offset) }; if write_result.success { fd.seek_offset += len as u64; @@ -133,9 +138,8 @@ fn try_write_file(mut fd: FileDescriptor, fd_index: usize, buf: *const u8, len: len as isize } else { log_error("Megaton: sys_write failed!", &write_result); - match write_result.description { - _ => GENERIC_ERRNO as isize, // TODO: Map nn error to errno - } + // TODO: Map nn error to errno + GENERIC_ERRNO as isize } } @@ -147,7 +151,7 @@ pub extern "C" fn sys_read(fd_index: i32, buf: *mut u8, len: usize) -> isize { match fd_index as usize { STDIN_FILENO => return GENERIC_ERRNO as isize, STDOUT_FILENO | STDERR_FILENO => { - todo!() + return -EINVAL as isize; } _ => {} }; @@ -156,16 +160,22 @@ pub extern "C" fn sys_read(fd_index: i32, buf: *mut u8, len: usize) -> isize { match fd_entry { None => -1, Some(fd) => match fd.kind { - FileDescriptorType::FILE => try_read_file(fd, fd_index as usize, buf, len), - FileDescriptorType::DIR => todo!(), - FileDescriptorType::TCP => todo!(), + FileDescriptorType::File => try_read_file(fd, fd_index as usize, buf, len), + FileDescriptorType::Directory => { + // FIXME + panic!("todo: read directory not implemented") + } + FileDescriptorType::Tcp => { + // FIXME + panic!("todo: read tcp not implemented") + } }, } } fn try_read_file(mut fd_entry: FileDescriptor, fd_index: usize, buf: *mut u8, len: usize) -> isize { let read_result = - unsafe { fs_helpers::read_file(fd_entry.inner, fd_entry.seek_offset, buf, len as u64) }; + unsafe { binding::read_file(fd_entry.inner, fd_entry.seek_offset, buf, len as u64) }; if !read_result.result.success { log_error("Failed to read file", &read_result.result); return GENERIC_ERRNO as isize; @@ -177,7 +187,7 @@ fn try_read_file(mut fd_entry: FileDescriptor, fd_index: usize, buf: *mut u8, le } #[unsafe(no_mangle)] -pub extern "C" fn sys_fstat(fd: i32, stat: *mut fs_helpers::stat) -> i32 { +pub extern "C" fn sys_fstat(fd: i32, stat: *mut hermit_abi::stat) -> i32 { megaton_log(format!("sys_fstat called for fd={}\n", fd).as_str()); match fd as usize { STDIN_FILENO | STDOUT_FILENO | STDERR_FILENO => { @@ -186,26 +196,25 @@ pub extern "C" fn sys_fstat(fd: i32, stat: *mut fs_helpers::stat) -> i32 { _ => {} }; - if let Some(fd) = get_fd_entry(fd as usize) { - stat_file(&fd, stat) - } else { + let Some(fd) = get_fd_entry(fd as usize) else { return GENERIC_ERRNO; - } + }; + stat_file(&fd, stat) } // https://github.com/hermit-os/hermit-rs/blob/111a7b480a18ce1b6c576d9dac02a688203432ee/hermit/src/syscall/mod.rs#L187 #[unsafe(no_mangle)] -pub extern "C" fn sys_stat(name: *const i8, stat: *mut fs_helpers::stat) -> i32 { +pub extern "C" fn sys_stat(name: *const i8, stat: *mut hermit_abi::stat) -> i32 { megaton_log( format!("sys_stat called with name={:?}\n", unsafe { std::ffi::CStr::from_ptr(name as *const std::ffi::c_char) }) .as_str(), ); - let entry_type: GetEntryTypeResult = unsafe { fs_helpers::get_entry_type(name) }; + let entry_type: GetEntryTypeResult = unsafe { binding::get_entry_type(name) }; if !entry_type.result.success { match entry_type.result.description { - fs_helpers::PATH_NOT_FOUND => { + binding::PATH_NOT_FOUND => { return GENERIC_ERRNO; // TODO: get err code for this } @@ -217,10 +226,10 @@ pub extern "C" fn sys_stat(name: *const i8, stat: *mut fs_helpers::stat) -> i32 } let mut already_open = false; - let open_result = unsafe { fs_helpers::open(name, 0, O_RDONLY) }; + let open_result = unsafe { binding::open(name, 0, O_RDONLY) }; if !open_result.result.success { match open_result.result.description { - fs_helpers::TARGET_LOCKED => { + binding::TARGET_LOCKED => { already_open = true; } _ => { @@ -232,7 +241,8 @@ pub extern "C" fn sys_stat(name: *const i8, stat: *mut fs_helpers::stat) -> i32 let handle: FileDescriptor = if already_open { megaton_log("sys_stat failed! TODO: Get fd entry with matching name"); - todo!() // get fd entry with matching name + // FIXME + panic!("todo: sys_stat for opened file not implemented"); } else { open_result.fd }; @@ -242,8 +252,9 @@ pub extern "C" fn sys_stat(name: *const i8, stat: *mut fs_helpers::stat) -> i32 result } -fn stat_file(fd: &FileDescriptor, stat: *mut fs_helpers::stat) -> i32 { - let mut stat = unsafe { *stat }; +fn stat_file(fd: &FileDescriptor, stat: *mut hermit_abi::stat) -> i32 { + // FIXME: unsafe note + let stat = unsafe { &mut *stat }; stat.st_uid = 0; // owner user id stat.st_gid = 0; // owner group id stat.st_dev = 0; // device number @@ -254,25 +265,24 @@ fn stat_file(fd: &FileDescriptor, stat: *mut fs_helpers::stat) -> i32 { const S_IWUSR: u32 = 0o200; // write const S_ISREG: u32 = 0o100000; // regular file const S_ISDIR: u32 = 0o040000; // directory - const S_ISSOCK: u32 = 0140000; // socket + const S_ISSOCK: u32 = 0o140000; // socket let mut st_mode: u32 = S_IRUSR | S_IWUSR; match fd.kind { - FileDescriptorType::FILE => st_mode |= S_ISREG, - FileDescriptorType::DIR => st_mode |= S_ISDIR, - FileDescriptorType::TCP => st_mode |= S_ISSOCK, + FileDescriptorType::File => st_mode |= S_ISREG, + FileDescriptorType::Directory => st_mode |= S_ISDIR, + FileDescriptorType::Tcp => st_mode |= S_ISSOCK, }; stat.st_mode = st_mode; - let size_result = unsafe { fs_helpers::get_file_size(fd.inner) }; + let size_result = unsafe { binding::get_file_size(fd.inner) }; if !size_result.result.success { return GENERIC_ERRNO; - } else { - stat.st_size = size_result.size; - stat.st_blksize = 1000; - stat.st_blocks = ((size_result.size as f64) / 1000.0f64).ceil() as i64; - return 0; } + stat.st_size = size_result.size; + stat.st_blksize = 1000; + stat.st_blocks = ((size_result.size as f64) / 1000.0f64).ceil() as i64; + 0 } #[unsafe(no_mangle)] @@ -282,7 +292,7 @@ pub fn sys_close(fd: i32) -> i32 { match fd as usize { STDIN_FILENO | STDOUT_FILENO | STDERR_FILENO => { - todo!(); + return -EINVAL; } _ => {} }; @@ -294,11 +304,14 @@ pub fn sys_close(fd: i32) -> i32 { } Some(fd) => { match fd.kind { - FileDescriptorType::FILE => try_close_file(fd.inner), - FileDescriptorType::DIR => unsafe { - fs_helpers::close_directory(fd.inner); + FileDescriptorType::File => try_close_file(fd.inner), + FileDescriptorType::Directory => unsafe { + binding::close_directory(fd.inner); }, - FileDescriptorType::TCP => todo!(), + FileDescriptorType::Tcp => { + //FIXME + panic!("todo: close tcp not implemented") + } }; } }; @@ -309,11 +322,12 @@ pub fn sys_close(fd: i32) -> i32 { #[unsafe(no_mangle)] pub extern "C" fn sys_writev(_fd: i32, _iov: *const u8, _iovcnt: usize) -> isize { - todo!() + //FIXME + panic!("todo: sys_writev not implemented") } fn try_close_file(fd: u64) { - unsafe { fs_helpers::close_file(fd) }; + unsafe { binding::close_file(fd) }; } #[unsafe(no_mangle)] @@ -324,6 +338,6 @@ pub extern "C" fn sys_unlink(name: *const i8) -> i32 { }) .as_str(), ); - let result = unsafe { fs_helpers::unlink(name) }; + let result = unsafe { binding::unlink(name) }; if result.success { 0 } else { GENERIC_ERRNO } } diff --git a/packages/lib/src/lib.rs b/packages/lib/src/lib.rs index a92829e..5274b87 100644 --- a/packages/lib/src/lib.rs +++ b/packages/lib/src/lib.rs @@ -1,5 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Copyright (c) 2025-2026 Megaton contributors +// see src/megaton/futex.cpp +static_assertions::assert_type_eq_all!(hermit_abi::time_t, i64); +static_assertions::assert_eq_size!(hermit_abi::timespec, [u8; 0x10]); +static_assertions::assert_eq_size!(hermit_abi::stat, [u8; 0x78]); + mod fs; pub use megaton_macros::main; diff --git a/packages/lib/src/megamain.c b/packages/lib/src/megamain.c index a51a53d..9b3c27f 100644 --- a/packages/lib/src/megamain.c +++ b/packages/lib/src/megamain.c @@ -34,7 +34,7 @@ const char* __megaton_title_id_hex() { return MEGART_TITLE_ID_HEX; } void __megaton_lib_init(); // NOLINTNEXTLINE(bugprone-reserved-identifier) void __megaton_librs_init(); -void megaton_main(); +void megaton_main(); // defined by user // real module entry point // NOLINTNEXTLINE(bugprone-reserved-identifier) diff --git a/packages/lib/src/megaton/fs.cpp b/packages/lib/src/megaton/fs.cpp index 0b811a5..6eb5055 100644 --- a/packages/lib/src/megaton/fs.cpp +++ b/packages/lib/src/megaton/fs.cpp @@ -158,11 +158,7 @@ extern "C" ReadResult __megaton_lib_fs_read_file(uint64_t nn_fd, uint64_t seek_p uint64_t bytes_read; // u64 *outSize, nn::fs::FileHandle handle, // s64 offset, void *buffer, u64 bufferSize - nn::Result result = nn::fs::ReadFile(&bytes_read, - { - ._internal = nn_fd, - }, - s64(seek_pos), buf, len); + nn::Result result = nn::fs::ReadFile(&bytes_read, {nn_fd}, s64(seek_pos), buf, len); ReadResult read_result = {.result = build_simple_result(result), .bytes_read = bytes_read}; return read_result; } diff --git a/packages/lib/src/megaton/futex.cpp b/packages/lib/src/megaton/futex.cpp index c1558e6..8628d57 100644 --- a/packages/lib/src/megaton/futex.cpp +++ b/packages/lib/src/megaton/futex.cpp @@ -5,6 +5,11 @@ #include #include #include +#include + +// see lib.rs +static_assert(std::is_same_v, "time_t is i64 in hermit-abi"); +static_assert(sizeof(timespec) == 0x10, "timespec size is 0x10 in hermit-abi"); extern "C" { #include diff --git a/packages/test-mod/.gitignore b/packages/test-mod/.gitignore index 0acb2b1..1d3f83f 100644 --- a/packages/test-mod/.gitignore +++ b/packages/test-mod/.gitignore @@ -1,5 +1,4 @@ -target/ -target/** +/target +/.cache debug-font compile_commands.json -Cargo.lock \ No newline at end of file diff --git a/packages/test-mod/Cargo.toml b/packages/test-mod/Cargo.toml index 7078766..b5714fe 100644 --- a/packages/test-mod/Cargo.toml +++ b/packages/test-mod/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" [dependencies] cxx = "1.0.194" -megaton = { version = "0.0.0", path = "../lib/" } +megaton.path = "../lib/" # usually we should point to target/megaton/lib [lib] crate-type = ["staticlib"] diff --git a/packages/test-mod/Megaton.toml b/packages/test-mod/Megaton.toml index 5152675..4767ac9 100644 --- a/packages/test-mod/Megaton.toml +++ b/packages/test-mod/Megaton.toml @@ -1,14 +1,11 @@ [module] -name = "TestMod" +name = "test-mod" title-id = 0x01007ef00011e000 [megaton] version = "0.1.0" -[cargo] -enabled = true - -[build] # *profile enabled* +[build] sources = [ "src", ] @@ -35,26 +32,10 @@ c = [ "-DNN_WARE_MAJOR=4", "-DNN_WARE_MINOR=4", "-DNN_WARE_PATCH=0", -] -cxx = [ - "", - "-Wno-invalid-offsetof", -] - -ld = [ - "", -] - -rust = [ -] - -cargo = [ - "", + "-DTest" ] -[check] # *profile enabled* -ignore = [] +[check] symbols = [ "sdk.syms" ] -disallowed-instructions = [] diff --git a/packages/test-mod/Taskfile.yml b/packages/test-mod/Taskfile.yml index dd39475..25d87af 100644 --- a/packages/test-mod/Taskfile.yml +++ b/packages/test-mod/Taskfile.yml @@ -9,3 +9,10 @@ tasks: upload: cmds: - lftp 192.168.0.170:5000 < scripts/lftp-upload.sh + + download: + desc: Download logs and crash reports from console + cmds: + - mkdir -p target/crash_reports/dumps + - mkdir -p target/logs + - lftp 192.168.0.170:5000 < scripts/lftp-download.sh diff --git a/packages/test-mod/scripts/lftp-download.sh b/packages/test-mod/scripts/lftp-download.sh new file mode 100644 index 0000000..621540b --- /dev/null +++ b/packages/test-mod/scripts/lftp-download.sh @@ -0,0 +1,8 @@ +lcd target +lcd crash_reports +mget -E /atmosphere/crash_reports/* +lcd dumps +mget -E /atmosphere/crash_reports/dumps/* +rm -rf /atmosphere/crash_reports/* +lcd ../../logs +mget -E /megaton_logs.txt diff --git a/packages/test-mod/scripts/lftp-upload.sh b/packages/test-mod/scripts/lftp-upload.sh index f86108c..c59f52b 100644 --- a/packages/test-mod/scripts/lftp-upload.sh +++ b/packages/test-mod/scripts/lftp-upload.sh @@ -1,6 +1,6 @@ mkdir -p -f /atmosphere/contents/01007EF00011E000/exefs cd /atmosphere/contents/01007EF00011E000/exefs -mput -e target/megaton/none/example.nso -mput -e target/megaton/none/main.npdm +mput -e target/megaton/none/test-mod/test-mod.nso +mput -e target/megaton/none/test-mod/main.npdm rm -f subsdk9 -mv example.nso subsdk9 +mv test-mod.nso subsdk9 diff --git a/packages/test-mod/src/fs_tests.rs b/packages/test-mod/src/fs_tests.rs index 7d57ab4..936a4e0 100644 --- a/packages/test-mod/src/fs_tests.rs +++ b/packages/test-mod/src/fs_tests.rs @@ -11,29 +11,33 @@ use crate::MegatonTests; const LINES: [&[u8]; 2] = ["Hello world!\n".as_bytes(), "A".as_bytes()]; const TOTAL_CONTENT: &[u8] = "Hello world!\nA".as_bytes(); -const STDOUT_PATH: &str = "sd:/megaton_stdout.txt"; -const STDERR_PATH: &str = "sd:/megaton_stderr.txt"; +// const STDOUT_PATH: &str = "sd:/megaton_stdout.txt"; +// const STDERR_PATH: &str = "sd:/megaton_stderr.txt"; pub fn megaton_file_tests(mtt: &mut MegatonTests) { mtt.start_category("Files"); basic_tests(mtt); - test_exists(mtt); + // test_exists(mtt); crash: + // sys_stat failed! TODO: Get fd entry with matching name test_consecutive_writes(mtt); test_write_seek_offset(mtt); test_close_frees_fd(mtt); test_multiple_files(mtt); test_read_seek_offset(mtt); - test_open_flags(mtt); - test_print(mtt); - test_stderr(mtt); + // test_open_flags(mtt); crash: + // sys_open called with name="sd:/test_open_flags.txt" flags=577 mode=511 + // sys_open called with name="sd:/test_open_flags.txt" flags=0 mode=0 + // Megaton Error: NNResult description: 7 message: Megaton: sys_open failed! + + // test_print(mtt); crashed + // test_stderr(mtt); crashed mtt.end_category(); } fn basic_tests(mtt: &mut MegatonTests) { let path: PathBuf = PathBuf::from("sd:/testfile.txt"); - const total_len: usize = TOTAL_CONTENT.len(); if path.exists() { let result = std::fs::remove_file(&path); @@ -61,55 +65,55 @@ fn basic_tests(mtt: &mut MegatonTests) { mtt.megaton_assert_ok(result, "Test \"basic_tests\": Failed to write to file\n"); } -fn test_print(mtt: &mut MegatonTests) { - let content = "Hello from test_print!"; - println!("Hello from test_print!"); - let stdout_path = PathBuf::from(STDOUT_PATH); - mtt.megaton_assert_msg( - stdout_path.exists(), - true, - "Test \"test_print\": Failed to write to stdout", - ); - if !stdout_path.exists() { - return; - } - - let read_result = std::fs::read_to_string(stdout_path); - if let Some(stdout_content) = - mtt.megaton_assert_ok(read_result, "Test \"test_print\": Failed to read stdout") - { - mtt.megaton_assert_msg( - stdout_content.contains(content), - true, - "Test \"test_print\": Content not written to stdout!", - ); - } -} - -fn test_stderr(mtt: &mut MegatonTests) { - let content = "This should go to stderr"; - dbg!(content); - let stderr_path = PathBuf::from(STDERR_PATH); - mtt.megaton_assert_msg( - stderr_path.exists(), - true, - "Test \"test_stderr\": Failed to write to stderr", - ); - if !stderr_path.exists() { - return; - } - - let read_result = std::fs::read_to_string(stderr_path); - if let Some(stdout_content) = - mtt.megaton_assert_ok(read_result, "Test \"test_stderr\": Failed to read stderr") - { - mtt.megaton_assert_msg( - stdout_content.contains(content), - true, - "Test \"test_stderr\": Failed to find expected error in stderr", - ); - } -} +// fn test_print(mtt: &mut MegatonTests) { +// let content = "Hello from test_print!"; +// println!("Hello from test_print!"); +// let stdout_path = PathBuf::from(STDOUT_PATH); +// mtt.megaton_assert_msg( +// stdout_path.exists(), +// true, +// "Test \"test_print\": Failed to write to stdout", +// ); +// if !stdout_path.exists() { +// return; +// } +// +// let read_result = std::fs::read_to_string(stdout_path); +// if let Some(stdout_content) = +// mtt.megaton_assert_ok(read_result, "Test \"test_print\": Failed to read stdout") +// { +// mtt.megaton_assert_msg( +// stdout_content.contains(content), +// true, +// "Test \"test_print\": Content not written to stdout!", +// ); +// } +// } + +// fn test_stderr(mtt: &mut MegatonTests) { +// let content = "This should go to stderr"; +// dbg!(content); +// let stderr_path = PathBuf::from(STDERR_PATH); +// mtt.megaton_assert_msg( +// stderr_path.exists(), +// true, +// "Test \"test_stderr\": Failed to write to stderr", +// ); +// if !stderr_path.exists() { +// return; +// } +// +// let read_result = std::fs::read_to_string(stderr_path); +// if let Some(stdout_content) = +// mtt.megaton_assert_ok(read_result, "Test \"test_stderr\": Failed to read stderr") +// { +// mtt.megaton_assert_msg( +// stdout_content.contains(content), +// true, +// "Test \"test_stderr\": Failed to find expected error in stderr", +// ); +// } +// } fn test_consecutive_writes(mtt: &mut MegatonTests) { let path: PathBuf = PathBuf::from("sd:/test_consecutive_writes.txt"); @@ -185,23 +189,24 @@ fn test_write_seek_offset(mtt: &mut MegatonTests) { } } -fn test_exists(mtt: &mut MegatonTests) { - let path: PathBuf = PathBuf::from("sd:/shouldnt_exist.txt"); - let _result = std::fs::remove_file(&path); - mtt.megaton_assert_msg( - path.exists(), - false, - "Test \"test_exists\": Expected file not to exist", - ); - - let path2 = PathBuf::from("sd:/should_exist.txt"); - let create_result = File::create(&path2); - mtt.megaton_assert_msg( - path2.exists(), - true, - "Test \"test_exists\": Expected file to exist", - ); -} +// fn test_exists(mtt: &mut MegatonTests) { +// let path: PathBuf = PathBuf::from("sd:/shouldnt_exist.txt"); +// let _result = std::fs::remove_file(&path); +// mtt.megaton_assert_msg( +// path.exists(), +// false, +// "Test \"test_exists\": Expected file not to exist", +// ); +// +// let path2 = PathBuf::from("sd:/should_exist.txt"); +// #[allow(unused_variables)] // FIXME +// let create_result = File::create(&path2); +// mtt.megaton_assert_msg( +// path2.exists(), +// true, +// "Test \"test_exists\": Expected file to exist", +// ); +// } fn test_close_frees_fd(mtt: &mut MegatonTests) { let path: PathBuf = PathBuf::from("sd:/testfile.txt"); @@ -218,6 +223,7 @@ fn test_close_frees_fd(mtt: &mut MegatonTests) { ); } + #[allow(unused_must_use)] // FIXME std::fs::remove_file(path); } @@ -242,8 +248,11 @@ fn test_multiple_files(mtt: &mut MegatonTests) { mtt.megaton_assert(file2.is_some(), true); mtt.megaton_assert(file3.is_some(), true); + #[allow(unused_must_use)] // FIXME std::fs::remove_file(path); + #[allow(unused_must_use)] // FIXME std::fs::remove_file(path2); + #[allow(unused_must_use)] // FIXME std::fs::remove_file(path3); } @@ -285,57 +294,59 @@ fn test_read_seek_offset(mtt: &mut MegatonTests) { } } + #[allow(unused_must_use)] // FIXME std::fs::remove_file(path); } -fn test_open_flags(mtt: &mut MegatonTests) { - let path: PathBuf = PathBuf::from("sd:/test_open_flags.txt"); - - // O_CREAT should create a new file - let result = File::create(&path); - mtt.megaton_assert_ok( - result, - "Test \"test_open_flags\": O_CREAT failed: could not create file\n", - ); - - // O_TRUNC should truncate on existing file - let result = File::create(&path); - if let Some(mut file) = mtt.megaton_assert_ok( - result, - "Test \"test_open_flags\": Failed to open file for truncate test\n", - ) { - file.write(TOTAL_CONTENT); - drop(file); - let result = File::create(&path); // truncates - if let Some(_) = mtt.megaton_assert_ok( - result, - "Test \"test_open_flags\": O_TRUNC failed: could not truncate file\n", - ) { - let bytes = std::fs::read(&path); - if let Some(bytes) = mtt.megaton_assert_ok( - bytes, - "Test \"test_open_flags\": Failed to read after truncate\n", - ) { - mtt.megaton_assert_msg( - bytes.len(), - 0, - "Test \"test_open_flags\": O_TRUNC did not truncate file to zero", - ); - } - } - } - - // O_RDONLY should not allow writing on file - let result = File::open(&path); - if let Some(mut file) = mtt.megaton_assert_ok( - result, - "Test \"test_open_flags\": O_RDONLY failed: could not open file for reading\n", - ) { - let write_result = file.write(TOTAL_CONTENT); - mtt.megaton_assert_msg( - write_result.is_err(), - true, - "Test \"test_open_flags\": Writing to file opened with O_RDONLY should fail!", - ); - } -} +// fn test_open_flags(mtt: &mut MegatonTests) { +// let path: PathBuf = PathBuf::from("sd:/test_open_flags.txt"); +// +// // O_CREAT should create a new file +// let result = File::create(&path); +// mtt.megaton_assert_ok( +// result, +// "Test \"test_open_flags\": O_CREAT failed: could not create file\n", +// ); +// +// // O_TRUNC should truncate on existing file +// let result = File::create(&path); +// if let Some(mut file) = mtt.megaton_assert_ok( +// result, +// "Test \"test_open_flags\": Failed to open file for truncate test\n", +// ) { +// #[allow(unused_must_use)] // FIXME +// file.write(TOTAL_CONTENT); +// drop(file); +// let result = File::create(&path); // truncates +// if let Some(_) = mtt.megaton_assert_ok( +// result, +// "Test \"test_open_flags\": O_TRUNC failed: could not truncate file\n", +// ) { +// let bytes = std::fs::read(&path); +// if let Some(bytes) = mtt.megaton_assert_ok( +// bytes, +// "Test \"test_open_flags\": Failed to read after truncate\n", +// ) { +// mtt.megaton_assert_msg( +// bytes.len(), +// 0, +// "Test \"test_open_flags\": O_TRUNC did not truncate file to zero", +// ); +// } +// } +// } +// +// // O_RDONLY should not allow writing on file +// let result = File::open(&path); +// if let Some(mut file) = mtt.megaton_assert_ok( +// result, +// "Test \"test_open_flags\": O_RDONLY failed: could not open file for reading\n", +// ) { +// let write_result = file.write(TOTAL_CONTENT); +// mtt.megaton_assert_msg( +// write_result.is_err(), +// true, +// "Test \"test_open_flags\": Writing to file opened with O_RDONLY should fail!", +// ); +// } +// } diff --git a/packages/test-mod/src/lib.rs b/packages/test-mod/src/lib.rs index 68902a1..9deb773 100644 --- a/packages/test-mod/src/lib.rs +++ b/packages/test-mod/src/lib.rs @@ -203,7 +203,7 @@ fn run_megaton_tests() { ); } -#[unsafe(no_mangle)] -extern "C" fn __megaton_rs_main() { +#[megaton::main] +fn main() { ffi::init_function_in_c(); } diff --git a/packages/test-mod/src/main.cpp b/packages/test-mod/src/main.cpp index f5fd468..1cdc7cd 100644 --- a/packages/test-mod/src/main.cpp +++ b/packages/test-mod/src/main.cpp @@ -2,6 +2,7 @@ // Copyright (c) 2026 Megaton contributors #include +#include #include #include @@ -11,12 +12,12 @@ namespace nn::fs { void MountSdCard(const char* path); } -extern "C" void __megaton_rs_main(); - extern "C" void megaton_main() { - __megaton_rs_main(); + nn::fs::MountSdCard("sd"); + megaton::rust_main(); } +// FIXME: mutable static static FILE* f; void write_test_output(rust::Str data) { @@ -24,7 +25,6 @@ void write_test_output(rust::Str data) { } void init_function_in_c() { - nn::fs::MountSdCard("sd"); f = fopen("sd:/test_output.txt", "w"); example_rs::run_megaton_tests(); fclose(f);