From af4e8e86ec91389a04df2b1b8621c0ad5c84917b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 5 Jun 2023 11:49:35 +0200 Subject: [PATCH 1/4] battery-util: split out code that checks AC power state into its own .c/.h pair No code change, just some splitting out of the relevant code from udev-util.[ch]. This makes sense on its own, but is also prepartion to move the code that checks for low battery state into battery-util.[ch], too. --- src/ac-power/ac-power.c | 2 +- src/login/logind-core.c | 1 + src/shared/battery-util.c | 178 ++++++++++++++++++++++++++++++++++++++ src/shared/battery-util.h | 4 + src/shared/condition.c | 2 +- src/shared/meson.build | 1 + src/shared/sleep-config.c | 4 +- src/shared/udev-util.c | 170 ------------------------------------ src/shared/udev-util.h | 2 - src/test/test-condition.c | 2 +- 10 files changed, 189 insertions(+), 177 deletions(-) create mode 100644 src/shared/battery-util.c create mode 100644 src/shared/battery-util.h diff --git a/src/ac-power/ac-power.c b/src/ac-power/ac-power.c index 853635047d..62181a83df 100644 --- a/src/ac-power/ac-power.c +++ b/src/ac-power/ac-power.c @@ -2,9 +2,9 @@ #include +#include "battery-util.h" #include "build.h" #include "main-func.h" -#include "udev-util.h" static bool arg_verbose = false; diff --git a/src/login/logind-core.c b/src/login/logind-core.c index dd4b246893..df67e5509b 100644 --- a/src/login/logind-core.c +++ b/src/login/logind-core.c @@ -8,6 +8,7 @@ #include "sd-device.h" #include "alloc-util.h" +#include "battery-util.h" #include "bus-error.h" #include "bus-locator.h" #include "bus-util.h" diff --git a/src/shared/battery-util.c b/src/shared/battery-util.c new file mode 100644 index 0000000000..aa93b531a0 --- /dev/null +++ b/src/shared/battery-util.c @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-device.h" + +#include "device-private.h" +#include "device-util.h" +#include "string-util.h" +#include "battery-util.h" + +static int device_is_power_sink(sd_device *device) { + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; + bool found_source = false, found_sink = false; + sd_device *parent, *d; + int r; + + assert(device); + + /* USB-C power supply device has two power roles: source or sink. See, + * https://docs.kernel.org/admin-guide/abi-testing.html#abi-file-testing-sysfs-class-typec */ + + r = sd_device_enumerator_new(&e); + if (r < 0) + return r; + + r = sd_device_enumerator_allow_uninitialized(e); + if (r < 0) + return r; + + r = sd_device_enumerator_add_match_subsystem(e, "typec", true); + if (r < 0) + return r; + + r = sd_device_get_parent(device, &parent); + if (r < 0) + return r; + + r = sd_device_enumerator_add_match_parent(e, parent); + if (r < 0) + return r; + + FOREACH_DEVICE(e, d) { + const char *val; + + r = sd_device_get_sysattr_value(d, "power_role", &val); + if (r < 0) { + if (r != -ENOENT) + log_device_debug_errno(d, r, "Failed to read 'power_role' sysfs attribute, ignoring: %m"); + continue; + } + + if (strstr(val, "[source]")) { + found_source = true; + log_device_debug(d, "The USB type-C port is in power source mode."); + } else if (strstr(val, "[sink]")) { + found_sink = true; + log_device_debug(d, "The USB type-C port is in power sink mode."); + } + } + + if (found_sink) + log_device_debug(device, "The USB type-C device has at least one port in power sink mode."); + else if (!found_source) + log_device_debug(device, "The USB type-C device has no port in power source mode, assuming the device is in power sink mode."); + else + log_device_debug(device, "All USB type-C ports are in power source mode."); + + return found_sink || !found_source; +} + +static bool battery_is_discharging(sd_device *d) { + const char *val; + int r; + + assert(d); + + r = sd_device_get_sysattr_value(d, "scope", &val); + if (r < 0) { + if (r != -ENOENT) + log_device_debug_errno(d, r, "Failed to read 'scope' sysfs attribute, ignoring: %m"); + } else if (streq(val, "Device")) { + log_device_debug(d, "The power supply is a device battery, ignoring device."); + return false; + } + + r = device_get_sysattr_bool(d, "present"); + if (r < 0) + log_device_debug_errno(d, r, "Failed to read 'present' sysfs attribute, assuming the battery is present: %m"); + else if (r == 0) { + log_device_debug(d, "The battery is not present, ignoring the power supply."); + return false; + } + + /* Possible values: "Unknown", "Charging", "Discharging", "Not charging", "Full" */ + r = sd_device_get_sysattr_value(d, "status", &val); + if (r < 0) { + log_device_debug_errno(d, r, "Failed to read 'status' sysfs attribute, assuming the battery is discharging: %m"); + return true; + } + if (!streq(val, "Discharging")) { + log_device_debug(d, "The battery status is '%s', assuming the battery is not used as a power source of this machine.", val); + return false; + } + + return true; +} + +int on_ac_power(void) { + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; + bool found_ac_online = false, found_discharging_battery = false; + sd_device *d; + int r; + + r = sd_device_enumerator_new(&e); + if (r < 0) + return r; + + r = sd_device_enumerator_allow_uninitialized(e); + if (r < 0) + return r; + + r = sd_device_enumerator_add_match_subsystem(e, "power_supply", true); + if (r < 0) + return r; + + FOREACH_DEVICE(e, d) { + /* See + * https://github.com/torvalds/linux/blob/4eef766b7d4d88f0b984781bc1bcb574a6eafdc7/include/linux/power_supply.h#L176 + * for defined power source types. Also see: + * https://docs.kernel.org/admin-guide/abi-testing.html#abi-file-testing-sysfs-class-power */ + + const char *val; + r = sd_device_get_sysattr_value(d, "type", &val); + if (r < 0) { + log_device_debug_errno(d, r, "Failed to read 'type' sysfs attribute, ignoring device: %m"); + continue; + } + + /* Ignore USB-C power supply in source mode. See issue #21988. */ + if (streq(val, "USB")) { + r = device_is_power_sink(d); + if (r <= 0) { + if (r < 0) + log_device_debug_errno(d, r, "Failed to determine the current power role, ignoring device: %m"); + else + log_device_debug(d, "USB power supply is in source mode, ignoring device."); + continue; + } + } + + if (streq(val, "Battery")) { + if (battery_is_discharging(d)) { + found_discharging_battery = true; + log_device_debug(d, "The power supply is a battery and currently discharging."); + } + continue; + } + + r = device_get_sysattr_unsigned(d, "online", NULL); + if (r < 0) { + log_device_debug_errno(d, r, "Failed to query 'online' sysfs attribute, ignoring device: %m"); + continue; + } else if (r > 0) /* At least 1 and 2 are defined as different types of 'online' */ + found_ac_online = true; + + log_device_debug(d, "The power supply is currently %s.", r > 0 ? "online" : "offline"); + } + + if (found_ac_online) { + log_debug("Found at least one online non-battery power supply, system is running on AC."); + return true; + } else if (found_discharging_battery) { + log_debug("Found at least one discharging battery and no online power sources, assuming system is running from battery."); + return false; + } else { + log_debug("No power supply reported online and no discharging battery found, assuming system is running on AC."); + return true; + } +} diff --git a/src/shared/battery-util.h b/src/shared/battery-util.h new file mode 100644 index 0000000000..4089fff0c4 --- /dev/null +++ b/src/shared/battery-util.h @@ -0,0 +1,4 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int on_ac_power(void); diff --git a/src/shared/condition.c b/src/shared/condition.c index fce3fc2afb..9eb49d0eb5 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -18,6 +18,7 @@ #include "apparmor-util.h" #include "architecture.h" #include "audit-util.h" +#include "battery-util.h" #include "blockdev-util.h" #include "cap-list.h" #include "cgroup-util.h" @@ -56,7 +57,6 @@ #include "string-util.h" #include "tomoyo-util.h" #include "tpm2-util.h" -#include "udev-util.h" #include "uid-alloc-range.h" #include "user-util.h" #include "virt.h" diff --git a/src/shared/meson.build b/src/shared/meson.build index 31241bc08d..b34d218c1e 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -6,6 +6,7 @@ shared_sources = files( 'ask-password-api.c', 'barrier.c', 'base-filesystem.c', + 'battery-util.c', 'binfmt-util.c', 'bitmap.c', 'blockdev-util.c', diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c index d388c89192..51aebf2f0d 100644 --- a/src/shared/sleep-config.c +++ b/src/shared/sleep-config.c @@ -18,6 +18,7 @@ #include "sd-device.h" #include "alloc-util.h" +#include "battery-util.h" #include "blockdev-util.h" #include "btrfs-util.h" #include "conf-parser.h" @@ -34,15 +35,14 @@ #include "log.h" #include "macro.h" #include "path-util.h" -#include "sleep-config.h" #include "siphash24.h" +#include "sleep-config.h" #include "stat-util.h" #include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" #include "time-util.h" -#include "udev-util.h" #define BATTERY_LOW_CAPACITY_LEVEL 5 #define DISCHARGE_RATE_FILEPATH "/var/lib/systemd/sleep/battery_discharge_percentage_rate_per_hour" diff --git a/src/shared/udev-util.c b/src/shared/udev-util.c index 646eaf9135..088eb0fe9a 100644 --- a/src/shared/udev-util.c +++ b/src/shared/udev-util.c @@ -591,176 +591,6 @@ int udev_queue_init(void) { return TAKE_FD(fd); } -static int device_is_power_sink(sd_device *device) { - _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; - bool found_source = false, found_sink = false; - sd_device *parent, *d; - int r; - - assert(device); - - /* USB-C power supply device has two power roles: source or sink. See, - * https://docs.kernel.org/admin-guide/abi-testing.html#abi-file-testing-sysfs-class-typec */ - - r = sd_device_enumerator_new(&e); - if (r < 0) - return r; - - r = sd_device_enumerator_allow_uninitialized(e); - if (r < 0) - return r; - - r = sd_device_enumerator_add_match_subsystem(e, "typec", true); - if (r < 0) - return r; - - r = sd_device_get_parent(device, &parent); - if (r < 0) - return r; - - r = sd_device_enumerator_add_match_parent(e, parent); - if (r < 0) - return r; - - FOREACH_DEVICE(e, d) { - const char *val; - - r = sd_device_get_sysattr_value(d, "power_role", &val); - if (r < 0) { - if (r != -ENOENT) - log_device_debug_errno(d, r, "Failed to read 'power_role' sysfs attribute, ignoring: %m"); - continue; - } - - if (strstr(val, "[source]")) { - found_source = true; - log_device_debug(d, "The USB type-C port is in power source mode."); - } else if (strstr(val, "[sink]")) { - found_sink = true; - log_device_debug(d, "The USB type-C port is in power sink mode."); - } - } - - if (found_sink) - log_device_debug(device, "The USB type-C device has at least one port in power sink mode."); - else if (!found_source) - log_device_debug(device, "The USB type-C device has no port in power source mode, assuming the device is in power sink mode."); - else - log_device_debug(device, "All USB type-C ports are in power source mode."); - - return found_sink || !found_source; -} - -static bool battery_is_discharging(sd_device *d) { - const char *val; - int r; - - assert(d); - - r = sd_device_get_sysattr_value(d, "scope", &val); - if (r < 0) { - if (r != -ENOENT) - log_device_debug_errno(d, r, "Failed to read 'scope' sysfs attribute, ignoring: %m"); - } else if (streq(val, "Device")) { - log_device_debug(d, "The power supply is a device battery, ignoring device."); - return false; - } - - r = device_get_sysattr_bool(d, "present"); - if (r < 0) - log_device_debug_errno(d, r, "Failed to read 'present' sysfs attribute, assuming the battery is present: %m"); - else if (r == 0) { - log_device_debug(d, "The battery is not present, ignoring the power supply."); - return false; - } - - /* Possible values: "Unknown", "Charging", "Discharging", "Not charging", "Full" */ - r = sd_device_get_sysattr_value(d, "status", &val); - if (r < 0) { - log_device_debug_errno(d, r, "Failed to read 'status' sysfs attribute, assuming the battery is discharging: %m"); - return true; - } - if (!streq(val, "Discharging")) { - log_device_debug(d, "The battery status is '%s', assuming the battery is not used as a power source of this machine.", val); - return false; - } - - return true; -} - -int on_ac_power(void) { - _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; - bool found_ac_online = false, found_discharging_battery = false; - sd_device *d; - int r; - - r = sd_device_enumerator_new(&e); - if (r < 0) - return r; - - r = sd_device_enumerator_allow_uninitialized(e); - if (r < 0) - return r; - - r = sd_device_enumerator_add_match_subsystem(e, "power_supply", true); - if (r < 0) - return r; - - FOREACH_DEVICE(e, d) { - /* See - * https://github.com/torvalds/linux/blob/4eef766b7d4d88f0b984781bc1bcb574a6eafdc7/include/linux/power_supply.h#L176 - * for defined power source types. Also see: - * https://docs.kernel.org/admin-guide/abi-testing.html#abi-file-testing-sysfs-class-power */ - - const char *val; - r = sd_device_get_sysattr_value(d, "type", &val); - if (r < 0) { - log_device_debug_errno(d, r, "Failed to read 'type' sysfs attribute, ignoring device: %m"); - continue; - } - - /* Ignore USB-C power supply in source mode. See issue #21988. */ - if (streq(val, "USB")) { - r = device_is_power_sink(d); - if (r <= 0) { - if (r < 0) - log_device_debug_errno(d, r, "Failed to determine the current power role, ignoring device: %m"); - else - log_device_debug(d, "USB power supply is in source mode, ignoring device."); - continue; - } - } - - if (streq(val, "Battery")) { - if (battery_is_discharging(d)) { - found_discharging_battery = true; - log_device_debug(d, "The power supply is a battery and currently discharging."); - } - continue; - } - - r = device_get_sysattr_unsigned(d, "online", NULL); - if (r < 0) { - log_device_debug_errno(d, r, "Failed to query 'online' sysfs attribute, ignoring device: %m"); - continue; - } else if (r > 0) /* At least 1 and 2 are defined as different types of 'online' */ - found_ac_online = true; - - log_device_debug(d, "The power supply is currently %s.", r > 0 ? "online" : "offline"); - } - - if (found_ac_online) { - log_debug("Found at least one online non-battery power supply, system is running on AC."); - return true; - } else if (found_discharging_battery) { - log_debug("Found at least one discharging battery and no online power sources, assuming system is running from battery."); - return false; - } else { - log_debug("No power supply reported online and no discharging battery found, assuming system is running on AC."); - return true; - } -} - bool udev_available(void) { static int cache = -1; diff --git a/src/shared/udev-util.h b/src/shared/udev-util.h index c3c3dbe842..175fa0b6f0 100644 --- a/src/shared/udev-util.h +++ b/src/shared/udev-util.h @@ -55,8 +55,6 @@ bool devpath_conflict(const char *a, const char *b); int udev_queue_is_empty(void); int udev_queue_init(void); -int on_ac_power(void); - bool udev_available(void); #if HAVE_SYS_SDT_H diff --git a/src/test/test-condition.c b/src/test/test-condition.c index 8f52dab8ce..317a104f4e 100644 --- a/src/test/test-condition.c +++ b/src/test/test-condition.c @@ -11,6 +11,7 @@ #include "apparmor-util.h" #include "architecture.h" #include "audit-util.h" +#include "battery-util.h" #include "cgroup-util.h" #include "condition.h" #include "cpu-set-util.h" @@ -39,7 +40,6 @@ #include "tests.h" #include "tmpfile-util.h" #include "tomoyo-util.h" -#include "udev-util.h" #include "uid-alloc-range.h" #include "user-util.h" #include "virt.h" From 319c46483e3eb1a3c33a3ca97b90ba13c3987da1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 5 Jun 2023 11:56:31 +0200 Subject: [PATCH 2/4] battery-util: move battery_is_discharging_and_low() to battery-util.[ch] This moves a first batch of functions from sleep-config.[ch] over to battery-util.[ch]. In the long run we should probably move even more stuff over, i.e. anything that deals with the battery sysfs driver interface. No code change. --- src/shared/battery-util.c | 80 ++++++++++++++++++++++++++++++++++++++ src/shared/battery-util.h | 7 ++++ src/shared/sleep-config.c | 81 +-------------------------------------- src/shared/sleep-config.h | 1 - src/sleep/sleep.c | 1 + 5 files changed, 89 insertions(+), 81 deletions(-) diff --git a/src/shared/battery-util.c b/src/shared/battery-util.c index aa93b531a0..b11b6dc989 100644 --- a/src/shared/battery-util.c +++ b/src/shared/battery-util.c @@ -7,6 +7,8 @@ #include "string-util.h" #include "battery-util.h" +#define BATTERY_LOW_CAPACITY_LEVEL 5 + static int device_is_power_sink(sd_device *device) { _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; bool found_source = false, found_sink = false; @@ -176,3 +178,81 @@ int on_ac_power(void) { return true; } } + +/* Get the list of batteries */ +int battery_enumerator_new(sd_device_enumerator **ret) { + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; + int r; + + assert(ret); + + r = sd_device_enumerator_new(&e); + if (r < 0) + return r; + + r = sd_device_enumerator_add_match_subsystem(e, "power_supply", /* match = */ true); + if (r < 0) + return r; + + r = sd_device_enumerator_allow_uninitialized(e); + if (r < 0) + return r; + + r = sd_device_enumerator_add_match_sysattr(e, "type", "Battery", /* match = */ true); + if (r < 0) + return r; + + r = sd_device_enumerator_add_match_sysattr(e, "present", "1", /* match = */ true); + if (r < 0) + return r; + + r = sd_device_enumerator_add_match_sysattr(e, "scope", "Device", /* match = */ false); + if (r < 0) + return r; + + *ret = TAKE_PTR(e); + return 0; +} + +/* Battery percentage capacity fetched from capacity file and if in range 0-100 then returned */ +int battery_read_capacity_percentage(sd_device *dev) { + int battery_capacity, r; + + assert(dev); + + r = device_get_sysattr_int(dev, "capacity", &battery_capacity); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to read/parse POWER_SUPPLY_CAPACITY: %m"); + + if (battery_capacity < 0 || battery_capacity > 100) + return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ERANGE), "Invalid battery capacity"); + + return battery_capacity; +} + +/* If a battery whose percentage capacity is <= 5% exists, and we're not on AC power, return success */ +int battery_is_discharging_and_low(void) { + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; + sd_device *dev; + int r; + + /* We have not used battery capacity_level since value is set to full + * or Normal in case ACPI is not working properly. In case of no battery + * 0 will be returned and system will be suspended for 1st cycle then hibernated */ + + r = on_ac_power(); + if (r < 0) + log_debug_errno(r, "Failed to check if the system is running on AC, assuming it is not: %m"); + if (r > 0) + return false; + + r = battery_enumerator_new(&e); + if (r < 0) + return log_debug_errno(r, "Failed to initialize battery enumerator: %m"); + + FOREACH_DEVICE(e, dev) + if (battery_read_capacity_percentage(dev) > BATTERY_LOW_CAPACITY_LEVEL) + return false; + + return true; +} diff --git a/src/shared/battery-util.h b/src/shared/battery-util.h index 4089fff0c4..c58f30b0b1 100644 --- a/src/shared/battery-util.h +++ b/src/shared/battery-util.h @@ -1,4 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-device.h" + int on_ac_power(void); + +int battery_is_discharging_and_low(void); + +int battery_enumerator_new(sd_device_enumerator **ret); +int battery_read_capacity_percentage(sd_device *dev); diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c index 51aebf2f0d..4fd129b27e 100644 --- a/src/shared/sleep-config.c +++ b/src/shared/sleep-config.c @@ -44,7 +44,6 @@ #include "strv.h" #include "time-util.h" -#define BATTERY_LOW_CAPACITY_LEVEL 5 #define DISCHARGE_RATE_FILEPATH "/var/lib/systemd/sleep/battery_discharge_percentage_rate_per_hour" #define BATTERY_DISCHARGE_RATE_HASH_KEY SD_ID128_MAKE(5f,9a,20,18,38,76,46,07,8d,36,58,0b,bb,c4,e0,63) #define SYS_ENTRY_RAW_FILE_TYPE1 "/sys/firmware/dmi/entries/1-0/raw" @@ -128,41 +127,6 @@ int parse_sleep_config(SleepConfig **ret_sleep_config) { return 0; } -/* Get the list of batteries */ -static int battery_enumerator_new(sd_device_enumerator **ret) { - _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; - int r; - - assert(ret); - - r = sd_device_enumerator_new(&e); - if (r < 0) - return r; - - r = sd_device_enumerator_add_match_subsystem(e, "power_supply", /* match = */ true); - if (r < 0) - return r; - - r = sd_device_enumerator_allow_uninitialized(e); - if (r < 0) - return r; - - r = sd_device_enumerator_add_match_sysattr(e, "type", "Battery", /* match = */ true); - if (r < 0) - return r; - - r = sd_device_enumerator_add_match_sysattr(e, "present", "1", /* match = */ true); - if (r < 0) - return r; - - r = sd_device_enumerator_add_match_sysattr(e, "scope", "Device", /* match = */ false); - if (r < 0) - return r; - - *ret = TAKE_PTR(e); - return 0; -} - int get_capacity_by_name(Hashmap *capacities_by_name, const char *name) { void *p; @@ -176,49 +140,6 @@ int get_capacity_by_name(Hashmap *capacities_by_name, const char *name) { return PTR_TO_CAPACITY(p); } -/* Battery percentage capacity fetched from capacity file and if in range 0-100 then returned */ -static int read_battery_capacity_percentage(sd_device *dev) { - int battery_capacity, r; - - assert(dev); - - r = device_get_sysattr_int(dev, "capacity", &battery_capacity); - if (r < 0) - return log_device_debug_errno(dev, r, "Failed to read/parse POWER_SUPPLY_CAPACITY: %m"); - - if (battery_capacity < 0 || battery_capacity > 100) - return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ERANGE), "Invalid battery capacity"); - - return battery_capacity; -} - -/* If a battery whose percentage capacity is <= 5% exists, and we're not on AC power, return success */ -int battery_is_discharging_and_low(void) { - _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; - sd_device *dev; - int r; - - /* We have not used battery capacity_level since value is set to full - * or Normal in case ACPI is not working properly. In case of no battery - * 0 will be returned and system will be suspended for 1st cycle then hibernated */ - - r = on_ac_power(); - if (r < 0) - log_debug_errno(r, "Failed to check if the system is running on AC, assuming it is not: %m"); - if (r > 0) - return false; - - r = battery_enumerator_new(&e); - if (r < 0) - return log_debug_errno(r, "Failed to initialize battery enumerator: %m"); - - FOREACH_DEVICE(e, dev) - if (read_battery_capacity_percentage(dev) > BATTERY_LOW_CAPACITY_LEVEL) - return false; - - return true; -} - /* Store current capacity of each battery before suspension and timestamp */ int fetch_batteries_capacity_by_name(Hashmap **ret) { _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; @@ -241,7 +162,7 @@ int fetch_batteries_capacity_by_name(Hashmap **ret) { const char *battery_name; int battery_capacity; - battery_capacity = r = read_battery_capacity_percentage(dev); + battery_capacity = r = battery_read_capacity_percentage(dev); if (r < 0) continue; diff --git a/src/shared/sleep-config.h b/src/shared/sleep-config.h index 5c528b890b..b8245b129f 100644 --- a/src/shared/sleep-config.h +++ b/src/shared/sleep-config.h @@ -60,7 +60,6 @@ int find_hibernate_location(HibernateLocation **ret_hibernate_location); int can_sleep(SleepOperation operation); int can_sleep_disk(char **types); int can_sleep_state(char **types); -int battery_is_discharging_and_low(void); int get_total_suspend_interval(Hashmap *last_capacity, usec_t *ret); int fetch_batteries_capacity_by_name(Hashmap **ret_current_capacity); int get_capacity_by_name(Hashmap *capacities_by_name, const char *name); diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index a9445c1a61..0dc3ad3558 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -17,6 +17,7 @@ #include "sd-bus.h" #include "sd-messages.h" +#include "battery-util.h" #include "btrfs-util.h" #include "build.h" #include "bus-error.h" From c41e3605ec2f5c1d3040aa3a5fd57b9aee08c198 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 5 Jun 2023 12:12:03 +0200 Subject: [PATCH 3/4] battery-util: be more careful when determining whether we are in a low battery state Let's avoid assuming a low battery battery state if in doubt. That means, handle errors reading battery state gracefully. --- src/shared/battery-util.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/shared/battery-util.c b/src/shared/battery-util.c index b11b6dc989..948a5e3b9d 100644 --- a/src/shared/battery-util.c +++ b/src/shared/battery-util.c @@ -233,6 +233,7 @@ int battery_read_capacity_percentage(sd_device *dev) { /* If a battery whose percentage capacity is <= 5% exists, and we're not on AC power, return success */ int battery_is_discharging_and_low(void) { _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; + bool unsure = false, found_low = false; sd_device *dev; int r; @@ -250,9 +251,29 @@ int battery_is_discharging_and_low(void) { if (r < 0) return log_debug_errno(r, "Failed to initialize battery enumerator: %m"); - FOREACH_DEVICE(e, dev) - if (battery_read_capacity_percentage(dev) > BATTERY_LOW_CAPACITY_LEVEL) + FOREACH_DEVICE(e, dev) { + int level; + + level = battery_read_capacity_percentage(dev); + if (level < 0) { + unsure = true; + continue; + } + + if (level > BATTERY_LOW_CAPACITY_LEVEL) /* Found a charged battery */ return false; - return true; + found_low = true; + } + + /* If we found a battery whose state we couldn't read, don't assume we are in low battery state */ + if (unsure) + return false; + + /* Found no charged battery, but did find low batteries */ + if (found_low) + return true; + + /* Found neither charged nor low batteries? let's return that we aren't on low battery state */ + return false; } From 7f1520c6a150f4eea531ef6e57d5bd37c682af21 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 5 Jun 2023 12:14:12 +0200 Subject: [PATCH 4/4] ac-power: add --low switch to systemd-ac-power tool This allows checking from shell scripts whether the system is in a low battery state. It just exposed the code we anyway have in a directly accessible way. This is also very useful for testing things. --- man/systemd-ac-power.xml | 8 ++++++++ src/ac-power/ac-power.c | 26 ++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/man/systemd-ac-power.xml b/man/systemd-ac-power.xml index a4c9378cfd..e06a88a975 100644 --- a/man/systemd-ac-power.xml +++ b/man/systemd-ac-power.xml @@ -50,6 +50,14 @@ Show result as text instead of just returning success or failure. + + + + Instead of showing AC power state, show low battery state. In this case will return + zero if all batteries are currently discharging and below 5% of maximum charge. Returns non-zero + otherwise. + + diff --git a/src/ac-power/ac-power.c b/src/ac-power/ac-power.c index 62181a83df..f027c31208 100644 --- a/src/ac-power/ac-power.c +++ b/src/ac-power/ac-power.c @@ -8,12 +8,18 @@ static bool arg_verbose = false; +static enum { + ACTION_AC_POWER, + ACTION_LOW, +} arg_action = ACTION_AC_POWER; + static void help(void) { printf("%s\n\n" "Report whether we are connected to an external power source.\n\n" " -h --help Show this help\n" " --version Show package version\n" - " -v --verbose Show state as text\n", + " -v --verbose Show state as text\n" + " --low Checks if battery is discharing and low\n", program_invocation_short_name); } @@ -21,12 +27,14 @@ static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, + ARG_LOW, }; static const struct option options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, { "verbose", no_argument, NULL, 'v' }, + { "low", no_argument, NULL, ARG_LOW }, {} }; @@ -50,6 +58,10 @@ static int parse_argv(int argc, char *argv[]) { arg_verbose = true; break; + case ARG_LOW: + arg_action = ACTION_LOW; + break; + case '?': return -EINVAL; @@ -78,9 +90,15 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - r = on_ac_power(); - if (r < 0) - return log_error_errno(r, "Failed to read AC status: %m"); + if (arg_action == ACTION_AC_POWER) { + r = on_ac_power(); + if (r < 0) + return log_error_errno(r, "Failed to read AC status: %m"); + } else { + r = battery_is_discharging_and_low(); + if (r < 0) + return log_error_errno(r, "Failed to read battery discharging + low status: %m"); + } if (arg_verbose) puts(yes_no(r));