Merge pull request #4891 from khaneliman/bugfix/stab-003-test-001-hyprland-ipc
fix(hyprland-ipc): harden fd lifecycle and listener loop
This commit is contained in:
@@ -9,9 +9,14 @@
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#include "util/scoped_fd.hpp"
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
std::filesystem::path IPC::socketFolder_;
|
||||
@@ -45,8 +50,8 @@ std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
|
||||
|
||||
IPC::IPC() {
|
||||
// will start IPC and relay events to parseIPC
|
||||
ipcThread_ = std::thread([this]() { socketListener(); });
|
||||
socketOwnerPid_ = getpid();
|
||||
ipcThread_ = std::thread([this]() { socketListener(); });
|
||||
}
|
||||
|
||||
IPC::~IPC() {
|
||||
@@ -54,19 +59,20 @@ IPC::~IPC() {
|
||||
// failed exec()) exits.
|
||||
if (getpid() != socketOwnerPid_) return;
|
||||
|
||||
running_ = false;
|
||||
running_.store(false, std::memory_order_relaxed);
|
||||
spdlog::info("Hyprland IPC stopping...");
|
||||
if (socketfd_ != -1) {
|
||||
spdlog::trace("Shutting down socket");
|
||||
if (shutdown(socketfd_, SHUT_RDWR) == -1) {
|
||||
spdlog::error("Hyprland IPC: Couldn't shutdown socket");
|
||||
}
|
||||
spdlog::trace("Closing socket");
|
||||
if (close(socketfd_) == -1) {
|
||||
spdlog::error("Hyprland IPC: Couldn't close socket");
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(socketMutex_);
|
||||
if (socketfd_ != -1) {
|
||||
spdlog::trace("Shutting down socket");
|
||||
if (shutdown(socketfd_, SHUT_RDWR) == -1 && errno != ENOTCONN) {
|
||||
spdlog::error("Hyprland IPC: Couldn't shutdown socket");
|
||||
}
|
||||
}
|
||||
}
|
||||
ipcThread_.join();
|
||||
if (ipcThread_.joinable()) {
|
||||
ipcThread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
IPC& IPC::inst() {
|
||||
@@ -86,9 +92,9 @@ void IPC::socketListener() {
|
||||
spdlog::info("Hyprland IPC starting");
|
||||
|
||||
struct sockaddr_un addr;
|
||||
socketfd_ = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
const int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
if (socketfd_ == -1) {
|
||||
if (socketfd == -1) {
|
||||
spdlog::error("Hyprland IPC: socketfd failed");
|
||||
return;
|
||||
}
|
||||
@@ -102,38 +108,67 @@ void IPC::socketListener() {
|
||||
|
||||
int l = sizeof(struct sockaddr_un);
|
||||
|
||||
if (connect(socketfd_, (struct sockaddr*)&addr, l) == -1) {
|
||||
spdlog::error("Hyprland IPC: Unable to connect?");
|
||||
if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {
|
||||
spdlog::error("Hyprland IPC: Unable to connect? {}", std::strerror(errno));
|
||||
close(socketfd);
|
||||
return;
|
||||
}
|
||||
auto* file = fdopen(socketfd_, "r");
|
||||
if (file == nullptr) {
|
||||
spdlog::error("Hyprland IPC: Couldn't open file descriptor");
|
||||
return;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(socketMutex_);
|
||||
socketfd_ = socketfd;
|
||||
}
|
||||
while (running_) {
|
||||
|
||||
std::string pending;
|
||||
while (running_.load(std::memory_order_relaxed)) {
|
||||
std::array<char, 1024> buffer; // Hyprland socket2 events are max 1024 bytes
|
||||
const ssize_t bytes_read = read(socketfd, buffer.data(), buffer.size());
|
||||
|
||||
auto* receivedCharPtr = fgets(buffer.data(), buffer.size(), file);
|
||||
|
||||
if (receivedCharPtr == nullptr) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
continue;
|
||||
if (bytes_read == 0) {
|
||||
if (running_.load(std::memory_order_relaxed)) {
|
||||
spdlog::warn("Hyprland IPC: Socket closed by peer");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
std::string messageReceived(buffer.data());
|
||||
messageReceived = messageReceived.substr(0, messageReceived.find_first_of('\n'));
|
||||
spdlog::debug("hyprland IPC received {}", messageReceived);
|
||||
|
||||
try {
|
||||
parseIPC(messageReceived);
|
||||
} catch (std::exception& e) {
|
||||
spdlog::warn("Failed to parse IPC message: {}, reason: {}", messageReceived, e.what());
|
||||
} catch (...) {
|
||||
throw;
|
||||
if (bytes_read < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (!running_.load(std::memory_order_relaxed)) {
|
||||
break;
|
||||
}
|
||||
spdlog::error("Hyprland IPC: read failed: {}", std::strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
pending.append(buffer.data(), static_cast<std::size_t>(bytes_read));
|
||||
for (auto newline_pos = pending.find('\n'); newline_pos != std::string::npos;
|
||||
newline_pos = pending.find('\n')) {
|
||||
std::string messageReceived = pending.substr(0, newline_pos);
|
||||
pending.erase(0, newline_pos + 1);
|
||||
if (messageReceived.empty()) {
|
||||
continue;
|
||||
}
|
||||
spdlog::debug("hyprland IPC received {}", messageReceived);
|
||||
|
||||
try {
|
||||
parseIPC(messageReceived);
|
||||
} catch (std::exception& e) {
|
||||
spdlog::warn("Failed to parse IPC message: {}, reason: {}", messageReceived, e.what());
|
||||
} catch (...) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(socketMutex_);
|
||||
if (socketfd_ != -1) {
|
||||
if (close(socketfd_) == -1) {
|
||||
spdlog::error("Hyprland IPC: Couldn't close socket");
|
||||
}
|
||||
socketfd_ = -1;
|
||||
}
|
||||
}
|
||||
spdlog::debug("Hyprland IPC stopped");
|
||||
}
|
||||
@@ -178,7 +213,7 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) {
|
||||
std::string IPC::getSocket1Reply(const std::string& rq) {
|
||||
// basically hyprctl
|
||||
|
||||
const auto serverSocket = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
util::ScopedFd serverSocket(socket(AF_UNIX, SOCK_STREAM, 0));
|
||||
|
||||
if (serverSocket < 0) {
|
||||
throw std::runtime_error("Hyprland IPC: Couldn't open a socket (1)");
|
||||
@@ -223,13 +258,11 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
|
||||
|
||||
if (sizeWritten < 0) {
|
||||
spdlog::error("Hyprland IPC: Couldn't read (5)");
|
||||
close(serverSocket);
|
||||
return "";
|
||||
}
|
||||
response.append(buffer.data(), sizeWritten);
|
||||
} while (sizeWritten > 0);
|
||||
|
||||
close(serverSocket);
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "util/scoped_fd.hpp"
|
||||
#include "giomm/datainputstream.h"
|
||||
#include "giomm/dataoutputstream.h"
|
||||
#include "giomm/unixinputstream.h"
|
||||
@@ -30,7 +31,7 @@ int IPC::connectToSocket() {
|
||||
}
|
||||
|
||||
struct sockaddr_un addr;
|
||||
int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
util::ScopedFd socketfd(socket(AF_UNIX, SOCK_STREAM, 0));
|
||||
|
||||
if (socketfd == -1) {
|
||||
throw std::runtime_error("socketfd failed");
|
||||
@@ -45,11 +46,10 @@ int IPC::connectToSocket() {
|
||||
int l = sizeof(struct sockaddr_un);
|
||||
|
||||
if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {
|
||||
close(socketfd);
|
||||
throw std::runtime_error("unable to connect");
|
||||
}
|
||||
|
||||
return socketfd;
|
||||
return socketfd.release();
|
||||
}
|
||||
|
||||
void IPC::startIPC() {
|
||||
@@ -235,7 +235,7 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) {
|
||||
}
|
||||
|
||||
Json::Value IPC::send(const Json::Value& request) {
|
||||
int socketfd = connectToSocket();
|
||||
util::ScopedFd socketfd(connectToSocket());
|
||||
|
||||
auto unix_istream = Gio::UnixInputStream::create(socketfd, true);
|
||||
auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false);
|
||||
|
||||
@@ -9,8 +9,8 @@ namespace waybar::modules::sway {
|
||||
|
||||
Ipc::Ipc() {
|
||||
const std::string& socketPath = getSocketPath();
|
||||
fd_ = open(socketPath);
|
||||
fd_event_ = open(socketPath);
|
||||
fd_ = util::ScopedFd(open(socketPath));
|
||||
fd_event_ = util::ScopedFd(open(socketPath));
|
||||
}
|
||||
|
||||
Ipc::~Ipc() {
|
||||
@@ -21,15 +21,11 @@ Ipc::~Ipc() {
|
||||
if (write(fd_, "close-sway-ipc", 14) == -1) {
|
||||
spdlog::error("Failed to close sway IPC");
|
||||
}
|
||||
close(fd_);
|
||||
fd_ = -1;
|
||||
}
|
||||
if (fd_event_ > 0) {
|
||||
if (write(fd_event_, "close-sway-ipc", 14) == -1) {
|
||||
spdlog::error("Failed to close sway IPC event handler");
|
||||
}
|
||||
close(fd_event_);
|
||||
fd_event_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +60,7 @@ const std::string Ipc::getSocketPath() const {
|
||||
}
|
||||
|
||||
int Ipc::open(const std::string& socketPath) const {
|
||||
int32_t fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
util::ScopedFd fd(socket(AF_UNIX, SOCK_STREAM, 0));
|
||||
if (fd == -1) {
|
||||
throw std::runtime_error("Unable to open Unix socket");
|
||||
}
|
||||
@@ -78,7 +74,7 @@ int Ipc::open(const std::string& socketPath) const {
|
||||
if (::connect(fd, reinterpret_cast<struct sockaddr*>(&addr), l) == -1) {
|
||||
throw std::runtime_error("Unable to connect to Sway");
|
||||
}
|
||||
return fd;
|
||||
return fd.release();
|
||||
}
|
||||
|
||||
struct Ipc::ipc_response Ipc::recv(int fd) {
|
||||
|
||||
@@ -27,14 +27,14 @@ inline auto byteswap(uint32_t x) -> uint32_t {
|
||||
auto pack_and_write(Sock& sock, std::string&& buf) -> void {
|
||||
uint32_t len = buf.size();
|
||||
if constexpr (std::endian::native != std::endian::little) len = byteswap(len);
|
||||
(void)write(sock.fd, &len, 4);
|
||||
(void)write(sock.fd, buf.data(), buf.size());
|
||||
(void)write(sock, &len, 4);
|
||||
(void)write(sock, buf.data(), buf.size());
|
||||
}
|
||||
|
||||
auto read_exact(Sock& sock, size_t n) -> std::string {
|
||||
auto buf = std::string(n, 0);
|
||||
for (size_t i = 0; i < n;) {
|
||||
auto r = read(sock.fd, &buf[i], n - i);
|
||||
auto r = read(sock, &buf[i], n - i);
|
||||
if (r <= 0) {
|
||||
throw std::runtime_error("Wayfire IPC: read failed");
|
||||
}
|
||||
@@ -111,7 +111,7 @@ auto IPC::connect() -> Sock {
|
||||
throw std::runtime_error{"Wayfire IPC: ipc not available"};
|
||||
}
|
||||
|
||||
auto sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
util::ScopedFd sock(socket(AF_UNIX, SOCK_STREAM, 0));
|
||||
if (sock == -1) {
|
||||
throw std::runtime_error{"Wayfire IPC: socket() failed"};
|
||||
}
|
||||
@@ -121,11 +121,10 @@ auto IPC::connect() -> Sock {
|
||||
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
|
||||
|
||||
if (::connect(sock, (const sockaddr*)&addr, sizeof(addr)) == -1) {
|
||||
close(sock);
|
||||
throw std::runtime_error{"Wayfire IPC: connect() failed"};
|
||||
}
|
||||
|
||||
return {sock};
|
||||
return sock;
|
||||
}
|
||||
|
||||
auto IPC::receive(Sock& sock) -> Json::Value {
|
||||
|
||||
Reference in New Issue
Block a user