From 856e143d1969ea0f630665873ed22c8bd89fd20d Mon Sep 17 00:00:00 2001
From: Didier Roche <didrocks@ubuntu.com>
Date: Mon, 26 Jan 2015 16:40:52 +0100
Subject: [PATCH 07/13] Support cancellation of fsck in progress

Grab in fsckd plymouth watch key for C or c, and propagate this cancel order
to systemd-fsck which will terminate fsck.

Send a message to signal to user what key we are grabbing for fsck cancel.

Message is: fsckd-cancel-msg:<string>
Where string is a translated string ready to be displayed by the plymouth theme
indicating that c or C can be used to cancel current checks. It can be
overriden (matching only fsckd-cancel-msg prefix) for themes supporting i18n.
---
 src/fsck/fsck.c   | 31 +++++++++++++++++++++++--------
 src/fsckd/fsckd.c | 30 ++++++++++++++++++++++++++++++
 src/fsckd/fsckd.h |  1 +
 3 files changed, 54 insertions(+), 8 deletions(-)

diff --git a/src/fsck/fsck.c b/src/fsck/fsck.c
index 2298cd5..47dd8b2 100644
--- a/src/fsck/fsck.c
+++ b/src/fsck/fsck.c
@@ -46,6 +46,8 @@
 static bool arg_skip = false;
 static bool arg_force = false;
 static const char *arg_repair = "-a";
