Browse Source
* Create an API to spawn a process, control it and communicate with it though pipes.update/opus-1.6.1
committed by
Steve Lhomme
7 changed files with 635 additions and 3 deletions
@ -0,0 +1,114 @@ |
|||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|||
/*****************************************************************************
|
|||
* vlc_process.h: vlc_process functions |
|||
***************************************************************************** |
|||
* Copyright © 2025 Videolabs, VideoLAN and VLC authors |
|||
* |
|||
* Authors: Gabriel Lafond Thenaille <gabriel@videolabs.io> |
|||
*****************************************************************************/ |
|||
|
|||
#ifndef VLC_PROCESS_H |
|||
#define VLC_PROCESS_H |
|||
|
|||
#include <stdatomic.h> |
|||
|
|||
#include <vlc_common.h> |
|||
#include <vlc_vector.h> |
|||
#include <vlc_tick.h> |
|||
#include <vlc_threads.h> |
|||
#include <vlc_stream.h> |
|||
|
|||
/**
|
|||
* @ingroup misc |
|||
* @file |
|||
* VLC_PROCESS API |
|||
* @defgroup process Process API |
|||
* @{ |
|||
*/ |
|||
|
|||
/**
|
|||
* Spawn a new process with input and output redirection. |
|||
* |
|||
* Creates and starts a new vlc_process for the specified executable path with |
|||
* the given arguments. Sets up pipes to allow reading from the process's |
|||
* standard output and writing to its standard input. |
|||
* |
|||
* @param [in] path Path to the executable to run. Must not be NULL. |
|||
* @param [in] argc Number of arguments passed to the process (must be |
|||
* greater than 0). |
|||
* @param [in] argv Array of argument strings (argv[0] must not be NULL). |
|||
* |
|||
* @return A pointer to the newly created vlc_process structure on |
|||
* success, or NULL on failure. |
|||
*/ |
|||
VLC_API struct vlc_process * |
|||
vlc_process_Spawn(const char *path, int argc, const char *const *argv); |
|||
|
|||
/**
|
|||
* Stop a vlc_process and wait for its termination. |
|||
* |
|||
* Closes its file descriptors, and waits for it to exit. Optionally sends a |
|||
* termination signal to the process, |
|||
* |
|||
* @param [in] process Pointer to the vlc_process instance. Must not |
|||
* be NULL. |
|||
* @param [in] kill_process Whether to forcibly terminate the process |
|||
* before waiting. |
|||
* |
|||
* @return The exit status of the process, or -1 on error. |
|||
*/ |
|||
VLC_API int |
|||
vlc_process_Terminate(struct vlc_process *process, bool kill_process); |
|||
|
|||
/**
|
|||
* Read data from the process's standard output with a timeout. |
|||
* |
|||
* Attempts to read up to @p size bytes from the process's standard output |
|||
* into the provided buffer, waiting up to @p timeout_ms milliseconds for data |
|||
* to become available. |
|||
* |
|||
* On POSIX systems, this uses poll to wait for readability. On Windows, |
|||
* a platform-specific implementation is used due to limitations with poll on |
|||
* non-socket handles. |
|||
* |
|||
* @param [in] process Pointer to the vlc_process instance. |
|||
* @param [out] buf Buffer where the read data will be stored. |
|||
* @param [in] size Maximum number of bytes to read. |
|||
* @param [in] timeout_ms Timeout in milliseconds to wait for data. |
|||
* |
|||
* @return The number of bytes read on success, |
|||
* -1 on error, and errno is set to indicate the error. |
|||
*/ |
|||
VLC_API ssize_t |
|||
vlc_process_fd_Read(struct vlc_process *process, uint8_t *buf, size_t size, |
|||
vlc_tick_t timeout_ms); |
|||
|
|||
/**
|
|||
* Write data to the process's standard input with a timeout. |
|||
* |
|||
* Attempts to write up to @p size bytes from the provided buffer to the |
|||
* process's standard input, waiting up to @p timeout_ms milliseconds for the |
|||
* pipe to become writable. |
|||
* |
|||
* On POSIX systems, this uses poll to wait for writability. On Windows, |
|||
* a platform-specific implementation is used due to limitations with poll on |
|||
* non-socket handles. |
|||
* |
|||
* @param [in] process Pointer to the vlc_process instance. |
|||
* @param [in] buf Buffer containing the data to write. |
|||
* @param [in] size Number of bytes to write. |
|||
* @param [in] timeout_ms Timeout in milliseconds to wait for the pipe to be |
|||
* writable. |
|||
* |
|||
* @return The number of bytes read on success, |
|||
* -1 on error, and errno is set to indicate the error. |
|||
*/ |
|||
VLC_API ssize_t |
|||
vlc_process_fd_Write(struct vlc_process *process, const uint8_t *buf, size_t size, |
|||
vlc_tick_t timeout_ms); |
|||
|
|||
/**
|
|||
* @} process |
|||
*/ |
|||
|
|||
#endif /* VLC_PROCESS_H */ |
|||
@ -0,0 +1,188 @@ |
|||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|||
/*****************************************************************************
|
|||
* process.c: posix implementation of process management |
|||
***************************************************************************** |
|||
* Copyright © 2025 Videolabs, VideoLAN and VLC authors |
|||
* |
|||
* Authors: Gabriel Lafond Thenaille <gabriel@videolabs.io> |
|||
*****************************************************************************/ |
|||
|
|||
#ifdef HAVE_CONFIG_H |
|||
# include "config.h" |
|||
#endif |
|||
|
|||
#ifdef HAVE_POLL_H |
|||
# include <poll.h> |
|||
#endif |
|||
|
|||
#include <signal.h> |
|||
#include <sys/poll.h> |
|||
|
|||
#include <vlc_common.h> |
|||
#include <vlc_process.h> |
|||
#include <vlc_interrupt.h> |
|||
#include <vlc_spawn.h> |
|||
#include <vlc_poll.h> |
|||
#include <vlc_fs.h> |
|||
#include <vlc_network.h> |
|||
|
|||
struct vlc_process { |
|||
|
|||
/* Pid of the linked process */ |
|||
pid_t pid; |
|||
|
|||
/* File descriptor of the socketpair */ |
|||
int fd; |
|||
}; |
|||
|
|||
struct vlc_process* |
|||
vlc_process_Spawn(const char *path, int argc, const char *const *argv) |
|||
{ |
|||
assert(path != NULL); |
|||
assert(argc > 0); |
|||
assert(argv != NULL); |
|||
assert(argv[0] != NULL); |
|||
|
|||
int ret = VLC_EGENERIC; |
|||
int fds[2] = { -1, -1 }; |
|||
int extfd_in = -1; |
|||
int extfd_out = -1; |
|||
const char **args = NULL; |
|||
|
|||
struct vlc_process *process = malloc(sizeof(*process)); |
|||
if (process == NULL) { |
|||
return NULL; |
|||
} |
|||
|
|||
ret = vlc_socketpair(PF_LOCAL, SOCK_STREAM, 0, fds, false); |
|||
if (ret != 0) { |
|||
goto end; |
|||
} |
|||
|
|||
extfd_in = vlc_dup(fds[1]); |
|||
if (extfd_in == -1) { |
|||
ret = -1; |
|||
goto end; |
|||
} |
|||
extfd_out = vlc_dup(fds[1]); |
|||
if (extfd_out == -1) { |
|||
ret = -1; |
|||
goto end; |
|||
} |
|||
|
|||
process->fd = fds[0]; |
|||
|
|||
int process_fds[4] = {extfd_in, extfd_out, STDERR_FILENO, -1}; |
|||
|
|||
/* `argc + 2`, 1 for the process->path and the last to be NULL */ |
|||
args = malloc((argc + 2) * sizeof(*args)); |
|||
if (args == NULL) { |
|||
ret = VLC_ENOMEM; |
|||
goto end; |
|||
} |
|||
args[0] = path; |
|||
for (int i = 0; i < argc; i++) { |
|||
args[i + 1] = argv[i]; |
|||
} |
|||
args[argc + 1] = NULL; |
|||
|
|||
ret = vlc_spawnp(&process->pid, path, process_fds, args); |
|||
if (ret != 0) { |
|||
goto end; |
|||
} |
|||
|
|||
end: |
|||
|
|||
free(args); |
|||
|
|||
if (extfd_in != -1) { |
|||
vlc_close(extfd_in); |
|||
} |
|||
if (extfd_out != -1) { |
|||
vlc_close(extfd_out); |
|||
} |
|||
if (fds[1] != -1) { |
|||
net_Close(fds[1]); |
|||
} |
|||
|
|||
if (ret != 0) { |
|||
if (fds[0] != -1) { |
|||
shutdown(fds[0], SHUT_RDWR); |
|||
net_Close(fds[0]); |
|||
} |
|||
free(process); |
|||
return NULL; |
|||
} |
|||
return process; |
|||
} |
|||
|
|||
VLC_API int |
|||
vlc_process_Terminate(struct vlc_process *process, bool kill_process) |
|||
{ |
|||
assert(process != NULL); |
|||
|
|||
if (kill_process) { |
|||
kill(process->pid, SIGTERM); |
|||
} |
|||
|
|||
shutdown(process->fd, SHUT_RDWR); |
|||
net_Close(process->fd); |
|||
|
|||
int status = vlc_waitpid(process->pid); |
|||
process->pid = 0; |
|||
process->fd = -1; |
|||
free(process); |
|||
return status; |
|||
} |
|||
|
|||
ssize_t |
|||
vlc_process_fd_Read(struct vlc_process *process, uint8_t *buf, size_t size, |
|||
vlc_tick_t timeout_ms) |
|||
{ |
|||
assert(process != NULL); |
|||
assert(process->fd != -1); |
|||
assert(buf != NULL); |
|||
|
|||
struct pollfd fds = { |
|||
.fd = process->fd, .events = POLLIN, .revents = 0 |
|||
}; |
|||
|
|||
int ret = vlc_poll_i11e(&fds, 1, timeout_ms); |
|||
if (ret < 0) { |
|||
return -1; |
|||
} else if (ret == 0) { |
|||
errno = ETIMEDOUT; |
|||
return -1; |
|||
} else if (!(fds.revents & POLLIN)) { |
|||
errno = EINVAL; |
|||
return -1; |
|||
} |
|||
|
|||
return recv(process->fd, buf, size, 0); |
|||
} |
|||
|
|||
ssize_t |
|||
vlc_process_fd_Write(struct vlc_process *process, const uint8_t *buf, |
|||
size_t size, vlc_tick_t timeout_ms) |
|||
{ |
|||
assert(process != NULL); |
|||
assert(process->fd != -1); |
|||
assert(buf != NULL); |
|||
|
|||
struct pollfd fds = { |
|||
.fd = process->fd, .events = POLLOUT, .revents = 0 |
|||
}; |
|||
|
|||
int ret = vlc_poll_i11e(&fds, 1, timeout_ms); |
|||
if (ret < 0) { |
|||
return -1; |
|||
} else if (ret == 0) { |
|||
errno = ETIMEDOUT; |
|||
return -1; |
|||
} else if (!(fds.revents & POLLOUT)) { |
|||
errno = EINVAL; |
|||
return -1; |
|||
} |
|||
|
|||
return vlc_send_i11e(process->fd, buf, size, 0); |
|||
} |
|||
@ -0,0 +1,275 @@ |
|||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|||
/*****************************************************************************
|
|||
* process.c: win32 implementation of process management |
|||
***************************************************************************** |
|||
* Copyright © 2025 Videolabs, VideoLAN and VLC authors |
|||
* |
|||
* Authors: Gabriel Lafond Thenaille <gabriel@videolabs.io> |
|||
*****************************************************************************/ |
|||
|
|||
#ifdef HAVE_CONFIG_H |
|||
# include "config.h" |
|||
#endif |
|||
|
|||
#include <windows.h> |
|||
|
|||
#include <vlc_common.h> |
|||
#include <vlc_process.h> |
|||
#include <vlc_interrupt.h> |
|||
#include <vlc_spawn.h> |
|||
#include <vlc_fs.h> |
|||
|
|||
static void CALLBACK |
|||
vlc_process_WindowsPoll_i11e_wake_self(ULONG_PTR data) |
|||
{ |
|||
(void) data; |
|||
} |
|||
|
|||
static void |
|||
vlc_process_WindowsPoll_i11e_wake(void *opaque) |
|||
{ |
|||
assert(opaque != NULL); |
|||
|
|||
HANDLE th = opaque; |
|||
QueueUserAPC(vlc_process_WindowsPoll_i11e_wake_self, th, 0); |
|||
} |
|||
|
|||
static int |
|||
vlc_process_WindowsPoll(HANDLE hFd, LPOVERLAPPED lpoverlapped, DWORD *bytes, |
|||
vlc_tick_t timeout_ms) |
|||
{ |
|||
HANDLE th; |
|||
if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), |
|||
GetCurrentProcess(), &th, 0, FALSE, |
|||
DUPLICATE_SAME_ACCESS)) { |
|||
return ENOMEM; |
|||
} |
|||
vlc_interrupt_register(vlc_process_WindowsPoll_i11e_wake, th); |
|||
DWORD waitResult = WaitForSingleObjectEx(lpoverlapped->hEvent, |
|||
timeout_ms, TRUE); |
|||
vlc_interrupt_unregister(); |
|||
CloseHandle(th); |
|||
switch (waitResult) { |
|||
case WAIT_OBJECT_0: |
|||
if (GetOverlappedResult(hFd, lpoverlapped, bytes, FALSE)) { |
|||
return VLC_SUCCESS; |
|||
} else { |
|||
return EINVAL; |
|||
} |
|||
case WAIT_TIMEOUT: |
|||
/* Timeout occurred */ |
|||
CancelIo(hFd); /* Cancel the I/O operation */ |
|||
return ETIMEDOUT; |
|||
case WAIT_IO_COMPLETION: |
|||
/* Interrupt occurred */ |
|||
CancelIo(hFd); /* Cancel the I/O operation */ |
|||
return EINTR; |
|||
default: |
|||
return EINVAL; |
|||
} |
|||
} |
|||
|
|||
struct vlc_process { |
|||
/* Pid of the linked process */ |
|||
pid_t pid; |
|||
|
|||
int fd_in; |
|||
int fd_out; |
|||
|
|||
HANDLE hEvent; |
|||
}; |
|||
|
|||
struct vlc_process* |
|||
vlc_process_Spawn(const char *path, int argc, const char *const *argv) |
|||
{ |
|||
assert(path != NULL); |
|||
if (argc > 0) { |
|||
assert(argv != NULL); |
|||
assert(argv[0] != NULL); |
|||
} |
|||
|
|||
int ret = VLC_EGENERIC; |
|||
int fds[2] = { -1, -1 }; |
|||
int extfd_in = -1; |
|||
int extfd_out = -1; |
|||
const char **args = NULL; |
|||
|
|||
struct vlc_process *process = malloc(sizeof(*process)); |
|||
if (process == NULL) { |
|||
return NULL; |
|||
} |
|||
|
|||
process->fd_in = -1; |
|||
process->fd_out = -1; |
|||
process->hEvent = INVALID_HANDLE_VALUE; |
|||
|
|||
ret = vlc_pipe(fds); |
|||
if (ret != 0) { |
|||
goto end; |
|||
} |
|||
extfd_out = fds[1]; |
|||
process->fd_in = fds[0]; |
|||
|
|||
ret = vlc_pipe(fds); |
|||
if (ret != 0) { |
|||
goto end; |
|||
} |
|||
extfd_in = fds[0]; |
|||
process->fd_out = fds[1]; |
|||
|
|||
process->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); |
|||
if (process->hEvent == INVALID_HANDLE_VALUE) { |
|||
errno = EINVAL; |
|||
goto end; |
|||
} |
|||
|
|||
int process_fds[4] = {extfd_in, extfd_out, STDERR_FILENO, -1}; |
|||
|
|||
/* `argc + 2`, 1 for the process->path and the last to be NULL */ |
|||
args = malloc((argc + 2) * sizeof(*args)); |
|||
if (args == NULL) { |
|||
ret = VLC_ENOMEM; |
|||
goto end; |
|||
} |
|||
args[0] = path; |
|||
for (int i = 0; i < argc; i++) { |
|||
args[i + 1] = argv[i]; |
|||
} |
|||
args[argc + 1] = NULL; |
|||
|
|||
ret = vlc_spawnp(&process->pid, path, process_fds, args); |
|||
if (ret != 0) { |
|||
goto end; |
|||
} |
|||
|
|||
end: |
|||
|
|||
free(args); |
|||
|
|||
if (extfd_in != -1) { |
|||
vlc_close(extfd_in); |
|||
} |
|||
if (extfd_out != -1) { |
|||
vlc_close(extfd_out); |
|||
} |
|||
|
|||
if (ret != 0) { |
|||
if (process->fd_in != -1) { |
|||
vlc_close(process->fd_in); |
|||
} |
|||
if (process->fd_out != -1) { |
|||
vlc_close(process->fd_out); |
|||
} |
|||
if (process->hEvent != INVALID_HANDLE_VALUE) { |
|||
CloseHandle(process->hEvent); |
|||
} |
|||
free(process); |
|||
return NULL; |
|||
} |
|||
return process; |
|||
} |
|||
|
|||
int |
|||
vlc_process_Terminate(struct vlc_process *process, bool kill_process) |
|||
{ |
|||
assert(process != NULL); |
|||
|
|||
if (kill_process) { |
|||
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, process->pid); |
|||
if (hProcess) { |
|||
TerminateProcess(hProcess, 15); |
|||
CloseHandle(hProcess); |
|||
} |
|||
} |
|||
|
|||
vlc_close(process->fd_in); |
|||
vlc_close(process->fd_out); |
|||
CloseHandle(process->hEvent); |
|||
|
|||
int status = vlc_waitpid(process->pid); |
|||
process->pid = 0; |
|||
free(process); |
|||
return status; |
|||
} |
|||
|
|||
ssize_t |
|||
vlc_process_fd_Read(struct vlc_process *process, uint8_t *buf, size_t size, |
|||
vlc_tick_t timeout_ms) |
|||
{ |
|||
assert(process != NULL); |
|||
assert(buf != NULL); |
|||
|
|||
intptr_t h = _get_osfhandle(process->fd_in); |
|||
if (h == -1) { |
|||
errno = EINVAL; |
|||
return -1; |
|||
} |
|||
HANDLE hFd = (HANDLE)h; |
|||
|
|||
DWORD bytes = 0; |
|||
OVERLAPPED overlapped = {0}; |
|||
overlapped.hEvent = process->hEvent; |
|||
|
|||
BOOL ret = FALSE; |
|||
ret = ReadFile(hFd, buf, size, &bytes, &overlapped); |
|||
|
|||
int err = VLC_SUCCESS; |
|||
|
|||
if (ret) { |
|||
return bytes; |
|||
} else { |
|||
DWORD error = GetLastError(); |
|||
if (error == ERROR_IO_PENDING) { |
|||
err = vlc_process_WindowsPoll(hFd, &overlapped, &bytes, |
|||
timeout_ms); |
|||
} else { |
|||
err = EINVAL; |
|||
} |
|||
} |
|||
if (err == VLC_SUCCESS) { |
|||
return bytes; |
|||
} |
|||
errno = err; |
|||
return -1; |
|||
} |
|||
|
|||
ssize_t |
|||
vlc_process_fd_Write(struct vlc_process *process, const uint8_t *buf, size_t size, |
|||
vlc_tick_t timeout_ms) |
|||
{ |
|||
assert(process != NULL); |
|||
assert(buf != NULL); |
|||
|
|||
intptr_t h = _get_osfhandle(process->fd_out); |
|||
if (h == -1) { |
|||
errno = EINVAL; |
|||
return -1; |
|||
} |
|||
HANDLE hFd = (HANDLE)h; |
|||
|
|||
DWORD bytes = 0; |
|||
OVERLAPPED overlapped = {0}; |
|||
overlapped.hEvent = process->hEvent; |
|||
|
|||
BOOL ret = FALSE; |
|||
ret = WriteFile(hFd, buf, size, &bytes, &overlapped); |
|||
|
|||
int err = VLC_SUCCESS; |
|||
|
|||
if (ret) { |
|||
return bytes; |
|||
} else { |
|||
DWORD error = GetLastError(); |
|||
if (error == ERROR_IO_PENDING) { |
|||
err = vlc_process_WindowsPoll(hFd, &overlapped, &bytes, |
|||
timeout_ms); |
|||
} else { |
|||
err = EINVAL; |
|||
} |
|||
} |
|||
if (err == VLC_SUCCESS) { |
|||
return bytes; |
|||
} |
|||
errno = err; |
|||
return -1; |
|||
} |
|||
Loading…
Reference in new issue