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/>.
31 #include <sys/socket.h>
32 #include <sys/types.h>
38 #include "event-util.h"
43 #include "sd-daemon.h"
44 #include "socket-util.h"
47 #define IDLE_TIME_SECONDS 30
48 #define PLYMOUTH_REQUEST_KEY "K\2\2\3"
52 typedef struct Client {
53 struct Manager *manager;
63 LIST_FIELDS(struct Client, clients);
66 typedef struct Manager {
75 bool plymouth_cancel_sent;
76 bool cancel_requested;
79 static int connect_plymouth(Manager *m);
80 static int update_global_progress(Manager *m);
81 static void manager_free(Manager *m);
82 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
83 #define _cleanup_manager_free_ _cleanup_(manager_freep)
85 static double compute_percent(int pass, size_t cur, size_t max) {
86 /* Values stolen from e2fsck */
88 static const double pass_table[] = {
89 0, 70, 90, 92, 95, 100
95 if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
98 return pass_table[pass-1] +
99 (pass_table[pass] - pass_table[pass-1]) *
103 static int request_cancel_client(Client *current) {
104 FsckdMessage cancel_msg;
106 cancel_msg.cancel = 1;
108 n = send(current->fd, &cancel_msg, sizeof(FsckdMessage), 0);
109 if (n < 0 || (size_t) n < sizeof(FsckdMessage))
110 return log_warning_errno(n, "Cannot send cancel to fsck on (%u, %u): %m",
111 major(current->devnum), minor(current->devnum));
113 current->cancelled = true;
117 static void remove_client(Client **first, Client *item) {
118 LIST_REMOVE(clients, *first, item);
119 safe_close(item->fd);
123 static void on_plymouth_disconnect(Manager *m) {
124 safe_close(m->plymouth_fd);
126 m->plymouth_cancel_sent = false;
129 static int plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
130 Manager *m = userdata;
137 r = read(m->plymouth_fd, buffer, sizeof(buffer));
139 on_plymouth_disconnect(m);
141 if (buffer[0] == '\15')
142 log_error("Message update to plymouth wasn't delivered successfully");
144 /* the only answer support type we requested is a key interruption */
145 if (buffer[0] == '\2' && buffer[5] == '\3') {
146 m->cancel_requested = true;
147 /* cancel all connected clients */
148 LIST_FOREACH(clients, current, m->clients)
149 request_cancel_client(current);
156 static int send_message_plymouth_socket(int plymouth_fd, const char *message, bool update) {
157 _cleanup_free_ char *packet = NULL;
164 if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0)
166 r = loop_write(plymouth_fd, packet, n + 1, true);
171 static int send_message_plymouth(Manager *m, const char *message) {
173 const char *plymouth_cancel_message = NULL;
175 r = connect_plymouth(m);
179 if (!m->plymouth_cancel_sent) {
180 /* indicate to plymouth that we listen to Ctrl+C */
181 r = loop_write(m->plymouth_fd, PLYMOUTH_REQUEST_KEY, sizeof(PLYMOUTH_REQUEST_KEY), true);
183 return log_warning_errno(errno, "Can't send to plymouth cancel key: %m");
184 m->plymouth_cancel_sent = true;
185 plymouth_cancel_message = strjoina("fsckd-cancel-msg:", "Press Ctrl+C to cancel all filesystem checks in progress");
186 r = send_message_plymouth_socket(m->plymouth_fd, plymouth_cancel_message, false);
188 log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m");
189 } else if (m->numdevices == 0) {
190 m->plymouth_cancel_sent = false;
191 r = send_message_plymouth_socket(m->plymouth_fd, "", false);
193 log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
196 r = send_message_plymouth_socket(m->plymouth_fd, message, true);
198 return log_warning_errno(errno, "Couldn't send \"%s\" to plymouth: %m", message);
203 static int update_global_progress(Manager *m) {
204 Client *current = NULL;
205 _cleanup_free_ char *console_message = NULL;
206 _cleanup_free_ char *fsck_message = NULL;
207 int current_numdevices = 0, l = 0, r;
208 double current_percent = 100;
210 /* get the overall percentage */
211 LIST_FOREACH(clients, current, m->clients) {
212 current_numdevices++;
214 /* right now, we only keep the minimum % of all fsckd processes. We could in the future trying to be
215 linear, but max changes and corresponds to the pass. We have all the informations into fsckd
216 already if we can treat that in a smarter way. */
217 current_percent = MIN(current_percent, current->percent);
220 /* update if there is anything user-visible to update */
221 if (fabs(current_percent - m->percent) > 0.001 || current_numdevices != m->numdevices) {
222 m->numdevices = current_numdevices;
223 m->percent = current_percent;
225 if (asprintf(&console_message, "Checking in progress on %d disks (%3.1f%% complete)",
226 m->numdevices, m->percent) < 0)
228 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
231 /* write to console */
233 fprintf(m->console, "\r%s\r%n", console_message, &l);
237 /* try to connect to plymouth and send message */
238 r = send_message_plymouth(m, fsck_message);
240 log_debug("Couldn't send message to plymouth");
248 static int connect_plymouth(Manager *m) {
249 union sockaddr_union sa = PLYMOUTH_SOCKET;
252 /* try to connect or reconnect if sending a message */
253 if (m->plymouth_fd <= 0) {
254 m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
255 if (m->plymouth_fd < 0) {
256 return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
258 if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
259 on_plymouth_disconnect(m);
260 return log_warning_errno(errno, "Couldn't connect to plymouth: %m");
262 r = sd_event_add_io(m->event, NULL, m->plymouth_fd, EPOLLIN, plymouth_feedback_handler, m);
264 on_plymouth_disconnect(m);
265 return log_warning_errno(r, "Can't listen to plymouth socket: %m");
272 static int progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
273 Client *client = userdata;
275 FsckProgress fsck_data;
282 /* check first if we need to cancel this client */
283 if (m->cancel_requested) {
284 if (!client->cancelled)
285 request_cancel_client(client);
288 /* ensure we have enough data to read */
289 r = ioctl(fd, FIONREAD, &buflen);
290 if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
291 if (client->buflen != buflen)
292 client->buflen = buflen;
293 /* we got twice the same size from a bad behaving client, kick it off the list */
295 log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
296 remove_client(&(m->clients), client);
297 r = update_global_progress(m);
299 log_warning_errno(r, "Couldn't update global progress: %m");
304 /* read actual data */
305 r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
307 log_debug("Fsck client connected to fd %d disconnected", client->fd);
308 remove_client(&(m->clients), client);
309 } else if (r > 0 && r != sizeof(FsckProgress))
310 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
311 else if (r > 0 && r == sizeof(FsckProgress)) {
312 client->devnum = fsck_data.devnum;
313 client->cur = fsck_data.cur;
314 client->max = fsck_data.max;
315 client->pass = fsck_data.pass;
316 client->percent = compute_percent(client->pass, client->cur, client->max);
317 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
318 major(client->devnum), minor(client->devnum),
319 client->cur, client->max, client->pass, client->percent);
321 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
323 r = update_global_progress(m);
325 log_warning_errno(r, "Couldn't update global progress: %m");
330 static int new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
331 Manager *m = userdata;
332 Client *client = NULL;
333 int new_client_fd, r;
337 /* Initialize and list new clients */
338 new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
339 if (new_client_fd > 0) {
340 log_debug("New fsck client connected to fd: %d", new_client_fd);
341 client = new0(Client, 1);
344 client->fd = new_client_fd;
346 LIST_PREPEND(clients, m->clients, client);
347 r = sd_event_add_io(m->event, NULL, client->fd, EPOLLIN, progress_handler, client);
349 remove_client(&(m->clients), client);
352 /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
353 if (m->cancel_requested)
354 request_cancel_client(client);
356 return log_error_errno(errno, "Couldn't accept a new connection: %m");
361 static void manager_free(Manager *m) {
362 Client *current = NULL, *l = NULL;
366 /* clear last line */
367 if (m->console && m->clear > 0) {
370 fputc('\r', m->console);
371 for (j = 0; j < (unsigned) m->clear; j++)
372 fputc(' ', m->console);
373 fputc('\r', m->console);
377 safe_close(m->connection_fd);
378 safe_close(m->plymouth_fd);
382 LIST_FOREACH_SAFE(clients, current, l, m->clients)
383 remove_client(&(m->clients), current);
385 sd_event_unref(m->event);
390 static int manager_new(Manager **ret, int fd) {
391 _cleanup_manager_free_ Manager *m = NULL;
396 m = new0(Manager, 1);
400 r = sd_event_default(&m->event);
404 m->connection_fd = fd;
405 if (access("/run/systemd/show-status", F_OK) >= 0) {
406 m->console = fopen("/dev/console", "we");
408 return log_warning_errno(errno, "Can't connect to /dev/console: %m");
419 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
425 r = sd_event_get_state(e);
428 if (r == SD_EVENT_FINISHED)
431 r = sd_event_run(e, timeout);
435 /* timeout reached */
442 r = sd_event_get_exit_code(e, &code);
449 static void help(void) {
450 printf("%s [OPTIONS...]\n\n"
451 "Capture fsck progress and forward one stream to plymouth\n\n"
452 " -h --help Show this help\n"
453 " --version Show package version\n",
454 program_invocation_short_name);
457 static int parse_argv(int argc, char *argv[]) {
464 static const struct option options[] = {
465 { "help", no_argument, NULL, 'h' },
466 { "version", no_argument, NULL, ARG_VERSION },
475 while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
483 puts(PACKAGE_STRING);
484 puts(SYSTEMD_FEATURES);
491 assert_not_reached("Unhandled option");
495 log_error("Extraneous arguments");
502 int main(int argc, char *argv[]) {
503 _cleanup_manager_free_ Manager *m = NULL;
507 log_set_target(LOG_TARGET_AUTO);
508 log_parse_environment();
511 r = parse_argv(argc, argv);
513 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
515 n = sd_listen_fds(0);
517 log_error("Too many file descriptors received.");
520 fd = SD_LISTEN_FDS_START + 0;
522 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
524 log_error_errno(r, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
529 r = manager_new(&m, fd);
531 log_error_errno(r, "Failed to allocate manager: %m");
535 r = sd_event_add_io(m->event, NULL, fd, EPOLLIN, new_connection_handler, m);
537 log_error_errno(r, "Can't listen to connection socket: %m");
541 r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
543 log_error_errno(r, "Failed to run event loop: %m");
547 sd_event_get_exit_code(m->event, &r);
549 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;