chiark / gitweb /
df413fbb83cc4348a423e3268e6e9caa0fe53831
[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         m->plymouth_fd = safe_close(m->plymouth_fd);
126         m->plymouth_cancel_sent = false;
127 }
128
129 static int plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
130         Manager *m = userdata;
131         Client *current;
132         char buffer[6];
133         int r;
134
135         assert(m);
136
137         r = read(m->plymouth_fd, buffer, sizeof(buffer));
138         if (r <= 0)
139                 on_plymouth_disconnect(m);
140         else {
141                if (buffer[0] == '\15')
142                        log_error("Message update to plymouth wasn't delivered successfully");
143
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);
150                }
151         }
152
153         return 0;
154 }
155
156 static int send_message_plymouth_socket(int plymouth_fd, const char *message, bool update) {
157         _cleanup_free_ char *packet = NULL;
158         int r, n;
159         char mode = 'M';
160
161         if (update)
162                 mode = 'U';
163
164         if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0)
165                 return log_oom();
166         r = loop_write(plymouth_fd, packet, n + 1, true);
167         return r;
168 }
169
170
171 static int send_message_plymouth(Manager *m, const char *message) {
172         int r;
173         const char *plymouth_cancel_message = NULL;
174
175         r = connect_plymouth(m);
176         if (r < 0)
177                 return r;
178
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);
182                 if (r < 0)
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);
187                 if (r < 0)
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);
192                 if (r < 0)
193                         log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
194         }
195
196         r = send_message_plymouth_socket(m->plymouth_fd,  message, true);
197         if (r < 0)
198                 return log_warning_errno(errno, "Couldn't send \"%s\" to plymouth: %m", message);
199
200         return 0;
201 }
202
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;
209
210         /* get the overall percentage */
211         LIST_FOREACH(clients, current, m->clients) {
212                 current_numdevices++;
213
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);
218         }
219
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;
224
225                 if (asprintf(&console_message,
226                              ngettext("Checking in progress on %d disk (%3.1f%% complete)",
227                                       "Checking in progress on %d disks (%3.1f%% complete)", m->numdevices),
228                                       m->numdevices, m->percent) < 0)
229                         return -ENOMEM;
230                 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
231                         return -ENOMEM;
232
233                 /* write to console */
234                 if (m->console) {
235                         fprintf(m->console, "\r%s\r%n", console_message, &l);
236                         fflush(m->console);
237                 }
238
239                 /* try to connect to plymouth and send message */
240                 r = send_message_plymouth(m, fsck_message);
241                 if (r < 0)
242                         log_debug("Couldn't send message to plymouth");
243
244                 if (l > m->clear)
245                         m->clear = l;
246         }
247         return 0;
248 }
249
250 static int connect_plymouth(Manager *m) {
251         union sockaddr_union sa = PLYMOUTH_SOCKET;
252         int r;
253
254         /* try to connect or reconnect if sending a message */
255         if (m->plymouth_fd <= 0) {
256                 m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
257                 if (m->plymouth_fd < 0) {
258                         return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
259                 }
260                 if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
261                         on_plymouth_disconnect(m);
262                         return log_warning_errno(errno, "Couldn't connect to plymouth: %m");
263                 }
264                 r = sd_event_add_io(m->event, NULL, m->plymouth_fd, EPOLLIN, plymouth_feedback_handler, m);
265                 if (r < 0) {
266                         on_plymouth_disconnect(m);
267                         return log_warning_errno(r, "Can't listen to plymouth socket: %m");
268                 }
269         }
270
271         return 0;
272 }
273
274 static int progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
275         Client *client = userdata;
276         Manager *m = NULL;
277         FsckProgress fsck_data;
278         size_t buflen;
279         int r;
280
281         assert(client);
282         m = client->manager;
283
284         /* check first if we need to cancel this client */
285         if (m->cancel_requested) {
286                 if (!client->cancelled)
287                         request_cancel_client(client);
288         }
289
290         /* ensure we have enough data to read */
291         r = ioctl(fd, FIONREAD, &buflen);
292         if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
293                 if (client->buflen != buflen)
294                         client->buflen = buflen;
295                 /* we got twice the same size from a bad behaving client, kick it off the list */
296                 else {
297                         log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
298                         remove_client(&(m->clients), client);
299                         r = update_global_progress(m);
300                         if (r < 0)
301                                 log_warning_errno(r, "Couldn't update global progress: %m");
302                 }
303                 return 0;
304         }
305
306         /* read actual data */
307         r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
308         if (r == 0) {
309                 log_debug("Fsck client connected to fd %d disconnected", client->fd);
310                 remove_client(&(m->clients), client);
311         } else if (r > 0 && r != sizeof(FsckProgress))
312                 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
313         else if (r > 0 && r == sizeof(FsckProgress)) {
314                 client->devnum = fsck_data.devnum;
315                 client->cur = fsck_data.cur;
316                 client->max = fsck_data.max;
317                 client->pass = fsck_data.pass;
318                 client->percent = compute_percent(client->pass, client->cur, client->max);
319                 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
320                           major(client->devnum), minor(client->devnum),
321                           client->cur, client->max, client->pass, client->percent);
322         } else
323                 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
324
325         r = update_global_progress(m);
326         if (r < 0)
327                 log_warning_errno(r, "Couldn't update global progress: %m");
328
329         return 0;
330 }
331
332 static int new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
333         Manager *m = userdata;
334         Client *client = NULL;
335         int new_client_fd, r;
336
337         assert(m);
338
339         /* Initialize and list new clients */
340         new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
341         if (new_client_fd < 0)
342                 return log_error_errno(errno, "Couldn't accept a new connection: %m");
343
344         log_debug("New fsck client connected to fd: %d", new_client_fd);
345
346         client = new0(Client, 1);
347         if (!client)
348                 return log_oom();
349         client->fd = new_client_fd;
350         client->manager = m;
351         LIST_PREPEND(clients, m->clients, client);
352         r = sd_event_add_io(m->event, NULL, client->fd, EPOLLIN, progress_handler, client);
353         if (r < 0) {
354                 remove_client(&(m->clients), client);
355                 return r;
356         }
357         /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
358         if (m->cancel_requested)
359                 request_cancel_client(client);
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 }