diff --git a/server/streamreader/process.hpp b/server/streamreader/process.hpp deleted file mode 100644 index 4f6ac622..00000000 --- a/server/streamreader/process.hpp +++ /dev/null @@ -1,247 +0,0 @@ -/*** - This file is part of snapcast - Copyright (C) 2014-2020 Johannes Pohl - 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 . -***/ - -#ifndef TINY_PROCESS_LIBRARY_HPP_ -#define TINY_PROCESS_LIBRARY_HPP_ - -#include -#include -#include -#include -#include -#include - -// Forked from: https://github.com/eidheim/tiny-process-library -// Copyright (c) 2015-2016 Ole Christian Eidheim -// Thanks, Christian :-) - -namespace streamreader -{ - -/// Create a new process given command and run path. -/// Thus, at the moment, if read_stdout==nullptr, read_stderr==nullptr and open_stdin==false, -/// the stdout, stderr and stdin are sent to the parent process instead. -/// Compile with -DMSYS_PROCESS_USE_SH to run command using "sh -c [command]" on Windows as well. -class Process -{ - -public: - typedef int fd_type; - - Process(const std::string& command, const std::string& path = "") : closed(true) - { - open(command, path); - } - - ~Process() - { - close_fds(); - } - - /// Get the process id of the started process. - pid_t getPid() - { - return pid; - } - - /// Write to stdin. Convenience function using write(const char *, size_t). - bool write(const std::string& data) - { - return write(data.c_str(), data.size()); - } - - /// Wait until process is finished, and return exit status. - int get_exit_status() - { - if (pid <= 0) - return -1; - - int exit_status; - waitpid(pid, &exit_status, 0); - { - std::lock_guard lock(close_mutex); - closed = true; - } - close_fds(); - - if (exit_status >= 256) - exit_status = exit_status >> 8; - - return exit_status; - } - - /// Write to stdin. - bool write(const char* bytes, size_t n) - { - std::lock_guard lock(stdin_mutex); - if (::write(stdin_fd, bytes, n) >= 0) - return true; - else - return false; - } - - /// Close stdin. If the process takes parameters from stdin, use this to notify that all parameters have been sent. - void close_stdin() - { - std::lock_guard lock(stdin_mutex); - if (pid > 0) - close(stdin_fd); - } - - /// Kill the process. - void kill(bool force = false) - { - std::lock_guard lock(close_mutex); - if (pid > 0 && !closed) - { - if (force) - ::kill(-pid, SIGTERM); - else - ::kill(-pid, SIGINT); - } - } - - /// Kill a given process id. Use kill(bool force) instead if possible. - static void kill(pid_t id, bool force = false) - { - if (id <= 0) - return; - if (force) - ::kill(-id, SIGTERM); - else - ::kill(-id, SIGINT); - } - - fd_type getStdout() - { - return stdout_fd; - } - - fd_type getStderr() - { - return stderr_fd; - } - - fd_type getStdin() - { - return stdin_fd; - } - - -private: - pid_t pid; - bool closed; - std::mutex close_mutex; - std::mutex stdin_mutex; - - fd_type stdout_fd, stderr_fd, stdin_fd; - - void closePipe(int pipefd[2]) - { - close(pipefd[0]); - close(pipefd[1]); - } - - pid_t open(const std::string& command, const std::string& path) - { - int stdin_p[2], stdout_p[2], stderr_p[2]; - - if (pipe(stdin_p) != 0) - return -1; - - if (pipe(stdout_p) != 0) - { - closePipe(stdin_p); - return -1; - } - - if (pipe(stderr_p) != 0) - { - closePipe(stdin_p); - closePipe(stdout_p); - return -1; - } - - pid = fork(); - - if (pid < 0) - { - closePipe(stdin_p); - closePipe(stdout_p); - closePipe(stderr_p); - return pid; - } - else if (pid == 0) - { - dup2(stdin_p[0], 0); - dup2(stdout_p[1], 1); - dup2(stderr_p[1], 2); - - closePipe(stdin_p); - closePipe(stdout_p); - closePipe(stderr_p); - - // Based on http://stackoverflow.com/a/899533/3808293 - int fd_max = sysconf(_SC_OPEN_MAX); - for (int fd = 3; fd < fd_max; fd++) - close(fd); - - setpgid(0, 0); - - if (!path.empty()) - { - auto path_escaped = path; - size_t pos = 0; - // Based on https://www.reddit.com/r/cpp/comments/3vpjqg/a_new_platform_independent_process_library_for_c11/cxsxyb7 - while ((pos = path_escaped.find('\'', pos)) != std::string::npos) - { - path_escaped.replace(pos, 1, "'\\''"); - pos += 4; - } - execl("/bin/sh", "sh", "-c", ("cd '" + path_escaped + "' && " + command).c_str(), NULL); - } - else - execl("/bin/sh", "sh", "-c", command.c_str(), NULL); - - _exit(EXIT_FAILURE); - } - - close(stdin_p[0]); - close(stdout_p[1]); - close(stderr_p[1]); - - stdin_fd = stdin_p[1]; - stdout_fd = stdout_p[0]; - stderr_fd = stderr_p[0]; - - closed = false; - return pid; - } - - - void close_fds() - { - close_stdin(); - if (pid > 0) - { - close(stdout_fd); - close(stderr_fd); - } - } -}; - -} // namespace streamreader - -#endif // TINY_PROCESS_LIBRARY_HPP_ diff --git a/server/streamreader/process_stream.cpp b/server/streamreader/process_stream.cpp index a291cdb5..1d0926bd 100644 --- a/server/streamreader/process_stream.cpp +++ b/server/streamreader/process_stream.cpp @@ -35,8 +35,7 @@ namespace streamreader static constexpr auto LOG_TAG = "ProcessStream"; -ProcessStream::ProcessStream(PcmListener* pcmListener, boost::asio::io_context& ioc, const StreamUri& uri) - : PosixStream(pcmListener, ioc, uri), path_(""), process_(nullptr) +ProcessStream::ProcessStream(PcmListener* pcmListener, boost::asio::io_context& ioc, const StreamUri& uri) : PosixStream(pcmListener, ioc, uri) { params_ = uri_.getQuery("params"); wd_timeout_sec_ = cpt::stoul(uri_.getQuery("wd_timeout", "0")); @@ -103,11 +102,16 @@ void ProcessStream::do_connect() return; initExeAndPath(uri_.path); LOG(DEBUG, LOG_TAG) << "Launching: '" << path_ + exe_ << "', with params: '" << params_ << "', in path: '" << path_ << "'\n"; - process_.reset(new Process(path_ + exe_ + " " + params_, path_)); - int flags = fcntl(process_->getStdout(), F_GETFL, 0); - fcntl(process_->getStdout(), F_SETFL, flags | O_NONBLOCK); - stream_ = make_unique(ioc_, process_->getStdout()); - stream_stderr_ = make_unique(ioc_, process_->getStderr()); + + pipe_stdout_ = bp::pipe(); + pipe_stderr_ = bp::pipe(); + // stdout pipe should not block + int flags = fcntl(pipe_stdout_.native_source(), F_GETFL, 0); + fcntl(pipe_stdout_.native_source(), F_SETFL, flags | O_NONBLOCK); + + process_ = bp::child(path_ + exe_ + " " + params_, bp::std_out > pipe_stdout_, bp::std_err > pipe_stderr_, bp::start_dir = path_); + stream_ = make_unique(ioc_, pipe_stdout_.native_source()); + stream_stderr_ = make_unique(ioc_, pipe_stderr_.native_source()); on_connect(); if (wd_timeout_sec_ > 0) { @@ -124,8 +128,8 @@ void ProcessStream::do_connect() void ProcessStream::do_disconnect() { - if (process_) - process_->kill(); + if (process_.running()) + ::kill(-process_.native_handle(), SIGINT); } @@ -146,7 +150,7 @@ void ProcessStream::stderrReadLine() *stream_stderr_, streambuf_stderr_, delimiter, [this, self, delimiter](const std::error_code& ec, std::size_t bytes_transferred) { if (ec) { - LOG(ERROR, LOG_TAG) << "Error while reading from control socket: " << ec.message() << "\n"; + LOG(ERROR, LOG_TAG) << "Error while reading from stderr: " << ec.message() << "\n"; return; } @@ -171,7 +175,7 @@ void ProcessStream::onTimeout(const Watchdog& /*watchdog*/, std::chrono::millise { LOG(ERROR, LOG_TAG) << "Watchdog timeout: " << ms.count() / 1000 << "s\n"; if (process_) - process_->kill(); + ::kill(-process_.native_handle(), SIGINT); } } // namespace streamreader diff --git a/server/streamreader/process_stream.hpp b/server/streamreader/process_stream.hpp index 5cdea921..d81eab16 100644 --- a/server/streamreader/process_stream.hpp +++ b/server/streamreader/process_stream.hpp @@ -19,13 +19,17 @@ #ifndef PROCESS_STREAM_HPP #define PROCESS_STREAM_HPP +#include #include #include #include "posix_stream.hpp" -#include "process.hpp" #include "watchdog.hpp" + +namespace bp = boost::process; + + namespace streamreader { @@ -49,7 +53,9 @@ protected: std::string exe_; std::string path_; std::string params_; - std::unique_ptr process_; + bp::pipe pipe_stdout_; + bp::pipe pipe_stderr_; + bp::child process_; bool logStderr_; boost::asio::streambuf streambuf_stderr_;