libwebsockets
Lightweight C library for HTML5 websockets
Loading...
Searching...
No Matches
Spawning and Managing Child Processes with <tt>lws_spawn</tt>

The lws_spawn API provides a robust, platform-agnostic way to create and manage child processes directly from your C application, fully integrated with the libwebsockets event loop.

Overview

The lws_spawn API is designed to securely run external executables as child processes. Its key features include:

  • Event-Loop Integration: The child process lifecycle is managed without blocking the main lws event loop.
  • Standard I/O Redirection: The child's stdin, stdout, and stderr are automatically redirected to pipes. These pipes are represented as standard struct lws connection instances (wsi), allowing you to interact with the child process using familiar lws protocol callbacks.
  • Automatic Lifecycle Management: Lws handles waiting for and "reaping" the terminated child process, providing a callback with its exit status and resource accounting information.
  • Security: Provides options for setting a chroot() jail and the working directory for the child process.
  • Timeout Management: An optional timeout can be specified to automatically kill a child process that runs for too long.
  • Linux Cgroup Containment: On Linux, spawned processes can be automatically placed into a new, dedicated cgroup for resource control and isolation. The cgroup is automatically removed when the process is reaped.

Core API Usage

The primary entrypoint for this API is lws_spawn_piped(). It takes a single argument: a pointer to a struct lws_spawn_piped_info which you populate to describe the child process you want to create.

Key members of struct lws_spawn_piped_info:

  • exec_array: A NULL-terminated array of strings for the executable path and its arguments (equivalent to argv).
  • env_array: A NULL-terminated array of strings for the child's environment variables (e.g., {"VAR1=VALUE1", "VAR2=VALUE2", NULL}).
  • vh: The lws_vhost the new stdio wsi should be associated with.
  • protocol_name: The name of the lws protocol that will handle events for the stdio wsi. This is mandatory for proper cleanup.
  • reap_cb: A mandatory callback function of type lsp_cb_t that lws will call after the child process has terminated and been reaped.
  • opaque: A user pointer that will be passed to your reap_cb and will also be available on the stdio wsi via lws_get_opaque_user_data().
  • timeout_us: Optional timeout in microseconds. If the process runs longer than this, it will be sent a SIGTERM.

Lifecycle and Cleanup

Proper cleanup is essential. When the child process exits, its stdio pipes are closed by the operating system. This generates a LWS_CALLBACK_RAW_FILE_CLOSE event on each of the three stdio wsi.

Your protocol handler must implement a case for this reason and call lws_spawn_stdwsi_closed():

static int my_spawn_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, ...)
{
struct my_spawn_state *st = (struct my_spawn_state *)
switch (reason) {
case LWS_CALLBACK_RAW_FILE_CLOSE:
if (st && st->lsp)
lws_spawn_stdwsi_closed(st->lsp, wsi);
break;
/* ... other cases ... */
}
return 0;
}
LWS_VISIBLE LWS_EXTERN void * lws_get_opaque_user_data(const struct lws *wsi)
lws_callback_reasons

When lws has been notified that all three stdio wsi have closed, it will proceed to reap the child process and invoke your reap_cb.

Linux Cgroup Support

On Linux, lws_spawn can automatically create a transient cgroup v2 for each spawned process, providing resource isolation. The cgroup is automatically removed when the process is reaped.

Permissions and One-Time Setup

By default, creating new cgroups in /sys/fs/cgroup/ requires root privileges. A non-root user will get a "Permission Denied" error.

The recommended solution is to have an administrator (or a CI setup script) perform a one-time setup to delegate control of a subdirectory to the user running the lws application:

# Run these commands as root once
sudo mkdir -p /sys/fs/cgroup/lws
sudo chown myuser:mygroup /sys/fs/cgroup/lws
echo "+cpu +memory +pids +io" | sudo tee /sys/fs/cgroup/lws/cgroup.subtree_control

For applications that start as root and then drop privileges, lws provides a helper function to perform this setup programmatically: int lws_spawn_cgroup_admin_init(const char *toplevel_name, const char *owner_or_NULL, const char *group_or_NULL);. Call this function once at startup while your process still has root privileges. The cgroup directory will take on the ownership and group given in the args, NULL means to skip the change. If toplevel_name is NULL, it defaults to the builtin one "lws" as the toplevel token. It returns success (0) if the toplevel entry already existed as well as if successfully created.

Typically it will called by a daemon during init while it is still root, and configured to prepare the cgroup dir ownership for the less-privileged user / group it subsequently runs under.

API Usage

To enable cgroup containment for a spawned process, set the following members in your struct lws_spawn_piped_info:

  • cgroup_name_suffix: A string that will be used to name the cgroup directory. For example, a suffix of "lws/my-task" will create a cgroup at /sys/fs/cgroup/lws/my-task. This must be unique for concurrent spawns.
  • p_cgroup_ret: An optional pointer to an int. If provided, lws will write 0 to this integer on successful cgroup creation, and 1 on failure.

If cgroup creation fails (e.g., due to permissions), lws_spawn_piped() will not fail. It will log a warning and proceed to spawn the process without cgroup containment. Use p_cgroup_ret to confirm the outcome.

Example

For a complete, working example that demonstrates these concepts, including cgroup setup and verification, please refer to the API test located at:

minimal-examples-lowlevel/api-tests/api-test-lws_spawn/