chiark / gitweb /
834476c14a689dcb48f7a0a1c802551eacaa2481
[elogind.git] / src / fsckd / fsckd.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2015 Canonical
7
8   Author:
9     Didier Roche <didrocks@ubuntu.com>
10
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.
15
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.
20
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/>.
23 ***/
24
25 #include <getopt.h>
26 #include <errno.h>
27 #include <libintl.h>
28 #include <math.h>
29 #include <stdbool.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <sys/socket.h>
33 #include <sys/types.h>
34 #include <sys/un.h>
35 #include <unistd.h>
36
37 #include "build.h"
38 #include "def.h"
39 #include "event-util.h"
40 #include "fsckd.h"
41 #include "log.h"
42 #include "list.h"
43 #include "macro.h"
44 #include "sd-daemon.h"
45 #include "socket-util.h"
46 #include "util.h"
47
48 #define IDLE_TIME_SECONDS 30
49 #define PLYMOUTH_REQUEST_KEY "K\2\2\3"
50
51 struct Manager;
52
53 typedef struct Client {
54         struct Manager *manager;
55         int fd;
56         dev_t devnum;
57         size_t cur;
58         size_t max;
59         int pass;
60         double percent;
61         size_t buflen;
62         bool cancelled;
63
64         LIST_FIELDS(struct Client, clients);
65 } Client;
66
67 typedef struct Manager {
68         sd_event *event;
69         Client *clients;
70         int clear;
71         int connection_fd;
72         FILE *console;
73         double percent;
74         int numdevices;
75         int plymouth_fd;
76         bool plymouth_cancel_sent;
77         bool cancel_requested;
78 } Manager;
79
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)
85
86 static double compute_percent(int pass, size_t cur, size_t max) {
87         /* Values stolen from e2fsck */
88
89         static const double pass_table[] = {
90                 0, 70, 90, 92, 95, 100
91         };
92
93         if (pass <= 0)
94                 return 0.0;
95
96         if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
97                 return 100.0;
98
99         return pass_table[pass-1] +
100                 (pass_table[pass] - pass_table[pass-1]) *
101                 (double) cur / max;
102 }
103
104 static int request_cancel_client(Client *current) {
105         FsckdMessage cancel_msg;
106         ssize_t n;
107         cancel_msg.cancel = 1;
108
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));
113         else
114                 current->cancelled = true;
115         return 0;
116 }
117
118 static void remove_client(Client **first, Client *item) {
119         LIST_REMOVE(clients, *first, item);
120         safe_close(item->fd);
121         free(item);
122 }
123
124 static void on_plymouth_disconnect(Manager *m) {
125         safe_close(m->plymouth_fd);
126         m->plymouth_fd = -1;
127         m->plymouth_cancel_sent = false;
128 }
129
130 static int plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
131         Manager *m = userdata;
132         Client *current;
133         char buffer[6];
134         int r;
135
136         assert(m);
137
138         r = read(m->plymouth_fd, buffer, sizeof(buffer));
139         if (r <= 0)
140                 on_plymouth_disconnect(m);
141         else {
142                if (buffer[0] == '\15')
143                        log_error("Message update to plymouth wasn't delivered successfully");
144
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);
151                }
152         }
153
154         return 0;
155 }
156
157 static int send_message_plymouth_socket(int plymouth_fd, const char *message, bool update) {
158         _cleanup_free_ char *packet = NULL;
159         int r, n;
160         char mode = 'M';
161
162         if (update)
163                 mode = 'U';
164
165         if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0)
166                 return log_oom();
167         r = loop_write(plymouth_fd, packet, n + 1, true);
168         return r;
169 }
170
171
172 static int send_message_plymouth(Manager *m, const char *message) {
173         int r;
174         const char *plymouth_cancel_message = NULL;
175
176         r = connect_plymouth(m);
177         if (r < 0)
178                 return r;
179
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);
183                 if (r < 0)
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);
188                 if (r < 0)
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);
193                 if (r < 0)
194                         log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
195         }
196
197         r = send_message_plymouth_socket(m->plymouth_fd,  message, true);
198         if (r < 0)
199                 return log_warning_errno(errno, "Couldn't send \"%s\" to plymouth: %m", message);
200
201         return 0;
202 }
203
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;
210
211         /* get the overall percentage */
212         LIST_FOREACH(clients, current, m->clients) {
213                 current_numdevices++;
214
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);
219         }
220
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;
225
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)
230                         return -ENOMEM;
231                 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
232                         return -ENOMEM;
233
234                 /* write to console */
235                 if (m->console) {
236                         fprintf(m->console, "\r%s\r%n", console_message, &l);
237                         fflush(m->console);
238                 }
239
240                 /* try to connect to plymouth and send message */
241                 r = send_message_plymouth(m, fsck_message);
242                 if (r < 0)
243                         log_debug("Couldn't send message to plymouth");
244
245                 if (l > m->clear)
246                         m->clear = l;
247         }
248         return 0;
249 }
250
251 static int connect_plymouth(Manager *m) {
252         union sockaddr_union sa = PLYMOUTH_SOCKET;
253         int r;
254
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");
260                 }
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");
264                 }
265                 r = sd_event_add_io(m->event, NULL, m->plymouth_fd, EPOLLIN, plymouth_feedback_handler, m);
266                 if (r < 0) {
267                         on_plymouth_disconnect(m);
268                         return log_warning_errno(r, "Can't listen to plymouth socket: %m");
269                 }
270         }
271
272         return 0;
273 }
274
275 static int progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
276         Client *client = userdata;
277         Manager *m = NULL;
278         FsckProgress fsck_data;
279         size_t buflen;
280         int r;
281
282         assert(client);
283         m = client->manager;
284
285         /* check first if we need to cancel this client */
286         if (m->cancel_requested) {
287                 if (!client->cancelled)
288                         request_cancel_client(client);
289         }
290
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 */
297                 else {
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);
301                         if (r < 0)
302                                 log_warning_errno(r, "Couldn't update global progress: %m");
303                 }
304                 return 0;
305         }
306
307         /* read actual data */
308         r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
309         if (r == 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);
323         } else
324                 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
325
326         r = update_global_progress(m);
327         if (r < 0)
328                 log_warning_errno(r, "Couldn't update global progress: %m");
329
330         return 0;
331 }
332
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;
337
338         assert(m);
339
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);
345                 if (!client)
346                         return log_oom();
347                 client->fd = new_client_fd;
348                 client->manager = m;
349                 LIST_PREPEND(clients, m->clients, client);
350                 r = sd_event_add_io(m->event, NULL, client->fd, EPOLLIN, progress_handler, client);
351                 if (r < 0) {
352                         remove_client(&(m->clients), client);
353                         return r;
354                 }
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);
358         } else
359                 return log_error_errno(errno, "Couldn't accept a new connection: %m");
360
361         return 0;
362 }
363
364 static void manager_free(Manager *m) {
365         Client *current = NULL, *l = NULL;
366         if (!m)
367                 return;
368
369         /* clear last line */
370         if (m->console && m->clear > 0) {
371                 unsigned j;
372
373                 fputc('\r', m->console);
374                 for (j = 0; j < (unsigned) m->clear; j++)
375                         fputc(' ', m->console);
376                 fputc('\r', m->console);
377                 fflush(m->console);
378         }
379
380         safe_close(m->connection_fd);
381         safe_close(m->plymouth_fd);
382         if (m->console)
383                 fclose(m->console);
384
385         LIST_FOREACH_SAFE(clients, current, l, m->clients)
386                 remove_client(&(m->clients), current);
387
388         sd_event_unref(m->event);
389
390         free(m);
391 }
392
393 static int manager_new(Manager **ret, int fd) {
394         _cleanup_manager_free_ Manager *m = NULL;
395         int r;
396
397         assert(ret);
398
399         m = new0(Manager, 1);
400         if (!m)
401                 return -ENOMEM;
402
403         r = sd_event_default(&m->event);
404         if (r < 0)
405                 return r;
406
407         m->connection_fd = fd;
408         if (access("/run/systemd/show-status", F_OK) >= 0) {
409                 m->console = fopen("/dev/console", "we");
410                 if (!m->console)
411                         return log_warning_errno(errno, "Can't connect to /dev/console: %m");
412         }
413         m->percent = 100;
414
415         m->plymouth_fd = -1;
416         *ret = m;
417         m = NULL;
418
419         return 0;
420 }
421
422 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
423         int r, code;
424
425         assert(e);
426
427         for (;;) {
428                 r = sd_event_get_state(e);
429                 if (r < 0)
430                         return r;
431                 if (r == SD_EVENT_FINISHED)
432                         break;
433
434                 r = sd_event_run(e, timeout);
435                 if (r < 0)
436                         return r;
437
438                 /* timeout reached */
439                 if (r == 0) {
440                         sd_event_exit(e, 0);
441                         break;
442                 }
443         }
444
445         r = sd_event_get_exit_code(e, &code);
446         if (r < 0)
447                 return r;
448
449         return code;
450 }
451
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);
458 }
459
460 static int parse_argv(int argc, char *argv[]) {
461
462         enum {
463                 ARG_VERSION = 0x100,
464                 ARG_ROOT,
465         };
466
467         static const struct option options[] = {
468                 { "help",      no_argument,       NULL, 'h'           },
469                 { "version",   no_argument,       NULL, ARG_VERSION   },
470                 {}
471         };
472
473         int c;
474
475         assert(argc >= 0);
476         assert(argv);
477
478         while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
479                 switch (c) {
480
481                 case 'h':
482                         help();
483                         return 0;
484
485                 case ARG_VERSION:
486                         puts(PACKAGE_STRING);
487                         puts(SYSTEMD_FEATURES);
488                         return 0;
489
490                 case '?':
491                         return -EINVAL;
492
493                 default:
494                         assert_not_reached("Unhandled option");
495                 }
496
497         if (optind < argc) {
498                 log_error("Extraneous arguments");
499                 return -EINVAL;
500         }
501
502         return 1;
503 }
504
505 int main(int argc, char *argv[]) {
506         _cleanup_manager_free_ Manager *m = NULL;
507         int fd = -1;
508         int r, n;
509
510         log_set_target(LOG_TARGET_AUTO);
511         log_parse_environment();
512         log_open();
513         init_gettext();
514
515         r = parse_argv(argc, argv);
516         if (r <= 0)
517                 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
518
519         n = sd_listen_fds(0);
520         if (n > 1) {
521                 log_error("Too many file descriptors received.");
522                 return EXIT_FAILURE;
523         } else if (n == 1) {
524                 fd = SD_LISTEN_FDS_START + 0;
525         } else {
526                 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
527                 if (fd < 0) {
528                         log_error_errno(r, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
529                         return EXIT_FAILURE;
530                 }
531         }
532
533         r = manager_new(&m, fd);
534         if (r < 0) {
535                 log_error_errno(r, "Failed to allocate manager: %m");
536                 return EXIT_FAILURE;
537         }
538
539         r = sd_event_add_io(m->event, NULL, fd, EPOLLIN, new_connection_handler, m);
540         if (r < 0) {
541                 log_error_errno(r, "Can't listen to connection socket: %m");
542                 return EXIT_FAILURE;
543         }
544
545         r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
546         if (r < 0) {
547                 log_error_errno(r, "Failed to run event loop: %m");
548                 return EXIT_FAILURE;
549         }
550
551         sd_event_get_exit_code(m->event, &r);
552
553         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
554 }