chiark / gitweb /
cf823322a1c5622f2dc64ce6e9d6ddc0579fabdd
[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
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                 return 0;
258
259         m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
260         if (m->plymouth_fd < 0)
261                 return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
262
263         if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
264                 r = log_warning_errno(errno, "Couldn't connect to plymouth: %m");
265                 goto fail;
266         }
267
268         r = sd_event_add_io(m->event, NULL, m->plymouth_fd, EPOLLIN, plymouth_feedback_handler, m);
269         if (r < 0) {
270                 log_warning_errno(r, "Can't listen to plymouth socket: %m");
271                 goto fail;
272         }
273
274         return 0;
275
276 fail:
277         on_plymouth_disconnect(m);
278         return r;
279 }
280
281 static int progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
282         Client *client = userdata;
283         Manager *m = NULL;
284         FsckProgress fsck_data;
285         size_t buflen;
286         int r;
287
288         assert(client);
289         m = client->manager;
290
291         /* check first if we need to cancel this client */
292         if (m->cancel_requested) {
293                 if (!client->cancelled)
294                         request_cancel_client(client);
295         }
296
297         /* ensure we have enough data to read */
298         r = ioctl(fd, FIONREAD, &buflen);
299         if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
300                 if (client->buflen != buflen)
301                         client->buflen = buflen;
302                 /* we got twice the same size from a bad behaving client, kick it off the list */
303                 else {
304                         log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
305                         remove_client(&(m->clients), client);
306                         r = update_global_progress(m);
307                         if (r < 0)
308                                 log_warning_errno(r, "Couldn't update global progress: %m");
309                 }
310                 return 0;
311         }
312
313         /* read actual data */
314         r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
315         if (r == 0) {
316                 log_debug("Fsck client connected to fd %d disconnected", client->fd);
317                 remove_client(&(m->clients), client);
318         } else if (r > 0 && r != sizeof(FsckProgress))
319                 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
320         else if (r > 0 && r == sizeof(FsckProgress)) {
321                 client->devnum = fsck_data.devnum;
322                 client->cur = fsck_data.cur;
323                 client->max = fsck_data.max;
324                 client->pass = fsck_data.pass;
325                 client->percent = compute_percent(client->pass, client->cur, client->max);
326                 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
327                           major(client->devnum), minor(client->devnum),
328                           client->cur, client->max, client->pass, client->percent);
329         } else
330                 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
331
332         r = update_global_progress(m);
333         if (r < 0)
334                 log_warning_errno(r, "Couldn't update global progress: %m");
335
336         return 0;
337 }
338
339 static int new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
340         Manager *m = userdata;
341         Client *client = NULL;
342         int new_client_fd, r;
343
344         assert(m);
345
346         /* Initialize and list new clients */
347         new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
348         if (new_client_fd < 0)
349                 return log_error_errno(errno, "Couldn't accept a new connection: %m");
350
351         log_debug("New fsck client connected to fd: %d", new_client_fd);
352
353         client = new0(Client, 1);
354         if (!client)
355                 return log_oom();
356         client->fd = new_client_fd;
357         client->manager = m;
358         LIST_PREPEND(clients, m->clients, client);
359         r = sd_event_add_io(m->event, NULL, client->fd, EPOLLIN, progress_handler, client);
360         if (r < 0) {
361                 remove_client(&(m->clients), client);
362                 return r;
363         }
364         /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
365         if (m->cancel_requested)
366                 request_cancel_client(client);
367
368         return 0;
369 }
370
371 static void manager_free(Manager *m) {
372         Client *current = NULL, *l = NULL;
373         if (!m)
374                 return;
375
376         /* clear last line */
377         if (m->console && m->clear > 0) {
378                 unsigned j;
379
380                 fputc('\r', m->console);
381                 for (j = 0; j < (unsigned) m->clear; j++)
382                         fputc(' ', m->console);
383                 fputc('\r', m->console);
384                 fflush(m->console);
385         }
386
387         safe_close(m->connection_fd);
388         safe_close(m->plymouth_fd);
389         if (m->console)
390                 fclose(m->console);
391
392         LIST_FOREACH_SAFE(clients, current, l, m->clients)
393                 remove_client(&(m->clients), current);
394
395         sd_event_unref(m->event);
396
397         free(m);
398 }
399
400 static int manager_new(Manager **ret, int fd) {
401         _cleanup_manager_free_ Manager *m = NULL;
402         int r;
403
404         assert(ret);
405
406         m = new0(Manager, 1);
407         if (!m)
408                 return -ENOMEM;
409
410         r = sd_event_default(&m->event);
411         if (r < 0)
412                 return r;
413
414         m->connection_fd = fd;
415         if (access("/run/systemd/show-status", F_OK) >= 0) {
416                 m->console = fopen("/dev/console", "we");
417                 if (!m->console)
418                         return log_warning_errno(errno, "Can't connect to /dev/console: %m");
419         }
420         m->percent = 100;
421
422         m->plymouth_fd = -1;
423         *ret = m;
424         m = NULL;
425
426         return 0;
427 }
428
429 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
430         int r, code;
431
432         assert(e);
433
434         for (;;) {
435                 r = sd_event_get_state(e);
436                 if (r < 0)
437                         return r;
438                 if (r == SD_EVENT_FINISHED)
439                         break;
440
441                 r = sd_event_run(e, timeout);
442                 if (r < 0)
443                         return r;
444
445                 /* timeout reached */
446                 if (r == 0) {
447                         sd_event_exit(e, 0);
448                         break;
449                 }
450         }
451
452         r = sd_event_get_exit_code(e, &code);
453         if (r < 0)
454                 return r;
455
456         return code;
457 }
458
459 static void help(void) {
460         printf("%s [OPTIONS...]\n\n"
461                "Capture fsck progress and forward one stream to plymouth\n\n"
462                "  -h --help             Show this help\n"
463                "     --version          Show package version\n",
464                program_invocation_short_name);
465 }
466
467 static int parse_argv(int argc, char *argv[]) {
468
469         enum {
470                 ARG_VERSION = 0x100,
471                 ARG_ROOT,
472         };
473
474         static const struct option options[] = {
475                 { "help",      no_argument,       NULL, 'h'           },
476                 { "version",   no_argument,       NULL, ARG_VERSION   },
477                 {}
478         };
479
480         int c;
481
482         assert(argc >= 0);
483         assert(argv);
484
485         while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
486                 switch (c) {
487
488                 case 'h':
489                         help();
490                         return 0;
491
492                 case ARG_VERSION:
493                         puts(PACKAGE_STRING);
494                         puts(SYSTEMD_FEATURES);
495                         return 0;
496
497                 case '?':
498                         return -EINVAL;
499
500                 default:
501                         assert_not_reached("Unhandled option");
502                 }
503
504         if (optind < argc) {
505                 log_error("Extraneous arguments");
506                 return -EINVAL;
507         }
508
509         return 1;
510 }
511
512 int main(int argc, char *argv[]) {
513         _cleanup_manager_free_ Manager *m = NULL;
514         int fd = -1;
515         int r, n;
516
517         log_set_target(LOG_TARGET_AUTO);
518         log_parse_environment();
519         log_open();
520         init_gettext();
521
522         r = parse_argv(argc, argv);
523         if (r <= 0)
524                 goto finish;
525
526         n = sd_listen_fds(0);
527         if (n > 1) {
528                 log_error("Too many file descriptors received.");
529                 r = -EINVAL;
530                 goto finish;
531         } else if (n == 1)
532                 fd = SD_LISTEN_FDS_START + 0;
533         else {
534                 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
535                 if (fd < 0) {
536                         r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
537                         goto finish;
538                 }
539         }
540
541         r = manager_new(&m, fd);
542         if (r < 0) {
543                 log_error_errno(r, "Failed to allocate manager: %m");
544                 goto finish;
545         }
546
547         r = sd_event_add_io(m->event, NULL, fd, EPOLLIN, new_connection_handler, m);
548         if (r < 0) {
549                 log_error_errno(r, "Can't listen to connection socket: %m");
550                 goto finish;
551         }
552
553         r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
554         if (r < 0) {
555                 log_error_errno(r, "Failed to run event loop: %m");
556                 goto finish;
557         }
558
559         sd_event_get_exit_code(m->event, &r);
560
561 finish:
562         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
563 }