// yabridge: a Wine plugin bridge
// Copyright (C) 2020-2022 Robbert van der Helm
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
#pragma once
#include
#include
#include
#include
#include
#include
// We also use this header from the chainloaders, and we don't want to pull in
// Asio there
#ifndef PROCESS_NO_ASIO
#ifdef __WINE__
#include "../wine-host/asio-fix.h"
#endif
#include
#endif // PROCESS_NO_ASIO
// A minimal API akin to Boost.Process for launching and managing processes
// using plain Linux APIs. Needed so we can implement our chainloader without
// pulling in Boost.Process' Boost.Filesystem dependency (which would defeat the
// entire purpose).
/**
* Check whether a process with the given PID is still active (and not a
* zombie).
*/
bool pid_running(pid_t pid);
/**
* Return the search path as defined in `$PATH`, with `~/.local/share/yabridge`
* appended to the end. Even though it likely won't be set, this does respect
* `$XDG_DATA_HOME`. I'd rather not do this since more magic makes things harder
* to comprehend, but I can understand that modifying your login shell's `PATH`
* environment variable can be a big hurdle if you've never done anything like
* that before. And since this is the recommended installation location, it
* makes sense to also search there by default.
*/
std::vector get_augmented_search_path();
/**
* Split a `PATH`-like environment variable on colons. These environment
* variables don't support escaping, which makes this a lot simpler.
*/
std::vector split_path(const std::string_view& path_env);
/**
* Search through a search path vector created by `split_path` for an executable
* binary called `target`, returning the first match if any.
*/
std::optional search_in_path(
const std::vector& path,
const std::string_view& target);
/**
* Helper to create an `environ`-like environment object for passing to the
* `exec*e()` family of functions.
*/
class ProcessEnvironment {
public:
/**
* Create a new environment object based on an existing environment
* described by an array of null-terminated strings, terminated by a null
* pointer. You'll want to pass `environ` here.
*/
ProcessEnvironment(char** initial_env);
/**
* Check if an environment variable exists within this environment. Mostly
* useful for debugging.
*/
bool contains(const std::string_view& key) const;
/**
* Get the value for an environment variable, if it exists in this
* environment. Mostly useful for debugging.
*/
std::optional get(const std::string_view& key) const;
/**
* Add an environment variable to the environment or overwrite an
* existing one.
*/
void insert(const std::string& key, const std::string& value);
/**
* Create an environ-like object from the updated environment that can be
* passed to the `exec*e()` functions. These pointers will be invalidated
* when this object changes or when gets dropped.
*/
char* const* make_environ() const;
private:
/**
* All environment variables read from the constructor argument and those
* inserted through `insert()`. These should be in `key=value` format.
*/
std::vector variables_;
/**
* Contains pointers to the strings in `variables`, so we can return a
* `char**` in `make_environ()`.
*/
mutable std::vector recreated_environ_;
};
/**
* A child process whose output can be captured. Simple wrapper around the Posix
* APIs. The functions provided for running processes this way are very much
* tailored towards yabridge's needs.
*/
class Process {
public:
/**
* Marker to indicate that the program was not found.
*/
struct CommandNotFound {};
/**
* A handle to a running process.
*/
class Handle {
protected:
Handle(pid_t pid);
friend Process;
public:
/**
* Terminates the process when it gets dropped.
*/
~Handle();
Handle(const Handle&) = delete;
Handle& operator=(const Handle&) = delete;
Handle(Handle&&) noexcept;
Handle& operator=(Handle&&) noexcept;
/**
* The process' ID.
*/
pid_t pid() const noexcept;
/**
* Whether the process is still running **and not a zombie**.
*/
bool running() const noexcept;
/**
* Don't terminate the process when this object gets dropped.
*/
void detach() noexcept;
/**
* Forcefully terminate the process by sending `SIGKILL`. Will reap the
* process zombie after sending the signal.
*/
void terminate() const noexcept;
/**
* Wait for the process to exit, returning the exit code if it exited
* successfully. Returns a nullopt otherwise.
*/
std::optional wait() const noexcept;
private:
/**
* If `true`, don't terminate the process when this object gets dropped.
* Also set when this object gets moved from.
*/
bool detached_ = false;
pid_t pid_ = 0;
};
using StringResult =
std::variant;
using StatusResult = std::variant;
using HandleResult = std::variant;
/**
* Build a process. Use the other functions to add arguments or to
* launch the process.
*
* @param command The name of the command. `$PATH` will be searched for
* this command if it is not absolute.
*/
Process(std::string command);
/**
* Add an argument to the command invocation. Returns a reference to this
* object for easier chaining.
*/
inline Process& arg(std::string arg) {
args_.emplace_back(std::move(arg));
return *this;
}
/**
* Use the specified environment for this command.
*
* @see environment
*/
inline Process& environment(ProcessEnvironment env) {
env_ = std::move(env);
return *this;
}
/**
* Spawn the process, leave STDIN, redirect STDERR to `/dev/null`, and
* return the first line (without the trailing linefeed) of STDOUT. The
* first output line will still be returned even if the process exits with a
* non-zero exit code. Uses `posix_spawn()`, leaves file descriptors in
* tact.
*/
StringResult spawn_get_stdout_line() const;
/**
* Spawn the process, leave STDOUT, STDIN and STDERR alone, and return an
* empty string if the program ran successfully. Uses `posix_spawn()`,
* leaves file descriptors in tact.
*/
StatusResult spawn_get_status() const;
#ifndef PROCESS_NO_ASIO
/**
* 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;
#endif // PROCESS_NO_ASIO
/**
* 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:
/**
* Create the `argv` array from the command and the arguments. Only valid as
* long as the pointers in `args_` at the time of calling stay valid.
*/
char* const* build_argv() const;
std::string command_;
std::vector args_;
std::optional env_;
mutable std::vector argv_;
};