#include "ipc/ipc.hpp" #include #include #include #include "components/eventloop.hpp" #include "components/logger.hpp" #include "errors.hpp" #include "events/signal.hpp" #include "events/signal_emitter.hpp" #include "ipc/encoder.hpp" #include "ipc/util.hpp" #include "utils/env.hpp" #include "utils/file.hpp" #include "utils/string.hpp" POLYBAR_NS using namespace eventloop; namespace ipc { /** * Message types */ static constexpr const char* ipc_command_prefix{"cmd:"}; static constexpr const char* ipc_hook_prefix{"hook:"}; static constexpr const char* ipc_action_prefix{"action:"}; /** * Create instance */ ipc::make_type ipc::make(loop& loop) { return std::make_unique(signal_emitter::make(), logger::make(), loop); } /** * Construct ipc handler */ ipc::ipc(signal_emitter& emitter, const logger& logger, loop& loop) : m_sig(emitter), m_log(logger), m_loop(loop), socket(loop.handle()) { m_pipe_path = string_util::replace(PATH_MESSAGING_FIFO, "%pid%", to_string(getpid())); if (file_util::exists(m_pipe_path) && unlink(m_pipe_path.c_str()) == -1) { throw system_error("Failed to remove ipc channel"); } if (mkfifo(m_pipe_path.c_str(), 0600) == -1) { throw system_error("Failed to create ipc channel"); } m_log.info("Created legacy ipc fifo at '%s'", m_pipe_path); ipc_pipe = make_unique(m_loop, *this, m_pipe_path); string sock_path = get_socket_path(getpid()); m_log.info("Opening ipc socket at '%s'", sock_path); m_log.notice("Listening for IPC messages (PID: %d)", getpid()); socket->bind(sock_path); socket->listen( 4, [this]() { on_connection(); }, [this](const auto& e) { m_log.err("libuv error while listening to IPC socket: %s", uv_strerror(e.status)); socket->close(); }); } /** * Deconstruct ipc handler */ ipc::~ipc() { m_log.trace("ipc: Removing named pipe at: %s", m_pipe_path); if (unlink(m_pipe_path.c_str()) == -1) { m_log.err("Failed to delete ipc named pipe: %s", strerror(errno)); } } string ipc::get_socket_path(int pid) { return ensure_runtime_path() + "/ipc." + to_string(pid) + ".sock"; } bool ipc::trigger_ipc(v0::ipc_type type, const string& msg) { switch (type) { case v0::ipc_type::CMD: m_log.info("Received ipc command: '%s'", msg); return m_sig.emit(signals::ipc::command{msg}); case v0::ipc_type::ACTION: m_log.info("Received ipc action: '%s'", msg); return m_sig.emit(signals::ipc::action{msg}); } assert(false); return false; } void ipc::trigger_legacy_ipc(const string& msg) { m_log.info("Received ipc message: '%s'", msg); if (msg.find(ipc_command_prefix) == 0) { m_sig.emit(signals::ipc::command{msg.substr(strlen(ipc_command_prefix))}); } else if (msg.find(ipc_hook_prefix) == 0) { m_sig.emit(signals::ipc::hook{msg.substr(strlen(ipc_hook_prefix))}); } else if (msg.find(ipc_action_prefix) == 0) { m_sig.emit(signals::ipc::action{msg.substr(strlen(ipc_action_prefix))}); } else { m_log.warn("Received unknown ipc message: (payload=%s)", msg); } } void ipc::on_connection() { auto connection = make_unique( m_loop, [this](ipc::connection& c, uint8_t, type_t type, const vector& msg) { vector response; if (type == to_integral(v0::ipc_type::ACTION) || type == to_integral(v0::ipc_type::CMD)) { auto ipc_type = static_cast(type); string str; str.insert(str.end(), msg.begin(), msg.end()); if (trigger_ipc(ipc_type, str)) { response = encode(TYPE_OK); } else { response = encode(TYPE_ERR, "Error while executing ipc message, see polybar log for details."); } } else { response = encode(TYPE_ERR, "Unrecognized IPC message type " + to_string(type)); } c.client_pipe->write( response, [this, &c]() { remove_client(c); }, [this, &c](const auto& e) { m_log.err("ipc: libuv error while writing to IPC socket: %s", uv_strerror(e.status)); remove_client(c); }); }); auto& c = *connection; socket->accept(*c.client_pipe); c.client_pipe->read_start( [this, &c](const auto& e) { try { c.dec.on_read((const uint8_t*)e.data, e.len); } catch (const decoder::error& e) { m_log.err("ipc: Failed to decode IPC message (reason: %s)", e.what()); c.client_pipe->write( encode(TYPE_ERR, "Invalid binary message format: "s + e.what()), [this, &c]() { remove_client(c); }, [this, &c](const auto& e) { m_log.err("ipc: libuv error while writing to IPC socket: %s", uv_strerror(e.status)); remove_client(c); }); } }, [this, &c]() { remove_client(c); }, [this, &c](const auto& e) { m_log.err("ipc: libuv error while listening to IPC socket: %s", uv_strerror(e.status)); remove_client(c); }); connections.emplace(std::move(connection)); m_log.info("ipc: New connection (%d clients)", connections.size()); } void ipc::remove_client(connection& conn) { connections.erase(connections.find(conn)); } ipc::connection::connection(loop& loop, cb msg_callback) : client_pipe(loop.handle()) , dec(logger::make(), [this, msg_callback](uint8_t version, auto type, const auto& msg) { msg_callback(*this, version, type, msg); }) {} ipc::connection::~connection() { client_pipe->close(); } ipc::fifo::fifo(loop& loop, ipc& ipc, const string& path) : pipe_handle(loop.handle()) { int fd; if ((fd = open(path.c_str(), O_RDONLY | O_NONBLOCK)) == -1) { throw system_error("Failed to open pipe '" + path + "'"); } pipe_handle->open(fd); pipe_handle->read_start([&ipc](const auto& e) mutable { ipc.receive_data(string(e.data, e.len)); }, [&ipc]() { ipc.receive_eof(); }, [&ipc](const auto& e) mutable { ipc.m_log.err("libuv error while listening to IPC channel: %s", uv_strerror(e.status)); ipc.ipc_pipe.reset(); }); } ipc::fifo::~fifo() { pipe_handle->close(); } /** * Receive parts of an IPC message */ void ipc::receive_data(string buf) { m_pipe_buffer += buf; m_log.warn("Using the named pipe at '%s' for ipc is deprecated, always use 'polybar-msg'", m_pipe_path); } /** * Called once the end of the message arrives. */ void ipc::receive_eof() { ipc_pipe = make_unique(m_loop, *this, m_pipe_path); if (m_pipe_buffer.empty()) { return; } trigger_legacy_ipc(string_util::trim(std::move(m_pipe_buffer), '\n')); m_pipe_buffer.clear(); } } // namespace ipc POLYBAR_NS_END