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