carb::launcher::ILauncher

Defined in carb/launcher/ILauncher.h

struct ILauncher

A simple process launcher helper interface.

This is responsible for creating child processes, tracking their lifetime, and communicating with the child processes. All operations on this interface are thread safe within themselves. It is the caller’s responsibility to manage the lifetime of and any multi-threaded access to each process handle object that is returned from launchProcess(). For example, destroying a process handle object while another thread is operating on it will result in undefined behavior.

Linux notes:

  • If any other component of the software using this plugin sets the SIGCHLD handler back to default behavior (ie: SIG_DFL) or installs a SIGCHLD handler function, any child process that exits while the parent is still running will become a zombie process. The only way to remove a zombie process in this case is to wait on it in the parent process with waitProcessExit() or getProcessExitCode() at some point after the child process has exited. Explicitly killing the child process with killProcess() will also avoid creating a zombie process.

  • Setting the SIGCHLD handler to be ignored (ie: SIG_IGN) will completely break the ability to wait on the exit of any child process for this entire process. Any call to wait on a child process such as waitProcessExit() or getProcessExitCode() will fail immediately. There is no way to detect or work around this situation on the side of this interface since signal handlers are process-wide and can be installed at any time by any component of the process.

  • The problem with leaving a zombie processes hanging around is that it continues to take up a slot in the kernel’s process table. If this process table fills up, the kernel will not be able to launch any new processes (or threads). All of the zombie children of a process will be automatically cleaned up when the parent exits however.

  • If a read callback is provided for either stdout or stderr of the child process and the parent process destroys its process handle for the child process before the child process exits, the child process will be terminated with SIGPIPE if it ever tries to write to either stream again. This is the default Linux kernel behavior for writing to a broken pipe or socket. The only portable way around this is to ensure the child process will ignore SIGPIPE signals. It is generally best practice to ensure the parent process waits on all child processes before destroying its process handle. This also prevents the appearance of zombie processes as mentioned above.

Windows notes:

  • Reading from a child process’s stdout or stderr streams will not necessarily be aligned to the end of a single ‘message’ (ie: the contents of a single write call on the child process’s end of the stream). This means that a partial message may be received in the callbacks registered during the launch of a child process. It is left up to the caller to accumulate input from these streams until an appropriate delimiter has been reached and the received data can be fully parsed. This may be fixed in a future version of this interface.

Public Functions

inline ProcessId launchProcessDetached(LaunchDesc &desc)

Launches a detached child process.

Remark

This is a convenience version of launchProcess() that launches a child process but does not return the process handle. Instead the operating system’s process ID for the new child process is returned. This is intended to be used for situations where the parent process neither needs to communicate with the child process nor know when it has exited. Using this should be reserved for child processes that manage their own lifetime and communication with the parent in another prearranged manner. The returned process ID may be used in OS level process management functions, but is not useful to pass into any other ILauncher functions.

Parameters

desc[in] A descriptor of the child process to launch. At least the LaunchDesc::argc and LaunchDesc::argv members must be filled in. Default values on all other members are sufficient. Note that setting a read callback for stdout or stderr is not allowed in a detached process. The LaunchDesc::onReadStderr and LaunchDesc::onReadStdout parameters will be cleared to nullptr before launching the child process. Callers may still redirect stdout and stderr to log files however. The fLaunchFlagLaunchDetached flag will also be added to the descriptor so that zombie processes are not inadvertently created on Unix based platforms.

Return values

kBadId – if the new process could not be launched for any reason. This may include insufficient permissions, failed memory or resource allocations, etc.

Returns

The process ID of the new child process if successfully launched.

inline bool isProcessActive(Process *process)

Tests whether a process is still running.

Parameters

process[in] The process handle object representing the child process to query. This may not be nullptr.

Returns

true if the given child process is still running.

Returns

false if the child process has exited.

Public Members

Process *(*launchProcess)(LaunchDesc &desc)

Launches a new child process.

Remark

This attempts to launch a new child process. The new process will be created and start to run before successful return here. Depending on what flags and callbacks are provided in the launch descriptor, it may be possible for this process to communicate with the new child process.

