diff --git a/Cargo.lock b/Cargo.lock index 0620e83..ebdd94c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,13 @@ dependencies = [ "ns_string", ] +[[package]] +name = "ns_process" +version = "0.2.6" +dependencies = [ + "ns_error", +] + [[package]] name = "ns_string" version = "0.2.6" diff --git a/Cargo.toml b/Cargo.toml index d15d809..eca5db9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,8 @@ members = [ "crates/ns_string", "crates/ns_error", "crates/ns_data", - "crates/ns_cmd", + "crates/ns_cmd", + "crates/ns_process", ] # Shared metadata diff --git a/Makefile b/Makefile index cf8522f..6417847 100644 --- a/Makefile +++ b/Makefile @@ -15,13 +15,14 @@ LIBS_TO_INSTALL = $(RUST_DIR)/libns_data.a \ $(RUST_DIR)/libns_io.a \ $(RUST_DIR)/libns_string.a \ $(RUST_DIR)/libns_error.a \ - $(RUST_DIR)/libns_cmd.a + $(RUST_DIR)/libns_cmd.a \ + $(RUST_DIR)/libns_process.a # Flags # Added -Iinclude so C finds your new header folder locally INCLUDES = -I. -Iinclude # Added -lns_strings and -lns_data to link the Rust crates -LIBS = -L$(RUST_DIR) -lns_cmd -lns_data -lns_io -lns_string -lns_error -lpthread -ldl -lm -Wl,-rpath=$(RUST_DIR) +LIBS = -L$(RUST_DIR) -lns_process -lns_cmd -lns_data -lns_io -lns_string -lns_error -lpthread -ldl -lm -Wl,-rpath=$(RUST_DIR) EXAMPLES = $(patsubst $(EXAMPLE_DIR)/%.c,%,$(wildcard $(EXAMPLE_DIR)/*.c)) diff --git a/ROADMAP.md b/ROADMAP.md index a540844..8e447cb 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -13,17 +13,17 @@ The foundational architecture and core modules are fully implemented and ready for production use. * [x] **Type-Safe Printing (`ns_io`):** `ns_print` and `ns_println` functions - with `_Generic` routing for `int`, `float`, `double`, and `char*`. + with `_Generic` routing for `int`, `float`, `double`, and `char*`. * [x] **Safe User Input:** `ns_read()` macro for dynamically capturing terminal - input without buffer overflows. + input without buffer overflows. * [x] **Memory-Safe Strings (`ns_string`):** Small String Optimization (SSO), - dynamic scaling, safe concatenation, and length tracking. + dynamic scaling, safe concatenation, and length tracking. * [x] **Crash-Proof Control Flow (`ns_error`):** Python-style `NS_TRY` and - `NS_EXCEPT` macros, null-pointer protection, and `ns_error_t` enums. + `NS_EXCEPT` macros, null-pointer protection, and `ns_error_t` enums. * [x] **Safe Data Structures (`ns_data`):** Bounds-checked dynamic arrays - (`ns_vec`) and Key-Value Maps (`ns_map`). + (`ns_vec`) and Key-Value Maps (`ns_map`). * [x] **Build & Architecture:** Cargo workspace integration, Makefile - automation, and system-wide installation logic. + automation, and system-wide installation logic. --- @@ -32,7 +32,7 @@ for production use. These features will finalize the core `ns_io` and `ns_string` modules. * [x] **String Interpolation & Formatting:** Introduce Python/Rust-style string - formatting to eliminate the need for `sprintf`. + formatting to eliminate the need for `sprintf`. * *Concept:* `ns_println("Value: {}", val);` ## Phase 2: Process Execution (`ns_cmd` & `ns_process`) @@ -41,16 +41,15 @@ Replacing standard C's `fork()`, `exec()`, and the highly insecure `system()` calls with safe, memory-managed alternatives. * [x] **The Better `system()` (`ns_cmd`):** A high-level execution macro that - prevents shell injection and captures output safely without POSIX pipes. + prevents shell injection and captures output safely without POSIX pipes. * *Architecture:* Introduces an `ns_cmd_output` struct containing separated `ns_string stdout` and `ns_string stderr` fields. This allows developers to parse clean data from `stdout` while independently handling error logs from `stderr`. -* [ ] **Advanced Process Management (`ns_process`):** A wrapper around Rust's - `std::process::Command` using a safe builder pattern. - * *Features:* Ability to construct commands safely (`ns_process_add_arg`), - spawn processes in the background, and manage process handles (e.g., - launching external media players like `mpv`). +* [x] **Advanced Process Management (`ns_process`):** A wrapper around Rust's + `std::process::Command` using a safe builder pattern. + * *Features:* Similar to `ns_cmd` but it is non-blocking and allows to run + background processes. ## Phase 3: Safe File System (`ns_file`) @@ -58,11 +57,11 @@ Fixing the resource leaks and buffer overflow risks associated with standard C's `FILE*` API. * [ ] **One-Shot File I/O:** High-level functions like `ns_file_read_to_string` - that automatically open a file, calculate its size, dynamically allocate - an `ns_string`, and safely close the handle. + that automatically open a file, calculate its size, dynamically allocate + an `ns_string`, and safely close the handle. * [ ] **Safe Streaming:** Opaque `ns_file_t` structs managed entirely by the - Rust backend, allowing C to safely read large files in chunks without ever - touching raw pointers or leaking descriptors. + Rust backend, allowing C to safely read large files in chunks without ever + touching raw pointers or leaking descriptors. ## Phase 4: Networking (`ns_http`) @@ -70,14 +69,14 @@ Bringing modern, memory-safe HTTPS requests to C without the massive boilerplate of `libcurl`. * [ ] **Simple GET Requests:** Wrapper around Rust's `reqwest` to easily fetch - HTML or JSON APIs directly into dynamically sized `ns_string` structures. + HTML or JSON APIs directly into dynamically sized `ns_string` structures. * [ ] **Safe Error Propagation:** Catch dropped connections, 404s, or Cloudflare - blocks via `NS_TRY` blocks instead of crashing the program. + blocks via `NS_TRY` blocks instead of crashing the program. ## Phase 5: C++ Integration * [ ] **C++ Support:** Ensure seamless compatibility for using NextStd macros - and functions directly in C++ codebases. + and functions directly in C++ codebases. --- diff --git a/crates/ns_process/Cargo.toml b/crates/ns_process/Cargo.toml new file mode 100644 index 0000000..5d8e4e3 --- /dev/null +++ b/crates/ns_process/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ns_process" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[dependencies] +ns_error = { path = "../ns_error" } + +[lib] +crate-type = ["staticlib"] diff --git a/crates/ns_process/src/lib.rs b/crates/ns_process/src/lib.rs new file mode 100644 index 0000000..3c10e0f --- /dev/null +++ b/crates/ns_process/src/lib.rs @@ -0,0 +1,74 @@ +use ns_error::NsError; +use std::ffi::CStr; +use std::os::raw::c_char; +use std::process::{Child, Command}; + +pub struct NsProcess { + inner: Child, +} + +/// # Safety +/// +/// * `command` must be a valid, null-terminated C string or null +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ns_process_spawn(command: *const c_char) -> *mut NsProcess { + if command.is_null() { + return std::ptr::null_mut(); + } + + let cmd_str = unsafe { CStr::from_ptr(command) }.to_string_lossy(); + + // Spawn using sh -c to allow for easy argument passing from C + let result = Command::new("sh").arg("-c").arg(cmd_str.as_ref()).spawn(); + + match result { + Ok(child) => Box::into_raw(Box::new(NsProcess { inner: child })), + Err(_) => std::ptr::null_mut(), + } +} + +/// # Safety +/// +/// * `proc` must be a valid pointer to an `NsProcess` spawned by this library, or null +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ns_process_is_running(proc: *mut NsProcess) -> bool { + if proc.is_null() { + return false; + } + + let ns_proc = unsafe { &mut *proc }; + + matches!(ns_proc.inner.try_wait(), Ok(None)) +} + +/// # Safety +/// +/// * `proc` must be a valid pointer to an `NsProcess` spawned by this library, or null +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ns_process_kill(proc: *mut NsProcess) -> NsError { + if proc.is_null() { + return NsError::Any; + } + + let ns_proc = unsafe { &mut *proc }; + + match ns_proc.inner.kill() { + Ok(_) => NsError::Success, + Err(_) => NsError::Any, + } +} + +/// # Safety +/// +/// * `proc` must be a valid pointer to an `NsProcess` spawned by this library. +/// * This function must not be called more than once on the same pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ns_process_free(proc: *mut NsProcess) { + if !proc.is_null() { + unsafe { + let mut ns_proc = Box::from_raw(proc); + + let _ = ns_proc.inner.wait(); + } + } +} diff --git a/examples/16_process.c b/examples/16_process.c new file mode 100644 index 0000000..7f938e3 --- /dev/null +++ b/examples/16_process.c @@ -0,0 +1,71 @@ +#include "../include/ns.h" +#include "../include/ns_process.h" +#include // For sleep() + +int main(void) +{ + ns_println("NextStd Asynchronous Process Demo"); + + ns_println("\nTest 1: Spawning and waiting for completion"); + { + ns_println("Spawning: sleep 2"); + ns_process* proc = ns_process_spawn("sleep 2"); + + if (proc == NULL) { + ns_println("Failed to spawn process."); + return 1; + } + + int tick = 1; + // Loop continues while the background process is alive + while (ns_process_is_running(proc)) { + ns_println("C thread doing other work... tick {}", tick); + sleep(1); + tick++; + } + + ns_println("Process finished naturally."); + + // Always free the process handle to prevent memory leaks and zombies + ns_process_free(proc); + } + + ns_println("\nTest 2: Terminating a process early"); + { + // In your scraper, this could be: ns_process_spawn("mpv https://animepahe..."); + ns_println("Spawning long-running task: sleep 10"); + ns_process* proc = ns_process_spawn("sleep 10"); + + if (proc == NULL) { + ns_println("Failed to spawn process."); + return 1; + } + + ns_println("Waiting 2 seconds before sending kill signal..."); + sleep(1); + ns_println("Tick 1"); + sleep(1); + ns_println("Tick 2"); + + ns_println("User requested skip! Killing process early..."); + + // Send the kill signal + int err = ns_process_kill(proc); + + if (err == 0) { // 0 is NsError::Success + ns_println("Kill signal sent successfully."); + } else { + ns_println("Failed to kill process."); + } + + // Verify it actually died + if (!ns_process_is_running(proc)) { + ns_println("Verified: Process is no longer running."); + } + + ns_process_free(proc); + } + + ns_println("\nAll asynchronous tests completed safely!"); + return 0; +} diff --git a/examples/17_concurrent_process.c b/examples/17_concurrent_process.c new file mode 100644 index 0000000..d9b422a --- /dev/null +++ b/examples/17_concurrent_process.c @@ -0,0 +1,56 @@ +#include "../include/ns.h" +#include "../include/ns_process.h" +#include // For sleep() + +int main(void) +{ + ns_println("=== NextStd Concurrent Processes Demo ==="); + + // 1. Fire off a background download (Simulating the scraper) + ns_println("Task 1: Starting background HTML download..."); + ns_process* curl_proc = ns_process_spawn( + "curl -s 'https://en.wikipedia.org/wiki/Bleach_(manga)' -o bleach_data.html" + ); + + // 2. Fire off a media player + // --idle and --force-window ensures it opens a window even without a video link + ns_println("Task 2: Launching mpv..."); + ns_process* mpv_proc = ns_process_spawn("mpv --idle --force-window=yes"); + + if (!curl_proc || !mpv_proc) { + ns_println("Failed to spawn one or more processes."); + return 1; + } + + int tick = 1; + + // 3. The Main Thread Loop + // This continues as long as AT LEAST ONE process is still running + while (ns_process_is_running(curl_proc) || ns_process_is_running(mpv_proc)) { + + bool curl_alive = ns_process_is_running(curl_proc); + bool mpv_alive = ns_process_is_running(mpv_proc); + + ns_println("Tick {}: Main thread monitoring... [Curl: {}] [MPV: {}]", + tick, + curl_alive ? "Active" : "Finished", + mpv_alive ? "Active" : "Finished"); + + sleep(1); + tick++; + + // Let's force kill mpv after 5 seconds so you don't have to close it manually + if (tick == 6 && mpv_alive) { + ns_println("\nTimeout reached! Sending kill signal to mpv..."); + ns_process_kill(mpv_proc); + } + } + + ns_println("\nAll concurrent tasks finished safely!"); + + // 4. Cleanup + ns_process_free(curl_proc); + ns_process_free(mpv_proc); + + return 0; +} diff --git a/include/ns_process.h b/include/ns_process.h new file mode 100644 index 0000000..ac04bec --- /dev/null +++ b/include/ns_process.h @@ -0,0 +1,40 @@ +#ifndef NS_PROCESS_H +#define NS_PROCESS_H + +#include +#include "ns_error.h" + +#ifdef __cplusplus +extern "C" { +#endif + + typedef struct ns_process ns_process; + + /** + * Spawns a process in the background + * Returns a handle to the process on success, NULL on failure + */ + ns_process* ns_process_spawn(const char* command); + + /** + * Checks if the background process is still running + */ + bool ns_process_is_running(ns_process* proc); + + /** + * Forcefully terminates the process + */ + ns_error_t ns_process_kill(ns_process* proc); + + /** + * Waits for process to finish and frees the handle + */ + void ns_process_free(ns_process* proc); + +#ifdef __cplusplus + +} + +#endif + +#endif // !DEBUG