diff --git a/CMakeLists.txt b/CMakeLists.txt index 55661be..29cdc42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.30) +cmake_minimum_required(VERSION 3.20) if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "MinSizeRel" CACHE STRING "Choose the type of build, options are: Debug, Release, RelWithDebInfo, or MinSizeRel." FORCE) diff --git a/scripts/clang-format-check.sh b/scripts/clang-format-check.sh new file mode 100755 index 0000000..392a751 --- /dev/null +++ b/scripts/clang-format-check.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -lt 1 || $# -gt 2 ]]; then + echo "Usage: $0 [working_directory]" >&2 + echo "Example: $0 my_plugin plugins_source/plugins" >&2 + exit 2 +fi + +name="$1" +workdir="${2:-.}" +file_list="clang-format-files" +results_file="clang-format-results.txt" + +# Allow override via env; fall back to clang-format-18. +CLANG_FORMAT="${CLANG_FORMAT:-clang-format-18}" + +print_results() { + if [[ -f "$results_file" ]]; then + cat "$results_file" + fi +} + +trap print_results EXIT + +cd "$workdir" + +find "$name" -type d -name third_party -prune -false -o -name '*.cc' -o -name '*.hpp' -o -name '*.h' > "$file_list" + +echo "Formatting files:" +cat "$file_list" + +which "$CLANG_FORMAT" +"$CLANG_FORMAT" --version +"$CLANG_FORMAT" -n --Werror --files="$file_list" 2> "$results_file" diff --git a/scripts/clang-format.sh b/scripts/clang-format.sh new file mode 100755 index 0000000..b5c20fe --- /dev/null +++ b/scripts/clang-format.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -lt 1 || $# -gt 2 ]]; then + echo "Usage: $0 [working_directory]" >&2 + echo "Example: $0 my_plugin plugins_source/plugins" >&2 + exit 2 +fi + +name="$1" +workdir="${2:-.}" +file_list="clang-format-files" + +# Allow override via env; fall back to clang-format-18. +CLANG_FORMAT="${CLANG_FORMAT:-clang-format-18}" + +cd "$workdir" + +find "$name" -type d -name third_party -prune -false -o -name '*.cc' -o -name '*.hpp' -o -name '*.h' > "$file_list" + +echo "Formatting files:" +cat "$file_list" + +which "$CLANG_FORMAT" +"$CLANG_FORMAT" --version +"$CLANG_FORMAT" -i --files="$file_list" diff --git a/scripts/run-clang-tidy.sh b/scripts/run-clang-tidy.sh new file mode 100755 index 0000000..c5be452 --- /dev/null +++ b/scripts/run-clang-tidy.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +set -euo pipefail + +MYPATH="$(readlink -f "${BASH_SOURCE[0]}")" +MYDIR="${MYPATH%/*}" +MYNAME="${MYPATH##*/}" + +TARGET_PATH="${1:-}" +WORK_DIR="${2:-$(pwd)}" +BUILD_DIR="${3:-$(readlink -f "${WORK_DIR}/../../build")}" +LOG_FILE="tidy-results-log.txt" +FIX_FILE="tidy-results-suggested-fixes.txt" + +print_results() { + [ -f "${WORK_DIR}/${LOG_FILE}" ] && cat "${WORK_DIR}/${LOG_FILE}" + [ -f "${WORK_DIR}/${FIX_FILE}" ] && cat "${WORK_DIR}/${FIX_FILE}" +} + +trap print_results EXIT + +if [ -z "${TARGET_PATH}" ]; then + echo "Usage: ${MYNAME} [work-dir] [build-dir]" + echo "Example: ${MYNAME} plugin_a ./plugins_source/plugins ./build" + exit 1 +fi + +if ! command -v run-clang-tidy-18 >/dev/null 2>&1; then + echo "run-clang-tidy-18 not found in PATH" + exit 1 +fi + +if [ ! -d "${WORK_DIR}" ]; then + echo "work-dir does not exist: ${WORK_DIR}" + exit 1 +fi + +if [ ! -d "${BUILD_DIR}" ]; then + echo "build-dir does not exist: ${BUILD_DIR}" + exit 1 +fi + +pushd "${WORK_DIR}" >/dev/null + +if [ ! -e "${TARGET_PATH}" ]; then + echo "target-path does not exist in work-dir: ${TARGET_PATH}" + popd >/dev/null + exit 1 +fi + +mapfile -t FILES < <( + find "${TARGET_PATH}" \ + -type d -name third_party -prune -o \ + -type f \( -name '*.cc' -o -name '*.hpp' -o -name '*.h' \) -print +) + +if [ ${#FILES[@]} -eq 0 ]; then + echo "No files found for clang-tidy under: ${TARGET_PATH}" + popd >/dev/null + exit 0 +fi + +printf '%s\n' "${FILES[@]}" + +run-clang-tidy-18 \ + -warnings-as-errors='*,-bugprone-macro-parentheses' \ + -export-fixes="${FIX_FILE}" \ + -p "${BUILD_DIR}" \ + "${FILES[@]}" \ + 2>/dev/null \ + | tee "${LOG_FILE}" + +if [ -f "${FIX_FILE}" ] && grep -q "Level: Error" "${FIX_FILE}"; then + popd >/dev/null + exit 1 +fi + +popd >/dev/null \ No newline at end of file diff --git a/src/avahi/avahi_server.h b/src/avahi/avahi_server.h index 8a3492e..61e37cd 100644 --- a/src/avahi/avahi_server.h +++ b/src/avahi/avahi_server.h @@ -18,6 +18,7 @@ #include "../proxy/org/freedesktop/Avahi/Server/server_proxy.h" #include "../proxy/org/freedesktop/Avahi/Server2/server2_proxy.h" +#include "../utils/logging.h" #include "../utils/utils.h" class AvahiServer final @@ -38,7 +39,7 @@ class AvahiServer final std::int32_t state_{}; void onStateChanged(const int32_t& state, const std::string& error) override { - spdlog::info("onStateChanged: state={}, error={}", state, error); + LOG_INFO("onStateChanged: state={}, error={}", state, error); state_ = state; } }; diff --git a/src/avahi/main.cc b/src/avahi/main.cc index 81dd9e2..e118592 100644 --- a/src/avahi/main.cc +++ b/src/avahi/main.cc @@ -14,6 +14,7 @@ #include "avahi_server.h" +#include "../utils/logging.h" #include "../utils/utils.h" int main() { @@ -23,15 +24,15 @@ int main() { if (const auto names = Utils::ListNames(*connection); Utils::isServicePresent(names, "org.freedesktop.Avahi")) { AvahiServer server(*connection); - spdlog::info("API Version: {}", server.GetAPIVersion()); - spdlog::info("Domain Name: {}", server.GetDomainName()); - spdlog::info("Host Name: {}", server.GetHostName()); - spdlog::info("FQDN: {}", server.GetHostNameFqdn()); - spdlog::info("Local Service Cookie: {}", server.GetLocalServiceCookie()); - spdlog::info("State: {}", server.GetState()); - spdlog::info("Version: {}", server.GetVersionString()); - spdlog::info("NSSS upport available: {}", - server.IsNSSSupportAvailable() ? "Yes" : "No"); + LOG_INFO("API Version: {}", server.GetAPIVersion()); + LOG_INFO("Domain Name: {}", server.GetDomainName()); + LOG_INFO("Host Name: {}", server.GetHostName()); + LOG_INFO("FQDN: {}", server.GetHostNameFqdn()); + LOG_INFO("Local Service Cookie: {}", server.GetLocalServiceCookie()); + LOG_INFO("State: {}", server.GetState()); + LOG_INFO("Version: {}", server.GetVersionString()); + LOG_INFO("NSSS upport available: {}", + server.IsNSSSupportAvailable() ? "Yes" : "No"); } connection->leaveEventLoop(); diff --git a/src/bluez/bluez_client.cc b/src/bluez/bluez_client.cc index 589f119..0b2ba7d 100644 --- a/src/bluez/bluez_client.cc +++ b/src/bluez/bluez_client.cc @@ -14,6 +14,8 @@ #include "bluez_client.h" +#include "../utils/property_utils.h" +#include "../utils/resource_limits.h" #include "battery_provider_manager1.h" #include "gatt_manager1.h" #include "gatt_service1.h" @@ -54,6 +56,12 @@ void BluezClient::onInterfacesAdded( if (interface == org::bluez::Adapter1_proxy::INTERFACE_NAME) { std::scoped_lock lock(adapters_mutex_); if (!adapters_.contains(objectPath)) { + if (resource_limits::IsAtCapacity(adapters_.size(), + resource_limits::kMaxAdapters)) { + LOG_WARN("Skipping Adapter1 {}: resource limit reached ({}/{})", + objectPath, adapters_.size(), resource_limits::kMaxAdapters); + continue; + } auto adapter1 = std::make_unique( getProxy().getConnection(), sdbus::ServiceName(INTERFACE_NAME), objectPath, properties); @@ -62,14 +70,27 @@ void BluezClient::onInterfacesAdded( } else if (interface == org::bluez::Device1_proxy::INTERFACE_NAME) { std::scoped_lock lock(devices_mutex_); if (!devices_.contains(objectPath)) { + if (resource_limits::IsAtCapacity(devices_.size(), + resource_limits::kMaxDevices)) { + LOG_WARN("Skipping Device1 {}: resource limit reached ({}/{})", + objectPath, devices_.size(), resource_limits::kMaxDevices); + continue; + } auto device = std::make_unique( getProxy().getConnection(), sdbus::ServiceName(INTERFACE_NAME), objectPath, properties); devices_[objectPath] = std::move(device); } } else if (interface == org::bluez::GattService1_proxy::INTERFACE_NAME) { - std::scoped_lock lock(gatt_services_mutex_); + std::scoped_lock lock(gatt_mutex_); if (!gatt_services_.contains(objectPath)) { + if (resource_limits::IsAtCapacity(gatt_services_.size(), + resource_limits::kMaxGattServices)) { + LOG_WARN("Skipping GattService1 {}: resource limit reached ({}/{})", + objectPath, gatt_services_.size(), + resource_limits::kMaxGattServices); + continue; + } auto device = std::make_unique( getProxy().getConnection(), sdbus::ServiceName(INTERFACE_NAME), objectPath, properties); @@ -77,16 +98,54 @@ void BluezClient::onInterfacesAdded( } } else if (interface == org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME) { - std::scoped_lock lock(gatt_services_mutex_); + std::scoped_lock lock(gatt_mutex_); auto key = sdbus::MemberName("Service"); - auto object_path = properties.at(key).get(); + + // Safely get the Service property + auto object_path = + property_utils::getProperty(properties, key); + if (!object_path) { + LOG_WARN("GattCharacteristic1 at {} missing 'Service' property", + objectPath); + continue; // Skip this characteristic + } + + if (!gatt_characteristics_.contains(objectPath) && + resource_limits::IsAtCapacity( + gatt_characteristics_.size(), + resource_limits::kMaxGattCharacteristics)) { + LOG_WARN( + "Skipping GattCharacteristic1 {}: resource limit reached ({}/{})", + objectPath, gatt_characteristics_.size(), + resource_limits::kMaxGattCharacteristics); + continue; + } + gatt_characteristics_[objectPath] = std::make_unique( getProxy().getConnection(), sdbus::ServiceName(INTERFACE_NAME), objectPath, properties); } else if (interface == org::bluez::GattDescriptor1_proxy::INTERFACE_NAME) { - std::scoped_lock lock(gatt_services_mutex_); + std::scoped_lock lock(gatt_mutex_); auto key = sdbus::MemberName("Characteristic"); - auto object_path = properties.at(key).get(); + + // Safely get the Characteristic property + auto object_path = + property_utils::getProperty(properties, key); + if (!object_path) { + LOG_WARN("GattDescriptor1 at {} missing 'Characteristic' property", + objectPath); + continue; // Skip this descriptor + } + + if (!gatt_descriptors_.contains(objectPath) && + resource_limits::IsAtCapacity(gatt_descriptors_.size(), + resource_limits::kMaxGattDescriptors)) { + LOG_WARN("Skipping GattDescriptor1 {}: resource limit reached ({}/{})", + objectPath, gatt_descriptors_.size(), + resource_limits::kMaxGattDescriptors); + continue; + } + gatt_descriptors_[objectPath] = std::make_unique( getProxy().getConnection(), sdbus::ServiceName(INTERFACE_NAME), objectPath, properties); @@ -98,6 +157,13 @@ void BluezClient::onInterfacesAdded( } else if (interface == org::bluez::Battery1_proxy::INTERFACE_NAME) { std::scoped_lock lock(battery1_mutex_); if (!battery1_.contains(objectPath)) { + if (resource_limits::IsAtCapacity( + battery1_.size(), resource_limits::kMaxBatteryEntries)) { + LOG_WARN("Skipping Battery1 {}: resource limit reached ({}/{})", + objectPath, battery1_.size(), + resource_limits::kMaxBatteryEntries); + continue; + } auto device = std::make_unique( getProxy().getConnection(), sdbus::ServiceName(INTERFACE_NAME), objectPath); @@ -110,6 +176,13 @@ void BluezClient::onInterfacesAdded( } else if (interface == org::bluez::Input1_proxy::INTERFACE_NAME) { std::lock_guard lock(input1_mutex_); if (!input1_.contains(objectPath)) { + if (resource_limits::IsAtCapacity(input1_.size(), + resource_limits::kMaxInputEntries)) { + LOG_WARN("Skipping Input1 {}: resource limit reached ({}/{})", + objectPath, input1_.size(), + resource_limits::kMaxInputEntries); + continue; + } input1_[objectPath] = std::make_unique( getProxy().getConnection(), sdbus::ServiceName(org::bluez::Input1_proxy::INTERFACE_NAME), @@ -130,7 +203,7 @@ void BluezClient::onInterfacesAdded( objectPath); } } - spdlog::info(os.str()); + LOG_INFO(os.str()); } void BluezClient::onInterfacesRemoved( @@ -139,41 +212,48 @@ void BluezClient::onInterfacesRemoved( std::ostringstream os; os << std::endl; for (const auto& interface : interfaces) { + os << "[" << objectPath << "] Remove - " << interface << std::endl; + if (interface == org::bluez::Adapter1_proxy::INTERFACE_NAME) { std::scoped_lock lock(adapters_mutex_); - if (!adapters_.contains(objectPath)) { - if (adapters_.contains(objectPath)) { - adapters_[objectPath].reset(); - adapters_.erase(objectPath); - } + if (adapters_.contains(objectPath)) { + adapters_[objectPath].reset(); + adapters_.erase(objectPath); } } else if (interface == org::bluez::Device1_proxy::INTERFACE_NAME) { std::scoped_lock lock(devices_mutex_); - if (!devices_.contains(objectPath)) { - if (devices_.contains(objectPath)) { - devices_[objectPath].reset(); - devices_.erase(objectPath); - } + if (devices_.contains(objectPath)) { + devices_[objectPath].reset(); + devices_.erase(objectPath); } } else if (interface == org::bluez::GattService1_proxy::INTERFACE_NAME) { - std::scoped_lock lock(gatt_services_mutex_); - if (!gatt_services_.contains(objectPath)) { - if (gatt_services_.contains(objectPath)) { - gatt_services_[objectPath].reset(); - gatt_services_.erase(objectPath); - } + std::scoped_lock lock(gatt_mutex_); + if (gatt_services_.contains(objectPath)) { + gatt_services_[objectPath].reset(); + gatt_services_.erase(objectPath); + } + } else if (interface == + org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME) { + std::scoped_lock lock(gatt_mutex_); + if (gatt_characteristics_.contains(objectPath)) { + gatt_characteristics_[objectPath].reset(); + gatt_characteristics_.erase(objectPath); + } + } else if (interface == org::bluez::GattDescriptor1_proxy::INTERFACE_NAME) { + std::scoped_lock lock(gatt_mutex_); + if (gatt_descriptors_.contains(objectPath)) { + gatt_descriptors_[objectPath].reset(); + gatt_descriptors_.erase(objectPath); } } else if (interface == org::bluez::Battery1_proxy::INTERFACE_NAME) { std::scoped_lock lock(battery1_mutex_); - if (!battery1_.contains(objectPath)) { - if (battery1_.contains(objectPath)) { - battery1_[objectPath].reset(); - battery1_.erase(objectPath); - } + if (battery1_.contains(objectPath)) { + battery1_[objectPath].reset(); + battery1_.erase(objectPath); } } else if (interface == org::bluez::Input1_proxy::INTERFACE_NAME) { std::lock_guard lock(input1_mutex_); - if (!input1_.contains(objectPath)) { + if (input1_.contains(objectPath)) { input1_[objectPath].reset(); input1_.erase(objectPath); } @@ -191,16 +271,5 @@ void BluezClient::onInterfacesRemoved( network_server1_.reset(); } } - for (auto it = interfaces.begin(); it != interfaces.end(); ++it) { - os << "[" << objectPath << "] Remove - " << *it; - if (std::next(it) != interfaces.end()) { - os << std::endl; - } - std::scoped_lock lock(devices_mutex_); - if (devices_.contains(objectPath)) { - devices_[objectPath].reset(); - devices_.erase(objectPath); - } - } - spdlog::info(os.str()); + LOG_INFO(os.str()); } diff --git a/src/bluez/bluez_client.h b/src/bluez/bluez_client.h index 191dbc4..623f10c 100644 --- a/src/bluez/bluez_client.h +++ b/src/bluez/bluez_client.h @@ -42,11 +42,25 @@ class BluezClient final static constexpr auto INTROSPECTABLE_INTERFACE_NAME = "org.freedesktop.DBus.Introspectable"; + // Mutex protection strategy: + // - adapters_mutex_ protects adapters_ map + // - devices_mutex_ protects devices_ map + // - gatt_mutex_ protects ALL GATT-related maps (services, characteristics, + // descriptors) + // These are protected by a single mutex because they form a hierarchy: + // Service -> Characteristic -> Descriptor + // This prevents deadlocks and simplifies the locking logic + // - battery1_mutex_ protects battery1_ map + // - input1_mutex_ protects input1_ map + std::mutex adapters_mutex_; std::map> adapters_; + std::mutex devices_mutex_; std::map> devices_; - std::mutex gatt_services_mutex_; + + // GATT mutex protects services, characteristics, AND descriptors + std::mutex gatt_mutex_; std::map> gatt_services_; std::map> gatt_descriptors_; diff --git a/src/bluez/device1.h b/src/bluez/device1.h index f027fcc..372cb7d 100644 --- a/src/bluez/device1.h +++ b/src/bluez/device1.h @@ -19,6 +19,7 @@ #include #include "../proxy/org/bluez/Device1/device1_proxy.h" +#include "../utils/logging.h" class Device1 final : public sdbus::ProxyInterfaces { @@ -55,7 +56,7 @@ class Device1 final : public sdbus::ProxyInterfaces& properties) : ProxyInterfaces{connection, destination, objectPath} { registerProxy(); - spdlog::debug("Device1: {}", objectPath); + LOG_DEBUG("Device1: {}", objectPath); onPropertiesChanged(sdbus::InterfaceName(Device1_proxy::INTERFACE_NAME), properties, {}); } @@ -71,7 +72,7 @@ class Device1 final : public sdbus::ProxyInterfaces(); - spdlog::debug("RSSI: {}", properties_.rssi); + LOG_DEBUG("RSSI: {}", properties_.rssi); } if (const auto key = sdbus::MemberName("ServicesResolved"); changedProperties.contains(key)) { diff --git a/src/bluez/hidraw.hpp b/src/bluez/hidraw.hpp index 5fac8be..bb3d1d9 100644 --- a/src/bluez/hidraw.hpp +++ b/src/bluez/hidraw.hpp @@ -37,6 +37,16 @@ class Hidraw { Hidraw() = default; virtual ~Hidraw() = default; + // Delete copy operations - this class manages a mutex which cannot be copied + Hidraw(const Hidraw&) = delete; + Hidraw& operator=(const Hidraw&) = delete; + + // Delete move operations - std::mutex is not movable + // If move semantics are required in the future, would need to use + // std::unique_ptr instead of std::mutex directly + Hidraw(Hidraw&&) = delete; + Hidraw& operator=(Hidraw&&) = delete; + void HidDevicesLock() { devices_mutex_.lock(); } void HidDevicesUnlock() { devices_mutex_.unlock(); } @@ -50,6 +60,43 @@ class Hidraw { return devices_.at(dev_key); } + /** + * \brief RAII wrapper for udev context + */ + struct UdevDeleter { + void operator()(udev* u) const { + if (u) { + udev_unref(u); + } + } + }; + using UdevPtr = std::unique_ptr; + + /** + * \brief RAII wrapper for udev_enumerate + */ + struct UdevEnumerateDeleter { + void operator()(udev_enumerate* e) const { + if (e) { + udev_enumerate_unref(e); + } + } + }; + using UdevEnumeratePtr = + std::unique_ptr; + + /** + * \brief RAII wrapper for udev_device + */ + struct UdevDeviceDeleter { + void operator()(udev_device* d) const { + if (d) { + udev_device_unref(d); + } + } + }; + using UdevDevicePtr = std::unique_ptr; + /** * \brief Retrieves the udev framebuffer system attributes. * @@ -61,39 +108,66 @@ class Hidraw { const std::vector>& match_params = {}) { std::unordered_map> results; - const auto udev = udev_new(); + + // Use RAII wrapper for automatic cleanup + const UdevPtr udev(udev_new()); if (!udev) { spdlog::error("Can't create udev"); - return results; + return results; // Safe - RAII cleans up automatically } - const auto enumerate = udev_enumerate_new(udev); - udev_enumerate_add_match_subsystem(enumerate, sub_system.c_str()); - udev_enumerate_scan_devices(enumerate); + // Use RAII wrapper for enumerating + const UdevEnumeratePtr enumerate(udev_enumerate_new(udev.get())); + if (!enumerate) { + spdlog::error("Can't create udev enumerate"); + return results; // Safe - both RAII objects clean up + } + + if (const int res = udev_enumerate_add_match_subsystem(enumerate.get(), + sub_system.c_str()); + res < 0) { + spdlog::error("Failed to add subsystem match: {}", sub_system); + return results; // Safe - RAII cleanup + } + + if (const int res = udev_enumerate_scan_devices(enumerate.get()); res < 0) { + spdlog::error("Failed to scan devices"); + return results; // Safe - RAII cleanup + } - const auto devices = udev_enumerate_get_list_entry(enumerate); + const auto devices = udev_enumerate_get_list_entry(enumerate.get()); udev_list_entry* dev_list_entry; udev_list_entry_foreach(dev_list_entry, devices) { std::map properties; const auto path = udev_list_entry_get_name(dev_list_entry); - const auto dev = udev_device_new_from_syspath(udev, path); + if (!path) { + continue; // Skip invalid entries + } + + // Use RAII wrapper for a device + UdevDevicePtr dev(udev_device_new_from_syspath(udev.get(), path)); + if (!dev) { + spdlog::debug("Failed to get device from syspath: {}", path); + continue; // Skip this device, continue with others + } - const auto properties_list = udev_device_get_properties_list_entry(dev); + const auto properties_list = + udev_device_get_properties_list_entry(dev.get()); udev_list_entry* properties_list_entry; udev_list_entry_foreach(properties_list_entry, properties_list) { const auto properties_name = udev_list_entry_get_name(properties_list_entry); if (properties_name) { const auto value = - udev_device_get_property_value(dev, properties_name); + udev_device_get_property_value(dev.get(), properties_name); properties[properties_name] = value ? value : ""; if (debug) { spdlog::debug(" {} = {}", properties_name, value ? value : ""); } } } - udev_device_unref(dev); + // dev automatically cleaned up at the end of scope bool match = true; for (const auto& [key, value] : match_params) { @@ -107,7 +181,7 @@ class Hidraw { results[path] = std::move(properties); } } - udev_unref(udev); + // enumerate and udev automatically cleaned up here return results; } @@ -209,11 +283,32 @@ class Hidraw { return false; } - // Extract the common part of the paths - const std::string input_common = input_path.substr( - prefix.size(), input_path.find("/input") - prefix.size()); - const std::string hidraw_common = hidraw_path.substr( - prefix.size(), hidraw_path.find("/hidraw") - prefix.size()); + // Find the positions of "/input" and "/hidraw" + const auto input_pos = input_path.find("/input"); + const auto hidraw_pos = hidraw_path.find("/hidraw"); + + // Validate that both substrings were found + if (input_pos == std::string::npos) { + spdlog::debug("Path does not contain '/input': {}", input_path); + return false; + } + + if (hidraw_pos == std::string::npos) { + spdlog::debug("Path does not contain '/hidraw': {}", hidraw_path); + return false; + } + + // Ensure the positions are after the prefix + if (input_pos < prefix.size() || hidraw_pos < prefix.size()) { + spdlog::debug("Invalid path structure - subsystem before prefix"); + return false; + } + + // Extract the common part of the paths (safe now that we've validated) + const std::string input_common = + input_path.substr(prefix.size(), input_pos - prefix.size()); + const std::string hidraw_common = + hidraw_path.substr(prefix.size(), hidraw_pos - prefix.size()); // Compare the common parts return input_common == hidraw_common; diff --git a/src/bluez/horipad_steam/horipad_steam.cc b/src/bluez/horipad_steam/horipad_steam.cc index bb4c700..7814459 100644 --- a/src/bluez/horipad_steam/horipad_steam.cc +++ b/src/bluez/horipad_steam/horipad_steam.cc @@ -16,6 +16,8 @@ #include +#include "../../utils/property_utils.h" +#include "../../utils/resource_limits.h" #include "../hidraw.hpp" const std::vector> input_match_params_bt = { @@ -38,10 +40,9 @@ HoripadSteam::HoripadSteam(sdbus::IConnection& connection) [&](const char* action, const char* dev_node, const char* sub_system) { - spdlog::debug("Action: {}, Device: {}, Subsystem: {}", - action ? action : "", - dev_node ? dev_node : "", - sub_system ? sub_system : ""); + LOG_DEBUG("Action: {}, Device: {}, Subsystem: {}", + action ? action : "", dev_node ? dev_node : "", + sub_system ? sub_system : ""); if (std::strcmp(sub_system, "hidraw") == 0) { if (std::strcmp(action, "remove") == 0) { input_reader_->stop(); @@ -80,6 +81,12 @@ void HoripadSteam::onInterfacesAdded( if (interface == org::bluez::Adapter1_proxy::INTERFACE_NAME) { std::scoped_lock lock(adapters_mutex_); if (!adapters_.contains(objectPath)) { + if (resource_limits::IsAtCapacity(adapters_.size(), + resource_limits::kMaxAdapters)) { + LOG_WARN("Skipping Adapter1 {}: resource limit reached ({}/{})", + objectPath, adapters_.size(), resource_limits::kMaxAdapters); + continue; + } auto adapter1 = std::make_unique( getProxy().getConnection(), sdbus::ServiceName(INTERFACE_NAME), objectPath, properties); @@ -87,57 +94,87 @@ void HoripadSteam::onInterfacesAdded( } } else if (interface == org::bluez::Device1_proxy::INTERFACE_NAME) { auto mod_alias_key = sdbus::MemberName("Modalias"); - if (!properties.contains(mod_alias_key)) - continue; - auto mod_alias = Device1::parse_modalias( - properties.at(mod_alias_key).get()); - if (mod_alias.has_value()) { - spdlog::debug("VID: {}, PID: {}, DID: {}", mod_alias.value().vid, - mod_alias.value().pid, mod_alias.value().did); + // Safely get the Modalias property + auto mod_alias_str = + property_utils::getProperty(properties, mod_alias_key); + if (!mod_alias_str) { + continue; // Skip devices without Modalias + } + + if (auto mod_alias = Device1::parse_modalias(*mod_alias_str); + mod_alias.has_value()) { + LOG_DEBUG("VID: {}, PID: {}, DID: {}", mod_alias.value().vid, + mod_alias.value().pid, mod_alias.value().did); if (auto [vid, pid, did] = mod_alias.value(); vid != VENDOR_ID || pid != PRODUCT_ID) { continue; } - spdlog::debug("VID: {}, PID: {}, DID: {}", mod_alias.value().vid, - mod_alias.value().pid, mod_alias.value().did); + LOG_DEBUG("VID: {}, PID: {}, DID: {}", mod_alias.value().vid, + mod_alias.value().pid, mod_alias.value().did); } else { - spdlog::debug("modalias has no value assigned: {}", objectPath); + LOG_DEBUG("modalias has no value assigned: {}", objectPath); continue; } - std::scoped_lock lock(devices_mutex_); - if (!devices_.contains(objectPath)) { + std::string hidraw_device_key; + { + std::scoped_lock lock(devices_mutex_); + if (devices_.contains(objectPath)) { + continue; + } + + if (resource_limits::IsAtCapacity(devices_.size(), + resource_limits::kMaxDevices)) { + LOG_WARN("Skipping Device1 {}: resource limit reached ({}/{})", + objectPath, devices_.size(), resource_limits::kMaxDevices); + continue; + } + auto device = std::make_unique( getProxy().getConnection(), sdbus::ServiceName(INTERFACE_NAME), objectPath, properties); if (auto props = device->GetProperties(); props.modalias.has_value()) { auto [vid, pid, did] = props.modalias.value(); - spdlog::info("Adding: {}, {}, {}", vid, pid, did); + LOG_INFO("Adding: {}, {}, {}", vid, pid, did); if (vid == VENDOR_ID && pid == PRODUCT_ID) { if (props.connected && props.paired && props.trusted) { - const auto dev_key = + hidraw_device_key = create_device_key_from_serial_number(props.address); - HidDevicesLock(); - if (HidDevicesContains(dev_key)) { - spdlog::info("Adding hidraw device: {}", dev_key); - if (!input_reader_) { - input_reader_ = - std::make_unique(GetHidDevice(dev_key)); - input_reader_->start(); - } - } - HidDevicesUnlock(); } } } devices_[objectPath] = std::move(device); } + + if (!hidraw_device_key.empty()) { + std::string hidraw_device; + HidDevicesLock(); + if (HidDevicesContains(hidraw_device_key)) { + hidraw_device = GetHidDevice(hidraw_device_key); + } + HidDevicesUnlock(); + + if (!hidraw_device.empty()) { + LOG_INFO("Adding hidraw device: {}", hidraw_device_key); + if (!input_reader_) { + input_reader_ = std::make_unique(hidraw_device); + input_reader_->start(); + } + } + } } else if (interface == org::bluez::Input1_proxy::INTERFACE_NAME) { std::lock_guard lock(input1_mutex_); if (!input1_.contains(objectPath)) { + if (resource_limits::IsAtCapacity(input1_.size(), + resource_limits::kMaxInputEntries)) { + LOG_WARN("Skipping Input1 {}: resource limit reached ({}/{})", + objectPath, input1_.size(), + resource_limits::kMaxInputEntries); + continue; + } input1_[objectPath] = std::make_unique( getProxy().getConnection(), sdbus::ServiceName(org::bluez::Input1_proxy::INTERFACE_NAME), @@ -153,34 +190,22 @@ void HoripadSteam::onInterfacesRemoved( for (const auto& interface : interfaces) { if (interface == org::bluez::Adapter1_proxy::INTERFACE_NAME) { std::scoped_lock lock(adapters_mutex_); - if (!adapters_.contains(objectPath)) { - if (adapters_.contains(objectPath)) { - adapters_[objectPath].reset(); - adapters_.erase(objectPath); - } + if (adapters_.contains(objectPath)) { + adapters_[objectPath].reset(); + adapters_.erase(objectPath); } } else if (interface == org::bluez::Device1_proxy::INTERFACE_NAME) { std::scoped_lock devices_lock(devices_mutex_); - if (!devices_.contains(objectPath)) { - if (devices_.contains(objectPath)) { - devices_[objectPath].reset(); - devices_.erase(objectPath); - } + if (devices_.contains(objectPath)) { + devices_[objectPath].reset(); + devices_.erase(objectPath); } } else if (interface == org::bluez::Input1_proxy::INTERFACE_NAME) { std::lock_guard lock(input1_mutex_); - if (!input1_.contains(objectPath)) { + if (input1_.contains(objectPath)) { input1_[objectPath].reset(); input1_.erase(objectPath); } } } - for (auto it = interfaces.begin(); it != interfaces.end(); ++it) { - std::scoped_lock lock(devices_mutex_); - if (devices_.contains(objectPath)) { - auto& device = devices_[objectPath]; - device.reset(); - devices_.erase(objectPath); - } - } } diff --git a/src/bluez/horipad_steam/horipad_steam.h b/src/bluez/horipad_steam/horipad_steam.h index a3243aa..cb1854d 100644 --- a/src/bluez/horipad_steam/horipad_steam.h +++ b/src/bluez/horipad_steam/horipad_steam.h @@ -45,6 +45,9 @@ class HoripadSteam final static constexpr auto INTROSPECTABLE_INTERFACE_NAME = "org.freedesktop.DBus.Introspectable"; + // Locking policy: avoid nested locking where possible. + // If nested locking is required, always acquire in this order: + // adapters_mutex_ -> devices_mutex_ -> input1_mutex_. std::mutex adapters_mutex_; std::map> adapters_; diff --git a/src/bluez/horipad_steam/input_reader.cc b/src/bluez/horipad_steam/input_reader.cc index 29647e1..8088beb 100644 --- a/src/bluez/horipad_steam/input_reader.cc +++ b/src/bluez/horipad_steam/input_reader.cc @@ -19,6 +19,7 @@ #include #include +#include "../../utils/logging.h" #include "../hidraw.hpp" #include "input_reader.h" @@ -26,13 +27,13 @@ InputReader::InputReader(std::string device) : device_(std::move(device)), stop_flag_(false) {} void InputReader::start() { - spdlog::debug("InputReader start: {}", device_); + LOG_DEBUG("InputReader start: {}", device_); stop_flag_ = false; read_input(); } void InputReader::stop() { - spdlog::debug("InputReader stop: {}", device_); + LOG_DEBUG("InputReader stop: {}", device_); stop_flag_ = true; } @@ -42,71 +43,71 @@ InputReader::~InputReader() { // NOLINTNEXTLINE(readability-static-accessed-through-instance) InputReader::Task InputReader::read_input() { - spdlog::debug("hidraw device: {}", device_); + LOG_DEBUG("hidraw device: {}", device_); const int fd = open(device_.c_str(), O_RDWR); while (true) { if (fd < 0) { - spdlog::error("unable to open device"); + LOG_ERROR("unable to open device"); break; } // Raw Info hidraw_devinfo raw_dev_info{}; if (const auto res = ioctl(fd, HIDIOCGRAWINFO, &raw_dev_info); res < 0) { - spdlog::error("HIDIOCGRAWINFO"); + LOG_ERROR("HIDIOCGRAWINFO"); break; } - spdlog::info("bustype: {}", Hidraw::bus_str(raw_dev_info.bustype)); - spdlog::info("Vendor ID: {:04X}", raw_dev_info.vendor); - spdlog::info("Product ID: {:04X}", raw_dev_info.product); + LOG_INFO("bustype: {}", Hidraw::bus_str(raw_dev_info.bustype)); + LOG_INFO("Vendor ID: {:04X}", raw_dev_info.vendor); + LOG_INFO("Product ID: {:04X}", raw_dev_info.product); // Raw Name char buf[256]{}; auto res = ioctl(fd, HIDIOCGRAWNAME(sizeof(buf)), buf); if (res < 0) { - spdlog::error("HIDIOCGRAWNAME"); + LOG_ERROR("HIDIOCGRAWNAME"); break; } - spdlog::info("HID Name: {}", buf); + LOG_INFO("HID Name: {}", buf); // Raw Physical Location res = ioctl(fd, HIDIOCGRAWPHYS(sizeof(buf)), buf); if (res < 0) { - spdlog::error("HIDIOCGRAWPHYS"); + LOG_ERROR("HIDIOCGRAWPHYS"); break; } - spdlog::info("HID Physical Location: {}", buf); + LOG_INFO("HID Physical Location: {}", buf); // Report Descriptor Size int desc_size = 0; res = ioctl(fd, HIDIOCGRDESCSIZE, &desc_size); if (res < 0) { - spdlog::error("HIDIOCGRDESCSIZE"); + LOG_ERROR("HIDIOCGRDESCSIZE"); break; } - spdlog::info("Report Descriptor Size: {}", desc_size); + LOG_INFO("Report Descriptor Size: {}", desc_size); // Report Descriptor hidraw_report_descriptor rpt_desc{}; rpt_desc.size = desc_size; res = ioctl(fd, HIDIOCGRDESC, &rpt_desc); if (res < 0) { - spdlog::error("HIDIOCGRDESC"); + LOG_ERROR("HIDIOCGRDESC"); break; } std::ostringstream os; os << "Report Descriptor\n"; os << CustomHexdump<400, false>(rpt_desc.value, rpt_desc.size); - spdlog::info(os.str()); + LOG_INFO(os.str()); while (!stop_flag_) { std::uint8_t buffer[sizeof(inputReport12_t)]; ssize_t result = 0; if (result = read(fd, &buffer[0], sizeof(inputReport12_t)); result < 0) { - spdlog::error("read failed: {}", strerror(errno)); + LOG_ERROR("read failed: {}", strerror(errno)); break; } @@ -128,7 +129,7 @@ InputReader::Task InputReader::read_input() { reinterpret_cast(buffer); PrintInputReport14(*input_report14); } else { - spdlog::error("Unknown report id: {}", buffer[0]); + LOG_ERROR("Unknown report id: {}", buffer[0]); } } } @@ -165,52 +166,52 @@ std::string InputReader::dpad_to_string(const Direction dpad) { } void InputReader::PrintInputReport7(const inputReport07_t& input_report07) { - spdlog::info("Stick L/R: [{},{}] [{},{}] ", input_report07.GD_GamepadX, - input_report07.GD_GamepadY, input_report07.GD_GamepadZ, - input_report07.GD_GamepadRz); - spdlog::info("L1/L2: {}, {}", input_report07.SIM_GamepadBrake, - input_report07.SIM_GamepadAccelerator); - spdlog::info("D-PAD: {}", dpad_to_string(static_cast( - input_report07.GD_GamepadHatSwitch))); - spdlog::info("A: {}", input_report07.BTN_GamepadButton1); - spdlog::info("B: {}", input_report07.BTN_GamepadButton2); - spdlog::info("Quick Access: {}", input_report07.BTN_GamepadButton3); - spdlog::info("X: {}", input_report07.BTN_GamepadButton4); - spdlog::info("Y: {}", input_report07.BTN_GamepadButton5); - spdlog::info("M1: {}", input_report07.BTN_GamepadButton6); - spdlog::info("L1: {}", input_report07.BTN_GamepadButton7); - spdlog::info("R1: {}", input_report07.BTN_GamepadButton8); - spdlog::info("L2: {}", input_report07.BTN_GamepadButton9); - spdlog::info("R2: {}", input_report07.BTN_GamepadButton10); - spdlog::info("View: {}", input_report07.BTN_GamepadButton11); - spdlog::info("Menu: {}", input_report07.BTN_GamepadButton12); - spdlog::info("Home: {}", input_report07.BTN_GamepadButton13); - spdlog::info("Left Stick Btn: {}", input_report07.BTN_GamepadButton14); - spdlog::info("Right Stick Btn: {}", input_report07.BTN_GamepadButton15); - spdlog::info("M2: {}", input_report07.BTN_GamepadButton16); - spdlog::info("Button 17: {}", input_report07.BTN_GamepadButton17); - spdlog::info("Button 18: {}", input_report07.BTN_GamepadButton18); - spdlog::info("L4: {}", input_report07.BTN_GamepadButton19); - spdlog::info("R4: {}", input_report07.BTN_GamepadButton20); + LOG_INFO("Stick L/R: [{},{}] [{},{}] ", input_report07.GD_GamepadX, + input_report07.GD_GamepadY, input_report07.GD_GamepadZ, + input_report07.GD_GamepadRz); + LOG_INFO("L1/L2: {}, {}", input_report07.SIM_GamepadBrake, + input_report07.SIM_GamepadAccelerator); + LOG_INFO("D-PAD: {}", dpad_to_string(static_cast( + input_report07.GD_GamepadHatSwitch))); + LOG_INFO("A: {}", input_report07.BTN_GamepadButton1); + LOG_INFO("B: {}", input_report07.BTN_GamepadButton2); + LOG_INFO("Quick Access: {}", input_report07.BTN_GamepadButton3); + LOG_INFO("X: {}", input_report07.BTN_GamepadButton4); + LOG_INFO("Y: {}", input_report07.BTN_GamepadButton5); + LOG_INFO("M1: {}", input_report07.BTN_GamepadButton6); + LOG_INFO("L1: {}", input_report07.BTN_GamepadButton7); + LOG_INFO("R1: {}", input_report07.BTN_GamepadButton8); + LOG_INFO("L2: {}", input_report07.BTN_GamepadButton9); + LOG_INFO("R2: {}", input_report07.BTN_GamepadButton10); + LOG_INFO("View: {}", input_report07.BTN_GamepadButton11); + LOG_INFO("Menu: {}", input_report07.BTN_GamepadButton12); + LOG_INFO("Home: {}", input_report07.BTN_GamepadButton13); + LOG_INFO("Left Stick Btn: {}", input_report07.BTN_GamepadButton14); + LOG_INFO("Right Stick Btn: {}", input_report07.BTN_GamepadButton15); + LOG_INFO("M2: {}", input_report07.BTN_GamepadButton16); + LOG_INFO("Button 17: {}", input_report07.BTN_GamepadButton17); + LOG_INFO("Button 18: {}", input_report07.BTN_GamepadButton18); + LOG_INFO("L4: {}", input_report07.BTN_GamepadButton19); + LOG_INFO("R4: {}", input_report07.BTN_GamepadButton20); } void InputReader::PrintInputReport10(const inputReport10_t& input_report10) { std::ostringstream os; os << CustomHexdump<400, false>(input_report10.VEN_Gamepad0024, sizeof(inputReport10_t)); - spdlog::info("Input Report 10: {}", os.str()); + LOG_INFO("Input Report 10: {}", os.str()); } void InputReader::PrintInputReport12(const inputReport12_t& input_report12) { std::ostringstream os; os << CustomHexdump<400, false>(input_report12.VEN_Gamepad0022, sizeof(inputReport10_t)); - spdlog::info("Input Report 10: {}", os.str()); + LOG_INFO("Input Report 10: {}", os.str()); } void InputReader::PrintInputReport14(const inputReport14_t& input_report14) { std::ostringstream os; os << CustomHexdump<400, false>(input_report14.VEN_Gamepad0026, sizeof(inputReport10_t)); - spdlog::info("Input Report 14: {}", os.str()); + LOG_INFO("Input Report 14: {}", os.str()); } diff --git a/src/bluez/horipad_steam/main.cc b/src/bluez/horipad_steam/main.cc index b3b040a..c775ee3 100644 --- a/src/bluez/horipad_steam/main.cc +++ b/src/bluez/horipad_steam/main.cc @@ -12,21 +12,40 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include "../../utils/signal_handler.h" #include "horipad_steam.h" int main() { - spdlog::set_level(spdlog::level::debug); - spdlog::flush_every(std::chrono::seconds(5)); + try { + installSignalHandlers(); - const auto connection = sdbus::createSystemBusConnection(); - connection->enterEventLoopAsync(); + spdlog::set_level(spdlog::level::debug); + spdlog::flush_every(kLogFlushInterval); - HoripadSteam client(*connection); + const auto connection = sdbus::createSystemBusConnection(); + connection->enterEventLoopAsync(); - using namespace std::chrono_literals; - std::this_thread::sleep_for(120000ms); - connection->leaveEventLoop(); + HoripadSteam client(*connection); - return 0; + LOG_INFO("HoriPad Steam client running - Press Ctrl+C to exit"); + + // Monitor loop with shared connection health timing defaults + auto result = monitorLoop(*connection); + + if (result) { + LOG_ERROR("Exiting due to: {}", *result); + } else { + LOG_INFO("Shutting down..."); + } + + connection->leaveEventLoop(); + return result ? 1 : 0; + + } catch (const sdbus::Error& e) { + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); + return 1; + } catch (const std::exception& e) { + LOG_ERROR("Exception: {}", e.what()); + return 1; + } } diff --git a/src/bluez/main.cc b/src/bluez/main.cc index 23b1d76..b25b42c 100644 --- a/src/bluez/main.cc +++ b/src/bluez/main.cc @@ -12,17 +12,43 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "../utils/signal_handler.h" #include "bluez_client.h" int main() { - const auto connection = sdbus::createSystemBusConnection(); - connection->enterEventLoopAsync(); + try { + // Initialize logging with environment variable configuration + logging_config::initializeLogging("bluez_client"); - BluezClient client(*connection); + installSignalHandlers(); - using namespace std::chrono_literals; - std::this_thread::sleep_for(120000ms); - connection->leaveEventLoop(); + const auto connection = sdbus::createSystemBusConnection(); + connection->enterEventLoopAsync(); - return 0; + BluezClient client(*connection); + + LOG_INFO("BlueZ client running - Press Ctrl+C to exit"); + + // Monitor loop with shared connection health timing defaults + auto result = monitorLoop(*connection); + + if (result) { + // Connection was lost + LOG_ERROR("Exiting due to: {}", *result); + } else { + // Graceful shutdown via signal + LOG_INFO("Shutting down..."); + } + + connection->leaveEventLoop(); + + return result ? 1 : 0; + + } catch (const sdbus::Error& e) { + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); + return 1; + } catch (const std::exception& e) { + LOG_ERROR("Exception: {}", e.what()); + return 1; + } } diff --git a/src/bluez/ps5_dual_sense/dual_sense.cc b/src/bluez/ps5_dual_sense/dual_sense.cc index 6b7a36d..1e59771 100644 --- a/src/bluez/ps5_dual_sense/dual_sense.cc +++ b/src/bluez/ps5_dual_sense/dual_sense.cc @@ -14,6 +14,9 @@ #include "dual_sense.h" +#include "../../utils/property_utils.h" +#include "../../utils/resource_limits.h" + const std::vector> input_match_bt = { {"ID_BUS", "bluetooth"}, {"NAME", "\"DualSense Wireless Controller\""}, @@ -34,10 +37,9 @@ DualSense::DualSense(sdbus::IConnection& connection) [&](const char* action, const char* dev_node, const char* sub_system) { - spdlog::debug("Action: {}, Device: {}, Subsystem: {}", - action ? action : "", - dev_node ? dev_node : "", - sub_system ? sub_system : ""); + LOG_DEBUG("Action: {}, Device: {}, Subsystem: {}", + action ? action : "", dev_node ? dev_node : "", + sub_system ? sub_system : ""); if (std::strcmp(sub_system, "hidraw") == 0) { if (std::strcmp(action, "remove") == 0) { input_reader_->stop(); @@ -76,6 +78,12 @@ void DualSense::onInterfacesAdded( if (interface == org::bluez::Adapter1_proxy::INTERFACE_NAME) { std::scoped_lock lock(adapters_mutex_); if (!adapters_.contains(objectPath)) { + if (resource_limits::IsAtCapacity(adapters_.size(), + resource_limits::kMaxAdapters)) { + LOG_WARN("Skipping Adapter1 {}: resource limit reached ({}/{})", + objectPath, adapters_.size(), resource_limits::kMaxAdapters); + continue; + } auto adapter1 = std::make_unique( getProxy().getConnection(), sdbus::ServiceName(INTERFACE_NAME), objectPath, properties); @@ -83,65 +91,109 @@ void DualSense::onInterfacesAdded( } } else if (interface == org::bluez::Device1_proxy::INTERFACE_NAME) { auto mod_alias_key = sdbus::MemberName("Modalias"); - if (!properties.contains(mod_alias_key)) - continue; - auto mod_alias = Device1::parse_modalias( - properties.at(mod_alias_key).get()); - if (mod_alias.has_value()) { - spdlog::debug("VID: {}, PID: {}, DID: {}", mod_alias.value().vid, - mod_alias.value().pid, mod_alias.value().did); + // Safely get the Modalias property + auto mod_alias_str = + property_utils::getProperty(properties, mod_alias_key); + if (!mod_alias_str) { + continue; // Skip devices without Modalias + } + + if (auto mod_alias = Device1::parse_modalias(*mod_alias_str); + mod_alias.has_value()) { + LOG_DEBUG("VID: {}, PID: {}, DID: {}", mod_alias.value().vid, + mod_alias.value().pid, mod_alias.value().did); if (auto [vid, pid, did] = mod_alias.value(); vid != VENDOR_ID || pid != PRODUCT_ID) { continue; } } else { - spdlog::debug("modalias has no value assigned: {}", objectPath); + LOG_DEBUG("modalias has no value assigned: {}", objectPath); continue; } - std::scoped_lock lock(devices_mutex_); - if (!devices_.contains(objectPath)) { + std::string power_path_to_add; + std::string hidraw_device_key; + { + std::scoped_lock lock(devices_mutex_); + if (devices_.contains(objectPath)) { + continue; + } + + if (resource_limits::IsAtCapacity(devices_.size(), + resource_limits::kMaxDevices)) { + LOG_WARN("Skipping Device1 {}: resource limit reached ({}/{})", + objectPath, devices_.size(), resource_limits::kMaxDevices); + continue; + } + auto device = std::make_unique( getProxy().getConnection(), sdbus::ServiceName(INTERFACE_NAME), objectPath, properties); if (auto props = device->GetProperties(); props.modalias.has_value()) { auto [vid, pid, did] = props.modalias.value(); - spdlog::info("Adding: {}, {}, {}", vid, pid, did); + LOG_INFO("Adding: {}, {}, {}", vid, pid, did); if (vid == VENDOR_ID && pid == PRODUCT_ID) { - // if connected, paired, trusted, and bonded a hidraw device should + // if connected, paired, trusted, and bonded, a hidraw device should // be ready to use if (props.connected && props.paired && props.trusted) { - const auto dev_key = + hidraw_device_key = create_device_key_from_serial_number(props.address); - if (HidDevicesContains(dev_key)) { - spdlog::info("Adding hidraw device: {}", dev_key); - if (!input_reader_) { - input_reader_ = - std::make_unique(GetHidDevice(dev_key)); - input_reader_->start(); - } - } } - // Add UPower Display Device - if (std::string power_path = convert_mac_to_path(props.address); - !upower_clients_.contains(power_path)) { - upower_display_devices_mutex_.lock(); - spdlog::info("[Add] UPower Display Device: {}", power_path); - upower_clients_[power_path] = std::make_unique( - getProxy().getConnection(), sdbus::ObjectPath(power_path)); - upower_display_devices_mutex_.unlock(); - } + power_path_to_add = convert_mac_to_path(props.address); } } devices_[objectPath] = std::move(device); } + + if (!hidraw_device_key.empty()) { + std::string hidraw_device; + HidDevicesLock(); + if (HidDevicesContains(hidraw_device_key)) { + hidraw_device = GetHidDevice(hidraw_device_key); + } + HidDevicesUnlock(); + + if (!hidraw_device.empty()) { + LOG_INFO("Adding hidraw device: {}", hidraw_device_key); + if (!input_reader_) { + input_reader_ = std::make_unique(hidraw_device); + input_reader_->start(); + } + } + } + + // Avoid nested locking with devices_mutex_ + + // upower_display_devices_mutex_. + if (!power_path_to_add.empty()) { + std::scoped_lock power_lock(upower_display_devices_mutex_); + if (!upower_clients_.contains(power_path_to_add)) { + if (resource_limits::IsAtCapacity( + upower_clients_.size(), resource_limits::kMaxUPowerClients)) { + LOG_WARN( + "Skipping UPower client {}: resource limit reached ({}/{})", + power_path_to_add, upower_clients_.size(), + resource_limits::kMaxUPowerClients); + continue; + } + LOG_INFO("[Add] UPower Display Device: {}", power_path_to_add); + upower_clients_[power_path_to_add] = std::make_unique( + getProxy().getConnection(), sdbus::ObjectPath(power_path_to_add)); + } + } } else if (interface == org::bluez::Input1_proxy::INTERFACE_NAME) { std::lock_guard lock(input1_mutex_); if (!input1_.contains(objectPath)) { + if (resource_limits::IsAtCapacity(input1_.size(), + resource_limits::kMaxInputEntries)) { + LOG_WARN("Skipping Input1 {}: resource limit reached ({}/{})", + objectPath, input1_.size(), + resource_limits::kMaxInputEntries); + continue; + } input1_[objectPath] = std::make_unique( getProxy().getConnection(), sdbus::ServiceName(org::bluez::Input1_proxy::INTERFACE_NAME), @@ -157,52 +209,46 @@ void DualSense::onInterfacesRemoved( for (const auto& interface : interfaces) { if (interface == org::bluez::Adapter1_proxy::INTERFACE_NAME) { std::scoped_lock lock(adapters_mutex_); - if (!adapters_.contains(objectPath)) { - if (adapters_.contains(objectPath)) { - adapters_[objectPath].reset(); - adapters_.erase(objectPath); - } + if (adapters_.contains(objectPath)) { + adapters_[objectPath].reset(); + adapters_.erase(objectPath); } } else if (interface == org::bluez::Device1_proxy::INTERFACE_NAME) { - std::scoped_lock devices_lock(devices_mutex_); - if (!devices_.contains(objectPath)) { + std::string power_path_to_remove; + { + std::scoped_lock devices_lock(devices_mutex_); if (devices_.contains(objectPath)) { - devices_[objectPath].reset(); + auto& device = devices_[objectPath]; + if (auto props = device->GetProperties(); + props.modalias.has_value()) { + auto [vid, pid, did] = props.modalias.value(); + LOG_INFO("Removing: {}, {}, {}", vid, pid, did); + if (vid == VENDOR_ID && pid == PRODUCT_ID) { + power_path_to_remove = convert_mac_to_path(props.address); + } + } + device.reset(); devices_.erase(objectPath); } } + + if (!power_path_to_remove.empty()) { + std::scoped_lock power_lock(upower_display_devices_mutex_); + if (upower_clients_.contains(power_path_to_remove)) { + LOG_INFO("[Remove] UPower Display Device: {}", power_path_to_remove); + auto& power_device = upower_clients_[power_path_to_remove]; + power_device.reset(); + upower_clients_.erase(power_path_to_remove); + } + } } else if (interface == org::bluez::Input1_proxy::INTERFACE_NAME) { std::lock_guard lock(input1_mutex_); - if (!input1_.contains(objectPath)) { + if (input1_.contains(objectPath)) { input1_[objectPath].reset(); input1_.erase(objectPath); } } } - for (auto it = interfaces.begin(); it != interfaces.end(); ++it) { - std::scoped_lock lock(devices_mutex_); - if (devices_.contains(objectPath)) { - auto& device = devices_[objectPath]; - - if (auto props = device->GetProperties(); props.modalias.has_value()) { - auto [vid, pid, did] = props.modalias.value(); - spdlog::info("Removing: {}, {}, {}", vid, pid, did); - if (vid == VENDOR_ID && pid == PRODUCT_ID) { - if (std::string power_path = convert_mac_to_path(props.address); - upower_clients_.contains(power_path)) { - std::scoped_lock power_lock(upower_display_devices_mutex_); - spdlog::info("[Remove] UPower Display Device: {}", power_path); - auto& power_device = upower_clients_[power_path]; - power_device.reset(); - upower_clients_.erase(power_path); - } - } - } - - device.reset(); - devices_.erase(objectPath); - } - } } std::string DualSense::convert_mac_to_path(const std::string& mac_address) { diff --git a/src/bluez/ps5_dual_sense/dual_sense.h b/src/bluez/ps5_dual_sense/dual_sense.h index 1d02778..b06d75f 100644 --- a/src/bluez/ps5_dual_sense/dual_sense.h +++ b/src/bluez/ps5_dual_sense/dual_sense.h @@ -46,6 +46,10 @@ class DualSense final static constexpr auto INTROSPECTABLE_INTERFACE_NAME = "org.freedesktop.DBus.Introspectable"; + // Locking policy: avoid nested locking where possible. + // If nested locking is required, always acquire in this order: + // adapters_mutex_ -> devices_mutex_ -> input1_mutex_ -> + // upower_display_devices_mutex_. std::mutex adapters_mutex_; std::map> adapters_; diff --git a/src/bluez/ps5_dual_sense/input_reader.cc b/src/bluez/ps5_dual_sense/input_reader.cc index ab0b1c7..ae9d79c 100644 --- a/src/bluez/ps5_dual_sense/input_reader.cc +++ b/src/bluez/ps5_dual_sense/input_reader.cc @@ -17,23 +17,23 @@ #include #include +#include +#include "../../utils/logging.h" #include "../hidraw.hpp" #include "input_reader.h" -#include - InputReader::InputReader(std::string device) : device_(std::move(device)), stop_flag_(false) {} void InputReader::start() { - spdlog::debug("InputReader start: {}", device_); + LOG_DEBUG("InputReader start: {}", device_); stop_flag_ = false; read_input(); } void InputReader::stop() { - spdlog::debug("InputReader stop: {}", device_); + LOG_DEBUG("InputReader stop: {}", device_); stop_flag_ = true; } @@ -43,65 +43,65 @@ InputReader::~InputReader() { // NOLINTNEXTLINE(readability-static-accessed-through-instance) InputReader::Task InputReader::read_input() { - spdlog::debug("hidraw device: {}", device_); + LOG_DEBUG("hidraw device: {}", device_); const int fd = open(device_.c_str(), O_RDWR); while (true) { if (fd < 0) { - spdlog::error("unable to open device"); + LOG_ERROR("unable to open device"); break; } // Raw Info hidraw_devinfo raw_dev_info{}; if (const auto res = ioctl(fd, HIDIOCGRAWINFO, &raw_dev_info); res < 0) { - spdlog::error("HIDIOCGRAWINFO"); + LOG_ERROR("HIDIOCGRAWINFO"); break; } - spdlog::info("bustype: {}", Hidraw::bus_str(raw_dev_info.bustype)); - spdlog::info("Vendor ID: {:04X}", raw_dev_info.vendor); - spdlog::info("Product ID: {:04X}", raw_dev_info.product); + LOG_INFO("bustype: {}", Hidraw::bus_str(raw_dev_info.bustype)); + LOG_INFO("Vendor ID: {:04X}", raw_dev_info.vendor); + LOG_INFO("Product ID: {:04X}", raw_dev_info.product); // Raw Name char buf[256]{}; auto res = ioctl(fd, HIDIOCGRAWNAME(sizeof(buf)), buf); if (res < 0) { - spdlog::error("HIDIOCGRAWNAME"); + LOG_ERROR("HIDIOCGRAWNAME"); break; } - spdlog::info("HID Name: {}", buf); + LOG_INFO("HID Name: {}", buf); // Raw Physical Location res = ioctl(fd, HIDIOCGRAWPHYS(sizeof(buf)), buf); if (res < 0) { - spdlog::error("HIDIOCGRAWPHYS"); + LOG_ERROR("HIDIOCGRAWPHYS"); break; } - spdlog::info("HID Physical Location: {}", buf); + LOG_INFO("HID Physical Location: {}", buf); // Report Descriptor Size int desc_size = 0; res = ioctl(fd, HIDIOCGRDESCSIZE, &desc_size); if (res < 0) { - spdlog::error("HIDIOCGRDESCSIZE"); + LOG_ERROR("HIDIOCGRDESCSIZE"); break; } - spdlog::info("Report Descriptor Size: {}", desc_size); + LOG_INFO("Report Descriptor Size: {}", desc_size); // Report Descriptor hidraw_report_descriptor rpt_desc{}; rpt_desc.size = desc_size; res = ioctl(fd, HIDIOCGRDESC, &rpt_desc); if (res < 0) { - spdlog::error("HIDIOCGRDESC"); + LOG_ERROR("HIDIOCGRDESC"); break; } std::ostringstream os; os << "Report Descriptor\n"; os << CustomHexdump<400, false>(rpt_desc.value, rpt_desc.size); - spdlog::info(os.str()); + LOG_INFO(os.str()); // Get Features GetControllerCalibrationData( @@ -113,7 +113,7 @@ InputReader::Task InputReader::read_input() { std::uint8_t buffer[sizeof(USBGetStateData)]; ssize_t result = 0; if (result = read(fd, &buffer[0], sizeof(USBGetStateData)); result < 0) { - spdlog::error("GetInputReport4 failed: {}", strerror(errno)); + LOG_ERROR("GetInputReport4 failed: {}", strerror(errno)); break; } @@ -126,14 +126,14 @@ InputReader::Task InputReader::read_input() { } else if (report_id == 49) { const auto& input_report31 = reinterpret_cast(buffer); if (input_report31.Data.HasHID) { - spdlog::info("[ReportIn31] Has HID"); + LOG_INFO("[ReportIn31] Has HID"); PrintControllerStateUsb(input_report31.Data.State.StateData, hw_cal_data_); } else if (input_report31.Data.HasMic) { - spdlog::info("[ReportIn31] Has Microphone"); + LOG_INFO("[ReportIn31] Has Microphone"); } } else { - spdlog::error("Unknown report id: {}", report_id); + LOG_ERROR("Unknown report id: {}", report_id); } } } @@ -152,12 +152,12 @@ int InputReader::GetControllerMacAll(const int fd, if (const auto res = ioctl(fd, HIDIOCGFEATURE(sizeof(ReportFeatureInMacAll)), &mac_all); res < 0) { - spdlog::error("GetControllerMacAll failed: {}", strerror(errno)); + LOG_ERROR("GetControllerMacAll failed: {}", strerror(errno)); return 1; } if (mac_all.ReportID != 0x09 || mac_all.Hard08 != 0x08 || mac_all.Hard25 != 0x25 || mac_all.Hard00 != 0x00) { - spdlog::error("GetControllerMacAll invalid response"); + LOG_ERROR("GetControllerMacAll invalid response"); return 1; } return 0; @@ -169,11 +169,11 @@ int InputReader::GetControllerVersion(const int fd, if (const auto res = ioctl(fd, HIDIOCGFEATURE(sizeof(ReportFeatureInVersion)), &version); res < 0) { - spdlog::error("GetControllerVersion failed: {}", strerror(errno)); + LOG_ERROR("GetControllerVersion failed: {}", strerror(errno)); return 1; } if (version.Data.ReportID != 0x20) { - spdlog::error("GetControllerVersion invalid response"); + LOG_ERROR("GetControllerVersion invalid response"); return 1; } return 0; @@ -192,11 +192,11 @@ int InputReader::GetControllerCalibrationData( if (const auto res = ioctl( fd, HIDIOCGFEATURE(sizeof(ReportFeatureCalibrationData)), &cal_data); res < 0) { - spdlog::error("GetControllerCalibrationData failed: {}", strerror(errno)); + LOG_ERROR("GetControllerCalibrationData failed: {}", strerror(errno)); return 1; } if (cal_data.Data.ReportID != 0x05) { - spdlog::error("GetControllerCalibrationData invalid response"); + LOG_ERROR("GetControllerCalibrationData invalid response"); return 1; } @@ -251,7 +251,7 @@ int InputReader::GetControllerCalibrationData( for (auto& [abs_code, bias, sens_numer, sens_denom] : hw_cal_data.gyro) { if (sens_denom == 0) { abs_code = ABS_RX + i; - spdlog::warn( + LOG_WARN( "Invalid gyro calibration data for axis ({}), disabling calibration.", abs_code); bias = 0; @@ -266,7 +266,7 @@ int InputReader::GetControllerCalibrationData( for (auto& [abs_code, bias, sens_numer, sens_denom] : hw_cal_data.accel) { if (sens_denom == 0) { abs_code = ABS_RX + i; - spdlog::warn( + LOG_WARN( "Invalid accelerometer calibration data for axis ({}), disabling " "calibration.", abs_code); @@ -385,23 +385,23 @@ std::string InputReader::light_fade_animation_to_string( void InputReader::PrintCalibrationData( HardwareCalibrationData const& hw_cal_data) { - spdlog::info("HW Calibration Data"); + LOG_INFO("HW Calibration Data"); for (auto const& [abs_code, bias, sens_numer, sens_denom] : hw_cal_data.accel) { - spdlog::info("\tAccel {}: bias: {}, sens_numer: {}, sens_denom: {}", - abs_code, bias, sens_numer, sens_denom); + LOG_INFO("\tAccel {}: bias: {}, sens_numer: {}, sens_denom: {}", abs_code, + bias, sens_numer, sens_denom); } for (auto const& [abs_code, bias, sens_numer, sens_denom] : hw_cal_data.gyro) { - spdlog::info("\tGyro {}: bias: {}, sens_numer: {}, sens_denom: {}", - abs_code, bias, sens_numer, sens_denom); + LOG_INFO("\tGyro {}: bias: {}, sens_numer: {}, sens_denom: {}", abs_code, + bias, sens_numer, sens_denom); } } void InputReader::PrintControllerMacAll( ReportFeatureInMacAll const& controller_and_host_mac) { - spdlog::info("Controller Mac All"); - spdlog::info("\tReport ID: 0x{:02X}", controller_and_host_mac.ReportID); + LOG_INFO("Controller Mac All"); + LOG_INFO("\tReport ID: 0x{:02X}", controller_and_host_mac.ReportID); std::ostringstream os; os << "\tClient: "; @@ -412,7 +412,7 @@ void InputReader::PrintControllerMacAll( os << ":"; } } - spdlog::info(os.str()); + LOG_INFO(os.str()); os.clear(); os.str(""); os << "\tHost: "; @@ -423,18 +423,18 @@ void InputReader::PrintControllerMacAll( os << ":"; } } - spdlog::info(os.str()); + LOG_INFO(os.str()); } void InputReader::PrintControllerVersion( ReportFeatureInVersion const& version) { - spdlog::info("Firmware Info"); - spdlog::info("\tReportID: 0x{:02X}", version.Data.ReportID); - spdlog::info("\tBuildDate: {}", std::string_view(version.Data.BuildDate, 11)); - spdlog::info("\tBuildTime: {}", std::string_view(version.Data.BuildTime, 8)); - spdlog::info("\tFwType: {}", version.Data.FwType); - spdlog::info("\tSwSeries: 0x{:04X}", version.Data.SwSeries); - spdlog::info("\tHardwareInfo: 0x{:08X}", version.Data.HardwareInfo); + LOG_INFO("Firmware Info"); + LOG_INFO("\tReportID: 0x{:02X}", version.Data.ReportID); + LOG_INFO("\tBuildDate: {}", std::string_view(version.Data.BuildDate, 11)); + LOG_INFO("\tBuildTime: {}", std::string_view(version.Data.BuildTime, 8)); + LOG_INFO("\tFwType: {}", version.Data.FwType); + LOG_INFO("\tSwSeries: 0x{:04X}", version.Data.SwSeries); + LOG_INFO("\tHardwareInfo: 0x{:08X}", version.Data.HardwareInfo); const uint32_t firmware_version = version.Data.FirmwareVersion; std::ostringstream firmware_version_str; firmware_version_str << std::hex << std::setw(2) << std::setfill('0') @@ -443,18 +443,16 @@ void InputReader::PrintControllerVersion( << ((firmware_version >> 16) & 0xFF) << "." << std::setw(4) << std::setfill('0') << (firmware_version & 0xFFFF); - spdlog::info("\tFirmwareVersion: {}", firmware_version_str.str()); - // spdlog::info("\tFirmwareVersion: 0x{:08X}", version.Data.FirmwareVersion); - spdlog::info("\tDeviceInfo: {}", version.Data.DeviceInfo); - spdlog::info("\tUpdateVersion: 0x{:04X}", version.Data.UpdateVersion); - spdlog::info("\tUpdateImageInfo: 0x{:02}", - static_cast(version.Data.UpdateImageInfo)); - spdlog::info("\tUpdateUnk: 0x{:02}", - static_cast(version.Data.UpdateUnk)); - spdlog::info("\tSblFwVersion: 0x{:08X}", version.Data.SblFwVersion); - spdlog::info("\tVenomFwVersion: 0x{:08X}", version.Data.VenomFwVersion); - spdlog::info("\tSpiderDspFwVersion: 0x{:08X}", - version.Data.SpiderDspFwVersion); + LOG_INFO("\tFirmwareVersion: {}", firmware_version_str.str()); + // LOG_INFO("\tFirmwareVersion: 0x{:08X}", version.Data.FirmwareVersion); + LOG_INFO("\tDeviceInfo: {}", version.Data.DeviceInfo); + LOG_INFO("\tUpdateVersion: 0x{:04X}", version.Data.UpdateVersion); + LOG_INFO("\tUpdateImageInfo: 0x{:02}", + static_cast(version.Data.UpdateImageInfo)); + LOG_INFO("\tUpdateUnk: 0x{:02}", static_cast(version.Data.UpdateUnk)); + LOG_INFO("\tSblFwVersion: 0x{:08X}", version.Data.SblFwVersion); + LOG_INFO("\tVenomFwVersion: 0x{:08X}", version.Data.VenomFwVersion); + LOG_INFO("\tSpiderDspFwVersion: 0x{:08X}", version.Data.SpiderDspFwVersion); } template @@ -467,32 +465,32 @@ T mult_frac(T x, U n, V d) { void InputReader::PrintControllerStateUsb( USBGetStateData const& state, HardwareCalibrationData const& hw_cal_data) { - spdlog::info("Controller State (USB)"); - spdlog::info("\tLeftStick: {}, {}", state.LeftStickX, state.LeftStickY); - spdlog::info("\tRightStick: {}, {}", state.RightStickX, state.RightStickY); - spdlog::info("\tDPad: {}", dpad_to_string(state.DPad)); - spdlog::info("\tButtonSquare: {}", state.ButtonSquare); - spdlog::info("\tButtonCross: {}", state.ButtonCross); - spdlog::info("\tButtonCircle: {}", state.ButtonCircle); - spdlog::info("\tButtonTriangle: {}", state.ButtonTriangle); - spdlog::info("\tButtonL1: {}", state.ButtonL1); - spdlog::info("\tButtonR1: {}", state.ButtonR1); - spdlog::info("\tButtonL2: {}", state.ButtonL2); - spdlog::info("\tButtonR2: {}", state.ButtonR2); - spdlog::info("\tButtonCreate: {}", state.ButtonCreate); - spdlog::info("\tButtonOptions: {}", state.ButtonOptions); - spdlog::info("\tButtonL3: {}", state.ButtonL3); - spdlog::info("\tButtonR3: {}", state.ButtonR3); - spdlog::info("\tButtonHome: {}", state.ButtonHome); - spdlog::info("\tButtonPad: {}", state.ButtonPad); - spdlog::info("\tButtonMute: {}", state.ButtonMute); - spdlog::info("\tButtonLeftFunction: {}", state.ButtonLeftFunction); - spdlog::info("\tButtonRightFunction: {}", state.ButtonRightFunction); - spdlog::info("\tButtonLeftPaddle: {}", state.ButtonLeftPaddle); - spdlog::info("\tButtonRightPaddle: {}", state.ButtonRightPaddle); - spdlog::info("\tTimeStamp: {}", state.TimeStamp); - spdlog::info("\tAngularVelocity (Raw): {}, {}, {}", state.AngularVelocityX, - state.AngularVelocityY, state.AngularVelocityZ); + LOG_INFO("Controller State (USB)"); + LOG_INFO("\tLeftStick: {}, {}", state.LeftStickX, state.LeftStickY); + LOG_INFO("\tRightStick: {}, {}", state.RightStickX, state.RightStickY); + LOG_INFO("\tDPad: {}", dpad_to_string(state.DPad)); + LOG_INFO("\tButtonSquare: {}", state.ButtonSquare); + LOG_INFO("\tButtonCross: {}", state.ButtonCross); + LOG_INFO("\tButtonCircle: {}", state.ButtonCircle); + LOG_INFO("\tButtonTriangle: {}", state.ButtonTriangle); + LOG_INFO("\tButtonL1: {}", state.ButtonL1); + LOG_INFO("\tButtonR1: {}", state.ButtonR1); + LOG_INFO("\tButtonL2: {}", state.ButtonL2); + LOG_INFO("\tButtonR2: {}", state.ButtonR2); + LOG_INFO("\tButtonCreate: {}", state.ButtonCreate); + LOG_INFO("\tButtonOptions: {}", state.ButtonOptions); + LOG_INFO("\tButtonL3: {}", state.ButtonL3); + LOG_INFO("\tButtonR3: {}", state.ButtonR3); + LOG_INFO("\tButtonHome: {}", state.ButtonHome); + LOG_INFO("\tButtonPad: {}", state.ButtonPad); + LOG_INFO("\tButtonMute: {}", state.ButtonMute); + LOG_INFO("\tButtonLeftFunction: {}", state.ButtonLeftFunction); + LOG_INFO("\tButtonRightFunction: {}", state.ButtonRightFunction); + LOG_INFO("\tButtonLeftPaddle: {}", state.ButtonLeftPaddle); + LOG_INFO("\tButtonRightPaddle: {}", state.ButtonRightPaddle); + LOG_INFO("\tTimeStamp: {}", state.TimeStamp); + LOG_INFO("\tAngularVelocity (Raw): {}, {}, {}", state.AngularVelocityX, + state.AngularVelocityY, state.AngularVelocityZ); auto gyro_x = mult_frac( hw_cal_data.gyro[0].sens_numer, state.AngularVelocityX, @@ -503,10 +501,10 @@ void InputReader::PrintControllerStateUsb( auto gyro_z = mult_frac( hw_cal_data.gyro[2].sens_numer, state.AngularVelocityZ, hw_cal_data.gyro[2].sens_denom); - spdlog::info("\tAngularVelocity (Cal): {}, {}, {}", gyro_x, gyro_y, gyro_z); + LOG_INFO("\tAngularVelocity (Cal): {}, {}, {}", gyro_x, gyro_y, gyro_z); - spdlog::info("\tAccelerometer (Raw): {}, {}, {}", state.AccelerometerX, - state.AccelerometerY, state.AccelerometerZ); + LOG_INFO("\tAccelerometer (Raw): {}, {}, {}", state.AccelerometerX, + state.AccelerometerY, state.AccelerometerZ); auto acc_x = mult_frac( hw_cal_data.accel[0].sens_numer, state.AccelerometerX, @@ -517,58 +515,57 @@ void InputReader::PrintControllerStateUsb( auto acc_z = mult_frac( hw_cal_data.accel[2].sens_numer, state.AccelerometerZ, hw_cal_data.accel[2].sens_denom); - spdlog::info("\tAccelerometer (Cal): {}, {}, {}", acc_x, acc_y, acc_z); + LOG_INFO("\tAccelerometer (Cal): {}, {}, {}", acc_x, acc_y, acc_z); - spdlog::info("\tSensorTimestamp: {}", state.SensorTimestamp); - spdlog::info("\tTemperature: {}", state.Temperature); - spdlog::info( + LOG_INFO("\tSensorTimestamp: {}", state.SensorTimestamp); + LOG_INFO("\tTemperature: {}", state.Temperature); + LOG_INFO( "\tTouchData: timestamp: {}, index: {}, X: {}, Y: {}, NotTouching: {}", state.touchData.Timestamp, state.touchData.Finger[0].Index, state.touchData.Finger[0].FingerX, state.touchData.Finger[0].FingerY, state.touchData.Finger[0].NotTouching); - spdlog::info( + LOG_INFO( "\tTouchData: timestamp: {}, index: {}, X: {}, Y: {}, NotTouching: {}", state.touchData.Timestamp, state.touchData.Finger[1].Index, state.touchData.Finger[1].FingerX, state.touchData.Finger[1].FingerY, state.touchData.Finger[1].NotTouching); - spdlog::info("\tTriggerRightStopLocation: {}", - state.TriggerRightStopLocation); - spdlog::info("\tTriggerRightStatus: {}", state.TriggerRightStatus); - spdlog::info("\tTriggerLeftStopLocation: {}", state.TriggerLeftStopLocation); - spdlog::info("\tTriggerLeftStatus: {}", state.TriggerLeftStatus); - spdlog::info("\tTriggerRightEffect: {}", state.TriggerRightEffect); - spdlog::info("\tTriggerLeftEffect: {}", state.TriggerLeftEffect); - spdlog::info("\tPowerPercent: {}", state.PowerPercent); - spdlog::info("\tPowerState: {}", power_state_to_string(state.powerState)); - spdlog::info("\tPluggedHeadphones: {}", state.PluggedHeadphones); - spdlog::info("\tPluggedMic: {}", state.PluggedMic); - spdlog::info("\tMicMuted: {}", state.MicMuted); - spdlog::info("\tPluggedUsbData: {}", state.PluggedUsbData); - spdlog::info("\tPluggedUsbPower: {}", state.PluggedUsbPower); - spdlog::info("\tPluggedExternalMic: {}", state.PluggedExternalMic); - spdlog::info("\tHapticLowPassFilter: {}", state.HapticLowPassFilter); + LOG_INFO("\tTriggerRightStopLocation: {}", state.TriggerRightStopLocation); + LOG_INFO("\tTriggerRightStatus: {}", state.TriggerRightStatus); + LOG_INFO("\tTriggerLeftStopLocation: {}", state.TriggerLeftStopLocation); + LOG_INFO("\tTriggerLeftStatus: {}", state.TriggerLeftStatus); + LOG_INFO("\tTriggerRightEffect: {}", state.TriggerRightEffect); + LOG_INFO("\tTriggerLeftEffect: {}", state.TriggerLeftEffect); + LOG_INFO("\tPowerPercent: {}", state.PowerPercent); + LOG_INFO("\tPowerState: {}", power_state_to_string(state.powerState)); + LOG_INFO("\tPluggedHeadphones: {}", state.PluggedHeadphones); + LOG_INFO("\tPluggedMic: {}", state.PluggedMic); + LOG_INFO("\tMicMuted: {}", state.MicMuted); + LOG_INFO("\tPluggedUsbData: {}", state.PluggedUsbData); + LOG_INFO("\tPluggedUsbPower: {}", state.PluggedUsbPower); + LOG_INFO("\tPluggedExternalMic: {}", state.PluggedExternalMic); + LOG_INFO("\tHapticLowPassFilter: {}", state.HapticLowPassFilter); } void InputReader::PrintControllerStateBt(BTSimpleGetStateData const& state) { - spdlog::info("Controller State (BT)"); - spdlog::info("\tLeftStick: {}, {}", state.LeftStickX, state.LeftStickY); - spdlog::info("\tRightStick: {}, {}", state.RightStickX, state.RightStickY); - spdlog::info("\tDPad: {}", dpad_to_string(state.DPad)); - spdlog::info("\tButtonSquare: {}", state.ButtonSquare); - spdlog::info("\tButtonCross: {}", state.ButtonCross); - spdlog::info("\tButtonCircle: {}", state.ButtonCircle); - spdlog::info("\tButtonTriangle: {}", state.ButtonTriangle); - spdlog::info("\tButtonL1: {}", state.ButtonL1); - spdlog::info("\tButtonR1: {}", state.ButtonR1); - spdlog::info("\tButtonL2: {}", state.ButtonL2); - spdlog::info("\tButtonR2: {}", state.ButtonR2); - spdlog::info("\tButtonShare: {}", state.ButtonShare); - spdlog::info("\tButtonOptions: {}", state.ButtonOptions); - spdlog::info("\tButtonL3: {}", state.ButtonL3); - spdlog::info("\tButtonR3: {}", state.ButtonR3); - spdlog::info("\tButtonHome: {}", state.ButtonHome); - spdlog::info("\tButtonPad: {}", state.ButtonPad); - spdlog::info("\tCounter: {}", state.Counter); - spdlog::info("\tTriggerLeft: {}", state.TriggerLeft); - spdlog::info("\tTriggerRight: {}", state.TriggerRight); + LOG_INFO("Controller State (BT)"); + LOG_INFO("\tLeftStick: {}, {}", state.LeftStickX, state.LeftStickY); + LOG_INFO("\tRightStick: {}, {}", state.RightStickX, state.RightStickY); + LOG_INFO("\tDPad: {}", dpad_to_string(state.DPad)); + LOG_INFO("\tButtonSquare: {}", state.ButtonSquare); + LOG_INFO("\tButtonCross: {}", state.ButtonCross); + LOG_INFO("\tButtonCircle: {}", state.ButtonCircle); + LOG_INFO("\tButtonTriangle: {}", state.ButtonTriangle); + LOG_INFO("\tButtonL1: {}", state.ButtonL1); + LOG_INFO("\tButtonR1: {}", state.ButtonR1); + LOG_INFO("\tButtonL2: {}", state.ButtonL2); + LOG_INFO("\tButtonR2: {}", state.ButtonR2); + LOG_INFO("\tButtonShare: {}", state.ButtonShare); + LOG_INFO("\tButtonOptions: {}", state.ButtonOptions); + LOG_INFO("\tButtonL3: {}", state.ButtonL3); + LOG_INFO("\tButtonR3: {}", state.ButtonR3); + LOG_INFO("\tButtonHome: {}", state.ButtonHome); + LOG_INFO("\tButtonPad: {}", state.ButtonPad); + LOG_INFO("\tCounter: {}", state.Counter); + LOG_INFO("\tTriggerLeft: {}", state.TriggerLeft); + LOG_INFO("\tTriggerRight: {}", state.TriggerRight); } diff --git a/src/bluez/ps5_dual_sense/main.cc b/src/bluez/ps5_dual_sense/main.cc index 5123156..883e662 100644 --- a/src/bluez/ps5_dual_sense/main.cc +++ b/src/bluez/ps5_dual_sense/main.cc @@ -12,21 +12,40 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include "../../utils/signal_handler.h" #include "dual_sense.h" int main() { - spdlog::set_level(spdlog::level::debug); - spdlog::flush_every(std::chrono::seconds(5)); + try { + installSignalHandlers(); - const auto connection = sdbus::createSystemBusConnection(); - connection->enterEventLoopAsync(); + spdlog::set_level(spdlog::level::debug); + spdlog::flush_every(kLogFlushInterval); - DualSense client(*connection); + const auto connection = sdbus::createSystemBusConnection(); + connection->enterEventLoopAsync(); - using namespace std::chrono_literals; - std::this_thread::sleep_for(120000ms); - connection->leaveEventLoop(); + DualSense client(*connection); - return 0; + LOG_INFO("PS5 DualSense client running - Press Ctrl+C to exit"); + + // Monitor loop with shared connection health timing defaults + auto result = monitorLoop(*connection); + + if (result) { + LOG_ERROR("Exiting due to: {}", *result); + } else { + LOG_INFO("Shutting down..."); + } + + connection->leaveEventLoop(); + return result ? 1 : 0; + + } catch (const sdbus::Error& e) { + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); + return 1; + } catch (const std::exception& e) { + LOG_ERROR("Exception: {}", e.what()); + return 1; + } } diff --git a/src/bluez/udev_monitor.hpp b/src/bluez/udev_monitor.hpp index cb4d1f5..d15fc9c 100644 --- a/src/bluez/udev_monitor.hpp +++ b/src/bluez/udev_monitor.hpp @@ -37,24 +37,59 @@ class UdevMonitor { callback) : sub_systems_(std::move(sub_systems)), callback_(callback) { if (pipe(pipe_fds_) == -1) { + spdlog::error("Failed to create pipe: {} ({})", std::strerror(errno), + errno); + pipe_fds_[0] = -1; + pipe_fds_[1] = -1; return; } - std::thread(&UdevMonitor::run, this).detach(); + + // Start a thread (not detached, so we can join it) + worker_thread_ = std::thread(&UdevMonitor::run, this); } + // Delete copy operations - this class manages a thread and file descriptor + UdevMonitor(const UdevMonitor&) = delete; + UdevMonitor& operator=(const UdevMonitor&) = delete; + + // Move operations deleted - moving a class with a running thread is complex + // and not needed for this use case. If move semantics are required in the + // future, would need to: + // 1. Stop the thread in the moved-from object + // 2. Transfer ownership of file descriptors + // 3. Start a new thread in the moved-to object + UdevMonitor(UdevMonitor&&) = delete; + UdevMonitor& operator=(UdevMonitor&&) = delete; + virtual ~UdevMonitor() { - if (is_running_) { - stop(); + stop(); + + // Wait for worker thread to finish before closing pipes + if (worker_thread_.joinable()) { + worker_thread_.join(); + } + + // Now safe to close pipes + if (pipe_fds_[0] != -1) { + close(pipe_fds_[0]); + pipe_fds_[0] = -1; + } + if (pipe_fds_[1] != -1) { + close(pipe_fds_[1]); + pipe_fds_[1] = -1; } - close(pipe_fds_[0]); - close(pipe_fds_[1]); } void stop() { - is_running_ = false; + // Only execute stop logic once + if (bool expected = true; + !is_running_.compare_exchange_strong(expected, false)) { + return; // Already stopped + } + + // Signal the worker thread to exit if (pipe_fds_[1] != -1) { - ssize_t wrote = write(pipe_fds_[1], "x", 1); - if (wrote == -1) { + if (const ssize_t wrote = write(pipe_fds_[1], "x", 1); wrote == -1) { spdlog::error("Failed to write to stop pipe: {} ({})", std::strerror(errno), errno); } @@ -64,12 +99,26 @@ class UdevMonitor { private: std::vector sub_systems_; std::atomic is_running_{true}; - int pipe_fds_[2]{}; + int pipe_fds_[2]{-1, -1}; std::function callback_; + std::thread worker_thread_; void run() { const auto udev = udev_new(); + if (!udev) { + spdlog::error("Failed to create udev context"); + is_running_ = false; + return; + } + const auto mon = udev_monitor_new_from_netlink(udev, "udev"); + if (!mon) { + spdlog::error("Failed to create udev monitor"); + udev_unref(udev); + is_running_ = false; + return; + } + for (const auto& sub_system : sub_systems_) { if (int res = udev_monitor_filter_add_match_subsystem_devtype( mon, sub_system.c_str(), nullptr); @@ -84,6 +133,11 @@ class UdevMonitor { const int epoll_fd = epoll_create1(0); if (epoll_fd == -1) { + spdlog::error("Failed to create epoll: {} ({})", std::strerror(errno), + errno); + udev_monitor_unref(mon); + udev_unref(udev); + is_running_ = false; return; } @@ -91,13 +145,23 @@ class UdevMonitor { ev.events = EPOLLIN; ev.data.fd = fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { + spdlog::error("Failed to add udev fd to epoll: {} ({})", + std::strerror(errno), errno); close(epoll_fd); + udev_monitor_unref(mon); + udev_unref(udev); + is_running_ = false; return; } ev.data.fd = pipe_fds_[0]; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, pipe_fds_[0], &ev) == -1) { + spdlog::error("Failed to add pipe fd to epoll: {} ({})", + std::strerror(errno), errno); close(epoll_fd); + udev_monitor_unref(mon); + udev_unref(udev); + is_running_ = false; return; } @@ -105,6 +169,11 @@ class UdevMonitor { epoll_event events[2]; const int triggered_event_count = epoll_wait(epoll_fd, events, 2, -1); if (triggered_event_count == -1) { + if (errno == EINTR) { + continue; // Interrupted by signal, retry + } + spdlog::error("epoll_wait failed: {} ({})", std::strerror(errno), + errno); break; } @@ -118,9 +187,22 @@ class UdevMonitor { if (events[n].data.fd == fd) { if (const auto dev = udev_monitor_receive_device(mon)) { if (callback_) { - callback_(udev_device_get_action(dev), - udev_device_get_devnode(dev), - udev_device_get_subsystem(dev)); + // Get device properties - these can return NULL + const char* action = udev_device_get_action(dev); + const char* devnode = udev_device_get_devnode(dev); + const char* subsystem = udev_device_get_subsystem(dev); + + // Only invoke callback if we have valid data + // Note: devnode can legitimately be NULL for some devices + if (action && subsystem) { + callback_(action, devnode, subsystem); + } else { + spdlog::debug( + "Skipping callback for device with missing properties: " + "action={}, devnode={}, subsystem={}", + action ? action : "null", devnode ? devnode : "null", + subsystem ? subsystem : "null"); + } } udev_device_unref(dev); } @@ -128,8 +210,12 @@ class UdevMonitor { } } + // Clean up resources in reverse order + close(epoll_fd); udev_monitor_unref(mon); udev_unref(udev); + + spdlog::debug("UdevMonitor worker thread exiting"); } }; diff --git a/src/bluez/xbox_controller/input_reader.cc b/src/bluez/xbox_controller/input_reader.cc index 6e87575..5cc62f7 100644 --- a/src/bluez/xbox_controller/input_reader.cc +++ b/src/bluez/xbox_controller/input_reader.cc @@ -19,6 +19,7 @@ #include #include +#include "../../utils/logging.h" #include "../hidraw.hpp" #include "input_reader.h" @@ -26,13 +27,13 @@ InputReader::InputReader(std::string device) : device_(std::move(device)), stop_flag_(false) {} void InputReader::start() { - spdlog::debug("InputReader start: {}", device_); + LOG_DEBUG("InputReader start: {}", device_); stop_flag_ = false; read_input(); } void InputReader::stop() { - spdlog::debug("InputReader stop: {}", device_); + LOG_DEBUG("InputReader stop: {}", device_); stop_flag_ = true; } @@ -42,71 +43,71 @@ InputReader::~InputReader() { // NOLINTNEXTLINE(readability-static-accessed-through-instance) InputReader::Task InputReader::read_input() { - spdlog::debug("hidraw device: {}", device_); + LOG_DEBUG("hidraw device: {}", device_); const int fd = open(device_.c_str(), O_RDWR); while (true) { if (fd < 0) { - spdlog::error("unable to open device"); + LOG_ERROR("unable to open device"); break; } // Raw Info hidraw_devinfo raw_dev_info{}; if (const auto res = ioctl(fd, HIDIOCGRAWINFO, &raw_dev_info); res < 0) { - spdlog::error("HIDIOCGRAWINFO"); + LOG_ERROR("HIDIOCGRAWINFO"); break; } - spdlog::info("bustype: {}", Hidraw::bus_str(raw_dev_info.bustype)); - spdlog::info("Vendor ID: {:04X}", raw_dev_info.vendor); - spdlog::info("Product ID: {:04X}", raw_dev_info.product); + LOG_INFO("bustype: {}", Hidraw::bus_str(raw_dev_info.bustype)); + LOG_INFO("Vendor ID: {:04X}", raw_dev_info.vendor); + LOG_INFO("Product ID: {:04X}", raw_dev_info.product); // Raw Name char buf[256]{}; auto res = ioctl(fd, HIDIOCGRAWNAME(sizeof(buf)), buf); if (res < 0) { - spdlog::error("HIDIOCGRAWNAME"); + LOG_ERROR("HIDIOCGRAWNAME"); break; } - spdlog::info("HID Name: {}", buf); + LOG_INFO("HID Name: {}", buf); // Raw Physical Location res = ioctl(fd, HIDIOCGRAWPHYS(sizeof(buf)), buf); if (res < 0) { - spdlog::error("HIDIOCGRAWPHYS"); + LOG_ERROR("HIDIOCGRAWPHYS"); break; } - spdlog::info("HID Physical Location: {}", buf); + LOG_INFO("HID Physical Location: {}", buf); // Report Descriptor Size int desc_size = 0; res = ioctl(fd, HIDIOCGRDESCSIZE, &desc_size); if (res < 0) { - spdlog::error("HIDIOCGRDESCSIZE"); + LOG_ERROR("HIDIOCGRDESCSIZE"); break; } - spdlog::info("Report Descriptor Size: {}", desc_size); + LOG_INFO("Report Descriptor Size: {}", desc_size); // Report Descriptor hidraw_report_descriptor rpt_desc{}; rpt_desc.size = desc_size; res = ioctl(fd, HIDIOCGRDESC, &rpt_desc); if (res < 0) { - spdlog::error("HIDIOCGRDESC"); + LOG_ERROR("HIDIOCGRDESC"); break; } std::ostringstream os; os << "Report Descriptor\n"; os << CustomHexdump<400, false>(rpt_desc.value, rpt_desc.size); - spdlog::info(os.str()); + LOG_INFO(os.str()); while (!stop_flag_) { std::uint8_t buffer[sizeof(inputReport01_t)]; ssize_t result = 0; if (result = read(fd, &buffer[0], sizeof(inputReport01_t)); result < 0) { - spdlog::error("GetInputReport4 failed: {}", strerror(errno)); + LOG_ERROR("GetInputReport4 failed: {}", strerror(errno)); break; } @@ -124,7 +125,7 @@ InputReader::Task InputReader::read_input() { reinterpret_cast(buffer); PrintInputReport4(*input_report04); } else { - spdlog::error("Unknown report id: {}", buffer[0]); + LOG_ERROR("Unknown report id: {}", buffer[0]); } } } @@ -161,52 +162,50 @@ std::string InputReader::dpad_to_string(const Direction dpad) { } void InputReader::PrintInputReport1(const inputReport01_t& input_report01) { - spdlog::info("Stick L/R: [{},{}] [{},{}] ", input_report01.GD_GamepadPointerX, - input_report01.GD_GamepadPointerY, - input_report01.GD_GamepadPointerZ, - input_report01.GD_GamepadPointerRz); - spdlog::info("Trigger L/R: {}, {}", input_report01.SIM_GamepadBrake, - input_report01.SIM_GamepadAccelerator); - spdlog::info("D-PAD: {}", dpad_to_string(static_cast( - input_report01.GD_GamepadHatSwitch))); - spdlog::info("A: {}", input_report01.BTN_GamepadButton1); - spdlog::info("B: {}", input_report01.BTN_GamepadButton2); - spdlog::info("Button3: {}", input_report01.BTN_GamepadButton3); - spdlog::info("X: {}", input_report01.BTN_GamepadButton4); - spdlog::info("Y: {}", input_report01.BTN_GamepadButton5); - spdlog::info("Button6: {}", input_report01.BTN_GamepadButton6); - spdlog::info("Left Bumper: {}", input_report01.BTN_GamepadButton7); - spdlog::info("Right Bumper: {}", input_report01.BTN_GamepadButton8); - spdlog::info("Button9: {}", input_report01.BTN_GamepadButton9); - spdlog::info("Button10: {}", input_report01.BTN_GamepadButton10); - spdlog::info("Button11: {}", input_report01.BTN_GamepadButton11); - spdlog::info("Menu: {}", input_report01.BTN_GamepadButton12); - spdlog::info("Button13: {}", input_report01.BTN_GamepadButton13); - spdlog::info("Left Stick Btn: {}", input_report01.BTN_GamepadButton14); - spdlog::info("Right Stick Btn: {}", input_report01.BTN_GamepadButton15); - spdlog::info("Back: {}", input_report01.CD_GamepadAcBack); + LOG_INFO("Stick L/R: [{},{}] [{},{}] ", input_report01.GD_GamepadPointerX, + input_report01.GD_GamepadPointerY, input_report01.GD_GamepadPointerZ, + input_report01.GD_GamepadPointerRz); + LOG_INFO("Trigger L/R: {}, {}", input_report01.SIM_GamepadBrake, + input_report01.SIM_GamepadAccelerator); + LOG_INFO("D-PAD: {}", dpad_to_string(static_cast( + input_report01.GD_GamepadHatSwitch))); + LOG_INFO("A: {}", input_report01.BTN_GamepadButton1); + LOG_INFO("B: {}", input_report01.BTN_GamepadButton2); + LOG_INFO("Button3: {}", input_report01.BTN_GamepadButton3); + LOG_INFO("X: {}", input_report01.BTN_GamepadButton4); + LOG_INFO("Y: {}", input_report01.BTN_GamepadButton5); + LOG_INFO("Button6: {}", input_report01.BTN_GamepadButton6); + LOG_INFO("Left Bumper: {}", input_report01.BTN_GamepadButton7); + LOG_INFO("Right Bumper: {}", input_report01.BTN_GamepadButton8); + LOG_INFO("Button9: {}", input_report01.BTN_GamepadButton9); + LOG_INFO("Button10: {}", input_report01.BTN_GamepadButton10); + LOG_INFO("Button11: {}", input_report01.BTN_GamepadButton11); + LOG_INFO("Menu: {}", input_report01.BTN_GamepadButton12); + LOG_INFO("Button13: {}", input_report01.BTN_GamepadButton13); + LOG_INFO("Left Stick Btn: {}", input_report01.BTN_GamepadButton14); + LOG_INFO("Right Stick Btn: {}", input_report01.BTN_GamepadButton15); + LOG_INFO("Back: {}", input_report01.CD_GamepadAcBack); } void InputReader::PrintInputReport2(const inputReport02_t& input_report02) { - spdlog::info("Home Button: {}", - input_report02.CD_GamepadConsumerControlAcHome); + LOG_INFO("Home Button: {}", input_report02.CD_GamepadConsumerControlAcHome); } void InputReader::PrintOutputReport3(const outputReport03_t& output_report03) { - spdlog::info("OutputReport3: {}", output_report03.reportId); - spdlog::info("PID_GamepadSetEffectReportDcEnableActuators: {}", - output_report03.PID_GamepadSetEffectReportDcEnableActuators); - spdlog::info("PID_GamepadSetEffectReportMagnitude: {}, {}, {}, {}", - output_report03.PID_GamepadSetEffectReportMagnitude[0], - output_report03.PID_GamepadSetEffectReportMagnitude[1], - output_report03.PID_GamepadSetEffectReportMagnitude[2], - output_report03.PID_GamepadSetEffectReportMagnitude[3]); - spdlog::info("PID_GamepadSetEffectReportDuration: {}", - output_report03.PID_GamepadSetEffectReportDuration); - spdlog::info("PID_GamepadSetEffectReportStartDelay: {}", - output_report03.PID_GamepadSetEffectReportStartDelay); - spdlog::info("PID_GamepadSetEffectReportLoopCount: {}", - output_report03.PID_GamepadSetEffectReportLoopCount); + LOG_INFO("OutputReport3: {}", output_report03.reportId); + LOG_INFO("PID_GamepadSetEffectReportDcEnableActuators: {}", + output_report03.PID_GamepadSetEffectReportDcEnableActuators); + LOG_INFO("PID_GamepadSetEffectReportMagnitude: {}, {}, {}, {}", + output_report03.PID_GamepadSetEffectReportMagnitude[0], + output_report03.PID_GamepadSetEffectReportMagnitude[1], + output_report03.PID_GamepadSetEffectReportMagnitude[2], + output_report03.PID_GamepadSetEffectReportMagnitude[3]); + LOG_INFO("PID_GamepadSetEffectReportDuration: {}", + output_report03.PID_GamepadSetEffectReportDuration); + LOG_INFO("PID_GamepadSetEffectReportStartDelay: {}", + output_report03.PID_GamepadSetEffectReportStartDelay); + LOG_INFO("PID_GamepadSetEffectReportLoopCount: {}", + output_report03.PID_GamepadSetEffectReportLoopCount); } void InputReader::PrintInputReport4(const inputReport04_t& output_report04) { @@ -215,5 +214,5 @@ void InputReader::PrintInputReport4(const inputReport04_t& output_report04) { 255.0f) * 100.0f; battery_percentage = std::round(battery_percentage); // Round the percentage - spdlog::info("Battery: {}", battery_percentage); + LOG_INFO("Battery: {}", battery_percentage); } diff --git a/src/bluez/xbox_controller/main.cc b/src/bluez/xbox_controller/main.cc index f76665c..6fcc231 100644 --- a/src/bluez/xbox_controller/main.cc +++ b/src/bluez/xbox_controller/main.cc @@ -12,21 +12,36 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include "../../utils/signal_handler.h" #include "xbox_controller.h" int main() { - spdlog::set_level(spdlog::level::debug); - spdlog::flush_every(std::chrono::seconds(5)); + try { + installSignalHandlers(); - const auto connection = sdbus::createSystemBusConnection(); - connection->enterEventLoopAsync(); + const auto connection = sdbus::createSystemBusConnection(); + connection->enterEventLoopAsync(); - XboxController client(*connection); + XboxController client(*connection); - using namespace std::chrono_literals; - std::this_thread::sleep_for(120000ms); - connection->leaveEventLoop(); + LOG_INFO("Xbox controller client running - Press Ctrl+C to exit"); - return 0; + auto result = monitorLoop(*connection); + + if (result) { + LOG_ERROR("Exiting due to: {}", *result); + } else { + LOG_INFO("Shutting down..."); + } + + connection->leaveEventLoop(); + return result ? 1 : 0; + + } catch (const sdbus::Error& e) { + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); + return 1; + } catch (const std::exception& e) { + LOG_ERROR("Exception: {}", e.what()); + return 1; + } } diff --git a/src/bluez/xbox_controller/xbox_controller.cc b/src/bluez/xbox_controller/xbox_controller.cc index a434ac9..45d3726 100644 --- a/src/bluez/xbox_controller/xbox_controller.cc +++ b/src/bluez/xbox_controller/xbox_controller.cc @@ -16,6 +16,8 @@ #include +#include "../../utils/property_utils.h" +#include "../../utils/resource_limits.h" #include "../hidraw.hpp" const std::vector> input_match_params_bt = { @@ -38,10 +40,9 @@ XboxController::XboxController(sdbus::IConnection& connection) [&](const char* action, const char* dev_node, const char* sub_system) { - spdlog::debug("Action: {}, Device: {}, Subsystem: {}", - action ? action : "", - dev_node ? dev_node : "", - sub_system ? sub_system : ""); + LOG_DEBUG("Action: {}, Device: {}, Subsystem: {}", + action ? action : "", dev_node ? dev_node : "", + sub_system ? sub_system : ""); if (std::strcmp(sub_system, "hidraw") == 0) { if (std::strcmp(action, "remove") == 0) { input_reader_->stop(); @@ -79,6 +80,12 @@ void XboxController::onInterfacesAdded( if (interface == org::bluez::Adapter1_proxy::INTERFACE_NAME) { std::scoped_lock lock(adapters_mutex_); if (!adapters_.contains(objectPath)) { + if (resource_limits::IsAtCapacity(adapters_.size(), + resource_limits::kMaxAdapters)) { + LOG_WARN("Skipping Adapter1 {}: resource limit reached ({}/{})", + objectPath, adapters_.size(), resource_limits::kMaxAdapters); + continue; + } auto adapter1 = std::make_unique( getProxy().getConnection(), sdbus::ServiceName(INTERFACE_NAME), objectPath, properties); @@ -86,67 +93,108 @@ void XboxController::onInterfacesAdded( } } else if (interface == org::bluez::Device1_proxy::INTERFACE_NAME) { auto mod_alias_key = sdbus::MemberName("Modalias"); - if (!properties.contains(mod_alias_key)) - continue; - auto mod_alias = Device1::parse_modalias( - properties.at(mod_alias_key).get()); - if (mod_alias.has_value()) { + // Safely get the Modalias property + auto mod_alias_str = + property_utils::getProperty(properties, mod_alias_key); + if (!mod_alias_str) { + continue; // Skip devices without Modalias + } + + if (auto mod_alias = Device1::parse_modalias(*mod_alias_str); + mod_alias.has_value()) { if (auto [vid, pid, did] = mod_alias.value(); vid != VENDOR_ID || (pid != PRODUCT_ID0 && pid != PRODUCT_ID1)) { continue; } - spdlog::debug("VID: {}, PID: {}, DID: {}", mod_alias.value().vid, - mod_alias.value().pid, mod_alias.value().did); + LOG_DEBUG("VID: {}, PID: {}, DID: {}", mod_alias.value().vid, + mod_alias.value().pid, mod_alias.value().did); } else { - spdlog::debug("modalias has no value assigned: {}", objectPath); + LOG_DEBUG("modalias has no value assigned: {}", objectPath); continue; } - std::scoped_lock lock(devices_mutex_); - if (!devices_.contains(objectPath)) { + std::string power_path_to_add; + std::string hidraw_device_key; + { + std::scoped_lock lock(devices_mutex_); + if (devices_.contains(objectPath)) { + continue; + } + + if (resource_limits::IsAtCapacity(devices_.size(), + resource_limits::kMaxDevices)) { + LOG_WARN("Skipping Device1 {}: resource limit reached ({}/{})", + objectPath, devices_.size(), resource_limits::kMaxDevices); + continue; + } + auto device = std::make_unique( getProxy().getConnection(), sdbus::ServiceName(INTERFACE_NAME), objectPath, properties); if (auto props = device->GetProperties(); props.modalias.has_value()) { auto [vid, pid, did] = props.modalias.value(); - spdlog::info("Adding: {}, {}, {}", vid, pid, did); + LOG_INFO("Adding: {}, {}, {}", vid, pid, did); if ((vid == VENDOR_ID && pid == PRODUCT_ID0) || (vid == VENDOR_ID && pid == PRODUCT_ID1)) { if (props.connected && props.paired && props.trusted) { - const auto dev_key = + hidraw_device_key = create_device_key_from_serial_number(props.address); - HidDevicesLock(); - if (HidDevicesContains(dev_key)) { - spdlog::info("Adding hidraw device: {}", dev_key); - if (!input_reader_) { - input_reader_ = - std::make_unique(GetHidDevice(dev_key)); - input_reader_->start(); - } - } - HidDevicesUnlock(); } - // Add UPower Display Device - if (std::string power_path = - convert_mac_to_upower_path(props.address); - !upower_clients_.contains(power_path)) { - upower_display_devices_mutex_.lock(); - spdlog::info("[Add] UPower Display Device: {}", power_path); - upower_clients_[power_path] = std::make_unique( - getProxy().getConnection(), sdbus::ObjectPath(power_path)); - upower_display_devices_mutex_.unlock(); - } + power_path_to_add = convert_mac_to_upower_path(props.address); } } devices_[objectPath] = std::move(device); } + + if (!hidraw_device_key.empty()) { + std::string hidraw_device; + HidDevicesLock(); + if (HidDevicesContains(hidraw_device_key)) { + hidraw_device = GetHidDevice(hidraw_device_key); + } + HidDevicesUnlock(); + + if (!hidraw_device.empty()) { + LOG_INFO("Adding hidraw device: {}", hidraw_device_key); + if (!input_reader_) { + input_reader_ = std::make_unique(hidraw_device); + input_reader_->start(); + } + } + } + + // Avoid nested locking with devices_mutex_ + + // upower_display_devices_mutex_. + if (!power_path_to_add.empty()) { + std::scoped_lock power_lock(upower_display_devices_mutex_); + if (!upower_clients_.contains(power_path_to_add)) { + if (resource_limits::IsAtCapacity( + upower_clients_.size(), resource_limits::kMaxUPowerClients)) { + LOG_WARN( + "Skipping UPower client {}: resource limit reached ({}/{})", + power_path_to_add, upower_clients_.size(), + resource_limits::kMaxUPowerClients); + continue; + } + LOG_INFO("[Add] UPower Display Device: {}", power_path_to_add); + upower_clients_[power_path_to_add] = std::make_unique( + getProxy().getConnection(), sdbus::ObjectPath(power_path_to_add)); + } + } } else if (interface == org::bluez::Input1_proxy::INTERFACE_NAME) { std::lock_guard lock(input1_mutex_); if (!input1_.contains(objectPath)) { + if (resource_limits::IsAtCapacity(input1_.size(), + resource_limits::kMaxInputEntries)) { + LOG_WARN("Skipping Input1 {}: resource limit reached ({}/{})", + objectPath, input1_.size(), + resource_limits::kMaxInputEntries); + continue; + } input1_[objectPath] = std::make_unique( getProxy().getConnection(), sdbus::ServiceName(org::bluez::Input1_proxy::INTERFACE_NAME), @@ -162,54 +210,48 @@ void XboxController::onInterfacesRemoved( for (const auto& interface : interfaces) { if (interface == org::bluez::Adapter1_proxy::INTERFACE_NAME) { std::scoped_lock lock(adapters_mutex_); - if (!adapters_.contains(objectPath)) { - if (adapters_.contains(objectPath)) { - adapters_[objectPath].reset(); - adapters_.erase(objectPath); - } + if (adapters_.contains(objectPath)) { + adapters_[objectPath].reset(); + adapters_.erase(objectPath); } } else if (interface == org::bluez::Device1_proxy::INTERFACE_NAME) { - std::scoped_lock devices_lock(devices_mutex_); - if (!devices_.contains(objectPath)) { + std::string power_path_to_remove; + { + std::scoped_lock devices_lock(devices_mutex_); if (devices_.contains(objectPath)) { - devices_[objectPath].reset(); + auto& device = devices_[objectPath]; + if (auto props = device->GetProperties(); + props.modalias.has_value()) { + auto [vid, pid, did] = props.modalias.value(); + LOG_INFO("Removing: {}, {}, {}", vid, pid, did); + if ((vid == VENDOR_ID && pid == PRODUCT_ID0) || + (vid == VENDOR_ID && pid == PRODUCT_ID1)) { + power_path_to_remove = convert_mac_to_upower_path(props.address); + } + } + + device.reset(); devices_.erase(objectPath); } } + + if (!power_path_to_remove.empty()) { + std::scoped_lock power_lock(upower_display_devices_mutex_); + if (upower_clients_.contains(power_path_to_remove)) { + LOG_INFO("[Remove] UPower Display Device: {}", power_path_to_remove); + auto& power_device = upower_clients_[power_path_to_remove]; + power_device.reset(); + upower_clients_.erase(power_path_to_remove); + } + } } else if (interface == org::bluez::Input1_proxy::INTERFACE_NAME) { std::lock_guard lock(input1_mutex_); - if (!input1_.contains(objectPath)) { + if (input1_.contains(objectPath)) { input1_[objectPath].reset(); input1_.erase(objectPath); } } } - for (auto it = interfaces.begin(); it != interfaces.end(); ++it) { - std::scoped_lock lock(devices_mutex_); - if (devices_.contains(objectPath)) { - auto& device = devices_[objectPath]; - - if (auto props = device->GetProperties(); props.modalias.has_value()) { - auto [vid, pid, did] = props.modalias.value(); - spdlog::info("Removing: {}, {}, {}", vid, pid, did); - if ((vid == VENDOR_ID && pid == PRODUCT_ID0) || - (vid == VENDOR_ID && pid == PRODUCT_ID1)) { - if (std::string power_path = - convert_mac_to_upower_path(props.address); - upower_clients_.contains(power_path)) { - std::scoped_lock power_lock(upower_display_devices_mutex_); - spdlog::info("[Remove] UPower Display Device: {}", power_path); - auto& power_device = upower_clients_[power_path]; - power_device.reset(); - upower_clients_.erase(power_path); - } - } - } - - device.reset(); - devices_.erase(objectPath); - } - } } std::string XboxController::convert_mac_to_upower_path( diff --git a/src/bluez/xbox_controller/xbox_controller.h b/src/bluez/xbox_controller/xbox_controller.h index e1a48d7..8159b25 100644 --- a/src/bluez/xbox_controller/xbox_controller.h +++ b/src/bluez/xbox_controller/xbox_controller.h @@ -46,6 +46,10 @@ class XboxController final static constexpr auto INTROSPECTABLE_INTERFACE_NAME = "org.freedesktop.DBus.Introspectable"; + // Locking policy: avoid nested locking where possible. + // If nested locking is required, always acquire in this order: + // adapters_mutex_ -> devices_mutex_ -> input1_mutex_ -> + // upower_display_devices_mutex_. std::mutex adapters_mutex_; std::map> adapters_; diff --git a/src/fwupd/fwupd_client.cc b/src/fwupd/fwupd_client.cc index a23fc87..b470080 100644 --- a/src/fwupd/fwupd_client.cc +++ b/src/fwupd/fwupd_client.cc @@ -14,6 +14,7 @@ #include "fwupd_client.h" +#include "../utils/logging.h" #include "../utils/utils.h" FwupdClient::FwupdClient(sdbus::IConnection& connection) @@ -35,7 +36,7 @@ void FwupdClient::onPropertiesChanged( } void FwupdClient::onChanged() { - spdlog::info("onChanged"); + LOG_INFO("onChanged"); } void FwupdClient::onDeviceAdded( @@ -43,7 +44,7 @@ void FwupdClient::onDeviceAdded( std::ostringstream os; os << std::endl << "[FwupdClient] onDeviceAdded" << std::endl; Utils::append_properties(device, os); - spdlog::info(os.str()); + LOG_INFO(os.str()); } void FwupdClient::onDeviceRemoved( @@ -51,7 +52,7 @@ void FwupdClient::onDeviceRemoved( std::ostringstream os; os << std::endl << "[FwupdClient] onDeviceRemoved" << std::endl; Utils::append_properties(device, os); - spdlog::info(os.str()); + LOG_INFO(os.str()); } void FwupdClient::onDeviceChanged( @@ -59,7 +60,7 @@ void FwupdClient::onDeviceChanged( std::ostringstream os; os << std::endl << "[FwupdClient] onDeviceChanged" << std::endl; Utils::append_properties(device, os); - spdlog::info(os.str()); + LOG_INFO(os.str()); } void FwupdClient::onDeviceRequest( @@ -67,5 +68,5 @@ void FwupdClient::onDeviceRequest( std::ostringstream os; os << std::endl << "[FwupdClient] onDeviceRequest" << std::endl; Utils::append_properties(request, os); - spdlog::info(os.str()); + LOG_INFO(os.str()); } diff --git a/src/fwupd/main.cc b/src/fwupd/main.cc index 09d536b..9ed900c 100644 --- a/src/fwupd/main.cc +++ b/src/fwupd/main.cc @@ -14,6 +14,7 @@ #include "fwupd_client.h" +#include "../utils/logging.h" #include "../utils/utils.h" int main() { @@ -43,24 +44,24 @@ int main() { Utils::print_changed_properties( sdbus::InterfaceName(FwupdClient::INTERFACE_NAME), properties, {}); } catch (const sdbus::Error& e) { - spdlog::warn("Failed to get fwupd properties: {}", e.what()); + LOG_WARN("Failed to get fwupd properties: {}", e.what()); } } // Get and print all devices try { const auto devices = client.GetDevices(); - spdlog::info("Found {} device(s)", devices.size()); + LOG_INFO("Found {} device(s)", devices.size()); for (size_t i = 0; i < devices.size(); ++i) { std::ostringstream os; os << std::endl << "Device " << (i + 1) << " of " << devices.size() << std::endl; Utils::append_properties(devices[i], os); - spdlog::info(os.str()); + LOG_INFO(os.str()); } } catch (const sdbus::Error& e) { - spdlog::error("Failed to get devices: {}", e.what()); + LOG_ERROR("Failed to get devices: {}", e.what()); } connection->leaveEventLoop(); diff --git a/src/geoclue2/main.cc b/src/geoclue2/main.cc index ebe2240..308d7b4 100644 --- a/src/geoclue2/main.cc +++ b/src/geoclue2/main.cc @@ -14,35 +14,53 @@ #include +#include "../utils/signal_handler.h" #include "geoclue2_manager.h" int main() { - const auto connection = sdbus::createSystemBusConnection(); - connection->enterEventLoopAsync(); - - const GeoClue2Manager manager( - *connection, [&](const GeoClue2Location& location) { - const auto [Accuracy, Altitude, Description, Heading, Latitude, - Longitude, Speed, Timestamp] = location.Properties(); - spdlog::info("Timestamp: {}.{}", Timestamp.tv_sec, Timestamp.tv_nsec); - spdlog::info("Lat/Long: {}, {}", Latitude, Longitude); - spdlog::info("Heading: {}", Heading); - spdlog::info("Speed: {}", Speed); - spdlog::info("Accuracy: {}", Accuracy); - spdlog::info("Altitude: {}", Altitude); - spdlog::info("Description: {}", Description); - }); - - const auto& client = manager.Client(); - - // `desktop id` must be set for Start to work - client->DesktopId("org.example.geoclue2"); - client->Start(); - - using namespace std::chrono_literals; - std::this_thread::sleep_for(30000ms); - manager.Client()->Stop(); - connection->leaveEventLoop(); - - return 0; + try { + installSignalHandlers(); + + const auto connection = sdbus::createSystemBusConnection(); + connection->enterEventLoopAsync(); + + const GeoClue2Manager manager( + *connection, [&](const GeoClue2Location& location) { + const auto [Accuracy, Altitude, Description, Heading, Latitude, + Longitude, Speed, Timestamp] = location.Properties(); + LOG_INFO("Timestamp: {}.{}", Timestamp.tv_sec, Timestamp.tv_nsec); + LOG_INFO("Lat/Long: {}, {}", Latitude, Longitude); + LOG_INFO("Heading: {}", Heading); + LOG_INFO("Speed: {}", Speed); + LOG_INFO("Accuracy: {}", Accuracy); + LOG_INFO("Altitude: {}", Altitude); + LOG_INFO("Description: {}", Description); + }); + + const auto& client = manager.Client(); + + // `desktop id` must be set for Start to work + client->DesktopId("org.example.geoclue2"); + client->Start(); + + LOG_INFO("Geoclue2 monitor daemon running - Press Ctrl+C to exit"); + + auto result = monitorLoop(*connection); + + if (result) { + LOG_ERROR("Exiting due to: {}", *result); + } else { + LOG_INFO("Shutting down..."); + } + + connection->leaveEventLoop(); + return result ? 1 : 0; + + } catch (const sdbus::Error& e) { + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); + return 1; + } catch (const std::exception& e) { + LOG_ERROR("Exception: {}", e.what()); + return 1; + } } diff --git a/src/hostname1/hostname1_client.cc b/src/hostname1/hostname1_client.cc index 6eeb057..483d10f 100644 --- a/src/hostname1/hostname1_client.cc +++ b/src/hostname1/hostname1_client.cc @@ -16,6 +16,7 @@ #include +#include "../utils/logging.h" #include "../utils/utils.h" Hostname1Client::Hostname1Client(sdbus::IConnection& connection) @@ -185,7 +186,7 @@ void Hostname1Client::printHostname1() const { } os << std::endl; } - spdlog::info("\n{}", os.str()); + LOG_INFO("\n{}", os.str()); } void Hostname1Client::printHostname1(const Hostname1& val) { @@ -269,5 +270,5 @@ void Hostname1Client::printHostname1(const Hostname1& val) { if (val.BootID.has_value()) { os << "\tBootID: " << val.BootID.value() << std::endl; } - spdlog::info("\n{}", os.str()); + LOG_INFO("\n{}", os.str()); } \ No newline at end of file diff --git a/src/hostname1/main.cc b/src/hostname1/main.cc index e6b707e..de2b5b2 100644 --- a/src/hostname1/main.cc +++ b/src/hostname1/main.cc @@ -25,15 +25,14 @@ int main() { std::promise> promise; auto future = promise.get_future(); - client.GetAllAsync( - Hostname1Client::INTERFACE_NAME, - [&](std::optional error, - std::map values) { - if (!error) - promise.set_value(std::move(values)); - else - promise.set_exception(std::make_exception_ptr(*std::move(error))); - }); + client.GetAllAsync(Hostname1Client::INTERFACE_NAME, + [&](std::optional error, + std::map values) { + if (!error) + promise.set_value(std::move(values)); + else + promise.set_exception(std::make_exception_ptr(*error)); + }); const auto properties = future.get(); client.updateHostname1(properties); diff --git a/src/locale1/locale1_client.cc b/src/locale1/locale1_client.cc index a5c144d..caf46a0 100644 --- a/src/locale1/locale1_client.cc +++ b/src/locale1/locale1_client.cc @@ -14,6 +14,7 @@ #include "locale1_client.h" +#include "../utils/logging.h" #include "../utils/utils.h" Locale1Client::Locale1Client(sdbus::IConnection& connection) @@ -84,5 +85,5 @@ void Locale1Client::printLocale1() const { << std::endl; } - spdlog::info("\n{}", os.str()); + LOG_INFO("\n{}", os.str()); } \ No newline at end of file diff --git a/src/locale1/main.cc b/src/locale1/main.cc index eef0598..acd7a38 100644 --- a/src/locale1/main.cc +++ b/src/locale1/main.cc @@ -23,15 +23,14 @@ int main() { std::promise> promise; auto future = promise.get_future(); - client.GetAllAsync( - Locale1Client::INTERFACE_NAME, - [&](std::optional error, - std::map values) { - if (!error) - promise.set_value(std::move(values)); - else - promise.set_exception(std::make_exception_ptr(*std::move(error))); - }); + client.GetAllAsync(Locale1Client::INTERFACE_NAME, + [&](std::optional error, + std::map values) { + if (!error) + promise.set_value(std::move(values)); + else + promise.set_exception(std::make_exception_ptr(*error)); + }); const auto properties = future.get(); client.updateLocale1(properties); diff --git a/src/login1/login1_manager_client.cc b/src/login1/login1_manager_client.cc index dc09b87..1472e56 100644 --- a/src/login1/login1_manager_client.cc +++ b/src/login1/login1_manager_client.cc @@ -50,8 +50,8 @@ Login1ManagerClient::Login1ManagerClient(sdbus::IConnection& connection) onUserNew(id, path); } } else - spdlog::error("[{}] {} - {}", Manager_proxy::INTERFACE_NAME, - error->getName(), error->getMessage()); + LOG_ERROR("[{}] {} - {}", Manager_proxy::INTERFACE_NAME, + error->getName(), error->getMessage()); }); registerProxy(); } @@ -88,7 +88,7 @@ void Login1ManagerClient::onPropertiesChanged( void Login1ManagerClient::onSessionNew(const std::string& session_id, const sdbus::ObjectPath& object_path) { - spdlog::info("onSessionNew: {}: {}", session_id, object_path); + LOG_INFO("onSessionNew: {}: {}", session_id, object_path); if (!sessions_.contains(object_path)) { sessions_[object_path] = std::make_unique( getProxy().getConnection(), object_path); @@ -105,7 +105,7 @@ void Login1ManagerClient::onSessionRemoved( os << "SESSION REMOVED: " << object_path << std::endl; os << " Session ID: " << session_id << std::endl; os << "========================================" << std::endl; - spdlog::info(os.str()); + LOG_INFO(os.str()); if (sessions_.contains(object_path)) { sessions_[object_path].reset(); sessions_.erase(object_path); @@ -114,7 +114,7 @@ void Login1ManagerClient::onSessionRemoved( void Login1ManagerClient::onUserNew(const uint32_t& uid, const sdbus::ObjectPath& object_path) { - spdlog::info("onUserNew: {}: {}", uid, object_path); + LOG_INFO("onUserNew: {}: {}", uid, object_path); if (!users_.contains(object_path)) { users_[object_path] = std::make_unique(getProxy().getConnection(), object_path); @@ -130,7 +130,7 @@ void Login1ManagerClient::onUserRemoved(const uint32_t& uid, os << "USER REMOVED: " << object_path << std::endl; os << " UID: " << uid << std::endl; os << "========================================" << std::endl; - spdlog::info(os.str()); + LOG_INFO(os.str()); if (users_.contains(object_path)) { users_[object_path].reset(); users_.erase(object_path); @@ -139,7 +139,7 @@ void Login1ManagerClient::onUserRemoved(const uint32_t& uid, void Login1ManagerClient::onSeatNew(const std::string& seat_id, const sdbus::ObjectPath& object_path) { - spdlog::info("onSeatNew: {}: {}", seat_id, object_path); + LOG_INFO("onSeatNew: {}: {}", seat_id, object_path); if (!seats_.contains(object_path)) { seats_[object_path] = std::make_unique(getProxy().getConnection(), object_path); @@ -155,7 +155,7 @@ void Login1ManagerClient::onSeatRemoved(const std::string& seat_id, os << "SEAT REMOVED: " << object_path << std::endl; os << " Seat ID: " << seat_id << std::endl; os << "========================================" << std::endl; - spdlog::info(os.str()); + LOG_INFO(os.str()); if (seats_.contains(object_path)) { seats_[object_path].reset(); seats_.erase(object_path); @@ -163,19 +163,19 @@ void Login1ManagerClient::onSeatRemoved(const std::string& seat_id, } void Login1ManagerClient::onPrepareForShutdown(const bool& start) { - spdlog::info("onPrepareForShutdown: {}", start); + LOG_INFO("onPrepareForShutdown: {}", start); } void Login1ManagerClient::onPrepareForShutdownWithMetadata( const bool& start, const std::map& metadata) { - spdlog::info("onPrepareForShutdownWithMetadata: {}", start); + LOG_INFO("onPrepareForShutdownWithMetadata: {}", start); std::ostringstream os; os << std::endl; Utils::append_properties(metadata, os); - spdlog::info(os.str()); + LOG_INFO(os.str()); } void Login1ManagerClient::onPrepareForSleep(const bool& start) { - spdlog::info("onPrepareForSleep: {}", start); + LOG_INFO("onPrepareForSleep: {}", start); } diff --git a/src/login1/login1_seat.h b/src/login1/login1_seat.h index 2d8cfc7..14556a2 100644 --- a/src/login1/login1_seat.h +++ b/src/login1/login1_seat.h @@ -16,6 +16,7 @@ #define SRC_LOGIN1_LOGIN1_SEAT_H #include "../proxy/org/freedesktop/login1/Seat/seat_proxy.h" +#include "../utils/logging.h" #include "../utils/utils.h" class Login1Seat final @@ -38,8 +39,8 @@ class Login1Seat final onPropertiesChanged( sdbus::InterfaceName(Seat_proxy::INTERFACE_NAME), values, {}); } else - spdlog::error("login1.Seat: {} - {}", error->getName(), - error->getMessage()); + LOG_ERROR("login1.Seat: {} - {}", error->getName(), + error->getMessage()); }); } } @@ -56,10 +57,10 @@ class Login1Seat final os << "========================================" << std::endl; Utils::append_properties(props, os); os << "========================================" << std::endl; - spdlog::info(os.str()); + LOG_INFO(os.str()); } catch (const sdbus::Error& e) { - spdlog::error("Failed to get seat properties for {}: {} - {}", - object_path_, e.getName(), e.getMessage()); + LOG_ERROR("Failed to get seat properties for {}: {} - {}", object_path_, + e.getName(), e.getMessage()); } } diff --git a/src/login1/login1_session.h b/src/login1/login1_session.h index f2f1dee..9b2120e 100644 --- a/src/login1/login1_session.h +++ b/src/login1/login1_session.h @@ -16,6 +16,7 @@ #define SRC_LOGIN1_LOGIN1_SESSION_H #include "../proxy/org/freedesktop/login1/Session/session_proxy.h" +#include "../utils/logging.h" #include "../utils/utils.h" class Login1Session final @@ -39,8 +40,8 @@ class Login1Session final sdbus::InterfaceName(Session_proxy::INTERFACE_NAME), values, {}); } else - spdlog::error("login1.Session: {} - {}", error->getName(), - error->getMessage()); + LOG_ERROR("login1.Session: {} - {}", error->getName(), + error->getMessage()); }); } } @@ -57,10 +58,10 @@ class Login1Session final os << "========================================" << std::endl; Utils::append_properties(props, os); os << "========================================" << std::endl; - spdlog::info(os.str()); + LOG_INFO(os.str()); } catch (const sdbus::Error& e) { - spdlog::error("Failed to get session properties for {}: {} - {}", - object_path_, e.getName(), e.getMessage()); + LOG_ERROR("Failed to get session properties for {}: {} - {}", + object_path_, e.getName(), e.getMessage()); } } @@ -81,20 +82,20 @@ class Login1Session final void onPauseDevice(const uint32_t& major, const uint32_t& minor, const std::string& type) override { - spdlog::info("[Login1Session] onPauseDevice: major={}, minor={}, type={}", - major, minor, type); + LOG_INFO("[Login1Session] onPauseDevice: major={}, minor={}, type={}", + major, minor, type); } void onResumeDevice(const uint32_t& major, const uint32_t& minor, const sdbus::UnixFd& fd) override { - spdlog::info("[Login1Session] onResumeDevice: major={}, minor={}, fd={}", - major, minor, fd.get()); + LOG_INFO("[Login1Session] onResumeDevice: major={}, minor={}, fd={}", major, + minor, fd.get()); } - void onLock() override { spdlog::info("[Login1Session] onLock"); } + void onLock() override { LOG_INFO("[Login1Session] onLock"); } - void onUnlock() override { spdlog::info("[Login1Session] onUnlock"); } + void onUnlock() override { LOG_INFO("[Login1Session] onUnlock"); } }; #endif // SRC_LOGIN1_LOGIN1_SESSION_H diff --git a/src/login1/login1_user.h b/src/login1/login1_user.h index f775ca1..2fabdda 100644 --- a/src/login1/login1_user.h +++ b/src/login1/login1_user.h @@ -38,8 +38,8 @@ class Login1User final onPropertiesChanged( sdbus::InterfaceName(User_proxy::INTERFACE_NAME), values, {}); } else - spdlog::error("login1.User: {} - {}", error->getName(), - error->getMessage()); + LOG_ERROR("login1.User: {} - {}", error->getName(), + error->getMessage()); }); } } @@ -56,10 +56,10 @@ class Login1User final os << "========================================" << std::endl; Utils::append_properties(props, os); os << "========================================" << std::endl; - spdlog::info(os.str()); + LOG_INFO(os.str()); } catch (const sdbus::Error& e) { - spdlog::error("Failed to get user properties for {}: {} - {}", - object_path_, e.getName(), e.getMessage()); + LOG_ERROR("Failed to get user properties for {}: {} - {}", object_path_, + e.getName(), e.getMessage()); } } diff --git a/src/login1/main.cc b/src/login1/main.cc index 9b4802f..8eba922 100644 --- a/src/login1/main.cc +++ b/src/login1/main.cc @@ -12,17 +12,37 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "../utils/signal_handler.h" #include "login1_manager_client.h" int main() { - const auto connection = sdbus::createSystemBusConnection(); - connection->enterEventLoopAsync(); + try { + installSignalHandlers(); - Login1ManagerClient client(*connection); + const auto connection = sdbus::createSystemBusConnection(); + connection->enterEventLoopAsync(); - using namespace std::chrono_literals; - std::this_thread::sleep_for(120000ms); - connection->leaveEventLoop(); + Login1ManagerClient client(*connection); - return 0; + LOG_INFO("Login1 client running - Press Ctrl+C to exit"); + + // Monitor loop with shared connection health timing defaults + auto result = monitorLoop(*connection); + + if (result) { + LOG_ERROR("Exiting due to: {}", *result); + } else { + LOG_INFO("Shutting down..."); + } + + connection->leaveEventLoop(); + return result ? 1 : 0; + + } catch (const sdbus::Error& e) { + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); + return 1; + } catch (const std::exception& e) { + LOG_ERROR("Exception: {}", e.what()); + return 1; + } } diff --git a/src/network1/main.cc b/src/network1/main.cc index a671f2e..47533c5 100644 --- a/src/network1/main.cc +++ b/src/network1/main.cc @@ -1,17 +1,35 @@ +#include "../utils/signal_handler.h" #include "network1_client.h" #include -#include int main() { - const auto connection = sdbus::createSystemBusConnection(); - connection->enterEventLoopAsync(); + try { + installSignalHandlers(); - Network1ManagerClient client(*connection); + const auto connection = sdbus::createSystemBusConnection(); + connection->enterEventLoopAsync(); - using namespace std::chrono_literals; - std::this_thread::sleep_for(2s); // allow async enumeration + Network1ManagerClient network_manager(*connection); - connection->leaveEventLoop(); - return 0; + LOG_INFO("network1 monitor daemon running - Press Ctrl+C to exit"); + + auto result = monitorLoop(*connection); + + if (result) { + LOG_ERROR("Exiting due to: {}", *result); + } else { + LOG_INFO("Shutting down..."); + } + + connection->leaveEventLoop(); + return result ? 1 : 0; + + } catch (const sdbus::Error& e) { + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); + return 1; + } catch (const std::exception& e) { + LOG_ERROR("Exception: {}", e.what()); + return 1; + } } \ No newline at end of file diff --git a/src/network1/network1_client.cc b/src/network1/network1_client.cc index b466121..a2948d8 100644 --- a/src/network1/network1_client.cc +++ b/src/network1/network1_client.cc @@ -4,6 +4,8 @@ #include "network1_client.h" +#include "../utils/logging.h" + Network1ManagerClient::Network1ManagerClient(sdbus::IConnection& connection) : ProxyInterfaces{connection, sdbus::ServiceName(SERVICE_NAME), sdbus::ObjectPath(OBJECT_PATH)}, @@ -14,8 +16,8 @@ Network1ManagerClient::Network1ManagerClient(sdbus::IConnection& connection) [this](std::optional error, const std::map& values) { if (error) { - spdlog::warn("network1.Manager GetAllAsync failed: {} - {}", - error->getName(), error->getMessage()); + LOG_WARN("network1.Manager GetAllAsync failed: {} - {}", + error->getName(), error->getMessage()); } else { Utils::print_changed_properties( sdbus::InterfaceName(Manager_proxy::INTERFACE_NAME), values, {}); @@ -46,10 +48,10 @@ void Network1ManagerClient::enumerateLinks() { LinkInfo info{entry.get<0>(), entry.get<1>(), entry.get<2>()}; links_.push_back(info); } - spdlog::info("[network1] Found {} link(s)", links_.size()); + LOG_INFO("[network1] Found {} link(s)", links_.size()); for (const auto& [ifindex, name, path] : links_) { - spdlog::info(" ifindex={} name={} path={}", ifindex, name, - static_cast(path)); + LOG_INFO(" ifindex={} name={} path={}", ifindex, name, + static_cast(path)); const auto linkProxy = sdbus::createProxy( connection_, sdbus::ServiceName(SERVICE_NAME), path); std::string description; @@ -59,7 +61,7 @@ void Network1ManagerClient::enumerateLinks() { // Parse JSON description using simd-json and log a compact summary std::string parsedSummary = Utils::parseDescriptionJson(description); - spdlog::info("\n{}", parsedSummary); + LOG_INFO("\n{}", parsedSummary); auto props = linkProxy->getAllProperties().onInterface( "org.freedesktop.network1.Link"); @@ -67,6 +69,6 @@ void Network1ManagerClient::enumerateLinks() { linkProxy->unregister(); } } catch (const sdbus::Error& e) { - spdlog::error("ListLinks failed: {} - {}", e.getName(), e.getMessage()); + LOG_ERROR("ListLinks failed: {} - {}", e.getName(), e.getMessage()); } } \ No newline at end of file diff --git a/src/networkmanager/main.cc b/src/networkmanager/main.cc index 78b74ee..d257a34 100644 --- a/src/networkmanager/main.cc +++ b/src/networkmanager/main.cc @@ -12,17 +12,36 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "../utils/signal_handler.h" #include "networkmanager_client.h" int main() { - const auto connection = sdbus::createSystemBusConnection(); - connection->enterEventLoopAsync(); + try { + installSignalHandlers(); - NetworkManagerClient client(*connection); + const auto connection = sdbus::createSystemBusConnection(); + connection->enterEventLoopAsync(); - using namespace std::chrono_literals; - std::this_thread::sleep_for(120000ms); - connection->leaveEventLoop(); + NetworkManagerClient client(*connection); - return 0; + LOG_INFO("NetworkManager monitor daemon running - Press Ctrl+C to exit"); + + auto result = monitorLoop(*connection); + + if (result) { + LOG_ERROR("Exiting due to: {}", *result); + } else { + LOG_INFO("Shutting down..."); + } + + connection->leaveEventLoop(); + return result ? 1 : 0; + + } catch (const sdbus::Error& e) { + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); + return 1; + } catch (const std::exception& e) { + LOG_ERROR("Exception: {}", e.what()); + return 1; + } } diff --git a/src/networkmanager/networkmanager_client.cc b/src/networkmanager/networkmanager_client.cc index 1282983..1b0741d 100644 --- a/src/networkmanager/networkmanager_client.cc +++ b/src/networkmanager/networkmanager_client.cc @@ -14,6 +14,7 @@ #include "networkmanager_client.h" +#include "../utils/logging.h" #include "../utils/utils.h" NetworkManagerClient::NetworkManagerClient(sdbus::IConnection& connection) @@ -40,7 +41,7 @@ void NetworkManagerClient::onInterfacesAdded( os << "[" << objectPath << "] Add - " << interface << std::endl; Utils::append_properties(properties, os); } - spdlog::info(os.str()); + LOG_INFO(os.str()); } void NetworkManagerClient::onInterfacesRemoved( @@ -54,7 +55,7 @@ void NetworkManagerClient::onInterfacesRemoved( os << std::endl; } } - spdlog::info(os.str()); + LOG_INFO(os.str()); } void NetworkManagerClient::onPropertiesChanged( @@ -63,22 +64,22 @@ void NetworkManagerClient::onPropertiesChanged( os << std::endl; os << "NetworkManagerClient Properties changed" << std::endl; Utils::append_properties(properties, os); - spdlog::info(os.str()); + LOG_INFO(os.str()); } void NetworkManagerClient::onCheckPermissions() { - spdlog::info("NetworkManagerClient::onCheckPermissions()"); + LOG_INFO("NetworkManagerClient::onCheckPermissions()"); } void NetworkManagerClient::onStateChanged(const uint32_t& state) { - spdlog::info("NetworkManagerClient::onStateChanged: {}", state); + LOG_INFO("NetworkManagerClient::onStateChanged: {}", state); } void NetworkManagerClient::onDeviceAdded(const sdbus::ObjectPath& device_path) { - spdlog::info("NetworkManagerClient::onDeviceAdded: {}", device_path); + LOG_INFO("NetworkManagerClient::onDeviceAdded: {}", device_path); } void NetworkManagerClient::onDeviceRemoved( const sdbus::ObjectPath& device_path) { - spdlog::info("NetworkManagerClient::onDeviceRemoved: {}", device_path); + LOG_INFO("NetworkManagerClient::onDeviceRemoved: {}", device_path); } diff --git a/src/packagekit/main.cc b/src/packagekit/main.cc index e4b8b41..e50354d 100644 --- a/src/packagekit/main.cc +++ b/src/packagekit/main.cc @@ -13,17 +13,16 @@ // limitations under the License. #include "packagekit_client.h" -#include "packagekit_transaction.h" +#include "../utils/logging.h" #include "../utils/utils.h" int main() { const auto connection = sdbus::createSystemBusConnection(); connection->enterEventLoopAsync(); - PackageKitClient client(*connection); - { + PackageKitClient client(*connection); std::promise> promise; auto future = promise.get_future(); @@ -42,7 +41,7 @@ int main() { Utils::print_changed_properties( sdbus::InterfaceName(PackageKitClient::INTERFACE_NAME), properties, {}); auto state = client.GetDaemonState(); - spdlog::info("Daemon {}", state); + LOG_INFO("Daemon {}", state); } connection->leaveEventLoop(); diff --git a/src/packagekit/packagekit_client.cc b/src/packagekit/packagekit_client.cc index 113c2f6..3e4b293 100644 --- a/src/packagekit/packagekit_client.cc +++ b/src/packagekit/packagekit_client.cc @@ -1,6 +1,7 @@ #include "packagekit_client.h" +#include "../utils/logging.h" #include "../utils/utils.h" PackageKitClient::PackageKitClient(sdbus::IConnection& connection) @@ -23,19 +24,19 @@ void PackageKitClient::onTransactionListChanged( for (const auto& transaction : transactions) { os << transaction << std::endl; } - spdlog::info(os.str()); + LOG_INFO(os.str()); } void PackageKitClient::onRestartSchedule() { - spdlog::info("onRestartSchedule"); + LOG_INFO("onRestartSchedule"); } void PackageKitClient::onRepoListChanged() { - spdlog::info("onRepoListChanged"); + LOG_INFO("onRepoListChanged"); } void PackageKitClient::onUpdatesChanged() { - spdlog::info("onUpdatesChanged"); + LOG_INFO("onUpdatesChanged"); } void PackageKitClient::onPropertiesChanged( diff --git a/src/packagekit/packagekit_transaction.cc b/src/packagekit/packagekit_transaction.cc index 667fde7..5d6b9b0 100644 --- a/src/packagekit/packagekit_transaction.cc +++ b/src/packagekit/packagekit_transaction.cc @@ -1,6 +1,7 @@ #include "packagekit_transaction.h" +#include "../utils/logging.h" #include "../utils/utils.h" PackageKitTransaction::PackageKitTransaction( @@ -29,45 +30,45 @@ void PackageKitTransaction::onCategory(const std::string& parent_id, const std::string& name, const std::string& summary, const std::string& icon) { - spdlog::info("PackageKitTransaction::onCategory"); + LOG_INFO("PackageKitTransaction::onCategory"); } void PackageKitTransaction::onDetails( const std::map& data) { - spdlog::info("PackageKitTransaction::onDetails"); + LOG_INFO("PackageKitTransaction::onDetails"); } void PackageKitTransaction::onErrorCode(const uint32_t& code, const std::string& details) { - spdlog::info("PackageKitTransaction::onErrorCode"); + LOG_INFO("PackageKitTransaction::onErrorCode"); } void PackageKitTransaction::onFiles(const std::string& package_id, const std::vector& file_list) { - spdlog::info("PackageKitTransaction::onFiles"); + LOG_INFO("PackageKitTransaction::onFiles"); } void PackageKitTransaction::onFinished(const uint32_t& exit, const uint32_t& runtime) { - spdlog::info("PackageKitTransaction::onFinished"); + LOG_INFO("PackageKitTransaction::onFinished"); } void PackageKitTransaction::onPackage(const uint32_t& info, const std::string& package_id, const std::string& summary) { - spdlog::info("PackageKitTransaction::onPackage"); + LOG_INFO("PackageKitTransaction::onPackage"); } void PackageKitTransaction::onPackages( const std::vector>& packages) { - spdlog::info("PackageKitTransaction::onPackages"); + LOG_INFO("PackageKitTransaction::onPackages"); } void PackageKitTransaction::onRepoDetail(const std::string& repo_id, const std::string& description, const bool& enabled) { - spdlog::info("PackageKitTransaction::onRepoDetail"); + LOG_INFO("PackageKitTransaction::onRepoDetail"); } void PackageKitTransaction::onRepoSignatureRequired( @@ -79,7 +80,7 @@ void PackageKitTransaction::onRepoSignatureRequired( const std::string& key_fingerprint, const std::string& key_timestamp, const uint32_t& type) { - spdlog::info("PackageKitTransaction::onRepoSignatureRequired"); + LOG_INFO("PackageKitTransaction::onRepoSignatureRequired"); } void PackageKitTransaction::onEulaRequired( @@ -87,19 +88,19 @@ void PackageKitTransaction::onEulaRequired( const std::string& package_id, const std::string& vendor_name, const std::string& license_agreement) { - spdlog::info("PackageKitTransaction::onEulaRequired"); + LOG_INFO("PackageKitTransaction::onEulaRequired"); } void PackageKitTransaction::onMediaChangeRequired( const uint32_t& media_type, const std::string& media_id, const std::string& media_text) { - spdlog::info("PackageKitTransaction::onMediaChangeRequired"); + LOG_INFO("PackageKitTransaction::onMediaChangeRequired"); } void PackageKitTransaction::onRequireRestart(const uint32_t& type, const std::string& package_id) { - spdlog::info("PackageKitTransaction::onRequireRestart"); + LOG_INFO("PackageKitTransaction::onRequireRestart"); } void PackageKitTransaction::onTransaction(const sdbus::ObjectPath& object_path, @@ -110,7 +111,7 @@ void PackageKitTransaction::onTransaction(const sdbus::ObjectPath& object_path, const std::string& data, const uint32_t& uid, const std::string& cmdline) { - spdlog::info("PackageKitTransaction::onTransaction"); + LOG_INFO("PackageKitTransaction::onTransaction"); } void PackageKitTransaction::onUpdateDetail( @@ -126,7 +127,7 @@ void PackageKitTransaction::onUpdateDetail( const uint32_t& state, const std::string& issued, const std::string& updated) { - spdlog::info("PackageKitTransaction::onUpdateDetail"); + LOG_INFO("PackageKitTransaction::onUpdateDetail"); } void PackageKitTransaction::onUpdateDetails( @@ -142,23 +143,23 @@ void PackageKitTransaction::onUpdateDetails( uint32_t, std::string, std::string>>& details) { - spdlog::info("PackageKitTransaction::onUpdateDetails"); + LOG_INFO("PackageKitTransaction::onUpdateDetails"); } void PackageKitTransaction::onDistroUpgrade(const uint32_t& type, const std::string& name, const std::string& summary) { - spdlog::info("PackageKitTransaction::onDistroUpgrade: {}, {}, {}", type, name, - summary); + LOG_INFO("PackageKitTransaction::onDistroUpgrade: {}, {}, {}", type, name, + summary); } void PackageKitTransaction::onItemProgress(const std::string& id, const uint32_t& status, const uint32_t& percentage) { - spdlog::info("PackageKitTransaction::onItemProgress: {}, {}, {}", id, status, - percentage); + LOG_INFO("PackageKitTransaction::onItemProgress: {}, {}, {}", id, status, + percentage); } void PackageKitTransaction::onDestroy() { - spdlog::info("PackageKitTransaction::onDestroy"); + LOG_INFO("PackageKitTransaction::onDestroy"); } diff --git a/src/realtimekit1/main.cc b/src/realtimekit1/main.cc index 89a813b..107504f 100644 --- a/src/realtimekit1/main.cc +++ b/src/realtimekit1/main.cc @@ -12,27 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "../utils/logging.h" #include "realtimekit1_manager_client.h" #include -#include extern void exerciseRealtime(RealtimeKit1ManagerClient&); int main() { try { - auto connection = sdbus::createSystemBusConnection(); - auto proxy = sdbus::createProxy( + const auto connection = sdbus::createSystemBusConnection(); + const auto proxy = sdbus::createProxy( *connection, sdbus::ServiceName("org.freedesktop.RealtimeKit1"), sdbus::ObjectPath("/org/freedesktop/RealtimeKit1")); RealtimeKit1ManagerClient client(*proxy); exerciseRealtime(client); - spdlog::info("RealtimeKit1 example complete"); + LOG_INFO("RealtimeKit1 example complete"); } catch (const sdbus::Error& e) { - spdlog::error("D-Bus error: {} - {}", e.getName(), e.getMessage()); + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); return 1; } catch (const std::exception& e) { - spdlog::error("Exception: {}", e.what()); + LOG_ERROR("Exception: {}", e.what()); return 1; } return 0; diff --git a/src/realtimekit1/realtimekit1_manager_client.cc b/src/realtimekit1/realtimekit1_manager_client.cc index a6bc678..ba104ce 100644 --- a/src/realtimekit1/realtimekit1_manager_client.cc +++ b/src/realtimekit1/realtimekit1_manager_client.cc @@ -13,14 +13,14 @@ // limitations under the License. #include "realtimekit1_manager_client.h" +#include "../utils/logging.h" void exerciseRealtime(RealtimeKit1ManagerClient& client) { client.dumpProperties(); client.tryHighPriority(-5); // illustrative nice level - auto maxRT = client.MaxRealtimePriority(); - if (maxRT > 1) { + if (const auto maxRT = client.MaxRealtimePriority(); maxRT > 1) { client.tryRealtime(static_cast(maxRT / 2)); } else { - spdlog::info("MaxRealtimePriority too low to attempt realtime scheduling"); + LOG_INFO("MaxRealtimePriority too low to attempt realtime scheduling"); } } diff --git a/src/realtimekit1/realtimekit1_manager_client.h b/src/realtimekit1/realtimekit1_manager_client.h index 28fb595..b44d306 100644 --- a/src/realtimekit1/realtimekit1_manager_client.h +++ b/src/realtimekit1/realtimekit1_manager_client.h @@ -18,13 +18,11 @@ #include "../proxy/org/freedesktop/RealtimeKit1/realtime_kit1_proxy.h" #include -#include -#include -#include #include #include -#include + +#include "../utils/logging.h" class RealtimeKit1ManagerClient : public org::freedesktop::RealtimeKit1_proxy { public: @@ -32,22 +30,21 @@ class RealtimeKit1ManagerClient : public org::freedesktop::RealtimeKit1_proxy { : org::freedesktop::RealtimeKit1_proxy(proxy) {} void dumpProperties() { - spdlog::info("RTTimeUSecMax : {}", RTTimeUSecMax()); - spdlog::info("MaxRealtimePriority : {}", MaxRealtimePriority()); - spdlog::info("MinNiceLevel : {}", MinNiceLevel()); + LOG_INFO("RTTimeUSecMax : {}", RTTimeUSecMax()); + LOG_INFO("MaxRealtimePriority : {}", MaxRealtimePriority()); + LOG_INFO("MinNiceLevel : {}", MinNiceLevel()); } bool tryHighPriority(int32_t niceLevel) { try { auto tid = currentTid(); - spdlog::info("MakeThreadHighPriority thread={} priority={}", tid, - niceLevel); + LOG_INFO("MakeThreadHighPriority thread={} priority={}", tid, niceLevel); MakeThreadHighPriority(tid, niceLevel); - spdlog::info("High priority change succeeded"); + LOG_INFO("High priority change succeeded"); return true; } catch (const sdbus::Error& e) { - spdlog::warn("High priority change failed: {} ({})", e.getName(), - e.getMessage()); + LOG_WARN("High priority change failed: {} ({})", e.getName(), + e.getMessage()); return false; } } @@ -55,14 +52,12 @@ class RealtimeKit1ManagerClient : public org::freedesktop::RealtimeKit1_proxy { bool tryRealtime(uint32_t rtPriority) { try { auto tid = currentTid(); - spdlog::info("MakeThreadRealtime thread={} rtPriority={}", tid, - rtPriority); + LOG_INFO("MakeThreadRealtime thread={} rtPriority={}", tid, rtPriority); MakeThreadRealtime(tid, rtPriority); - spdlog::info("Realtime change succeeded"); + LOG_INFO("Realtime change succeeded"); return true; } catch (const sdbus::Error& e) { - spdlog::warn("Realtime change failed: {} ({})", e.getName(), - e.getMessage()); + LOG_WARN("Realtime change failed: {} ({})", e.getName(), e.getMessage()); return false; } } diff --git a/src/resolve1/main.cc b/src/resolve1/main.cc index 6e054b4..d56f5e7 100644 --- a/src/resolve1/main.cc +++ b/src/resolve1/main.cc @@ -12,17 +12,37 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "../utils/signal_handler.h" #include "resolve1_manager.h" int main() { - const auto connection = sdbus::createSystemBusConnection(); - connection->enterEventLoopAsync(); + try { + installSignalHandlers(); - Resolve1Manager client(*connection); + const auto connection = sdbus::createSystemBusConnection(); + connection->enterEventLoopAsync(); - using namespace std::chrono_literals; - std::this_thread::sleep_for(120000ms); - connection->leaveEventLoop(); + Resolve1Manager manager(*connection); - return 0; + LOG_INFO("Resolved client running - Press Ctrl+C to exit"); + + // Monitor loop with shared connection health timing defaults + auto result = monitorLoop(*connection); + + if (result) { + LOG_ERROR("Exiting due to: {}", *result); + } else { + LOG_INFO("Shutting down..."); + } + + connection->leaveEventLoop(); + return result ? 1 : 0; + + } catch (const sdbus::Error& e) { + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); + return 1; + } catch (const std::exception& e) { + LOG_ERROR("Exception: {}", e.what()); + return 1; + } } diff --git a/src/systemd1/main.cc b/src/systemd1/main.cc index 16bd98e..f15fa5a 100644 --- a/src/systemd1/main.cc +++ b/src/systemd1/main.cc @@ -1,34 +1,35 @@ #include -#include + +#include "../utils/signal_handler.h" #include "systemd1_manager_client.h" int main() { - auto connection = sdbus::createSystemBusConnection(); - connection->enterEventLoopAsync(); - - Systemd1ManagerClient client(*connection); - - // Optional unit start (avoid direct Unit_proxy instantiation; use generic - // proxy) - if (const char* unitPathEnv = std::getenv("SYSTEMD_UNIT_PATH")) { - try { - const auto unitPath = sdbus::ObjectPath(unitPathEnv); - const auto unitProxy = sdbus::createProxy( - *connection, sdbus::ServiceName(Systemd1ManagerClient::SERVICE_NAME), - unitPath); - unitProxy->callMethod("Start") - .onInterface(org::freedesktop::systemd1::Unit_proxy::INTERFACE_NAME) - .withArguments(std::string("replace")); - spdlog::info("Attempted Start on unit {}", unitPathEnv); - } catch (const sdbus::Error& e) { - spdlog::error("Failed to start unit {}: {} - {}", unitPathEnv, - e.getName(), e.getMessage()); + try { + installSignalHandlers(); + + const auto connection = sdbus::createSystemBusConnection(); + connection->enterEventLoopAsync(); + + Systemd1ManagerClient manager(*connection); + + LOG_INFO("Systemd1 monitor daemon running - Press Ctrl+C to exit"); + + auto result = monitorLoop(*connection); + + if (result) { + LOG_ERROR("Exiting due to: {}", *result); + } else { + LOG_INFO("Shutting down..."); } - } - using namespace std::chrono_literals; - std::this_thread::sleep_for(5s); // collect signals + connection->leaveEventLoop(); + return result ? 1 : 0; - connection->leaveEventLoop(); - return 0; + } catch (const sdbus::Error& e) { + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); + return 1; + } catch (const std::exception& e) { + LOG_ERROR("Exception: {}", e.what()); + return 1; + } } \ No newline at end of file diff --git a/src/systemd1/systemd1_manager_client.cc b/src/systemd1/systemd1_manager_client.cc index 730d9fd..3e93b3d 100644 --- a/src/systemd1/systemd1_manager_client.cc +++ b/src/systemd1/systemd1_manager_client.cc @@ -6,6 +6,8 @@ #include +#include "../utils/logging.h" + Systemd1ManagerClient::Systemd1ManagerClient(sdbus::IConnection& connection) : ProxyInterfaces{connection, sdbus::ServiceName(SERVICE_NAME), sdbus::ObjectPath(OBJECT_PATH)} { @@ -15,8 +17,8 @@ Systemd1ManagerClient::Systemd1ManagerClient(sdbus::IConnection& connection) [](std::optional error, const std::map& values) { if (error) { - spdlog::warn("systemd1.Manager GetAllAsync failed: {} - {}", - error->getName(), error->getMessage()); + LOG_WARN("systemd1.Manager GetAllAsync failed: {} - {}", + error->getName(), error->getMessage()); } else { Utils::print_changed_properties( sdbus::InterfaceName(Manager_proxy::INTERFACE_NAME), values, {}); @@ -42,31 +44,31 @@ void Systemd1ManagerClient::onPropertiesChanged( void Systemd1ManagerClient::onUnitNew(const std::string& id, const sdbus::ObjectPath& unit) { activeUnits_.push_back(id); - spdlog::info("[systemd1] UnitNew id={} path={}", id, - static_cast(unit)); + LOG_INFO("[systemd1] UnitNew id={} path={}", id, + static_cast(unit)); } void Systemd1ManagerClient::onUnitRemoved(const std::string& id, const sdbus::ObjectPath& unit) { activeUnits_.erase(std::ranges::remove(activeUnits_, id).begin(), activeUnits_.end()); - spdlog::info("[systemd1] UnitRemoved id={} path={}", id, - static_cast(unit)); + LOG_INFO("[systemd1] UnitRemoved id={} path={}", id, + static_cast(unit)); } void Systemd1ManagerClient::onJobNew(const uint32_t& id, const sdbus::ObjectPath& job, const std::string& unit) { - spdlog::info("[systemd1] JobNew job_id={} job_path={} unit={}", id, - static_cast(job), unit); + LOG_INFO("[systemd1] JobNew job_id={} job_path={} unit={}", id, + static_cast(job), unit); } void Systemd1ManagerClient::onJobRemoved(const uint32_t& id, const sdbus::ObjectPath& job, const std::string& unit, const std::string& result) { - spdlog::info("[systemd1] JobRemoved job_id={} job_path={} unit={} result={}", - id, static_cast(job), unit, result); + LOG_INFO("[systemd1] JobRemoved job_id={} job_path={} unit={} result={}", id, + static_cast(job), unit, result); } void Systemd1ManagerClient::onStartupFinished(const uint64_t& firmware, @@ -75,16 +77,16 @@ void Systemd1ManagerClient::onStartupFinished(const uint64_t& firmware, const uint64_t& initrd, const uint64_t& userspace, const uint64_t& total) { - spdlog::info( + LOG_INFO( "[systemd1] StartupFinished firmware={} loader={} kernel={} initrd={} " "userspace={} total={}", firmware, loader, kernel, initrd, userspace, total); } void Systemd1ManagerClient::onUnitFilesChanged() { - spdlog::info("[systemd1] UnitFilesChanged"); + LOG_INFO("[systemd1] UnitFilesChanged"); } void Systemd1ManagerClient::onReloading(const bool& active) { - spdlog::info("[systemd1] Reloading active={}", active); + LOG_INFO("[systemd1] Reloading active={}", active); } \ No newline at end of file diff --git a/src/timedate1/main.cc b/src/timedate1/main.cc index 85a7377..4e44ef7 100644 --- a/src/timedate1/main.cc +++ b/src/timedate1/main.cc @@ -14,6 +14,7 @@ #include "timedate1_client.h" +#include "../utils/logging.h" #include "../utils/utils.h" int main() { @@ -34,8 +35,7 @@ int main() { if (!error) promises[i].set_value(std::move(values)); else - promises[i].set_exception( - std::make_exception_ptr(std::move(*error))); + promises[i].set_exception(std::make_exception_ptr(*error)); }); } @@ -45,7 +45,7 @@ int main() { client.updateTimedate1(properties); client.printTimedate1(); } catch (const std::exception& e) { - spdlog::error("Error: {}", e.what()); + LOG_ERROR("Error: {}", e.what()); } } diff --git a/src/timedate1/timedate1_client.cc b/src/timedate1/timedate1_client.cc index 86460a0..c820389 100644 --- a/src/timedate1/timedate1_client.cc +++ b/src/timedate1/timedate1_client.cc @@ -16,6 +16,7 @@ #include +#include "../utils/logging.h" #include "../utils/utils.h" Timedate1Client::Timedate1Client(sdbus::IConnection& connection) @@ -82,5 +83,5 @@ void Timedate1Client::printTimedate1() const { os << "\tRTCTimeUSec: " << timedate1_.RTCTimeUSec << std::endl; appendTimeUSecAsDate(timedate1_.RTCTimeUSec, os); - spdlog::info("\n{}", os.str()); + LOG_INFO("\n{}", os.str()); } \ No newline at end of file diff --git a/src/timesync1/main.cc b/src/timesync1/main.cc index d4c3b26..e16a203 100644 --- a/src/timesync1/main.cc +++ b/src/timesync1/main.cc @@ -15,24 +15,24 @@ #include "timesync1_manager_client.h" #include -#include +#include "../utils/logging.h" extern void exerciseTimesync(Timesync1ManagerClient&); int main() { try { - auto connection = sdbus::createSystemBusConnection(); - auto proxy = sdbus::createProxy( + const auto connection = sdbus::createSystemBusConnection(); + const auto proxy = sdbus::createProxy( *connection, sdbus::ServiceName("org.freedesktop.timesync1"), sdbus::ObjectPath("/org/freedesktop/timesync1/Manager")); Timesync1ManagerClient client(*proxy); exerciseTimesync(client); - spdlog::info("timesync1 Manager example complete"); + LOG_INFO("timesync1 Manager example complete"); } catch (const sdbus::Error& e) { - spdlog::error("D-Bus error: {} - {}", e.getName(), e.getMessage()); + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); return 1; } catch (const std::exception& e) { - spdlog::error("Exception: {}", e.what()); + LOG_ERROR("Exception: {}", e.what()); return 1; } return 0; diff --git a/src/timesync1/timesync1_manager_client.h b/src/timesync1/timesync1_manager_client.h index 4c179b5..c019a5b 100644 --- a/src/timesync1/timesync1_manager_client.h +++ b/src/timesync1/timesync1_manager_client.h @@ -18,11 +18,12 @@ #include "../proxy/org/freedesktop/timesync1/manager_proxy.h" #include -#include #include #include +#include "../utils/logging.h" + class Timesync1ManagerClient : public org::freedesktop::timesync1::Manager_proxy { public: @@ -36,38 +37,38 @@ class Timesync1ManagerClient logVector("FallbackNTPServers", FallbackNTPServers()); } void dumpTiming() { - spdlog::info("PollIntervalMinUSec : {}", PollIntervalMinUSec()); - spdlog::info("PollIntervalMaxUSec : {}", PollIntervalMaxUSec()); - spdlog::info("PollIntervalUSec : {}", PollIntervalUSec()); - spdlog::info("RootDistanceMaxUSec : {}", RootDistanceMaxUSec()); - spdlog::info("Frequency : {}", Frequency()); + LOG_INFO("PollIntervalMinUSec : {}", PollIntervalMinUSec()); + LOG_INFO("PollIntervalMaxUSec : {}", PollIntervalMaxUSec()); + LOG_INFO("PollIntervalUSec : {}", PollIntervalUSec()); + LOG_INFO("RootDistanceMaxUSec : {}", RootDistanceMaxUSec()); + LOG_INFO("Frequency : {}", Frequency()); } void dumpServer() { - spdlog::info("ServerName : {}", ServerName()); + LOG_INFO("ServerName : {}", ServerName()); auto addr = ServerAddress(); - spdlog::info("ServerAddress family={} bytes={}", addr.get<0>(), - addr.get<1>().size()); + LOG_INFO("ServerAddress family={} bytes={}", addr.get<0>(), + addr.get<1>().size()); } void setRuntimeServersExample() { std::vector servers{"time1.google.com", "time.cloudflare.com"}; try { - spdlog::info("Setting RuntimeNTPServers..."); + LOG_INFO("Setting RuntimeNTPServers..."); SetRuntimeNTPServers(servers); logVector("Updated RuntimeNTPServers", RuntimeNTPServers()); } catch (const sdbus::Error& e) { - spdlog::warn("SetRuntimeNTPServers failed: {} ({})", e.getName(), - e.getMessage()); + LOG_WARN("SetRuntimeNTPServers failed: {} ({})", e.getName(), + e.getMessage()); } } private: static void logVector(const char* label, const std::vector& v) { if (v.empty()) { - spdlog::info("{}: (empty)", label); + LOG_INFO("{}: (empty)", label); return; } for (const auto& s : v) - spdlog::info("{}: {}", label, s); + LOG_INFO("{}: {}", label, s); } }; diff --git a/src/udisks2/main.cc b/src/udisks2/main.cc index 39b1a08..d896e44 100644 --- a/src/udisks2/main.cc +++ b/src/udisks2/main.cc @@ -12,17 +12,37 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "../utils/signal_handler.h" #include "udisks2_manager.h" int main() { - const auto connection = sdbus::createSystemBusConnection(); - connection->enterEventLoopAsync(); + try { + installSignalHandlers(); - UDisks2Manager manager(*connection); + const auto connection = sdbus::createSystemBusConnection(); + connection->enterEventLoopAsync(); - using namespace std::chrono_literals; - std::this_thread::sleep_for(120000ms); - connection->leaveEventLoop(); + UDisks2Manager manager(*connection); - return 0; + LOG_INFO("UDisks2 client running - Press Ctrl+C to exit"); + + // Monitor loop with shared connection health timing defaults + auto result = monitorLoop(*connection); + + if (result) { + LOG_ERROR("Exiting due to: {}", *result); + } else { + LOG_INFO("Shutting down..."); + } + + connection->leaveEventLoop(); + return result ? 1 : 0; + + } catch (const sdbus::Error& e) { + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); + return 1; + } catch (const std::exception& e) { + LOG_ERROR("Exception: {}", e.what()); + return 1; + } } diff --git a/src/udisks2/udisks2_manager.cc b/src/udisks2/udisks2_manager.cc index e10e157..71df3c4 100644 --- a/src/udisks2/udisks2_manager.cc +++ b/src/udisks2/udisks2_manager.cc @@ -14,9 +14,11 @@ #include "udisks2_manager.h" -#include "../utils/utils.h" #include "udisks2_manager_nvme.h" +#include "../utils/logging.h" +#include "../utils/utils.h" + UDisks2Manager::UDisks2Manager(sdbus::IConnection& connection) : ProxyInterfaces(connection, sdbus::ServiceName(INTERFACE_NAME), @@ -46,7 +48,7 @@ void UDisks2Manager::onInterfacesAdded( std::ostringstream os; os << std::endl; Utils::append_properties(properties, os); - spdlog::info("[{}] Add - {}\n{}", objectPath, interface, os.str()); + LOG_INFO("[{}] Add - {}\n{}", objectPath, interface, os.str()); if ("org.freedesktop.UDisks2.Manager" == interface) { continue; } @@ -102,7 +104,7 @@ void UDisks2Manager::onInterfacesAdded( getProxy().getConnection(), objectPath); } } else { - spdlog::info("not handled interface: {}", interface); + LOG_INFO("not handled interface: {}", interface); } } } @@ -111,6 +113,6 @@ void UDisks2Manager::onInterfacesRemoved( const sdbus::ObjectPath& objectPath, const std::vector& interfaces) { for (const auto& interface : interfaces) { - spdlog::info("[{}] Remove - {}", objectPath, interface); + LOG_INFO("[{}] Remove - {}", objectPath, interface); } } \ No newline at end of file diff --git a/src/udisks2/udisks2_monitor_daemon.cc b/src/udisks2/udisks2_monitor_daemon.cc index 471f19a..6effdf5 100644 --- a/src/udisks2/udisks2_monitor_daemon.cc +++ b/src/udisks2/udisks2_monitor_daemon.cc @@ -28,8 +28,7 @@ #include #include "../proxy/org/freedesktop/UDisks2/Block/block_proxy.h" - -#include +#include "../utils/logging.h" // Forward declarations class RemovableDeviceMonitor; @@ -134,7 +133,7 @@ class BlockDeviceWatcher driveProps = driveProxy->getAllProperties().onInterface(driveInterfaceName); } catch (const std::exception& e) { - spdlog::warn("Failed to get drive properties: {}", e.what()); + LOG_WARN("Failed to get drive properties: {}", e.what()); } // Report the device @@ -144,7 +143,7 @@ class BlockDeviceWatcher } } } catch (const std::exception& e) { - spdlog::debug("Error checking device readiness: {}", e.what()); + LOG_DEBUG("Error checking device readiness: {}", e.what()); } } }; @@ -163,12 +162,12 @@ class RemovableDeviceMonitor final registerProxy(); // Process existing objects - spdlog::info("Scanning for existing removable devices..."); + LOG_INFO("Scanning for existing removable devices..."); for (auto managedObjects = GetManagedObjects(); const auto& [objectPath, interfacesAndProperties] : managedObjects) { onInterfacesAdded(objectPath, interfacesAndProperties); } - spdlog::info("Initial scan complete. Monitoring for new devices..."); + LOG_INFO("Initial scan complete. Monitoring for new devices..."); } ~RemovableDeviceMonitor() { unregisterProxy(); } @@ -233,7 +232,7 @@ class RemovableDeviceMonitor final } if (isRemovable) { - spdlog::info("Detected removable device: {}", objectPath.c_str()); + LOG_INFO("Detected removable device: {}", objectPath.c_str()); // Create a watcher for this device std::lock_guard lock(watchers_mutex_); @@ -243,8 +242,8 @@ class RemovableDeviceMonitor final } } } catch (const std::exception& e) { - spdlog::debug("Error checking drive removability for {}: {}", - objectPath.c_str(), e.what()); + LOG_DEBUG("Error checking drive removability for {}: {}", + objectPath.c_str(), e.what()); } } @@ -257,7 +256,7 @@ class RemovableDeviceMonitor final if (interface == blockInterface) { std::lock_guard lock(watchers_mutex_); if (auto it = watchers_.find(objectPath); it != watchers_.end()) { - spdlog::info("Removable device removed: {}", objectPath.c_str()); + LOG_INFO("Removable device removed: {}", objectPath.c_str()); watchers_.erase(it); } } @@ -282,11 +281,15 @@ std::string byteArrayToString(const std::vector& bytes) { // Helper to format size in human-readable format std::string formatSize(const uint64_t bytes) { - const char* units[] = {"B", "KB", "MB", "GB", "TB"}; + constexpr const char* units[] = {"B", "KB", "MB", "GB", "TB"}; + constexpr int max_unit = std::size(units) - 1; + int unit = 0; auto size = static_cast(bytes); - while (size >= 1024.0 && unit < 4) { + // Prevent division issues with extreme values + // Also ensure we don't exceed available units + while (size >= 1024.0 && unit < max_unit && size / 1024.0 < size) { size /= 1024.0; unit++; } @@ -484,9 +487,9 @@ void printDeviceInformation( int main() { try { - spdlog::info("Starting UDisks2 Removable Device Monitor Daemon"); - spdlog::info("This daemon monitors for removable device insertions"); - spdlog::info("Press Ctrl+C to stop...\n"); + LOG_INFO("Starting UDisks2 Removable Device Monitor Daemon"); + LOG_INFO("This daemon monitors for removable device insertions"); + LOG_INFO("Press Ctrl+C to stop...\n"); // Create the system bus connection const auto connection = sdbus::createSystemBusConnection(); @@ -502,12 +505,12 @@ int main() { // The event loop runs in a separate thread handling D-Bus messages // Note: In a production daemon, implement proper signal handling (SIGTERM, // SIGINT) to gracefully shut down and call connection->leaveEventLoop() - using namespace std::chrono_literals; + constexpr auto kDaemonIdleSleepInterval = std::chrono::seconds(1); while (true) { - std::this_thread::sleep_for(1000ms); + std::this_thread::sleep_for(kDaemonIdleSleepInterval); } } catch (const std::exception& e) { - spdlog::error("Fatal error: {}", e.what()); + LOG_ERROR("Fatal error: {}", e.what()); return 1; } diff --git a/src/upower/main.cc b/src/upower/main.cc index 52ebe2e..c201086 100644 --- a/src/upower/main.cc +++ b/src/upower/main.cc @@ -12,19 +12,38 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "../utils/signal_handler.h" #include "upower_client.h" int main() { - const auto connection = sdbus::createSystemBusConnection(); - connection->enterEventLoopAsync(); + try { + installSignalHandlers(); - // UPowerClient client(*connection, - // "/org/freedesktop/UPower/devices/battery_ps_controller_battery_88o03o4co82o6bo29"); - UPowerClient client(*connection); + const auto connection = sdbus::createSystemBusConnection(); + connection->enterEventLoopAsync(); - using namespace std::chrono_literals; - std::this_thread::sleep_for(120000ms); - connection->leaveEventLoop(); + UPowerClient client( + *connection, + sdbus::ObjectPath("/org/freedesktop/UPower/devices/DisplayDevice")); - return 0; + LOG_INFO("UPower monitor daemon running - Press Ctrl+C to exit"); + + auto result = monitorLoop(*connection); + + if (result) { + LOG_ERROR("Exiting due to: {}", *result); + } else { + LOG_INFO("Shutting down..."); + } + + connection->leaveEventLoop(); + return result ? 1 : 0; + + } catch (const sdbus::Error& e) { + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); + return 1; + } catch (const std::exception& e) { + LOG_ERROR("Exception: {}", e.what()); + return 1; + } } diff --git a/src/upower/upower_client.h b/src/upower/upower_client.h index 5c5b6e3..ba7ddd0 100644 --- a/src/upower/upower_client.h +++ b/src/upower/upower_client.h @@ -18,6 +18,7 @@ #include #include "../proxy/org/freedesktop/UPower/upower_proxy.h" +#include "../utils/logging.h" #include "../utils/utils.h" #include "upower_display_device.h" @@ -59,7 +60,7 @@ class UPowerClient final void onDeviceAdded(const sdbus::ObjectPath& device) override { if (device_filter_.empty() || !device_filter_.empty() && device_filter_ == device) { - spdlog::info("onDeviceAdded: {}", device); + LOG_INFO("onDeviceAdded: {}", device); std::lock_guard lock(devices_mutex_); if (!devices_.contains(device)) { devices_[device] = std::make_shared( @@ -71,7 +72,7 @@ class UPowerClient final void onDeviceRemoved(const sdbus::ObjectPath& device) override { if (device_filter_.empty() || !device_filter_.empty() && device_filter_ == device) { - spdlog::info("onDeviceRemoved: {}", device); + LOG_INFO("onDeviceRemoved: {}", device); std::lock_guard lock(devices_mutex_); if (devices_.contains(device)) { devices_[device].reset(); @@ -84,7 +85,7 @@ class UPowerClient final const sdbus::InterfaceName& interfaceName, const std::map& changedProperties, const std::vector& invalidatedProperties) override { - spdlog::info("onPropertiesChanged: {}", interfaceName); + LOG_INFO("onPropertiesChanged: {}", interfaceName); Utils::print_changed_properties(interfaceName, changedProperties, invalidatedProperties); } diff --git a/src/upower/upower_display_device.cc b/src/upower/upower_display_device.cc index 0c7f968..543bf3d 100644 --- a/src/upower/upower_display_device.cc +++ b/src/upower/upower_display_device.cc @@ -14,8 +14,7 @@ #include "upower_display_device.h" -#include - +#include "../utils/logging.h" #include "../utils/utils.h" UPowerDisplayDevice::UPowerDisplayDevice(sdbus::IConnection& connection, @@ -30,7 +29,7 @@ UPowerDisplayDevice::UPowerDisplayDevice(sdbus::IConnection& connection, UPowerDisplayDevice::onPropertiesChanged( sdbus::InterfaceName("org.freedesktop.UPower.Device"), properties, {}); } catch (const sdbus::Error& e) { - spdlog::error("UPowerDisplayDevice::UPowerDisplayDevice: {}", e.what()); + LOG_ERROR("UPowerDisplayDevice::UPowerDisplayDevice: {}", e.what()); } } diff --git a/src/utils/logging.h b/src/utils/logging.h new file mode 100644 index 0000000..819d0b1 --- /dev/null +++ b/src/utils/logging.h @@ -0,0 +1,218 @@ +// 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_LOGGING_H +#define SRC_UTILS_LOGGING_H + +/** + * @brief Centralized logging header - SINGLE PLACE spdlog.h is included + * + * This header consolidates all logging infrastructure: + * - spdlog.h (the only place it's included from) + * - Logging configuration (environment-based setup) + * - Error reporting macros (standardized logging levels) + * + * All other files should include this header instead of including spdlog + * directly. + * + * Usage: + * #include "src/utils/logging.h" + * + * LOG_INFO("Message"); + * LOG_DEBUG("Debug info"); + * LOG_ERROR("Error occurred"); + */ + +// ============================================================================ +// SINGLE INCLUDE OF SPDLOG - ONLY LOCATION IN CODEBASE +// ============================================================================ +#include +#include +#include +#include +#include + +#include +#include +#include + +// ============================================================================ +// LOGGING CONFIGURATION - Environment-based setup +// ============================================================================ + +namespace logging_config { + +/** + * @brief Get log level from the environment variable or default to info + * @return spdlog::level::level_enum + */ +inline spdlog::level::level_enum getLogLevelFromEnv() { + const char* level_str = std::getenv("LOG_LEVEL"); + if (!level_str) { + return spdlog::level::info; + } + + const std::string level{level_str}; + if (level == "trace") + return spdlog::level::trace; + if (level == "debug") + return spdlog::level::debug; + if (level == "info") + return spdlog::level::info; + if (level == "warn") + return spdlog::level::warn; + if (level == "err") + return spdlog::level::err; + if (level == "critical") + return spdlog::level::critical; + if (level == "off") + return spdlog::level::off; + + return spdlog::level::info; +} + +/** + * @brief Check if file logging is enabled via an environment variable + * @return std::string path to a log file, empty string if disabled + */ +inline std::string getLogFilePathFromEnv() { + const char* log_file = std::getenv("LOG_FILE"); + return log_file ? std::string(log_file) : ""; +} + +/** + * @brief Initialize logging with console and optional file output + * @param logger_name Name of the logger instance + */ +inline void initializeLogging(const std::string& logger_name = "default") { + try { + const auto level = getLogLevelFromEnv(); + auto log_file = getLogFilePathFromEnv(); + const char* pattern_str = std::getenv("LOG_PATTERN"); + const std::string pattern = pattern_str ? std::string(pattern_str) + : "[%Y-%m-%d %H:%M:%S.%e] [%l] %v"; + + const auto console_sink = + std::make_shared(); + console_sink->set_level(level); + + std::vector sinks; + sinks.push_back(console_sink); + + if (!log_file.empty()) { + const auto file_sink = + std::make_shared( + log_file, 10 * 1024 * 1024, 3); + file_sink->set_level(level); + sinks.push_back(file_sink); + } + + const auto logger = std::make_shared( + logger_name, sinks.begin(), sinks.end()); + logger->set_level(level); + logger->set_pattern(pattern); + logger->flush_on(spdlog::level::err); + + spdlog::register_logger(logger); + spdlog::set_default_logger(logger); + + spdlog::info("Logging initialized - Level: {}, File: {}", + spdlog::level::to_string_view(level), + log_file.empty() ? "console only" : log_file); + } catch (const spdlog::spdlog_ex& ex) { + spdlog::error("Log initialization failed: {}", ex.what()); + } +} + +// Helpers for callers that need runtime level/flush configuration without +// referencing spdlog symbols directly. +inline void setLevelDebug() { + spdlog::set_level(spdlog::level::debug); +} +inline void setLevelInfo() { + spdlog::set_level(spdlog::level::info); +} +inline void setFlushInterval(const std::chrono::seconds interval) { + spdlog::flush_every(interval); +} + +} // namespace logging_config + +// ============================================================================ +// ERROR REPORTING MACROS - Standardized logging levels +// ============================================================================ + +/** + * @brief TRACE - Development only: detailed traces, function entry/exit + */ +#define LOG_TRACE(...) spdlog::trace(__VA_ARGS__) + +/** + * @brief DEBUG - Development/troubleshooting: state changes, discoveries + */ +#define LOG_DEBUG(...) spdlog::debug(__VA_ARGS__) + +/** + * @brief INFO - Normal operation: startup/shutdown, significant events + * (DEFAULT) + */ +#define LOG_INFO(...) spdlog::info(__VA_ARGS__) + +/** + * @brief WARN - Recoverable errors: retries, degradation, resource warnings + */ +#define LOG_WARN(...) spdlog::warn(__VA_ARGS__) + +/** + * @brief ERROR - Critical failures: hard limits, invariant violations + */ +#define LOG_ERROR(...) spdlog::error(__VA_ARGS__) + +/** + * @brief CRITICAL - Fatal conditions: data corruption, immediate shutdown + */ +#define LOG_CRITICAL(...) spdlog::critical(__VA_ARGS__) + +// ============================================================================ +// ENVIRONMENT VARIABLES FOR LOGGING CONFIGURATION +// ============================================================================ +// +// LOG_LEVEL={trace,debug,info,warn,err,critical,off} - Set log level (default: +// info) LOG_FILE=/path/to/file - Enable file logging with automatic rotation +// LOG_PATTERN="[%t] [%l] %v" - Custom spdlog pattern string +// +// Examples: +// LOG_LEVEL=debug ./app +// LOG_FILE=/var/log/app.log LOG_LEVEL=info ./app +// LOG_PATTERN="[%H:%M:%S] [%l] %v" LOG_LEVEL=debug ./app + +// ============================================================================ +// ERROR REPORTING GUIDELINES - Standardized logging severity reference +// ============================================================================ +// +// LOG_TRACE - Development only: detailed traces, function entry/exit +// LOG_DEBUG - Development/troubleshooting: state changes, discoveries +// LOG_INFO - Normal operation: startup/shutdown, significant events (DEFAULT) +// LOG_WARN - Recoverable errors: retries, degradation, resource warnings +// LOG_ERROR - Fatal errors: critical failures, hard limits, invariants broken +// LOG_CRITICAL - Application fatal: data corruption, immediate shutdown +// +// Decision tree for choosing level: +// Is this a debug/internal detail? -> LOG_DEBUG +// Is this a normal operation milestone? -> LOG_INFO +// Is the error recoverable/expected to retry? -> LOG_WARN +// Can the operation continue? NO -> LOG_ERROR, YES -> LOG_WARN +// Is this application-fatal? -> LOG_CRITICAL before exit + +#endif // SRC_UTILS_LOGGING_H diff --git a/src/utils/property_utils.h b/src/utils/property_utils.h new file mode 100644 index 0000000..dbf147e --- /dev/null +++ b/src/utils/property_utils.h @@ -0,0 +1,117 @@ +// 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_PROPERTY_UTILS_H +#define SRC_UTILS_PROPERTY_UTILS_H + +#include +#include +#include +#include +#include "../utils/logging.h" + +namespace property_utils { + +/** + * @brief Safely get a D-Bus property value with type checking + * + * @tparam T The expected type of the property + * @param properties The property map + * @param key The property name to retrieve + * @return std::optional The property value if found and correct type, + * nullopt otherwise + */ +template +std::optional getProperty( + const std::map& properties, + const sdbus::PropertyName& key) { + // Check if a property exists + const auto it = properties.find(key); + if (it == properties.end()) { + LOG_DEBUG("Property '{}' not found", key); + return std::nullopt; + } + + // Try to get the value with type checking + try { + return it->second.get(); + } catch (const sdbus::Error& e) { + LOG_ERROR("D-Bus error getting property '{}': {} - {}", key, e.getName(), + e.getMessage()); + return std::nullopt; + } catch (const std::exception& e) { + LOG_ERROR("Exception getting property '{}': {}", key, e.what()); + return std::nullopt; + } +} + +/** + * @brief Safely get a D-Bus property value with type checking and default value + * + * @tparam T The expected type of the property + * @param properties The property map + * @param key The property name to retrieve + * @param default_value The default value to return if property not found or + * wrong type + * @return T The property value if found and correct type, default_value + * otherwise + */ +template +T getPropertyOr(const std::map& properties, + const sdbus::PropertyName& key, + const T& default_value) { + auto result = getProperty(properties, key); + return result.value_or(default_value); +} + +/** + * @brief Check if a property exists in the map + * + * @param properties The property map + * @param key The property name to check + * @return bool True if property exists, false otherwise + */ +inline bool hasProperty( + const std::map& properties, + const sdbus::PropertyName& key) { + return properties.contains(key); +} + +/** + * @brief Safely get a required D-Bus property value + * + * Throws std::runtime_error if property not found or wrong type. + * Use this for properties that are mandatory for correct operation. + * + * @tparam T The expected type of the property + * @param properties The property map + * @param key The property name to retrieve + * @return T The property value + * @throws std::runtime_error if property not found or wrong type + */ +template +T getRequiredProperty( + const std::map& properties, + const sdbus::PropertyName& key) { + auto result = getProperty(properties, key); + if (!result) { + throw std::runtime_error("Required property '" + std::string(key) + + "' not found or has wrong type"); + } + return *result; +} + +} // namespace property_utils + +#endif // SRC_UTILS_PROPERTY_UTILS_H diff --git a/src/utils/resource_limits.h b/src/utils/resource_limits.h new file mode 100644 index 0000000..6f6505e --- /dev/null +++ b/src/utils/resource_limits.h @@ -0,0 +1,38 @@ +// 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_RESOURCE_LIMITS_H_ +#define SRC_UTILS_RESOURCE_LIMITS_H_ + +#include + +namespace resource_limits { + +inline constexpr std::size_t kMaxAdapters = 64; +inline constexpr std::size_t kMaxDevices = 1024; +inline constexpr std::size_t kMaxGattServices = 4096; +inline constexpr std::size_t kMaxGattCharacteristics = 16384; +inline constexpr std::size_t kMaxGattDescriptors = 32768; +inline constexpr std::size_t kMaxBatteryEntries = 1024; +inline constexpr std::size_t kMaxInputEntries = 1024; +inline constexpr std::size_t kMaxUPowerClients = 256; + +inline bool IsAtCapacity(const std::size_t current_size, + const std::size_t limit) { + return current_size >= limit; +} + +} // namespace resource_limits + +#endif // SRC_UTILS_RESOURCE_LIMITS_H_ diff --git a/src/utils/signal_handler.h b/src/utils/signal_handler.h new file mode 100644 index 0000000..a6a71dd --- /dev/null +++ b/src/utils/signal_handler.h @@ -0,0 +1,139 @@ +// 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_SIGNAL_HANDLER_H +#define SRC_UTILS_SIGNAL_HANDLER_H + +#include +#include +#include +#include +#include + +#include +#include "logging.h" + +/** + * @brief Global flag to control application lifecycle + * + * Set to false by signal handlers to trigger graceful shutdown. + */ +inline std::atomic g_running{true}; + +inline constexpr auto kConnectionCheckInterval = std::chrono::seconds(30); +inline constexpr auto kMonitorSleepInterval = std::chrono::milliseconds(100); +inline constexpr auto kLogFlushInterval = std::chrono::seconds(5); + +/** + * @brief Signal handler for graceful shutdown + * + * Handles SIGINT (Ctrl+C) and SIGTERM (kill) signals. + * + * @param signum Signal number received + */ +inline void signalHandler(int signum) { + auto signame = "UNKNOWN"; + switch (signum) { + case SIGINT: + signame = "SIGINT"; + break; + case SIGTERM: + signame = "SIGTERM"; + break; + default: + break; + } + + LOG_INFO("Received signal {} - initiating graceful shutdown", signame); + g_running = false; +} + +/** + * @brief Install signal handlers for graceful shutdown + * + * Sets up handlers for SIGINT, SIGTERM, and ignores SIGPIPE. + */ +inline void installSignalHandlers() { + std::signal(SIGINT, signalHandler); + std::signal(SIGTERM, signalHandler); + std::signal(SIGPIPE, SIG_IGN); // Prevent crashes on broken pipes +} + +/** + * @brief Check if D-Bus connection is still alive + * + * Attempts to call a simple method to verify the connection is working. + * + * @param connection D-Bus connection to check + * @return true if the connection is alive, false otherwise + */ +inline bool isConnectionAlive(sdbus::IConnection& connection) { + try { + // Try to get the connection's unique name - this requires communication + // with the bus + const auto proxy = sdbus::createProxy( + connection, sdbus::ServiceName("org.freedesktop.DBus"), + sdbus::ObjectPath("/org/freedesktop/DBus")); + + // Call GetId - a simple method that should always work if the connection is + // alive + std::string bus_id; + proxy->callMethod("GetId") + .onInterface("org.freedesktop.DBus") + .storeResultsTo(bus_id); + + return true; + } catch (const sdbus::Error& e) { + LOG_WARN("D-Bus connection check failed: {}", e.what()); + return false; + } catch (const std::exception& e) { + LOG_WARN("Connection check exception: {}", e.what()); + return false; + } +} + +/** + * @brief Monitor loop with connection health checks + * + * Periodically checks if the D-Bus connection is still alive and if the + * application should continue running. + * + * @param connection D-Bus connection to monitor + * @param check_interval How often to check connection health + * @param sleep_interval How long to sleep between checks + * @return std::optional Error message if connection lost, nullopt + * if graceful shutdown + */ +inline std::optional monitorLoop( + sdbus::IConnection& connection, + const std::chrono::seconds check_interval = kConnectionCheckInterval, + const std::chrono::milliseconds sleep_interval = kMonitorSleepInterval) { + auto last_check = std::chrono::steady_clock::now(); + + while (g_running) { + std::this_thread::sleep_for(sleep_interval); + + if (auto now = std::chrono::steady_clock::now(); + now - last_check >= check_interval) { + if (!isConnectionAlive(connection)) { + return "D-Bus connection lost"; + } + last_check = now; + } + } + + return std::nullopt; // Graceful shutdown via signal +} + +#endif // SRC_UTILS_SIGNAL_HANDLER_H diff --git a/src/utils/utils.cc b/src/utils/utils.cc index ce26f4d..a907829 100644 --- a/src/utils/utils.cc +++ b/src/utils/utils.cc @@ -17,6 +17,8 @@ #include #include +#include "../utils/logging.h" + void Utils::append_property(const sdbus::Variant& value, std::ostringstream& os) { const std::string_view type = value.peekValueType(); @@ -58,7 +60,7 @@ void Utils::print_changed_properties( os << "[" << interfaceName << "] Invalidated property: " << name << std::endl; } - spdlog::info(os.str()); + LOG_INFO(os.str()); } std::vector Utils::ListNames(sdbus::IConnection& connection) { diff --git a/src/utils/utils.h b/src/utils/utils.h index 32cb831..25d9087 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -21,8 +21,6 @@ #include #include -#include - class Utils { public: static void append_property(const sdbus::Variant& value, diff --git a/src/wpa_supplicant/main.cc b/src/wpa_supplicant/main.cc index 5b6b257..ec5ce09 100644 --- a/src/wpa_supplicant/main.cc +++ b/src/wpa_supplicant/main.cc @@ -1,17 +1,35 @@ +#include "../utils/signal_handler.h" #include "wpa_supplicant1_client.h" #include -#include int main() { - const auto connection = sdbus::createSystemBusConnection(); - connection->enterEventLoopAsync(); + try { + installSignalHandlers(); - WpaSupplicant1Client client(*connection); + const auto connection = sdbus::createSystemBusConnection(); + connection->enterEventLoopAsync(); - using namespace std::chrono_literals; - std::this_thread::sleep_for(2s); // allow async enumeration + WpaSupplicant1Client client(*connection); - connection->leaveEventLoop(); - return 0; + LOG_INFO("wpa_supplicant client running - Press Ctrl+C to exit"); + + auto result = monitorLoop(*connection); + + if (result) { + LOG_ERROR("Exiting due to: {}", *result); + } else { + LOG_INFO("Shutting down..."); + } + + connection->leaveEventLoop(); + return result ? 1 : 0; + + } catch (const sdbus::Error& e) { + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); + return 1; + } catch (const std::exception& e) { + LOG_ERROR("Exception: {}", e.what()); + return 1; + } } \ No newline at end of file diff --git a/src/wpa_supplicant/wpa_supplicant1_client.cc b/src/wpa_supplicant/wpa_supplicant1_client.cc index 508703d..020d417 100644 --- a/src/wpa_supplicant/wpa_supplicant1_client.cc +++ b/src/wpa_supplicant/wpa_supplicant1_client.cc @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "wpa_supplicant1_client.h" +#include "../utils/logging.h" WpaSupplicant1Client::WpaSupplicant1Client(sdbus::IConnection& connection) : ProxyInterfaces{connection, sdbus::ServiceName(SERVICE_NAME), @@ -14,8 +15,8 @@ WpaSupplicant1Client::WpaSupplicant1Client(sdbus::IConnection& connection) [](std::optional error, const std::map& values) { if (error) { - spdlog::warn("wpa_supplicant1 GetAllAsync failed: {} - {}", - error->getName(), error->getMessage()); + LOG_WARN("wpa_supplicant1 GetAllAsync failed: {} - {}", + error->getName(), error->getMessage()); } else { Utils::print_changed_properties( sdbus::InterfaceName(wpa_supplicant1_proxy::INTERFACE_NAME), @@ -45,9 +46,9 @@ void WpaSupplicant1Client::onPropertiesChanged( void WpaSupplicant1Client::onInterfaceAdded( const sdbus::ObjectPath& path, const std::map& properties) { - spdlog::info("onInterfaceAdded"); + LOG_INFO("onInterfaceAdded"); } void WpaSupplicant1Client::onInterfaceRemoved(const sdbus::ObjectPath& path) { - spdlog::info("onInterfaceRemoved"); + LOG_INFO("onInterfaceRemoved"); }