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 client_request_cancel(Client *c) {
114 FsckdMessage cancel_msg = {
125 n = send(c->fd, &cancel_msg, sizeof(FsckdMessage), 0);
127 return log_warning_errno(errno, "Cannot send cancel to fsck on (%u:%u): %m", major(c->devnum), minor(c->devnum));
128 if ((size_t) n < sizeof(FsckdMessage)) {
129 log_warning("Short send when sending cancel to fsck on (%u:%u).", major(c->devnum), minor(c->devnum));
137 static void client_free(Client *c) {
141 LIST_REMOVE(clients, c->manager->clients, c);
147 static void plymouth_disconnect(Manager *m) {
150 m->plymouth_event_source = sd_event_source_unref(m->plymouth_event_source);
151 m->plymouth_fd = safe_close(m->plymouth_fd);
152 m->plymouth_cancel_sent = false;
155 static int plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
156 Manager *m = userdata;
163 l = read(m->plymouth_fd, buffer, sizeof(buffer));
165 log_warning_errno(errno, "Got error while reading from plymouth: %m");
166 plymouth_disconnect(m);
170 plymouth_disconnect(m);
174 if (buffer[0] == '\15')
175 log_error("Message update to plymouth wasn't delivered successfully");
177 /* the only answer support type we requested is a key interruption */
178 if (buffer[0] == '\2' && buffer[5] == '\3') {
179 m->cancel_requested = true;
181 /* cancel all connected clients */
182 LIST_FOREACH(clients, current, m->clients)
183 client_request_cancel(current);
189 static int send_message_plymouth_socket(int plymouth_fd, const char *message, bool update) {
190 _cleanup_free_ char *packet = NULL;
197 if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0)
200 return loop_write(plymouth_fd, packet, n + 1, true);
203 static int send_message_plymouth(Manager *m, const char *message) {
204 const char *plymouth_cancel_message = NULL;
207 r = connect_plymouth(m);
211 if (!m->plymouth_cancel_sent) {
213 /* Indicate to plymouth that we listen to Ctrl+C */
214 r = loop_write(m->plymouth_fd, PLYMOUTH_REQUEST_KEY, sizeof(PLYMOUTH_REQUEST_KEY), true);
216 return log_warning_errno(r, "Can't send to plymouth cancel key: %m");
218 m->plymouth_cancel_sent = true;
220 plymouth_cancel_message = strjoina("fsckd-cancel-msg:", _("Press Ctrl+C to cancel all filesystem checks in progress"));
222 r = send_message_plymouth_socket(m->plymouth_fd, plymouth_cancel_message, false);
224 log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m");
226 } else if (m->numdevices == 0) {
228 m->plymouth_cancel_sent = false;
230 r = send_message_plymouth_socket(m->plymouth_fd, "", false);
232 log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
235 r = send_message_plymouth_socket(m->plymouth_fd, message, true);
237 return log_warning_errno(r, "Couldn't send \"%s\" to plymouth: %m", message);
242 static int update_global_progress(Manager *m) {
243 Client *current = NULL;
244 _cleanup_free_ char *console_message = NULL;
245 _cleanup_free_ char *fsck_message = NULL;
246 int current_numdevices = 0, l = 0, r;
247 double current_percent = 100;
249 /* get the overall percentage */
250 LIST_FOREACH(clients, current, m->clients) {
251 current_numdevices++;
253 /* right now, we only keep the minimum % of all fsckd processes. We could in the future trying to be
254 linear, but max changes and corresponds to the pass. We have all the informations into fsckd
255 already if we can treat that in a smarter way. */
256 current_percent = MIN(current_percent, current->percent);
259 /* update if there is anything user-visible to update */
260 if (fabs(current_percent - m->percent) > 0.001 || current_numdevices != m->numdevices) {
261 m->numdevices = current_numdevices;
262 m->percent = current_percent;
264 if (asprintf(&console_message,
265 ngettext("Checking in progress on %d disk (%3.1f%% complete)",
266 "Checking in progress on %d disks (%3.1f%% complete)", m->numdevices),
267 m->numdevices, m->percent) < 0)
270 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
273 /* write to console */
275 fprintf(m->console, "\r%s\r%n", console_message, &l);
279 /* try to connect to plymouth and send message */
280 r = send_message_plymouth(m, fsck_message);
282 log_debug("Couldn't send message to plymouth");
290 static int connect_plymouth(Manager *m) {
291 union sockaddr_union sa = PLYMOUTH_SOCKET;
294 /* try to connect or reconnect if sending a message */
295 if (m->plymouth_fd >= 0)
298 m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
299 if (m->plymouth_fd < 0)
300 return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
302 if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
303 r = log_warning_errno(errno, "Couldn't connect to plymouth: %m");
307 r = sd_event_add_io(m->event, &m->plymouth_event_source, m->plymouth_fd, EPOLLIN, plymouth_feedback_handler, m);
309 log_warning_errno(r, "Can't listen to plymouth socket: %m");
316 plymouth_disconnect(m);
320 static int progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
321 Client *client = userdata;
323 FsckProgress fsck_data;
330 /* check first if we need to cancel this client */
331 if (m->cancel_requested)
332 client_request_cancel(client);
334 /* ensure we have enough data to read */
335 r = ioctl(fd, FIONREAD, &buflen);
336 if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
337 if (client->buflen != buflen)
338 client->buflen = buflen;
339 /* we got twice the same size from a bad behaving client, kick it off the list */
341 log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
343 r = update_global_progress(m);
345 log_warning_errno(r, "Couldn't update global progress: %m");
350 /* read actual data */
351 r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
353 log_debug("Fsck client connected to fd %d disconnected", client->fd);
355 } else if (r > 0 && r != sizeof(FsckProgress))
356 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
357 else if (r > 0 && r == sizeof(FsckProgress)) {
358 client->devnum = fsck_data.devnum;
359 client->cur = fsck_data.cur;
360 client->max = fsck_data.max;
361 client->pass = fsck_data.pass;
362 client->percent = compute_percent(client->pass, client->cur, client->max);
363 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
364 major(client->devnum), minor(client->devnum),
365 client->cur, client->max, client->pass, client->percent);
367 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
369 r = update_global_progress(m);
371 log_warning_errno(r, "Couldn't update global progress: %m");
376 static int new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
377 Manager *m = userdata;
378 Client *client = NULL;
379 int new_client_fd, r;
383 /* Initialize and list new clients */
384 new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
385 if (new_client_fd < 0)
386 return log_error_errno(errno, "Couldn't accept a new connection: %m");
388 log_debug("New fsck client connected to fd: %d", new_client_fd);
390 client = new0(Client, 1);
393 client->fd = new_client_fd;
395 LIST_PREPEND(clients, m->clients, client);
396 r = sd_event_add_io(m->event, NULL, client->fd, EPOLLIN, progress_handler, client);
401 /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
402 if (m->cancel_requested)
403 client_request_cancel(client);
408 static void manager_free(Manager *m) {
412 /* clear last line */
413 if (m->console && m->clear > 0) {
416 fputc('\r', m->console);
417 for (j = 0; j < (unsigned) m->clear; j++)
418 fputc(' ', m->console);
419 fputc('\r', m->console);
423 plymouth_disconnect(m);
425 safe_close(m->connection_fd);
431 client_free(m->clients);
433 sd_event_unref(m->event);
438 static int manager_new(Manager **ret, int fd) {
439 _cleanup_manager_free_ Manager *m = NULL;
444 m = new0(Manager, 1);
448 r = sd_event_default(&m->event);
452 m->connection_fd = fd;
453 if (access("/run/systemd/show-status", F_OK) >= 0) {
454 m->console = fopen("/dev/console", "we");
456 return log_warning_errno(errno, "Can't connect to /dev/console: %m");
467 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
473 r = sd_event_get_state(e);
476 if (r == SD_EVENT_FINISHED)
479 r = sd_event_run(e, timeout);
483 /* timeout reached */
490 r = sd_event_get_exit_code(e, &code);
497 static void help(void) {
498 printf("%s [OPTIONS...]\n\n"
499 "Capture fsck progress and forward one stream to plymouth\n\n"
500 " -h --help Show this help\n"
501 " --version Show package version\n",
502 program_invocation_short_name);
505 static int parse_argv(int argc, char *argv[]) {
512 static const struct option options[] = {
513 { "help", no_argument, NULL, 'h' },
514 { "version", no_argument, NULL, ARG_VERSION },
523 while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
531 puts(PACKAGE_STRING);
532 puts(SYSTEMD_FEATURES);
539 assert_not_reached("Unhandled option");
543 log_error("Extraneous arguments");
550 int main(int argc, char *argv[]) {
551 _cleanup_manager_free_ Manager *m = NULL;
555 log_set_target(LOG_TARGET_AUTO);
556 log_parse_environment();
560 r = parse_argv(argc, argv);
564 n = sd_listen_fds(0);
566 log_error("Too many file descriptors received.");
570 fd = SD_LISTEN_FDS_START + 0;
572 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
574 r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
579 r = manager_new(&m, fd);
581 log_error_errno(r, "Failed to allocate manager: %m");
585 r = sd_event_add_io(m->event, NULL, fd, EPOLLIN, new_connection_handler, m);
587 log_error_errno(r, "Can't listen to connection socket: %m");
591 r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
593 log_error_errno(r, "Failed to run event loop: %m");
597 sd_event_get_exit_code(m->event, &r);
600 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;