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