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