Modules

Process

moduleSource
macOSLinuxWindowsAndroid

#import "Process";

Starting programs, feeding them input and collecting their output and exit status.

run_command is the short way: it runs a program to its end, optionally with a time limit and with its output captured, and returns how it ended. create_process starts one that you then drive yourself with write_to_process, read_from_process, get_process_result and kill_process, and release with deinit. No shell is involved: the arguments go to the program as they are, so use /bin/sh -c when you want shell syntax. break_command_into_strings, get_quoted_command_string and append_quoted handle command-line text.

Unix targets (Linux, macOS) start the program with fork and execvp, and report a signal-killed child as SIGNALED; Windows builds one command line, and reports everything as EXITED. A child started with kill_process_if_parent_exits (the default) does not outlive this program. There is no wasm backend.

Example
result, out, err, timed_out := run_command("/bin/sh", "-c", "printf hello", capture_and_return_output = true, timeout_ms = 5000);
assert(!timed_out && result.type == .EXITED && result.exit_code == 0);
assert(out == "hello");
free(out);
free(err);

Doesn't run in the browser (only on linux, macos).

Procedures#

Arguments#

(input: string) -> [..] string

Splits a command line into words: the pieces between spaces, where a piece in double quotes may hold spaces and loses its quotes.

words := break_command_into_strings(" maple   \"red tree\" cedar ");
assert(words.count == 3);
assert(words[0] == "maple" && words[1] == "red tree" && words[2] == "cedar");

Doesn't run in the browser (skipped on wasm).

(args: [] string) -> string

Joins args into one command line with a space between them, each quoted as append_quoted does. The string is new, from context.allocator, and the caller frees it. Use it to show or log a command, or to build a Windows command line.

line := get_quoted_command_string(.["plain", "two words", "say\"hi"]);
assert(line == "plain \"two words\" \"say\\\"hi\"");
free(line);

Doesn't run in the browser (skipped on wasm).

(builder: *String_Builder, s: string)

Appends s to builder, in double quotes when it has to be: when it is empty or holds a space, a tab or a double quote. A quote inside is escaped with a backslash, and backslashes before a quote or before the closing quote are doubled, which are the rules of the Windows command line, so the program at the other end splits it back into the same text.

builder: String_Builder;
append_quoted(*builder, "plain");
append(*builder, " ");
append_quoted(*builder, "two words");
append(*builder, " ");
append_quoted(*builder, "");
text := builder_to_string(*builder);
assert(text == "plain \"two words\" \"\"");
free(text);

Doesn't run in the browser (skipped on wasm).

Run#

(args: ..string, working_directory := "", capture_and_return_output := false, print_captured_output := false, timeout_ms := -1, arg_quoting := Process_Argument_Quoting.QUOTE_IF_NEEDED) -> (process_result: Process_Result, output_string := "", error_string := "", timeout_reached := false)

Runs a program to its end and returns how it ended: (process_result, output_string, error_string, timeout_reached).

result, out, err, timed_out := run_command("/bin/sh", "-c", "printf copper; printf blue >&2; exit 7", capture_and_return_output = true, timeout_ms = 5000);
assert(!timed_out && result.type == .EXITED && result.exit_code == 7);
assert(out == "copper" && err == "blue");
free(out);
free(err);

result, out, err, timed_out = run_command("/bin/sleep", "30", timeout_ms = 20);
assert(timed_out && result.type == .SIGNALED && result.signal == 9);

result, out, err, timed_out = run_command("/jai-doc-no-such-program");
assert(result.type == .FAILED_TO_LAUNCH);

Doesn't run in the browser (only on linux, macos).

Windows#

(process: *Process, args: ..string, working_directory := "", capture_and_return_output := false, arg_quoting := Process_Argument_Quoting.QUOTE_IF_NEEDED, kill_process_if_parent_exits := true) -> success: bool

Starts the program args[0] with the arguments args[1..] and returns true once it is running; the result comes later from get_process_result. process must be a fresh Process.

deinit2 overloads
(process: *Process)

Releases a Process: closes its pipes and handles and leaves it as a fresh record. Unlike on Unix it does not wait for the child. A child started with kill_process_if_parent_exits (the default) is killed when its job handle closes here; one started without it keeps running. Always call it once per successful create_process.

(process: *Process, data: [] u8) -> (success: bool, bytes_written: int)

Writes data to the child's standard input and returns (success, bytes_written). It waits until the pipe has taken everything. It fails with the number of bytes that did go in when the pipe is closed, or the child has closed its end. Empty data succeeds with 0.

(process: *Process, output_buffer: [] u8, error_buffer: [] u8, timeout_ms := -1) -> (success: bool, output_bytes: int, error_bytes: int)

Waits up to timeout_ms for the child's output or error and reads what arrived into the two buffers: (success, output_bytes, error_bytes).

(process: *Process) -> bool

Terminates the child with exit code 1 and returns whether that was done: false for a process that was never started, and true without doing anything for one that has already ended. The result then reads EXITED; collect it with get_process_result.

(process: *Process, timeout_ms := -1) -> (success: bool, Process_Result)

The state of the child as (success, result), waiting up to timeout_ms for it to finish. 0 only checks and -1 (the default) waits for ever. While it runs the result is STILL_RUNNING; afterwards EXITED with the exit code. A process never started is UNSTARTED. success is false when the system could not tell.

(pipe: *Process.Pipe, buffer: [] u8) -> (success: bool, bytes_read: int)

Reads what is available now from one pipe of a process into buffer, and returns (success, bytes_read). 0 bytes with success means nothing is there yet, or the end: check pipe.eof. It fails with a closed pipe or an empty buffer. Use read_from_process to wait for data on both pipes.

(pipe: *Process.Pipe, is_input: bool) -> (success: bool, client_handle: W.HANDLE = W.INVALID_HANDLE_VALUE)

Sets up pipe as one end of a new named pipe and returns the other end for the child, as (success, client_handle). is_input says which way the data flows: true for a pipe the parent writes. The child's handle is inheritable; the caller passes it to the child and closes it. Windows only; create_process uses it, and callers rarely need it.

Types#

Processstruct
:: struct

A child process and its pipes. Make an empty one (process: Process;), start it with create_process, and release it with deinit.

:: struct

The system part of a Process: the job object that holds the child (so it can be killed with its descendants), and the PROCESS_INFORMATION with its handles and ids.

:: struct

The system part of a Process.Pipe: the pipe handle (INVALID_HANDLE_VALUE when there is none) and the overlapped record its reads and writes use. Read it, but let the procedures of this module do the closing.

:: enum

Whether create_process and run_command put quotes around the arguments they are given. Windows starts a program from one command-line string, so the arguments are joined into it.

:: enum

How far a process has got, as Process_Result.type: UNSTARTED (nothing was created), STILL_RUNNING, FAILED_TO_LAUNCH (run_command could not start the program) and EXITED with its exit_code. SIGNALED is never produced on Windows; a process that was killed is EXITED.

:: struct

How a process ended: the type, the exit_code when it .EXITED (-1 otherwise) and the signal, which is always 0 on Windows.