X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Ffsckd%2Ffsckd.c;h=1415b53dd487f9302ed55f409392d85aca7c458c;hp=0f647468dd0d3d57c07a263e9a2e157742e38224;hb=57b394b50579feeee90a4cb12243d5785a9176a6;hpb=76bb3afd811ea33fcd20b278fe04ff0a7be203fd diff --git a/src/fsckd/fsckd.c b/src/fsckd/fsckd.c index 0f647468d..1415b53dd 100644 --- a/src/fsckd/fsckd.c +++ b/src/fsckd/fsckd.c @@ -34,19 +34,20 @@ #include #include +#include "sd-daemon.h" #include "build.h" #include "def.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" +#include "fsckd.h" #define IDLE_TIME_SECONDS 30 #define PLYMOUTH_REQUEST_KEY "K\2\2\3" +#define CLIENTS_MAX 128 struct Manager; @@ -54,34 +55,48 @@ typedef struct Client { struct Manager *manager; int fd; dev_t devnum; + size_t cur; size_t max; int pass; + double percent; + size_t buflen; bool cancelled; + sd_event_source *event_source; + LIST_FIELDS(struct Client, clients); } Client; typedef struct Manager { sd_event *event; - Client *clients; + + LIST_HEAD(Client, clients); + unsigned n_clients; + int clear; + int connection_fd; + sd_event_source *connection_event_source; + FILE *console; double percent; int numdevices; + int plymouth_fd; + sd_event_source *plymouth_event_source; bool plymouth_cancel_sent; + bool cancel_requested; } Manager; -static int connect_plymouth(Manager *m); -static int update_global_progress(Manager *m); +static void client_free(Client *c); static void manager_free(Manager *m); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Client*, client_free); 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 */ @@ -101,61 +116,119 @@ static double compute_percent(int pass, size_t cur, size_t max) { (double) cur / max; } -static int request_cancel_client(Client *current) { - FsckdMessage cancel_msg; +static int client_request_cancel(Client *c) { + FsckdMessage cancel_msg = { + .cancel = 1, + }; + ssize_t n; - cancel_msg.cancel = 1; - - n = send(current->fd, &cancel_msg, sizeof(FsckdMessage), 0); - if (n < 0 || (size_t) n < sizeof(FsckdMessage)) - return log_warning_errno(n, "Cannot send cancel to fsck on (%u, %u): %m", - major(current->devnum), minor(current->devnum)); - else - current->cancelled = true; - return 0; + + assert(c); + + if (c->cancelled) + return 0; + + n = send(c->fd, &cancel_msg, sizeof(FsckdMessage), 0); + if (n < 0) + return log_warning_errno(errno, "Cannot send cancel to fsck on (%u:%u): %m", major(c->devnum), minor(c->devnum)); + if ((size_t) n < sizeof(FsckdMessage)) { + log_warning("Short send when sending cancel to fsck on (%u:%u).", major(c->devnum), minor(c->devnum)); + return -EIO; + } + + c->cancelled = true; + return 1; } -static void remove_client(Client **first, Client *item) { - LIST_REMOVE(clients, *first, item); - safe_close(item->fd); - free(item); +static void client_free(Client *c) { + assert(c); + + if (c->manager) { + LIST_REMOVE(clients, c->manager->clients, c); + c->manager->n_clients--; + } + + sd_event_source_unref(c->event_source); + + safe_close(c->fd); + free(c); } -static void on_plymouth_disconnect(Manager *m) { +static void manager_disconnect_plymouth(Manager *m) { + assert(m); + + m->plymouth_event_source = sd_event_source_unref(m->plymouth_event_source); m->plymouth_fd = safe_close(m->plymouth_fd); m->plymouth_cancel_sent = false; } -static int plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { +static int manager_plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { Manager *m = userdata; Client *current; char buffer[6]; - int r; + ssize_t l; assert(m); - r = read(m->plymouth_fd, buffer, sizeof(buffer)); - if (r <= 0) - on_plymouth_disconnect(m); - else { - if (buffer[0] == '\15') - log_error("Message update to plymouth wasn't delivered successfully"); - - /* the only answer support type we requested is a key interruption */ - if (buffer[0] == '\2' && buffer[5] == '\3') { - m->cancel_requested = true; - /* cancel all connected clients */ - LIST_FOREACH(clients, current, m->clients) - request_cancel_client(current); - } + l = read(m->plymouth_fd, buffer, sizeof(buffer)); + if (l < 0) { + log_warning_errno(errno, "Got error while reading from plymouth: %m"); + manager_disconnect_plymouth(m); + return -errno; + } + if (l == 0) { + manager_disconnect_plymouth(m); + return 0; + } + + if (l > 1 && buffer[0] == '\15') + log_error("Message update to plymouth wasn't delivered successfully"); + + /* the only answer support type we requested is a key interruption */ + if (l > 2 && buffer[0] == '\2' && buffer[5] == '\3') { + m->cancel_requested = true; + + /* cancel all connected clients */ + LIST_FOREACH(clients, current, m->clients) + client_request_cancel(current); } return 0; } -static int send_message_plymouth_socket(int plymouth_fd, const char *message, bool update) { +static int manager_connect_plymouth(Manager *m) { + union sockaddr_union sa = PLYMOUTH_SOCKET; + int r; + + /* try to connect or reconnect if sending a message */ + if (m->plymouth_fd >= 0) + return 0; + + m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + if (m->plymouth_fd < 0) + return log_warning_errno(errno, "Connection to plymouth socket failed: %m"); + + if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) { + r = log_warning_errno(errno, "Couldn't connect to plymouth: %m"); + goto fail; + } + + r = sd_event_add_io(m->event, &m->plymouth_event_source, m->plymouth_fd, EPOLLIN, manager_plymouth_feedback_handler, m); + if (r < 0) { + log_warning_errno(r, "Can't listen to plymouth socket: %m"); + goto fail; + } + + return 1; + +fail: + manager_disconnect_plymouth(m); + return r; +} + +static int plymouth_send_message(int plymouth_fd, const char *message, bool update) { _cleanup_free_ char *packet = NULL; - int r, n; + int n; char mode = 'M'; if (update) @@ -163,44 +236,50 @@ static int send_message_plymouth_socket(int plymouth_fd, const char *message, bo if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0) return log_oom(); - r = loop_write(plymouth_fd, packet, n + 1, true); - return r; -} + return loop_write(plymouth_fd, packet, n + 1, true); +} -static int send_message_plymouth(Manager *m, const char *message) { - int r; +static int manager_send_plymouth_message(Manager *m, const char *message) { const char *plymouth_cancel_message = NULL; + int r; - r = connect_plymouth(m); + r = manager_connect_plymouth(m); if (r < 0) return r; if (!m->plymouth_cancel_sent) { - /* indicate to plymouth that we listen to Ctrl+C */ + + /* Indicate to plymouth that we listen to Ctrl+C */ r = loop_write(m->plymouth_fd, PLYMOUTH_REQUEST_KEY, sizeof(PLYMOUTH_REQUEST_KEY), true); if (r < 0) - return log_warning_errno(errno, "Can't send to plymouth cancel key: %m"); + return log_warning_errno(r, "Can't send to plymouth cancel key: %m"); + m->plymouth_cancel_sent = true; + plymouth_cancel_message = strjoina("fsckd-cancel-msg:", _("Press Ctrl+C to cancel all filesystem checks in progress")); - r = send_message_plymouth_socket(m->plymouth_fd, plymouth_cancel_message, false); + + r = plymouth_send_message(m->plymouth_fd, plymouth_cancel_message, false); if (r < 0) log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m"); + } else if (m->numdevices == 0) { + m->plymouth_cancel_sent = false; - r = send_message_plymouth_socket(m->plymouth_fd, "", false); + + r = plymouth_send_message(m->plymouth_fd, "", false); if (r < 0) log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m"); } - r = send_message_plymouth_socket(m->plymouth_fd, message, true); + r = plymouth_send_message(m->plymouth_fd, message, true); if (r < 0) - return log_warning_errno(errno, "Couldn't send \"%s\" to plymouth: %m", message); + return log_warning_errno(r, "Couldn't send \"%s\" to plymouth: %m", message); return 0; } -static int update_global_progress(Manager *m) { +static int manager_update_global_progress(Manager *m) { Client *current = NULL; _cleanup_free_ char *console_message = NULL; _cleanup_free_ char *fsck_message = NULL; @@ -227,6 +306,7 @@ static int update_global_progress(Manager *m) { "Checking in progress on %d disks (%3.1f%% complete)", m->numdevices), m->numdevices, m->percent) < 0) return -ENOMEM; + if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0) return -ENOMEM; @@ -237,7 +317,7 @@ static int update_global_progress(Manager *m) { } /* try to connect to plymouth and send message */ - r = send_message_plymouth(m, fsck_message); + r = manager_send_plymouth_message(m, fsck_message); if (r < 0) log_debug("Couldn't send message to plymouth"); @@ -247,47 +327,20 @@ static int update_global_progress(Manager *m) { return 0; } -static int connect_plymouth(Manager *m) { - union sockaddr_union sa = PLYMOUTH_SOCKET; - int r; - - /* try to connect or reconnect if sending a message */ - if (m->plymouth_fd >= 0) - return 0; - - m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); - if (m->plymouth_fd < 0) - return log_warning_errno(errno, "Connection to plymouth socket failed: %m"); - - if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) { - on_plymouth_disconnect(m); - return log_warning_errno(errno, "Couldn't connect to plymouth: %m"); - } - - r = sd_event_add_io(m->event, NULL, m->plymouth_fd, EPOLLIN, plymouth_feedback_handler, m); - if (r < 0) { - on_plymouth_disconnect(m); - return log_warning_errno(r, "Can't listen to plymouth socket: %m"); - } - - return 0; -} - -static int progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { +static int client_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; + Manager *m; int r; assert(client); + m = client->manager; /* check first if we need to cancel this client */ - if (m->cancel_requested) { - if (!client->cancelled) - request_cancel_client(client); - } + if (m->cancel_requested) + client_request_cancel(client); /* ensure we have enough data to read */ r = ioctl(fd, FIONREAD, &buflen); @@ -297,8 +350,8 @@ static int progress_handler(sd_event_source *s, int fd, uint32_t revents, void * /* we got twice the same size from a bad behaving client, kick it off the list */ else { log_warning("Closing bad behaving fsck client connection at fd %d", client->fd); - remove_client(&(m->clients), client); - r = update_global_progress(m); + client_free(client); + r = manager_update_global_progress(m); if (r < 0) log_warning_errno(r, "Couldn't update global progress: %m"); } @@ -309,7 +362,7 @@ static int progress_handler(sd_event_source *s, int fd, uint32_t revents, void * 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); + client_free(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)) { @@ -324,17 +377,18 @@ static int progress_handler(sd_event_source *s, int fd, uint32_t revents, void * } else log_error_errno(r, "Unknown error while trying to read fsck data: %m"); - r = update_global_progress(m); + r = manager_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) { +static int manager_new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(client_freep) Client *c = NULL; + _cleanup_close_ int new_client_fd = -1; Manager *m = userdata; - Client *client = NULL; - int new_client_fd, r; + int r; assert(m); @@ -343,28 +397,41 @@ static int new_connection_handler(sd_event_source *s, int fd, uint32_t revents, if (new_client_fd < 0) return log_error_errno(errno, "Couldn't accept a new connection: %m"); + if (m->n_clients >= CLIENTS_MAX) { + log_error("Too many clients, refusing connection."); + return 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); + c = new0(Client, 1); + if (!c) { + log_oom(); + return 0; + } + + c->fd = new_client_fd; + new_client_fd = -1; + + r = sd_event_add_io(m->event, &c->event_source, c->fd, EPOLLIN, client_progress_handler, c); if (r < 0) { - remove_client(&(m->clients), client); - return r; + log_oom(); + return 0; } + + LIST_PREPEND(clients, m->clients, c); + m->n_clients++; + c->manager = m; + /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */ if (m->cancel_requested) - request_cancel_client(client); + client_request_cancel(c); + c = NULL; return 0; } static void manager_free(Manager *m) { - Client *current = NULL, *l = NULL; if (!m) return; @@ -379,21 +446,24 @@ static void manager_free(Manager *m) { fflush(m->console); } + sd_event_source_unref(m->connection_event_source); safe_close(m->connection_fd); - safe_close(m->plymouth_fd); + + while (m->clients) + client_free(m->clients); + + manager_disconnect_plymouth(m); + 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; + _cleanup_(manager_freep) Manager *m = NULL; int r; assert(ret); @@ -402,19 +472,24 @@ static int manager_new(Manager **ret, int fd) { if (!m) return -ENOMEM; + m->plymouth_fd = -1; + m->connection_fd = fd; + m->percent = 100; + r = sd_event_default(&m->event); if (r < 0) return r; - m->connection_fd = fd; if (access("/run/systemd/show-status", F_OK) >= 0) { m->console = fopen("/dev/console", "we"); if (!m->console) - return log_warning_errno(errno, "Can't connect to /dev/console: %m"); + return -errno; } - m->percent = 100; - m->plymouth_fd = -1; + r = sd_event_add_io(m->event, &m->connection_event_source, fd, EPOLLIN, manager_new_connection_handler, m); + if (r < 0) + return r; + *ret = m; m = NULL; @@ -505,7 +580,7 @@ static int parse_argv(int argc, char *argv[]) { } int main(int argc, char *argv[]) { - _cleanup_manager_free_ Manager *m = NULL; + _cleanup_(manager_freep) Manager *m = NULL; int fd = -1; int r, n; @@ -516,41 +591,37 @@ int main(int argc, char *argv[]) { r = parse_argv(argc, argv); if (r <= 0) - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + goto finish; n = sd_listen_fds(0); if (n > 1) { log_error("Too many file descriptors received."); - return EXIT_FAILURE; + r = -EINVAL; + goto finish; } 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(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH); - return EXIT_FAILURE; + r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH); + goto finish; } } 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; + goto finish; } 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; + goto finish; } sd_event_get_exit_code(m->event, &r); +finish: return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; }