Note

On Linux, the child process’s executable should not have the set-user-ID or set- group-ID capabilities set on it when using the carb::launcher::fLaunchFlagKillOnParentExit flag. These executables will clear the setting that allows the child process to receive a signal when the parent process exits for any reason (ie: exits gracefully, crashes, is externally terminated, etc). Setting these capabilities on an executable file requires running something along the lines of:

  • sudo setcap cap_setuid+ep <filename>
    sudo setcap cap_setgid+ep <filename>
    

Note

Any built executable will, be default, not have any of these capabilities set. They will have to be explicitly set by a user, installer, or script. These executables can be identified in an “ls -laF” output by either being highlighted in red (default bash color scheme), or by having the ‘-rwsr-xr-x’ permissions (or similar) instead of ‘-rwxr-xr-x’, or by passing the executable path to ‘capsh &#8212;print’ to see what it’s effective or permissive capabilities are.

Note

If a set-user-ID or set-group-ID executable needs to be launched as a child process and the fLaunchFlagKillOnParentExit flag is desired for it, the child process should call

prctl(PR_SET_PDEATHSIG, SIGTERM)
early in its main() function to regain this behavior. The restoreParentDeathSignal() helper function is also offered here to make that call easier in Carbonite apps. Note however that there will be a possible race condition in this case - if the parent exits before the child process calls prctl(), the child process will not be signaled. Also note that unconditionally calling one of these functions in a child process will result in the fLaunchFlagKillOnParentExit behavior being imposed on that child process regardless of whether the flag was used by the parent process.

Note

Since we can neither guarantee the child process will be a Carbonite app nor that it will even be source under control of the same developer (ie: a command line tool), resetting this death signal is unfortunately not handled as a task in this interface.

Param desc

[in] A descriptor of the child process to launch. At least the LaunchDesc::argc and LaunchDesc::argv members must be filled in. Default values on all other members are sufficient.

Return

A new process object if the child process is successfully launched. This must be destroyed with destroyProcessHandle() when it is no longer needed by the caller. Note that closing the process object will not terminate the child process. It simply means that the calling process can no longer communicate with or kill the child process. If the child process needs to be killed first, it is the caller’s responsibility to call kill() before destroying the handle.

Return

nullptr if the new process could not be launched for any reason. This may include insufficient permissions, failed memory or resource allocations, etc.

void (*destroyProcessHandle)(Process *process)

Destroys a process handle when it is no longer needed.

Remark

This destroys a process handle object that was previously returned by a call to launchProcess(). The process handle will no longer be valid upon return.

Note

Calling this does not kill the child process the process handle refers to. It simply recovers the resources taken up by the process handle object. In order to kill the child process, the caller must call killProcess() first.

Note

[Linux] If the child process was started with a read callback for one or both of its standard streams, destroying the handle here will cause the child process to be terminated with SIGPIPE if it ever tries to write to any of the standard streams that were redirected to the read callbacks. This is the default Linux kernel behavior for attempting to write to a broken pipe or socket. The only portable way to work around this is to ensure the child process ignores SIGPIPE signals. However, the general best practice is for the parent process to wait for the child process to exit (or explicitly kill it) before destroying the process handle.

Param process

[in] The process handle to destroy. This will no longer be valid upon return. This call will be silently ignored if nullptr is passed in.

Return

No return value.

ProcessId (*getProcessId)(Process *process)

Retrieves the process identifier for a child process.

Remark

This retrieves the process identifier for a child process. This can be used to gain more advanced platform specific access to the child process if needed, or simply for debugging or logging identification purposes.

Param process

[in] The process handle object representing the child process to retrieve the identifier for. This may not be nullptr.

Retval kBadId

if the child process if it is not running.

Return

The integer identifier of the child process if it is still running.

ExitCode (*waitProcessExit)(Process *process, uint64_t timeout)

Waits for a child process to exit.

Remark

This waits for a child process to exit and retrieves its exit code if the exit has occurred. Note that this does not in any way signal the child process to exit. That is left up to the caller since that method would be different for each child process. This simply waits for the exit to occur. If the child process doesn’t exit within the allotted timeout period, it will remain running and the special kStillActive exit code will be returned.

