diff --git a/.gitignore b/.gitignore index 4c5d9d8061..84a9961e05 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,6 @@ CONTEXT.md .continue/ .superpowers/ + +# nonos C sysroot (build artifact of toolchain/nonos-c/build-sysroot.sh) +toolchain/nonos-c/sysroot/ diff --git a/Cargo.toml b/Cargo.toml index 4701999832..d95649cae2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,10 @@ sched = [] # below maps 1:1 to a capsule directory under `userland/`. nonos-capsule-proof-io = [] nonos-capsule-std-proof = [] +nonos-capsule-c-proof = [] +nonos-capsule-relibc-test = [] +nonos-capsule-c-net = [] +nonos-capsule-nsfb-probe = [] nonos-capsule-ripgrep = [] nonos-capsule-ramfs = [] nonos-capsule-wallpaper = [] @@ -212,6 +216,77 @@ microkernel-ramfs = [ "nonos-capsule-ramfs", ] +microkernel-c-proof = [ + "microkernel-core", + "nonos-production", + "nonos-capsule-proof-io", + "nonos-capsule-c-proof", +] + +# Temporary unverified boot proof of the clang/relibc C capsule: spawns +# c_proof via the legacy unverified path (no ZK attestation) while the +# capsule-attestation NZKCAPS2 migration is owned elsewhere. Superseded by +# microkernel-c-proof once a valid c_proof trailer is available. +microkernel-c-proof-smoketest = [ + "microkernel-core", + "nonos-dev-unverified-capsules", + "nonos-capsule-c-proof", +] + +# Phase-4 framebuffer probe (verified path). Brings up the compositor +# and the virtio-gpu driver so a real framebuffer exists, then spawns +# nsfb_probe which registers a surface, reads OP_DISPLAY_INFO, paints a +# gradient, and presents it. +microkernel-nsfb-probe = [ + "microkernel-core", + "nonos-production", + "nonos-capsule-proof-io", + "nonos-capsule-compositor", + "nonos-capsule-driver-virtio-gpu", + "nonos-capsule-nsfb-probe", +] + +# Unverified smoke build of the framebuffer probe: spawns nsfb_probe via +# the legacy path while the NZKCAPS2 migration is owned elsewhere. The +# compositor + virtio-gpu carry real ZK trailers that the always-on +# transparent verifier would reject, so `nonos-zk-rollout` keeps a stale +# trailer non-fatal — the same reason the cnet/relibc smoketests set it. +microkernel-nsfb-probe-smoketest = [ + "microkernel-core", + "nonos-dev-unverified-capsules", + "nonos-zk-rollout", + "nonos-capsule-compositor", + "nonos-capsule-driver-virtio-gpu", + "nonos-capsule-nsfb-probe", +] + +microkernel-relibc-test-smoketest = [ + "microkernel-core", + "nonos-dev-unverified-capsules", + "nonos-zk-rollout", + "nonos-capsule-proof-io", + "nonos-capsule-ramfs", + "nonos-capsule-vfs", + "nonos-capsule-relibc-test", +] + +microkernel-cnet-smoketest = [ + "microkernel-core", + "nonos-dev-unverified-capsules", + "nonos-zk-rollout", + "nonos-capsule-proof-io", + "nonos-capsule-driver-virtio-net", + "nonos-capsule-net-l2", + "nonos-capsule-net-ip", + "nonos-capsule-net-udp", + "nonos-capsule-net-dhcp", + "nonos-capsule-net-tcp", + "nonos-capsule-net-dns", + "nonos-capsule-net-sockets", + "nonos-capsule-net-nym", + "nonos-capsule-c-net", +] + microkernel-keyring = [ "microkernel-core", "nonos-production", diff --git a/Makefile b/Makefile index 4f0b5e74f8..87872be68d 100644 --- a/Makefile +++ b/Makefile @@ -534,6 +534,10 @@ nonos-mk-trust-policy: $(NONOS_TRUST_ANCHOR_POLICY_BIN) # nonos-mk-check--keys assert publisher seeds + pubs exist include userland/capsule_proof_io/Capsule.mk include userland/capsule_std_proof/Capsule.mk +include userland/capsule_c_proof/Capsule.mk +include userland/capsule_relibc_test/Capsule.mk +include userland/capsule_c_net/Capsule.mk +include userland/capsule_nsfb_probe/Capsule.mk include userland/capsule_ripgrep/Capsule.mk include userland/capsule_ramfs/Capsule.mk include userland/capsule_keyring/Capsule.mk @@ -809,6 +813,72 @@ nonos-mk-std-proof-prod: $(proof-io_ARTIFACTS) $(std-proof_ARTIFACTS) nonos-mk-c $(CARGO) build $(KERNEL_BUILD_FLAGS) \ --no-default-features --features microkernel-proof-io,nonos-capsule-std-proof +.PHONY: nonos-mk-cproof-prod nonos-mk-boot-cproof nonos-mk-cproof-test +nonos-mk-cproof-prod: $(proof-io_ARTIFACTS) $(c-proof_ARTIFACTS) \ + nonos-mk-check-deps nonos-mk-ensure-signing-key + @echo "Building kernel (microkernel-c-proof)..." + @$(SDK_FLAGS) NONOS_SIGNING_KEY=$(KERNEL_SIGNING_KEY) \ + RUSTUP_TOOLCHAIN=$(TOOLCHAIN) \ + $(CARGO) build $(KERNEL_BUILD_FLAGS) \ + --no-default-features --features microkernel-c-proof + +nonos-mk-boot-cproof: + @./tests/boot/cproof.sh + +nonos-mk-cproof-test: nonos-mk-boot-cproof + +.PHONY: nonos-mk-cproof-smoke-prod nonos-mk-cproof-smoke-test +nonos-mk-cproof-smoke-prod: $(c-proof_ARTIFACTS) \ + nonos-mk-check-deps nonos-mk-ensure-signing-key + @echo "Building kernel (microkernel-c-proof-smoketest, unverified)..." + @$(SDK_FLAGS) NONOS_SIGNING_KEY=$(KERNEL_SIGNING_KEY) \ + RUSTUP_TOOLCHAIN=$(TOOLCHAIN) \ + $(CARGO) build $(KERNEL_BUILD_FLAGS) \ + --no-default-features --features microkernel-c-proof-smoketest + +nonos-mk-cproof-smoke-test: + @CPROOF_PROD_TARGET=nonos-mk-cproof-smoke-prod ./tests/boot/cproof.sh + +.PHONY: nonos-mk-relibc-smoke-prod nonos-mk-relibc-smoke-test +nonos-mk-relibc-smoke-prod: $(relibc-test_ARTIFACTS) \ + nonos-mk-check-deps nonos-mk-ensure-signing-key + @echo "Building kernel (microkernel-relibc-test-smoketest, unverified)..." + @$(SDK_FLAGS) NONOS_SIGNING_KEY=$(KERNEL_SIGNING_KEY) \ + RUSTUP_TOOLCHAIN=$(TOOLCHAIN) \ + $(CARGO) build $(KERNEL_BUILD_FLAGS) \ + --no-default-features --features microkernel-relibc-test-smoketest + +nonos-mk-relibc-smoke-test: + @CPROOF_PROD_TARGET=nonos-mk-relibc-smoke-prod ./tests/boot/relibc.sh + +.PHONY: nonos-mk-cnet-smoke-prod nonos-mk-cnet-smoke-test +nonos-mk-cnet-smoke-prod: $(proof-io_ARTIFACTS) $(driver-virtio-net_ARTIFACTS) \ + $(net-l2_ARTIFACTS) $(net-ip_ARTIFACTS) $(net-udp_ARTIFACTS) \ + $(net-dhcp_ARTIFACTS) $(net-tcp_ARTIFACTS) $(net-dns_ARTIFACTS) \ + $(net-sockets_ARTIFACTS) $(net-nym_ARTIFACTS) $(c-net_ARTIFACTS) \ + nonos-mk-check-deps nonos-mk-ensure-signing-key + @echo "Building kernel (microkernel-cnet-smoketest, unverified)..." + @$(SDK_FLAGS) NONOS_SIGNING_KEY=$(KERNEL_SIGNING_KEY) \ + RUSTUP_TOOLCHAIN=$(TOOLCHAIN) \ + $(CARGO) build $(KERNEL_BUILD_FLAGS) \ + --no-default-features --features microkernel-cnet-smoketest + +nonos-mk-cnet-smoke-test: + @CNET_PROD_TARGET=nonos-mk-cnet-smoke-prod ./tests/boot/cnet.sh + +.PHONY: nonos-mk-nsfbprobe-smoke-prod nonos-mk-nsfbprobe-smoke-test +nonos-mk-nsfbprobe-smoke-prod: $(compositor_ARTIFACTS) \ + $(driver-virtio-gpu_ARTIFACTS) $(nsfb-probe_ARTIFACTS) \ + nonos-mk-check-deps nonos-mk-ensure-signing-key + @echo "Building kernel (microkernel-nsfb-probe-smoketest, unverified)..." + @$(SDK_FLAGS) NONOS_SIGNING_KEY=$(KERNEL_SIGNING_KEY) \ + RUSTUP_TOOLCHAIN=$(TOOLCHAIN) \ + $(CARGO) build $(KERNEL_BUILD_FLAGS) \ + --no-default-features --features microkernel-nsfb-probe-smoketest + +nonos-mk-nsfbprobe-smoke-test: + @NSFBPROBE_PROD_TARGET=nonos-mk-nsfbprobe-smoke-prod ./tests/boot/nsfbprobe.sh + nonos-mk-ramfs-prod: $(proof-io_ARTIFACTS) $(ramfs_ARTIFACTS) \ nonos-mk-check-deps nonos-mk-ensure-signing-key @echo "Building kernel (microkernel-ramfs)..." diff --git a/nonos-mk b/nonos-mk index 164ce49161..15205476cc 160000 --- a/nonos-mk +++ b/nonos-mk @@ -1 +1 @@ -Subproject commit 164ce491614688bbdaf436acdc15fd4ddf51cdc9 +Subproject commit 15205476cc34a5ec0f893b51219c27be0c497377 diff --git a/src/userspace/capsule_c_net/embed.rs b/src/userspace/capsule_c_net/embed.rs new file mode 100644 index 0000000000..2c753301b1 --- /dev/null +++ b/src/userspace/capsule_c_net/embed.rs @@ -0,0 +1,44 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#[cfg(feature = "nonos-capsule-c-net")] +pub(crate) const C_NET_ELF: &[u8] = include_bytes!( + "../../../userland/capsule_c_net/target/x86_64-nonos-user/release/c_net" +); + +#[cfg(feature = "nonos-capsule-c-net")] +pub(crate) const C_NET_NONOS_ID_CERT_BYTES: &[u8] = + include_bytes!("../../../nonos-data/trust/capsules/c_net.nonos_id_cert.bin"); + +#[cfg(feature = "nonos-capsule-c-net")] +pub(crate) const C_NET_MANIFEST_BYTES: &[u8] = + include_bytes!("../../../nonos-data/trust/capsules/c_net.manifest.bin"); + +#[cfg(feature = "nonos-capsule-c-net")] +pub(crate) const C_NET_ATTESTATION_BYTES: &[u8] = + include_bytes!("../../../nonos-data/trust/capsules/c_net.zk_trailer.bin"); + +#[cfg(not(feature = "nonos-capsule-c-net"))] +pub(crate) const C_NET_ELF: &[u8] = &[]; + +#[cfg(not(feature = "nonos-capsule-c-net"))] +pub(crate) const C_NET_NONOS_ID_CERT_BYTES: &[u8] = &[]; + +#[cfg(not(feature = "nonos-capsule-c-net"))] +pub(crate) const C_NET_MANIFEST_BYTES: &[u8] = &[]; + +#[cfg(not(feature = "nonos-capsule-c-net"))] +pub(crate) const C_NET_ATTESTATION_BYTES: &[u8] = &[]; diff --git a/src/userspace/capsule_c_net/mod.rs b/src/userspace/capsule_c_net/mod.rs new file mode 100644 index 0000000000..a0b0dec9a1 --- /dev/null +++ b/src/userspace/capsule_c_net/mod.rs @@ -0,0 +1,20 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +mod embed; +mod spawn; + +pub use spawn::spawn_c_net_capsule; diff --git a/src/userspace/capsule_c_net/spawn.rs b/src/userspace/capsule_c_net/spawn.rs new file mode 100644 index 0000000000..1f6b73832e --- /dev/null +++ b/src/userspace/capsule_c_net/spawn.rs @@ -0,0 +1,84 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use super::embed::C_NET_ELF; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +use super::embed::{ + C_NET_ATTESTATION_BYTES, C_NET_MANIFEST_BYTES, C_NET_NONOS_ID_CERT_BYTES, +}; +use crate::capabilities::Capability; +use crate::kernel_core::process_spawn::capsule_spawn::{self, SpawnError}; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +use crate::kernel_core::process_spawn::capsule_spawn::CapsuleSpecVerified; +#[cfg(feature = "nonos-dev-unverified-capsules")] +use crate::kernel_core::process_spawn::capsule_spawn::CapsuleSpec; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +use crate::security::nonos_id_cert::IdCertVerifyError; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +use crate::security::nonos_trust_anchor::{ + decode as decode_trust_anchor, BAKED_TRUST_ANCHOR_POLICY, +}; + +const SERVICE_NAME: &str = "c_net"; +const SERVICE_PORT: u32 = 4508; +const REPLY_INBOX: &str = "endpoint.c_net.reply"; +const REPLY_PORT: u32 = 4509; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +const TARGET_TRIPLE: &str = "x86_64-nonos-user"; + +fn requested_caps() -> u64 { + Capability::CoreExec.bit() + | Capability::IPC.bit() + | Capability::Memory.bit() + | Capability::Debug.bit() +} + +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +pub fn spawn_c_net_capsule() -> Result<(), SpawnError> { + let trust_anchor = decode_trust_anchor(BAKED_TRUST_ANCHOR_POLICY) + .map_err(|_| SpawnError::NonosIdCertRejected(IdCertVerifyError::TrustAnchorPolicy))?; + + let spec = CapsuleSpecVerified { + name: SERVICE_NAME, + service_port: SERVICE_PORT, + reply_inbox: REPLY_INBOX, + reply_port: REPLY_PORT, + elf: C_NET_ELF, + nonos_id_cert_bytes: C_NET_NONOS_ID_CERT_BYTES, + manifest_bytes: C_NET_MANIFEST_BYTES, + attestation_trailer: C_NET_ATTESTATION_BYTES, + target_triple: TARGET_TRIPLE, + requested_caps: requested_caps(), + debug_tag: b"", + }; + capsule_spawn::spawn_verified(&spec, &trust_anchor, None)?; + Ok(()) +} + +#[cfg(feature = "nonos-dev-unverified-capsules")] +pub fn spawn_c_net_capsule() -> Result<(), SpawnError> { + let spec = CapsuleSpec { + name: SERVICE_NAME, + service_port: SERVICE_PORT, + reply_inbox: REPLY_INBOX, + reply_port: REPLY_PORT, + elf: C_NET_ELF, + caps_bits: requested_caps(), + debug_tag: b"", + }; + capsule_spawn::spawn(&spec)?; + Ok(()) +} diff --git a/src/userspace/capsule_c_proof/embed.rs b/src/userspace/capsule_c_proof/embed.rs new file mode 100644 index 0000000000..edc4f58189 --- /dev/null +++ b/src/userspace/capsule_c_proof/embed.rs @@ -0,0 +1,44 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#[cfg(feature = "nonos-capsule-c-proof")] +pub(crate) const C_PROOF_ELF: &[u8] = include_bytes!( + "../../../userland/capsule_c_proof/target/x86_64-nonos-user/release/c_proof" +); + +#[cfg(feature = "nonos-capsule-c-proof")] +pub(crate) const C_PROOF_NONOS_ID_CERT_BYTES: &[u8] = + include_bytes!("../../../nonos-data/trust/capsules/c_proof.nonos_id_cert.bin"); + +#[cfg(feature = "nonos-capsule-c-proof")] +pub(crate) const C_PROOF_MANIFEST_BYTES: &[u8] = + include_bytes!("../../../nonos-data/trust/capsules/c_proof.manifest.bin"); + +#[cfg(feature = "nonos-capsule-c-proof")] +pub(crate) const C_PROOF_ATTESTATION_BYTES: &[u8] = + include_bytes!("../../../nonos-data/trust/capsules/c_proof.zk_trailer.bin"); + +#[cfg(not(feature = "nonos-capsule-c-proof"))] +pub(crate) const C_PROOF_ELF: &[u8] = &[]; + +#[cfg(not(feature = "nonos-capsule-c-proof"))] +pub(crate) const C_PROOF_NONOS_ID_CERT_BYTES: &[u8] = &[]; + +#[cfg(not(feature = "nonos-capsule-c-proof"))] +pub(crate) const C_PROOF_MANIFEST_BYTES: &[u8] = &[]; + +#[cfg(not(feature = "nonos-capsule-c-proof"))] +pub(crate) const C_PROOF_ATTESTATION_BYTES: &[u8] = &[]; diff --git a/src/userspace/capsule_c_proof/mod.rs b/src/userspace/capsule_c_proof/mod.rs new file mode 100644 index 0000000000..9f79b072b1 --- /dev/null +++ b/src/userspace/capsule_c_proof/mod.rs @@ -0,0 +1,20 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +mod embed; +mod spawn; + +pub use spawn::spawn_c_proof_capsule; diff --git a/src/userspace/capsule_c_proof/spawn.rs b/src/userspace/capsule_c_proof/spawn.rs new file mode 100644 index 0000000000..9ce9c38db7 --- /dev/null +++ b/src/userspace/capsule_c_proof/spawn.rs @@ -0,0 +1,84 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use super::embed::C_PROOF_ELF; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +use super::embed::{ + C_PROOF_ATTESTATION_BYTES, C_PROOF_MANIFEST_BYTES, C_PROOF_NONOS_ID_CERT_BYTES, +}; +use crate::capabilities::Capability; +use crate::kernel_core::process_spawn::capsule_spawn::{self, SpawnError}; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +use crate::kernel_core::process_spawn::capsule_spawn::CapsuleSpecVerified; +#[cfg(feature = "nonos-dev-unverified-capsules")] +use crate::kernel_core::process_spawn::capsule_spawn::CapsuleSpec; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +use crate::security::nonos_id_cert::IdCertVerifyError; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +use crate::security::nonos_trust_anchor::{ + decode as decode_trust_anchor, BAKED_TRUST_ANCHOR_POLICY, +}; + +const SERVICE_NAME: &str = "c_proof"; +const SERVICE_PORT: u32 = 4504; +const REPLY_INBOX: &str = "endpoint.c_proof.reply"; +const REPLY_PORT: u32 = 4505; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +const TARGET_TRIPLE: &str = "x86_64-nonos-user"; + +fn requested_caps() -> u64 { + Capability::CoreExec.bit() + | Capability::IPC.bit() + | Capability::Memory.bit() + | Capability::Debug.bit() +} + +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +pub fn spawn_c_proof_capsule() -> Result<(), SpawnError> { + let trust_anchor = decode_trust_anchor(BAKED_TRUST_ANCHOR_POLICY) + .map_err(|_| SpawnError::NonosIdCertRejected(IdCertVerifyError::TrustAnchorPolicy))?; + + let spec = CapsuleSpecVerified { + name: SERVICE_NAME, + service_port: SERVICE_PORT, + reply_inbox: REPLY_INBOX, + reply_port: REPLY_PORT, + elf: C_PROOF_ELF, + nonos_id_cert_bytes: C_PROOF_NONOS_ID_CERT_BYTES, + manifest_bytes: C_PROOF_MANIFEST_BYTES, + attestation_trailer: C_PROOF_ATTESTATION_BYTES, + target_triple: TARGET_TRIPLE, + requested_caps: requested_caps(), + debug_tag: b"", + }; + capsule_spawn::spawn_verified(&spec, &trust_anchor, None)?; + Ok(()) +} + +#[cfg(feature = "nonos-dev-unverified-capsules")] +pub fn spawn_c_proof_capsule() -> Result<(), SpawnError> { + let spec = CapsuleSpec { + name: SERVICE_NAME, + service_port: SERVICE_PORT, + reply_inbox: REPLY_INBOX, + reply_port: REPLY_PORT, + elf: C_PROOF_ELF, + caps_bits: requested_caps(), + debug_tag: b"", + }; + capsule_spawn::spawn(&spec)?; + Ok(()) +} diff --git a/src/userspace/capsule_nsfb_probe/embed.rs b/src/userspace/capsule_nsfb_probe/embed.rs new file mode 100644 index 0000000000..a87ae963eb --- /dev/null +++ b/src/userspace/capsule_nsfb_probe/embed.rs @@ -0,0 +1,44 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#[cfg(feature = "nonos-capsule-nsfb-probe")] +pub(crate) const NSFB_PROBE_ELF: &[u8] = include_bytes!( + "../../../userland/capsule_nsfb_probe/target/x86_64-nonos-user/release/nsfb_probe" +); + +#[cfg(feature = "nonos-capsule-nsfb-probe")] +pub(crate) const NSFB_PROBE_NONOS_ID_CERT_BYTES: &[u8] = + include_bytes!("../../../nonos-data/trust/capsules/nsfb_probe.nonos_id_cert.bin"); + +#[cfg(feature = "nonos-capsule-nsfb-probe")] +pub(crate) const NSFB_PROBE_MANIFEST_BYTES: &[u8] = + include_bytes!("../../../nonos-data/trust/capsules/nsfb_probe.manifest.bin"); + +#[cfg(feature = "nonos-capsule-nsfb-probe")] +pub(crate) const NSFB_PROBE_ATTESTATION_BYTES: &[u8] = + include_bytes!("../../../nonos-data/trust/capsules/nsfb_probe.zk_trailer.bin"); + +#[cfg(not(feature = "nonos-capsule-nsfb-probe"))] +pub(crate) const NSFB_PROBE_ELF: &[u8] = &[]; + +#[cfg(not(feature = "nonos-capsule-nsfb-probe"))] +pub(crate) const NSFB_PROBE_NONOS_ID_CERT_BYTES: &[u8] = &[]; + +#[cfg(not(feature = "nonos-capsule-nsfb-probe"))] +pub(crate) const NSFB_PROBE_MANIFEST_BYTES: &[u8] = &[]; + +#[cfg(not(feature = "nonos-capsule-nsfb-probe"))] +pub(crate) const NSFB_PROBE_ATTESTATION_BYTES: &[u8] = &[]; diff --git a/src/userspace/capsule_nsfb_probe/mod.rs b/src/userspace/capsule_nsfb_probe/mod.rs new file mode 100644 index 0000000000..b5d7d822b2 --- /dev/null +++ b/src/userspace/capsule_nsfb_probe/mod.rs @@ -0,0 +1,20 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +mod embed; +mod spawn; + +pub use spawn::spawn_nsfb_probe_capsule; diff --git a/src/userspace/capsule_nsfb_probe/spawn.rs b/src/userspace/capsule_nsfb_probe/spawn.rs new file mode 100644 index 0000000000..b0ac64b637 --- /dev/null +++ b/src/userspace/capsule_nsfb_probe/spawn.rs @@ -0,0 +1,85 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use super::embed::NSFB_PROBE_ELF; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +use super::embed::{ + NSFB_PROBE_ATTESTATION_BYTES, NSFB_PROBE_MANIFEST_BYTES, NSFB_PROBE_NONOS_ID_CERT_BYTES, +}; +use crate::capabilities::Capability; +use crate::kernel_core::process_spawn::capsule_spawn::{self, SpawnError}; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +use crate::kernel_core::process_spawn::capsule_spawn::CapsuleSpecVerified; +#[cfg(feature = "nonos-dev-unverified-capsules")] +use crate::kernel_core::process_spawn::capsule_spawn::CapsuleSpec; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +use crate::security::nonos_id_cert::IdCertVerifyError; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +use crate::security::nonos_trust_anchor::{ + decode as decode_trust_anchor, BAKED_TRUST_ANCHOR_POLICY, +}; + +const SERVICE_NAME: &str = "nsfb_probe"; +const SERVICE_PORT: u32 = 4512; +const REPLY_INBOX: &str = "endpoint.nsfb_probe.reply"; +const REPLY_PORT: u32 = 4513; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +const TARGET_TRIPLE: &str = "x86_64-nonos-user"; + +fn requested_caps() -> u64 { + Capability::CoreExec.bit() + | Capability::IPC.bit() + | Capability::Memory.bit() + | Capability::Debug.bit() + | Capability::GraphicsSurfaceCreate.bit() +} + +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +pub fn spawn_nsfb_probe_capsule() -> Result<(), SpawnError> { + let trust_anchor = decode_trust_anchor(BAKED_TRUST_ANCHOR_POLICY) + .map_err(|_| SpawnError::NonosIdCertRejected(IdCertVerifyError::TrustAnchorPolicy))?; + + let spec = CapsuleSpecVerified { + name: SERVICE_NAME, + service_port: SERVICE_PORT, + reply_inbox: REPLY_INBOX, + reply_port: REPLY_PORT, + elf: NSFB_PROBE_ELF, + nonos_id_cert_bytes: NSFB_PROBE_NONOS_ID_CERT_BYTES, + manifest_bytes: NSFB_PROBE_MANIFEST_BYTES, + attestation_trailer: NSFB_PROBE_ATTESTATION_BYTES, + target_triple: TARGET_TRIPLE, + requested_caps: requested_caps(), + debug_tag: b"", + }; + capsule_spawn::spawn_verified(&spec, &trust_anchor, None)?; + Ok(()) +} + +#[cfg(feature = "nonos-dev-unverified-capsules")] +pub fn spawn_nsfb_probe_capsule() -> Result<(), SpawnError> { + let spec = CapsuleSpec { + name: SERVICE_NAME, + service_port: SERVICE_PORT, + reply_inbox: REPLY_INBOX, + reply_port: REPLY_PORT, + elf: NSFB_PROBE_ELF, + caps_bits: requested_caps(), + debug_tag: b"", + }; + capsule_spawn::spawn(&spec)?; + Ok(()) +} diff --git a/src/userspace/capsule_relibc_test/embed.rs b/src/userspace/capsule_relibc_test/embed.rs new file mode 100644 index 0000000000..31f0f444e7 --- /dev/null +++ b/src/userspace/capsule_relibc_test/embed.rs @@ -0,0 +1,44 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#[cfg(feature = "nonos-capsule-relibc-test")] +pub(crate) const RELIBC_TEST_ELF: &[u8] = include_bytes!( + "../../../userland/capsule_relibc_test/target/x86_64-nonos-user/release/relibc_test" +); + +#[cfg(feature = "nonos-capsule-relibc-test")] +pub(crate) const RELIBC_TEST_NONOS_ID_CERT_BYTES: &[u8] = + include_bytes!("../../../nonos-data/trust/capsules/relibc_test.nonos_id_cert.bin"); + +#[cfg(feature = "nonos-capsule-relibc-test")] +pub(crate) const RELIBC_TEST_MANIFEST_BYTES: &[u8] = + include_bytes!("../../../nonos-data/trust/capsules/relibc_test.manifest.bin"); + +#[cfg(feature = "nonos-capsule-relibc-test")] +pub(crate) const RELIBC_TEST_ATTESTATION_BYTES: &[u8] = + include_bytes!("../../../nonos-data/trust/capsules/relibc_test.zk_trailer.bin"); + +#[cfg(not(feature = "nonos-capsule-relibc-test"))] +pub(crate) const RELIBC_TEST_ELF: &[u8] = &[]; + +#[cfg(not(feature = "nonos-capsule-relibc-test"))] +pub(crate) const RELIBC_TEST_NONOS_ID_CERT_BYTES: &[u8] = &[]; + +#[cfg(not(feature = "nonos-capsule-relibc-test"))] +pub(crate) const RELIBC_TEST_MANIFEST_BYTES: &[u8] = &[]; + +#[cfg(not(feature = "nonos-capsule-relibc-test"))] +pub(crate) const RELIBC_TEST_ATTESTATION_BYTES: &[u8] = &[]; diff --git a/src/userspace/capsule_relibc_test/mod.rs b/src/userspace/capsule_relibc_test/mod.rs new file mode 100644 index 0000000000..2be0eb79f1 --- /dev/null +++ b/src/userspace/capsule_relibc_test/mod.rs @@ -0,0 +1,20 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +mod embed; +mod spawn; + +pub use spawn::spawn_relibc_test_capsule; diff --git a/src/userspace/capsule_relibc_test/spawn.rs b/src/userspace/capsule_relibc_test/spawn.rs new file mode 100644 index 0000000000..4c7d322b7a --- /dev/null +++ b/src/userspace/capsule_relibc_test/spawn.rs @@ -0,0 +1,86 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use super::embed::RELIBC_TEST_ELF; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +use super::embed::{ + RELIBC_TEST_ATTESTATION_BYTES, RELIBC_TEST_MANIFEST_BYTES, RELIBC_TEST_NONOS_ID_CERT_BYTES, +}; +use crate::capabilities::Capability; +use crate::kernel_core::process_spawn::capsule_spawn::{self, SpawnError}; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +use crate::kernel_core::process_spawn::capsule_spawn::CapsuleSpecVerified; +#[cfg(feature = "nonos-dev-unverified-capsules")] +use crate::kernel_core::process_spawn::capsule_spawn::CapsuleSpec; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +use crate::security::nonos_id_cert::IdCertVerifyError; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +use crate::security::nonos_trust_anchor::{ + decode as decode_trust_anchor, BAKED_TRUST_ANCHOR_POLICY, +}; + +const SERVICE_NAME: &str = "relibc_test"; +const SERVICE_PORT: u32 = 4506; +const REPLY_INBOX: &str = "endpoint.relibc_test.reply"; +const REPLY_PORT: u32 = 4507; +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +const TARGET_TRIPLE: &str = "x86_64-nonos-user"; + +fn requested_caps() -> u64 { + Capability::CoreExec.bit() + | Capability::IPC.bit() + | Capability::Memory.bit() + | Capability::Crypto.bit() + | Capability::FileSystem.bit() + | Capability::Debug.bit() +} + +#[cfg(not(feature = "nonos-dev-unverified-capsules"))] +pub fn spawn_relibc_test_capsule() -> Result<(), SpawnError> { + let trust_anchor = decode_trust_anchor(BAKED_TRUST_ANCHOR_POLICY) + .map_err(|_| SpawnError::NonosIdCertRejected(IdCertVerifyError::TrustAnchorPolicy))?; + + let spec = CapsuleSpecVerified { + name: SERVICE_NAME, + service_port: SERVICE_PORT, + reply_inbox: REPLY_INBOX, + reply_port: REPLY_PORT, + elf: RELIBC_TEST_ELF, + nonos_id_cert_bytes: RELIBC_TEST_NONOS_ID_CERT_BYTES, + manifest_bytes: RELIBC_TEST_MANIFEST_BYTES, + attestation_trailer: RELIBC_TEST_ATTESTATION_BYTES, + target_triple: TARGET_TRIPLE, + requested_caps: requested_caps(), + debug_tag: b"", + }; + capsule_spawn::spawn_verified(&spec, &trust_anchor, None)?; + Ok(()) +} + +#[cfg(feature = "nonos-dev-unverified-capsules")] +pub fn spawn_relibc_test_capsule() -> Result<(), SpawnError> { + let spec = CapsuleSpec { + name: SERVICE_NAME, + service_port: SERVICE_PORT, + reply_inbox: REPLY_INBOX, + reply_port: REPLY_PORT, + elf: RELIBC_TEST_ELF, + caps_bits: requested_caps(), + debug_tag: b"", + }; + capsule_spawn::spawn(&spec)?; + Ok(()) +} diff --git a/src/userspace/init/entry.rs b/src/userspace/init/entry.rs index 150debe0ab..6841b8ed6f 100644 --- a/src/userspace/init/entry.rs +++ b/src/userspace/init/entry.rs @@ -21,13 +21,17 @@ pub fn run_init() -> ! { boot_log::ok("INIT", "Starting"); run_user_entry_proof(); run_std_proof(); + run_c_proof(); + run_relibc_test(); run_ripgrep(); spawn_plan::spawn_ramfs(); spawn_plan::spawn_core_after_ramfs(); spawn_plan::spawn_display_core(); spawn_plan::spawn_drivers(); + run_nsfb_probe(); spawn_plan::spawn_vfs(); spawn_plan::spawn_network(); + run_c_net(); spawn_plan::spawn_desktop(); spawn_plan::spawn_market(); spawn_plan::spawn_apps(); @@ -57,6 +61,50 @@ fn run_std_proof() { #[cfg(not(feature = "nonos-capsule-std-proof"))] fn run_std_proof() {} +#[cfg(feature = "nonos-capsule-c-proof")] +fn run_c_proof() { + match crate::userspace::capsule_c_proof::spawn_c_proof_capsule() { + Ok(()) => boot_log::ok("C-PROOF", "capsule spawned"), + Err(_) => boot_log::error("C-PROOF capsule spawn failed"), + } +} + +#[cfg(not(feature = "nonos-capsule-c-proof"))] +fn run_c_proof() {} + +#[cfg(feature = "nonos-capsule-nsfb-probe")] +fn run_nsfb_probe() { + match crate::userspace::capsule_nsfb_probe::spawn_nsfb_probe_capsule() { + Ok(()) => boot_log::ok("NSFB-PROBE", "capsule spawned"), + Err(_) => boot_log::error("NSFB-PROBE capsule spawn failed"), + } +} + +#[cfg(not(feature = "nonos-capsule-nsfb-probe"))] +fn run_nsfb_probe() {} + +#[cfg(feature = "nonos-capsule-relibc-test")] +fn run_relibc_test() { + match crate::userspace::capsule_relibc_test::spawn_relibc_test_capsule() { + Ok(()) => boot_log::ok("RELIBC-TEST", "capsule spawned"), + Err(_) => boot_log::error("RELIBC-TEST capsule spawn failed"), + } +} + +#[cfg(not(feature = "nonos-capsule-relibc-test"))] +fn run_relibc_test() {} + +#[cfg(feature = "nonos-capsule-c-net")] +fn run_c_net() { + match crate::userspace::capsule_c_net::spawn_c_net_capsule() { + Ok(()) => boot_log::ok("C-NET", "capsule spawned"), + Err(_) => boot_log::error("C-NET capsule spawn failed"), + } +} + +#[cfg(not(feature = "nonos-capsule-c-net"))] +fn run_c_net() {} + #[cfg(feature = "nonos-capsule-ripgrep")] fn run_ripgrep() { match crate::userspace::capsule_ripgrep::spawn_ripgrep_capsule() { diff --git a/src/userspace/mod.rs b/src/userspace/mod.rs index 5b9262c021..423fa069fb 100644 --- a/src/userspace/mod.rs +++ b/src/userspace/mod.rs @@ -61,6 +61,10 @@ pub mod capsule_settings; #[cfg(feature = "nonos-capsule-setup-wizard")] pub mod capsule_setup_wizard; pub mod capsule_snake; +pub mod capsule_c_proof; +pub mod capsule_nsfb_probe; +pub mod capsule_c_net; +pub mod capsule_relibc_test; pub mod capsule_std_proof; pub mod capsule_terminal; pub mod capsule_text_editor; diff --git a/toolchain/nonos-c/build-sysroot.sh b/toolchain/nonos-c/build-sysroot.sh new file mode 100755 index 0000000000..c5b8175fba --- /dev/null +++ b/toolchain/nonos-c/build-sysroot.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Assemble the NONOS C sysroot from the vendored relibc build output: +# sysroot/lib/{libc.a,crt0.o,crti.o,crtn.o} + sysroot/include (cbindgen + +# hand-written headers). Static-link only — libc.so/ld.so are deliberately +# not built (the nonos capsule link is fully static), so this builds the +# specific targets rather than `all`. Re-run after a relibc change. Idempotent. +set -euo pipefail + +HERE="$(cd "$(dirname "$0")" && pwd)" +REPO="$(cd "$HERE/../.." && pwd)" +RELIBC="$REPO/third_party/redox/src-repos/relibc" +OUT="$HERE/sysroot" +B="$RELIBC/target/x86_64-unknown-nonos/release" + +export PATH="/usr/local/opt/llvm/bin:/usr/local/opt/lld/bin:/usr/local/opt/findutils/libexec/gnubin:$PATH" + +"$REPO/toolchain/nonos-relibc/apply.sh" + +make -C "$RELIBC" TARGET=x86_64-unknown-nonos NONOS_C_DIR="$HERE" USE_RUST_LIBM=1 \ + headers "$B/libc.a" "$B/crt0.o" "$B/crti.o" "$B/crtn.o" + +rm -rf "$OUT" +mkdir -p "$OUT/lib" "$OUT/include" +cp "$B/libc.a" "$B/crt0.o" "$B/crti.o" "$B/crtn.o" "$OUT/lib/" +cp -r "$RELIBC/target/x86_64-unknown-nonos/include/." "$OUT/include/" +echo "[sysroot] assembled at $OUT" diff --git a/toolchain/nonos-c/cc-nonos b/toolchain/nonos-c/cc-nonos new file mode 100755 index 0000000000..05be42ad82 --- /dev/null +++ b/toolchain/nonos-c/cc-nonos @@ -0,0 +1,2 @@ +#!/bin/sh +exec clang --config "$(dirname "$0")/x86_64-unknown-nonos.cfg" "$@" diff --git a/toolchain/nonos-c/x86_64-unknown-nonos.cfg b/toolchain/nonos-c/x86_64-unknown-nonos.cfg new file mode 100644 index 0000000000..e0754f23a5 --- /dev/null +++ b/toolchain/nonos-c/x86_64-unknown-nonos.cfg @@ -0,0 +1,9 @@ +--target=x86_64-unknown-none-elf +-D__nonos__ +-ffreestanding +-fno-stack-protector +-fPIE +-mno-red-zone +-nostdlib +-fno-builtin +-fuse-ld=lld diff --git a/toolchain/nonos-c/x86_64-unknown-nonos.json b/toolchain/nonos-c/x86_64-unknown-nonos.json new file mode 100644 index 0000000000..388933d9f4 --- /dev/null +++ b/toolchain/nonos-c/x86_64-unknown-nonos.json @@ -0,0 +1,40 @@ +{ + "llvm-target": "x86_64-unknown-nonos", + "arch": "x86_64", + "vendor": "unknown", + "os": "nonos", + "env": "", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", + + "target-endian": "little", + "target-pointer-width": 64, + "target-c-int-width": 32, + "max-atomic-width": 64, + + "executables": true, + "panic-strategy": "abort", + "disable-redzone": true, + + "frame-pointer": "always", + + "cpu": "x86-64", + "features": "+sse,+sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,-mmx", + + "linker-flavor": "ld.lld", + "linker": "rust-lld", + + "pre-link-args": { + "ld.lld": ["-nostdlib", "-pie", "--gc-sections", "-z", "max-page-size=0x1000"] + }, + + "relocation-model": "pic", + "code-model": "small", + "position-independent-executables": true, + "static-position-independent-executables": true, + + "no-default-libraries": true, + "has-rpath": false, + + "stack-probes": { "kind": "inline" }, + "eh-frame-header": false +} diff --git a/toolchain/nonos-relibc/apply.sh b/toolchain/nonos-relibc/apply.sh new file mode 100755 index 0000000000..0203547d4d --- /dev/null +++ b/toolchain/nonos-relibc/apply.sh @@ -0,0 +1,221 @@ +#!/usr/bin/env bash +# Graft the NONOS relibc platform backend into the gitignored vendored relibc +# (third_party/redox/src-repos/relibc). The backend is authored as tracked +# source here; this copies it in and idempotently patches the vendored platform +# selector, a Sync errno static (kernel has no per-thread TLS), and config.mk +# (cc/ld block + an `override CARGOFLAGS` carrying the json target spec) so +# `make TARGET=x86_64-unknown-nonos libs` builds the nonos arm. Re-run after a +# relibc re-import or `git clean`. Idempotent. +set -euo pipefail + +HERE="$(cd "$(dirname "$0")" && pwd)" +REPO="$(cd "$HERE/../.." && pwd)" +RELIBC="$REPO/third_party/redox/src-repos/relibc" +CC_NONOS="$REPO/toolchain/nonos-c/cc-nonos" + +# 1. copy the platform backend +mkdir -p "$RELIBC/src/platform/nonos" +cp "$HERE/platform/nonos/mod.rs" "$RELIBC/src/platform/nonos/mod.rs" +cp "$HERE/platform/nonos/lowlevel.rs" "$RELIBC/src/platform/nonos/lowlevel.rs" +cp "$HERE/platform/nonos/socket.rs" "$RELIBC/src/platform/nonos/socket.rs" +cp "$HERE/platform/nonos/socket_rt.rs" "$RELIBC/src/platform/nonos/socket_rt.rs" +cp "$HERE/platform/nonos/signal.rs" "$RELIBC/src/platform/nonos/signal.rs" +cp "$HERE/platform/nonos/epoll.rs" "$RELIBC/src/platform/nonos/epoll.rs" +cp "$HERE/platform/nonos/epoll_rt.rs" "$RELIBC/src/platform/nonos/epoll_rt.rs" +cp "$HERE/platform/nonos/ptrace.rs" "$RELIBC/src/platform/nonos/ptrace.rs" +cp "$HERE/platform/nonos/fs.rs" "$RELIBC/src/platform/nonos/fs.rs" + +# 2. patch the platform selector + config.mk (idempotent) +python3 - "$RELIBC" "$CC_NONOS" <<'PY' +import re, sys +relibc, cc = sys.argv[1], sys.argv[2] +tgtdir = cc.rsplit("/", 1)[0] + +modrs = f"{relibc}/src/platform/mod.rs" +with open(modrs) as f: + s = f.read() +orig = s +if '#[path = "nonos/mod.rs"]' not in s: + # vendored-relibc-version-specific anchor — re-check on a relibc re-import + anchor = '\npub use self::rlb::{Line, RawLineBuffer};' + arm = ('\n#[cfg(target_os = "nonos")]\n#[path = "nonos/mod.rs"]\n' + 'pub(crate) mod sys;\n') + i = s.index(anchor) + s = s[:i] + arm + s[i:] +errno_block = ( + '#[cfg(not(target_os = "nonos"))]\n' + '#[thread_local]\n' + 'pub static ERRNO: Cell = Cell::new(0);\n' + '#[cfg(target_os = "nonos")]\n' + 'pub struct SyncErrno(Cell);\n' + '#[cfg(target_os = "nonos")]\n' + 'unsafe impl Sync for SyncErrno {}\n' + '#[cfg(target_os = "nonos")]\n' + 'impl SyncErrno {\n' + ' pub fn get(&self) -> c_int { self.0.get() }\n' + ' pub fn set(&self, v: c_int) { self.0.set(v) }\n' + ' pub fn as_ptr(&self) -> *mut c_int { self.0.as_ptr() }\n' + '}\n' + '#[cfg(target_os = "nonos")]\n' + 'pub static ERRNO: SyncErrno = SyncErrno(Cell::new(0));' +) +if 'struct SyncErrno' not in s: + s = re.sub( + r'(#\[cfg_attr\(not\(target_os = "nonos"\), thread_local\)\]|#\[thread_local\])' + r'\npub static ERRNO: Cell = Cell::new\(0\);', + lambda _m: errno_block, s, count=1) +if s != orig: + with open(modrs, "w") as f: + f.write(s) + +cfgmk = f"{relibc}/config.mk" +with open(cfgmk) as f: + s = f.read() +block = ("\nifeq ($(TARGET),x86_64-unknown-nonos)\n" + f"\texport CC={cc}\n" + "\texport LD=ld.lld\n" + "\texport AR=llvm-ar\n" + "\texport NM=llvm-nm\n" + "\texport OBJCOPY=llvm-objcopy\n" + "\texport CPPFLAGS=\n" + "\texport CARGO_TEST=\n" + "\toverride CARGOFLAGS := -Z build-std=core,alloc,compiler_builtins " + f"-Z json-target-spec --target={tgtdir}/x86_64-unknown-nonos.json " + "--no-default-features\n" + "endif\n") +s = re.sub(r"\nifeq \(\$\(TARGET\),x86_64-unknown-nonos\).*?endif\n", "", s, flags=re.S) +with open(cfgmk, "w") as f: + f.write(s.rstrip("\n") + "\n" + block) +PY + +# 3. reuse linux generic-x86_64 ABI definitions for nonos (idempotent) +python3 - "$RELIBC" <<'PY' +import sys +relibc = sys.argv[1] + +def patch(rel, old, new): + p = f"{relibc}/{rel}" + with open(p) as f: + s = f.read() + if new in s: + return + if old not in s: + raise SystemExit(f"anchor not found in {rel}") + with open(p, "w") as f: + f.write(s.replace(old, new, 1)) + +LX = '#[cfg(target_os = "linux")]' +NX = '#[cfg(any(target_os = "linux", target_os = "nonos"))]' + +def broaden(rel, marker): + patch(rel, f'{LX}\n{marker}', f'{NX}\n{marker}') + +for m in ("_paths", "bits_open-flags", "fcntl", "netdb", "signal", + "sys_mman", "sys_syslog", "termios"): + patch(f"src/header/{m}/mod.rs", + f'{LX}\n#[path = "linux.rs"]', f'{NX}\n#[path = "linux.rs"]') + +patch("src/header/time/constants.rs", + f'{LX}\n#[path = "linux.rs"]', f'{NX}\n#[path = "linux.rs"]') +patch("src/header/unistd/sysconf.rs", + f'{LX}\n#[path = "sysconf/linux.rs"]', f'{NX}\n#[path = "sysconf/linux.rs"]') +patch("src/platform/mod.rs", f'{LX}\npub mod auxv_defs;', f'{NX}\npub mod auxv_defs;') +patch("src/header/sys_epoll/mod.rs", + f'{LX}\npub const EPOLL_CLOEXEC: c_int = 0x8_0000;', + f'{NX}\npub const EPOLL_CLOEXEC: c_int = 0x8_0000;') + +patch("cbindgen.globdefs.toml", + '"target_os=linux" = "__linux__"', + '"target_os=linux" = "__linux__"\n"target_os=nonos" = "__nonos__"') + +broaden("src/header/termios/mod.rs", + '#[repr(C)]\n#[derive(Default, Clone)]\npub struct termios {') +for fn in ("cfgetispeed", "cfgetospeed", "cfsetispeed", "cfsetospeed"): + broaden("src/header/termios/mod.rs", + f'#[unsafe(no_mangle)]\npub unsafe extern "C" fn {fn}') + +broaden("src/header/grp/mod.rs", "const SEPARATOR: char = ':';") +broaden("src/header/pwd/mod.rs", "const SEPARATOR: u8 = b':';") +broaden("src/header/shadow/mod.rs", "const SEPARATOR: char = ':';") +broaden("src/header/pwd/mod.rs", "mod linux;") +broaden("src/header/pwd/mod.rs", "use self::linux as sys;") +broaden("src/header/limits/mod.rs", "pub const PAGE_SIZE: usize = 4096;") +broaden("src/header/sys_mman/mod.rs", 'static SHM_PATH: &[u8] = b"/dev/shm/";') +broaden("src/header/stdlib/mod.rs", + ' let r = unsafe { open(c"/dev/ptmx".as_ptr(), flags) };') +broaden("src/header/stdlib/mod.rs", + ' let name = format!("/dev/pts/{}", pty);') + +patch("src/header/signal/mod.rs", + '#[cfg(not(target_os = "linux"))]\npub struct sigevent {', + '#[cfg(not(any(target_os = "linux", target_os = "nonos")))]\npub struct sigevent {') +broaden("src/header/signal/mod.rs", "pub struct sigevent {") + +patch("src/header/sys_ioctl/mod.rs", + 'use crate::{\n error::ResultExt,\n' + ' platform::types::{c_char, c_int, c_ulong, c_ushort, c_void},\n};', + '#[cfg(not(target_os = "nonos"))]\nuse crate::error::ResultExt;\n' + 'use crate::platform::types::{c_char, c_int, c_ulong, c_ushort, c_void};') +patch("src/header/sys_ioctl/mod.rs", + ' #[cfg(target_os = "redox")]\n' + ' unsafe { self::redox::ioctl_inner(fd, request, out) }.or_minus_one_errno()\n}', + ' #[cfg(target_os = "nonos")]\n { let _ = (fd, request, out); -1 }\n' + ' #[cfg(target_os = "redox")]\n' + ' unsafe { self::redox::ioctl_inner(fd, request, out) }.or_minus_one_errno()\n}') + +patch("src/ld_so/tcb.rs", + '#[cfg(target_os = "linux")]\npub type OsSpecific = ();', + '#[cfg(any(target_os = "linux", target_os = "nonos"))]\npub type OsSpecific = ();') +patch("src/ld_so/tcb.rs", + '#[cfg(any(target_os = "linux", target_os = "redox"))]\n unsafe fn os_new(', + '#[cfg(any(target_os = "linux", target_os = "redox", target_os = "nonos"))]\n unsafe fn os_new(') +patch("src/ld_so/tcb.rs", + '#[cfg(all(target_os = "linux", target_arch = "x86_64"))]', + '#[cfg(all(target_os = "nonos", target_arch = "x86_64"))]\n' + ' unsafe fn os_arch_activate(_os: &(), _tls_end: usize, _tls_len: usize) {}\n\n' + ' #[cfg(all(target_os = "linux", target_arch = "x86_64"))]') + +patch("src/ld_so/mod.rs", + '#[cfg(any(target_os = "linux", target_os = "redox"))]\npub unsafe fn init(', + '#[cfg(any(target_os = "linux", target_os = "redox", target_os = "nonos"))]\npub unsafe fn init(') +patch("src/ld_so/mod.rs", + '#[cfg(all(target_os = "linux", target_arch = "x86_64"))]\n {\n const ARCH_GET_FS', + '#[cfg(all(target_os = "nonos", target_arch = "x86_64"))]\n { tp = 1; }\n' + ' #[cfg(all(target_os = "linux", target_arch = "x86_64"))]\n {\n const ARCH_GET_FS') + +patch("src/ld_so/tcb.rs", + " pub unsafe fn current() -> Option<&'static mut Self> {\n" + " unsafe { Some(&mut *GenericTcb::::current_ptr()?.cast()) }\n }", + " #[cfg(target_os = \"nonos\")]\n" + " pub unsafe fn current() -> Option<&'static mut Self> { None }\n" + " #[cfg(not(target_os = \"nonos\"))]\n" + " pub unsafe fn current() -> Option<&'static mut Self> {\n" + " unsafe { Some(&mut *GenericTcb::::current_ptr()?.cast()) }\n }") + +patch("src/pthread/mod.rs", + 'pub unsafe fn init() {\n let mut thread = Pthread {', + '#[cfg(target_os = "nonos")]\npub unsafe fn init() {}\n' + '#[cfg(not(target_os = "nonos"))]\n' + 'pub unsafe fn init() {\n let mut thread = Pthread {') + +patch("src/crt0/src/lib.rs", + '#[cfg(target_arch = "x86_64")]\nglobal_asm!(', + '#[cfg(target_os = "nonos")]\nglobal_asm!(\n' + ' "\n' + ' .globl _start\n' + ' .type _start, @function\n' + '_start:\n' + ' xor rbp, rbp\n' + ' and rsp, -16\n' + ' sub rsp, 8\n' + ' push 0\n push 0\n push 0\n push 0\n push 0\n' + ' mov rdi, rsp\n' + ' call relibc_crt0\n' + ' .size _start, . - _start\n' + '"\n);\n\n' + '#[cfg(all(target_arch = "x86_64", not(target_os = "nonos")))]\nglobal_asm!(') +PY + +echo "NONOS relibc backend grafted into $RELIBC" +echo " src/platform/nonos/{mod,lowlevel,socket,signal,epoll,ptrace}.rs" +echo " cfg arm + Sync-errno patch + config.mk TARGET block (override CARGOFLAGS)" diff --git a/toolchain/nonos-relibc/platform/nonos/epoll.rs b/toolchain/nonos-relibc/platform/nonos/epoll.rs new file mode 100644 index 0000000000..6cbdae28e4 --- /dev/null +++ b/toolchain/nonos-relibc/platform/nonos/epoll.rs @@ -0,0 +1,81 @@ +use super::{ + super::{PalEpoll, types::*}, + Sys, +}; +use crate::{ + error::{Errno, Result}, + header::{ + bits_sigset_t::sigset_t, + errno::{EBADF, EINVAL, EMFILE, ENOENT}, + sys_epoll::{epoll_data, epoll_event, EPOLLIN, EPOLLOUT}, + }, +}; + +fn poll_one(handle: u32, events: u16) -> u16 { + use super::socket_rt as rt; + let mut payload = [0u8; 12]; + payload[0..4].copy_from_slice(&handle.to_le_bytes()); + payload[4..6].copy_from_slice(&events.to_le_bytes()); + let mut resp = [0u8; 24]; + match rt::nskt_call(rt::OP_POLL, &payload, &mut resp) { + Ok((0, n)) if n >= 2 => u16::from_le_bytes([resp[20], resp[21]]), + _ => 0, + } +} + +impl PalEpoll for Sys { + fn epoll_create1(_flags: c_int) -> Result { + super::epoll_rt::create().ok_or(Errno(EMFILE)) + } + unsafe fn epoll_ctl(epfd: c_int, op: c_int, fd: c_int, event: *mut epoll_event) -> Result<()> { + use super::epoll_rt as rt; + use crate::header::sys_epoll::{EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD}; + match op { + EPOLL_CTL_ADD | EPOLL_CTL_MOD => { + if event.is_null() { return Err(Errno(EINVAL)); } + let events = unsafe { (*event).events }; + let data = unsafe { (*event).data.u64 }; + if rt::set(epfd, fd, events, data) { Ok(()) } else { Err(Errno(EINVAL)) } + } + EPOLL_CTL_DEL => { + if rt::remove(epfd, fd) { Ok(()) } else { Err(Errno(ENOENT)) } + } + _ => Err(Errno(EINVAL)), + } + } + unsafe fn epoll_pwait(epfd: c_int, events: *mut epoll_event, maxevents: c_int, timeout: c_int, _sigmask: *const sigset_t) -> Result { + use super::epoll_rt as ep; + use super::socket_rt as rt; + if maxevents <= 0 { + return Err(Errno(EINVAL)); + } + let max = maxevents as usize; + let deadline = super::now_ms().saturating_add(timeout as i64); + loop { + let mut n = 0usize; + let ok = ep::for_each(epfd, |fd, want, data| { + if n >= max { return; } + let Some(handle) = rt::handle_of(fd) else { return; }; + let mut pe: u16 = 0; + if want & EPOLLIN != 0 { pe |= 1; } + if want & EPOLLOUT != 0 { pe |= 2; } + let revents = poll_one(handle, pe); + if revents != 0 { + let mut e: u32 = 0; + if revents & 1 != 0 { e |= EPOLLIN; } + if revents & 2 != 0 { e |= EPOLLOUT; } + unsafe { + (*events.add(n)).events = e; + (*events.add(n)).data = epoll_data { u64: data }; + } + n += 1; + } + }); + if !ok { return Err(Errno(EBADF)); } + if n > 0 || timeout == 0 || (timeout > 0 && super::now_ms() >= deadline) { + return Ok(n); + } + unsafe { super::lowlevel::syscall0(super::lowlevel::MK_YIELD); } + } + } +} diff --git a/toolchain/nonos-relibc/platform/nonos/epoll_rt.rs b/toolchain/nonos-relibc/platform/nonos/epoll_rt.rs new file mode 100644 index 0000000000..8b53dd1d99 --- /dev/null +++ b/toolchain/nonos-relibc/platform/nonos/epoll_rt.rs @@ -0,0 +1,63 @@ +use core::cell::SyncUnsafeCell; + +use super::super::types::c_int; + +const EPOLL_FD_BASE: c_int = 512; +const INSTANCES: usize = 16; +const MAX_REGS: usize = 32; + +#[derive(Clone, Copy)] +struct Reg { fd: c_int, events: u32, data: u64, used: bool } + +#[derive(Clone, Copy)] +struct Instance { used: bool, regs: [Reg; MAX_REGS] } + +const EMPTY_REG: Reg = Reg { fd: 0, events: 0, data: 0, used: false }; +const EMPTY_INST: Instance = Instance { used: false, regs: [EMPTY_REG; MAX_REGS] }; + +static EPOLL: SyncUnsafeCell<[Instance; INSTANCES]> = SyncUnsafeCell::new([EMPTY_INST; INSTANCES]); + +fn table() -> &'static mut [Instance; INSTANCES] { unsafe { &mut *EPOLL.get() } } + +pub fn is_epoll_fd(fd: c_int) -> bool { + fd >= EPOLL_FD_BASE && (fd - EPOLL_FD_BASE) < INSTANCES as c_int +} +fn idx(fd: c_int) -> Option { + if is_epoll_fd(fd) { Some((fd - EPOLL_FD_BASE) as usize) } else { None } +} +fn live(fd: c_int) -> Option { + let i = idx(fd)?; + if table()[i].used { Some(i) } else { None } +} + +pub fn create() -> Option { + for (i, inst) in table().iter_mut().enumerate() { + if !inst.used { *inst = Instance { used: true, regs: [EMPTY_REG; MAX_REGS] }; return Some(EPOLL_FD_BASE + i as c_int); } + } + None +} +pub fn destroy(fd: c_int) { + if let Some(i) = idx(fd) { table()[i].used = false; } +} +pub fn set(epfd: c_int, fd: c_int, events: u32, data: u64) -> bool { + let Some(i) = live(epfd) else { return false; }; + for r in table()[i].regs.iter_mut() { + if r.used && r.fd == fd { r.events = events; r.data = data; return true; } + } + for r in table()[i].regs.iter_mut() { + if !r.used { *r = Reg { fd, events, data, used: true }; return true; } + } + false +} +pub fn remove(epfd: c_int, fd: c_int) -> bool { + let Some(i) = live(epfd) else { return false; }; + for r in table()[i].regs.iter_mut() { + if r.used && r.fd == fd { r.used = false; return true; } + } + false +} +pub fn for_each(epfd: c_int, mut f: F) -> bool { + let Some(i) = live(epfd) else { return false; }; + for r in table()[i].regs { if r.used { f(r.fd, r.events, r.data); } } + true +} diff --git a/toolchain/nonos-relibc/platform/nonos/fs.rs b/toolchain/nonos-relibc/platform/nonos/fs.rs new file mode 100644 index 0000000000..924a300adb --- /dev/null +++ b/toolchain/nonos-relibc/platform/nonos/fs.rs @@ -0,0 +1,69 @@ +use alloc::vec::Vec; +use core::cell::SyncUnsafeCell; +use crate::{error::{Errno, Result}, header::errno::EIO}; +use super::{super::types::c_int, lowlevel::{syscall6, MK_IPC_CALL}}; + +const VFS_ENDPOINT: u64 = 4104; +const VFS_MAGIC: u32 = 0x4E4F_5646; +const VFS_VERSION: u16 = 1; +const HDR_LEN: usize = 20; +pub const OP_OPEN: u16 = 1; +pub const OP_CLOSE: u16 = 2; +pub const OP_READ: u16 = 3; +pub const OP_WRITE: u16 = 4; +pub const OP_STAT: u16 = 5; +pub const OP_MKDIR: u16 = 8; +pub const OP_UNLINK: u16 = 9; +pub const OP_RENAME: u16 = 10; +pub const OP_SEEK: u16 = 11; +pub const OP_PREAD: u16 = 12; +pub const OP_PWRITE: u16 = 13; +pub const VFS_O_CREATE: u32 = 1 << 0; +pub const VFS_O_TRUNC: u32 = 1 << 1; +pub const VFS_O_APPEND: u32 = 1 << 2; + +const FD_SLOTS: usize = 64; +static FD_TABLE: SyncUnsafeCell<[i32; FD_SLOTS]> = SyncUnsafeCell::new([-1; FD_SLOTS]); +fn table() -> &'static mut [i32; FD_SLOTS] { unsafe { &mut *FD_TABLE.get() } } + +pub fn fd_alloc(vfs_fd: u32) -> Option { + for (i, s) in table().iter_mut().enumerate() { + if *s < 0 { *s = vfs_fd as i32; return Some((i + 3) as c_int); } + } + None +} +pub fn fd_vfs(fd: c_int) -> Option { + let i = fd.checked_sub(3)? as usize; + if i >= FD_SLOTS { return None; } + let v = table()[i]; + if v < 0 { None } else { Some(v as u32) } +} +pub fn fd_free(fd: c_int) { + if let Some(i) = fd.checked_sub(3).map(|v| v as usize) { + if i < FD_SLOTS { table()[i] = -1; } + } +} +pub fn fd_dup(fd: c_int) -> Option { fd_alloc(fd_vfs(fd)?) } +pub fn fd_set(fd: c_int, vfs_fd: u32) -> bool { + match fd.checked_sub(3).map(|v| v as usize) { + Some(i) if i < FD_SLOTS => { table()[i] = vfs_fd as i32; true } + _ => false, + } +} +pub fn vfs_call(op: u16, payload: &[u8], resp: &mut [u8]) -> Result<(i32, usize)> { + let mut req = Vec::with_capacity(HDR_LEN + payload.len()); + req.extend_from_slice(&VFS_MAGIC.to_le_bytes()); + req.extend_from_slice(&VFS_VERSION.to_le_bytes()); + req.extend_from_slice(&op.to_le_bytes()); + req.extend_from_slice(&[0u8; 8]); + req.extend_from_slice(&(payload.len() as u32).to_le_bytes()); + req.extend_from_slice(payload); + let ret = unsafe { + syscall6(MK_IPC_CALL, VFS_ENDPOINT, + req.as_ptr() as u64, req.len() as u64, + resp.as_mut_ptr() as u64, resp.len() as u64, 0) + }; + if ret < 24 { return Err(Errno(EIO)); } + let s = i32::from_le_bytes([resp[20], resp[21], resp[22], resp[23]]); + Ok((s, (ret as usize) - 24)) +} diff --git a/toolchain/nonos-relibc/platform/nonos/lowlevel.rs b/toolchain/nonos-relibc/platform/nonos/lowlevel.rs new file mode 100644 index 0000000000..a491b6fcc7 --- /dev/null +++ b/toolchain/nonos-relibc/platform/nonos/lowlevel.rs @@ -0,0 +1,117 @@ +use core::arch::{asm, global_asm}; + +pub const fn tag4(s: &[u8; 4]) -> u64 { + (s[0] as u64) | (s[1] as u64) << 8 | (s[2] as u64) << 16 | (s[3] as u64) << 24 +} + +pub const MK_DEBUG: u64 = tag4(b"MDBG"); +pub const MK_EXIT: u64 = tag4(b"MEXT"); +pub const MK_GETPID: u64 = tag4(b"MGPD"); +pub const MK_MMAP: u64 = tag4(b"MMAP"); +pub const MK_MUNMAP: u64 = tag4(b"MUMP"); +pub const MK_TIME_MILLIS: u64 = tag4(b"MTMS"); +pub const MK_YIELD: u64 = tag4(b"MYLD"); +pub const MK_CRYPTO_RANDOM: u64 = tag4(b"CRND"); +pub const MK_IPC_CALL: u64 = tag4(b"MICL"); +pub const MK_THREAD_SPAWN: u64 = tag4(b"MTSP"); + +#[inline] +pub unsafe fn syscall0(n: u64) -> i64 { + let r: i64; + unsafe { + asm!("syscall", inlateout("rax") n => r, + lateout("rdi") _, lateout("rsi") _, lateout("rdx") _, + lateout("rcx") _, lateout("r11") _, options(nostack)); + } + r +} + +#[inline] +pub unsafe fn syscall1(n: u64, a0: u64) -> i64 { + let r: i64; + unsafe { + asm!("syscall", inlateout("rax") n => r, in("rdi") a0, + lateout("rsi") _, lateout("rdx") _, + lateout("rcx") _, lateout("r11") _, options(nostack)); + } + r +} + +#[inline] +pub unsafe fn syscall2(n: u64, a0: u64, a1: u64) -> i64 { + let r: i64; + unsafe { + asm!("syscall", inlateout("rax") n => r, in("rdi") a0, in("rsi") a1, + lateout("rdx") _, + lateout("rcx") _, lateout("r11") _, options(nostack)); + } + r +} + +#[inline] +pub unsafe fn syscall3(n: u64, a0: u64, a1: u64, a2: u64) -> i64 { + let r: i64; + unsafe { + asm!("syscall", inlateout("rax") n => r, in("rdi") a0, in("rsi") a1, + in("rdx") a2, lateout("rcx") _, lateout("r11") _, options(nostack)); + } + r +} + +#[inline] +pub unsafe fn syscall4(n: u64, a0: u64, a1: u64, a2: u64, a3: u64) -> i64 { + let r: i64; + unsafe { + asm!("syscall", inlateout("rax") n => r, in("rdi") a0, in("rsi") a1, + in("rdx") a2, in("r10") a3, + lateout("rcx") _, lateout("r11") _, options(nostack)); + } + r +} + +#[inline] +pub unsafe fn syscall5(n: u64, a0: u64, a1: u64, a2: u64, a3: u64, a4: u64) -> i64 { + let r: i64; + unsafe { + asm!("syscall", inlateout("rax") n => r, in("rdi") a0, in("rsi") a1, + in("rdx") a2, in("r10") a3, in("r8") a4, + lateout("rcx") _, lateout("r11") _, options(nostack)); + } + r +} + +#[inline] +pub unsafe fn syscall6(n: u64, a0: u64, a1: u64, a2: u64, a3: u64, a4: u64, a5: u64) -> i64 { + let r: i64; + unsafe { + asm!("syscall", inlateout("rax") n => r, in("rdi") a0, in("rsi") a1, + in("rdx") a2, in("r10") a3, in("r8") a4, in("r9") a5, + lateout("rcx") _, lateout("r11") _, options(nostack)); + } + r +} + +#[cfg(target_arch = "x86_64")] +global_asm!( + ".globl rlct_trampoline", + ".type rlct_trampoline, @function", + "rlct_trampoline:", + "pop rax", + "pop rdi", + "pop rsi", + "pop rdx", + "pop rcx", + "pop r8", + "pop r9", + "call rax", + "xor rdi, rdi", + "mov rax, 0x5458454d", + "syscall", + "ud2", + ".size rlct_trampoline, . - rlct_trampoline", +); + +#[cfg(target_arch = "x86_64")] +unsafe extern "C" { + pub fn rlct_trampoline(); +} diff --git a/toolchain/nonos-relibc/platform/nonos/mod.rs b/toolchain/nonos-relibc/platform/nonos/mod.rs new file mode 100644 index 0000000000..9a00421c11 --- /dev/null +++ b/toolchain/nonos-relibc/platform/nonos/mod.rs @@ -0,0 +1,418 @@ +//! NONOS platform backend for relibc. Task 0.3 ships the full `Pal` surface: +//! five real methods backed by `MK_*` syscalls (write, exit, getpid, mmap, +//! munmap) and the remaining required methods stubbed to `ENOSYS` (or the +//! obvious constant). Phase 1 de-stubs this backend method by method. + +use alloc::vec::Vec; +use core::num::NonZeroU64; + +use super::{Pal, types::*}; +use crate::{ + c_str::CStr, + error::{Errno, Result}, + header::{ + errno::{EBADF, EIO, EINVAL, EMFILE, ENOMEM, ENOSYS}, + fcntl::{O_APPEND, O_CREAT, O_TRUNC}, + signal::sigevent, + sys_resource::{rlimit, rusage}, + sys_select::timeval, + sys_stat::{stat, S_IFDIR, S_IFREG}, + sys_statvfs::statvfs, + sys_time::timezone, + sys_utsname::utsname, + time::{itimerspec, timespec}, + }, + iter::NulTerminated, + ld_so::tcb::OsSpecific, + out::Out, + pthread, +}; + +pub mod lowlevel; +mod fs; + +mod epoll; +mod ptrace; +mod signal; +mod socket; +mod socket_rt; +mod epoll_rt; + +fn now_ms() -> i64 { + let r = unsafe { lowlevel::syscall0(lowlevel::MK_TIME_MILLIS) }; + if r < 0 { 0 } else { r } +} + +pub struct Sys; + +impl Pal for Sys { + fn write(fildes: c_int, buf: &[u8]) -> Result { + if fildes == 1 || fildes == 2 { + unsafe { lowlevel::syscall2(lowlevel::MK_DEBUG, buf.as_ptr() as u64, buf.len() as u64); } + return Ok(buf.len()); + } + if socket_rt::is_socket_fd(fildes) { + return socket::sock_send(fildes, buf); + } + let vfs_fd = fs::fd_vfs(fildes).ok_or(Errno(EBADF))?; + if buf.len() > 65536 { return Err(Errno(EINVAL)); } + let pid = Self::getpid() as u32; + let mut payload = alloc::vec![0u8; 8 + buf.len()]; + payload[0..4].copy_from_slice(&pid.to_le_bytes()); + payload[4..8].copy_from_slice(&vfs_fd.to_le_bytes()); + payload[8..].copy_from_slice(buf); + let mut resp = [0u8; 28]; + let (status, data_len) = fs::vfs_call(fs::OP_WRITE, &payload, &mut resp)?; + if status < 0 { return Err(Errno(-status)); } + if data_len < 4 { return Err(Errno(EIO)); } + Ok(u32::from_le_bytes([resp[24], resp[25], resp[26], resp[27]]) as usize) + } + + fn exit(status: c_int) -> ! { + unsafe { lowlevel::syscall1(lowlevel::MK_EXIT, status as u64); } + loop {} + } + + fn getpid() -> pid_t { + unsafe { lowlevel::syscall0(lowlevel::MK_GETPID) as pid_t } + } + + unsafe fn mmap(_addr: *mut c_void, len: usize, _prot: c_int, _flags: c_int, _fildes: c_int, _off: off_t) -> Result<*mut c_void> { + let r = unsafe { lowlevel::syscall4(lowlevel::MK_MMAP, 0, len as u64, 3, 0) }; + if r <= 0 { + return Err(Errno(ENOMEM)); + } + Ok(r as *mut c_void) + } + + unsafe fn munmap(addr: *mut c_void, len: usize) -> Result<()> { + unsafe { lowlevel::syscall2(lowlevel::MK_MUNMAP, addr as u64, len as u64); } + Ok(()) + } + + unsafe fn brk(_addr: *mut c_void) -> Result<*mut c_void> { + Err(Errno(ENOSYS)) + } + + fn faccessat(_fd: c_int, path: CStr, _amode: c_int, _flags: c_int) -> Result<()> { + let pb = path.to_bytes(); + if pb.is_empty() || pb.len() > 255 { return Err(Errno(EINVAL)); } + let pid = Self::getpid() as u32; + let mut payload = Vec::with_capacity(5 + pb.len()); + payload.extend_from_slice(&pid.to_le_bytes()); + payload.push(pb.len() as u8); + payload.extend_from_slice(pb); + let mut resp = [0u8; 36]; + let (status, _) = fs::vfs_call(fs::OP_STAT, &payload, &mut resp)?; + if status < 0 { Err(Errno(-status)) } else { Ok(()) } + } + fn chdir(_path: CStr) -> Result<()> { Err(Errno(ENOSYS)) } + fn close(fildes: c_int) -> Result<()> { + if fildes <= 2 { return Ok(()); } + if socket_rt::is_socket_fd(fildes) { + return socket::close_fd(fildes); + } + if epoll_rt::is_epoll_fd(fildes) { + epoll_rt::destroy(fildes); + return Ok(()); + } + let vfs_fd = fs::fd_vfs(fildes).ok_or(Errno(EBADF))?; + let pid = Self::getpid() as u32; + let mut payload = [0u8; 8]; + payload[0..4].copy_from_slice(&pid.to_le_bytes()); + payload[4..8].copy_from_slice(&vfs_fd.to_le_bytes()); + let mut resp = [0u8; 24]; + let (status, _) = fs::vfs_call(fs::OP_CLOSE, &payload, &mut resp)?; + fs::fd_free(fildes); + if status < 0 { Err(Errno(-status)) } else { Ok(()) } + } + fn dup2(fildes: c_int, fildes2: c_int) -> Result { + if fildes == fildes2 { return Ok(fildes2); } + if fildes2 < 3 { return Err(Errno(EBADF)); } + let vfs_fd = fs::fd_vfs(fildes).ok_or(Errno(EBADF))?; + if fs::fd_vfs(fildes2).is_some() { let _ = Self::close(fildes2); } + if !fs::fd_set(fildes2, vfs_fd) { return Err(Errno(EBADF)); } + Ok(fildes2) + } + fn fchdir(_fildes: c_int) -> Result<()> { Err(Errno(ENOSYS)) } + fn fchmodat(_dirfd: c_int, _path: Option, _mode: mode_t, _flags: c_int) -> Result<()> { Err(Errno(ENOSYS)) } + fn fchownat(_fildes: c_int, _path: CStr, _owner: uid_t, _group: gid_t, _flags: c_int) -> Result<()> { Err(Errno(ENOSYS)) } + fn fdatasync(_fildes: c_int) -> Result<()> { Err(Errno(ENOSYS)) } + fn flock(_fd: c_int, _operation: c_int) -> Result<()> { Err(Errno(ENOSYS)) } + fn fstatat(_fildes: c_int, path: Option, mut buf: Out, _flags: c_int) -> Result<()> { + let p = match path { + Some(p) if !p.to_bytes().is_empty() => p, + _ => return Err(Errno(ENOSYS)), + }; + let pb = p.to_bytes(); + if pb.len() > 255 { return Err(Errno(EINVAL)); } + let pid = Self::getpid() as u32; + let mut payload = Vec::with_capacity(5 + pb.len()); + payload.extend_from_slice(&pid.to_le_bytes()); + payload.push(pb.len() as u8); + payload.extend_from_slice(pb); + let mut resp = [0u8; 36]; + let (status, data_len) = fs::vfs_call(fs::OP_STAT, &payload, &mut resp)?; + if status < 0 { return Err(Errno(-status)); } + if data_len < 12 { return Err(Errno(EIO)); } + let size = u64::from_le_bytes([resp[24], resp[25], resp[26], resp[27], + resp[28], resp[29], resp[30], resp[31]]); + let vfs_flags = u32::from_le_bytes([resp[32], resp[33], resp[34], resp[35]]); + buf.write(stat { + st_size: size as off_t, + st_mode: if vfs_flags & 1 != 0 { S_IFDIR | 0o555 } else { S_IFREG | 0o644 }, + ..Default::default() + }); + Ok(()) + } + fn fstatvfs(_fildes: c_int, _buf: Out) -> Result<()> { Err(Errno(ENOSYS)) } + fn fcntl(fildes: c_int, cmd: c_int, arg: c_ulonglong) -> Result { + if socket_rt::is_socket_fd(fildes) { + return socket::fcntl_sock(fildes, cmd, arg); + } + Err(Errno(ENOSYS)) + } + fn fpath(_fildes: c_int, _out: &mut [u8]) -> Result { Err(Errno(ENOSYS)) } + fn fsync(_fildes: c_int) -> Result<()> { Err(Errno(ENOSYS)) } + fn ftruncate(_fildes: c_int, _length: off_t) -> Result<()> { Err(Errno(ENOSYS)) } + unsafe fn utimensat(_dirfd: c_int, _path: CStr, _times: *const timespec, _flag: c_int) -> Result<()> { Err(Errno(ENOSYS)) } + fn getcwd(_buf: Out<[u8]>) -> Result<()> { Err(Errno(ENOSYS)) } + fn getdents(_fd: c_int, _buf: &mut [u8], _opaque_offset: u64) -> Result { Err(Errno(ENOSYS)) } + fn dir_seek(_fd: c_int, _opaque_offset: u64) -> Result<()> { Err(Errno(ENOSYS)) } + unsafe fn dent_reclen_offset(_this_dent: &[u8], _offset: usize) -> Option<(u16, u64)> { None } + fn linkat(_fd1: c_int, _oldpath: CStr, _fd2: c_int, _newpath: CStr, _flags: c_int) -> Result<()> { Err(Errno(ENOSYS)) } + fn lseek(fildes: c_int, offset: off_t, whence: c_int) -> Result { + let vfs_fd = fs::fd_vfs(fildes).ok_or(Errno(EBADF))?; + let pid = Self::getpid() as u32; + let mut payload = [0u8; 20]; + payload[0..4].copy_from_slice(&pid.to_le_bytes()); + payload[4..8].copy_from_slice(&vfs_fd.to_le_bytes()); + payload[8..10].copy_from_slice(&(whence as u16).to_le_bytes()); + payload[12..20].copy_from_slice(&offset.to_le_bytes()); + let mut resp = [0u8; 32]; + let (status, data_len) = fs::vfs_call(fs::OP_SEEK, &payload, &mut resp)?; + if status < 0 { return Err(Errno(-status)); } + if data_len < 8 { return Err(Errno(EIO)); } + Ok(i64::from_le_bytes([resp[24], resp[25], resp[26], resp[27], resp[28], resp[29], resp[30], resp[31]]) as off_t) + } + fn mkdirat(_fildes: c_int, path: CStr, _mode: mode_t) -> Result<()> { + let pb = path.to_bytes(); + if pb.is_empty() || pb.len() > 255 { return Err(Errno(EINVAL)); } + let pid = Self::getpid() as u32; + let mut payload = Vec::with_capacity(5 + pb.len()); + payload.extend_from_slice(&pid.to_le_bytes()); + payload.push(pb.len() as u8); + payload.extend_from_slice(pb); + let mut resp = [0u8; 24]; + let (status, _) = fs::vfs_call(fs::OP_MKDIR, &payload, &mut resp)?; + if status < 0 { Err(Errno(-status)) } else { Ok(()) } + } + fn mkfifoat(_dir_fd: c_int, _path: CStr, _mode: mode_t) -> Result<()> { Err(Errno(ENOSYS)) } + fn mknodat(_fildes: c_int, _path: CStr, _mode: mode_t, _dev: dev_t) -> Result<()> { Err(Errno(ENOSYS)) } + fn openat(_dirfd: c_int, path: CStr, oflag: c_int, _mode: mode_t) -> Result { + let pb = path.to_bytes(); + if pb.is_empty() || pb.len() > 255 { return Err(Errno(EINVAL)); } + let pid = Self::getpid() as u32; + let vfs_flags = + (if oflag & O_CREAT != 0 { fs::VFS_O_CREATE } else { 0 }) | + (if oflag & O_TRUNC != 0 { fs::VFS_O_TRUNC } else { 0 }) | + (if oflag & O_APPEND != 0 { fs::VFS_O_APPEND } else { 0 }); + let mut payload = Vec::with_capacity(9 + pb.len()); + payload.extend_from_slice(&pid.to_le_bytes()); + payload.push(pb.len() as u8); + payload.extend_from_slice(pb); + payload.extend_from_slice(&vfs_flags.to_le_bytes()); + let mut resp = [0u8; 28]; + let (status, data_len) = fs::vfs_call(fs::OP_OPEN, &payload, &mut resp)?; + if status < 0 { return Err(Errno(-status)); } + if data_len < 4 { return Err(Errno(EIO)); } + let vfs_fd = u32::from_le_bytes([resp[24], resp[25], resp[26], resp[27]]); + fs::fd_alloc(vfs_fd).ok_or(Errno(EMFILE)) + } + fn pipe2(_fildes: Out<[c_int; 2]>, _flags: c_int) -> Result<()> { Err(Errno(ENOSYS)) } + fn posix_fallocate(_fd: c_int, _offset: u64, _length: NonZeroU64) -> Result<()> { Err(Errno(ENOSYS)) } + fn posix_getdents(_fildes: c_int, _buf: &mut [u8]) -> Result { Err(Errno(ENOSYS)) } + fn read(fildes: c_int, buf: &mut [u8]) -> Result { + if socket_rt::is_socket_fd(fildes) { + return socket::sock_recv(fildes, buf); + } + let vfs_fd = fs::fd_vfs(fildes).ok_or(Errno(EBADF))?; + let count = (buf.len() as u32).min(65536); + let pid = Self::getpid() as u32; + let mut payload = [0u8; 12]; + payload[0..4].copy_from_slice(&pid.to_le_bytes()); + payload[4..8].copy_from_slice(&vfs_fd.to_le_bytes()); + payload[8..12].copy_from_slice(&count.to_le_bytes()); + let mut resp = alloc::vec![0u8; 24 + count as usize]; + let (status, n) = fs::vfs_call(fs::OP_READ, &payload, &mut resp)?; + if status < 0 { return Err(Errno(-status)); } + buf[..n].copy_from_slice(&resp[24..24 + n]); + Ok(n) + } + fn pread(fildes: c_int, buf: &mut [u8], offset: off_t) -> Result { + let vfs_fd = fs::fd_vfs(fildes).ok_or(Errno(EBADF))?; + let count = (buf.len() as u32).min(65536); + let pid = Self::getpid() as u32; + let mut payload = [0u8; 20]; + payload[0..4].copy_from_slice(&pid.to_le_bytes()); + payload[4..8].copy_from_slice(&vfs_fd.to_le_bytes()); + payload[8..16].copy_from_slice(&(offset as i64).to_le_bytes()); + payload[16..20].copy_from_slice(&count.to_le_bytes()); + let mut resp = alloc::vec![0u8; 24 + count as usize]; + let (status, n) = fs::vfs_call(fs::OP_PREAD, &payload, &mut resp)?; + if status < 0 { return Err(Errno(-status)); } + buf[..n].copy_from_slice(&resp[24..24 + n]); + Ok(n) + } + fn pwrite(fildes: c_int, buf: &[u8], offset: off_t) -> Result { + let vfs_fd = fs::fd_vfs(fildes).ok_or(Errno(EBADF))?; + if buf.len() > 65536 { return Err(Errno(EINVAL)); } + let pid = Self::getpid() as u32; + let mut payload = alloc::vec![0u8; 16 + buf.len()]; + payload[0..4].copy_from_slice(&pid.to_le_bytes()); + payload[4..8].copy_from_slice(&vfs_fd.to_le_bytes()); + payload[8..16].copy_from_slice(&(offset as i64).to_le_bytes()); + payload[16..].copy_from_slice(buf); + let mut resp = [0u8; 28]; + let (status, data_len) = fs::vfs_call(fs::OP_PWRITE, &payload, &mut resp)?; + if status < 0 { return Err(Errno(-status)); } + if data_len < 4 { return Err(Errno(EIO)); } + Ok(u32::from_le_bytes([resp[24], resp[25], resp[26], resp[27]]) as usize) + } + fn readlinkat(_dirfd: c_int, _pathname: CStr, _out: &mut [u8]) -> Result { Err(Errno(ENOSYS)) } + fn renameat2(_old_dir: c_int, old_path: CStr, _new_dir: c_int, new_path: CStr, _flags: c_uint) -> Result<()> { + let ob = old_path.to_bytes(); + let nb = new_path.to_bytes(); + if ob.is_empty() || ob.len() > 255 || nb.is_empty() || nb.len() > 255 { + return Err(Errno(EINVAL)); + } + let pid = Self::getpid() as u32; + let mut payload = Vec::with_capacity(4 + 1 + ob.len() + 1 + nb.len()); + payload.extend_from_slice(&pid.to_le_bytes()); + payload.push(ob.len() as u8); + payload.extend_from_slice(ob); + payload.push(nb.len() as u8); + payload.extend_from_slice(nb); + let mut resp = [0u8; 24]; + let (status, _) = fs::vfs_call(fs::OP_RENAME, &payload, &mut resp)?; + if status < 0 { Err(Errno(-status)) } else { Ok(()) } + } + fn symlinkat(_path1: CStr, _fd: c_int, _path2: CStr) -> Result<()> { Err(Errno(ENOSYS)) } + fn sync() -> Result<()> { Err(Errno(ENOSYS)) } + fn unlinkat(_fd: c_int, path: CStr, _flags: c_int) -> Result<()> { + let pb = path.to_bytes(); + if pb.is_empty() || pb.len() > 255 { return Err(Errno(EINVAL)); } + let pid = Self::getpid() as u32; + let mut payload = Vec::with_capacity(5 + pb.len()); + payload.extend_from_slice(&pid.to_le_bytes()); + payload.push(pb.len() as u8); + payload.extend_from_slice(pb); + let mut resp = [0u8; 24]; + let (status, _) = fs::vfs_call(fs::OP_UNLINK, &payload, &mut resp)?; + if status < 0 { Err(Errno(-status)) } else { Ok(()) } + } + unsafe fn mlock(_addr: *const c_void, _len: usize) -> Result<()> { Ok(()) } + unsafe fn mlockall(_flags: c_int) -> Result<()> { Ok(()) } + unsafe fn mremap(_addr: *mut c_void, _len: usize, _new_len: usize, _flags: c_int, _args: *mut c_void) -> Result<*mut c_void> { Err(Errno(ENOSYS)) } + unsafe fn mprotect(_addr: *mut c_void, _len: usize, _prot: c_int) -> Result<()> { Ok(()) } + unsafe fn msync(_addr: *mut c_void, _len: usize, _flags: c_int) -> Result<()> { Ok(()) } + unsafe fn munlock(_addr: *const c_void, _len: usize) -> Result<()> { Ok(()) } + unsafe fn madvise(_addr: *mut c_void, _len: usize, _flags: c_int) -> Result<()> { Ok(()) } + unsafe fn munlockall() -> Result<()> { Ok(()) } + unsafe fn nanosleep(rqtp: *const timespec, _rmtp: *mut timespec) -> Result<()> { + let rq = unsafe { &*rqtp }; + let deadline = now_ms() + rq.tv_sec * 1000 + rq.tv_nsec / 1_000_000; + while now_ms() < deadline { + unsafe { lowlevel::syscall0(lowlevel::MK_YIELD); } + } + Ok(()) + } + fn getpagesize() -> usize { 4096 } + fn clock_getres(_clk_id: clockid_t, tp: Option>) -> Result<()> { + if let Some(mut tp) = tp { + tp.write(timespec { tv_sec: 0, tv_nsec: 1_000_000 }); + } + Ok(()) + } + fn clock_gettime(_clk_id: clockid_t, mut tp: Out) -> Result<()> { + let ms = now_ms(); + tp.write(timespec { tv_sec: ms / 1000, tv_nsec: (ms % 1000) * 1_000_000 }); + Ok(()) + } + unsafe fn clock_settime(_clk_id: clockid_t, _tp: *const timespec) -> Result<()> { Err(Errno(ENOSYS)) } + fn gettimeofday(mut tp: Out, _tzp: Option>) -> Result<()> { + let ms = now_ms(); + tp.write(timeval { tv_sec: ms / 1000, tv_usec: ((ms % 1000) * 1000) as suseconds_t }); + Ok(()) + } + fn timer_create(_clock_id: clockid_t, _evp: &sigevent, _timerid: Out) -> Result<()> { Err(Errno(ENOSYS)) } + fn timer_delete(_timerid: timer_t) -> Result<()> { Err(Errno(ENOSYS)) } + fn timer_gettime(_timerid: timer_t, _value: Out) -> Result<()> { Err(Errno(ENOSYS)) } + fn timer_settime(_timerid: timer_t, _flags: c_int, _value: &itimerspec, _ovalue: Option>) -> Result<()> { Err(Errno(ENOSYS)) } + fn getegid() -> gid_t { 0 } + fn geteuid() -> uid_t { 0 } + fn getgid() -> gid_t { 0 } + fn getgroups(_list: Out<[gid_t]>) -> Result { Err(Errno(ENOSYS)) } + fn getpgid(_pid: pid_t) -> Result { Err(Errno(ENOSYS)) } + fn getppid() -> pid_t { 0 } + fn getpriority(_which: c_int, _who: id_t) -> Result { Err(Errno(ENOSYS)) } + fn getrandom(buf: &mut [u8], _flags: c_uint) -> Result { + if buf.is_empty() { + return Ok(0); + } + let mut filled = 0usize; + for chunk in buf.chunks_mut(4096) { + let r = unsafe { lowlevel::syscall2(lowlevel::MK_CRYPTO_RANDOM, chunk.as_mut_ptr() as u64, chunk.len() as u64) }; + if r < 0 { + return Err(Errno(EIO)); + } + filled += r as usize; + } + Ok(filled) + } + fn getresgid(_rgid: Option>, _egid: Option>, _sgid: Option>) -> Result<()> { Err(Errno(ENOSYS)) } + fn getresuid(_ruid: Option>, _euid: Option>, _suid: Option>) -> Result<()> { Err(Errno(ENOSYS)) } + fn getrlimit(_resource: c_int, _rlim: Out) -> Result<()> { Err(Errno(ENOSYS)) } + unsafe fn setrlimit(_resource: c_int, _rlim: *const rlimit) -> Result<()> { Err(Errno(ENOSYS)) } + fn getrusage(_who: c_int, _r_usage: Out) -> Result<()> { Err(Errno(ENOSYS)) } + fn getsid(_pid: pid_t) -> Result { Err(Errno(ENOSYS)) } + fn gettid() -> pid_t { 0 } + fn getuid() -> uid_t { 0 } + unsafe fn setgroups(_size: size_t, _list: *const gid_t) -> Result<()> { Err(Errno(ENOSYS)) } + fn setpgid(_pid: pid_t, _pgid: pid_t) -> Result<()> { Err(Errno(ENOSYS)) } + fn setpriority(_which: c_int, _who: id_t, _prio: c_int) -> Result<()> { Err(Errno(ENOSYS)) } + fn setresgid(_rgid: gid_t, _egid: gid_t, _sgid: gid_t) -> Result<()> { Err(Errno(ENOSYS)) } + fn setresuid(_ruid: uid_t, _euid: uid_t, _suid: uid_t) -> Result<()> { Err(Errno(ENOSYS)) } + fn setsid() -> Result { Err(Errno(ENOSYS)) } + fn umask(_mask: mode_t) -> mode_t { 0 } + unsafe fn execve(_path: CStr, _argv: *const *mut c_char, _envp: *const *mut c_char) -> Result<()> { Err(Errno(ENOSYS)) } + unsafe fn fexecve(_fildes: c_int, _argv: *const *mut c_char, _envp: *const *mut c_char) -> Result<()> { Err(Errno(ENOSYS)) } + unsafe fn exit_thread(_stack_base: *mut (), _stack_size: usize) -> ! { + unsafe { lowlevel::syscall1(lowlevel::MK_EXIT, 0); } + loop {} + } + unsafe fn fork() -> Result { Err(Errno(ENOSYS)) } + unsafe fn futex_wait(_addr: *mut u32, _val: u32, _deadline: Option<×pec>) -> Result<()> { Ok(()) } + unsafe fn futex_wake(_addr: *mut u32, _num: u32) -> Result { Ok(0) } + unsafe fn rlct_clone(stack: *mut usize, _os_specific: &mut OsSpecific) -> Result { + let r = unsafe { lowlevel::syscall2(lowlevel::MK_THREAD_SPAWN, lowlevel::rlct_trampoline as *const () as u64, stack as u64) }; + if r < 0 { return Err(Errno(-r as c_int)); } + Ok(pthread::OsTid {}) + } + unsafe fn rlct_kill(_os_tid: pthread::OsTid, _signal: usize) -> Result<()> { Err(Errno(ENOSYS)) } + fn current_os_tid() -> pthread::OsTid { + let _pid = unsafe { lowlevel::syscall0(lowlevel::MK_GETPID) }; + pthread::OsTid {} + } + unsafe fn spawn(_program: CStr, _fac: Option<&crate::header::spawn::posix_spawn_file_actions_t>, _fat: Option<&crate::header::spawn::posix_spawnattr_t>, _argv: NulTerminated<*mut c_char>, _envp: Option>) -> Result { Err(Errno(ENOSYS)) } + fn waitpid(_pid: pid_t, _stat_loc: Option>, _options: c_int) -> Result { Err(Errno(ENOSYS)) } + fn sched_yield() -> Result<()> { + unsafe { lowlevel::syscall0(lowlevel::MK_YIELD); } + Ok(()) + } + fn uname(_utsname: Out) -> Result<()> { Err(Errno(ENOSYS)) } + + fn verify() -> bool { + true + } +} diff --git a/toolchain/nonos-relibc/platform/nonos/ptrace.rs b/toolchain/nonos-relibc/platform/nonos/ptrace.rs new file mode 100644 index 0000000000..0d7b682dca --- /dev/null +++ b/toolchain/nonos-relibc/platform/nonos/ptrace.rs @@ -0,0 +1,12 @@ +use super::{ + super::{PalPtrace, types::*}, + Sys, +}; +use crate::{ + error::{Errno, Result}, + header::errno::ENOSYS, +}; + +impl PalPtrace for Sys { + unsafe fn ptrace(_request: c_int, _pid: pid_t, _addr: *mut c_void, _data: *mut c_void) -> Result { Err(Errno(ENOSYS)) } +} diff --git a/toolchain/nonos-relibc/platform/nonos/signal.rs b/toolchain/nonos-relibc/platform/nonos/signal.rs new file mode 100644 index 0000000000..637af9ec78 --- /dev/null +++ b/toolchain/nonos-relibc/platform/nonos/signal.rs @@ -0,0 +1,32 @@ +use super::{ + super::{PalSignal, types::*}, + Sys, +}; +#[allow(deprecated)] +use crate::header::sys_time::itimerval; +use crate::{ + error::{Errno, Result}, + header::{ + bits_sigset_t::sigset_t, + errno::ENOSYS, + signal::{sigaction, siginfo_t, sigval, stack_t}, + time::timespec, + }, +}; + +impl PalSignal for Sys { + #[allow(deprecated)] + fn getitimer(_which: c_int, _out: &mut itimerval) -> Result<()> { Err(Errno(ENOSYS)) } + fn kill(_pid: pid_t, _sig: c_int) -> Result<()> { Err(Errno(ENOSYS)) } + fn sigqueue(_pid: pid_t, _sig: c_int, _val: sigval) -> Result<()> { Err(Errno(ENOSYS)) } + fn killpg(_pgrp: pid_t, _sig: c_int) -> Result<()> { Err(Errno(ENOSYS)) } + fn raise(_sig: c_int) -> Result<()> { Err(Errno(ENOSYS)) } + #[allow(deprecated)] + fn setitimer(_which: c_int, _new: &itimerval, _old: Option<&mut itimerval>) -> Result<()> { Err(Errno(ENOSYS)) } + fn sigaction(_sig: c_int, _act: Option<&sigaction>, _oact: Option<&mut sigaction>) -> Result<()> { Err(Errno(ENOSYS)) } + unsafe fn sigaltstack(_ss: Option<&stack_t>, _old_ss: Option<&mut stack_t>) -> Result<()> { Err(Errno(ENOSYS)) } + fn sigpending(_set: &mut sigset_t) -> Result<()> { Err(Errno(ENOSYS)) } + fn sigprocmask(_how: c_int, _set: Option<&sigset_t>, _oset: Option<&mut sigset_t>) -> Result<()> { Err(Errno(ENOSYS)) } + fn sigsuspend(_mask: &sigset_t) -> Errno { Errno(ENOSYS) } + fn sigtimedwait(_set: &sigset_t, _sig: Option<&mut siginfo_t>, _tp: Option<×pec>) -> Result { Err(Errno(ENOSYS)) } +} diff --git a/toolchain/nonos-relibc/platform/nonos/socket.rs b/toolchain/nonos-relibc/platform/nonos/socket.rs new file mode 100644 index 0000000000..fe7424ee69 --- /dev/null +++ b/toolchain/nonos-relibc/platform/nonos/socket.rs @@ -0,0 +1,139 @@ +use super::{ + super::{PalSocket, types::*}, + Sys, +}; +use crate::{ + error::{Errno, Result}, + header::{ + errno::{EAFNOSUPPORT, EBADF, EINPROGRESS, EINVAL, EMFILE, ENOSYS}, + fcntl::{F_GETFL, F_SETFL, O_NONBLOCK, O_RDWR}, + sys_socket::{msghdr, sockaddr, socklen_t}, + }, +}; + +impl PalSocket for Sys { + unsafe fn accept(_socket: c_int, _address: *mut sockaddr, _len: *mut socklen_t) -> Result { Err(Errno(ENOSYS)) } + unsafe fn bind(_socket: c_int, _address: *const sockaddr, _len: socklen_t) -> Result<()> { Err(Errno(ENOSYS)) } + unsafe fn connect(socket: c_int, address: *const sockaddr, address_len: socklen_t) -> Result { + use super::socket_rt as rt; + if address.is_null() || (address_len as usize) < 16 { + return Err(Errno(EINVAL)); + } + let sa = address as *const u8; + let family = u16::from_ne_bytes([unsafe { *sa }, unsafe { *sa.add(1) }]); + if family != 2 { + return Err(Errno(EAFNOSUPPORT)); + } + let port = u16::from_be_bytes([unsafe { *sa.add(2) }, unsafe { *sa.add(3) }]); + let ip = [unsafe { *sa.add(4) }, unsafe { *sa.add(5) }, unsafe { *sa.add(6) }, unsafe { *sa.add(7) }]; + let handle = rt::handle_of(socket).ok_or(Errno(EBADF))?; + let mut payload = [0u8; 10]; + payload[0..4].copy_from_slice(&handle.to_le_bytes()); + payload[4..8].copy_from_slice(&ip); + payload[8..10].copy_from_slice(&port.to_le_bytes()); + let mut resp = [0u8; 24]; + let (errno, _) = rt::nskt_call(rt::OP_CONNECT, &payload, &mut resp)?; + if errno != 0 { + return Err(Errno(rt::map_errno(errno))); + } + if rt::nonblock_of(socket) { + return Err(Errno(EINPROGRESS)); + } + Ok(0) + } + unsafe fn getpeername(_socket: c_int, _address: *mut sockaddr, _len: *mut socklen_t) -> Result<()> { Err(Errno(ENOSYS)) } + unsafe fn getsockname(_socket: c_int, _address: *mut sockaddr, _len: *mut socklen_t) -> Result<()> { Err(Errno(ENOSYS)) } + unsafe fn getsockopt(_socket: c_int, _level: c_int, _name: c_int, _val: *mut c_void, _len: *mut socklen_t) -> Result<()> { Err(Errno(ENOSYS)) } + fn listen(_socket: c_int, _backlog: c_int) -> Result<()> { Err(Errno(ENOSYS)) } + unsafe fn recvfrom(socket: c_int, buf: *mut c_void, len: size_t, _flags: c_int, _addr: *mut sockaddr, _alen: *mut socklen_t) -> Result { + let slice = unsafe { core::slice::from_raw_parts_mut(buf as *mut u8, len) }; + sock_recv(socket, slice) + } + unsafe fn recvmsg(_socket: c_int, _msg: *mut msghdr, _flags: c_int) -> Result { Err(Errno(ENOSYS)) } + unsafe fn sendmsg(_socket: c_int, _msg: *const msghdr, _flags: c_int) -> Result { Err(Errno(ENOSYS)) } + unsafe fn sendto(socket: c_int, buf: *const c_void, len: size_t, _flags: c_int, _addr: *const sockaddr, _alen: socklen_t) -> Result { + let slice = unsafe { core::slice::from_raw_parts(buf as *const u8, len) }; + sock_send(socket, slice) + } + unsafe fn setsockopt(_socket: c_int, _level: c_int, _name: c_int, _val: *const c_void, _len: socklen_t) -> Result<()> { Err(Errno(ENOSYS)) } + fn shutdown(_socket: c_int, _how: c_int) -> Result<()> { Err(Errno(ENOSYS)) } + unsafe fn socket(domain: c_int, kind: c_int, _protocol: c_int) -> Result { + use super::socket_rt as rt; + if domain != 2 { return Err(Errno(EAFNOSUPPORT)); } + let wire_kind = match kind { + 1 => rt::KIND_STREAM, + 2 => rt::KIND_DGRAM, + _ => return Err(Errno(EINVAL)), + }; + let mut payload = [0u8; 4]; + payload[0..2].copy_from_slice(&rt::FAMILY_INET.to_le_bytes()); + payload[2..4].copy_from_slice(&wire_kind.to_le_bytes()); + let mut resp = [0u8; 24]; + let (errno, n) = rt::nskt_call(rt::OP_SOCKET, &payload, &mut resp)?; + if errno != 0 || n < 4 { return Err(Errno(rt::map_errno(errno))); } + let handle = u32::from_le_bytes([resp[20], resp[21], resp[22], resp[23]]); + rt::alloc(handle).ok_or(Errno(EMFILE)) + } + fn socketpair(_domain: c_int, _kind: c_int, _protocol: c_int, _sv: &mut [c_int; 2]) -> Result<()> { Err(Errno(ENOSYS)) } +} + +pub fn sock_send(fd: c_int, buf: &[u8]) -> Result { + use super::socket_rt as rt; + let handle = rt::handle_of(fd).ok_or(Errno(EBADF))?; + let n = core::cmp::min(buf.len(), 65536); + let mut payload = alloc::vec![0u8; 4 + n]; + payload[0..4].copy_from_slice(&handle.to_le_bytes()); + payload[4..].copy_from_slice(&buf[..n]); + let mut resp = [0u8; 24]; + let (errno, _) = rt::nskt_call(rt::OP_SEND, &payload, &mut resp)?; + if errno != 0 { return Err(Errno(rt::map_errno(errno))); } + Ok(n) +} + +pub fn sock_recv(fd: c_int, buf: &mut [u8]) -> Result { + use super::socket_rt as rt; + let handle = rt::handle_of(fd).ok_or(Errno(EBADF))?; + let payload = handle.to_le_bytes(); + let mut resp = alloc::vec![0u8; 24 + buf.len()]; + let (errno, n) = rt::nskt_call(rt::OP_RECV, &payload, &mut resp)?; + if errno != 0 { return Err(Errno(rt::map_errno(errno))); } + let n = core::cmp::min(n, buf.len()); + buf[..n].copy_from_slice(&resp[20..20 + n]); + Ok(n) +} + +pub fn close_fd(fd: c_int) -> Result<()> { + use super::socket_rt as rt; + let handle = rt::handle_of(fd).ok_or(Errno(EBADF))?; + let mut payload = [0u8; 4]; + payload[0..4].copy_from_slice(&handle.to_le_bytes()); + let mut resp = [0u8; 24]; + let (errno, _) = rt::nskt_call(rt::OP_CLOSE, &payload, &mut resp)?; + rt::free(fd); + if errno != 0 { return Err(Errno(rt::map_errno(errno))); } + Ok(()) +} + +pub fn fcntl_sock(fd: c_int, cmd: c_int, arg: c_ulonglong) -> Result { + use super::socket_rt as rt; + match cmd { + F_GETFL => { + let mut fl = O_RDWR; + if rt::nonblock_of(fd) { fl |= O_NONBLOCK; } + Ok(fl) + } + F_SETFL => { + let on = (arg as c_int & O_NONBLOCK) != 0; + let handle = rt::handle_of(fd).ok_or(Errno(EBADF))?; + rt::set_nonblock(fd, on); + let mut payload = [0u8; 8]; + payload[0..4].copy_from_slice(&handle.to_le_bytes()); + payload[4..8].copy_from_slice(&(if on { 1u32 } else { 0u32 }).to_le_bytes()); + let mut resp = [0u8; 24]; + let (errno, _) = rt::nskt_call(rt::OP_SETFLAGS, &payload, &mut resp)?; + if errno != 0 { return Err(Errno(rt::map_errno(errno))); } + Ok(0) + } + _ => Err(Errno(ENOSYS)), + } +} diff --git a/toolchain/nonos-relibc/platform/nonos/socket_rt.rs b/toolchain/nonos-relibc/platform/nonos/socket_rt.rs new file mode 100644 index 0000000000..420a2d33c8 --- /dev/null +++ b/toolchain/nonos-relibc/platform/nonos/socket_rt.rs @@ -0,0 +1,97 @@ +use alloc::vec::Vec; +use core::cell::SyncUnsafeCell; + +use crate::error::{Errno, Result}; +use crate::header::errno::{EAFNOSUPPORT, EAGAIN, EBADF, ECONNREFUSED, EINVAL, EIO, EMFILE, ENOTCONN}; +use super::super::types::c_int; +use super::lowlevel::{syscall6, MK_IPC_CALL}; + +pub const NSKT_ENDPOINT: u64 = 4460; +pub const NSKT_MAGIC: u32 = 0x4E53_4B54; +pub const HDR_LEN: usize = 20; + +pub const OP_SOCKET: u16 = 2; +pub const OP_CONNECT: u16 = 6; +pub const OP_SEND: u16 = 7; +pub const OP_RECV: u16 = 8; +pub const OP_CLOSE: u16 = 9; +pub const OP_POLL: u16 = 12; +pub const OP_SETFLAGS: u16 = 13; +pub const OP_SETTIMEOUT: u16 = 15; +pub const E_WOULD_BLOCK: u16 = 11; + +pub const FAMILY_INET: u16 = 4; +pub const KIND_STREAM: u16 = 1; +pub const KIND_DGRAM: u16 = 2; + +const SOCK_FD_BASE: c_int = 256; +const SOCK_SLOTS: usize = 64; + +#[derive(Clone, Copy)] +struct SockEntry { handle: u32, nonblock: bool, used: bool } + +static SOCK_TABLE: SyncUnsafeCell<[SockEntry; SOCK_SLOTS]> = + SyncUnsafeCell::new([SockEntry { handle: 0, nonblock: false, used: false }; SOCK_SLOTS]); + +fn table() -> &'static mut [SockEntry; SOCK_SLOTS] { unsafe { &mut *SOCK_TABLE.get() } } + +pub fn is_socket_fd(fd: c_int) -> bool { + fd >= SOCK_FD_BASE && (fd - SOCK_FD_BASE) < SOCK_SLOTS as c_int +} +fn slot(fd: c_int) -> Option { + if is_socket_fd(fd) { Some((fd - SOCK_FD_BASE) as usize) } else { None } +} +pub fn handle_of(fd: c_int) -> Option { + let e = table()[slot(fd)?]; + if e.used { Some(e.handle) } else { None } +} +pub fn alloc(handle: u32) -> Option { + for (i, e) in table().iter_mut().enumerate() { + if !e.used { + *e = SockEntry { handle, nonblock: false, used: true }; + return Some(SOCK_FD_BASE + i as c_int); + } + } + None +} +pub fn free(fd: c_int) { + if let Some(i) = slot(fd) { table()[i].used = false; } +} +pub fn nonblock_of(fd: c_int) -> bool { + slot(fd).map(|i| table()[i].nonblock).unwrap_or(false) +} +pub fn set_nonblock(fd: c_int, on: bool) { + if let Some(i) = slot(fd) { table()[i].nonblock = on; } +} + +pub fn map_errno(e: u16) -> c_int { + match e { + 5 => EBADF, + 6 => ECONNREFUSED, + 7 => EMFILE, + 8 => EAFNOSUPPORT, + 9 => EINVAL, + 11 => EAGAIN, + 12 => ENOTCONN, + _ => EIO, + } +} + +pub fn nskt_call(op: u16, payload: &[u8], resp: &mut [u8]) -> Result<(u16, usize)> { + let mut req = Vec::with_capacity(HDR_LEN + payload.len()); + req.extend_from_slice(&NSKT_MAGIC.to_le_bytes()); + req.extend_from_slice(&1u16.to_le_bytes()); + req.extend_from_slice(&op.to_le_bytes()); + req.extend_from_slice(&[0u8; 4]); + req.extend_from_slice(&1u32.to_le_bytes()); + req.extend_from_slice(&(payload.len() as u32).to_le_bytes()); + req.extend_from_slice(payload); + let ret = unsafe { + syscall6(MK_IPC_CALL, NSKT_ENDPOINT, req.as_ptr() as u64, req.len() as u64, + resp.as_mut_ptr() as u64, resp.len() as u64, 0) + }; + if ret < HDR_LEN as i64 { return Err(Errno(EIO)); } + let errno = u16::from_le_bytes([resp[8], resp[9]]); + let len = u32::from_le_bytes([resp[16], resp[17], resp[18], resp[19]]) as usize; + Ok((errno, len.min(ret as usize - HDR_LEN))) +} diff --git a/userland/capsule_c_net/Capsule.mk b/userland/capsule_c_net/Capsule.mk new file mode 100644 index 0000000000..1b146a4d44 --- /dev/null +++ b/userland/capsule_c_net/Capsule.mk @@ -0,0 +1,17 @@ +CAPSULE_SLUG := c-net +CAPSULE_HANDLE := c_net +CAPSULE_DOMAIN := systems.nonos +CAPSULE_DIR := userland/capsule_c_net +CAPSULE_BIN_NAME := c_net +CAPSULE_FEATURE := nonos-capsule-c-net +CAPSULE_NAMESPACE := systems.nonos.c_net +CAPSULE_SERVICE_ENDPOINT := service:4508:c_net +CAPSULE_REPLY_ENDPOINT := reply:4509:endpoint.c_net.reply +# CoreExec | IPC | Memory | Debug = 0x01 | 0x08 | 0x10 | 0x100 = 0x119 +# Debug is required for the write(1,...) -> MkDebug serial marker. +CAPSULE_REQUIRED_CAPS := 0x119 +CAPSULE_KERNEL_MIRROR := src/userspace/capsule_c_net +CAPSULE_PREBUILT_BIN := userland/capsule_c_net/build/c_net + +include nonos-mk/capsule-c.mk +include nonos-mk/capsule.mk diff --git a/userland/capsule_c_net/src/main.c b/userland/capsule_c_net/src/main.c new file mode 100644 index 0000000000..60adb72b70 --- /dev/null +++ b/userland/capsule_c_net/src/main.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define EMIT(s) write(1, s, sizeof(s) - 1) + +int main(void) { + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { EMIT("[C-NET] FAIL socket\n"); return 1; } + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) { EMIT("[C-NET] FAIL fcntl\n"); return 1; } + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(7); + addr.sin_addr.s_addr = htonl(0x0A0002C8); + + int rc = -1; + for (int attempt = 0; attempt < 60; attempt++) { + rc = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (rc == 0 || errno == EINPROGRESS) break; + poll(NULL, 0, 500); + } + if (rc < 0 && errno != EINPROGRESS) { EMIT("[C-NET] FAIL connect\n"); return 1; } + + struct pollfd pfd; + pfd.fd = fd; pfd.events = POLLOUT; pfd.revents = 0; + if (poll(&pfd, 1, 4000) <= 0 || !(pfd.revents & POLLOUT)) { EMIT("[C-NET] FAIL connect-wait\n"); return 1; } + + if (write(fd, "ping", 4) != 4) { EMIT("[C-NET] FAIL send\n"); return 1; } + + pfd.events = POLLIN; pfd.revents = 0; + if (poll(&pfd, 1, 4000) <= 0 || !(pfd.revents & POLLIN)) { EMIT("[C-NET] FAIL recv-wait\n"); return 1; } + + char buf[8]; + for (int i = 0; i < 8; i++) buf[i] = 0; + long n = read(fd, buf, 4); + if (n != 4 || memcmp(buf, "ping", 4) != 0) { EMIT("[C-NET] FAIL echo\n"); return 1; } + + close(fd); + EMIT("[C-NET] PASS\n"); + return 0; +} diff --git a/userland/capsule_c_proof/.gitignore b/userland/capsule_c_proof/.gitignore new file mode 100644 index 0000000000..567609b123 --- /dev/null +++ b/userland/capsule_c_proof/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/userland/capsule_c_proof/Capsule.mk b/userland/capsule_c_proof/Capsule.mk new file mode 100644 index 0000000000..e65514c0cf --- /dev/null +++ b/userland/capsule_c_proof/Capsule.mk @@ -0,0 +1,17 @@ +CAPSULE_SLUG := c-proof +CAPSULE_HANDLE := c_proof +CAPSULE_DOMAIN := systems.nonos +CAPSULE_DIR := userland/capsule_c_proof +CAPSULE_BIN_NAME := c_proof +CAPSULE_FEATURE := nonos-capsule-c-proof +CAPSULE_NAMESPACE := systems.nonos.c_proof +CAPSULE_SERVICE_ENDPOINT := service:4504:c_proof +CAPSULE_REPLY_ENDPOINT := reply:4505:endpoint.c_proof.reply +# CoreExec | IPC | Memory | Debug = 0x01 | 0x08 | 0x10 | 0x100 = 0x119 +# Debug is required for the write(1,...) -> MkDebug serial marker. +CAPSULE_REQUIRED_CAPS := 0x119 +CAPSULE_KERNEL_MIRROR := src/userspace/capsule_c_proof +CAPSULE_PREBUILT_BIN := userland/capsule_c_proof/build/c_proof + +include nonos-mk/capsule-c.mk +include nonos-mk/capsule.mk diff --git a/userland/capsule_c_proof/src/main.c b/userland/capsule_c_proof/src/main.c new file mode 100644 index 0000000000..ebca3afd44 --- /dev/null +++ b/userland/capsule_c_proof/src/main.c @@ -0,0 +1,6 @@ +#include +static const char PASS[] = "[C-PROOF] PASS\n"; +int main(void) { + write(1, PASS, sizeof(PASS) - 1); + return 0; +} diff --git a/userland/capsule_net_sockets/src/clients/tcp.rs b/userland/capsule_net_sockets/src/clients/tcp.rs index 3cd7bfc048..e1c34f3d1d 100644 --- a/userland/capsule_net_sockets/src/clients/tcp.rs +++ b/userland/capsule_net_sockets/src/clients/tcp.rs @@ -18,6 +18,8 @@ use alloc::vec; use super::envelope::call; +pub const RX_EMPTY: u16 = 11; + const MAGIC: u32 = 0x4E54_4350; const LISTEN: u16 = 2; const CONNECT: u16 = 3; @@ -25,6 +27,9 @@ const ACCEPT: u16 = 4; const SEND: u16 = 5; const RECV: u16 = 6; const CLOSE: u16 = 7; +const STATE: u16 = 9; + +pub const ESTABLISHED: u8 = 3; pub fn listen(port: u32, local: u16) -> Result { call_handle(port, LISTEN, &local.to_le_bytes()) @@ -56,6 +61,14 @@ pub fn close(port: u32, handle: u32) -> Result<(), u16> { call(port, MAGIC, CLOSE, &handle.to_le_bytes(), &mut []).map(|_| ()) } +pub fn state(port: u32, handle: u32) -> Result { + let mut out = [0u8; 1]; + if call(port, MAGIC, STATE, &handle.to_le_bytes(), &mut out)? != 1 { + return Err(4); + } + Ok(out[0]) +} + fn call_handle(port: u32, op: u16, body: &[u8]) -> Result { let mut out = [0u8; 4]; if call(port, MAGIC, op, body, &mut out)? != 4 { diff --git a/userland/capsule_net_sockets/src/clients/udp.rs b/userland/capsule_net_sockets/src/clients/udp.rs index f9a72cf239..a9a63d5188 100644 --- a/userland/capsule_net_sockets/src/clients/udp.rs +++ b/userland/capsule_net_sockets/src/clients/udp.rs @@ -18,6 +18,8 @@ use alloc::vec; use super::envelope::call; +pub const RX_EMPTY: u16 = 8; + const MAGIC: u32 = 0x4E55_4450; const BIND: u16 = 2; const UNBIND: u16 = 3; diff --git a/userland/capsule_net_sockets/src/protocol/errno.rs b/userland/capsule_net_sockets/src/protocol/errno.rs index 249aaf907a..359ef70a90 100644 --- a/userland/capsule_net_sockets/src/protocol/errno.rs +++ b/userland/capsule_net_sockets/src/protocol/errno.rs @@ -25,4 +25,5 @@ pub const E_TABLE_FULL: u16 = 7; pub const E_BAD_FAMILY: u16 = 8; pub const E_BAD_KIND: u16 = 9; pub const E_NOT_BOUND: u16 = 10; +pub const E_WOULD_BLOCK: u16 = 11; pub const E_NOT_CONNECTED: u16 = 12; diff --git a/userland/capsule_net_sockets/src/protocol/mod.rs b/userland/capsule_net_sockets/src/protocol/mod.rs index cdf25ff374..57f6c26ce9 100644 --- a/userland/capsule_net_sockets/src/protocol/mod.rs +++ b/userland/capsule_net_sockets/src/protocol/mod.rs @@ -20,10 +20,11 @@ mod ops; pub use errno::{ E_BAD_FAMILY, E_BAD_KIND, E_BAD_LEN, E_BAD_MAGIC, E_BAD_OP, E_BAD_VERSION, E_NOT_BOUND, - E_NOT_CONNECTED, E_NO_HANDLE, E_NO_TRANSPORT, E_OK, E_TABLE_FULL, + E_NOT_CONNECTED, E_NO_HANDLE, E_NO_TRANSPORT, E_OK, E_TABLE_FULL, E_WOULD_BLOCK, }; pub use header::MAGIC; pub use ops::{ - OP_ACCEPT, OP_BIND, OP_CLOSE, OP_CONNECT, OP_GETSOCKOPT, OP_HEALTHCHECK, OP_LISTEN, OP_RECV, - OP_SEND, OP_SETSOCKOPT, OP_SOCKET, + FLAG_NONBLOCK, OP_ACCEPT, OP_BIND, OP_CLOSE, OP_CONNECT, OP_GETFLAGS, OP_GETSOCKOPT, + OP_HEALTHCHECK, OP_LISTEN, OP_POLL, OP_RECV, OP_SEND, OP_SETFLAGS, OP_SETSOCKOPT, + OP_SETTIMEOUT, OP_SOCKET, POLLIN, POLLOUT, }; diff --git a/userland/capsule_net_sockets/src/protocol/ops.rs b/userland/capsule_net_sockets/src/protocol/ops.rs index 1e683c4d4f..912e71c853 100644 --- a/userland/capsule_net_sockets/src/protocol/ops.rs +++ b/userland/capsule_net_sockets/src/protocol/ops.rs @@ -25,3 +25,10 @@ pub const OP_RECV: u16 = 8; pub const OP_CLOSE: u16 = 9; pub const OP_GETSOCKOPT: u16 = 10; pub const OP_SETSOCKOPT: u16 = 11; +pub const OP_POLL: u16 = 12; +pub const OP_SETFLAGS: u16 = 13; +pub const OP_GETFLAGS: u16 = 14; +pub const OP_SETTIMEOUT: u16 = 15; +pub const FLAG_NONBLOCK: u32 = 1; +pub const POLLIN: u16 = 1; +pub const POLLOUT: u16 = 2; diff --git a/userland/capsule_net_sockets/src/server/handlers/close.rs b/userland/capsule_net_sockets/src/server/handlers/close.rs index 3deb8258bf..6feb9fc3c7 100644 --- a/userland/capsule_net_sockets/src/server/handlers/close.rs +++ b/userland/capsule_net_sockets/src/server/handlers/close.rs @@ -19,7 +19,7 @@ use crate::protocol::{E_NO_HANDLE, E_NO_TRANSPORT, E_OK, OP_CLOSE}; use crate::server::handlers::io::u32_at; use crate::server::parse_req::Request; use crate::server::respond::respond; -use crate::sockets::{Kind, SocketKey, SOCKETS}; +use crate::sockets::{stash, Kind, SocketKey, SOCKETS}; use crate::state; pub fn handle(pid: u32, req: &Request, body: &[u8], tx: &mut [u8]) { @@ -51,6 +51,7 @@ pub fn handle(pid: u32, req: &Request, body: &[u8], tx: &mut [u8]) { if !SOCKETS.close(key) { return status(pid, req, E_NO_HANDLE, tx); } + stash::clear(pid, handle); status(pid, req, E_OK, tx); } diff --git a/userland/capsule_net_sockets/src/server/handlers/dispatch.rs b/userland/capsule_net_sockets/src/server/handlers/dispatch.rs index d4b2e87a91..c9bba75430 100644 --- a/userland/capsule_net_sockets/src/server/handlers/dispatch.rs +++ b/userland/capsule_net_sockets/src/server/handlers/dispatch.rs @@ -15,7 +15,8 @@ // along with this program. If not, see . use super::{ - accept, bind, close, connect, getsockopt, health, listen, recv, send, setsockopt, socket, + accept, bind, close, connect, getflags, getsockopt, health, listen, poll, recv, send, setflags, + setsockopt, settimeout, socket, }; use crate::protocol::*; use crate::server::parse_req::Request; @@ -33,6 +34,10 @@ pub fn dispatch(pid: u32, req: &Request, body: &[u8], tx: &mut [u8]) -> bool { OP_CLOSE => close::handle(pid, req, body, tx), OP_GETSOCKOPT => getsockopt::handle(pid, req, body, tx), OP_SETSOCKOPT => setsockopt::handle(pid, req, body, tx), + OP_POLL => poll::handle(pid, req, body, tx), + OP_SETFLAGS => setflags::handle(pid, req, body, tx), + OP_GETFLAGS => getflags::handle(pid, req, body, tx), + OP_SETTIMEOUT => settimeout::handle(pid, req, body, tx), _ => return false, } true diff --git a/userland/capsule_net_sockets/src/server/handlers/getflags.rs b/userland/capsule_net_sockets/src/server/handlers/getflags.rs new file mode 100644 index 0000000000..6571257ddc --- /dev/null +++ b/userland/capsule_net_sockets/src/server/handlers/getflags.rs @@ -0,0 +1,41 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::protocol::{E_NO_HANDLE, E_OK, FLAG_NONBLOCK, OP_GETFLAGS}; +use crate::server::handlers::io::u32_at; +use crate::server::parse_req::Request; +use crate::server::respond::respond; +use crate::sockets::{SocketKey, SOCKETS}; + +pub fn handle(pid: u32, req: &Request, body: &[u8], tx: &mut [u8]) { + let handle = match u32_at(body, 0) { + Ok(h) => h, + Err(e) => { + let _ = respond(pid, OP_GETFLAGS, e, req.request_id, 0, tx); + return; + } + }; + let key = SocketKey { pid, handle }; + match SOCKETS.with(key, |s| if s.nonblock { FLAG_NONBLOCK } else { 0 }) { + Some(flags) => { + tx[20..24].copy_from_slice(&flags.to_le_bytes()); + let _ = respond(pid, OP_GETFLAGS, E_OK, req.request_id, 4, tx); + } + None => { + let _ = respond(pid, OP_GETFLAGS, E_NO_HANDLE, req.request_id, 0, tx); + } + } +} diff --git a/userland/capsule_net_sockets/src/server/handlers/mod.rs b/userland/capsule_net_sockets/src/server/handlers/mod.rs index e8656dd545..5aa8ab7a29 100644 --- a/userland/capsule_net_sockets/src/server/handlers/mod.rs +++ b/userland/capsule_net_sockets/src/server/handlers/mod.rs @@ -19,13 +19,17 @@ mod bind; mod close; mod connect; mod dispatch; +mod getflags; mod getsockopt; mod health; mod io; mod listen; +mod poll; mod recv; mod send; +mod setflags; mod setsockopt; +mod settimeout; mod socket; pub use dispatch::dispatch; diff --git a/userland/capsule_net_sockets/src/server/handlers/poll.rs b/userland/capsule_net_sockets/src/server/handlers/poll.rs new file mode 100644 index 0000000000..15c1eb3deb --- /dev/null +++ b/userland/capsule_net_sockets/src/server/handlers/poll.rs @@ -0,0 +1,98 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use nonos_libc::{mk_time_millis, mk_yield}; + +use crate::clients::{tcp, udp}; +use crate::protocol::{E_NO_HANDLE, E_OK, OP_POLL, POLLIN, POLLOUT}; +use crate::server::handlers::io::{u16_at, u32_at}; +use crate::server::parse_req::Request; +use crate::server::respond::respond; +use crate::sockets::{stash, Kind, Socket, SocketKey, SOCKETS}; +use crate::state; + +pub fn handle(pid: u32, req: &Request, body: &[u8], tx: &mut [u8]) { + let (handle, events, timeout_ms) = match parse(body) { + Ok(v) => v, + Err(e) => return reply_err(pid, req, e, tx), + }; + let Some(sock) = SOCKETS.with(SocketKey { pid, handle }, |s| *s) else { + return reply_err(pid, req, E_NO_HANDLE, tx); + }; + let deadline = mk_time_millis().saturating_add(timeout_ms as i64); + loop { + let revents = poll_once(pid, handle, sock, events); + if revents != 0 || !(timeout_ms > 0 && mk_time_millis() < deadline) { + tx[20..22].copy_from_slice(&revents.to_le_bytes()); + let _ = respond(pid, OP_POLL, E_OK, req.request_id, 2, tx); + return; + } + mk_yield(); + } +} + +fn parse(body: &[u8]) -> Result<(u32, u16, u32), u16> { + Ok((u32_at(body, 0)?, u16_at(body, 4)?, u32_at(body, 8)?)) +} + +fn poll_once(pid: u32, handle: u32, sock: Socket, events: u16) -> u16 { + let mut revents = 0; + if events & POLLIN != 0 && pollin_ready(pid, handle, sock) { + revents |= POLLIN; + } + if events & POLLOUT != 0 && pollout_ready(sock) { + revents |= POLLOUT; + } + revents +} + +fn pollin_ready(pid: u32, handle: u32, sock: Socket) -> bool { + if stash::has(pid, handle) { + return true; + } + let mut buf = [0u8; 2048]; + let got = match sock.kind { + Kind::Stream if sock.transport_handle != 0 => { + tcp::recv(state::tcp(), sock.transport_handle, &mut buf) + } + Kind::Datagram => match sock.local { + Some(local) => udp::recv(state::udp(), local.port, &mut buf), + None => return false, + }, + _ => return false, + }; + match got { + Ok(n) if n > 0 => { + stash::put(pid, handle, &buf[..n]); + true + } + _ => false, + } +} + +fn pollout_ready(sock: Socket) -> bool { + match sock.kind { + Kind::Stream if sock.transport_handle != 0 => tcp::state(state::tcp(), sock.transport_handle) + .map(|s| s == tcp::ESTABLISHED) + .unwrap_or(false), + Kind::Datagram => true, + _ => false, + } +} + +fn reply_err(pid: u32, req: &Request, errno: u16, tx: &mut [u8]) { + let _ = respond(pid, OP_POLL, errno, req.request_id, 0, tx); +} diff --git a/userland/capsule_net_sockets/src/server/handlers/recv.rs b/userland/capsule_net_sockets/src/server/handlers/recv.rs index 284fdf9aa6..9533723bca 100644 --- a/userland/capsule_net_sockets/src/server/handlers/recv.rs +++ b/userland/capsule_net_sockets/src/server/handlers/recv.rs @@ -14,8 +14,10 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +use nonos_libc::{mk_time_millis, mk_yield}; + use crate::clients::{nym, tcp, udp}; -use crate::protocol::{E_NOT_CONNECTED, E_NO_HANDLE, E_NO_TRANSPORT, E_OK, OP_RECV}; +use crate::protocol::{E_NOT_CONNECTED, E_NO_HANDLE, E_NO_TRANSPORT, E_OK, E_WOULD_BLOCK, OP_RECV}; use crate::server::handlers::io::u32_at; use crate::server::parse_req::Request; use crate::server::respond::respond; @@ -31,22 +33,40 @@ pub fn handle(pid: u32, req: &Request, body: &[u8], tx: &mut [u8]) { let Some(sock) = SOCKETS.with(key, |s| *s) else { return status(pid, req, E_NO_HANDLE, tx); }; - match recv_socket(sock, &mut tx[20..]) { - Ok(n) => { - respond(pid, OP_RECV, E_OK, req.request_id, n as u32, tx); + let buffered = crate::sockets::stash::take(pid, handle, &mut tx[20..]); + if buffered > 0 { + let _ = respond(pid, OP_RECV, E_OK, req.request_id, buffered as u32, tx); + return; + } + let deadline = mk_time_millis().saturating_add(sock.timeout_ms as i64); + loop { + match recv_socket(sock, &mut tx[20..]) { + Ok(n) => { + let _ = respond(pid, OP_RECV, E_OK, req.request_id, n as u32, tx); + return; + } + Err(E_WOULD_BLOCK) if sock.timeout_ms > 0 && mk_time_millis() < deadline => { + mk_yield(); + } + Err(e) => return status(pid, req, e, tx), } - Err(e) => status(pid, req, e, tx), } } +fn map_recv_err(e: u16, empty: u16) -> u16 { + if e == empty { E_WOULD_BLOCK } else { E_NO_TRANSPORT } +} + fn recv_socket(sock: Socket, out: &mut [u8]) -> Result { match sock.kind { Kind::Stream if sock.transport_handle != 0 => { - tcp::recv(state::tcp(), sock.transport_handle, out).map_err(|_| E_NO_TRANSPORT) + tcp::recv(state::tcp(), sock.transport_handle, out) + .map_err(|e| map_recv_err(e, tcp::RX_EMPTY)) } Kind::Datagram => { let Some(local) = sock.local else { return Err(E_NOT_CONNECTED) }; - udp::recv(state::udp(), local.port, out).map_err(|_| E_NO_TRANSPORT) + udp::recv(state::udp(), local.port, out) + .map_err(|e| map_recv_err(e, udp::RX_EMPTY)) } Kind::Mixnet if sock.transport_handle != 0 => { nym::recv(state::nym(), sock.transport_handle, out).map_err(|_| E_NO_TRANSPORT) diff --git a/userland/capsule_net_sockets/src/server/handlers/setflags.rs b/userland/capsule_net_sockets/src/server/handlers/setflags.rs new file mode 100644 index 0000000000..915acc48a8 --- /dev/null +++ b/userland/capsule_net_sockets/src/server/handlers/setflags.rs @@ -0,0 +1,43 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::protocol::{E_NO_HANDLE, E_OK, FLAG_NONBLOCK, OP_SETFLAGS}; +use crate::server::handlers::io::u32_at; +use crate::server::parse_req::Request; +use crate::server::respond::respond; +use crate::sockets::{SocketKey, SOCKETS}; + +pub fn handle(pid: u32, req: &Request, body: &[u8], tx: &mut [u8]) { + let handle = match u32_at(body, 0) { + Ok(h) => h, + Err(e) => { + let _ = respond(pid, OP_SETFLAGS, e, req.request_id, 0, tx); + return; + } + }; + let flags = match u32_at(body, 4) { + Ok(f) => f, + Err(e) => { + let _ = respond(pid, OP_SETFLAGS, e, req.request_id, 0, tx); + return; + } + }; + let key = SocketKey { pid, handle }; + let errno = SOCKETS + .with(key, |s| s.nonblock = (flags & FLAG_NONBLOCK) != 0) + .map_or(E_NO_HANDLE, |_| E_OK); + let _ = respond(pid, OP_SETFLAGS, errno, req.request_id, 0, tx); +} diff --git a/userland/capsule_net_sockets/src/server/handlers/settimeout.rs b/userland/capsule_net_sockets/src/server/handlers/settimeout.rs new file mode 100644 index 0000000000..1a0395c9a8 --- /dev/null +++ b/userland/capsule_net_sockets/src/server/handlers/settimeout.rs @@ -0,0 +1,42 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::protocol::{E_NO_HANDLE, E_OK, OP_SETTIMEOUT}; +use crate::server::handlers::io::u32_at; +use crate::server::parse_req::Request; +use crate::server::respond::respond; +use crate::sockets::{SocketKey, SOCKETS}; + +pub fn handle(pid: u32, req: &Request, body: &[u8], tx: &mut [u8]) { + let handle = match u32_at(body, 0) { + Ok(h) => h, + Err(e) => { + let _ = respond(pid, OP_SETTIMEOUT, e, req.request_id, 0, tx); + return; + } + }; + let ms = match u32_at(body, 4) { + Ok(v) => v, + Err(e) => { + let _ = respond(pid, OP_SETTIMEOUT, e, req.request_id, 0, tx); + return; + } + }; + let errno = SOCKETS + .with(SocketKey { pid, handle }, |s| s.timeout_ms = ms) + .map_or(E_NO_HANDLE, |_| E_OK); + let _ = respond(pid, OP_SETTIMEOUT, errno, req.request_id, 0, tx); +} diff --git a/userland/capsule_net_sockets/src/sockets/mod.rs b/userland/capsule_net_sockets/src/sockets/mod.rs index 96553c288b..0605faeb9b 100644 --- a/userland/capsule_net_sockets/src/sockets/mod.rs +++ b/userland/capsule_net_sockets/src/sockets/mod.rs @@ -16,6 +16,7 @@ mod table; mod types; +pub mod stash; pub use table::{Socket, SOCKETS}; pub use types::{Kind, LocalAddr4, RemoteAddr4, SocketKey}; diff --git a/userland/capsule_net_sockets/src/sockets/stash.rs b/userland/capsule_net_sockets/src/sockets/stash.rs new file mode 100644 index 0000000000..0c6cd010b2 --- /dev/null +++ b/userland/capsule_net_sockets/src/sockets/stash.rs @@ -0,0 +1,51 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use alloc::collections::BTreeMap; +use alloc::vec::Vec; + +use spin::Mutex; + +static STASH: Mutex>> = Mutex::new(BTreeMap::new()); + +pub fn take(pid: u32, handle: u32, out: &mut [u8]) -> usize { + let mut g = STASH.lock(); + let Some(buf) = g.get_mut(&(pid, handle)) else { + return 0; + }; + let n = core::cmp::min(out.len(), buf.len()); + out[..n].copy_from_slice(&buf[..n]); + buf.drain(..n); + if buf.is_empty() { + g.remove(&(pid, handle)); + } + n +} + +pub fn put(pid: u32, handle: u32, bytes: &[u8]) { + if bytes.is_empty() { + return; + } + STASH.lock().entry((pid, handle)).or_default().extend_from_slice(bytes); +} + +pub fn has(pid: u32, handle: u32) -> bool { + STASH.lock().get(&(pid, handle)).is_some_and(|b| !b.is_empty()) +} + +pub fn clear(pid: u32, handle: u32) { + STASH.lock().remove(&(pid, handle)); +} diff --git a/userland/capsule_net_sockets/src/sockets/table/open.rs b/userland/capsule_net_sockets/src/sockets/table/open.rs index 39f17d95a0..1bbe9169c2 100644 --- a/userland/capsule_net_sockets/src/sockets/table/open.rs +++ b/userland/capsule_net_sockets/src/sockets/table/open.rs @@ -45,6 +45,8 @@ impl Socket { transport_handle: 0, bound: false, listening: false, + nonblock: false, + timeout_ms: 0, } } } diff --git a/userland/capsule_net_sockets/src/sockets/table/types.rs b/userland/capsule_net_sockets/src/sockets/table/types.rs index 54edac00f5..686c5e9d06 100644 --- a/userland/capsule_net_sockets/src/sockets/table/types.rs +++ b/userland/capsule_net_sockets/src/sockets/table/types.rs @@ -30,6 +30,8 @@ pub struct Socket { pub transport_handle: u32, pub bound: bool, pub listening: bool, + pub nonblock: bool, + pub timeout_ms: u32, } pub struct Table { diff --git a/userland/capsule_nsfb_probe/.gitignore b/userland/capsule_nsfb_probe/.gitignore new file mode 100644 index 0000000000..567609b123 --- /dev/null +++ b/userland/capsule_nsfb_probe/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/userland/capsule_nsfb_probe/Capsule.mk b/userland/capsule_nsfb_probe/Capsule.mk new file mode 100644 index 0000000000..c505b2f728 --- /dev/null +++ b/userland/capsule_nsfb_probe/Capsule.mk @@ -0,0 +1,19 @@ +CAPSULE_SLUG := nsfb-probe +CAPSULE_HANDLE := nsfb_probe +CAPSULE_DOMAIN := systems.nonos +CAPSULE_DIR := userland/capsule_nsfb_probe +CAPSULE_BIN_NAME := nsfb_probe +CAPSULE_FEATURE := nonos-capsule-nsfb-probe +CAPSULE_NAMESPACE := systems.nonos.nsfb_probe +CAPSULE_SERVICE_ENDPOINT := service:4512:nsfb_probe +CAPSULE_REPLY_ENDPOINT := reply:4513:endpoint.nsfb_probe.reply +# CoreExec | IPC | Memory | Debug | GraphicsSurfaceCreate +# = 0x01 | 0x08 | 0x10 | 0x100 | 0x1000 = 0x1119 +# Debug(0x100) carries the write(1,...) -> MkDebug serial marker; +# GraphicsSurfaceCreate(0x1000) authorizes surface register/share/release. +CAPSULE_REQUIRED_CAPS := 0x1119 +CAPSULE_KERNEL_MIRROR := src/userspace/capsule_nsfb_probe +CAPSULE_PREBUILT_BIN := userland/capsule_nsfb_probe/build/nsfb_probe + +include nonos-mk/capsule-c.mk +include nonos-mk/capsule.mk diff --git a/userland/capsule_nsfb_probe/src/main.c b/userland/capsule_nsfb_probe/src/main.c new file mode 100644 index 0000000000..cc1676e161 --- /dev/null +++ b/userland/capsule_nsfb_probe/src/main.c @@ -0,0 +1,88 @@ +#include "nonos_marker.h" +#include "nonos_surface.h" +#include "nonos_scene.h" + +#define SCENE_Z 1000u +#define ACQUIRE_ATTEMPTS 60u +#define ACQUIRE_YIELDS 256u + +static void hang(void) { + for (;;) + nonos_yield(); +} + +static void acquire_delay(void) { + for (u32 i = 0; i < ACQUIRE_YIELDS; i++) + nonos_yield(); +} + +static u32 acquire_compositor(struct display_info *di, i64 *last_rc) { + *last_rc = 0; + for (u32 attempt = 0; attempt < ACQUIRE_ATTEMPTS; attempt++) { + u32 port = nonos_lookup_port("compositor", 10); + if (port) { + i64 rc = ncmp_display_info(port, 1, di); + if (rc == 0) + return port; + *last_rc = rc; + } + acquire_delay(); + } + return 0; +} + +static void paint_gradient(u64 base, u32 w, u32 h, u32 stride) { + volatile u32 *px = (volatile u32 *)base; + u32 spx = stride / 4u; + for (u32 y = 0; y < h; y++) { + for (u32 x = 0; x < w; x++) { + u32 r = (x * 255u) / w; + u32 g = (y * 255u) / h; + px[(u64)y * spx + x] = 0xFF000000u | (r << 16) | (g << 8); + } + } +} + +int main(void) { + struct display_info di; + i64 rc = 0; + u32 port = acquire_compositor(&di, &rc); + if (!port) { + if (rc == 0) + emit_fail("lookup", 0); + else + emit_fail("display_info", rc); + hang(); + } + + u64 byte_len = (u64)di.stride * (u64)di.height; + u64 base = nonos_mmap(byte_len); + if (!base || (base & 0xFFFu)) { emit_fail("mmap", (i64)base); hang(); } + + struct surface_descriptor desc; + desc.width = di.width; + desc.height = di.height; + desc.stride = di.stride; + desc.format = SURFACE_FORMAT_ARGB8888; + desc.byte_len = byte_len; + desc.base_va = base; + desc.flags = 0; + + i64 sid = nonos_surface_register(&desc); + if (sid < 0) { emit_fail("register", sid); hang(); } + + i64 handle = nonos_surface_share((u64)sid); + if (handle <= 0) { emit_fail("share", handle); hang(); } + + paint_gradient(base, di.width, di.height, di.stride); + + rc = ncmp_scene_submit(port, 2, (u64)handle, 0, 0, di.width, di.height, SCENE_Z); + if (rc != 0) { emit_fail("submit", rc); hang(); } + + rc = ncmp_damage_commit(port, 3, 0, 0, di.width, di.height); + if (rc != 0) { emit_fail("damage", rc); hang(); } + + emit("[NSFB-PROBE] PASS\n"); + hang(); + return 0; +} diff --git a/userland/capsule_nsfb_probe/src/nonos_compositor.h b/userland/capsule_nsfb_probe/src/nonos_compositor.h new file mode 100644 index 0000000000..f6087d1b4d --- /dev/null +++ b/userland/capsule_nsfb_probe/src/nonos_compositor.h @@ -0,0 +1,75 @@ +#ifndef NONOS_COMPOSITOR_H +#define NONOS_COMPOSITOR_H + +#include "nonos_sys.h" + +#define NCMP_MAGIC 0x4E434D50u +#define NCMP_VERSION 1 +#define NCMP_HDR_LEN 20 +#define OP_SCENE_SUBMIT 0x0002 +#define OP_DAMAGE_COMMIT 0x0003 +#define OP_SCENE_REMOVE 0x0007 +#define OP_DISPLAY_INFO 0x0008 + +struct display_info { + u32 width; + u32 height; + u32 stride; + u32 format; +}; + +static inline u32 nonos_lookup_port(const char *name, u64 len) { + u32 port = 0; + u32 pid = 0; + i64 rc = nonos_sys6(SYS_SERVICE_LOOKUP, (u64)name, len, (u64)&port, + (u64)&pid, 0, 0); + if (rc < 0 || pid == 0) + return 0; + return port; +} + +static inline void ncmp_header(unsigned char *p, u32 op, u32 rid, u32 plen) { + put_u32(p, 0, NCMP_MAGIC); + put_u16(p, 4, NCMP_VERSION); + put_u16(p, 6, op); + put_u16(p, 8, 0); + put_u16(p, 10, 0); + put_u32(p, 12, rid); + put_u32(p, 16, plen); +} + +static inline i64 ncmp_call(u32 port, const unsigned char *tx, u64 txlen, + unsigned char *rx, u64 rxlen) { + return nonos_sys6(SYS_IPC_CALL, (u64)port, (u64)tx, txlen, (u64)rx, + rxlen, 0); +} + +static inline int ncmp_status_call(u32 port, const unsigned char *tx, u64 txlen) { + unsigned char rx[NCMP_HDR_LEN + 4]; + i64 rc = ncmp_call(port, tx, txlen, rx, sizeof(rx)); + if (rc < (i64)sizeof(rx)) + return -1; + if ((i32)get_u32(rx, NCMP_HDR_LEN) != 0) + return -2; + return 0; +} + +static inline int ncmp_display_info(u32 port, u32 rid, struct display_info *out) { + unsigned char tx[NCMP_HDR_LEN]; + unsigned char rx[NCMP_HDR_LEN + 4 + 16]; + ncmp_header(tx, OP_DISPLAY_INFO, rid, 0); + i64 rc = ncmp_call(port, tx, sizeof(tx), rx, sizeof(rx)); + if (rc < (i64)sizeof(rx)) + return -1; + if ((i32)get_u32(rx, NCMP_HDR_LEN) != 0) + return -2; + out->width = get_u32(rx, NCMP_HDR_LEN + 4); + out->height = get_u32(rx, NCMP_HDR_LEN + 8); + out->stride = get_u32(rx, NCMP_HDR_LEN + 12); + out->format = get_u32(rx, NCMP_HDR_LEN + 16); + if (out->format != 1u || !out->width || !out->height || !out->stride) + return -3; + return 0; +} + +#endif diff --git a/userland/capsule_nsfb_probe/src/nonos_marker.h b/userland/capsule_nsfb_probe/src/nonos_marker.h new file mode 100644 index 0000000000..aefc49ba1d --- /dev/null +++ b/userland/capsule_nsfb_probe/src/nonos_marker.h @@ -0,0 +1,47 @@ +#ifndef NONOS_MARKER_H +#define NONOS_MARKER_H + +#include + +#include "nonos_sys.h" + +static inline u64 cstr_len(const char *s) { + u64 n = 0; + while (s[n]) + n++; + return n; +} + +static inline void emit(const char *s) { + write(1, s, cstr_len(s)); +} + +static inline u64 fmt_i64(char *buf, i64 v) { + char tmp[24]; + u64 i = 0; + u64 neg = v < 0; + u64 u = neg ? (u64)(-v) : (u64)v; + do { + tmp[i++] = (char)('0' + (u % 10)); + u /= 10; + } while (u); + u64 n = 0; + if (neg) + buf[n++] = '-'; + while (i) + buf[n++] = tmp[--i]; + buf[n] = 0; + return n; +} + +static inline void emit_fail(const char *step, i64 rc) { + char num[24]; + fmt_i64(num, rc); + emit("[NSFB-PROBE] FAIL:"); + emit(step); + emit(":"); + emit(num); + emit("\n"); +} + +#endif diff --git a/userland/capsule_nsfb_probe/src/nonos_scene.h b/userland/capsule_nsfb_probe/src/nonos_scene.h new file mode 100644 index 0000000000..ab96fba0a0 --- /dev/null +++ b/userland/capsule_nsfb_probe/src/nonos_scene.h @@ -0,0 +1,38 @@ +#ifndef NONOS_SCENE_H +#define NONOS_SCENE_H + +#include "nonos_compositor.h" + +static inline int ncmp_scene_submit(u32 port, u32 rid, u64 handle, u32 x, + u32 y, u32 w, u32 h, u32 z) { + unsigned char tx[NCMP_HDR_LEN + 32]; + ncmp_header(tx, OP_SCENE_SUBMIT, rid, 32); + put_u64(tx, 20, handle); + put_u32(tx, 28, x); + put_u32(tx, 32, y); + put_u32(tx, 36, w); + put_u32(tx, 40, h); + put_u32(tx, 44, z); + put_u32(tx, 48, 0); + return ncmp_status_call(port, tx, sizeof(tx)); +} + +static inline int ncmp_damage_commit(u32 port, u32 rid, u32 x, u32 y, u32 w, u32 h) { + unsigned char tx[NCMP_HDR_LEN + 16]; + ncmp_header(tx, OP_DAMAGE_COMMIT, rid, 16); + put_u32(tx, 20, x); + put_u32(tx, 24, y); + put_u32(tx, 28, w); + put_u32(tx, 32, h); + return ncmp_status_call(port, tx, sizeof(tx)); +} + +static inline int ncmp_scene_remove(u32 port, u32 rid) { + unsigned char tx[NCMP_HDR_LEN + 8]; + ncmp_header(tx, OP_SCENE_REMOVE, rid, 8); + put_u32(tx, 20, 0); + put_u32(tx, 24, 0); + return ncmp_status_call(port, tx, sizeof(tx)); +} + +#endif diff --git a/userland/capsule_nsfb_probe/src/nonos_surface.h b/userland/capsule_nsfb_probe/src/nonos_surface.h new file mode 100644 index 0000000000..cb7146dfc5 --- /dev/null +++ b/userland/capsule_nsfb_probe/src/nonos_surface.h @@ -0,0 +1,46 @@ +#ifndef NONOS_SURFACE_H +#define NONOS_SURFACE_H + +#include +#include "nonos_sys.h" + +#define PROT_RW 0x3 +#define MAP_PRIVATE_ANON 0x22 +#define SURFACE_FORMAT_ARGB8888 1u + +struct surface_descriptor { + u32 width; + u32 height; + u32 stride; + u32 format; + u64 byte_len; + u64 base_va; + u64 flags; +}; +_Static_assert(sizeof(struct surface_descriptor) == 40, + "surface_descriptor size must be 40 bytes"); +_Static_assert(offsetof(struct surface_descriptor, byte_len) == 16, + "surface_descriptor byte_len must be at offset 16"); +_Static_assert(offsetof(struct surface_descriptor, base_va) == 24, + "surface_descriptor base_va must be at offset 24"); +_Static_assert(offsetof(struct surface_descriptor, flags) == 32, + "surface_descriptor flags must be at offset 32"); + +static inline u64 nonos_mmap(u64 len) { + return (u64)nonos_sys6(SYS_MMAP, 0, len, PROT_RW, MAP_PRIVATE_ANON, + (u64)(-1), 0); +} + +static inline i64 nonos_surface_register(const struct surface_descriptor *d) { + return nonos_sys6(SYS_SURFACE_REGISTER, (u64)d, 0, 0, 0, 0, 0); +} + +static inline i64 nonos_surface_share(u64 sid) { + return nonos_sys6(SYS_SURFACE_SHARE, sid, 0, 0, 0, 0, 0); +} + +static inline i64 nonos_surface_release(u64 handle) { + return nonos_sys6(SYS_SURFACE_RELEASE, handle, 0, 0, 0, 0, 0); +} + +#endif diff --git a/userland/capsule_nsfb_probe/src/nonos_sys.h b/userland/capsule_nsfb_probe/src/nonos_sys.h new file mode 100644 index 0000000000..ead540be56 --- /dev/null +++ b/userland/capsule_nsfb_probe/src/nonos_sys.h @@ -0,0 +1,61 @@ +#ifndef NONOS_SYS_H +#define NONOS_SYS_H + +typedef unsigned int u32; +typedef int i32; +typedef unsigned long u64; +typedef long i64; + +#define FOURCC(a, b, c, d) \ + ((i64)((unsigned char)(a) | ((unsigned char)(b) << 8) | \ + ((unsigned char)(c) << 16) | ((i64)(unsigned char)(d) << 24))) + +#define SYS_MMAP FOURCC('M', 'M', 'A', 'P') +#define SYS_SURFACE_REGISTER FOURCC('M', 'S', 'R', 'G') +#define SYS_SURFACE_SHARE FOURCC('M', 'S', 'S', 'H') +#define SYS_SURFACE_RELEASE FOURCC('M', 'S', 'R', 'L') +#define SYS_SERVICE_LOOKUP FOURCC('M', 'S', 'V', 'L') +#define SYS_IPC_CALL FOURCC('M', 'I', 'C', 'L') +#define SYS_YIELD FOURCC('M', 'Y', 'L', 'D') + +static inline i64 nonos_sys6(i64 num, u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6) { + register i64 rax __asm__("rax") = num; + register u64 rdi __asm__("rdi") = a1; + register u64 rsi __asm__("rsi") = a2; + register u64 rdx __asm__("rdx") = a3; + register u64 r10 __asm__("r10") = a4; + register u64 r8 __asm__("r8") = a5; + register u64 r9 __asm__("r9") = a6; + __asm__ volatile("syscall" + : "+r"(rax), "+r"(rdi), "+r"(rsi), "+r"(rdx), + "+r"(r10), "+r"(r8), "+r"(r9) + : + : "rcx", "r11", "memory"); + return rax; +} + +static inline void nonos_yield(void) { + nonos_sys6(SYS_YIELD, 0, 0, 0, 0, 0, 0); +} + +static inline void put_u16(unsigned char *p, u32 off, u32 v) { + p[off] = (unsigned char)(v & 0xFF); + p[off + 1] = (unsigned char)((v >> 8) & 0xFF); +} + +static inline void put_u32(unsigned char *p, u32 off, u32 v) { + put_u16(p, off, v & 0xFFFF); + put_u16(p, off + 2, (v >> 16) & 0xFFFF); +} + +static inline void put_u64(unsigned char *p, u32 off, u64 v) { + put_u32(p, off, (u32)v); + put_u32(p, off + 4, (u32)(v >> 32)); +} + +static inline u32 get_u32(const unsigned char *p, u32 off) { + return (u32)p[off] | ((u32)p[off + 1] << 8) | + ((u32)p[off + 2] << 16) | ((u32)p[off + 3] << 24); +} + +#endif diff --git a/userland/capsule_relibc_test/Capsule.mk b/userland/capsule_relibc_test/Capsule.mk new file mode 100644 index 0000000000..fafa099403 --- /dev/null +++ b/userland/capsule_relibc_test/Capsule.mk @@ -0,0 +1,17 @@ +CAPSULE_SLUG := relibc-test +CAPSULE_HANDLE := relibc_test +CAPSULE_DOMAIN := systems.nonos +CAPSULE_DIR := userland/capsule_relibc_test +CAPSULE_BIN_NAME := relibc_test +CAPSULE_FEATURE := nonos-capsule-relibc-test +CAPSULE_NAMESPACE := systems.nonos.relibc_test +CAPSULE_SERVICE_ENDPOINT := service:4506:relibc_test +CAPSULE_REPLY_ENDPOINT := reply:4507:endpoint.relibc_test.reply +# CoreExec | IPC | Memory | Crypto | FileSystem | Debug +# = 0x01 | 0x08 | 0x10 | 0x20 | 0x40 | 0x100 = 0x179 +CAPSULE_REQUIRED_CAPS := 0x179 +CAPSULE_KERNEL_MIRROR := src/userspace/capsule_relibc_test +CAPSULE_PREBUILT_BIN := userland/capsule_relibc_test/build/relibc_test + +include nonos-mk/capsule-c.mk +include nonos-mk/capsule.mk diff --git a/userland/capsule_relibc_test/src/main.c b/userland/capsule_relibc_test/src/main.c new file mode 100644 index 0000000000..a5c7759eaf --- /dev/null +++ b/userland/capsule_relibc_test/src/main.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include + +#define EMIT(s) write(1, s, sizeof(s) - 1) + +int main(void) { + char *p = (char *)malloc(1u << 20); + if (!p) { EMIT("[RELIBC-TEST] FAIL malloc\n"); return 1; } + for (unsigned i = 0; i < (1u << 20); i += 4096) p[i] = (char)i; + char *q = (char *)malloc(4096); + if (!q) { EMIT("[RELIBC-TEST] FAIL malloc2\n"); return 1; } + free(q); + free(p); + + struct timespec a, b; + clock_gettime(CLOCK_MONOTONIC, &a); + clock_gettime(CLOCK_MONOTONIC, &b); + if (b.tv_sec < a.tv_sec) { EMIT("[RELIBC-TEST] FAIL clock\n"); return 1; } + + FILE *f = fopen("/relibctest.txt", "w"); + if (!f) { EMIT("[RELIBC-TEST] FAIL fopen-w\n"); return 1; } + if (fwrite("hello", 1, 5, f) != 5) { EMIT("[RELIBC-TEST] FAIL fwrite\n"); return 1; } + fclose(f); + + f = fopen("/relibctest.txt", "r"); + if (!f) { EMIT("[RELIBC-TEST] FAIL fopen-r\n"); return 1; } + char buf[8]; + for (int i = 0; i < 8; i++) buf[i] = 0; + size_t n = fread(buf, 1, 5, f); + fclose(f); + if (n != 5 || buf[0] != 'h' || buf[4] != 'o') { EMIT("[RELIBC-TEST] FAIL fread\n"); return 1; } + + int fd = open("/relibctest.txt", O_RDONLY); + if (fd < 0) { EMIT("[RELIBC-TEST] FAIL open\n"); return 1; } + char c; + if (lseek(fd, 1, SEEK_SET) != 1) { EMIT("[RELIBC-TEST] FAIL lseek-set\n"); return 1; } + if (read(fd, &c, 1) != 1 || c != 'e') { EMIT("[RELIBC-TEST] FAIL read-set\n"); return 1; } + if (lseek(fd, 1, SEEK_CUR) != 3) { EMIT("[RELIBC-TEST] FAIL lseek-cur\n"); return 1; } + char pb[5]; + for (int i = 0; i < 5; i++) pb[i] = 0; + if (pread(fd, pb, 5, 0) != 5 || pb[0] != 'h' || pb[4] != 'o') { EMIT("[RELIBC-TEST] FAIL pread\n"); return 1; } + if (read(fd, &c, 1) != 1 || c != 'l') { EMIT("[RELIBC-TEST] FAIL pread-pos\n"); return 1; } + if (lseek(fd, 0, SEEK_END) != 5) { EMIT("[RELIBC-TEST] FAIL lseek-end\n"); return 1; } + close(fd); + + fd = open("/relibctest.txt", O_RDWR); + if (fd < 0) { EMIT("[RELIBC-TEST] FAIL open-rdwr\n"); return 1; } + if (pwrite(fd, "XY", 2, 1) != 2) { EMIT("[RELIBC-TEST] FAIL pwrite\n"); return 1; } + char vb[5]; + for (int i = 0; i < 5; i++) vb[i] = 0; + if (pread(fd, vb, 5, 0) != 5 || vb[1] != 'X' || vb[2] != 'Y') { EMIT("[RELIBC-TEST] FAIL pwrite-verify\n"); return 1; } + if (read(fd, &c, 1) != 1 || c != 'h') { EMIT("[RELIBC-TEST] FAIL pwrite-pos\n"); return 1; } + close(fd); + + EMIT("[RELIBC-TEST] PASS\n"); + return 0; +} diff --git a/userland/capsule_vfs/src/protocol/mod.rs b/userland/capsule_vfs/src/protocol/mod.rs index 185de4e0ef..b0c9792295 100644 --- a/userland/capsule_vfs/src/protocol/mod.rs +++ b/userland/capsule_vfs/src/protocol/mod.rs @@ -26,6 +26,6 @@ pub use errno::{ }; pub use types::{ Request, KERNEL_REPLY_ENDPOINT, MAX_DATA_BYTES, MAX_LIST_BYTES, MAX_PATH_BYTES, OP_CLOSE, - OP_HEALTHCHECK, OP_LIST, OP_MKDIR, OP_OPEN, OP_READ, OP_RENAME, OP_STAT, OP_UNLINK, OP_WRITE, - O_APPEND, O_CREATE, O_TRUNC, + OP_HEALTHCHECK, OP_LIST, OP_MKDIR, OP_OPEN, OP_PREAD, OP_PWRITE, OP_READ, OP_RENAME, OP_SEEK, + OP_STAT, OP_UNLINK, OP_WRITE, O_APPEND, O_CREATE, O_TRUNC, }; diff --git a/userland/capsule_vfs/src/protocol/types.rs b/userland/capsule_vfs/src/protocol/types.rs index 31508d74ec..b6159f94bb 100644 --- a/userland/capsule_vfs/src/protocol/types.rs +++ b/userland/capsule_vfs/src/protocol/types.rs @@ -27,6 +27,9 @@ pub const OP_HEALTHCHECK: u16 = 7; pub const OP_MKDIR: u16 = 8; pub const OP_UNLINK: u16 = 9; pub const OP_RENAME: u16 = 10; +pub const OP_SEEK: u16 = 11; +pub const OP_PREAD: u16 = 12; +pub const OP_PWRITE: u16 = 13; pub const O_CREATE: u32 = 1 << 0; pub const O_TRUNC: u32 = 1 << 1; diff --git a/userland/capsule_vfs/src/server/dispatch.rs b/userland/capsule_vfs/src/server/dispatch.rs index 4723373725..3b4e2ea8e2 100644 --- a/userland/capsule_vfs/src/server/dispatch.rs +++ b/userland/capsule_vfs/src/server/dispatch.rs @@ -19,7 +19,7 @@ use alloc::vec::Vec; use super::handlers; use crate::protocol::{ encode_response, Request, EINVAL, OP_CLOSE, OP_HEALTHCHECK, OP_LIST, OP_MKDIR, OP_OPEN, - OP_READ, OP_RENAME, OP_STAT, OP_UNLINK, OP_WRITE, + OP_PREAD, OP_PWRITE, OP_READ, OP_RENAME, OP_SEEK, OP_STAT, OP_UNLINK, OP_WRITE, }; use crate::store::Store; @@ -34,6 +34,9 @@ pub fn dispatch(store: &mut Store, req: Request<'_>, sender_pid: u32) -> Vec OP_MKDIR => handlers::mkdir(store, req, sender_pid), OP_UNLINK => handlers::unlink(store, req, sender_pid), OP_RENAME => handlers::rename(store, req, sender_pid), + OP_SEEK => handlers::seek(store, req, sender_pid), + OP_PREAD => handlers::pread(store, req, sender_pid), + OP_PWRITE => handlers::pwrite(store, req, sender_pid), OP_HEALTHCHECK => handlers::healthcheck(req), _ => encode_response(req.op, req.flags, req.request_id, EINVAL, &[]), } diff --git a/userland/capsule_vfs/src/server/handlers/mod.rs b/userland/capsule_vfs/src/server/handlers/mod.rs index a9b4810d44..10751e1ca7 100644 --- a/userland/capsule_vfs/src/server/handlers/mod.rs +++ b/userland/capsule_vfs/src/server/handlers/mod.rs @@ -19,8 +19,11 @@ mod healthcheck; mod list; mod mkdir; mod open; +mod pread; +mod pwrite; mod read; mod rename; +mod seek; mod stat; mod unlink; mod util; @@ -31,8 +34,11 @@ pub(super) use healthcheck::healthcheck; pub(super) use list::list; pub(super) use mkdir::mkdir; pub(super) use open::open; +pub(super) use pread::pread; +pub(super) use pwrite::pwrite; pub(super) use read::read; pub(super) use rename::rename; +pub(super) use seek::seek; pub(super) use stat::stat; pub(super) use unlink::unlink; pub(super) use write::write; diff --git a/userland/capsule_vfs/src/server/handlers/pread.rs b/userland/capsule_vfs/src/server/handlers/pread.rs new file mode 100644 index 0000000000..2a6fd43469 --- /dev/null +++ b/userland/capsule_vfs/src/server/handlers/pread.rs @@ -0,0 +1,47 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use alloc::vec::Vec; + +use super::util::{map_store_err, split_caller}; +use crate::protocol::{encode_response, Request, EINVAL, EMSGSIZE, MAX_DATA_BYTES, OP_PREAD}; +use crate::store::Store; + +pub fn pread(store: &mut Store, req: Request<'_>, sender_pid: u32) -> Vec { + let (pid, rest) = match split_caller(req.payload, sender_pid) { + Ok(v) => v, + Err(s) => return encode_response(OP_PREAD, req.flags, req.request_id, s, &[]), + }; + if rest.len() != 16 { + return encode_response(OP_PREAD, req.flags, req.request_id, EINVAL, &[]); + } + let fd = u32::from_le_bytes([rest[0], rest[1], rest[2], rest[3]]); + let offset = i64::from_le_bytes([ + rest[4], rest[5], rest[6], rest[7], + rest[8], rest[9], rest[10], rest[11], + ]); + let max = u32::from_le_bytes([rest[12], rest[13], rest[14], rest[15]]); + if offset < 0 { + return encode_response(OP_PREAD, req.flags, req.request_id, EINVAL, &[]); + } + if max > MAX_DATA_BYTES { + return encode_response(OP_PREAD, req.flags, req.request_id, EMSGSIZE, &[]); + } + match store.pread(fd, pid, offset as u64, max as usize) { + Ok(data) => encode_response(OP_PREAD, req.flags, req.request_id, 0, &data), + Err(e) => encode_response(OP_PREAD, req.flags, req.request_id, map_store_err(e), &[]), + } +} diff --git a/userland/capsule_vfs/src/server/handlers/pwrite.rs b/userland/capsule_vfs/src/server/handlers/pwrite.rs new file mode 100644 index 0000000000..873357bb3c --- /dev/null +++ b/userland/capsule_vfs/src/server/handlers/pwrite.rs @@ -0,0 +1,47 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use alloc::vec::Vec; + +use super::util::{map_store_err, split_caller}; +use crate::protocol::{encode_response, Request, EINVAL, EMSGSIZE, MAX_DATA_BYTES, OP_PWRITE}; +use crate::store::Store; + +pub fn pwrite(store: &mut Store, req: Request<'_>, sender_pid: u32) -> Vec { + let (pid, rest) = match split_caller(req.payload, sender_pid) { + Ok(v) => v, + Err(s) => return encode_response(OP_PWRITE, req.flags, req.request_id, s, &[]), + }; + if rest.len() < 12 { + return encode_response(OP_PWRITE, req.flags, req.request_id, EINVAL, &[]); + } + let fd = u32::from_le_bytes([rest[0], rest[1], rest[2], rest[3]]); + let offset = i64::from_le_bytes([ + rest[4], rest[5], rest[6], rest[7], + rest[8], rest[9], rest[10], rest[11], + ]); + let data = &rest[12..]; + if offset < 0 { + return encode_response(OP_PWRITE, req.flags, req.request_id, EINVAL, &[]); + } + if data.len() > MAX_DATA_BYTES as usize { + return encode_response(OP_PWRITE, req.flags, req.request_id, EMSGSIZE, &[]); + } + match store.pwrite(fd, pid, offset as u64, data) { + Ok(n) => encode_response(OP_PWRITE, req.flags, req.request_id, 0, &n.to_le_bytes()), + Err(e) => encode_response(OP_PWRITE, req.flags, req.request_id, map_store_err(e), &[]), + } +} diff --git a/userland/capsule_vfs/src/server/handlers/seek.rs b/userland/capsule_vfs/src/server/handlers/seek.rs new file mode 100644 index 0000000000..14a460d1fc --- /dev/null +++ b/userland/capsule_vfs/src/server/handlers/seek.rs @@ -0,0 +1,40 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use alloc::vec::Vec; + +use super::util::{map_store_err, split_caller}; +use crate::protocol::{encode_response, Request, EINVAL, OP_SEEK}; +use crate::store::Store; + +pub fn seek(store: &mut Store, req: Request<'_>, sender_pid: u32) -> Vec { + let (pid, rest) = match split_caller(req.payload, sender_pid) { + Ok(v) => v, + Err(s) => return encode_response(OP_SEEK, req.flags, req.request_id, s, &[]), + }; + if rest.len() != 16 { + return encode_response(OP_SEEK, req.flags, req.request_id, EINVAL, &[]); + } + let fd = u32::from_le_bytes([rest[0], rest[1], rest[2], rest[3]]); + let whence = u16::from_le_bytes([rest[4], rest[5]]); + let offset = i64::from_le_bytes([ + rest[8], rest[9], rest[10], rest[11], rest[12], rest[13], rest[14], rest[15], + ]); + match store.seek(fd, pid, whence, offset) { + Ok(new_pos) => encode_response(OP_SEEK, req.flags, req.request_id, 0, &new_pos.to_le_bytes()), + Err(e) => encode_response(OP_SEEK, req.flags, req.request_id, map_store_err(e), &[]), + } +} diff --git a/userland/capsule_vfs/src/server/handlers/util.rs b/userland/capsule_vfs/src/server/handlers/util.rs index cc581e3f41..ebc92d1b96 100644 --- a/userland/capsule_vfs/src/server/handlers/util.rs +++ b/userland/capsule_vfs/src/server/handlers/util.rs @@ -28,6 +28,7 @@ pub(super) fn map_store_err(e: StoreError) -> i32 { StoreError::Exists => EEXIST, StoreError::NotEmpty => ENOTEMPTY, StoreError::IsDir => EISDIR, + StoreError::Invalid => EINVAL, } } diff --git a/userland/capsule_vfs/src/store/fdtable.rs b/userland/capsule_vfs/src/store/fdtable.rs index 0091276f81..5e7129d9fe 100644 --- a/userland/capsule_vfs/src/store/fdtable.rs +++ b/userland/capsule_vfs/src/store/fdtable.rs @@ -20,8 +20,11 @@ mod mkdir; mod new; mod open; mod query; +mod pread; +mod pwrite; mod read; mod rename; +mod seek; mod seed; mod types; mod unlink; diff --git a/userland/capsule_vfs/src/store/fdtable/pread.rs b/userland/capsule_vfs/src/store/fdtable/pread.rs new file mode 100644 index 0000000000..3a5fa070f8 --- /dev/null +++ b/userland/capsule_vfs/src/store/fdtable/pread.rs @@ -0,0 +1,30 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use alloc::vec::Vec; + +use super::types::{Store, StoreError}; + +impl Store { + pub fn pread(&mut self, fd: u32, owner_pid: u32, offset: u64, max: usize) -> Result, StoreError> { + let file_idx = self.entry(fd, owner_pid)?.file_idx; + let data = &self.files[file_idx].data; + let off = (offset as usize).min(data.len()); + let avail = data.len() - off; + let n = if max < avail { max } else { avail }; + Ok(data[off..off + n].to_vec()) + } +} diff --git a/userland/capsule_vfs/src/store/fdtable/pwrite.rs b/userland/capsule_vfs/src/store/fdtable/pwrite.rs new file mode 100644 index 0000000000..f958697e93 --- /dev/null +++ b/userland/capsule_vfs/src/store/fdtable/pwrite.rs @@ -0,0 +1,40 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use super::types::{Store, StoreError, MAX_FILE_BYTES}; + +impl Store { + pub fn pwrite(&mut self, fd: u32, owner_pid: u32, offset: u64, bytes: &[u8]) -> Result { + let (file_idx, writable) = { + let e = self.entry(fd, owner_pid)?; + (e.file_idx, e.writable) + }; + if !writable { + return Err(StoreError::AccessDenied); + } + let start = offset as usize; + let end = start.saturating_add(bytes.len()); + if end > MAX_FILE_BYTES { + return Err(StoreError::Full); + } + let data = &mut self.files[file_idx].data; + if end > data.len() { + data.resize(end, 0); + } + data[start..end].copy_from_slice(bytes); + Ok(bytes.len() as u32) + } +} diff --git a/userland/capsule_vfs/src/store/fdtable/read.rs b/userland/capsule_vfs/src/store/fdtable/read.rs index 6c82631ce5..7a18663261 100644 --- a/userland/capsule_vfs/src/store/fdtable/read.rs +++ b/userland/capsule_vfs/src/store/fdtable/read.rs @@ -25,11 +25,13 @@ impl Store { (entry.file_idx, entry.pos) }; let data = &self.files[file_idx].data; + let raw_pos = pos; + let pos = pos.min(data.len()); let avail = data.len().saturating_sub(pos); let n = if max < avail { max } else { avail }; let out = data[pos..pos + n].to_vec(); if let Some(entry) = self.fds[fd as usize].as_mut() { - entry.pos = pos + n; + entry.pos = raw_pos + n; } Ok(out) } diff --git a/userland/capsule_vfs/src/store/fdtable/seek.rs b/userland/capsule_vfs/src/store/fdtable/seek.rs new file mode 100644 index 0000000000..4ef792493a --- /dev/null +++ b/userland/capsule_vfs/src/store/fdtable/seek.rs @@ -0,0 +1,40 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use super::types::{Store, StoreError, StoreResult}; + +impl Store { + pub fn seek(&mut self, fd: u32, owner_pid: u32, whence: u16, offset: i64) -> StoreResult { + let (file_idx, pos) = { + let e = self.entry(fd, owner_pid)?; + (e.file_idx, e.pos) + }; + let base: i64 = match whence { + 0 => 0, + 1 => pos as i64, + 2 => self.files[file_idx].data.len() as i64, + _ => return Err(StoreError::Invalid), + }; + let new = base.checked_add(offset).ok_or(StoreError::Invalid)?; + if new < 0 { + return Err(StoreError::Invalid); + } + if let Some(e) = self.fds[fd as usize].as_mut() { + e.pos = new as usize; + } + Ok(new as u64) + } +} diff --git a/userland/capsule_vfs/src/store/fdtable/types.rs b/userland/capsule_vfs/src/store/fdtable/types.rs index f6623e173c..266106e952 100644 --- a/userland/capsule_vfs/src/store/fdtable/types.rs +++ b/userland/capsule_vfs/src/store/fdtable/types.rs @@ -30,6 +30,7 @@ pub enum StoreError { Exists, NotEmpty, IsDir, + Invalid, } pub(super) type StoreResult = Result;