chiark / gitweb /
dc00fc6b309593ca2efa4a73c9c67f3322b6351f
[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 <math.h>
28 #include <stdbool.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <sys/socket.h>
32 #include <sys/types.h>
33 #include <sys/un.h>
34 #include <unistd.h>
35
36 #include "build.h"
37 #include "def.h"
38 #include "event-util.h"
39 #include "fsckd.h"
40 #include "log.h"
41 #include "list.h"
42 #include "macro.h"
43 #include "sd-daemon.h"
44 #include "socket-util.h"
45 #include "util.h"
46
47 #define IDLE_TIME_SECONDS 30
48 #define PLYMOUTH_REQUEST_KEY "K\2\2\3"
49
50 struct Manager;
51
52 typedef struct Client {
53         struct Manager *manager;
54         int fd;
55         dev_t devnum;
56         size_t cur;
57         size_t max;
58         int pass;
59         double percent;
60         size_t buflen;
61         bool cancelled;
62
63         LIST_FIELDS(struct Client, clients);
64 } Client;
65
66 typedef struct Manager {
67         sd_event *event;
68         Client *clients;
69         int clear;
70         int connection_fd;
71         FILE *console;
72         double percent;
73         int numdevices;
74         int plymouth_fd;
75         bool plymouth_cancel_sent;
76         bool cancel_requested;
77 } Manager;
78
79 static int connect_plymouth(Manager *m);
80 static int update_global_progress(Manager *m);
81 static void manager_free(Manager *m);
82 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
83 #define _cleanup_manager_free_ _cleanup_(manager_freep)
84
85 static double compute_percent(int pass, size_t cur, size_t max) {
86         /* Values stolen from e2fsck */
87
88         static const double pass_table[] = {
89                 0, 70, 90, 92, 95, 100
90         };
91
92         if (pass <= 0)
93                 return 0.0;
94
95         if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
96                 return 100.0;
97
98         return pass_table[pass-1] +
99                 (pass_table[pass] - pass_table[pass-1]) *
100                 (double) cur / max;
101 }
102
103 static int request_cancel_client(Client *current) {
104         FsckdMessage cancel_msg;
105         ssize_t n;
106         cancel_msg.cancel = 1;
107
108         n = send(current->fd, &cancel_msg, sizeof(FsckdMessage), 0);
109         if (n < 0 || (size_t) n < sizeof(FsckdMessage))
110                 return log_warning_errno(n, "Cannot send cancel to fsck on (%u, %u): %m",
111                                          major(current->devnum), minor(current->devnum));
112         else
113                 current->cancelled = true;
114         return 0;
115 }
116
117 static void remove_client(Client **first, Client *item) {
118         LIST_REMOVE(clients, *first, item);
119         safe_close(item->fd);
120         free(item);
121 }
122
123 static void on_plymouth_disconnect(Manager *m) {
124         safe_close(m->plymouth_fd);
125         m->plymouth_fd = -1;
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, "Checking in progress on %d disks (%3.1f%% complete)",
226                                                 m->numdevices, m->percent) < 0)
227                         return -ENOMEM;
228                 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
229                         return -ENOMEM;
230
231                 /* write to console */
232                 if (m->console) {
233                         fprintf(m->console, "\r%s\r%n", console_message, &l);
234                         fflush(m->console);
235                 }
236
237                 /* try to connect to plymouth and send message */
238                 r = send_message_plymouth(m, fsck_message);
239                 if (r < 0)
240                         log_debug("Couldn't send message to plymouth");
241
242                 if (l > m->clear)
243                         m->clear = l;
244         }
245         return 0;
246 }
247
248 static int connect_plymouth(Manager *m) {
249         union sockaddr_union sa = PLYMOUTH_SOCKET;
250         int r;
251
252         /* try to connect or reconnect if sending a message */
253         if (m->plymouth_fd <= 0) {
254                 m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
255                 if (m->plymouth_fd < 0) {
256                         return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
257                 }
258                 if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
259                         on_plymouth_disconnect(m);
260                         return log_warning_errno(errno, "Couldn't connect to plymouth: %m");
261                 }
262                 r = sd_event_add_io(m->event, NULL, m->plymouth_fd, EPOLLIN, plymouth_feedback_handler, m);
263                 if (r < 0) {
264                         on_plymouth_disconnect(m);
265                         return log_warning_errno(r, "Can't listen to plymouth socket: %m");
266                 }
267         }
268
269         return 0;
270 }
271
272 static int progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
273         Client *client = userdata;
274         Manager *m = NULL;
275         FsckProgress fsck_data;
276         size_t buflen;
277         int r;
278
279         assert(client);
280         m = client->manager;
281
282         /* check first if we need to cancel this client */
283         if (m->cancel_requested) {
284                 if (!client->cancelled)
285                         request_cancel_client(client);
286         }
287
288         /* ensure we have enough data to read */
289         r = ioctl(fd, FIONREAD, &buflen);
290         if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
291                 if (client->buflen != buflen)
292                         client->buflen = buflen;
293                 /* we got twice the same size from a bad behaving client, kick it off the list */
294                 else {
295                         log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
296                         remove_client(&(m->clients), client);
297                         r = update_global_progress(m);
298                         if (r < 0)
299                                 log_warning_errno(r, "Couldn't update global progress: %m");
300                 }
301                 return 0;
302         }
303
304         /* read actual data */
305         r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
306         if (r == 0) {
307                 log_debug("Fsck client connected to fd %d disconnected", client->fd);
308                 remove_client(&(m->clients), client);
309         } else if (r > 0 && r != sizeof(FsckProgress))
310                 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
311         else if (r > 0 && r == sizeof(FsckProgress)) {
312                 client->devnum = fsck_data.devnum;
313                 client->cur = fsck_data.cur;
314                 client->max = fsck_data.max;
315                 client->pass = fsck_data.pass;
316                 client->percent = compute_percent(client->pass, client->cur, client->max);
317                 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
318                           major(client->devnum), minor(client->devnum),
319                           client->cur, client->max, client->pass, client->percent);
320         } else
321                 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
322
323         r = update_global_progress(m);
324         if (r < 0)
325                 log_warning_errno(r, "Couldn't update global progress: %m");
326
327         return 0;
328 }
329
330 static int new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
331         Manager *m = userdata;
332         Client *client = NULL;
333         int new_client_fd, r;
334
335         assert(m);
336
337         /* Initialize and list new clients */
338         new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
339         if (new_client_fd > 0) {
340                 log_debug("New fsck client connected to fd: %d", new_client_fd);
341                 client = new0(Client, 1);
342                 if (!client)
343                         return log_oom();
344                 client->fd = new_client_fd;
345                 client->manager = m;
346                 LIST_PREPEND(clients, m->clients, client);
347                 r = sd_event_add_io(m->event, NULL, client->fd, EPOLLIN, progress_handler, client);
348                 if (r < 0) {
349                         remove_client(&(m->clients), client);
350                         return r;
351                 }
352                 /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
353                 if (m->cancel_requested)
354                         request_cancel_client(client);
355         } else
356                 return log_error_errno(errno, "Couldn't accept a new connection: %m");
357
358         return 0;
359 }
360
361 static void manager_free(Manager *m) {
362         Client *current = NULL, *l = NULL;
363         if (!m)
364                 return;
365
366         /* clear last line */
367         if (m->console && m->clear > 0) {
368                 unsigned j;
369
370                 fputc('\r', m->console);
371                 for (j = 0; j < (unsigned) m->clear; j++)
372                         fputc(' ', m->console);
373                 fputc('\r', m->console);
374                 fflush(m->console);
375         }
376
377         safe_close(m->connection_fd);
378         safe_close(m->plymouth_fd);
379         if (m->console)
380                 fclose(m->console);
381
382         LIST_FOREACH_SAFE(clients, current, l, m->clients)
383                 remove_client(&(m->clients), current);
384
385         sd_event_unref(m->event);
386
387         free(m);
388 }
389
390 static int manager_new(Manager **ret, int fd) {
391         _cleanup_manager_free_ Manager *m = NULL;
392         int r;
393
394         assert(ret);
395
396         m = new0(Manager, 1);
397         if (!m)
398                 return -ENOMEM;
399
400         r = sd_event_default(&m->event);
401         if (r < 0)
402                 return r;
403
404         m->connection_fd = fd;
405         if (access("/run/systemd/show-status", F_OK) >= 0) {
406                 m->console = fopen("/dev/console", "we");
407                 if (!m->console)
408                         return log_warning_errno(errno, "Can't connect to /dev/console: %m");
409         }
410         m->percent = 100;
411
412         m->plymouth_fd = -1;
413         *ret = m;
414         m = NULL;
415
416         return 0;
417 }
418
419 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
420         int r, code;
421
422         assert(e);
423
424         for (;;) {
425                 r = sd_event_get_state(e);
426                 if (r < 0)
427                         return r;
428                 if (r == SD_EVENT_FINISHED)
429                         break;
430
431                 r = sd_event_run(e, timeout);
432                 if (r < 0)
433                         return r;
434
435                 /* timeout reached */
436                 if (r == 0) {
437                         sd_event_exit(e, 0);
438                         break;
439                 }
440         }
441
442         r = sd_event_get_exit_code(e, &code);
443         if (r < 0)
444                 return r;
445
446         return code;
447 }
448
449 static void help(void) {
450         printf("%s [OPTIONS...]\n\n"
451                "Capture fsck progress and forward one stream to plymouth\n\n"
452                "  -h --help             Show this help\n"
453                "     --version          Show package version\n",
454                program_invocation_short_name);
455 }
456
457 static int parse_argv(int argc, char *argv[]) {
458
459         enum {
460                 ARG_VERSION = 0x100,
461                 ARG_ROOT,
462         };
463
464         static const struct option options[] = {
465                 { "help",      no_argument,       NULL, 'h'           },
466                 { "version",   no_argument,       NULL, ARG_VERSION   },
467                 {}
468         };
469
470         int c;
471
472         assert(argc >= 0);
473         assert(argv);
474
475         while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
476                 switch (c) {
477
478                 case 'h':
479                         help();
480                         return 0;
481
482                 case ARG_VERSION:
483                         puts(PACKAGE_STRING);
484                         puts(SYSTEMD_FEATURES);
485                         return 0;
486
487                 case '?':
488                         return -EINVAL;
489
490                 default:
491                         assert_not_reached("Unhandled option");
492                 }
493
494         if (optind < argc) {
495                 log_error("Extraneous arguments");
496                 return -EINVAL;
497         }
498
499         return 1;
500 }
501
502 int main(int argc, char *argv[]) {
503         _cleanup_manager_free_ Manager *m = NULL;
504         int fd = -1;
505         int r, n;
506
507         log_set_target(LOG_TARGET_AUTO);
508         log_parse_environment();
509         log_open();
510
511         r = parse_argv(argc, argv);
512         if (r <= 0)
513                 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
514
515         n = sd_listen_fds(0);
516         if (n > 1) {
517                 log_error("Too many file descriptors received.");
518                 return EXIT_FAILURE;
519         } else if (n == 1) {
520                 fd = SD_LISTEN_FDS_START + 0;
521         } else {
522                 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
523                 if (fd < 0) {
524                         log_error_errno(r, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
525                         return EXIT_FAILURE;
526                 }
527         }
528
529         r = manager_new(&m, fd);
530         if (r < 0) {
531                 log_error_errno(r, "Failed to allocate manager: %m");
532                 return EXIT_FAILURE;
533         }
534
535         r = sd_event_add_io(m->event, NULL, fd, EPOLLIN, new_connection_handler, m);
536         if (r < 0) {
537                 log_error_errno(r, "Can't listen to connection socket: %m");
538                 return EXIT_FAILURE;
539         }
540
541         r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
542         if (r < 0) {
543                 log_error_errno(r, "Failed to run event loop: %m");
544                 return EXIT_FAILURE;
545         }
546
547         sd_event_get_exit_code(m->event, &r);
548
549         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
550 }