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