Note

Despite the timeout value being a 64-bit value, on Windows this will only wait for up to ~49.7 days at a time (ie: 32 bits) for the child process to exit. This is due to all the underlying timing functions only taking a 32-bit timeout value. If a longer wait is required, the wait will be repeated with the remaining time. However, if this process is blocking for that long waiting for the child process, that might not be the most desirable behavior and a redesign might be warranted.

Note

[Linux] if the calling process sets a SIG_IGN signal handler for the SIGCHLD signal, this call will fail immediately regardless of the child process’s current running state. If a handler function is installed for SIGCHLD or that signal’s handler is still set to its default behavior (ie: SIG_DFL), the child process will become a zombie process once it exits. This will continue to occupy a slot in the kernel’s process table until the child process is waited on by the parent process. If the kernel’s process table fills up with zombie processes, the system will no longer be able to create new processes or threads.

Note

[Linux] It is considered an programming error to allow a child process to be leaked or to destroy the process handle without first waiting on the child process. If the handle is destroyed without waiting for the child process to [successfully] exit first, a zombie process will be created. The only exception to this is if the parent process exits before the child process. In this case, any zombie child child processes will be inherited by the init (1) system process which will wait on them and recover their resources.

Note

In general it is best practice to always wait on all child processes before destroying the process handle object. This guarantees that any zombie children will be removed and that all resources will be cleaned up from the child process.

Param process

[in] The process handle object representing the child process to wait for. This may not be nullptr. This is returned from a previous call to launchProcess().

Param timeout

[in] The maximum time in milliseconds to wait for the child process to exit. This may be kInfiniteTimeout to specify that this should block until the child process exits. This may be 0 to simply poll whether the child process has exited or not.

Retval kStillActive

if the wait timed out and the child process had not exited yet.

Return

The exit code of the child process if it successfully exited in some manner.

Return

EXIT_FAILURE if process is nullptr.

ExitCode (*getProcessExitCode)(Process *process)

Attempts to retrieve the exit code for a child process.

Remark

This attempts to retrieve the exit code for a child process if it has exited. The exit code isn’t set until the child process exits, is killed, or crashes. The special exit code kStillActive will be returned if the child process is still running.

Note

[Linux] see the notes about zombie processes in waitProcessExit(). These also apply to this function since it will also perform a short wait to see if the child process has exited. If it has exited already, calling this will also effectively clean up the child zombie process. However, if another component of this process has set the SIGCHLD signal handler to be ignored (ie: SIG_IGN), this call will also fail immediately.

Param process

[in] The process handle object representing the child process to retrieve the exit code for. The child process may or may not still be running. This may not be nullptr.

Retval kStillActive

if the child process is still running.

Return

The exit code of the child process if it has already exited in some manner.

Return

EXIT_FAILURE if process is nullptr.

bool (*writeProcessStdin)(Process *process, const void *data, size_t bytes)

Writes a buffer of data to the stdin stream of a child process.

Remark

This attempts to write a buffer of data to a child process’s stdin stream. This call will be ignored if the child process was not launched using the fLaunchFlagOpenStdin flag or if the stdin handle for the process has since been closed with closeProcessStdin(). The entire buffer will be written to the child process’s stdin stream. If even one byte cannot be successfully written, this call will fail. This can handle data buffers larger than 4GB if needed. This will block until all data is written.

Remark

When the kNullTerminated value is used in bytes, the data buffer is expected to contain a null terminated UTF-8 string. The caller is responsible for ensuring this. In the case of a null terminated string, all of the string up to but not including the null terminator will be sent to the child process. If the null terminator is to be sent as well, the caller must specify an explicit byte count.

Param process

[in] The process handle object representing the child process to write the data to. This may not be nullptr. The child process must have been opened using the fLaunchFlagOpenStdin flag in order for this to succeed.

Param data

[in] The buffer of data to write to the child process.

Param bytes

[in] The total number of bytes to write to the child process or the special value kNullTerminated to indicate that the buffer data is a null-terminated UTF-8 string. If the latter value is used, it is the caller’s responsibility to ensure the data buffer is indeed null terminated.

