diff --git a/man/rules/meson.build b/man/rules/meson.build
index 74027f35a5..f924f551eb 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 0000000000..58719c795c
--- /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 275ffb491f..fff58ccd24 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 2833125ed9..acc0c2e980 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 b64639622e..876a5a91e6 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 0000000000..14bb870ca4
--- /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 dd96af6894..2793bc748f 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 0000000000..4b14049746
--- /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 dff7b3904f..a552dd6c3f 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'],