03de9224fc
Add a tool to easily observe process termination. This allows to move this complexity out of the server code.
177 lines
4.1 KiB
C
177 lines
4.1 KiB
C
#ifndef SC_PROCESS_H
|
|
#define SC_PROCESS_H
|
|
|
|
#include "common.h"
|
|
|
|
#include <stdbool.h>
|
|
#include "util/thread.h"
|
|
|
|
#ifdef _WIN32
|
|
|
|
// not needed here, but winsock2.h must never be included AFTER windows.h
|
|
# include <winsock2.h>
|
|
# include <windows.h>
|
|
# define SC_PRIexitcode "lu"
|
|
// <https://stackoverflow.com/a/44383330/1987178>
|
|
# define SC_PRIsizet "Iu"
|
|
# define SC_PROCESS_NONE NULL
|
|
# define SC_EXIT_CODE_NONE -1u // max value as unsigned
|
|
typedef HANDLE sc_pid;
|
|
typedef DWORD sc_exit_code;
|
|
typedef HANDLE sc_pipe;
|
|
|
|
#else
|
|
|
|
# include <sys/types.h>
|
|
# define SC_PRIsizet "zu"
|
|
# define SC_PRIexitcode "d"
|
|
# define SC_PROCESS_NONE -1
|
|
# define SC_EXIT_CODE_NONE -1
|
|
typedef pid_t sc_pid;
|
|
typedef int sc_exit_code;
|
|
typedef int sc_pipe;
|
|
|
|
#endif
|
|
|
|
struct sc_process_listener {
|
|
void (*on_terminated)(void *userdata);
|
|
};
|
|
|
|
/**
|
|
* Tool to observe process termination
|
|
*
|
|
* To keep things simple and multiplatform, it runs a separate thread to wait
|
|
* for process termination (without closing the process to avoid race
|
|
* conditions).
|
|
*
|
|
* It allows a caller to block until the process is terminated (with a
|
|
* timeout), and to be notified asynchronously from the observer thread.
|
|
*
|
|
* The process is not owned by the observer (the observer will never close it).
|
|
*/
|
|
struct sc_process_observer {
|
|
sc_pid pid;
|
|
|
|
sc_mutex mutex;
|
|
sc_cond cond_terminated;
|
|
bool terminated;
|
|
|
|
sc_thread thread;
|
|
const struct sc_process_listener *listener;
|
|
void *listener_userdata;
|
|
};
|
|
|
|
enum sc_process_result {
|
|
SC_PROCESS_SUCCESS,
|
|
SC_PROCESS_ERROR_GENERIC,
|
|
SC_PROCESS_ERROR_MISSING_BINARY,
|
|
};
|
|
|
|
/**
|
|
* Execute the command and write the process id to `pid`
|
|
*/
|
|
enum sc_process_result
|
|
sc_process_execute(const char *const argv[], sc_pid *pid);
|
|
|
|
/**
|
|
* Execute the command and write the process id to `pid`
|
|
*
|
|
* If not NULL, provide a pipe for stdin (`pin`), stdout (`pout`) and stderr
|
|
* (`perr`).
|
|
*/
|
|
enum sc_process_result
|
|
sc_process_execute_p(const char *const argv[], sc_pid *pid,
|
|
sc_pipe *pin, sc_pipe *pout, sc_pipe *perr);
|
|
|
|
/**
|
|
* Kill the process
|
|
*/
|
|
bool
|
|
sc_process_terminate(sc_pid pid);
|
|
|
|
/**
|
|
* Wait and close the process (similar to waitpid())
|
|
*
|
|
* The `close` flag indicates if the process must be _closed_ (reaped) (passing
|
|
* false is equivalent to enable WNOWAIT in waitid()).
|
|
*/
|
|
sc_exit_code
|
|
sc_process_wait(sc_pid pid, bool close);
|
|
|
|
/**
|
|
* Close (reap) the process
|
|
*
|
|
* Semantically:
|
|
* sc_process_wait(close) = sc_process_wait(noclose) + sc_process_close()
|
|
*/
|
|
void
|
|
sc_process_close(sc_pid pid);
|
|
|
|
/**
|
|
* Convenience function to wait for a successful process execution
|
|
*
|
|
* Automatically log process errors with the provided process name.
|
|
*/
|
|
bool
|
|
sc_process_check_success(sc_pid pid, const char *name, bool close);
|
|
|
|
/**
|
|
* Read from the pipe
|
|
*
|
|
* Same semantic as read().
|
|
*/
|
|
ssize_t
|
|
sc_pipe_read(sc_pipe pipe, char *data, size_t len);
|
|
|
|
/**
|
|
* Read exactly `len` chars from a pipe (unless EOF)
|
|
*/
|
|
ssize_t
|
|
sc_pipe_read_all(sc_pipe pipe, char *data, size_t len);
|
|
|
|
/**
|
|
* Close the pipe
|
|
*/
|
|
void
|
|
sc_pipe_close(sc_pipe pipe);
|
|
|
|
/**
|
|
* Start observing process
|
|
*
|
|
* The listener is optional. If set, its callback will be called from the
|
|
* observer thread once the process is terminated.
|
|
*/
|
|
bool
|
|
sc_process_observer_init(struct sc_process_observer *observer, sc_pid pid,
|
|
const struct sc_process_listener *listener,
|
|
void *listener_userdata);
|
|
|
|
/**
|
|
* Wait for process termination until a deadline
|
|
*
|
|
* Return true if the process is already terminated. Return false if the
|
|
* process terminatation has not been detected yet (however, it may have
|
|
* terminated in the meantime).
|
|
*
|
|
* To wait without timeout/deadline, just use sc_process_wait() instead.
|
|
*/
|
|
bool
|
|
sc_process_observer_timedwait(struct sc_process_observer *observer,
|
|
sc_tick deadline);
|
|
|
|
/**
|
|
* Join the observer thread
|
|
*/
|
|
void
|
|
sc_process_observer_join(struct sc_process_observer *observer);
|
|
|
|
/**
|
|
* Destroy the observer
|
|
*
|
|
* This does not close the associated process.
|
|
*/
|
|
void
|
|
sc_process_observer_destroy(struct sc_process_observer *observer);
|
|
|
|
#endif
|