Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/bluez/horipad_steam/input_reader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <unistd.h>

#include "../../utils/logging.h"
#include "../../utils/sys.h"
#include "../hidraw.hpp"
#include "input_reader.h"

Expand All @@ -32,11 +33,12 @@ void InputReader::open_and_init() {
LOG_DEBUG("hidraw device: {}", device_);

// Non-blocking so dispatch()'s read() never stalls the event loop.
UniqueFd fd(open(device_.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC));
if (!fd.valid()) {
LOG_ERROR("unable to open device");
auto opened = sys::open_fd(device_.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC);
if (!opened) {
LOG_ERROR("unable to open device: {}", opened.error().message());
return;
}
UniqueFd fd = std::move(*opened);

// Raw Info
hidraw_devinfo raw_dev_info{};
Expand Down
8 changes: 5 additions & 3 deletions src/bluez/ps5_dual_sense/input_reader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <unistd.h>

#include "../../utils/logging.h"
#include "../../utils/sys.h"
#include "../hidraw.hpp"
#include "input_reader.h"

Expand All @@ -33,11 +34,12 @@ void InputReader::open_and_init() {
LOG_DEBUG("hidraw device: {}", device_);

// Non-blocking so dispatch()'s read() never stalls the event loop.
UniqueFd fd(open(device_.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC));
if (!fd.valid()) {
LOG_ERROR("unable to open device");
auto opened = sys::open_fd(device_.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC);
if (!opened) {
LOG_ERROR("unable to open device: {}", opened.error().message());
return;
}
UniqueFd fd = std::move(*opened);

// Raw Info
hidraw_devinfo raw_dev_info{};
Expand Down
8 changes: 5 additions & 3 deletions src/bluez/xbox_controller/input_reader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <unistd.h>

#include "../../utils/logging.h"
#include "../../utils/sys.h"
#include "../hidraw.hpp"
#include "input_reader.h"

Expand All @@ -32,11 +33,12 @@ void InputReader::open_and_init() {
LOG_DEBUG("hidraw device: {}", device_);

// Non-blocking so dispatch()'s read() never stalls the event loop.
UniqueFd fd(open(device_.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC));
if (!fd.valid()) {
LOG_ERROR("unable to open device");
auto opened = sys::open_fd(device_.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC);
if (!opened) {
LOG_ERROR("unable to open device: {}", opened.error().message());
return;
}
UniqueFd fd = std::move(*opened);

// Raw Info
hidraw_devinfo raw_dev_info{};
Expand Down
5 changes: 5 additions & 0 deletions src/utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
add_library(utils STATIC
event_loop.cc
event_loop.h
sys.h
utils.cc
utils.h
)
Expand All @@ -16,4 +17,8 @@ if (SDBUS_CPP_EXAMPLES_BUILD_TESTS)
add_executable(event_loop_test event_loop_test.cc)
target_link_libraries(event_loop_test PRIVATE utils sdbus-c++ spdlog::spdlog)
add_test(NAME event_loop_test COMMAND event_loop_test)

add_executable(sys_test sys_test.cc)
target_link_libraries(sys_test PRIVATE utils spdlog::spdlog)
add_test(NAME sys_test COMMAND sys_test)
endif ()
9 changes: 6 additions & 3 deletions src/utils/event_loop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,17 @@
#include <sdbus-c++/sdbus-c++.h>

#include "logging.h"
#include "sys.h"

namespace {
constexpr std::size_t kNotPresent = static_cast<std::size_t>(-1);
} // namespace

EventLoop::EventLoop() : wake_fd_(::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)) {
if (!wake_fd_.valid()) {
LOG_ERROR("EventLoop: failed to create eventfd: {}", strerror(errno));
EventLoop::EventLoop() {
if (auto fd = sys::make_eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)) {
wake_fd_ = std::move(*fd);
} else {
LOG_ERROR("EventLoop: failed to create eventfd: {}", fd.error().message());
}
}

Expand Down
8 changes: 5 additions & 3 deletions src/utils/signal_source.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#include "event_loop.h"
#include "logging.h"
#include "sys.h"
#include "unique_fd.h"

/// Turns POSIX signals into an EventLoop stop(). The signals are blocked
Expand All @@ -45,12 +46,13 @@ class SignalSource final : public EventSource {
if (sigprocmask(SIG_BLOCK, &mask_, nullptr) < 0) {
LOG_ERROR("SignalSource: sigprocmask failed: {}", strerror(errno));
}
fd_ = UniqueFd(::signalfd(-1, &mask_, SFD_CLOEXEC | SFD_NONBLOCK));
if (!fd_.valid()) {
if (auto fd = sys::make_signalfd(-1, mask_, SFD_CLOEXEC | SFD_NONBLOCK)) {
fd_ = std::move(*fd);
} else {
LOG_ERROR(
"SignalSource: signalfd failed: {}; restoring default signal "
"disposition",
strerror(errno));
fd.error().message());
// Undo the block, otherwise the signals stay blocked with no consumer and
// the process can no longer be stopped via SIGINT/SIGTERM.
sigprocmask(SIG_UNBLOCK, &mask_, nullptr);
Expand Down
97 changes: 97 additions & 0 deletions src/utils/sys.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) 2026 Joel Winarske
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef SRC_UTILS_SYS_H
#define SRC_UTILS_SYS_H

#include <cerrno>
#include <expected>
#include <system_error>

#include <fcntl.h>
#include <sys/eventfd.h>
#include <sys/signalfd.h>
#include <sys/stat.h>
#include <sys/timerfd.h>

#include "unique_fd.h"

/// Thin std::expected wrappers over the fd-creating syscalls.
///
/// Fallible system calls return std::expected<UniqueFd, std::error_code> so a
/// caller gets a [[nodiscard]], allocation-free error channel — errno mapped to
/// std::generic_category() — instead of a bare -1 it must remember to check,
/// and the fd is owned (closed on scope exit) the moment it is produced.
///
/// This is the errno layer only. D-Bus failures still surface as sdbus::Error
/// exceptions (sdbus-c++'s model), and absent-but-not-erroneous values still
/// use std::optional; both are the right tool for their layer.
namespace sys {

/// The current errno as a std::error_code.
[[nodiscard]] inline std::error_code last_error() noexcept {
return {errno, std::generic_category()};
}

[[nodiscard]] inline std::expected<UniqueFd, std::error_code> open_fd(
const char* path,
const int flags) noexcept {
UniqueFd fd(::open(path, flags));
if (!fd.valid()) {
return std::unexpected(last_error());
}
return fd;
}

[[nodiscard]] inline std::expected<UniqueFd, std::error_code>
open_fd(const char* path, const int flags, const mode_t mode) noexcept {
UniqueFd fd(::open(path, flags, mode));
if (!fd.valid()) {
return std::unexpected(last_error());
}
return fd;
}

[[nodiscard]] inline std::expected<UniqueFd, std::error_code> make_eventfd(
const unsigned initval,
const int flags) noexcept {
UniqueFd fd(::eventfd(initval, flags));
if (!fd.valid()) {
return std::unexpected(last_error());
}
return fd;
}

[[nodiscard]] inline std::expected<UniqueFd, std::error_code>
make_signalfd(const int fd, const sigset_t& mask, const int flags) noexcept {
UniqueFd out(::signalfd(fd, &mask, flags));
if (!out.valid()) {
return std::unexpected(last_error());
}
return out;
}

[[nodiscard]] inline std::expected<UniqueFd, std::error_code> make_timerfd(
const int clockid,
const int flags) noexcept {
UniqueFd fd(::timerfd_create(clockid, flags));
if (!fd.valid()) {
return std::unexpected(last_error());
}
return fd;
}

} // namespace sys

#endif // SRC_UTILS_SYS_H
66 changes: 66 additions & 0 deletions src/utils/sys_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) 2026 Joel Winarske
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Self-test for the sys:: std::expected syscall wrappers: a success returns an
// owned, valid fd; a failure returns the expected std::error_code.

#include <system_error>

#include <fcntl.h>
#include <sys/eventfd.h>
#include <sys/timerfd.h>

#include "logging.h"
#include "sys.h"

int main() {
int failures = 0;
const auto check = [&failures](const bool cond, const char* what) {
if (cond) {
LOG_INFO("sys test: {} - ok", what);
} else {
LOG_ERROR("sys test: {} - FAIL", what);
++failures;
}
};

// open_fd success: /dev/null always exists and is readable.
if (auto fd = sys::open_fd("/dev/null", O_RDONLY | O_CLOEXEC)) {
check(fd->valid(), "open_fd(/dev/null) yields a valid fd");
} else {
check(false, "open_fd(/dev/null) unexpectedly failed");
}

// open_fd failure: a missing path yields the matching error_code, no fd.
if (auto fd = sys::open_fd("/nonexistent/sdbus-cpp-examples/xyz",
O_RDONLY | O_CLOEXEC)) {
check(false, "open_fd(missing) unexpectedly succeeded");
} else {
check(fd.error() == std::errc::no_such_file_or_directory,
"open_fd(missing) reports ENOENT");
}

// make_eventfd / make_timerfd success.
check(sys::make_eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK).has_value(),
"make_eventfd yields a valid fd");
check(sys::make_timerfd(CLOCK_MONOTONIC, TFD_CLOEXEC).has_value(),
"make_timerfd yields a valid fd");

if (failures == 0) {
LOG_INFO("sys test: PASS");
return 0;
}
LOG_ERROR("sys test: FAIL ({} check(s))", failures);
return 1;
}
Loading