From 1d5fc53d08342a9a7c97ea3824b2e1da20a566de Mon Sep 17 00:00:00 2001 From: Vaishnav Sabari Girish Date: Sun, 22 Mar 2026 15:35:35 +0530 Subject: [PATCH] docs(command): Add docs for ns_cmd --- src/SUMMARY.md | 6 +++ src/cmd/README.md | 62 ++++++++++++++++++++++++++ src/cmd/auto-cleanup.md | 85 ++++++++++++++++++++++++++++++++++++ src/cmd/execution.md | 77 ++++++++++++++++++++++++++++++++ src/cmd/output-capturing.md | 74 +++++++++++++++++++++++++++++++ src/examples/process-exec.md | 1 + 6 files changed, 305 insertions(+) create mode 100644 src/cmd/README.md create mode 100644 src/cmd/auto-cleanup.md create mode 100644 src/cmd/execution.md create mode 100644 src/cmd/output-capturing.md create mode 100644 src/examples/process-exec.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 3f939a1..02a5cdd 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -28,10 +28,16 @@ - [Dynamic Arrays (`ns_vec`)](data_structures/vectors.md) - [Key-Value Maps (`ns_hashmap`)](data_structures/hashmap.md) +- [Command Execution (`ns_cmd`)](cmd/README.md) + - [Running Shell Commands](cmd/execution.md) + - [Capturing Stdout & Stderr](cmd/output-capturing.md) + - [Auto-Cleanup (RAII in C)](cmd/auto-cleanup.md) + - [Examples](examples/README.md) - [Safe User Input](examples/safe-input.md) - [Building a CLI Menu](examples/cli-menu.md) - [String Manipulation](examples/string-manipulation.md) + - [Process Execution & Auto-Free](examples/process-exec.md) - [Contributing](contributing/README.md) - [Architecture Overview](contributing/architecture.md) diff --git a/src/cmd/README.md b/src/cmd/README.md new file mode 100644 index 0000000..a5218b0 --- /dev/null +++ b/src/cmd/README.md @@ -0,0 +1,62 @@ +# Command Execution (`ns_cmd`) + +Executing shell commands in standard C usually involves reaching for the +`system()` function or wrestling with `popen()`. + +Both approaches are fraught with legacy issues. `system()` offers absolutely no +way to capture the output of the command (it just prints directly to the +terminal), and `popen()` requires manually managing streams, allocating buffers, +and parsing raw bytes—often leading to memory leaks or buffer overflows. + +The **`ns_cmd`** module completely replaces these legacy functions with a +modern, memory-safe, and highly ergonomic wrapper around Rust's +`std::process::Command`. + +## The NextStd Solution + +With `ns_cmd`, executing a command and capturing its exact output is safe and +synchronous. + +* **Complete Stream Capture:** Both `stdout` and `stderr` are automatically + captured and placed into NextStd's blazingly fast + [Small String Optimized (`ns_string`)](../string/README.md) structs. +* **Exit Codes:** The exact exit code of the shell process is captured and + returned. +* **Smart Routing:** Pass either a standard C string literal (`"uname -a"`) or a + dynamically built `ns_string`. The macros figure it out automatically. +* **Zero Memory Leaks:** Thanks to the `ns_autocmd` macro, process output + structs automatically free their own internal string buffers the moment they + go out of scope. + +## Quick Glance + +Here is how simple it is to execute a command, read its output, and clean up the +memory—all in four lines of C: + +```c +#include +#include + +int main(void) { + // 1. Initialize the output struct with the auto-cleanup macro + ns_autocmd ns_cmd_output out = {0}; + + // 2. Run the command safely + ns_cmd_run("uname -a", &out); + + // 3. Use the captured data! + ns_println("Status: {}", out.exit_code); + ns_println("Output: {}", out.stdout_data); + + // 4. No free() required. Memory cleans itself up here! + return 0; +} +``` + +## Deep Dive + +Explore the specific features of the `ns_cmd` module: + +* [Running Shell Commands](execution.md) +* [Capturing Stdout & Stderr](output-capturing.md) +* [Auto-Cleanup (RAII in C)](auto-cleanup.md) diff --git a/src/cmd/auto-cleanup.md b/src/cmd/auto-cleanup.md new file mode 100644 index 0000000..df0b140 --- /dev/null +++ b/src/cmd/auto-cleanup.md @@ -0,0 +1,85 @@ +# Auto-Cleanup (RAII in C) + +One of the biggest pain points in standard C is manual memory management. If a +library dynamically allocates memory to capture a string or a file, the +developer is strictly responsible for calling `free()` when they are done. + +If you forget, your program leaks memory. If you call it twice, your program +crashes. + +Languages like C++ and Rust solved this decades ago using **RAII** (Resource +Acquisition Is Initialization) and destructors. When a variable goes out of +scope (reaches the closing brace `}` of its block), the compiler automatically +inserts the cleanup code. + +With NextStd, you get that exact same magic in C. + +## The `ns_autocmd` Macro + +NextStd leverages a powerful, widely-supported GCC/Clang compiler extension +called `__attribute__((cleanup))`. We wrap this extension in a simple, ergonomic +macro called `ns_autocmd`. + +When you prefix your `ns_cmd_output` struct with `ns_autocmd`, you are attaching +a hidden destructor to the variable. The moment that variable falls out of +scope, the compiler automatically intercepts it and safely frees any internal +`ns_string` heap allocations. + +```c +#include +#include + +void run_status_check() { + // 1. Declare the struct with the auto-cleanup macro + ns_autocmd ns_cmd_output out = {0}; + + // 2. Run the command + ns_cmd_run("uptime", &out); + + // 3. Print the result + ns_println("System Uptime: {}", out.stdout_data); + + // 4. End of scope. The compiler automatically calls + // ns_cmd_output_free(&out) right here! No memory leaks! +} +``` + +## ⚠️ The Golden Rule: Zero-Initialization + +There is one critical rule when using `ns_autocmd`: +**You must always zero-initialize your struct (`= {0};`).** + +```c +// ❌ DANGEROUS: Contains random stack garbage memory +ns_autocmd ns_cmd_output bad_out; + +// ✅ SAFE: Memory is explicitly zeroed out +ns_autocmd ns_cmd_output good_out = {0}; +``` + +**Why is this required?** In C, uninitialized stack variables contain random +"garbage" data left over in RAM. + +If `ns_cmd_run` were to fail *before* it could successfully populate your struct +(for example, if the system couldn't spawn a shell), the destructor would +eventually run and try to `free()` that random garbage memory. This results in a +guaranteed segmentation fault. + +By zero-initializing the struct (`= {0};`), you guarantee that if the command +fails early, the destructor simply sees `NULL` pointers and lengths of `0`, and +safely does nothing. + +## Manual Cleanup + +If you are compiling on a highly restrictive embedded compiler that does not +support GCC/Clang extensions, or if you simply prefer manual memory management, +you can still declare the struct normally and use the manual free function: + +```c +ns_cmd_output out = {0}; +ns_cmd_run("ls", &out); +ns_println("Output: {}", out.stdout_data); + +// Manually free the internal strings +ns_cmd_output_free(&out); +``` diff --git a/src/cmd/execution.md b/src/cmd/execution.md new file mode 100644 index 0000000..52596ec --- /dev/null +++ b/src/cmd/execution.md @@ -0,0 +1,77 @@ +# Running Shell Commands + +The core of the `ns_cmd` module is the `ns_cmd_run` macro. Unlike standard C +functions that force you to strictly cast your types or use entirely different +function names for different inputs (like `print_int` vs `print_string`), +NextStd uses modern C11 features to do the heavy lifting for you. + +## The `_Generic` Routing Magic + +In standard C, if an execution function expected a dynamic string struct, you +would be forced to convert your simple string literals into structs every single +time you wanted to run a basic command. + +NextStd solves this by turning `ns_cmd_run` into a `_Generic` macro wrapper. + +When you call `ns_cmd_run`, the C compiler inspects the type of the argument you +passed and automatically routes it to the correct Rust FFI backend function: + +* If you pass a `"string literal"`, it routes to `ns_cmd_run_cstr`. +* If you pass an `ns_string` struct, it routes to `ns_cmd_run_ns`. + +### 1. Passing Standard C Strings + +For 90% of your use cases, you will likely just pass a hardcoded string literal. +The macro accepts it natively: + +```c +#include +#include + +int main(void) { + ns_autocmd ns_cmd_output out = {0}; + + // The compiler sees a const char* and routes it automatically! + ns_cmd_run("ls -la /var/log", &out); + + return 0; +} +``` + +### 2. Passing Dynamic `ns_string` Structs + +If you are building a command dynamically based on user input or file parsing, +you can pass your NextStd `ns_string` directly into the exact same macro. No +unwrapping or `.ptr` access required: + +```c +#include +#include +#include + +int main(void) { + ns_autocmd ns_cmd_output out = {0}; + ns_string my_dynamic_cmd; + + // Safely build the command + ns_string_new(&my_dynamic_cmd, "echo 'NextStd is awesome!'"); + + // Pass the struct directly! + ns_cmd_run(my_dynamic_cmd, &out); + + // Clean up the input string + ns_string_free(&my_dynamic_cmd); + + return 0; +} +``` + +## Under the Hood: `sh -c` + +When the Rust backend receives your command (regardless of how it was routed), +it securely spawns a new process using `sh -c ""`. + +This mimics the exact behavior of C's `system()`, meaning you have full access +to standard shell features like piping (`|`), redirecting (`>`), and environment +variable expansion (`$USER`), while still running entirely within Rust's +memory-safe execution sandbox. diff --git a/src/cmd/output-capturing.md b/src/cmd/output-capturing.md new file mode 100644 index 0000000..9cea255 --- /dev/null +++ b/src/cmd/output-capturing.md @@ -0,0 +1,74 @@ +# Capturing Stdout & Stderr + +The fatal flaw of standard C's `system()` function is that it blindly dumps the +command's output directly to the user's terminal. If your C program actually +needs to *read* that output to parse a version number, check a status, or log an +error, `system()` is completely useless. + +You are usually forced to use `popen()`, which requires manually allocating +buffers, reading chunks in a `while` loop, and praying you don't trigger a +buffer overflow. + +NextStd completely eliminates this friction. + +## The `ns_cmd_output` Struct + +When you run a command using `ns_cmd_run`, the entire lifecycle of the process +is captured and packed into a single, clean C struct: + +```c +typedef struct ns_cmd_output { + ns_string stdout_data; + ns_string stderr_data; + int exit_code; +} ns_cmd_output; +``` + +### 1. The Output Streams (`stdout_data` & `stderr_data`) + +Instead of raw `char*` arrays that you have to manually resize, NextStd uses the +underlying Rust `std::process::Command` engine to capture the raw bytes of the +streams and convert them directly into memory-safe `ns_string` structs. + +**The SSO Advantage:** Because these streams are backed by NextStd's Small +String Optimization (SSO), if your command outputs less than 24 bytes (like +simply printing `"OK"` or an exit status string), +**zero heap allocation occurs.** The output is stored directly inside the struct +on the stack, making it incredibly fast. + +### 2. The Exit Code + +NextStd captures the exact integer status code returned by the shell. You no +longer have to mess with C's cryptic `WEXITSTATUS` macros just to find out if +your command succeeded. If the process exits cleanly, `exit_code` will be `0`. + +## Example: Parsing Success vs. Failure + +Because NextStd strictly separates standard output from standard error, your C +program can easily build branching logic based on whether a command failed. + +```c +#include +#include + +int main(void) { + ns_autocmd ns_cmd_output out = {0}; + + // Attempting to read a file that doesn't exist + ns_cmd_run("cat /path/to/missing_file.txt", &out); + + if (out.exit_code != 0) { + ns_println("Command failed with code: {}", out.exit_code); + // We can safely read the exact error message the shell generated + ns_println("Error Reason: {}", out.stderr_data); + } else { + ns_println("File Contents: {}", out.stdout_data); + } + + return 0; +} +``` + +By default, these strings are dynamically allocated (if over 23 bytes) and +require cleanup. However, thanks to a powerful compiler extension, you rarely +ever need to free them manually. diff --git a/src/examples/process-exec.md b/src/examples/process-exec.md new file mode 100644 index 0000000..83beaef --- /dev/null +++ b/src/examples/process-exec.md @@ -0,0 +1 @@ +# Process Execution & Auto-Free