1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2015 Canonical
9 Didier Roche <didrocks@ubuntu.com>
11 systemd is free software; you can redistribute it and/or modify it
12 under the terms of the GNU Lesser General Public License as published by
13 the Free Software Foundation; either version 2.1 of the License, or
14 (at your option) any later version.
16 systemd is distributed in the hope that it will be useful, but
17 WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 Lesser General Public License for more details.
21 You should have received a copy of the GNU Lesser General Public License
22 along with systemd; If not, see <http://www.gnu.org/licenses/>.
32 #include <sys/socket.h>
33 #include <sys/types.h>
37 #include "sd-daemon.h"
40 #include "event-util.h"
44 #include "socket-util.h"
48 #define IDLE_TIME_SECONDS 30
49 #define PLYMOUTH_REQUEST_KEY "K\2\2\3"
53 typedef struct Client {
54 struct Manager *manager;
67 LIST_FIELDS(struct Client, clients);
70 typedef struct Manager {
73 LIST_HEAD(Client, clients);
83 sd_event_source *plymouth_event_source;
84 bool plymouth_cancel_sent;
86 bool cancel_requested;
89 static int connect_plymouth(Manager *m);
90 static int update_global_progress(Manager *m);
91 static void manager_free(Manager *m);
92 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
93 #define _cleanup_manager_free_ _cleanup_(manager_freep)
95 static double compute_percent(int pass, size_t cur, size_t max) {
96 /* Values stolen from e2fsck */
98 static const double pass_table[] = {
99 0, 70, 90, 92, 95, 100
105 if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
108 return pass_table[pass-1] +
109 (pass_table[pass] - pass_table[pass-1]) *
113 static int request_cancel_client(Client *current) {
114 FsckdMessage cancel_msg = {
120 n = send(current->fd, &cancel_msg, sizeof(FsckdMessage), 0);
122 return log_warning_errno(errno, "Cannot send cancel to fsck on (%u:%u): %m", major(current->devnum), minor(current->devnum));
123 if ((size_t) n < sizeof(FsckdMessage)) {
124 log_warning("Short send when sending cancel to fsck on (%u:%u).", major(current->devnum), minor(current->devnum));
128 current->cancelled = true;
132 static void client_free(Client *c) {
136 LIST_REMOVE(clients, c->manager->clients, c);
142 static void plymouth_disconnect(Manager *m) {
145 m->plymouth_event_source = sd_event_source_unref(m->plymouth_event_source);
146 m->plymouth_fd = safe_close(m->plymouth_fd);
147 m->plymouth_cancel_sent = false;
150 static int plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
151 Manager *m = userdata;
158 l = read(m->plymouth_fd, buffer, sizeof(buffer));
160 log_warning_errno(errno, "Got error while reading from plymouth: %m");
161 plymouth_disconnect(m);
165 plymouth_disconnect(m);
169 if (buffer[0] == '\15')
170 log_error("Message update to plymouth wasn't delivered successfully");
172 /* the only answer support type we requested is a key interruption */
173 if (buffer[0] == '\2' && buffer[5] == '\3') {
174 m->cancel_requested = true;
176 /* cancel all connected clients */
177 LIST_FOREACH(clients, current, m->clients)
178 request_cancel_client(current);
184 static int send_message_plymouth_socket(int plymouth_fd, const char *message, bool update) {
185 _cleanup_free_ char *packet = NULL;
192 if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0)
195 return loop_write(plymouth_fd, packet, n + 1, true);
198 static int send_message_plymouth(Manager *m, const char *message) {
199 const char *plymouth_cancel_message = NULL;
202 r = connect_plymouth(m);
206 if (!m->plymouth_cancel_sent) {
208 /* Indicate to plymouth that we listen to Ctrl+C */
209 r = loop_write(m->plymouth_fd, PLYMOUTH_REQUEST_KEY, sizeof(PLYMOUTH_REQUEST_KEY), true);
211 return log_warning_errno(r, "Can't send to plymouth cancel key: %m");
213 m->plymouth_cancel_sent = true;
215 plymouth_cancel_message = strjoina("fsckd-cancel-msg:", _("Press Ctrl+C to cancel all filesystem checks in progress"));
217 r = send_message_plymouth_socket(m->plymouth_fd, plymouth_cancel_message, false);
219 log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m");
221 } else if (m->numdevices == 0) {
223 m->plymouth_cancel_sent = false;
225 r = send_message_plymouth_socket(m->plymouth_fd, "", false);
227 log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
230 r = send_message_plymouth_socket(m->plymouth_fd, message, true);
232 return log_warning_errno(r, "Couldn't send \"%s\" to plymouth: %m", message);
237 static int update_global_progress(Manager *m) {
238 Client *current = NULL;
239 _cleanup_free_ char *console_message = NULL;
240 _cleanup_free_ char *fsck_message = NULL;
241 int current_numdevices = 0, l = 0, r;
242 double current_percent = 100;
244 /* get the overall percentage */
245 LIST_FOREACH(clients, current, m->clients) {
246 current_numdevices++;
248 /* right now, we only keep the minimum % of all fsckd processes. We could in the future trying to be
249 linear, but max changes and corresponds to the pass. We have all the informations into fsckd
250 already if we can treat that in a smarter way. */
251 current_percent = MIN(current_percent, current->percent);
254 /* update if there is anything user-visible to update */
255 if (fabs(current_percent - m->percent) > 0.001 || current_numdevices != m->numdevices) {
256 m->numdevices = current_numdevices;
257 m->percent = current_percent;
259 if (asprintf(&console_message,
260 ngettext("Checking in progress on %d disk (%3.1f%% complete)",
261 "Checking in progress on %d disks (%3.1f%% complete)", m->numdevices),
262 m->numdevices, m->percent) < 0)
265 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
268 /* write to console */
270 fprintf(m->console, "\r%s\r%n", console_message, &l);
274 /* try to connect to plymouth and send message */
275 r = send_message_plymouth(m, fsck_message);
277 log_debug("Couldn't send message to plymouth");
285 static int connect_plymouth(Manager *m) {
286 union sockaddr_union sa = PLYMOUTH_SOCKET;
289 /* try to connect or reconnect if sending a message */
290 if (m->plymouth_fd >= 0)
293 m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
294 if (m->plymouth_fd < 0)
295 return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
297 if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
298 r = log_warning_errno(errno, "Couldn't connect to plymouth: %m");
302 r = sd_event_add_io(m->event, &m->plymouth_event_source, m->plymouth_fd, EPOLLIN, plymouth_feedback_handler, m);
304 log_warning_errno(r, "Can't listen to plymouth socket: %m");
311 plymouth_disconnect(m);
315 static int progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
316 Client *client = userdata;
318 FsckProgress fsck_data;
325 /* check first if we need to cancel this client */
326 if (m->cancel_requested) {
327 if (!client->cancelled)
328 request_cancel_client(client);
331 /* ensure we have enough data to read */
332 r = ioctl(fd, FIONREAD, &buflen);
333 if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
334 if (client->buflen != buflen)
335 client->buflen = buflen;
336 /* we got twice the same size from a bad behaving client, kick it off the list */
338 log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
340 r = update_global_progress(m);
342 log_warning_errno(r, "Couldn't update global progress: %m");
347 /* read actual data */
348 r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
350 log_debug("Fsck client connected to fd %d disconnected", client->fd);
352 } else if (r > 0 && r != sizeof(FsckProgress))
353 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
354 else if (r > 0 && r == sizeof(FsckProgress)) {
355 client->devnum = fsck_data.devnum;
356 client->cur = fsck_data.cur;
357 client->max = fsck_data.max;
358 client->pass = fsck_data.pass;
359 client->percent = compute_percent(client->pass, client->cur, client->max);
360 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
361 major(client->devnum), minor(client->devnum),
362 client->cur, client->max, client->pass, client->percent);
364 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
366 r = update_global_progress(m);
368 log_warning_errno(r, "Couldn't update global progress: %m");
373 static int new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
374 Manager *m = userdata;
375 Client *client = NULL;
376 int new_client_fd, r;
380 /* Initialize and list new clients */
381 new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
382 if (new_client_fd < 0)
383 return log_error_errno(errno, "Couldn't accept a new connection: %m");
385 log_debug("New fsck client connected to fd: %d", new_client_fd);
387 client = new0(Client, 1);
390 client->fd = new_client_fd;
392 LIST_PREPEND(clients, m->clients, client);
393 r = sd_event_add_io(m->event, NULL, client->fd, EPOLLIN, progress_handler, client);
398 /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
399 if (m->cancel_requested)
400 request_cancel_client(client);
405 static void manager_free(Manager *m) {
409 /* clear last line */
410 if (m->console && m->clear > 0) {
413 fputc('\r', m->console);
414 for (j = 0; j < (unsigned) m->clear; j++)
415 fputc(' ', m->console);
416 fputc('\r', m->console);
420 plymouth_disconnect(m);
422 safe_close(m->connection_fd);
428 client_free(m->clients);
430 sd_event_unref(m->event);
435 static int manager_new(Manager **ret, int fd) {
436 _cleanup_manager_free_ Manager *m = NULL;
441 m = new0(Manager, 1);
445 r = sd_event_default(&m->event);
449 m->connection_fd = fd;
450 if (access("/run/systemd/show-status", F_OK) >= 0) {
451 m->console = fopen("/dev/console", "we");
453 return log_warning_errno(errno, "Can't connect to /dev/console: %m");
464 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
470 r = sd_event_get_state(e);
473 if (r == SD_EVENT_FINISHED)
476 r = sd_event_run(e, timeout);
480 /* timeout reached */
487 r = sd_event_get_exit_code(e, &code);
494 static void help(void) {
495 printf("%s [OPTIONS...]\n\n"
496 "Capture fsck progress and forward one stream to plymouth\n\n"
497 " -h --help Show this help\n"
498 " --version Show package version\n",
499 program_invocation_short_name);
502 static int parse_argv(int argc, char *argv[]) {
509 static const struct option options[] = {
510 { "help", no_argument, NULL, 'h' },
511 { "version", no_argument, NULL, ARG_VERSION },
520 while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
528 puts(PACKAGE_STRING);
529 puts(SYSTEMD_FEATURES);
536 assert_not_reached("Unhandled option");
540 log_error("Extraneous arguments");
547 int main(int argc, char *argv[]) {
548 _cleanup_manager_free_ Manager *m = NULL;
552 log_set_target(LOG_TARGET_AUTO);
553 log_parse_environment();
557 r = parse_argv(argc, argv);
561 n = sd_listen_fds(0);
563 log_error("Too many file descriptors received.");
567 fd = SD_LISTEN_FDS_START + 0;
569 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
571 r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
576 r = manager_new(&m, fd);
578 log_error_errno(r, "Failed to allocate manager: %m");
582 r = sd_event_add_io(m->event, NULL, fd, EPOLLIN, new_connection_handler, m);
584 log_error_errno(r, "Can't listen to connection socket: %m");
588 r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
590 log_error_errno(r, "Failed to run event loop: %m");
594 sd_event_get_exit_code(m->event, &r);
597 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;