]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #27830 from 1awesomeJ/initrd
authorLennart Poettering <lennart@poettering.net>
Wed, 28 Jun 2023 17:18:03 +0000 (19:18 +0200)
committerGitHub <noreply@github.com>
Wed, 28 Jun 2023 17:18:03 +0000 (19:18 +0200)
PID1: Detect battery level in initrd and if low refuse continuing to …

TODO
man/rules/meson.build
man/systemd-battery-check.xml [new file with mode: 0644]
meson.build
src/basic/glyph-util.c
src/basic/glyph-util.h
src/battery-check/battery-check.c [new file with mode: 0644]
src/test/test-locale-util.c
units/initrd-battery-check.service.in [new file with mode: 0644]
units/meson.build

diff --git a/TODO b/TODO
index 2322b4740be1c545ac90aad9e35a93d13c170c06..5a5f6217ff97d01c3b948c3cad16a9f638eff106 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1323,8 +1323,6 @@ Features:
 * when configuring loopback netif, and it fails due to EPERM, eat up error if
   it happens to be set up alright already.
 
-* at boot: check if battery above some threshold, if not power off again after explanation
-
 * userdb: add field for ambient caps, so that a user can have CAP_WAKE_ALARM
   for example. And add code that resets ambient caps for all services by
   default.
index 74027f35a5dd4f8e74e495df42632297fa8ab243..f924f551ebc1899596c4234342f99290133726c8 100644 (file)
@@ -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 (file)
index 0000000..58719c7
--- /dev/null
@@ -0,0 +1,63 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+
+<refentry id="systemd-battery-check" xmlns:xi="http://www.w3.org/2001/XInclude">
+
+  <refentryinfo>
+    <title>systemd-battery-check</title>
+    <productname>systemd</productname>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>systemd-battery-check</refentrytitle>
+    <manvolnum>1</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>systemd-battery-check</refname>
+    <refpurpose>Checks battery level to see whether there's enough charge.</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>systemd-battery-check</command>
+      <arg choice="opt" rep="repeat">OPTIONS</arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para><command>systemd-battery-check</command> 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.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>Options</title>
+
+    <para>The following options are understood:</para>
+
+    <variablelist>
+      <xi:include href="standard-options.xml" xpointer="help" />
+      <xi:include href="standard-options.xml" xpointer="version" />
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>Exit status</title>
+
+    <para>On success (running on AC power or battery capacity greater than 5%), 0 is returned, a non-zero failure code otherwise.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para>
+      <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+    </para>
+  </refsect1>
+
+</refentry>
index e6ea55c0d9f4e022abb23d477b6ce4bc33742b56..c50de25146d261bd4f5e80d3a6cacf667ed86322 100644 (file)
@@ -3846,6 +3846,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.
index 2833125ed99f07efa5a244a231ca8e6f153a6546..acc0c2e980ac4707113731bd999b71418daf9d0f 100644 (file)
@@ -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];
 }
index b64639622eee6c8d361ca6a31979dc0349df867b..876a5a91e604ddd156fbf79f5f59186c676d4c3f 100644 (file)
@@ -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 (file)
index 0000000..14bb870
--- /dev/null
@@ -0,0 +1,157 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <getopt.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#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);
index dd96af6894a7f23b24e45bac520e5b70bc89c43a..2793bc748fead4e40ca907eef1e60b4322da938f 100644 (file)
@@ -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 (file)
index 0000000..4b14049
--- /dev/null
@@ -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
index dff7b3904f5ef764208a474fbeaae0c587967bda..a552dd6c3fb0458af12e236db7da6f8ad088357d 100644 (file)
@@ -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'],