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