Return

true if the buffer of data is successfully written to the child process’s stdin stream.

Return

false if the entire data buffer could not be written to the child process’s stdin stream. In this case, the child process’s stdin stream will be left in an unknown state. It is the responsibility of the caller and the child process to negotiate a way to resynchronize the stdin stream.

Return

false if process is nullptr.

void (*closeProcessStdin)(Process *process)

Closes the stdin stream of a child process.

Remark

This closes the stdin stream of a child process. This call will be ignored if the stdin stream for the child process either was never opened or if it has already been closed by a previous call to closeProcessStdin(). Closing the stdin stream only closes the side of the stream that is owned by the calling process. It will not close the actual stream owned by the child process itself. Other processes may still retrieve the child process’s stdin stream handle in other ways if needed to communicate with it.

Param process

[in] The process handle object representing the child process to close the stdin stream for. This may not be nullptr.

Return

No return value.

void (*killProcess)(Process *process, KillFlags flags)

Kills a running child process.

Remark

This kills a running child process. If the process has already exited on its own, this call will be ignored. The child process will be terminated without being given any chance for it to clean up any of its state or save any information to persistent storage. This could result in corrupted data if the child process is in the middle of writing to persistent storage when it is terminated. It is the caller’s responsibility to ensure the child process is as safe as possible to terminate before calling.

Remark

On both Windows and Linux, this will set an exit code of either 137 if the fKillFlagForce flag is used or 143 otherwise. These values are the exit codes that are set in Linux by default for a terminated process. They are 128 plus the signal code used to terminate the process. In the case of a forced kill, this sends SIGKILL (9). In the case of a normal kill, this sends SIGTERM (15). On Windows, this behavior is simply mimicked.

Note

On Linux, the fKillFlagForce flag is available to cause the child process to be sent a SIGKILL signal instead of the default SIGTERM. The difference between the two signals is that SIGTERM can be caught and handled by the process whereas SIGKILL cannot. Having SIGTERM be sent to end the child process on Linux does allow for the possibility of a graceful shutdown for apps that handle the signal. On Linux, it is recommended that if a child process needs to be killed, it is first sent SIGTERM (by not using the fKillFlagForce flag), then if after a short time (ie: 2-5 seconds, depending on the app’s shutdown behavior) has not exited, kill it again using the fKillFlagForce flag.

Param process

[in] The process handle object representing the child process to kill. This may not be nullptr.

Param flags

[in] Flags to affect the behavior of this call. This may be zero or more of the KillFlags flags.

Return

No return value.

KillStatus (*killProcessWithTimeout)(Process *process, KillFlags flags, uint64_t timeout)

Kills a running child process.

Remark

This kills a running child process. If the process has already exited on its own, this call will be ignored. The child process will be terminated without being given any chance for it to clean up any of its state or save any information to persistent storage. This could result in corrupted data if the child process is in the middle of writing to persistent storage when it is terminated. It is the caller’s responsibility to ensure the child process is as safe as possible to terminate before calling.

Remark

On both Windows and Linux, this will set an exit code of either 137 if the fKillFlagForce flag is used or 143 otherwise. These values are the exit codes that are set in Linux by default for a terminated process. They are 128 plus the signal code used to terminate the process. In the case of a forced kill, this sends SIGKILL (9). In the case of a normal kill, this sends SIGTERM (15). On Windows, this behavior is simply mimicked.

Remark

This variant of killProcess() can be used to have more control over how the child process is terminated and whether it was successful or not. The timeout allows the caller control over how long the call should wait for the child process to exit. This can also be used in combination with other functions such as ILauncher::isDebuggerAttached() to figure out how and when a child process should be terminated.

Note

On Linux, the fKillFlagForce flag is available to cause the child process to be sent a SIGKILL signal instead of the default SIGTERM. The difference between the two signals is that SIGTERM can be caught and handled by the process whereas SIGKILL cannot. Having SIGTERM be sent to end the child process on Linux does allow for the possibility of a graceful shutdown for apps that handle the signal. On Linux, it is recommended that if a child process needs to be killed, it is first sent SIGTERM (by not using the fKillFlagForce flag), then if after a short time (ie: 2-5 seconds, depending on the app’s shutdown behavior) has not exited, kill it again using the fKillFlagForce flag.

