chiark / gitweb /
4a61d80a18e3980d41ce30c8bb058e5d615fc41e
[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 "sd-daemon.h"
38 #include "build.h"
39 #include "def.h"
40 #include "event-util.h"
41 #include "log.h"
42 #include "list.h"
43 #include "macro.h"
44 #include "socket-util.h"
45 #include "util.h"
46 #include "fsckd.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                 .cancel = 1,
107         };
108
109         ssize_t n;
110
111         n = send(current->fd, &cancel_msg, sizeof(FsckdMessage), 0);
112         if (n < 0)
113                 return log_warning_errno(errno, "Cannot send cancel to fsck on (%u:%u): %m", major(current->devnum), minor(current->devnum));
114         if ((size_t) n < sizeof(FsckdMessage)) {
115                 log_warning("Short send when sending cancel to fsck on (%u:%u).", major(current->devnum), minor(current->devnum));
116                 return -EIO;
117         }
118
119         current->cancelled = true;
120         return 0;
121 }
122
123 static void remove_client(Client **first, Client *item) {
124         LIST_REMOVE(clients, *first, item);
125         safe_close(item->fd);
126         free(item);
127 }
128
129 static void on_plymouth_disconnect(Manager *m) {
130         m->plymouth_fd = safe_close(m->plymouth_fd);
131         m->plymouth_cancel_sent = false;
132 }
133
134 static int plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
135         Manager *m = userdata;
136         Client *current;
137         char buffer[6];
138         int r;
139
140         assert(m);
141
142         r = read(m->plymouth_fd, buffer, sizeof(buffer));
143         if (r <= 0)
144                 on_plymouth_disconnect(m);
145         else {
146                if (buffer[0] == '\15')
147                        log_error("Message update to plymouth wasn't delivered successfully");
148
149                /* the only answer support type we requested is a key interruption */
150                if (buffer[0] == '\2' && buffer[5] == '\3') {
151                        m->cancel_requested = true;
152                        /* cancel all connected clients */
153                        LIST_FOREACH(clients, current, m->clients)
154                                request_cancel_client(current);
155                }
156         }
157
158         return 0;
159 }
160
161 static int send_message_plymouth_socket(int plymouth_fd, const char *message, bool update) {
162         _cleanup_free_ char *packet = NULL;
163         int r, n;
164         char mode = 'M';
165
166         if (update)
167                 mode = 'U';
168
169         if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0)
170                 return log_oom();
171         r = loop_write(plymouth_fd, packet, n + 1, true);
172         return r;
173 }
174
175
176 static int send_message_plymouth(Manager *m, const char *message) {
177         int r;
178         const char *plymouth_cancel_message = NULL;
179
180         r = connect_plymouth(m);
181         if (r < 0)
182                 return r;
183
184         if (!m->plymouth_cancel_sent) {
185                 /* indicate to plymouth that we listen to Ctrl+C */
186                 r = loop_write(m->plymouth_fd, PLYMOUTH_REQUEST_KEY, sizeof(PLYMOUTH_REQUEST_KEY), true);
187                 if (r < 0)
188                         return log_warning_errno(errno, "Can't send to plymouth cancel key: %m");
189                 m->plymouth_cancel_sent = true;
190                 plymouth_cancel_message = strjoina("fsckd-cancel-msg:", _("Press Ctrl+C to cancel all filesystem checks in progress"));
191                 r = send_message_plymouth_socket(m->plymouth_fd, plymouth_cancel_message, false);
192                 if (r < 0)
193                         log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m");
194         } else if (m->numdevices == 0) {
195                 m->plymouth_cancel_sent = false;
196                 r = send_message_plymouth_socket(m->plymouth_fd, "", false);
197                 if (r < 0)
198                         log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
199         }
200
201         r = send_message_plymouth_socket(m->plymouth_fd,  message, true);
202         if (r < 0)
203                 return log_warning_errno(errno, "Couldn't send \"%s\" to plymouth: %m", message);
204
205         return 0;
206 }
207
208 static int update_global_progress(Manager *m) {
209         Client *current = NULL;
210         _cleanup_free_ char *console_message = NULL;
211         _cleanup_free_ char *fsck_message = NULL;
212         int current_numdevices = 0, l = 0, r;
213         double current_percent = 100;
214
215         /* get the overall percentage */
216         LIST_FOREACH(clients, current, m->clients) {
217                 current_numdevices++;
218
219                 /* right now, we only keep the minimum % of all fsckd processes. We could in the future trying to be
220                    linear, but max changes and corresponds to the pass. We have all the informations into fsckd
221                    already if we can treat that in a smarter way. */
222                 current_percent = MIN(current_percent, current->percent);
223         }
224
225         /* update if there is anything user-visible to update */
226         if (fabs(current_percent - m->percent) > 0.001 || current_numdevices != m->numdevices) {
227                 m->numdevices = current_numdevices;
228                 m->percent = current_percent;
229
230                 if (asprintf(&console_message,
231                              ngettext("Checking in progress on %d disk (%3.1f%% complete)",
232                                       "Checking in progress on %d disks (%3.1f%% complete)", m->numdevices),
233                                       m->numdevices, m->percent) < 0)
234                         return -ENOMEM;
235
236                 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
237                         return -ENOMEM;
238
239                 /* write to console */
240                 if (m->console) {
241                         fprintf(m->console, "\r%s\r%n", console_message, &l);
242                         fflush(m->console);
243                 }
244
245                 /* try to connect to plymouth and send message */
246                 r = send_message_plymouth(m, fsck_message);
247                 if (r < 0)
248                         log_debug("Couldn't send message to plymouth");
249
250                 if (l > m->clear)
251                         m->clear = l;
252         }
253         return 0;
254 }
255
256 static int connect_plymouth(Manager *m) {
257         union sockaddr_union sa = PLYMOUTH_SOCKET;
258         int r;
259
260         /* try to connect or reconnect if sending a message */
261         if (m->plymouth_fd >= 0)
262                 return 0;
263
264         m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
265         if (m->plymouth_fd < 0)
266                 return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
267
268         if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
269                 r = log_warning_errno(errno, "Couldn't connect to plymouth: %m");
270                 goto fail;
271         }
272
273         r = sd_event_add_io(m->event, NULL, m->plymouth_fd, EPOLLIN, plymouth_feedback_handler, m);
274         if (r < 0) {
275                 log_warning_errno(r, "Can't listen to plymouth socket: %m");
276                 goto fail;
277         }
278
279         return 0;
280
281 fail:
282         on_plymouth_disconnect(m);
283         return r;
284 }
285
286 static int progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
287         Client *client = userdata;
288         Manager *m = NULL;
289         FsckProgress fsck_data;
290         size_t buflen;
291         int r;
292
293         assert(client);
294         m = client->manager;
295
296         /* check first if we need to cancel this client */
297         if (m->cancel_requested) {
298                 if (!client->cancelled)
299                         request_cancel_client(client);
300         }
301
302         /* ensure we have enough data to read */
303         r = ioctl(fd, FIONREAD, &buflen);
304         if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
305                 if (client->buflen != buflen)
306                         client->buflen = buflen;
307                 /* we got twice the same size from a bad behaving client, kick it off the list */
308                 else {
309                         log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
310                         remove_client(&(m->clients), client);
311                         r = update_global_progress(m);
312                         if (r < 0)
313                                 log_warning_errno(r, "Couldn't update global progress: %m");
314                 }
315                 return 0;
316         }
317
318         /* read actual data */
319         r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
320         if (r == 0) {
321                 log_debug("Fsck client connected to fd %d disconnected", client->fd);
322                 remove_client(&(m->clients), client);
323         } else if (r > 0 && r != sizeof(FsckProgress))
324                 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
325         else if (r > 0 && r == sizeof(FsckProgress)) {
326                 client->devnum = fsck_data.devnum;
327                 client->cur = fsck_data.cur;
328                 client->max = fsck_data.max;
329                 client->pass = fsck_data.pass;
330                 client->percent = compute_percent(client->pass, client->cur, client->max);
331                 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
332                           major(client->devnum), minor(client->devnum),
333                           client->cur, client->max, client->pass, client->percent);
334         } else
335                 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
336
337         r = update_global_progress(m);
338         if (r < 0)
339                 log_warning_errno(r, "Couldn't update global progress: %m");
340
341         return 0;
342 }
343
344 static int new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
345         Manager *m = userdata;
346         Client *client = NULL;
347         int new_client_fd, r;
348
349         assert(m);
350
351         /* Initialize and list new clients */
352         new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
353         if (new_client_fd < 0)
354                 return log_error_errno(errno, "Couldn't accept a new connection: %m");
355
356         log_debug("New fsck client connected to fd: %d", new_client_fd);
357
358         client = new0(Client, 1);
359         if (!client)
360                 return log_oom();
361         client->fd = new_client_fd;
362         client->manager = m;
363         LIST_PREPEND(clients, m->clients, client);
364         r = sd_event_add_io(m->event, NULL, client->fd, EPOLLIN, progress_handler, client);
365         if (r < 0) {
366                 remove_client(&(m->clients), client);
367                 return r;
368         }
369         /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
370         if (m->cancel_requested)
371                 request_cancel_client(client);
372
373         return 0;
374 }
375
376 static void manager_free(Manager *m) {
377         Client *current = NULL, *l = NULL;
378         if (!m)
379                 return;
380
381         /* clear last line */
382         if (m->console && m->clear > 0) {
383                 unsigned j;
384
385                 fputc('\r', m->console);
386                 for (j = 0; j < (unsigned) m->clear; j++)
387                         fputc(' ', m->console);
388                 fputc('\r', m->console);
389                 fflush(m->console);
390         }
391
392         safe_close(m->connection_fd);
393         safe_close(m->plymouth_fd);
394         if (m->console)
395                 fclose(m->console);
396
397         LIST_FOREACH_SAFE(clients, current, l, m->clients)
398                 remove_client(&(m->clients), current);
399
400         sd_event_unref(m->event);
401
402         free(m);
403 }
404
405 static int manager_new(Manager **ret, int fd) {
406         _cleanup_manager_free_ Manager *m = NULL;
407         int r;
408
409         assert(ret);
410
411         m = new0(Manager, 1);
412         if (!m)
413                 return -ENOMEM;
414
415         r = sd_event_default(&m->event);
416         if (r < 0)
417                 return r;
418
419         m->connection_fd = fd;
420         if (access("/run/systemd/show-status", F_OK) >= 0) {
421                 m->console = fopen("/dev/console", "we");
422                 if (!m->console)
423                         return log_warning_errno(errno, "Can't connect to /dev/console: %m");
424         }
425         m->percent = 100;
426
427         m->plymouth_fd = -1;
428         *ret = m;
429         m = NULL;
430
431         return 0;
432 }
433
434 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
435         int r, code;
436
437         assert(e);
438
439         for (;;) {
440                 r = sd_event_get_state(e);
441                 if (r < 0)
442                         return r;
443                 if (r == SD_EVENT_FINISHED)
444                         break;
445
446                 r = sd_event_run(e, timeout);
447                 if (r < 0)
448                         return r;
449
450                 /* timeout reached */
451                 if (r == 0) {
452                         sd_event_exit(e, 0);
453                         break;
454                 }
455         }
456
457         r = sd_event_get_exit_code(e, &code);
458         if (r < 0)
459                 return r;
460
461         return code;
462 }
463
464 static void help(void) {
465         printf("%s [OPTIONS...]\n\n"
466                "Capture fsck progress and forward one stream to plymouth\n\n"
467                "  -h --help             Show this help\n"
468                "     --version          Show package version\n",
469                program_invocation_short_name);
470 }
471
472 static int parse_argv(int argc, char *argv[]) {
473
474         enum {
475                 ARG_VERSION = 0x100,
476                 ARG_ROOT,
477         };
478
479         static const struct option options[] = {
480                 { "help",      no_argument,       NULL, 'h'           },
481                 { "version",   no_argument,       NULL, ARG_VERSION   },
482                 {}
483         };
484
485         int c;
486
487         assert(argc >= 0);
488         assert(argv);
489
490         while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
491                 switch (c) {
492
493                 case 'h':
494                         help();
495                         return 0;
496
497                 case ARG_VERSION:
498                         puts(PACKAGE_STRING);
499                         puts(SYSTEMD_FEATURES);
500                         return 0;
501
502                 case '?':
503                         return -EINVAL;
504
505                 default:
506                         assert_not_reached("Unhandled option");
507                 }
508
509         if (optind < argc) {
510                 log_error("Extraneous arguments");
511                 return -EINVAL;
512         }
513
514         return 1;
515 }
516
517 int main(int argc, char *argv[]) {
518         _cleanup_manager_free_ Manager *m = NULL;
519         int fd = -1;
520         int r, n;
521
522         log_set_target(LOG_TARGET_AUTO);
523         log_parse_environment();
524         log_open();
525         init_gettext();
526
527         r = parse_argv(argc, argv);
528         if (r <= 0)
529                 goto finish;
530
531         n = sd_listen_fds(0);
532         if (n > 1) {
533                 log_error("Too many file descriptors received.");
534                 r = -EINVAL;
535                 goto finish;
536         } else if (n == 1)
537                 fd = SD_LISTEN_FDS_START + 0;
538         else {
539                 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
540                 if (fd < 0) {
541                         r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
542                         goto finish;
543                 }
544         }
545
546         r = manager_new(&m, fd);
547         if (r < 0) {
548                 log_error_errno(r, "Failed to allocate manager: %m");
549                 goto finish;
550         }
551
552         r = sd_event_add_io(m->event, NULL, fd, EPOLLIN, new_connection_handler, m);
553         if (r < 0) {
554                 log_error_errno(r, "Can't listen to connection socket: %m");
555                 goto finish;
556         }
557
558         r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
559         if (r < 0) {
560                 log_error_errno(r, "Failed to run event loop: %m");
561                 goto finish;
562         }
563
564         sd_event_get_exit_code(m->event, &r);
565
566 finish:
567         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
568 }