From b8028b8e13d8ce834fd83232f673fc41bd2f9694 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 17 May 2020 18:59:10 +0200 Subject: [PATCH] Remap STDOUT and STDERR in group process to log This is not very pretty, but there's not really another way and I"m surprised that it actually works. --- src/wine-host/group-host.cpp | 118 +++++++++++++++++++++++++++++++---- 1 file changed, 107 insertions(+), 11 deletions(-) diff --git a/src/wine-host/group-host.cpp b/src/wine-host/group-host.cpp index afd3f03c..76a7450d 100644 --- a/src/wine-host/group-host.cpp +++ b/src/wine-host/group-host.cpp @@ -14,7 +14,16 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include "boost-fix.h" + +#include +#include +#include +#include +#include #include +#include +#include // Generated inside of build directory #include @@ -22,6 +31,27 @@ #include "wine-bridge.h" +// FIXME: `std::filesystem` is broken in wineg++, at least under Wine 5.8. Any +// path operation will thrown an encoding related error. +namespace fs = boost::filesystem; + +// TODO: Move most plumbing to another file + +/** + * Create a logger prefix containing the group name based on the socket path. + */ +std::string create_logger_prefix(const fs::path& socket_path); + +/** + * Continuously Read from a stream and write the lines to a logger instance. + * + * TODO: Merge this with the other similar function in `PluginBridge` + */ +void log_lines(Logger& logger, + boost::asio::posix::stream_descriptor& pipe, + boost::asio::streambuf& buffer, + std::string prefix); + /** * This works very similar to the host application defined in * `individual-host.cpp`, but instead of just loading a single plugin this will @@ -39,34 +69,55 @@ int __cdecl main(int argc, char* argv[]) { // Instead of directly hosting a plugin, this process will receive a UNIX // domain socket endpoint path that it should listen on to allow yabridge // instances to spawn plugins in this process. - if (argc < 3) { + if (argc < 2) { std::cerr << "Usage: " #ifdef __i386__ << yabridge_group_host_name_32bit #else << yabridge_group_host_name #endif - << " " << std::endl; + << " " << std::endl; return 1; } - const std::string group_name(argv[1]); - const std::string group_socket_endpoint_path(argv[2]); + const std::string group_socket_endpoint_path(argv[1]); // TODO: Before doing anything, try listening on the socket and fail // silently (or log a message?) if another application is already // listening on the socket. This way we don't need any complicated // inter-process synchronization to ensure that there is a single // active group host listening for this group. - // TODO: We should somehow try and redirect this process's STDOUT and STDERR - // streams to the logger so we can forward Wine's debug messages to - // the log even the yabridge plugin instance that initially spawned - // this group host process has exited. The only way I can think of - // doing this would be using some kind of in memory file and the - // `dup2()` system call. - Logger logger = Logger::create_from_environment(group_name); + // Has to be initialized before redirecting the STDIO streams + Logger logger = Logger::create_from_environment( + create_logger_prefix(group_socket_endpoint_path)); + + // Redirect this process's STDOUT and STDERR streams to a pipe so we can + // process the output and redirect it to a logger. Needed to capture Wine + // debug output, since this process will likely outlive the yabridge + // instance that originally spawned it. + int stdout_pipe[2]; + pipe(stdout_pipe); + dup2(stdout_pipe[1], STDOUT_FILENO); + close(stdout_pipe[1]); + + int stderr_pipe[2]; + pipe(stderr_pipe); + dup2(stderr_pipe[1], STDERR_FILENO); + close(stderr_pipe[1]); + + boost::asio::io_context io_context; + boost::asio::streambuf stdout_buffer; + boost::asio::streambuf stderr_buffer; + boost::asio::posix::stream_descriptor stdout_redirect(io_context, + stdout_pipe[0]); + boost::asio::posix::stream_descriptor stderr_redirect(io_context, + stderr_pipe[0]); + log_lines(logger, stdout_redirect, stdout_buffer, "[STDOUT] "); + log_lines(logger, stderr_redirect, stderr_buffer, "[STDERR] "); + std::thread io_handler([&]() { io_context.run(); }); + logger.log("Initializing yabridge group host version " + std::string(yabridge_git_version) #ifdef __i386__ @@ -74,8 +125,53 @@ int __cdecl main(int argc, char* argv[]) { #endif ); + // TODO: Remove debug prints + printf("This should be caught now!\n"); + std::cerr << "This too!" << std::endl; + // TODO: After initializing, listen for connections and spawn plugins // the exact same way as what happens in `individual-host.cpp` // TODO: Allow this process to exit when the last plugin exits. Make sure // that that doesn't cause any race conditions. + + // TODO: This usleep() is just to ensure that the second print to stderr + // also gets processed before stopping the IO context since we're + // immediately stopping it after starting. This would not needed in + // normal use. + usleep(1000); + io_context.stop(); + io_handler.join(); +} + +void log_lines(Logger& logger, + boost::asio::posix::stream_descriptor& pipe, + boost::asio::streambuf& buffer, + std::string prefix) { + boost::asio::async_read_until(pipe, buffer, '\n', + [&, prefix](const auto&, size_t) { + std::string line; + std::getline(std::istream(&buffer), line); + logger.log(prefix + line); + + log_lines(logger, pipe, buffer, prefix); + }); +} + +std::string create_logger_prefix(const fs::path& socket_path) { + // The group socket filename will be in the format + // '/tmp/yabridge-group---.sock', + // where Wine prefix ID is just Wine prefix ran through `std::hash` to + // prevent collisions without needing complicated filenames. We want to + // extract the group name. + std::string socket_name = + socket_path.filename().replace_extension().string(); + + std::smatch group_match; + std::regex group_regexp("^yabridge-group-(.*)-[^-]+-[^-]+$", + std::regex::ECMAScript); + if (std::regex_match(socket_name, group_match, group_regexp)) { + socket_name = group_match[1].str(); + } + + return "[" + socket_name + "] "; }