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;
89 sd_event_source *plymouth_event_source;
90 bool plymouth_cancel_sent;
92 bool cancel_requested;
95 static void client_free(Client *c);
96 static void manager_free(Manager *m);
98 DEFINE_TRIVIAL_CLEANUP_FUNC(Client*, client_free);
99 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
101 static double compute_percent(int pass, size_t cur, size_t max) {
102 /* Values stolen from e2fsck */
104 static const double pass_table[] = {
105 0, 70, 90, 92, 95, 100
111 if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
114 return pass_table[pass-1] +
115 (pass_table[pass] - pass_table[pass-1]) *
119 static int client_request_cancel(Client *c) {
120 FsckdMessage cancel_msg = {
131 n = send(c->fd, &cancel_msg, sizeof(FsckdMessage), 0);
133 return log_warning_errno(errno, "Cannot send cancel to fsck on (%u:%u): %m", major(c->devnum), minor(c->devnum));
134 if ((size_t) n < sizeof(FsckdMessage)) {
135 log_warning("Short send when sending cancel to fsck on (%u:%u).", major(c->devnum), minor(c->devnum));
143 static void client_free(Client *c) {
147 LIST_REMOVE(clients, c->manager->clients, c);
148 c->manager->n_clients--;
151 sd_event_source_unref(c->event_source);
157 static void manager_disconnect_plymouth(Manager *m) {
160 m->plymouth_event_source = sd_event_source_unref(m->plymouth_event_source);
161 m->plymouth_fd = safe_close(m->plymouth_fd);
162 m->plymouth_cancel_sent = false;
165 static int manager_plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
166 Manager *m = userdata;
173 l = read(m->plymouth_fd, buffer, sizeof(buffer));
175 log_warning_errno(errno, "Got error while reading from plymouth: %m");
176 manager_disconnect_plymouth(m);
180 manager_disconnect_plymouth(m);
184 if (l > 1 && buffer[0] == '\15')
185 log_error("Message update to plymouth wasn't delivered successfully");
187 /* the only answer support type we requested is a key interruption */
188 if (l > 2 && buffer[0] == '\2' && buffer[5] == '\3') {
189 m->cancel_requested = true;
191 /* cancel all connected clients */
192 LIST_FOREACH(clients, current, m->clients)
193 client_request_cancel(current);
199 static int manager_connect_plymouth(Manager *m) {
200 union sockaddr_union sa = PLYMOUTH_SOCKET;
203 /* try to connect or reconnect if sending a message */
204 if (m->plymouth_fd >= 0)
207 m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
208 if (m->plymouth_fd < 0)
209 return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
211 if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
212 r = log_warning_errno(errno, "Couldn't connect to plymouth: %m");
216 r = sd_event_add_io(m->event, &m->plymouth_event_source, m->plymouth_fd, EPOLLIN, manager_plymouth_feedback_handler, m);
218 log_warning_errno(r, "Can't listen to plymouth socket: %m");
225 manager_disconnect_plymouth(m);
229 static int plymouth_send_message(int plymouth_fd, const char *message, bool update) {
230 _cleanup_free_ char *packet = NULL;
237 if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0)
240 return loop_write(plymouth_fd, packet, n + 1, true);
243 static int manager_send_plymouth_message(Manager *m, const char *message) {
244 const char *plymouth_cancel_message = NULL;
247 r = manager_connect_plymouth(m);
251 if (!m->plymouth_cancel_sent) {
253 /* Indicate to plymouth that we listen to Ctrl+C */
254 r = loop_write(m->plymouth_fd, PLYMOUTH_REQUEST_KEY, sizeof(PLYMOUTH_REQUEST_KEY), true);
256 return log_warning_errno(r, "Can't send to plymouth cancel key: %m");
258 m->plymouth_cancel_sent = true;
260 plymouth_cancel_message = strjoina("fsckd-cancel-msg:", _("Press Ctrl+C to cancel all filesystem checks in progress"));
262 r = plymouth_send_message(m->plymouth_fd, plymouth_cancel_message, false);
264 log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m");
266 } else if (m->numdevices == 0) {
268 m->plymouth_cancel_sent = false;
270 r = plymouth_send_message(m->plymouth_fd, "", false);
272 log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
275 r = plymouth_send_message(m->plymouth_fd, message, true);
277 return log_warning_errno(r, "Couldn't send \"%s\" to plymouth: %m", message);
282 static int manager_update_global_progress(Manager *m) {
283 Client *current = NULL;
284 _cleanup_free_ char *console_message = NULL;
285 _cleanup_free_ char *fsck_message = NULL;
286 int current_numdevices = 0, l = 0, r;
287 double current_percent = 100;
289 /* get the overall percentage */
290 LIST_FOREACH(clients, current, m->clients) {
291 current_numdevices++;
293 /* right now, we only keep the minimum % of all fsckd processes. We could in the future trying to be
294 linear, but max changes and corresponds to the pass. We have all the informations into fsckd
295 already if we can treat that in a smarter way. */
296 current_percent = MIN(current_percent, current->percent);
299 /* update if there is anything user-visible to update */
300 if (fabs(current_percent - m->percent) > 0.001 || current_numdevices != m->numdevices) {
301 m->numdevices = current_numdevices;
302 m->percent = current_percent;
304 if (asprintf(&console_message,
305 ngettext("Checking in progress on %d disk (%3.1f%% complete)",
306 "Checking in progress on %d disks (%3.1f%% complete)", m->numdevices),
307 m->numdevices, m->percent) < 0)
310 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
313 /* write to console */
315 fprintf(m->console, "\r%s\r%n", console_message, &l);
319 /* try to connect to plymouth and send message */
320 r = manager_send_plymouth_message(m, fsck_message);
322 log_debug("Couldn't send message to plymouth");
330 static int client_progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
331 Client *client = userdata;
332 FsckProgress fsck_data;
341 /* check first if we need to cancel this client */
342 if (m->cancel_requested)
343 client_request_cancel(client);
345 /* ensure we have enough data to read */
346 r = ioctl(fd, FIONREAD, &buflen);
347 if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
348 if (client->buflen != buflen)
349 client->buflen = buflen;
350 /* we got twice the same size from a bad behaving client, kick it off the list */
352 log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
354 r = manager_update_global_progress(m);
356 log_warning_errno(r, "Couldn't update global progress: %m");
361 /* read actual data */
362 r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
364 log_debug("Fsck client connected to fd %d disconnected", client->fd);
366 } else if (r > 0 && r != sizeof(FsckProgress))
367 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
368 else if (r > 0 && r == sizeof(FsckProgress)) {
369 client->devnum = fsck_data.devnum;
370 client->cur = fsck_data.cur;
371 client->max = fsck_data.max;
372 client->pass = fsck_data.pass;
373 client->percent = compute_percent(client->pass, client->cur, client->max);
374 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
375 major(client->devnum), minor(client->devnum),
376 client->cur, client->max, client->pass, client->percent);
378 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
380 r = manager_update_global_progress(m);
382 log_warning_errno(r, "Couldn't update global progress: %m");
387 static int manager_new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
388 _cleanup_(client_freep) Client *c = NULL;
389 _cleanup_close_ int new_client_fd = -1;
390 Manager *m = userdata;
395 /* Initialize and list new clients */
396 new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
397 if (new_client_fd < 0)
398 return log_error_errno(errno, "Couldn't accept a new connection: %m");
400 if (m->n_clients >= CLIENTS_MAX) {
401 log_error("Too many clients, refusing connection.");
405 log_debug("New fsck client connected to fd: %d", new_client_fd);
413 c->fd = new_client_fd;
416 r = sd_event_add_io(m->event, &c->event_source, c->fd, EPOLLIN, client_progress_handler, c);
422 LIST_PREPEND(clients, m->clients, c);
426 /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
427 if (m->cancel_requested)
428 client_request_cancel(c);
434 static void manager_free(Manager *m) {
438 /* clear last line */
439 if (m->console && m->clear > 0) {
442 fputc('\r', m->console);
443 for (j = 0; j < (unsigned) m->clear; j++)
444 fputc(' ', m->console);
445 fputc('\r', m->console);
449 sd_event_source_unref(m->connection_event_source);
450 safe_close(m->connection_fd);
453 client_free(m->clients);
455 manager_disconnect_plymouth(m);
460 sd_event_unref(m->event);
465 static int manager_new(Manager **ret, int fd) {
466 _cleanup_(manager_freep) Manager *m = NULL;
471 m = new0(Manager, 1);
476 m->connection_fd = fd;
479 r = sd_event_default(&m->event);
483 if (access("/run/systemd/show-status", F_OK) >= 0) {
484 m->console = fopen("/dev/console", "we");
489 r = sd_event_add_io(m->event, &m->connection_event_source, fd, EPOLLIN, manager_new_connection_handler, m);
499 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
505 r = sd_event_get_state(e);
508 if (r == SD_EVENT_FINISHED)
511 r = sd_event_run(e, timeout);
515 /* timeout reached */
522 r = sd_event_get_exit_code(e, &code);
529 static void help(void) {
530 printf("%s [OPTIONS...]\n\n"
531 "Capture fsck progress and forward one stream to plymouth\n\n"
532 " -h --help Show this help\n"
533 " --version Show package version\n",
534 program_invocation_short_name);
537 static int parse_argv(int argc, char *argv[]) {
544 static const struct option options[] = {
545 { "help", no_argument, NULL, 'h' },
546 { "version", no_argument, NULL, ARG_VERSION },
555 while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
563 puts(PACKAGE_STRING);
564 puts(SYSTEMD_FEATURES);
571 assert_not_reached("Unhandled option");
575 log_error("Extraneous arguments");
582 int main(int argc, char *argv[]) {
583 _cleanup_(manager_freep) Manager *m = NULL;
587 log_set_target(LOG_TARGET_AUTO);
588 log_parse_environment();
592 r = parse_argv(argc, argv);
596 n = sd_listen_fds(0);
598 log_error("Too many file descriptors received.");
602 fd = SD_LISTEN_FDS_START + 0;
604 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
606 r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
611 r = manager_new(&m, fd);
613 log_error_errno(r, "Failed to allocate manager: %m");
617 r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
619 log_error_errno(r, "Failed to run event loop: %m");
623 sd_event_get_exit_code(m->event, &r);
626 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;