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>
39 #include "event-util.h"
44 #include "sd-daemon.h"
45 #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;
64 LIST_FIELDS(struct Client, clients);
67 typedef struct Manager {
76 bool plymouth_cancel_sent;
77 bool cancel_requested;
80 static int connect_plymouth(Manager *m);
81 static int update_global_progress(Manager *m);
82 static void manager_free(Manager *m);
83 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
84 #define _cleanup_manager_free_ _cleanup_(manager_freep)
86 static double compute_percent(int pass, size_t cur, size_t max) {
87 /* Values stolen from e2fsck */
89 static const double pass_table[] = {
90 0, 70, 90, 92, 95, 100
96 if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
99 return pass_table[pass-1] +
100 (pass_table[pass] - pass_table[pass-1]) *
104 static int request_cancel_client(Client *current) {
105 FsckdMessage cancel_msg;
107 cancel_msg.cancel = 1;
109 n = send(current->fd, &cancel_msg, sizeof(FsckdMessage), 0);
110 if (n < 0 || (size_t) n < sizeof(FsckdMessage))
111 return log_warning_errno(n, "Cannot send cancel to fsck on (%u, %u): %m",
112 major(current->devnum), minor(current->devnum));
114 current->cancelled = true;
118 static void remove_client(Client **first, Client *item) {
119 LIST_REMOVE(clients, *first, item);
120 safe_close(item->fd);
124 static void on_plymouth_disconnect(Manager *m) {
125 safe_close(m->plymouth_fd);
127 m->plymouth_cancel_sent = false;
130 static int plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
131 Manager *m = userdata;
138 r = read(m->plymouth_fd, buffer, sizeof(buffer));
140 on_plymouth_disconnect(m);
142 if (buffer[0] == '\15')
143 log_error("Message update to plymouth wasn't delivered successfully");
145 /* the only answer support type we requested is a key interruption */
146 if (buffer[0] == '\2' && buffer[5] == '\3') {
147 m->cancel_requested = true;
148 /* cancel all connected clients */
149 LIST_FOREACH(clients, current, m->clients)
150 request_cancel_client(current);
157 static int send_message_plymouth_socket(int plymouth_fd, const char *message, bool update) {
158 _cleanup_free_ char *packet = NULL;
165 if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0)
167 r = loop_write(plymouth_fd, packet, n + 1, true);
172 static int send_message_plymouth(Manager *m, const char *message) {
174 const char *plymouth_cancel_message = NULL;
176 r = connect_plymouth(m);
180 if (!m->plymouth_cancel_sent) {
181 /* indicate to plymouth that we listen to Ctrl+C */
182 r = loop_write(m->plymouth_fd, PLYMOUTH_REQUEST_KEY, sizeof(PLYMOUTH_REQUEST_KEY), true);
184 return log_warning_errno(errno, "Can't send to plymouth cancel key: %m");
185 m->plymouth_cancel_sent = true;
186 plymouth_cancel_message = strjoina("fsckd-cancel-msg:", _("Press Ctrl+C to cancel all filesystem checks in progress"));
187 r = send_message_plymouth_socket(m->plymouth_fd, plymouth_cancel_message, false);
189 log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m");
190 } else if (m->numdevices == 0) {
191 m->plymouth_cancel_sent = false;
192 r = send_message_plymouth_socket(m->plymouth_fd, "", false);
194 log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
197 r = send_message_plymouth_socket(m->plymouth_fd, message, true);
199 return log_warning_errno(errno, "Couldn't send \"%s\" to plymouth: %m", message);
204 static int update_global_progress(Manager *m) {
205 Client *current = NULL;
206 _cleanup_free_ char *console_message = NULL;
207 _cleanup_free_ char *fsck_message = NULL;
208 int current_numdevices = 0, l = 0, r;
209 double current_percent = 100;
211 /* get the overall percentage */
212 LIST_FOREACH(clients, current, m->clients) {
213 current_numdevices++;
215 /* right now, we only keep the minimum % of all fsckd processes. We could in the future trying to be
216 linear, but max changes and corresponds to the pass. We have all the informations into fsckd
217 already if we can treat that in a smarter way. */
218 current_percent = MIN(current_percent, current->percent);
221 /* update if there is anything user-visible to update */
222 if (fabs(current_percent - m->percent) > 0.001 || current_numdevices != m->numdevices) {
223 m->numdevices = current_numdevices;
224 m->percent = current_percent;
226 if (asprintf(&console_message,
227 ngettext("Checking in progress on %d disk (%3.1f%% complete)",
228 "Checking in progress on %d disks (%3.1f%% complete)", m->numdevices),
229 m->numdevices, m->percent) < 0)
231 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
234 /* write to console */
236 fprintf(m->console, "\r%s\r%n", console_message, &l);
240 /* try to connect to plymouth and send message */
241 r = send_message_plymouth(m, fsck_message);
243 log_debug("Couldn't send message to plymouth");
251 static int connect_plymouth(Manager *m) {
252 union sockaddr_union sa = PLYMOUTH_SOCKET;
255 /* try to connect or reconnect if sending a message */
256 if (m->plymouth_fd <= 0) {
257 m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
258 if (m->plymouth_fd < 0) {
259 return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
261 if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
262 on_plymouth_disconnect(m);
263 return log_warning_errno(errno, "Couldn't connect to plymouth: %m");
265 r = sd_event_add_io(m->event, NULL, m->plymouth_fd, EPOLLIN, plymouth_feedback_handler, m);
267 on_plymouth_disconnect(m);
268 return log_warning_errno(r, "Can't listen to plymouth socket: %m");
275 static int progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
276 Client *client = userdata;
278 FsckProgress fsck_data;
285 /* check first if we need to cancel this client */
286 if (m->cancel_requested) {
287 if (!client->cancelled)
288 request_cancel_client(client);
291 /* ensure we have enough data to read */
292 r = ioctl(fd, FIONREAD, &buflen);
293 if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
294 if (client->buflen != buflen)
295 client->buflen = buflen;
296 /* we got twice the same size from a bad behaving client, kick it off the list */
298 log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
299 remove_client(&(m->clients), client);
300 r = update_global_progress(m);
302 log_warning_errno(r, "Couldn't update global progress: %m");
307 /* read actual data */
308 r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
310 log_debug("Fsck client connected to fd %d disconnected", client->fd);
311 remove_client(&(m->clients), client);
312 } else if (r > 0 && r != sizeof(FsckProgress))
313 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
314 else if (r > 0 && r == sizeof(FsckProgress)) {
315 client->devnum = fsck_data.devnum;
316 client->cur = fsck_data.cur;
317 client->max = fsck_data.max;
318 client->pass = fsck_data.pass;
319 client->percent = compute_percent(client->pass, client->cur, client->max);
320 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
321 major(client->devnum), minor(client->devnum),
322 client->cur, client->max, client->pass, client->percent);
324 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
326 r = update_global_progress(m);
328 log_warning_errno(r, "Couldn't update global progress: %m");
333 static int new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
334 Manager *m = userdata;
335 Client *client = NULL;
336 int new_client_fd, r;
340 /* Initialize and list new clients */
341 new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
342 if (new_client_fd > 0) {
343 log_debug("New fsck client connected to fd: %d", new_client_fd);
344 client = new0(Client, 1);
347 client->fd = new_client_fd;
349 LIST_PREPEND(clients, m->clients, client);
350 r = sd_event_add_io(m->event, NULL, client->fd, EPOLLIN, progress_handler, client);
352 remove_client(&(m->clients), client);
355 /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
356 if (m->cancel_requested)
357 request_cancel_client(client);
359 return log_error_errno(errno, "Couldn't accept a new connection: %m");
364 static void manager_free(Manager *m) {
365 Client *current = NULL, *l = NULL;
369 /* clear last line */
370 if (m->console && m->clear > 0) {
373 fputc('\r', m->console);
374 for (j = 0; j < (unsigned) m->clear; j++)
375 fputc(' ', m->console);
376 fputc('\r', m->console);
380 safe_close(m->connection_fd);
381 safe_close(m->plymouth_fd);
385 LIST_FOREACH_SAFE(clients, current, l, m->clients)
386 remove_client(&(m->clients), current);
388 sd_event_unref(m->event);
393 static int manager_new(Manager **ret, int fd) {
394 _cleanup_manager_free_ Manager *m = NULL;
399 m = new0(Manager, 1);
403 r = sd_event_default(&m->event);
407 m->connection_fd = fd;
408 if (access("/run/systemd/show-status", F_OK) >= 0) {
409 m->console = fopen("/dev/console", "we");
411 return log_warning_errno(errno, "Can't connect to /dev/console: %m");
422 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
428 r = sd_event_get_state(e);
431 if (r == SD_EVENT_FINISHED)
434 r = sd_event_run(e, timeout);
438 /* timeout reached */
445 r = sd_event_get_exit_code(e, &code);
452 static void help(void) {
453 printf("%s [OPTIONS...]\n\n"
454 "Capture fsck progress and forward one stream to plymouth\n\n"
455 " -h --help Show this help\n"
456 " --version Show package version\n",
457 program_invocation_short_name);
460 static int parse_argv(int argc, char *argv[]) {
467 static const struct option options[] = {
468 { "help", no_argument, NULL, 'h' },
469 { "version", no_argument, NULL, ARG_VERSION },
478 while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
486 puts(PACKAGE_STRING);
487 puts(SYSTEMD_FEATURES);
494 assert_not_reached("Unhandled option");
498 log_error("Extraneous arguments");
505 int main(int argc, char *argv[]) {
506 _cleanup_manager_free_ Manager *m = NULL;
510 log_set_target(LOG_TARGET_AUTO);
511 log_parse_environment();
515 r = parse_argv(argc, argv);
517 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
519 n = sd_listen_fds(0);
521 log_error("Too many file descriptors received.");
524 fd = SD_LISTEN_FDS_START + 0;
526 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
528 log_error_errno(r, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
533 r = manager_new(&m, fd);
535 log_error_errno(r, "Failed to allocate manager: %m");
539 r = sd_event_add_io(m->event, NULL, fd, EPOLLIN, new_connection_handler, m);
541 log_error_errno(r, "Can't listen to connection socket: %m");
545 r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
547 log_error_errno(r, "Failed to run event loop: %m");
551 sd_event_get_exit_code(m->event, &r);
553 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;