polybar/src/ipc/ipc.cpp

221 lines
6.9 KiB
C++

#include "ipc/ipc.hpp"
#include <sys/stat.h>
#include <unistd.h>
#include <cassert>
#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<ipc>(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<PipeHandle>()) {
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<fifo>(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<ipc::connection>(
m_loop, [this](ipc::connection& c, uint8_t, type_t type, const vector<uint8_t>& msg) {
vector<uint8_t> response;
if (type == to_integral(v0::ipc_type::ACTION) || type == to_integral(v0::ipc_type::CMD)) {
auto ipc_type = static_cast<v0::ipc_type>(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<PipeHandle>())
, 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<PipeHandle>()) {
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<fifo>(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