On Thu, Feb 05, 2015 at 06:06:50PM +0100, Didier Roche wrote: > Hey, > > Posting the new set of patches for the fsck/plymouth integration, > rebased from all the comments and the systemd event loop system. > > This version talks the raw plymouth protocol directly, supporting > only what is needed (sending updates, messages, requesting key > listening, get key events). It's using Control+C as the cancellation > key. If plymouth disconnects and then later respawn, the connection > will be taken back. Same for any new fsck connection incoming after > a cancellation (they will get cancelled right away). The update > progress message is always reflecting the current connection state > (they will only disappear once they are actually cleaned). > > As always, I'm opened to any comments. > Cheers, > Didier
> From ac8d6f10768a5bcba0b7932547419637983637b2 Mon Sep 17 00:00:00 2001 > From: Didier Roche <didro...@ubuntu.com> > Date: Wed, 4 Feb 2015 16:42:47 +0100 > Subject: [PATCH 1/9] fsckd daemon for inter-fsckd communication > > Add systemd-fsckd multiplexer which accepts multiple systemd-fsck > instances to connect to it and sends progress report. systemd-fsckd then > computes and writes to /dev/console the number of devices currently being > checked and the minimum fsck progress. This will be used for interactive > progress report and cancelling in plymouth. > > systemd-fsckd stops on idle when no systemd-fsck is connected. > > Make the necessary changes to systemd-fsck to connect to the systemd-fsckd > socket. > --- > .gitignore | 1 + > Makefile.am | 13 ++ > src/fsck/fsck.c | 88 +++++------- > src/fsckd/Makefile | 1 + > src/fsckd/fsckd.c | 403 > +++++++++++++++++++++++++++++++++++++++++++++++++++++ > src/fsckd/fsckd.h | 34 +++++ > 6 files changed, 486 insertions(+), 54 deletions(-) > create mode 120000 src/fsckd/Makefile > create mode 100644 src/fsckd/fsckd.c > create mode 100644 src/fsckd/fsckd.h > > diff --git a/.gitignore b/.gitignore > index ab6d9d1..9400e75 100644 > --- a/.gitignore > +++ b/.gitignore > @@ -74,6 +74,7 @@ > /systemd-evcat > /systemd-firstboot > /systemd-fsck > +/systemd-fsckd > /systemd-fstab-generator > /systemd-getty-generator > /systemd-gnome-ask-password-agent > diff --git a/Makefile.am b/Makefile.am > index c463f23..e0e8bc6 100644 > --- a/Makefile.am > +++ b/Makefile.am > @@ -389,6 +389,7 @@ rootlibexec_PROGRAMS = \ > systemd-remount-fs \ > systemd-reply-password \ > systemd-fsck \ > + systemd-fsckd \ > systemd-machine-id-commit \ > systemd-ac-power \ > systemd-sysctl \ > @@ -2355,6 +2356,18 @@ systemd_fsck_LDADD = \ > libsystemd-shared.la > > # > ------------------------------------------------------------------------------ > +systemd_fsckd_SOURCES = \ > + src/fsckd/fsckd.c \ > + $(NULL) > + > +systemd_fsckd_LDADD = \ > + libsystemd-internal.la \ > + libsystemd-label.la \ > + libsystemd-shared.la \ > + libudev-internal.la \ > + $(NULL) > + > +# > ------------------------------------------------------------------------------ > systemd_machine_id_commit_SOURCES = \ > src/machine-id-commit/machine-id-commit.c \ > src/core/machine-id-setup.c \ > diff --git a/src/fsck/fsck.c b/src/fsck/fsck.c > index 20b7940..9d9739b 100644 > --- a/src/fsck/fsck.c > +++ b/src/fsck/fsck.c > @@ -27,6 +27,7 @@ > #include <unistd.h> > #include <fcntl.h> > #include <sys/file.h> > +#include <sys/stat.h> > > #include "sd-bus.h" > #include "libudev.h" > @@ -39,6 +40,8 @@ > #include "fileio.h" > #include "udev-util.h" > #include "path-util.h" > +#include "socket-util.h" > +#include "fsckd/fsckd.h" > > static bool arg_skip = false; > static bool arg_force = false; > @@ -132,58 +135,42 @@ static void test_files(void) { > arg_show_progress = true; > } > > -static double percent(int pass, unsigned long cur, unsigned long max) { > - /* Values stolen from e2fsck */ > - > - static const int pass_table[] = { > - 0, 70, 90, 92, 95, 100 > +static int process_progress(int fd, dev_t device_num) { > + _cleanup_fclose_ FILE *f = NULL; > + usec_t last = 0; > + _cleanup_close_ int fsckd_fd = -1; > + static const union sockaddr_union sa = { > + .un.sun_family = AF_UNIX, > + .un.sun_path = FSCKD_SOCKET_PATH, > }; > > - if (pass <= 0) > - return 0.0; > - > - if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0) > - return 100.0; > - > - return (double) pass_table[pass-1] + > - ((double) pass_table[pass] - (double) pass_table[pass-1]) * > - (double) cur / (double) max; > -} > - > -static int process_progress(int fd) { > - _cleanup_fclose_ FILE *console = NULL, *f = NULL; > - usec_t last = 0; > - bool locked = false; > - int clear = 0; > + fsckd_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); > + if (fsckd_fd < 0) { > + log_warning_errno(errno, "Cannot open fsckd socket, we won't > report fsck progress: %m"); > + return -errno; > + } > + if (connect(fsckd_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) > + strlen(sa.un.sun_path)) < 0) { > + log_warning_errno(errno, "Cannot connect to fsckd socket, we > won't report fsck progress: %m"); > + return -errno; Use 'return log_warning_errno(...)'. > + } > > f = fdopen(fd, "r"); > if (!f) { > - safe_close(fd); > + log_warning_errno(errno, "Cannot connect to fsck, we won't > report fsck progress: %m"); > return -errno; > } > > - console = fopen("/dev/console", "we"); > - if (!console) > - return -ENOMEM; > - > while (!feof(f)) { > - int pass, m; > - unsigned long cur, max; > - _cleanup_free_ char *device = NULL; > - double p; > + int pass; > + size_t cur, max; > + ssize_t n; > usec_t t; > + _cleanup_free_ char *device = NULL; > + FsckProgress progress; > > if (fscanf(f, "%i %lu %lu %ms", &pass, &cur, &max, &device) > != 4) > break; > > - /* Only show one progress counter at max */ > - if (!locked) { > - if (flock(fileno(console), LOCK_EX|LOCK_NB) < 0) > - continue; > - > - locked = true; > - } > - > /* Only update once every 50ms */ > t = now(CLOCK_MONOTONIC); > if (last + 50 * USEC_PER_MSEC > t) > @@ -191,22 +178,15 @@ static int process_progress(int fd) { > > last = t; > > - p = percent(pass, cur, max); > - fprintf(console, "\r%s: fsck %3.1f%% complete...\r%n", > device, p, &m); > - fflush(console); > - > - if (m > clear) > - clear = m; > - } > - > - if (clear > 0) { > - unsigned j; > + /* send progress to fsckd */ > + progress.devnum = device_num; > + progress.cur = cur; > + progress.max = max; > + progress.pass = pass; > > - fputc('\r', console); > - for (j = 0; j < (unsigned) clear; j++) > - fputc(' ', console); > - fputc('\r', console); > - fflush(console); > + n = send(fsckd_fd, &progress, sizeof(FsckProgress), 0); > + if (n < 0 || (size_t) n < sizeof(FsckProgress)) > + log_warning_errno(errno, "Cannot communicate fsck > progress to fsckd: %m"); > } > > return 0; > @@ -359,7 +339,7 @@ int main(int argc, char *argv[]) { > progress_pipe[1] = safe_close(progress_pipe[1]); > > if (progress_pipe[0] >= 0) { > - process_progress(progress_pipe[0]); > + process_progress(progress_pipe[0], st.st_rdev); > progress_pipe[0] = -1; > } > > diff --git a/src/fsckd/Makefile b/src/fsckd/Makefile > new file mode 120000 > index 0000000..d0b0e8e > --- /dev/null > +++ b/src/fsckd/Makefile > @@ -0,0 +1 @@ > +../Makefile > \ No newline at end of file > diff --git a/src/fsckd/fsckd.c b/src/fsckd/fsckd.c > new file mode 100644 > index 0000000..4a16f3d > --- /dev/null > +++ b/src/fsckd/fsckd.c > @@ -0,0 +1,403 @@ > +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ > + > +/*** > + This file is part of systemd. > + > + Copyright 2015 Canonical > + > + Author: > + Didier Roche <didro...@ubuntu.com> > + > + systemd is free software; you can redistribute it and/or modify it > + under the terms of the GNU Lesser General Public License as published by > + the Free Software Foundation; either version 2.1 of the License, or > + (at your option) any later version. > + > + systemd is distributed in the hope that it will be useful, but > + WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + Lesser General Public License for more details. > + > + You should have received a copy of the GNU Lesser General Public License > + along with systemd; If not, see <http://www.gnu.org/licenses/>. > +***/ > + > +#include <getopt.h> > +#include <errno.h> > +#include <math.h> > +#include <stdbool.h> > +#include <stdlib.h> > +#include <stdio.h> > +#include <sys/socket.h> > +#include <sys/types.h> > +#include <sys/un.h> > +#include <unistd.h> > + > +#include "build.h" > +#include "event-util.h" > +#include "fsckd.h" > +#include "log.h" > +#include "list.h" > +#include "macro.h" > +#include "sd-daemon.h" > +#include "socket-util.h" > +#include "util.h" > + > +#define IDLE_TIME_SECONDS 30 > + > +struct Manager; > + > +typedef struct Client { > + struct Manager *manager; > + int fd; > + dev_t devnum; > + size_t cur; > + size_t max; > + int pass; > + double percent; > + size_t buflen; > + > + LIST_FIELDS(struct Client, clients); > +} Client; > + > +typedef struct Manager { > + sd_event *event; > + Client *clients; > + int clear; > + int connection_fd; > + FILE *console; > + double percent; > + int numdevices; > +} Manager; > + > +static void manager_free(Manager *m); > +DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); > +#define _cleanup_manager_free_ _cleanup_(manager_freep) > + > +static double compute_percent(int pass, size_t cur, size_t max) { > + /* Values stolen from e2fsck */ > + > + static const double pass_table[] = { > + 0, 70, 90, 92, 95, 100 > + }; > + > + if (pass <= 0) > + return 0.0; > + > + if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0) > + return 100.0; > + > + return pass_table[pass-1] + > + (pass_table[pass] - pass_table[pass-1]) * > + (double) cur / max; > +} > + > + > +static void remove_client(Client **first, Client *item) { > + LIST_REMOVE(clients, *first, item); > + safe_close(item->fd); > + free(item); > +} > + > +static int update_global_progress(Manager *m) { > + Client *current = NULL; > + _cleanup_free_ char *console_message = NULL; > + int current_numdevices = 0, l = 0; > + double current_percent = 100; > + > + /* get the overall percentage */ > + LIST_FOREACH(clients, current, m->clients) { > + current_numdevices++; > + > + /* right now, we only keep the minimum % of all fsckd > processes. We could in the future trying to be > + linear, but max changes and corresponds to the pass. We > have all the informations into fsckd > + already if we can treat that in a smarter way. */ > + current_percent = MIN(current_percent, current->percent); > + } > + > + /* update if there is anything user-visible to update */ > + if (fabs(current_percent - m->percent) > 0.001 || current_numdevices > != m->numdevices) { > + m->numdevices = current_numdevices; > + m->percent = current_percent; > + > + if (asprintf(&console_message, "Checking in progress on %d > disks (%3.1f%% complete)", > + m->numdevices, m->percent) < > 0) > + return -ENOMEM; > + > + /* write to console */ > + if (m->console) { > + fprintf(m->console, "\r%s\r%n", console_message, &l); > + fflush(m->console); > + } > + > + if (l > m->clear) > + m->clear = l; > + } > + return 0; > +} > + > +static int progress_handler(sd_event_source *s, int fd, uint32_t revents, > void *userdata) { > + Client *client = userdata; > + Manager *m = NULL; > + FsckProgress fsck_data; > + size_t buflen; > + int r; > + > + assert(client); > + m = client->manager; > + > + /* ensure we have enough data to read */ > + r = ioctl(fd, FIONREAD, &buflen); > + if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) > { > + if (client->buflen != buflen) > + client->buflen = buflen; > + /* we got twice the same size from a bad behaving client, > kick it off the list */ > + else { Shouldn't this be logged? Seems like this should be detected and fixed if it happens. > + remove_client(&(m->clients), client); > + r = update_global_progress(m); > + if (r < 0) > + log_warning_errno(r, "Couldn't update global > progress: %m"); > + } > + return 0; > + } > + > + /* read actual data */ > + r = recv(fd, &fsck_data, sizeof(FsckProgress), 0); > + if (r == 0) { > + log_debug("Fsck client connected to fd %d disconnected", > client->fd); > + remove_client(&(m->clients), client); > + } else if (r > 0 && r != sizeof(FsckProgress)) > + log_warning("Unexpected data structure sent to fsckd socket > from fd: %d. Ignoring", client->fd); > + else if (r > 0 && r == sizeof(FsckProgress)) { > + client->devnum = fsck_data.devnum; > + client->cur = fsck_data.cur; > + client->max = fsck_data.max; > + client->pass = fsck_data.pass; > + client->percent = compute_percent(client->pass, client->cur, > client->max); > + log_debug("Getting progress for %u:%u (%lu, %lu, %d) : > %3.1f%%", > + major(client->devnum), minor(client->devnum), > + client->cur, client->max, client->pass, > client->percent); > + } else > + log_error_errno(r, "Unknown error while trying to read fsck > data: %m"); > + > + r = update_global_progress(m); > + if (r < 0) > + log_warning_errno(r, "Couldn't update global progress: %m"); > + > + return 0; > +} > + > +static int new_connection_handler(sd_event_source *s, int fd, uint32_t > revents, void *userdata) { > + Manager *m = userdata; > + Client *client = NULL; > + int new_client_fd, r; > + > + assert(m); > + > + /* Initialize and list new clients */ > + new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC); > + if (new_client_fd > 0) { > + log_debug("New fsck client connected to fd: %d", > new_client_fd); > + client = new0(Client, 1); > + if (!client) > + return log_oom(); > + client->fd = new_client_fd; > + client->manager = m; > + LIST_PREPEND(clients, m->clients, client); > + r = sd_event_add_io(m->event, NULL, client->fd, EPOLLIN, > progress_handler, client); > + if (r < 0) { > + remove_client(&(m->clients), client); > + return r; > + } > + } else > + return log_error_errno(errno, "Couldn't accept a new > connection: %m"); > + > + return 0; > +} > + > +static void manager_free(Manager *m) { > + Client *current = NULL, *l = NULL; > + if (!m) > + return; > + > + /* clear last line */ > + if (m->console && m->clear > 0) { > + unsigned j; > + > + fputc('\r', m->console); > + for (j = 0; j < (unsigned) m->clear; j++) > + fputc(' ', m->console); > + fputc('\r', m->console); > + fflush(m->console); > + } > + > + safe_close(m->connection_fd); > + if (m->console) > + fclose(m->console); > + > + LIST_FOREACH_SAFE(clients, current, l, m->clients) > + remove_client(&(m->clients), current); > + > + sd_event_unref(m->event); > + > + free(m); > +} > + > +static int manager_new(Manager **ret, int fd) { > + _cleanup_manager_free_ Manager *m = NULL; > + int r; > + > + assert(ret); > + > + m = new0(Manager, 1); > + if (!m) > + return -ENOMEM; > + > + r = sd_event_default(&m->event); > + if (r < 0) > + return r; > + > + m->connection_fd = fd; > + m->console = fopen("/dev/console", "we"); > + if (!m->console) > + return log_warning_errno(errno, "Can't connect to > /dev/console: %m"); > + m->percent = 100; > + > + *ret = m; > + m = NULL; > + > + return 0; > +} > + > +static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) { > + int r, code; > + > + assert(e); > + > + for (;;) { > + r = sd_event_get_state(e); > + if (r < 0) > + return r; > + if (r == SD_EVENT_FINISHED) > + break; > + > + r = sd_event_run(e, timeout); > + if (r < 0) > + return r; > + > + /* timeout reached */ > + if (r == 0) { > + sd_event_exit(e, 0); > + break; > + } > + } > + > + r = sd_event_get_exit_code(e, &code); > + if (r < 0) > + return r; > + > + return code; > +} > + > +static void help(void) { > + printf("%s [OPTIONS...]\n\n" > + "Capture fsck progress and forward one stream to plymouth\n\n" > + " -h --help Show this help\n" > + " --version Show package version\n", > + program_invocation_short_name); > +} > + > +static int parse_argv(int argc, char *argv[]) { > + > + enum { > + ARG_VERSION = 0x100, > + ARG_ROOT, > + }; > + > + static const struct option options[] = { > + { "help", no_argument, NULL, 'h' }, > + { "version", no_argument, NULL, ARG_VERSION }, > + {} > + }; > + > + int c; > + > + assert(argc >= 0); > + assert(argv); > + > + while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0) > + switch (c) { > + > + case 'h': > + help(); > + return 0; > + > + case ARG_VERSION: > + puts(PACKAGE_STRING); > + puts(SYSTEMD_FEATURES); > + return 0; > + > + case '?': > + return -EINVAL; > + > + default: > + assert_not_reached("Unhandled option"); > + } > + > + if (optind < argc) { > + log_error("Extraneous arguments"); > + return -EINVAL; > + } > + > + return 1; > +} > + > +int main(int argc, char *argv[]) { > + _cleanup_manager_free_ Manager *m = NULL; > + int fd = -1; > + int r, n; > + > + log_set_target(LOG_TARGET_AUTO); > + log_parse_environment(); > + log_open(); > + > + r = parse_argv(argc, argv); > + if (r <= 0) > + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; > + > + n = sd_listen_fds(0); > + if (n > 1) { > + log_error("Too many file descriptors received."); > + return EXIT_FAILURE; > + } else if (n == 1) { > + fd = SD_LISTEN_FDS_START + 0; > + } else { > + fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, > SOCK_STREAM | SOCK_CLOEXEC); > + if (fd < 0) { > + log_error_errno(r, "Couldn't create listening socket > fd on %s: %m", FSCKD_SOCKET_PATH); > + return EXIT_FAILURE; > + } > + } > + > + r = manager_new(&m, fd); > + if (r < 0) { > + log_error_errno(r, "Failed to allocate manager: %m"); > + return EXIT_FAILURE; > + } > + > + r = sd_event_add_io(m->event, NULL, fd, EPOLLIN, > new_connection_handler, m); > + if (r < 0) { > + log_error_errno(r, "Can't listen to connection socket: %m"); > + return EXIT_FAILURE; > + } > + > + r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * > USEC_PER_SEC); > + if (r < 0) { > + log_error_errno(r, "Failed to run event loop: %m"); > + return EXIT_FAILURE; > + } > + > + sd_event_get_exit_code(m->event, &r); > + > + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; > +} > diff --git a/src/fsckd/fsckd.h b/src/fsckd/fsckd.h > new file mode 100644 > index 0000000..6fe37a7 > --- /dev/null > +++ b/src/fsckd/fsckd.h > @@ -0,0 +1,34 @@ > +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ > + > +/*** > + This file is part of systemd. > + > + Copyright 2015 Canonical > + > + Author: > + Didier Roche <didro...@ubuntu.com> > + > + systemd is free software; you can redistribute it and/or modify it > + under the terms of the GNU Lesser General Public License as published by > + the Free Software Foundation; either version 2.1 of the License, or > + (at your option) any later version. > + > + systemd is distributed in the hope that it will be useful, but > + WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + Lesser General Public License for more details. > + > + You should have received a copy of the GNU Lesser General Public License > + along with systemd; If not, see <http://www.gnu.org/licenses/>. > +***/ > + > +#define FSCKD_SOCKET_PATH "/run/systemd/fsckd" > + > +#include "libudev.h" > + > +typedef struct FsckProgress { > + dev_t devnum; > + size_t cur; > + size_t max; > + int pass; > +} FsckProgress; > -- > 2.1.4 > Zbyszek _______________________________________________ systemd-devel mailing list systemd-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/systemd-devel