Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
39 changes: 19 additions & 20 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

---

Expand All @@ -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`)
Expand All @@ -41,43 +41,42 @@ 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`)

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`)

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.

---

Expand Down
11 changes: 11 additions & 0 deletions crates/ns_process/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"]
74 changes: 74 additions & 0 deletions crates/ns_process/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
71 changes: 71 additions & 0 deletions examples/16_process.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#include "../include/ns.h"
#include "../include/ns_process.h"
#include <unistd.h> // 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;
}
56 changes: 56 additions & 0 deletions examples/17_concurrent_process.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include "../include/ns.h"
#include "../include/ns_process.h"
#include <unistd.h> // 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;
}
40 changes: 40 additions & 0 deletions include/ns_process.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#ifndef NS_PROCESS_H
#define NS_PROCESS_H

#include <stdbool.h>
#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