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"
50 #define CLIENTS_MAX 128
54 typedef struct Client {
55 struct Manager *manager;
68 sd_event_source *event_source;
70 LIST_FIELDS(struct Client, clients);
73 typedef struct Manager {
76 LIST_HEAD(Client, clients);
82 sd_event_source *connection_event_source;
84 bool show_status_console;
90 sd_event_source *plymouth_event_source;
91 bool plymouth_cancel_sent;
93 bool cancel_requested;
96 static void client_free(Client *c);
97 static void manager_free(Manager *m);
99 DEFINE_TRIVIAL_CLEANUP_FUNC(Client*, client_free);
100 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
102 static int manager_write_console(Manager *m, const char *message) {
103 _cleanup_fclose_ FILE *console = NULL;
109 if (!m->show_status_console)
112 /* Reduce the SAK window by opening and closing console on every request */
113 console = fopen("/dev/console", "we");
118 fprintf(console, "\r%s\r%n", message, &l);
119 if (m->clear < (size_t)l)
120 m->clear = (size_t)l;
122 fputc('\r', console);
123 for (j = 0; j < m->clear; j++)
125 fputc('\r', console);
132 static double compute_percent(int pass, size_t cur, size_t max) {
133 /* Values stolen from e2fsck */
135 static const double pass_table[] = {
136 0, 70, 90, 92, 95, 100
142 if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
145 return pass_table[pass-1] +
146 (pass_table[pass] - pass_table[pass-1]) *
150 static int client_request_cancel(Client *c) {
151 FsckdMessage cancel_msg = {
162 n = send(c->fd, &cancel_msg, sizeof(FsckdMessage), 0);
164 return log_warning_errno(errno, "Cannot send cancel to fsck on (%u:%u): %m", major(c->devnum), minor(c->devnum));
165 if ((size_t) n < sizeof(FsckdMessage)) {
166 log_warning("Short send when sending cancel to fsck on (%u:%u).", major(c->devnum), minor(c->devnum));
174 static void client_free(Client *c) {
178 LIST_REMOVE(clients, c->manager->clients, c);
179 c->manager->n_clients--;
182 sd_event_source_unref(c->event_source);
188 static void manager_disconnect_plymouth(Manager *m) {
191 m->plymouth_event_source = sd_event_source_unref(m->plymouth_event_source);
192 m->plymouth_fd = safe_close(m->plymouth_fd);
193 m->plymouth_cancel_sent = false;
196 static int manager_plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
197 Manager *m = userdata;
204 l = read(m->plymouth_fd, buffer, sizeof(buffer));
206 log_warning_errno(errno, "Got error while reading from plymouth: %m");
207 manager_disconnect_plymouth(m);
211 manager_disconnect_plymouth(m);
215 if (l > 1 && buffer[0] == '\15')
216 log_error("Message update to plymouth wasn't delivered successfully");
218 /* the only answer support type we requested is a key interruption */
219 if (l > 2 && buffer[0] == '\2' && buffer[5] == '\3') {
220 m->cancel_requested = true;
222 /* cancel all connected clients */
223 LIST_FOREACH(clients, current, m->clients)
224 client_request_cancel(current);
230 static int manager_connect_plymouth(Manager *m) {
231 union sockaddr_union sa = PLYMOUTH_SOCKET;
234 /* try to connect or reconnect if sending a message */
235 if (m->plymouth_fd >= 0)
238 m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
239 if (m->plymouth_fd < 0)
240 return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
242 if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
243 r = log_warning_errno(errno, "Couldn't connect to plymouth: %m");
247 r = sd_event_add_io(m->event, &m->plymouth_event_source, m->plymouth_fd, EPOLLIN, manager_plymouth_feedback_handler, m);
249 log_warning_errno(r, "Can't listen to plymouth socket: %m");
256 manager_disconnect_plymouth(m);
260 static int plymouth_send_message(int plymouth_fd, const char *message, bool update) {
261 _cleanup_free_ char *packet = NULL;
268 if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0)
271 return loop_write(plymouth_fd, packet, n + 1, true);
274 static int manager_send_plymouth_message(Manager *m, const char *message) {
275 const char *plymouth_cancel_message = NULL;
278 r = manager_connect_plymouth(m);
282 if (!m->plymouth_cancel_sent) {
284 /* Indicate to plymouth that we listen to Ctrl+C */
285 r = loop_write(m->plymouth_fd, PLYMOUTH_REQUEST_KEY, sizeof(PLYMOUTH_REQUEST_KEY), true);
287 return log_warning_errno(r, "Can't send to plymouth cancel key: %m");
289 m->plymouth_cancel_sent = true;
291 plymouth_cancel_message = strjoina("fsckd-cancel-msg:", _("Press Ctrl+C to cancel all filesystem checks in progress"));
293 r = plymouth_send_message(m->plymouth_fd, plymouth_cancel_message, false);
295 log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m");
297 } else if (m->numdevices == 0) {
299 m->plymouth_cancel_sent = false;
301 r = plymouth_send_message(m->plymouth_fd, "", false);
303 log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
306 r = plymouth_send_message(m->plymouth_fd, message, true);
308 return log_warning_errno(r, "Couldn't send \"%s\" to plymouth: %m", message);
313 static int manager_update_global_progress(Manager *m) {
314 Client *current = NULL;
315 _cleanup_free_ char *console_message = NULL;
316 _cleanup_free_ char *fsck_message = NULL;
317 int current_numdevices = 0, r;
318 double current_percent = 100;
320 /* get the overall percentage */
321 LIST_FOREACH(clients, current, m->clients) {
322 current_numdevices++;
324 /* right now, we only keep the minimum % of all fsckd processes. We could in the future trying to be
325 linear, but max changes and corresponds to the pass. We have all the informations into fsckd
326 already if we can treat that in a smarter way. */
327 current_percent = MIN(current_percent, current->percent);
330 /* update if there is anything user-visible to update */
331 if (fabs(current_percent - m->percent) > 0.001 || current_numdevices != m->numdevices) {
332 m->numdevices = current_numdevices;
333 m->percent = current_percent;
335 if (asprintf(&console_message,
336 ngettext("Checking in progress on %d disk (%3.1f%% complete)",
337 "Checking in progress on %d disks (%3.1f%% complete)", m->numdevices),
338 m->numdevices, m->percent) < 0)
341 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
344 r = manager_write_console(m, console_message);
348 /* try to connect to plymouth and send message */
349 r = manager_send_plymouth_message(m, fsck_message);
351 log_debug("Couldn't send message to plymouth");
357 static int client_progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
358 Client *client = userdata;
359 FsckProgress fsck_data;
368 /* check first if we need to cancel this client */
369 if (m->cancel_requested)
370 client_request_cancel(client);
372 /* ensure we have enough data to read */
373 r = ioctl(fd, FIONREAD, &buflen);
374 if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
375 if (client->buflen != buflen)
376 client->buflen = buflen;
377 /* we got twice the same size from a bad behaving client, kick it off the list */
379 log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
381 r = manager_update_global_progress(m);
383 log_warning_errno(r, "Couldn't update global progress: %m");
388 /* read actual data */
389 r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
391 log_debug("Fsck client connected to fd %d disconnected", client->fd);
393 } else if (r > 0 && r != sizeof(FsckProgress))
394 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
395 else if (r > 0 && r == sizeof(FsckProgress)) {
396 client->devnum = fsck_data.devnum;
397 client->cur = fsck_data.cur;
398 client->max = fsck_data.max;
399 client->pass = fsck_data.pass;
400 client->percent = compute_percent(client->pass, client->cur, client->max);
401 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
402 major(client->devnum), minor(client->devnum),
403 client->cur, client->max, client->pass, client->percent);
405 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
407 r = manager_update_global_progress(m);
409 log_warning_errno(r, "Couldn't update global progress: %m");
414 static int manager_new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
415 _cleanup_(client_freep) Client *c = NULL;
416 _cleanup_close_ int new_client_fd = -1;
417 Manager *m = userdata;
422 /* Initialize and list new clients */
423 new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
424 if (new_client_fd < 0)
425 return log_error_errno(errno, "Couldn't accept a new connection: %m");
427 if (m->n_clients >= CLIENTS_MAX) {
428 log_error("Too many clients, refusing connection.");
432 log_debug("New fsck client connected to fd: %d", new_client_fd);
440 c->fd = new_client_fd;
443 r = sd_event_add_io(m->event, &c->event_source, c->fd, EPOLLIN, client_progress_handler, c);
449 LIST_PREPEND(clients, m->clients, c);
453 /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
454 if (m->cancel_requested)
455 client_request_cancel(c);
461 static void manager_free(Manager *m) {
465 /* clear last line */
466 manager_write_console(m, NULL);
468 sd_event_source_unref(m->connection_event_source);
469 safe_close(m->connection_fd);
472 client_free(m->clients);
474 manager_disconnect_plymouth(m);
476 sd_event_unref(m->event);
481 static int manager_new(Manager **ret, int fd) {
482 _cleanup_(manager_freep) Manager *m = NULL;
487 m = new0(Manager, 1);
492 m->connection_fd = fd;
495 r = sd_event_default(&m->event);
499 if (access("/run/systemd/show-status", F_OK) >= 0)
500 m->show_status_console = true;
502 r = sd_event_add_io(m->event, &m->connection_event_source, fd, EPOLLIN, manager_new_connection_handler, m);
512 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
518 r = sd_event_get_state(e);
521 if (r == SD_EVENT_FINISHED)
524 r = sd_event_run(e, timeout);
528 /* timeout reached */
535 r = sd_event_get_exit_code(e, &code);
542 static void help(void) {
543 printf("%s [OPTIONS...]\n\n"
544 "Capture fsck progress and forward one stream to plymouth\n\n"
545 " -h --help Show this help\n"
546 " --version Show package version\n",
547 program_invocation_short_name);
550 static int parse_argv(int argc, char *argv[]) {
557 static const struct option options[] = {
558 { "help", no_argument, NULL, 'h' },
559 { "version", no_argument, NULL, ARG_VERSION },
568 while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
576 puts(PACKAGE_STRING);
577 puts(SYSTEMD_FEATURES);
584 assert_not_reached("Unhandled option");
588 log_error("Extraneous arguments");
595 int main(int argc, char *argv[]) {
596 _cleanup_(manager_freep) Manager *m = NULL;
600 log_set_target(LOG_TARGET_AUTO);
601 log_parse_environment();
605 r = parse_argv(argc, argv);
609 n = sd_listen_fds(0);
611 log_error("Too many file descriptors received.");
615 fd = SD_LISTEN_FDS_START + 0;
617 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
619 r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
624 r = manager_new(&m, fd);
626 log_error_errno(r, "Failed to allocate manager: %m");
630 r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
632 log_error_errno(r, "Failed to run event loop: %m");
636 sd_event_get_exit_code(m->event, &r);
639 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;