+static pid_t fsck_pid;
+static bool cancel_requested = false;
 
 static void start_target(const char *target) {
         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
@@ -176,6 +178,9 @@ static int process_progress(int fd) {
         }
 
         while (!feof(f)) {
+                _cleanup_free_ char *progress_message = NULL;
+                /* 19 characters at max for fsckd messages */
+                char fsckd_message[20];
                 int pass;
                 unsigned long cur, max;
                 _cleanup_free_ char *device = NULL;
@@ -203,6 +208,16 @@ static int process_progress(int fd) {
                 if (n < 0 || (size_t) n < l)
                         log_warning_errno(n, "Cannot communicate %s to fsckd: %m", progress_message);
 
+                /* get fsckd requests */
+                n = read(fsckd_fd, fsckd_message, sizeof(fsckd_message)-1);
+                if (n > 0) {
+                        fsckd_message[n] = '\0';
+                        if (!strcmp(fsckd_message, FSCKD_CANCEL_MSG)) {
+                                log_warning("Request to cancel fsck from fsckd");
+                                cancel_requested = true;
+                                kill(fsck_pid, SIGTERM);
+                        }
+                }
         }
 
         return 0;
@@ -211,7 +226,6 @@ static int process_progress(int fd) {
 int main(int argc, char *argv[]) {
         const char *cmdline[9];
         int i = 0, r = EXIT_FAILURE, q;
-        pid_t pid;
         siginfo_t status;
         _cleanup_udev_unref_ struct udev *udev = NULL;
         _cleanup_udev_device_unref_ struct udev_device *udev_device = NULL;
@@ -339,11 +353,11 @@ int main(int argc, char *argv[]) {
         cmdline[i++] = device;
         cmdline[i++] = NULL;
 
-        pid = fork();
-        if (pid < 0) {
+        fsck_pid = fork();
+        if (fsck_pid < 0) {
                 log_error_errno(errno, "fork(): %m");
                 goto finish;
-        } else if (pid == 0) {
+        } else if (fsck_pid == 0) {
                 /* Child */
                 if (progress_pipe[0] >= 0)
                         safe_close(progress_pipe[0]);
@@ -358,7 +372,7 @@ int main(int argc, char *argv[]) {
                 progress_pipe[0] = -1;
         }
 
-        q = wait_for_terminate(pid, &status);
+        q = wait_for_terminate(fsck_pid, &status);
         if (q < 0) {
                 log_error_errno(q, "waitid(): %m");
                 goto finish;
@@ -366,11 +380,11 @@ int main(int argc, char *argv[]) {
 
         if (status.si_code != CLD_EXITED || (status.si_status & ~1)) {
 
-                if (status.si_code == CLD_KILLED || status.si_code == CLD_DUMPED)
+                if ((!cancel_requested && status.si_code == CLD_KILLED) || status.si_code == CLD_DUMPED)
                         log_error("fsck terminated by signal %s.", signal_to_string(status.si_status));
                 else if (status.si_code == CLD_EXITED)
                         log_error("fsck failed with error code %i.", status.si_status);
-                else
+                else if (!cancel_requested)
                         log_error("fsck failed due to unknown reason.");
 
                 if (status.si_code == CLD_EXITED && (status.si_status & 2) && root_directory)
@@ -381,7 +395,8 @@ int main(int argc, char *argv[]) {
                         start_target(SPECIAL_EMERGENCY_TARGET);
                 else {
                         r = EXIT_SUCCESS;
-                        log_warning("Ignoring error.");
+                        if (!cancel_requested)
+                                log_warning("Ignoring error.");
                 }
 
         } else
diff --git a/src/fsckd/fsckd.c b/src/fsckd/fsckd.c
index c418e49..634b8e0 100644
--- a/src/fsckd/fsckd.c
+++ b/src/fsckd/fsckd.c
@@ -49,10 +49,13 @@ typedef struct Clients {
         int fd;
         double percentage;
         char *device;
+        bool cancelled;
 
         LIST_FIELDS(struct Clients, clients);
 } Clients;
 
+static bool cancelled = false;
+
 /* this can eventually be put in fileio.c */
 static int read_full_fd(int fd, char **buf) {
         // message format is /dev/<device>:%\n
@@ -91,13 +94,22 @@ static int read_full_fd(int fd, char **buf) {
         return l;
 }
 
+static void cancel_requested(void) {
+         log_debug("Request to cancel fsck checking received");
+         cancelled = true;
+}
+
 static int treat_requests(int socket_fd) {
         Clients *first = NULL;
         usec_t last_activity = 0;
         int nbdevices = 0, clear = 0;
         double minpercent = 100;
         _cleanup_free_ char *buf = NULL;
+        const char *plymouth_cancel_message;
         _cleanup_fclose_ FILE *console = NULL;
+        bool cancel_message_plymouth_sent = false;
+
+        plymouth_cancel_message = strappenda("fsckd-cancel-msg:", "Press C to cancel all checks in progress");
 
         console = fopen("/dev/console", "we");
         if (!console) {
@@ -126,6 +138,7 @@ static int treat_requests(int socket_fd) {
                         log_debug("new fsck client connected to fd: %d", new_client_fd);
                         current = alloca0(sizeof(Clients));
                         current->fd = new_client_fd;
+                        current->cancelled = false;
                         if (!first)
                                 LIST_INIT(clients, current);
                         else
@@ -179,6 +192,15 @@ static int treat_requests(int socket_fd) {
                                         continue;
                                 }
 
+                                /* send cancel message if cancel key was pressed (and the socket wasn't closed) */
+                                if (cancelled && !current->cancelled) {
+                                        size_t l = strlen(FSCKD_CANCEL_MSG);
+                                        ssize_t n = write(current->fd, FSCKD_CANCEL_MSG, l);
+                                        if (n < 0 || (size_t) n < l)
+                                                log_warning_errno(n, "Cannot send cancel to %s: %m", current->device);
+                                        else
+                                                current->cancelled = true;
+                                }
                         }
 
                         if(remove_previous)
@@ -210,6 +232,10 @@ static int treat_requests(int socket_fd) {
                         }
 
                         /* send to plymouth */
+                        if (!cancel_message_plymouth_sent) {
+                                cancel_message_plymouth_sent = \
+                                        plymouth_watch_key("Cc", plymouth_cancel_message, cancel_requested);
+                        }
                         plymouth_update(fsck_message);
 
                         if (m > clear)
@@ -219,6 +245,10 @@ static int treat_requests(int socket_fd) {
                 /* idle out after IDLE_TIME_MINUTES minutes with no connected device */
                 t = now(CLOCK_MONOTONIC);
                 if (nbdevices == 0) {
+                        if (cancel_message_plymouth_sent) {
+                                plymouth_delete_message();
+                                cancel_message_plymouth_sent = false;
+                        }
                         if (t > last_activity + IDLE_TIME_MINUTES * USEC_PER_MINUTE) {
                             log_debug("No fsck in progress for the last %d minutes, shutting down.", IDLE_TIME_MINUTES);
                             break;
diff --git a/src/fsckd/fsckd.h b/src/fsckd/fsckd.h
index efe1994..1bdb833 100644
--- a/src/fsckd/fsckd.h
+++ b/src/fsckd/fsckd.h
@@ -20,3 +20,4 @@
 ***/
 
 #define FSCKD_SOCKET_PATH "/run/systemd/fsckd"
+#define FSCKD_CANCEL_MSG "fsckd:cancel"
-- 
2.1.4

