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