mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-09 20:29:10 +02:00
Add Process functions for detached spawning
This commit is contained in:
@@ -66,11 +66,12 @@ class patched_async_pipe {
|
|||||||
inline patched_async_pipe(asio::io_context& ios)
|
inline patched_async_pipe(asio::io_context& ios)
|
||||||
: patched_async_pipe(ios, ios) {}
|
: patched_async_pipe(ios, ios) {}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||||
inline patched_async_pipe(asio::io_context& ios_source,
|
inline patched_async_pipe(asio::io_context& ios_source,
|
||||||
asio::io_context& ios_sink)
|
asio::io_context& ios_sink)
|
||||||
: _source(ios_source), _sink(ios_sink) {
|
: _source(ios_source), _sink(ios_sink) {
|
||||||
int fds[2];
|
int fds[2];
|
||||||
if (::pipe(fds) == -1)
|
if (pipe(fds) == -1)
|
||||||
boost::process::detail::throw_last_error("pipe(2) failed");
|
boost::process::detail::throw_last_error("pipe(2) failed");
|
||||||
|
|
||||||
_source.assign(fds[0]);
|
_source.assign(fds[0]);
|
||||||
|
|||||||
+123
-11
@@ -141,38 +141,39 @@ std::optional<int> Process::Handle::wait() const noexcept {
|
|||||||
Process::Process(std::string command) : command_(command) {}
|
Process::Process(std::string command) : command_(command) {}
|
||||||
|
|
||||||
Process::StringResult Process::spawn_get_stdout_line() const {
|
Process::StringResult Process::spawn_get_stdout_line() const {
|
||||||
/// We'll read the results from a pipe. The child writes to the second pipe,
|
// We'll read the results from a pipe. The child writes to the second pipe,
|
||||||
/// we'll read from the first one.
|
// we'll read from the first one.
|
||||||
int output_pipe[2];
|
int stdout_pipe_fds[2];
|
||||||
::pipe(output_pipe);
|
pipe(stdout_pipe_fds);
|
||||||
|
|
||||||
const auto argv = build_argv();
|
const auto argv = build_argv();
|
||||||
const auto envp = env_ ? env_->make_environ() : environ;
|
const auto envp = env_ ? env_->make_environ() : environ;
|
||||||
|
|
||||||
posix_spawn_file_actions_t actions;
|
posix_spawn_file_actions_t actions;
|
||||||
posix_spawn_file_actions_init(&actions);
|
posix_spawn_file_actions_init(&actions);
|
||||||
posix_spawn_file_actions_adddup2(&actions, output_pipe[1], STDOUT_FILENO);
|
posix_spawn_file_actions_adddup2(&actions, stdout_pipe_fds[1],
|
||||||
|
STDOUT_FILENO);
|
||||||
posix_spawn_file_actions_addopen(&actions, STDERR_FILENO, "/dev/null",
|
posix_spawn_file_actions_addopen(&actions, STDERR_FILENO, "/dev/null",
|
||||||
O_WRONLY | O_APPEND, 0);
|
O_WRONLY | O_APPEND, 0);
|
||||||
posix_spawn_file_actions_addclose(&actions, output_pipe[0]);
|
posix_spawn_file_actions_addclose(&actions, stdout_pipe_fds[0]);
|
||||||
posix_spawn_file_actions_addclose(&actions, output_pipe[1]);
|
posix_spawn_file_actions_addclose(&actions, stdout_pipe_fds[1]);
|
||||||
|
|
||||||
pid_t child_pid = 0;
|
pid_t child_pid = 0;
|
||||||
const auto result = posix_spawnp(&child_pid, command_.c_str(), &actions,
|
const auto result = posix_spawnp(&child_pid, command_.c_str(), &actions,
|
||||||
nullptr, argv, envp);
|
nullptr, argv, envp);
|
||||||
|
|
||||||
close(output_pipe[1]);
|
close(stdout_pipe_fds[1]);
|
||||||
if (result == 2) {
|
if (result == 2) {
|
||||||
close(output_pipe[0]);
|
close(stdout_pipe_fds[0]);
|
||||||
return Process::CommandNotFound{};
|
return Process::CommandNotFound{};
|
||||||
} else if (result != 0) {
|
} else if (result != 0) {
|
||||||
close(output_pipe[0]);
|
close(stdout_pipe_fds[0]);
|
||||||
return std::error_code(result, std::system_category());
|
return std::error_code(result, std::system_category());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to read the first line out the output until the line feed
|
// Try to read the first line out the output until the line feed
|
||||||
std::array<char, 1024> output{0};
|
std::array<char, 1024> output{0};
|
||||||
FILE* output_pipe_stream = fdopen(output_pipe[0], "r");
|
FILE* output_pipe_stream = fdopen(stdout_pipe_fds[0], "r");
|
||||||
assert(output_pipe_stream);
|
assert(output_pipe_stream);
|
||||||
fgets(output.data(), output.size(), output_pipe_stream);
|
fgets(output.data(), output.size(), output_pipe_stream);
|
||||||
fclose(output_pipe_stream);
|
fclose(output_pipe_stream);
|
||||||
@@ -214,6 +215,117 @@ Process::StatusResult Process::spawn_get_status() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Process::HandleResult Process::spawn_child_piped(
|
||||||
|
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||||
|
asio::posix::stream_descriptor& stdout_pipe,
|
||||||
|
asio::posix::stream_descriptor& stderr_pipe) const {
|
||||||
|
// We'll reopen the child process' STDOUT and STDERR stream from a pipe, and
|
||||||
|
// we'll assign the other ends of those pipes to the stream descriptors
|
||||||
|
// passed to this function so they can be read from asynchronously in an
|
||||||
|
// Asio IO context loop. We'll read from the first elements of these pipes,
|
||||||
|
// and the child process will write to the second elements.
|
||||||
|
int stdout_pipe_fds[2];
|
||||||
|
int stderr_pipe_fds[2];
|
||||||
|
pipe(stdout_pipe_fds);
|
||||||
|
pipe(stderr_pipe_fds);
|
||||||
|
|
||||||
|
const auto argv = build_argv();
|
||||||
|
const auto envp = env_ ? env_->make_environ() : environ;
|
||||||
|
|
||||||
|
posix_spawn_file_actions_t actions;
|
||||||
|
posix_spawn_file_actions_init(&actions);
|
||||||
|
posix_spawn_file_actions_adddup2(&actions, stdout_pipe_fds[1],
|
||||||
|
STDOUT_FILENO);
|
||||||
|
posix_spawn_file_actions_adddup2(&actions, stderr_pipe_fds[1],
|
||||||
|
STDERR_FILENO);
|
||||||
|
// We'll close the four pipe fds along with the rest of the file descriptors
|
||||||
|
|
||||||
|
// NOTE: If the Wine process outlives the host, then it may cause issues if
|
||||||
|
// our process is still keeping the host's file descriptors alive
|
||||||
|
// that. This can prevent Ardour from restarting after an unexpected
|
||||||
|
// shutdown. Because of this we won't use `vfork()`, but instead we'll
|
||||||
|
// just manually close all non-STDIO file descriptors.
|
||||||
|
#if (__GLIBC__ > 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 34)
|
||||||
|
posix_spawn_file_actions_addclosefrom_np(&actions, STDERR_FILENO + 1);
|
||||||
|
#else
|
||||||
|
const int max_fds = static_cast<int>(sysconf(_SC_OPEN_MAX));
|
||||||
|
for (int fd = STDERR_FILENO + 1; fd < max_fds; fd++) {
|
||||||
|
posix_spawn_file_actions_addclose(&actions, fd);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
pid_t child_pid = 0;
|
||||||
|
const auto result = posix_spawnp(&child_pid, command_.c_str(), &actions,
|
||||||
|
nullptr, argv, envp);
|
||||||
|
|
||||||
|
// We'll assign the read ends of the pipes to the Asio stream descriptors
|
||||||
|
// passed to this function, even if launching the process failed.
|
||||||
|
// `asio::posix::stream_descriptor::assign()` will take ownership of the FD
|
||||||
|
// and close it when the object gets dropped.
|
||||||
|
stdout_pipe.assign(stdout_pipe_fds[0]);
|
||||||
|
stderr_pipe.assign(stderr_pipe_fds[0]);
|
||||||
|
close(stdout_pipe_fds[1]);
|
||||||
|
close(stderr_pipe_fds[1]);
|
||||||
|
|
||||||
|
if (result == 2) {
|
||||||
|
return Process::CommandNotFound{};
|
||||||
|
} else if (result != 0) {
|
||||||
|
return std::error_code(result, std::system_category());
|
||||||
|
}
|
||||||
|
|
||||||
|
// With glibc `posix_spawn*()` will return 2/`ENOENT` when the file does not
|
||||||
|
// exist, but the specification says that it should return a PID that exits
|
||||||
|
// with status 127 instead. I have no idea how we'd check for that without
|
||||||
|
// waiting here though, so this check may not work
|
||||||
|
int status = 0;
|
||||||
|
assert(waitpid(child_pid, &status, WNOHANG) >= 0);
|
||||||
|
if (WIFEXITED(status) && WEXITSTATUS(status) == 127) {
|
||||||
|
return Process::CommandNotFound{};
|
||||||
|
} else {
|
||||||
|
return Handle(child_pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process::HandleResult Process::spawn_child_redirected(
|
||||||
|
const ghc::filesystem::path& filename) const {
|
||||||
|
const auto argv = build_argv();
|
||||||
|
const auto envp = env_ ? env_->make_environ() : environ;
|
||||||
|
|
||||||
|
posix_spawn_file_actions_t actions;
|
||||||
|
posix_spawn_file_actions_init(&actions);
|
||||||
|
posix_spawn_file_actions_addopen(&actions, STDOUT_FILENO, filename.c_str(),
|
||||||
|
O_WRONLY | O_APPEND, 0);
|
||||||
|
posix_spawn_file_actions_addopen(&actions, STDERR_FILENO, filename.c_str(),
|
||||||
|
O_WRONLY | O_APPEND, 0);
|
||||||
|
|
||||||
|
// See the note in the other function
|
||||||
|
#if (__GLIBC__ > 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 34)
|
||||||
|
posix_spawn_file_actions_addclosefrom_np(&actions, STDERR_FILENO + 1);
|
||||||
|
#else
|
||||||
|
const int max_fds = static_cast<int>(sysconf(_SC_OPEN_MAX));
|
||||||
|
for (int fd = STDERR_FILENO + 1; fd < max_fds; fd++) {
|
||||||
|
posix_spawn_file_actions_addclose(&actions, fd);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
pid_t child_pid = 0;
|
||||||
|
const auto result = posix_spawnp(&child_pid, command_.c_str(), &actions,
|
||||||
|
nullptr, argv, envp);
|
||||||
|
if (result == 2) {
|
||||||
|
return Process::CommandNotFound{};
|
||||||
|
} else if (result != 0) {
|
||||||
|
return std::error_code(result, std::system_category());
|
||||||
|
}
|
||||||
|
|
||||||
|
int status = 0;
|
||||||
|
assert(waitpid(child_pid, &status, WNOHANG) >= 0);
|
||||||
|
if (WIFEXITED(status) && WEXITSTATUS(status) == 127) {
|
||||||
|
return Process::CommandNotFound{};
|
||||||
|
} else {
|
||||||
|
return Handle(child_pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
char* const* Process::build_argv() const {
|
char* const* Process::build_argv() const {
|
||||||
argv_.clear();
|
argv_.clear();
|
||||||
|
|
||||||
|
|||||||
+24
-1
@@ -94,7 +94,8 @@ class ProcessEnvironment {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A child process whose output can be captured. Simple wrapper around the Posix
|
* A child process whose output can be captured. Simple wrapper around the Posix
|
||||||
* APIs.
|
* APIs. The functions provided for running processes this way are very much
|
||||||
|
* tailored towards yabridge's needs.
|
||||||
*/
|
*/
|
||||||
class Process {
|
class Process {
|
||||||
public:
|
public:
|
||||||
@@ -110,6 +111,8 @@ class Process {
|
|||||||
protected:
|
protected:
|
||||||
Handle(pid_t pid);
|
Handle(pid_t pid);
|
||||||
|
|
||||||
|
friend Process;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* Terminates the process when it gets dropped.
|
* Terminates the process when it gets dropped.
|
||||||
@@ -195,6 +198,26 @@ class Process {
|
|||||||
*/
|
*/
|
||||||
StatusResult spawn_get_status() const;
|
StatusResult spawn_get_status() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn the process without waiting for its completion, leave STDIN alone,
|
||||||
|
* create pipes for STDOUT and STDERR, and assign those to the provided
|
||||||
|
* (empty) stream descriptors. Use `posix_spawn()`, closes all non-STDIO
|
||||||
|
* file descriptors. The process will be terminated when the child process
|
||||||
|
* handle gets dropped.
|
||||||
|
*/
|
||||||
|
HandleResult spawn_child_piped(
|
||||||
|
asio::posix::stream_descriptor& stdout_pipe,
|
||||||
|
asio::posix::stream_descriptor& stderr_pipe) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn the process without waiting for its completion, leave STDIN alone,
|
||||||
|
* and redirect STDOUT and STDERR to a file. Use `posix_spawn()`, closes all
|
||||||
|
* non-STDIO file descriptors. The process will be terminated when the child
|
||||||
|
* process handle gets dropped.
|
||||||
|
*/
|
||||||
|
HandleResult spawn_child_redirected(
|
||||||
|
const ghc::filesystem::path& filename) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Create the `argv` array from the command and the arguments. Only valid as
|
* Create the `argv` array from the command and the arguments. Only valid as
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ StdIoCapture::StdIoCapture(asio::io_context& io_context, int file_descriptor)
|
|||||||
original_fd_copy_(dup(file_descriptor)) {
|
original_fd_copy_(dup(file_descriptor)) {
|
||||||
// We'll use the second element of these two file descriptors to reopen
|
// We'll use the second element of these two file descriptors to reopen
|
||||||
// `file_descriptor`, and the first one to read the captured contents from
|
// `file_descriptor`, and the first one to read the captured contents from
|
||||||
if (::pipe(pipe_fd_) != 0) {
|
if (pipe(pipe_fd_) != 0) {
|
||||||
std::cerr << "Could not create pipe" << std::endl;
|
std::cerr << "Could not create pipe" << std::endl;
|
||||||
throw std::system_error(errno, std::system_category());
|
throw std::system_error(errno, std::system_category());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ class WineXdndProxy {
|
|||||||
*/
|
*/
|
||||||
Handle(WineXdndProxy* proxy);
|
Handle(WineXdndProxy* proxy);
|
||||||
|
|
||||||
|
friend WineXdndProxy;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* Reduces the reference count by one, and frees `proxy_` if this was
|
* Reduces the reference count by one, and frees `proxy_` if this was
|
||||||
@@ -120,8 +122,6 @@ class WineXdndProxy {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
WineXdndProxy* proxy_;
|
WineXdndProxy* proxy_;
|
||||||
|
|
||||||
friend WineXdndProxy;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user