udev: String substitutions can be done in ENV, too
[systemd_ALT.git] / src / battery-check / battery-check.c
blobc3091d3474fb0b02d980ea52731fdb3ac52f5f00
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include <errno.h>
4 #include <getopt.h>
5 #include <sys/socket.h>
6 #include <unistd.h>
8 #include "battery-util.h"
9 #include "build.h"
10 #include "constants.h"
11 #include "errno-util.h"
12 #include "glyph-util.h"
13 #include "fd-util.h"
14 #include "io-util.h"
15 #include "log.h"
16 #include "main-func.h"
17 #include "parse-util.h"
18 #include "pretty-print.h"
19 #include "proc-cmdline.h"
20 #include "socket-util.h"
21 #include "terminal-util.h"
22 #include "time-util.h"
24 #define BATTERY_LOW_MESSAGE \
25 "Battery level critically low. Please connect your charger or the system will power off in 10 seconds."
26 #define BATTERY_RESTORED_MESSAGE \
27 "A.C. power restored, continuing."
29 static bool arg_doit = true;
31 static int help(void) {
32 _cleanup_free_ char *link = NULL;
33 int r;
35 r = terminal_urlify_man("systemd-battery-check", "8", &link);
36 if (r < 0)
37 return log_oom();
39 printf("%s\n\n"
40 "%sCheck battery level to see whether there's enough charge.%s\n\n"
41 " -h --help Show this help\n"
42 " --version Show package version\n"
43 "\nSee the %s for details.\n",
44 program_invocation_short_name,
45 ansi_highlight(),
46 ansi_normal(),
47 link);
49 return 0;
52 static bool ERRNO_IS_NO_PLYMOUTH(int r) {
53 return IN_SET(abs(r), EAGAIN, ENOENT) || ERRNO_IS_DISCONNECT(r);
56 static int plymouth_send_message(const char *mode, const char *message) {
57 static const union sockaddr_union sa = PLYMOUTH_SOCKET;
58 _cleanup_free_ char *plymouth_message = NULL;
59 _cleanup_close_ int fd = -EBADF;
60 int c, r;
62 assert(mode);
63 assert(message);
65 c = asprintf(&plymouth_message,
66 "C\x02%c%s%c"
67 "M\x02%c%s%c",
68 (int) strlen(mode) + 1, mode, '\x00',
69 (int) strlen(message) + 1, message, '\x00');
70 if (c < 0)
71 return log_oom();
73 /* We set SOCK_NONBLOCK here so that we rather drop the
74 * message than wait for plymouth */
75 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
76 if (fd < 0)
77 return log_warning_errno(errno, "socket() failed: %m");
79 if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0)
80 return log_full_errno(ERRNO_IS_NO_PLYMOUTH(errno) ? LOG_DEBUG : LOG_WARNING, errno,
81 "Failed to connect to plymouth: %m");
83 r = loop_write(fd, plymouth_message, c, /* do_poll = */ false);
84 if (r < 0)
85 return log_full_errno(ERRNO_IS_NO_PLYMOUTH(r) ? LOG_DEBUG : LOG_WARNING, r,
86 "Failed to write to plymouth: %m");
88 return 0;
91 static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
92 int r;
94 assert(key);
96 if (streq(key, "systemd.battery-check")) {
98 r = value ? parse_boolean(value) : 1;
99 if (r < 0)
100 log_warning_errno(r, "Failed to parse %s switch, ignoring: %s", key, value);
101 else
102 arg_doit = r;
105 return 0;
108 static int parse_argv(int argc, char * argv[]) {
110 enum {
111 ARG_VERSION = 0x100,
114 static const struct option options[] = {
115 { "help", no_argument, NULL, 'h' },
116 { "version", no_argument, NULL, ARG_VERSION },
120 int c;
122 assert(argc >= 0);
123 assert(argv);
125 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
127 switch (c) {
129 case 'h':
130 return help();
132 case ARG_VERSION:
133 return version();
135 case '?':
136 return -EINVAL;
138 default:
139 assert_not_reached();
142 if (optind < argc)
143 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
144 "%s takes no argument.",
145 program_invocation_short_name);
146 return 1;
149 static int run(int argc, char *argv[]) {
150 _cleanup_free_ char *plymouth_message = NULL;
151 _cleanup_close_ int fd = -EBADF;
152 int r;
154 log_setup();
156 r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX);
157 if (r < 0)
158 log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
160 r = parse_argv(argc, argv);
161 if (r <= 0)
162 return r;
164 if (!arg_doit) {
165 log_info("Checking battery status and AC power existence is disabled by the kernel command line, skipping execution.");
166 return 0;
169 r = battery_is_discharging_and_low();
170 if (r < 0) {
171 log_warning_errno(r, "Failed to check battery status, ignoring: %m");
172 return 0;
174 if (r == 0)
175 return 0;
177 log_emergency("%s " BATTERY_LOW_MESSAGE, special_glyph(SPECIAL_GLYPH_LOW_BATTERY));
179 fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
180 if (fd < 0)
181 log_warning_errno(fd, "Failed to open console, ignoring: %m");
182 else
183 dprintf(fd, ANSI_HIGHLIGHT_RED "%s " BATTERY_LOW_MESSAGE ANSI_NORMAL "\n",
184 special_glyph_full(SPECIAL_GLYPH_LOW_BATTERY, /* force_utf = */ false));
186 if (asprintf(&plymouth_message, "%s " BATTERY_LOW_MESSAGE,
187 special_glyph_full(SPECIAL_GLYPH_LOW_BATTERY, /* force_utf = */ true)) < 0)
188 return log_oom();
190 (void) plymouth_send_message("shutdown", plymouth_message);
192 usleep_safe(10 * USEC_PER_SEC);
194 r = battery_is_discharging_and_low();
195 if (r < 0)
196 return log_warning_errno(r, "Failed to check battery status, assuming not charged yet, powering off: %m");
197 if (r > 0) {
198 log_emergency("Battery level critically low, powering off.");
199 return r;
202 log_info(BATTERY_RESTORED_MESSAGE);
203 if (fd >= 0)
204 dprintf(fd, BATTERY_RESTORED_MESSAGE "\n");
205 (void) plymouth_send_message("boot-up", BATTERY_RESTORED_MESSAGE);
207 return 0;
210 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);