Add a way to disable pipes for the Wine host

For some reason ujam plugins (and other plugins made with the Gorilla
Engine, like the LoopCloud plugins) will throw a `JS_EXEC_FAILED` error
when trying to load the plugin while either of the STDOUT or STDERR
streams is pointing to a pipe. Simply redirecting the output to a file
fixes this. By default we'll write the output to
`<temporary_directory>/yabridge-plugin-output.log`, but you can also set
the new `disable_pipes` option to `"/dev/null"` to completely throw away
all output.

This addresses #47.
This commit is contained in:
Robbert van der Helm
2021-05-18 17:55:39 +02:00
parent 99428ba28e
commit 95badeb1bc
6 changed files with 128 additions and 41 deletions
+11 -5
View File
@@ -72,26 +72,27 @@ class PluginBridge {
? std::unique_ptr<HostProcess>(std::make_unique<GroupHost>(
io_context,
generic_logger,
config,
sockets,
info,
HostRequest{
.plugin_type = plugin_type,
.plugin_path = info.windows_plugin_path.string(),
.endpoint_base_dir = sockets.base_dir.string(),
.parent_pid = getpid()},
sockets,
*config.group))
.parent_pid = getpid()}))
: std::unique_ptr<HostProcess>(
std::make_unique<IndividualHost>(
io_context,
generic_logger,
config,
sockets,
info,
HostRequest{
.plugin_type = plugin_type,
.plugin_path =
info.windows_plugin_path.string(),
.endpoint_base_dir = sockets.base_dir.string(),
.parent_pid = getpid()},
sockets))),
.parent_pid = getpid()}))),
has_realtime_priority(has_realtime_priority_promise.get_future()),
wine_io_handler([&]() {
// We no longer run this thread with realtime scheduling because
@@ -177,6 +178,11 @@ class PluginBridge {
init_msg << "other options: ";
std::vector<std::string> other_options;
if (config.disable_pipes) {
other_options.push_back(
"hack: pipes disabled, plugin output will go to \"" +
config.disable_pipes->string() + "\"");
}
if (config.editor_double_embed) {
other_options.push_back("editor: double embed");
}
+28 -15
View File
@@ -27,26 +27,39 @@ namespace fs = boost::filesystem;
HostProcess::HostProcess(boost::asio::io_context& io_context,
Logger& logger,
const Configuration& config,
Sockets& sockets)
: stdout_pipe(io_context),
stderr_pipe(io_context),
config(config),
sockets(sockets),
logger(logger) {
// Print the Wine host's STDOUT and STDERR streams to the log file. This
// should be done before trying to accept the sockets as otherwise we will
// miss all output.
logger.async_log_pipe_lines(stdout_pipe, stdout_buffer, "[Wine STDOUT] ");
logger.async_log_pipe_lines(stderr_pipe, stderr_buffer, "[Wine STDERR] ");
// See the comment above the `on_exec_setup` in `launch_host()`
if (config.disable_pipes) {
logger.log("");
logger.log("WARNING: All Wine output will be written to");
logger.log(" '" + config.disable_pipes->string() + "'.");
logger.log("");
} else {
// Print the Wine host's STDOUT and STDERR streams to the log file. This
// should be done before trying to accept the sockets as otherwise we
// will miss all output.
logger.async_log_pipe_lines(stdout_pipe, stdout_buffer,
"[Wine STDOUT] ");
logger.async_log_pipe_lines(stderr_pipe, stderr_buffer,
"[Wine STDERR] ");
}
}
HostProcess::~HostProcess() noexcept {}
IndividualHost::IndividualHost(boost::asio::io_context& io_context,
Logger& logger,
const Configuration& config,
Sockets& sockets,
const PluginInfo& plugin_info,
const HostRequest& host_request,
Sockets& sockets)
: HostProcess(io_context, logger, sockets),
const HostRequest& host_request)
: HostProcess(io_context, logger, config, sockets),
plugin_info(plugin_info),
host_path(find_vst_host(plugin_info.native_library_path,
plugin_info.plugin_arch,
@@ -103,11 +116,11 @@ void IndividualHost::terminate() {
GroupHost::GroupHost(boost::asio::io_context& io_context,
Logger& logger,
const PluginInfo& plugin_info,
const HostRequest& host_request,
const Configuration& config,
Sockets& sockets,
std::string group_name)
: HostProcess(io_context, logger, sockets),
const PluginInfo& plugin_info,
const HostRequest& host_request)
: HostProcess(io_context, logger, config, sockets),
plugin_info(plugin_info),
host_path(find_vst_host(plugin_info.native_library_path,
plugin_info.plugin_arch,
@@ -128,9 +141,9 @@ GroupHost::GroupHost(boost::asio::io_context& io_context,
// will try to connect to the socket once more in the case that another
// process is now listening on it.
const fs::path endpoint_base_dir = sockets.base_dir;
const fs::path group_socket_path =
generate_group_endpoint(group_name, plugin_info.normalize_wine_prefix(),
plugin_info.plugin_arch);
const fs::path group_socket_path = generate_group_endpoint(
*config.group, plugin_info.normalize_wine_prefix(),
plugin_info.plugin_arch);
const auto connect = [&io_context, host_request, endpoint_base_dir,
group_socket_path]() {
boost::asio::local::stream_protocol::socket group_socket(io_context);
+40 -12
View File
@@ -90,12 +90,31 @@ class HostProcess {
// 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.
// HACK: If the `disable_pipes` option is enabled, then we'll
// redirect the plugin's output to a file instead of using
// pipes to blend it in with the rest of yabridge's output.
// This is for some reason necessary for ujam's plugins and
// all other plugins made with Gorilla Engine to function.
// Otherwise they'll print a nondescriptive `JS_EXEC_FAILED`
// error message.
boost::process::extend::on_exec_setup =
[this](auto& /*executor*/) {
const int max_fds = static_cast<int>(sysconf(_SC_OPEN_MAX));
for (int fd = STDERR_FILENO + 1; fd < max_fds; fd++) {
close(fd);
}
// See above
if (config.disable_pipes) {
const int redirect_fd =
open(config.disable_pipes->c_str(),
O_CREAT | O_APPEND | O_WRONLY, 0640);
assert(redirect_fd != -1);
dup2(redirect_fd, STDOUT_FILENO);
dup2(redirect_fd, STDERR_FILENO);
close(redirect_fd);
}
},
std::forward<Args>(args)...);
}
@@ -114,6 +133,7 @@ class HostProcess {
*/
HostProcess(boost::asio::io_context& io_context,
Logger& logger,
const Configuration& config,
Sockets& sockets);
/**
@@ -125,6 +145,11 @@ class HostProcess {
*/
patched_async_pipe stderr_pipe;
/**
* The current plugin instance's configuration.
*/
const Configuration& config;
/**
* The associated sockets for the plugin we're hosting. This is used to
* terminate the plugin.
@@ -154,23 +179,25 @@ class IndividualHost : public HostProcess {
* handled on.
* @param logger The `Logger` instance the redirected STDIO streams will be
* written to.
* @param config The configuration for this plugin instance.
* @param sockets The socket endpoints that will be used for communication
* with the plugin. When the plugin shuts down, we'll close all of the
* sockets used by the plugin.
* @param plugin_info Information about the plugin we're going to use. Used
* to retrieve the Wine prefix and the plugin's architecture.
* @param host_request The information about the plugin we should launch a
* host process for. The values in the struct will be used as command line
* arguments.
* @param sockets The socket endpoints that will be used for communication
* with the plugin. When the plugin shuts down, we'll close all of the
* sockets used by the plugin.
*
* @throw std::runtime_error When `plugin_path` does not point to a valid
* 32-bit or 64-bit .dll file.
*/
IndividualHost(boost::asio::io_context& io_context,
Logger& logger,
const Configuration& config,
Sockets& sockets,
const PluginInfo& plugin_info,
const HostRequest& host_request,
Sockets& sockets);
const HostRequest& host_request);
boost::filesystem::path path() override;
bool running() noexcept override;
@@ -203,21 +230,22 @@ class GroupHost : public HostProcess {
* handled on.
* @param logger The `Logger` instance the redirected STDIO streams will be
* written to.
* @param config The configuration for this plugin instance. The group name
* will be retrieved from here.
* @param sockets The socket endpoints that will be used for communication
* with the plugin. When the plugin shuts down, we'll close all of the
* sockets used by the plugin.
* @param plugin_info Information about the plugin we're going to use. Used
* to retrieve the Wine prefix and the plugin's architecture.
* @param host_request The information about the plugin we should launch a
* host process for. This object will be sent to the group host process.
* @param sockets The socket endpoints that will be used for communication
* with the plugin. When the plugin shuts down, we'll close all of the
* sockets used by the plugin.
* @param group_name The name of the plugin group.
*/
GroupHost(boost::asio::io_context& io_context,
Logger& logger,
const PluginInfo& plugin_info,
const HostRequest& host_request,
const Configuration& config,
Sockets& sockets,
std::string group_name);
const PluginInfo& plugin_info,
const HostRequest& host_request);
boost::filesystem::path path() override;
bool running() noexcept override;