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);
87 sd_event_source *plymouth_event_source;
88 bool plymouth_cancel_sent;
90 bool cancel_requested;
93 static void client_free(Client *c);
94 static void manager_free(Manager *m);
96 DEFINE_TRIVIAL_CLEANUP_FUNC(Client*, client_free);
97 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
99 static double compute_percent(int pass, size_t cur, size_t max) {
100 /* Values stolen from e2fsck */
102 static const double pass_table[] = {
103 0, 70, 90, 92, 95, 100
109 if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
112 return pass_table[pass-1] +
113 (pass_table[pass] - pass_table[pass-1]) *
117 static int client_request_cancel(Client *c) {
118 FsckdMessage cancel_msg = {
129 n = send(c->fd, &cancel_msg, sizeof(FsckdMessage), 0);
131 return log_warning_errno(errno, "Cannot send cancel to fsck on (%u:%u): %m", major(c->devnum), minor(c->devnum));
132 if ((size_t) n < sizeof(FsckdMessage)) {
133 log_warning("Short send when sending cancel to fsck on (%u:%u).", major(c->devnum), minor(c->devnum));
141 static void client_free(Client *c) {
145 LIST_REMOVE(clients, c->manager->clients, c);
146 c->manager->n_clients--;
149 sd_event_source_unref(c->event_source);
155 static void manager_disconnect_plymouth(Manager *m) {
158 m->plymouth_event_source = sd_event_source_unref(m->plymouth_event_source);
159 m->plymouth_fd = safe_close(m->plymouth_fd);
160 m->plymouth_cancel_sent = false;
163 static int manager_plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
164 Manager *m = userdata;
171 l = read(m->plymouth_fd, buffer, sizeof(buffer));
173 log_warning_errno(errno, "Got error while reading from plymouth: %m");
174 manager_disconnect_plymouth(m);
178 manager_disconnect_plymouth(m);
182 if (l > 1 && buffer[0] == '\15')
183 log_error("Message update to plymouth wasn't delivered successfully");
185 /* the only answer support type we requested is a key interruption */
186 if (l > 2 && buffer[0] == '\2' && buffer[5] == '\3') {
187 m->cancel_requested = true;
189 /* cancel all connected clients */
190 LIST_FOREACH(clients, current, m->clients)
191 client_request_cancel(current);
197 static int manager_connect_plymouth(Manager *m) {
198 union sockaddr_union sa = PLYMOUTH_SOCKET;
201 /* try to connect or reconnect if sending a message */
202 if (m->plymouth_fd >= 0)
205 m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
206 if (m->plymouth_fd < 0)
207 return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
209 if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
210 r = log_warning_errno(errno, "Couldn't connect to plymouth: %m");
214 r = sd_event_add_io(m->event, &m->plymouth_event_source, m->plymouth_fd, EPOLLIN, manager_plymouth_feedback_handler, m);
216 log_warning_errno(r, "Can't listen to plymouth socket: %m");
223 manager_disconnect_plymouth(m);
227 static int plymouth_send_message(int plymouth_fd, const char *message, bool update) {
228 _cleanup_free_ char *packet = NULL;
235 if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0)
238 return loop_write(plymouth_fd, packet, n + 1, true);
241 static int manager_send_plymouth_message(Manager *m, const char *message) {
242 const char *plymouth_cancel_message = NULL;
245 r = manager_connect_plymouth(m);
249 if (!m->plymouth_cancel_sent) {
251 /* Indicate to plymouth that we listen to Ctrl+C */
252 r = loop_write(m->plymouth_fd, PLYMOUTH_REQUEST_KEY, sizeof(PLYMOUTH_REQUEST_KEY), true);
254 return log_warning_errno(r, "Can't send to plymouth cancel key: %m");
256 m->plymouth_cancel_sent = true;
258 plymouth_cancel_message = strjoina("fsckd-cancel-msg:", _("Press Ctrl+C to cancel all filesystem checks in progress"));
260 r = plymouth_send_message(m->plymouth_fd, plymouth_cancel_message, false);
262 log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m");
264 } else if (m->numdevices == 0) {
266 m->plymouth_cancel_sent = false;
268 r = plymouth_send_message(m->plymouth_fd, "", false);
270 log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
273 r = plymouth_send_message(m->plymouth_fd, message, true);
275 return log_warning_errno(r, "Couldn't send \"%s\" to plymouth: %m", message);
280 static int manager_update_global_progress(Manager *m) {
281 Client *current = NULL;
282 _cleanup_free_ char *console_message = NULL;
283 _cleanup_free_ char *fsck_message = NULL;
284 int current_numdevices = 0, l = 0, r;
285 double current_percent = 100;
287 /* get the overall percentage */
288 LIST_FOREACH(clients, current, m->clients) {
289 current_numdevices++;
291 /* right now, we only keep the minimum % of all fsckd processes. We could in the future trying to be
292 linear, but max changes and corresponds to the pass. We have all the informations into fsckd
293 already if we can treat that in a smarter way. */
294 current_percent = MIN(current_percent, current->percent);
297 /* update if there is anything user-visible to update */
298 if (fabs(current_percent - m->percent) > 0.001 || current_numdevices != m->numdevices) {
299 m->numdevices = current_numdevices;
300 m->percent = current_percent;
302 if (asprintf(&console_message,
303 ngettext("Checking in progress on %d disk (%3.1f%% complete)",
304 "Checking in progress on %d disks (%3.1f%% complete)", m->numdevices),
305 m->numdevices, m->percent) < 0)
308 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
311 /* write to console */
313 fprintf(m->console, "\r%s\r%n", console_message, &l);
317 /* try to connect to plymouth and send message */
318 r = manager_send_plymouth_message(m, fsck_message);
320 log_debug("Couldn't send message to plymouth");
328 static int client_progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
329 Client *client = userdata;
330 FsckProgress fsck_data;
339 /* check first if we need to cancel this client */
340 if (m->cancel_requested)
341 client_request_cancel(client);
343 /* ensure we have enough data to read */
344 r = ioctl(fd, FIONREAD, &buflen);
345 if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
346 if (client->buflen != buflen)
347 client->buflen = buflen;
348 /* we got twice the same size from a bad behaving client, kick it off the list */
350 log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
352 r = manager_update_global_progress(m);
354 log_warning_errno(r, "Couldn't update global progress: %m");
359 /* read actual data */
360 r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
362 log_debug("Fsck client connected to fd %d disconnected", client->fd);
364 } else if (r > 0 && r != sizeof(FsckProgress))
365 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
366 else if (r > 0 && r == sizeof(FsckProgress)) {
367 client->devnum = fsck_data.devnum;
368 client->cur = fsck_data.cur;
369 client->max = fsck_data.max;
370 client->pass = fsck_data.pass;
371 client->percent = compute_percent(client->pass, client->cur, client->max);
372 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
373 major(client->devnum), minor(client->devnum),
374 client->cur, client->max, client->pass, client->percent);
376 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
378 r = manager_update_global_progress(m);
380 log_warning_errno(r, "Couldn't update global progress: %m");
385 static int manager_new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
386 _cleanup_(client_freep) Client *c = NULL;
387 _cleanup_close_ int new_client_fd = -1;
388 Manager *m = userdata;
393 /* Initialize and list new clients */
394 new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
395 if (new_client_fd < 0)
396 return log_error_errno(errno, "Couldn't accept a new connection: %m");
398 if (m->n_clients >= CLIENTS_MAX) {
399 log_error("Too many clients, refusing connection.");
403 log_debug("New fsck client connected to fd: %d", new_client_fd);
411 c->fd = new_client_fd;
414 r = sd_event_add_io(m->event, &c->event_source, c->fd, EPOLLIN, client_progress_handler, c);
420 LIST_PREPEND(clients, m->clients, c);
424 /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
425 if (m->cancel_requested)
426 client_request_cancel(c);
432 static void manager_free(Manager *m) {
436 /* clear last line */
437 if (m->console && m->clear > 0) {
440 fputc('\r', m->console);
441 for (j = 0; j < (unsigned) m->clear; j++)
442 fputc(' ', m->console);
443 fputc('\r', m->console);
447 safe_close(m->connection_fd);
450 client_free(m->clients);
452 manager_disconnect_plymouth(m);
457 sd_event_unref(m->event);
462 static int manager_new(Manager **ret, int fd) {
463 _cleanup_(manager_freep) Manager *m = NULL;
468 m = new0(Manager, 1);
473 m->connection_fd = fd;
476 r = sd_event_default(&m->event);
480 if (access("/run/systemd/show-status", F_OK) >= 0) {
481 m->console = fopen("/dev/console", "we");
486 r = sd_event_add_io(m->event, NULL, fd, EPOLLIN, manager_new_connection_handler, m);
496 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
502 r = sd_event_get_state(e);
505 if (r == SD_EVENT_FINISHED)
508 r = sd_event_run(e, timeout);
512 /* timeout reached */
519 r = sd_event_get_exit_code(e, &code);
526 static void help(void) {
527 printf("%s [OPTIONS...]\n\n"
528 "Capture fsck progress and forward one stream to plymouth\n\n"
529 " -h --help Show this help\n"
530 " --version Show package version\n",
531 program_invocation_short_name);
534 static int parse_argv(int argc, char *argv[]) {
541 static const struct option options[] = {
542 { "help", no_argument, NULL, 'h' },
543 { "version", no_argument, NULL, ARG_VERSION },
552 while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
560 puts(PACKAGE_STRING);
561 puts(SYSTEMD_FEATURES);
568 assert_not_reached("Unhandled option");
572 log_error("Extraneous arguments");
579 int main(int argc, char *argv[]) {
580 _cleanup_(manager_freep) Manager *m = NULL;
584 log_set_target(LOG_TARGET_AUTO);
585 log_parse_environment();
589 r = parse_argv(argc, argv);
593 n = sd_listen_fds(0);
595 log_error("Too many file descriptors received.");
599 fd = SD_LISTEN_FDS_START + 0;
601 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
603 r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
608 r = manager_new(&m, fd);
610 log_error_errno(r, "Failed to allocate manager: %m");
614 r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
616 log_error_errno(r, "Failed to run event loop: %m");
620 sd_event_get_exit_code(m->event, &r);
623 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;