|
| 1 | +#include "dbus/upower/upower_charge_limit_support.h" |
| 2 | + |
| 3 | +#include <algorithm> |
| 4 | +#include <cctype> |
| 5 | +#include <charconv> |
| 6 | +#include <fstream> |
| 7 | +#include <ranges> |
| 8 | +#include <string> |
| 9 | + |
| 10 | +namespace { |
| 11 | + |
| 12 | + std::optional<std::uint32_t> readThresholdFile(const std::filesystem::path& path) { |
| 13 | + std::ifstream input(path); |
| 14 | + if (!input) { |
| 15 | + return std::nullopt; |
| 16 | + } |
| 17 | + |
| 18 | + std::string value; |
| 19 | + std::getline(input, value); |
| 20 | + const auto first = value.find_first_not_of(" \t\r\n"); |
| 21 | + if (first == std::string::npos) { |
| 22 | + return std::nullopt; |
| 23 | + } |
| 24 | + const auto last = value.find_last_not_of(" \t\r\n"); |
| 25 | + const std::string_view trimmed(value.data() + first, last - first + 1); |
| 26 | + std::uint32_t threshold = 0; |
| 27 | + const auto [end, error] = std::from_chars(trimmed.data(), trimmed.data() + trimmed.size(), threshold); |
| 28 | + if (error != std::errc{} || end != trimmed.data() + trimmed.size() || threshold > 100U) { |
| 29 | + return std::nullopt; |
| 30 | + } |
| 31 | + return threshold; |
| 32 | + } |
| 33 | + |
| 34 | + bool isPlainPowerSupplyComponent(std::string_view value) { |
| 35 | + if (value.empty() || value == "." || value == "..") { |
| 36 | + return false; |
| 37 | + } |
| 38 | + return std::ranges::all_of(value, [](unsigned char ch) { |
| 39 | + return std::isalnum(ch) != 0 || ch == '_' || ch == '-' || ch == '.' || ch == ':'; |
| 40 | + }); |
| 41 | + } |
| 42 | + |
| 43 | + std::optional<std::string> powerSupplyComponent(std::string_view nativePath) { |
| 44 | + if (isPlainPowerSupplyComponent(nativePath)) { |
| 45 | + return std::string(nativePath); |
| 46 | + } |
| 47 | + |
| 48 | + const std::filesystem::path path{nativePath}; |
| 49 | + if (!path.is_absolute() |
| 50 | + || !nativePath.starts_with("/sys/") |
| 51 | + || std::ranges::any_of(path, [](const std::filesystem::path& part) { return part == ".." || part == "."; })) { |
| 52 | + return std::nullopt; |
| 53 | + } |
| 54 | + |
| 55 | + const auto normalized = path.lexically_normal(); |
| 56 | + const std::string component = normalized.filename().string(); |
| 57 | + if (normalized.parent_path().filename() != "power_supply" || !isPlainPowerSupplyComponent(component)) { |
| 58 | + return std::nullopt; |
| 59 | + } |
| 60 | + return component; |
| 61 | + } |
| 62 | + |
| 63 | +} // namespace |
| 64 | + |
| 65 | +namespace upower::detail { |
| 66 | + |
| 67 | + ChargeThresholdProbe |
| 68 | + readChargeThresholdsFromSysfs(std::string_view nativePath, const std::filesystem::path& powerSupplyRoot) { |
| 69 | + ChargeThresholdProbe result; |
| 70 | + const auto component = powerSupplyComponent(nativePath); |
| 71 | + if (!component.has_value()) { |
| 72 | + return result; |
| 73 | + } |
| 74 | + |
| 75 | + // NativePath selects a kernel power_supply name only. Always read through the fixed class root, |
| 76 | + // even when UPower supplies its documented absolute /sys/devices/.../power_supply path. |
| 77 | + const auto batteryPath = powerSupplyRoot / *component; |
| 78 | + result.start = readThresholdFile(batteryPath / "charge_control_start_threshold"); |
| 79 | + result.end = readThresholdFile(batteryPath / "charge_control_end_threshold"); |
| 80 | + return result; |
| 81 | + } |
| 82 | + |
| 83 | +} // namespace upower::detail |
0 commit comments