Param process

[in] The process handle object representing the child process to kill. This may not be nullptr.

Param flags

[in] Flags to affect the behavior of this call. This may be zero or more of the KillFlags flags.

Param timeout

[in] The time in milliseconds to wait for the child process to fully exit. This may be 0 to indicate that no wait should occur after signaling the child process to terminate. This may also be kInfiniteTimeout to wait indefinitely for the child process to exit.

Retval KillStatus::eSuccess

if the child process is successfully terminated and was confirmed to have exited or the fKillFlagSkipWait flag is used and the child process is successfully signaled to exit, or if the child process had already terminated on its own or was otherwise killed before this call.

Retval KillStatus::eWaitFailed

if the child process was successfully signaled to exit but the timeout for the wait for it to fully exit expired before it exited. This may still indicate successful termination, but it is left up to the caller to determine that.

Return

No return value.

Return

Another KillStatus code if the termination failed in any way.

bool (*isDebuggerAttached)(Process *process)

Tests whether a debugger is currently attached to a child process.

Remark

This tests whether a debugger is currently attached to the given child process. On Windows at least, having a debugger attached to a child process will prevent it from being terminated with killProcess(). This can be queried to see if the debugger task has completed before attempting to kill it.

Param process

[in] The child process to check the debugger status for. This may not be nullptr. This may be a handle to a child process that has already exited.

Return

true if there is a debugger currently attached to the given child process.

Return

false if the child process has exited or no debugger is currently attached to it.

Return

false if process is nullptr.

bool (*waitForStreamEnd)(Process *process, WaitFlags flags, uint64_t timeout)

Waits for one or more standard streams from a child process to end.

Remark

This is used to wait on one or more of the standard streams (stdout or stderr) from the child process to end. This ensures that all data coming from the child process has been consumed and delivered to the reader callbacks.

Note

This does not allow for waiting on the standard streams of child processes that are writing directly to log files (ie: using the LaunchDesc::stdoutLog and LaunchDesc::stderrLog parameters). This only affects child processes that were launched with a read callback specified for stdout, stderr, or both.

Param process

[in] The child process to wait on the standard streams for. This may not be nullptr. This may be a handle to a child process that has already exited. This call will be ignored if the flagged stream(s) were not opened for the given child process.

Param flags

[in] Flags to control how the operation occurs and which stream(s) to wait on. At least one flag must be specified. This may not be 0. It is not an error to specify flags for a stream that was not opened by the child process. In this case, the corresponding flag(s) will simply be ignored.

Param timeout

[in] The maximum amount of time in milliseconds to wait for the flagged streams to end. This may be kInfiniteTimeout to wait infinitely for the stream(s) to end. A stream ends when it is closed by either the child process (by it exiting or explicitly closing its standard streams) or by the parent process closing it by using one of the fWaitFlagCloseStdOutStream or fWaitFlagCloseStdErrStream flags. When a stream ends due to the child process closing it, all of the pending data will have been consumed by the reader thread and already delivered to the read callback function for that stream.

Return

true if the wait operation specified by flags completed successfully. This means that at least one, possibly both, streams have ended and been fully consumed.

Return

false if the flagged streams did not end within the timeout period.

Public Static Functions

static inline constexpr carb::InterfaceDesc getInterfaceDesc() noexcept

Returns information about this interface.

Auto-generated by CARB_PLUGIN_INTERFACE() or CARB_PLUGIN_INTERFACE_EX.

Returns

The carb::InterfaceDesc struct with information about this interface.

static inline constexpr carb::InterfaceDesc getLatestInterfaceDesc() noexcept

Returns information about the latest version of this interface.

Auto-generated by CARB_PLUGIN_INTERFACE() or CARB_PLUGIN_INTERFACE_EX.

Returns

The carb::InterfaceDesc struct with information about the latest version of this interface.