/* 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 "parse-util.h" #include "pretty-print.h" #include "proc-cmdline.h" #include "socket-util.h" #include "terminal-util.h" #include "time-util.h" #define BATTERY_LOW_MESSAGE \ "Battery level critically low. Please connect your charger or the system will power off in 10 seconds." #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; r = terminal_urlify_man("systemd-battery-check", "8", &link); if (r < 0) return log_oom(); printf("%s\n\n" "%sCheck battery level to see whether there's enough charge.%s\n\n" " -h --help Show this help\n" " --version Show package version\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), ansi_normal(), link); return 0; } static bool ERRNO_IS_NO_PLYMOUTH(int r) { return IN_SET(abs(r), EAGAIN, ENOENT) || ERRNO_IS_DISCONNECT(r); } static int plymouth_send_message(const char *mode, const char *message) { static const union sockaddr_union sa = PLYMOUTH_SOCKET; _cleanup_free_ char *plymouth_message = NULL; _cleanup_close_ int fd = -EBADF; int c, r; assert(mode); assert(message); 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 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 log_warning_errno(errno, "socket() failed: %m"); if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) return log_full_errno(ERRNO_IS_NO_PLYMOUTH(errno) ? LOG_DEBUG : LOG_WARNING, errno, "Failed to connect to plymouth: %m"); r = loop_write(fd, plymouth_message, c, /* do_poll = */ false); if (r < 0) return log_full_errno(ERRNO_IS_NO_PLYMOUTH(r) ? LOG_DEBUG : LOG_WARNING, r, "Failed to write to plymouth: %m"); 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 { 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': return help(); 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[]) { _cleanup_free_ char *plymouth_message = NULL; _cleanup_close_ int fd = -EBADF; int r; 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"); return 0; } if (r == 0) return 0; log_emergency("%s " BATTERY_LOW_MESSAGE, special_glyph(SPECIAL_GLYPH_LOW_BATTERY)); 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 " BATTERY_LOW_MESSAGE ANSI_NORMAL "\n", special_glyph_full(SPECIAL_GLYPH_LOW_BATTERY, /* force_utf = */ false)); if (asprintf(&plymouth_message, "%s " BATTERY_LOW_MESSAGE, special_glyph_full(SPECIAL_GLYPH_LOW_BATTERY, /* force_utf = */ true)) < 0) return log_oom(); (void) plymouth_send_message("shutdown", plymouth_message); usleep_safe(10 * USEC_PER_SEC); r = battery_is_discharging_and_low(); if (r < 0) return log_warning_errno(r, "Failed to check battery status, assuming not charged yet, powering off: %m"); if (r > 0) { log_emergency("Battery level critically low, powering off."); return r; } log_info(BATTERY_RESTORED_MESSAGE); if (fd >= 0) dprintf(fd, BATTERY_RESTORED_MESSAGE "\n"); (void) plymouth_send_message("boot-up", BATTERY_RESTORED_MESSAGE); return 0; } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);