libgetargv(3) Library Functions Manual libgetargv(3)

libgetargvlibrary functions for getting and printing other processes' args

library “libgetargv”

#include <libgetargv.h>

struct GetArgvOptions { int skip; pid_t pid; bool nuls; };
struct ArgvArgcResult { char* buffer; char** argv; uint argc; };
struct ArgvResult { char* buffer; char* start_pointer; char* end_pointer; };

bool
get_argv_of_pid(const struct GetArgvOptions* options, struct ArgvResult* result);

bool
get_argv_and_argc_of_pid(pid_t pid, struct ArgvArgcResult* result);

bool
print_argv_of_pid(char* start_pointer, char* end_pointer);

void
free_ArgvResult(struct ArgvResult* result);

void
free_ArgvArgcResult(struct ArgvArgcResult* result);

The first and only library to provide a correct interface to the KERN_PROCARGS2 sysctl. Every other program/library (including Apple's ps(1)) incorrectly indexes the arguments while parsing, in the case of empty leading arguments; resulting in the inclusion of env vars in addition to arguments. This is because while it is conventional to put the executable name in argv[0], it is not enforced by xnu (the kernel), however everyone else relies on it being true.

() takes as arguments: a pointer to a struct GetArgvOptions which controls: whether the NULs which separate the arguments in argv are replaced with spaces (.nuls = true), how many leading arguments to skip (.skip = #), and the pid to target (.pid = #); and a pointer to a struct ArgvResult. The results of the function are populated in the result struct: (.start_pointer) is a pointer to the start of the argv (skipped ahead by as many args as requested), (.end_pointer) is a pointer to the end of the argv, (.buffer) is the entire raw buffer which it is the caller's responsibility to free(3) (you can use free_ArgvResult() to ensure you use the matching free(3) function to this library's malloc(3) function). The return value of the function is a boolean indicating success (true) or failure (false); in case of failure errno(2) is set, and the buffer pointer in result, is set to NULL. In the case where there are no arguments to return (.argc == 0) or (.skip == argc) then .start_pointer, .end_pointer, and .buffer are set to NULL. Use get_argv_and_argc_of_pid() if you want to inspect the results and not just print them.

() is an alternative to get_argv_of_pid() which returns the argc and argv of a pid, with argv formatted as a char**, just like if it was passed to (). It takes as arguments a pid_t pid, and a pointer to an ArgvArgcResult struct which will be populated with the argc and argv of the pid, and the backing buffer from the sysctl. It is the caller's responsibility to free(3) the backing buffer (.buffer) and the (.argv) array (you can use free_ArgvArgcResult() to ensure you use the matching free(3) function to this library's malloc(3) function), except when argc is 0 in which case the buffer and argv array are set to NULL. The return value of the function is a bool indicating success (true) or failure (false); in case of failure errno(2) is set, and the pointers in result are set to NULL.

() is a safe way to free(3) the pointer in a struct ArgvResult using the matching free(3) function to the malloc(3) that allocated the buffer it holds a pointer to. This function does not return a value.

() is a safe way to free(3) the pointers in a struct ArgvArgcResult using the matching free(3) function to the malloc(3) that allocated the buffers it holds pointers to. This function does not return a value.

In addition to the errors documented for the sysctl(3) and write(2) system calls, the functions in libgetargv (except the free_Argv*Result functions) may return false to indicate failure, and set errno(2) as described below:

[]
Targeted pid's args either are not NUL-terminated or there were none despite pid's argc being > 0, essentially the system is in an invalid state and should be rebooted ASAP as the kernel is producing garbage output.
[]
The targeted pid doesn't exist.
[]
User doesn't have permission to see targeted pid's args.
[]
Targeted pid's args are too long (somehow longer than ARG_MAX) and cannot be parsed safely.
[]
Function was asked to skip more args than targeted pid has.

I believe libgetargv is thread-safe under the following conditions: errno(2) is thread-local, () is thread safe, () is thread safe, as is copying out of the buffer it populates; these conditions are met on all macOS versions I've tested. Note: the arguments passed into the functions in libgetargv must not be modified (or freed) by other threads until after the functions return. Whether you use thread-local storage, or mutexes, or some other synchronization primitive, or separate blocks of shared memory, or some other approach, is up to you. Also note that: calling print_argv_of_pid() from multiple threads may interleave the output, so you may want to use (stdout) to prevent this.

I've spent a lot of time optimizing the performance of this library. All functions use the most efficient algorithm possible, and the fewest memory allocations possible. I've also benchmarked the functions I use from the C std library in order to choose the most optimized versions available. I regularly benchmark my code using hyperfine to test for performance regressions.

The () function may be slower than get_argv_of_pid() as it must perform an additional malloc(), though it has fewer conditionals per loop so if there are few arguments then it could wind up being faster. Both functions have the same time complexity: (n) where n is the length of the arguments to pid in bytes.

The following programs show minimal use of the entire library API.

See the file test/src/libtest1.c.

See the file test/src/libtest2.c.

getargv(1), ps(1), errno(2), sysctl(3), stdbool.h(0p), free(3)

The libgetargv library conforms to the xnu kernel's KERN_PROCARGS2 sysctl.

get the argv of the specified pid as a buffer
get the argc and argv of the specified pid in standard format
free the pointer in an ArgvResult struct using the correct free function
free the pointers in an ArgvArgcResult struct using the correct free function

This library does not necessarily interact well with other libraries due to unilaterally defining bool if <stdbool.h> isn't available. This is only a problem when compiling C in std < ISO/IEC 9899:1999 (“ISO C99”). If you do experience a conflict, you can remove the definition of bool from the header, so long as you only include the header after you define bool in your program.

January 5, 2023 macOS 14.1