diff --git a/NEWS b/NEWS index 185126f644..c1770565e9 100644 --- a/NEWS +++ b/NEWS @@ -698,7 +698,8 @@ CHANGES WITH 254 in spe: charge level of the system. In case the charge level is very low the user is notified (graphically via Plymouth – if available – as well as in text form on the console), and the system is turned off after a - 10s delay. + 10s delay. The feature can be disabled by passing + systemd.battery-check=0 through the kernel command line. * The 'passwdqc' library is now supported as an alternative to the 'pwquality' library and it can be selected at build time. diff --git a/man/systemd-battery-check.service.xml b/man/systemd-battery-check.service.xml index 2d9005696e..6f1b0ea687 100644 --- a/man/systemd-battery-check.service.xml +++ b/man/systemd-battery-check.service.xml @@ -62,6 +62,26 @@ + + Kernel Command Line + + The following variables are understood: + + + + systemd.battery-check=BOOL + + + Takes a boolean. If specified with false, systemd-battery-check command + will return immediately with exit status 0 without checking battery capacity and AC power + existence, and the service systemd-battery-check.service will succeed. This + may be useful when the command wrongly detects and reports battery capacity percentage or AC power + existence, or when you want to boot the system forcibly. + + + + + See Also diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c index 6203b79959..32d4f414cc 100644 --- a/src/basic/proc-cmdline.c +++ b/src/basic/proc-cmdline.c @@ -17,63 +17,95 @@ #include "strv.h" #include "virt.h" -int proc_cmdline_filter_pid1_args( - char **argv, /* input, may be reordered by this function. */ - char ***ret) { - +int proc_cmdline_filter_pid1_args(char **argv, char ***ret) { enum { COMMON_GETOPT_ARGS, SYSTEMD_GETOPT_ARGS, SHUTDOWN_GETOPT_ARGS, }; - static const struct option options[] = { COMMON_GETOPT_OPTIONS, SYSTEMD_GETOPT_OPTIONS, SHUTDOWN_GETOPT_OPTIONS, - {} }; + static const char *short_options = SYSTEMD_GETOPT_SHORT_OPTIONS; - int saved_optind, saved_opterr, saved_optopt, argc; - char *saved_optarg; - char **filtered; - size_t idx; + _cleanup_strv_free_ char **filtered = NULL; + int state, r; assert(argv); assert(ret); - /* Backup global variables. */ - saved_optind = optind; - saved_opterr = opterr; - saved_optopt = optopt; - saved_optarg = optarg; - - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). Here, we do not use - * the GNU extensions, but might be used previously. Hence, we need to always reset it. */ - optind = 0; - - /* Do not print an error message. */ - opterr = 0; + /* Currently, we do not support '-', '+', and ':' at the beginning. */ + assert(!IN_SET(short_options[0], '-', '+', ':')); /* Filter out all known options. */ - argc = strv_length(argv); - while (getopt_long(argc, argv, SYSTEMD_GETOPT_SHORT_OPTIONS, options, NULL) >= 0) - ; + state = no_argument; + STRV_FOREACH(p, strv_skip(argv, 1)) { + int prev_state = state; + const char *a = *p; - idx = optind; + /* Reset the state for the next step. */ + state = no_argument; - /* Restore global variables. */ - optind = saved_optind; - opterr = saved_opterr; - optopt = saved_optopt; - optarg = saved_optarg; + if (prev_state == required_argument || + (prev_state == optional_argument && a[0] != '-')) + /* Handled as an argument of the previous option, filtering out the string. */ + continue; - filtered = strv_copy(strv_skip(argv, idx)); - if (!filtered) - return -ENOMEM; + if (a[0] != '-') { + /* Not an option, accepting the string. */ + r = strv_extend(&filtered, a); + if (r < 0) + return r; + continue; + } - *ret = filtered; + if (a[1] == '-') { + if (a[2] == '\0') { + /* "--" is specified, accepting remaining strings. */ + r = strv_extend_strv(&filtered, strv_skip(p, 1), /* filter_duplicates = */ false); + if (r < 0) + return r; + break; + } + + /* long option, e.g. --foo */ + for (size_t i = 0; i < ELEMENTSOF(options); i++) { + const char *q = startswith(a + 2, options[i].name); + if (!q || !IN_SET(q[0], '=', '\0')) + continue; + + /* Found matching option, updating the state if necessary. */ + if (q[0] == '\0' && options[i].has_arg == required_argument) + state = required_argument; + + break; + } + continue; + } + + /* short option(s), e.g. -x or -xyz */ + while (a && *++a != '\0') + for (const char *q = short_options; *q != '\0'; q++) { + if (*q != *a) + continue; + + /* Found matching short option. */ + + if (q[1] == ':') { + /* An argument is required or optional, and remaining part + * is handled as argument if exists. */ + state = a[1] != '\0' ? no_argument : + q[2] == ':' ? optional_argument : required_argument; + + a = NULL; /* Not necessary to parse remaining part. */ + } + break; + } + } + + *ret = TAKE_PTR(filtered); return 0; } diff --git a/src/battery-check/battery-check.c b/src/battery-check/battery-check.c index ca13251ae0..c3091d3474 100644 --- a/src/battery-check/battery-check.c +++ b/src/battery-check/battery-check.c @@ -14,7 +14,9 @@ #include "io-util.h" #include "log.h" #include "main-func.h" +#include "parse-util.h" #include "pretty-print.h" +#include "proc-cmdline.h" #include "socket-util.h" #include "terminal-util.h" #include "time-util.h" @@ -24,6 +26,8 @@ #define BATTERY_RESTORED_MESSAGE \ "A.C. power restored, continuing." +static bool arg_doit = true; + static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -84,6 +88,23 @@ static int plymouth_send_message(const char *mode, const char *message) { return 0; } +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { + int r; + + assert(key); + + if (streq(key, "systemd.battery-check")) { + + r = value ? parse_boolean(value) : 1; + if (r < 0) + log_warning_errno(r, "Failed to parse %s switch, ignoring: %s", key, value); + else + arg_doit = r; + } + + return 0; +} + static int parse_argv(int argc, char * argv[]) { enum { @@ -132,10 +153,19 @@ static int run(int argc, char *argv[]) { log_setup(); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + r = parse_argv(argc, argv); if (r <= 0) return r; + if (!arg_doit) { + log_info("Checking battery status and AC power existence is disabled by the kernel command line, skipping execution."); + return 0; + } + r = battery_is_discharging_and_low(); if (r < 0) { log_warning_errno(r, "Failed to check battery status, ignoring: %m"); diff --git a/src/test/test-proc-cmdline.c b/src/test/test-proc-cmdline.c index 2d51dbe4b9..4fd9125378 100644 --- a/src/test/test-proc-cmdline.c +++ b/src/test/test-proc-cmdline.c @@ -284,14 +284,47 @@ TEST(proc_cmdline_filter_pid1_args) { test_proc_cmdline_filter_pid1_args_one("systemd\0", STRV_MAKE_EMPTY); + /* short option */ test_proc_cmdline_filter_pid1_args_one("systemd\0" - "hoge\0" - "-x\0" - "foo\0" - "--aaa\0" - "var\0", - STRV_MAKE("hoge", "foo", "var")); + "-a\0" /* unknown option */ + "-abc\0" /* unknown options */ + "-h\0" /* known option */ + "-hDbs\0" /* known options */ + "-hsx\0" /* mixed (known and unknown) options */ + "-z\0drop1\0" /* option with argument */ + "-z\0-z\0accept1\0" /* the second -z is handled as argument */ + "-az\0drop2\0" /* options with argument */ + "-za\0accept2\0" /* options with argument */ + "-z\0--\0-x\0", /* "--" is handled as argument */ + STRV_MAKE("accept1", "accept2")); + /* long option */ + test_proc_cmdline_filter_pid1_args_one("systemd\0" + "--unknown\0accept1\0" /* unknown option */ + "--system\0accept2\0" /* no argument */ + "--log-level\0drop1\0" /* required argument (separated with space) */ + "--log-level=drop2\0accept3\0" /* required argument (concatenated with '=') */ + "--log-level\0--log-level\0accept4\0" /* the second "--log-level" is handled as argument */ + "--log-level\0--\0-x\0" /* "--" is handled as argument */ + "--log-color\0--log-level\0drop3\0" /* optional argument ("--log-level" is handled as another option) */ + "--log-color\0accept5\0" /* optional argument (separated with space) */ + "--log-color=drop4\0accept6\0" /* optional argument (concatenated with '=') */ + "--log-color\0--\0" /* "--" is _not_ handled as argument, and remaining strings are accepted */ + "remaining\0-x\0--foo\0", + STRV_MAKE("accept1", "accept2", "accept3", "accept4", "accept5", "accept6", "remaining", "-x", "--foo")); + + /* test for "--" */ + test_proc_cmdline_filter_pid1_args_one("systemd\0" + "-a\0" + "--dropped\0" + "--\0" /* remaining strings are accepted */ + "-x\0" + "-abc\0" + "--hoge\0" + "accepted\0", + STRV_MAKE("-x", "-abc", "--hoge", "accepted")); + + /* test for space */ test_proc_cmdline_filter_pid1_args_one("/usr/lib/systemd/systemd\0" "--switched-root\0" "--system\0" diff --git a/units/systemd-battery-check.service.in b/units/systemd-battery-check.service.in index 61f4ae16e0..a6a0d26fde 100644 --- a/units/systemd-battery-check.service.in +++ b/units/systemd-battery-check.service.in @@ -12,6 +12,7 @@ Description=Check battery level during early boot Documentation=man:systemd-battery-check.service(8) ConditionVirtualization=no ConditionDirectoryNotEmpty=/sys/class/power_supply/ +ConditionKernelCommandLine=!systemd.battery-check=0 AssertPathExists=/etc/initrd-release DefaultDependencies=no After=plymouth-start.service