Browse Source

process: create a vlc_process API

* Create an API to spawn a process, control it and communicate with it
   though pipes.
update/opus-1.6.1
Gabriel Lafond-Thenaille 1 year ago
committed by Steve Lhomme
parent
commit
8903fedfd9
  1. 114
      include/vlc_process.h
  2. 10
      src/Makefile.am
  3. 4
      src/libvlccore.sym
  4. 3
      src/meson.build
  5. 44
      src/missing.c
  6. 188
      src/posix/process.c
  7. 275
      src/win32/process.c

114
include/vlc_process.h

@ -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 */

10
src/Makefile.am

@ -94,6 +94,7 @@ pluginsinclude_HEADERS.h = \
../include/vlc_poll.h \
../include/vlc_probe.h \
../include/vlc_preparser.h \
../include/vlc_process.h \
../include/vlc_queue.h \
../include/vlc_rand.h \
../include/vlc_renderer_discovery.h \
@ -448,7 +449,8 @@ libvlccore_la_SOURCES += \
win32/plugin.c \
win32/rand.c \
win32/specific.c \
win32/thread.c
win32/thread.c \
win32/process.c
if HAVE_WINSTORE
libvlccore_la_SOURCES += posix/timer.c win32/dirs-uap.c
else
@ -540,7 +542,8 @@ libvlccore_la_SOURCES += \
libvlccore_la_LIBADD += $(LIBEXECINFO)
if HAVE_OSX
libvlccore_la_SOURCES += \
posix/spawn.c
posix/spawn.c \
posix/process.c
endif
if !HAVE_DARWIN
if !HAVE_EMSCRIPTEN
@ -551,7 +554,8 @@ libvlccore_la_SOURCES += \
posix/error.c \
posix/picture.c \
posix/spawn.c \
posix/specific.c
posix/specific.c \
posix/process.c
if HAVE_LIBANL
libvlccore_la_SOURCES += \
linux/getaddrinfo.c

4
src/libvlccore.sym

@ -1066,3 +1066,7 @@ vlc_preparser_req_GetItem
vlc_preparser_req_Release
vlc_preparser_Delete
vlc_preparser_SetTimeout
vlc_process_Spawn
vlc_process_Terminate
vlc_process_fd_Read
vlc_process_fd_Write

3
src/meson.build

@ -330,6 +330,7 @@ if host_system == 'darwin'
if have_osx
libvlccore_sources += [
'posix/spawn.c',
'posix/process.c',
]
endif
elif host_system == 'windows'
@ -353,6 +354,7 @@ elif host_system == 'windows'
'win32/timer.c',
'win32/dirs.c',
'win32/spawn.c',
'win32/process.c',
]
endif
else
@ -372,6 +374,7 @@ else
'posix/picture.c',
'posix/specific.c',
'posix/thread.c',
'posix/process.c',
]
endif

44
src/missing.c

@ -37,6 +37,8 @@
#include <vlc_common.h>
#include <assert.h>
#include <vlc_process.h>
#ifndef ENABLE_VLM
# include <vlc_vlm.h>
@ -180,4 +182,46 @@ int vlc_waitpid(pid_t pid)
(void) pid;
vlc_assert_unreachable();
}
VLC_WEAK struct vlc_process *
vlc_process_Spawn(const char *path, int argc, const char *const *argv)
{
VLC_UNUSED(path);
VLC_UNUSED(argc);
VLC_UNUSED(argv);
return NULL;
}
VLC_WEAK int
vlc_process_Terminate(struct vlc_process *process, bool kill_process)
{
VLC_UNUSED(process);
VLC_UNUSED(kill_process);
vlc_assert_unreachable();
return -1;
}
VLC_WEAK ssize_t
vlc_process_fd_Read(struct vlc_process *process, uint8_t *buf, size_t size,
vlc_tick_t timeout_ms)
{
VLC_UNUSED(process);
VLC_UNUSED(buf);
VLC_UNUSED(size);
VLC_UNUSED(timeout_ms);
vlc_assert_unreachable();
return -1;
}
VLC_WEAK ssize_t
vlc_process_fd_Write(struct vlc_process *process, const uint8_t *buf, size_t size,
vlc_tick_t timeout_ms)
{
VLC_UNUSED(process);
VLC_UNUSED(buf);
VLC_UNUSED(size);
VLC_UNUSED(timeout_ms);
vlc_assert_unreachable();
return -1;
}
#endif

188
src/posix/process.c

@ -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);
}

275
src/win32/process.c

@ -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…
Cancel
Save