From e3d4148d50909119c4e9327e6ad96d3ca7f4661a Mon Sep 17 00:00:00 2001 From: OMOJOLA JOSHUA Date: Wed, 28 Jun 2023 14:04:42 +0100 Subject: [PATCH] PID1: detect battery level in initrd and if low refuse continuing to boot, print message and shut down. --- man/rules/meson.build | 1 + man/systemd-battery-check.xml | 63 +++++++++++ meson.build | 10 ++ src/basic/glyph-util.c | 6 +- src/basic/glyph-util.h | 11 +- src/battery-check/battery-check.c | 157 ++++++++++++++++++++++++++ src/test/test-locale-util.c | 1 + units/initrd-battery-check.service.in | 21 ++++ units/meson.build | 4 + 9 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 man/systemd-battery-check.xml create mode 100644 src/battery-check/battery-check.c create mode 100644 units/initrd-battery-check.service.in diff --git a/man/rules/meson.build b/man/rules/meson.build index 74027f35a5d..f924f551ebc 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -887,6 +887,7 @@ manpages = [ ''], ['systemd-ask-password', '1', [], ''], ['systemd-backlight@.service', '8', ['systemd-backlight'], 'ENABLE_BACKLIGHT'], + ['systemd-battery-check', '1', [], ''], ['systemd-binfmt.service', '8', ['systemd-binfmt'], 'ENABLE_BINFMT'], ['systemd-bless-boot-generator', '8', [], 'ENABLE_BOOTLOADER'], ['systemd-bless-boot.service', diff --git a/man/systemd-battery-check.xml b/man/systemd-battery-check.xml new file mode 100644 index 00000000000..58719c795c9 --- /dev/null +++ b/man/systemd-battery-check.xml @@ -0,0 +1,63 @@ + + + + + + + + systemd-battery-check + systemd + + + + systemd-battery-check + 1 + + + + systemd-battery-check + Checks battery level to see whether there's enough charge. + + + + + systemd-battery-check + OPTIONS + + + + + Description + + systemd-battery-check is used to check the battery level during the early boot + stage to determine whether there's sufficient battery power to carry on with the booting process. + The tool returns success if the device is connected to an AC power source + or if the battery charge is greater than 5%. It returns failure otherwise. + + + + Options + + The following options are understood: + + + + + + + + + Exit status + + On success (running on AC power or battery capacity greater than 5%), 0 is returned, a non-zero failure code otherwise. + + + + See Also + + systemd1 + + + + diff --git a/meson.build b/meson.build index 275ffb491f8..fff58ccd243 100644 --- a/meson.build +++ b/meson.build @@ -3845,6 +3845,16 @@ public_programs += executable( install : true, install_dir : rootbindir) +public_programs += executable( + 'systemd-battery-check', + 'src/battery-check/battery-check.c', + include_directories : includes, + link_with : [libshared], + dependencies : [userspace, versiondep], + install_rpath : rootpkglibdir, + install_dir : rootlibexecdir, + install : true) + # Protecting files from the distro in /usr doesn't make sense since they can be trivially accessed otherwise, # so don't restrict the access mode in /usr. That doesn't apply to /etc, so we do restrict the access mode # there. diff --git a/src/basic/glyph-util.c b/src/basic/glyph-util.c index 2833125ed99..acc0c2e980a 100644 --- a/src/basic/glyph-util.c +++ b/src/basic/glyph-util.c @@ -23,7 +23,7 @@ bool emoji_enabled(void) { return cached_emoji_enabled; } -const char *special_glyph(SpecialGlyph code) { +const char *special_glyph_full(SpecialGlyph code, bool force_utf) { /* A list of a number of interesting unicode glyphs we can use to decorate our output. It's probably wise to be * conservative here, and primarily stick to the glyphs defined in the eurlatgr font, so that display still @@ -71,6 +71,7 @@ const char *special_glyph(SpecialGlyph code) { [SPECIAL_GLYPH_RECYCLING] = "~", [SPECIAL_GLYPH_DOWNLOAD] = "\\", [SPECIAL_GLYPH_SPARKLES] = "*", + [SPECIAL_GLYPH_LOW_BATTERY] = "!", [SPECIAL_GLYPH_WARNING_SIGN] = "!", }, @@ -129,6 +130,7 @@ const char *special_glyph(SpecialGlyph code) { [SPECIAL_GLYPH_RECYCLING] = u8"♻️", /* actually called: UNIVERSAL RECYCLNG SYMBOL */ [SPECIAL_GLYPH_DOWNLOAD] = u8"⤵️", /* actually called: RIGHT ARROW CURVING DOWN */ [SPECIAL_GLYPH_SPARKLES] = u8"✨", + [SPECIAL_GLYPH_LOW_BATTERY] = u8"🪫", [SPECIAL_GLYPH_WARNING_SIGN] = u8"⚠️", }, }; @@ -137,5 +139,5 @@ const char *special_glyph(SpecialGlyph code) { return NULL; assert(code < _SPECIAL_GLYPH_MAX); - return draw_table[code >= _SPECIAL_GLYPH_FIRST_EMOJI ? emoji_enabled() : is_locale_utf8()][code]; + return draw_table[force_utf || (code >= _SPECIAL_GLYPH_FIRST_EMOJI ? emoji_enabled() : is_locale_utf8())][code]; } diff --git a/src/basic/glyph-util.h b/src/basic/glyph-util.h index b64639622ee..876a5a91e60 100644 --- a/src/basic/glyph-util.h +++ b/src/basic/glyph-util.h @@ -44,15 +44,24 @@ typedef enum SpecialGlyph { SPECIAL_GLYPH_RECYCLING, SPECIAL_GLYPH_DOWNLOAD, SPECIAL_GLYPH_SPARKLES, + SPECIAL_GLYPH_LOW_BATTERY, SPECIAL_GLYPH_WARNING_SIGN, _SPECIAL_GLYPH_MAX, _SPECIAL_GLYPH_INVALID = -EINVAL, } SpecialGlyph; -const char *special_glyph(SpecialGlyph code) _const_; +const char *special_glyph_full(SpecialGlyph code, bool force_utf) _const_; bool emoji_enabled(void); +static inline const char *special_glyph(SpecialGlyph code) { + return special_glyph_full(code, false); +} + +static inline const char *special_glyph_force_utf(SpecialGlyph code) { + return special_glyph_full(code, true); +} + static inline const char *special_glyph_check_mark(bool b) { return b ? special_glyph(SPECIAL_GLYPH_CHECK_MARK) : special_glyph(SPECIAL_GLYPH_CROSS_MARK); } diff --git a/src/battery-check/battery-check.c b/src/battery-check/battery-check.c new file mode 100644 index 00000000000..14bb870ca40 --- /dev/null +++ b/src/battery-check/battery-check.c @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include + +#include "battery-util.h" +#include "build.h" +#include "constants.h" +#include "errno-util.h" +#include "glyph-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "log.h" +#include "main-func.h" +#include "socket-util.h" +#include "terminal-util.h" + +static void help(void) { + printf("%s\n\n" + "Checks battery level to see whether there's enough charge.\n\n" + " -h --help Show this help\n" + " --version Show package version\n", + program_invocation_short_name); +} + +static void battery_check_send_plymouth_message(char *message, const char *mode) { + assert(message); + assert(mode); + + int r; + static const union sockaddr_union sa = PLYMOUTH_SOCKET; + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *plymouth_message = NULL; + + int c = asprintf(&plymouth_message, + "C\x02%c%s%c" + "M\x02%c%s%c", + (int) strlen(mode) + 1, mode, '\x00', + (int) strlen(message) + 1, message, '\x00'); + if (c < 0) + return (void) log_oom(); + + /* We set SOCK_NONBLOCK here so that we rather drop the + * message than wait for plymouth */ + fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) + return (void) log_warning_errno(errno, "socket() failed: %m"); + + if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) + return (void) log_full_errno(IN_SET(errno, EAGAIN, ENOENT) || ERRNO_IS_DISCONNECT(errno) ? LOG_DEBUG : LOG_WARNING, errno, "Connection to plymouth failed: %m"); + + r = loop_write(fd, plymouth_message, c, /* do_poll = */ false); + if (r < 0) + return (void) log_full_errno(IN_SET(r, -EAGAIN, -ENOENT) || ERRNO_IS_DISCONNECT(r) ? +LOG_DEBUG : LOG_WARNING, r, "Failed to write to plymouth, ignoring: %m"); +} + +static int parse_argv(int argc, char * argv[]) { + + enum { + ARG_VERSION = 0x100, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + if (optind < argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s takes no argument.", + program_invocation_short_name); + return 1; +} + +static int run(int argc, char *argv[]) { + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + r = battery_is_discharging_and_low(); + if (r < 0) { + log_warning_errno(r, "Failed to check battery status, ignoring: %m"); + return 0; + } + + if (r > 0) { + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *message = NULL, *plymouth_message = NULL, *ac_message = NULL; + + if (asprintf(&message, "%s Battery level critically low. Please connect your charger or the system will power off in 10 seconds.", special_glyph(SPECIAL_GLYPH_LOW_BATTERY)) < 0) + return log_oom(); + + if (asprintf(&plymouth_message, "%s Battery level critically low. Please connect your charger or the system will power off in 10 seconds.", special_glyph_force_utf(SPECIAL_GLYPH_LOW_BATTERY)) < 0) + return log_oom(); + + log_emergency("%s", message); + + fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + log_warning_errno(fd, "Failed to open console, ignoring: %m"); + else + dprintf(fd, ANSI_HIGHLIGHT_RED "%s" ANSI_NORMAL "\n", message); + + battery_check_send_plymouth_message(plymouth_message, "shutdown"); + sleep(10); + + r = battery_is_discharging_and_low(); + if (r > 0) { + log_emergency("Battery level critically low, powering off."); + return r; + } + if (r < 0) + return log_warning_errno(r, "Failed to check battery status, ignoring: %m"); + + if (asprintf(&ac_message, "A.C. power restored, continuing") < 0) + return log_oom(); + + log_info("%s",ac_message); + dprintf(fd, "%s\n", ac_message); + battery_check_send_plymouth_message(ac_message, "boot-up"); + } + return r; +} + +DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/test/test-locale-util.c b/src/test/test-locale-util.c index dd96af6894a..2793bc748fe 100644 --- a/src/test/test-locale-util.c +++ b/src/test/test-locale-util.c @@ -119,6 +119,7 @@ TEST(dump_special_glyphs) { dump_glyph(SPECIAL_GLYPH_RECYCLING); dump_glyph(SPECIAL_GLYPH_DOWNLOAD); dump_glyph(SPECIAL_GLYPH_SPARKLES); + dump_glyph(SPECIAL_GLYPH_LOW_BATTERY); dump_glyph(SPECIAL_GLYPH_WARNING_SIGN); } diff --git a/units/initrd-battery-check.service.in b/units/initrd-battery-check.service.in new file mode 100644 index 00000000000..4b140497462 --- /dev/null +++ b/units/initrd-battery-check.service.in @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Check battery level during early boot +Documentation=man:systemd-battery-check(1) +DefaultDependencies=no +AssertPathExists=/etc/initrd-release +Before=local-fs-pre.target + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart={{ROOTLIBEXECDIR}}/systemd-battery-check +FailureAction=poweroff-force diff --git a/units/meson.build b/units/meson.build index dff7b3904f5..a552dd6c3fb 100644 --- a/units/meson.build +++ b/units/meson.build @@ -55,6 +55,10 @@ units = [ 'file' : 'hybrid-sleep.target', 'conditions' : ['ENABLE_HIBERNATE'], }, + { + 'file' : 'initrd-battery-check.service.in', + 'conditions' : ['ENABLE_INITRD'], + }, { 'file' : 'initrd-cleanup.service', 'conditions' : ['ENABLE_INITRD'], -- 2.47.3