]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/fsck/fsck.c
build-sys: use #if Y instead of #ifdef Y everywhere
[thirdparty/systemd.git] / src / fsck / fsck.c
index 7eaf902e75b37533c5f201db006e8ec34c7635ca..cd39161f10f0a99f868a60d8a10eb970e2e699b9 100644 (file)
@@ -1,5 +1,3 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
 /***
   This file is part of systemd.
 
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
-#include <stdio.h>
-#include <stdbool.h>
 #include <errno.h>
-#include <unistd.h>
 #include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
 #include <sys/file.h>
+#include <sys/prctl.h>
 #include <sys/stat.h>
+#include <unistd.h>
 
 #include "sd-bus.h"
 #include "sd-device.h"
 
-#include "util.h"
-#include "special.h"
-#include "bus-util.h"
-#include "bus-error.h"
+#include "alloc-util.h"
 #include "bus-common-errors.h"
+#include "bus-error.h"
+#include "bus-util.h"
 #include "device-util.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "parse-util.h"
 #include "path-util.h"
+#include "proc-cmdline.h"
+#include "process-util.h"
+#include "signal-util.h"
 #include "socket-util.h"
-#include "fsckd/fsckd.h"
+#include "special.h"
+#include "stdio-util.h"
+#include "util.h"
+
+/* exit codes as defined in fsck(8) */
+enum {
+        FSCK_SUCCESS = 0,
+        FSCK_ERROR_CORRECTED = 1,
+        FSCK_SYSTEM_SHOULD_REBOOT = 2,
+        FSCK_ERRORS_LEFT_UNCORRECTED = 4,
+        FSCK_OPERATIONAL_ERROR = 8,
+        FSCK_USAGE_OR_SYNTAX_ERROR = 16,
+        FSCK_USER_CANCELLED = 32,
+        FSCK_SHARED_LIB_ERROR = 128,
+};
 
 static bool arg_skip = false;
 static bool arg_force = false;
+static bool arg_show_progress = false;
 static const char *arg_repair = "-a";
 
-static void start_target(const char *target) {
-        _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_bus_close_unref_ sd_bus *bus = NULL;
+static void start_target(const char *target, const char *mode) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         int r;
 
         assert(target);
 
-        r = bus_open_system_systemd(&bus);
+        r = bus_connect_system_systemd(&bus);
         if (r < 0) {
                 log_error_errno(r, "Failed to get D-Bus connection: %m");
                 return;
@@ -68,16 +87,22 @@ static void start_target(const char *target) {
                                "StartUnitReplace",
                                &error,
                                NULL,
-                               "sss", "basic.target", target, "replace");
+                               "sss", "basic.target", target, mode);
 
         /* Don't print a warning if we aren't called during startup */
         if (r < 0 && !sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_JOB))
-                log_error("Failed to start unit: %s", bus_error_message(&error, -r));
+                log_error("Failed to start unit: %s", bus_error_message(&error, r));
 }
 
-static int parse_proc_cmdline_item(const char *key, const char *value) {
+static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
+        int r;
+
+        assert(key);
 
-        if (streq(key, "fsck.mode") && value) {
+        if (streq(key, "fsck.mode")) {
+
+                if (proc_cmdline_value_missing(key, value))
+                        return 0;
 
                 if (streq(value, "auto"))
                         arg_force = arg_skip = false;
@@ -88,19 +113,25 @@ static int parse_proc_cmdline_item(const char *key, const char *value) {
                 else
                         log_warning("Invalid fsck.mode= parameter '%s'. Ignoring.", value);
 
-        } else if (streq(key, "fsck.repair") && value) {
+        } else if (streq(key, "fsck.repair")) {
+
+                if (proc_cmdline_value_missing(key, value))
+                        return 0;
 
                 if (streq(value, "preen"))
                         arg_repair = "-a";
-                else if (streq(value, "yes"))
-                        arg_repair = "-y";
-                else if (streq(value, "no"))
-                        arg_repair = "-n";
-                else
-                        log_warning("Invalid fsck.repair= parameter '%s'. Ignoring.", value);
+                else {
+                        r = parse_boolean(value);
+                        if (r > 0)
+                                arg_repair = "-y";
+                        else if (r == 0)
+                                arg_repair = "-n";
+                        else
+                                log_warning("Invalid fsck.repair= parameter '%s'. Ignoring.", value);
+                }
         }
 
-#ifdef HAVE_SYSV_COMPAT
+#if HAVE_SYSV_COMPAT
         else if (streq(key, "fastboot") && !value) {
                 log_warning("Please pass 'fsck.mode=skip' rather than 'fastboot' on the kernel command line.");
                 arg_skip = true;
@@ -116,7 +147,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value) {
 
 static void test_files(void) {
 
-#ifdef HAVE_SYSV_COMPAT
+#if HAVE_SYSV_COMPAT
         if (access("/fastboot", F_OK) >= 0) {
                 log_error("Please pass 'fsck.mode=skip' on the kernel command line rather than creating /fastboot on the root file system.");
                 arg_skip = true;
@@ -128,39 +159,74 @@ static void test_files(void) {
         }
 #endif
 
+        arg_show_progress = access("/run/systemd/show-status", F_OK) >= 0;
 }
 
-static int process_progress(int fd, pid_t fsck_pid, 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,
+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
         };
 
-        fsckd_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
-        if (fsckd_fd < 0)
-                return log_warning_errno(errno, "Cannot open fsckd socket, we won't report fsck progress: %m");
-        if (connect(fsckd_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
-                return log_warning_errno(errno, "Cannot connect to fsckd socket, we won't report fsck progress: %m");
-
-        f = fdopen(fd, "r");
-        if (!f)
-                return log_warning_errno(errno, "Cannot connect to fsck, we won't report fsck progress: %m");
-
-        while (!feof(f)) {
-                int pass;
-                size_t buflen;
-                size_t cur, max;
-                ssize_t r;
-                usec_t t;
+        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, r;
+
+        /* No progress pipe to process? Then we are a NOP. */
+        if (fd < 0)
+                return 0;
+
+        f = fdopen(fd, "re");
+        if (!f) {
+                safe_close(fd);
+                return -errno;
+        }
+
+        console = fopen("/dev/console", "we");
+        if (!console)
+                return -ENOMEM;
+
+        for (;;) {
+                int pass, m;
+                unsigned long cur, max;
                 _cleanup_free_ char *device = NULL;
-                FsckProgress progress;
-                FsckdMessage fsckd_message;
+                double p;
+                usec_t t;
 
-                if (fscanf(f, "%i %zu %zu %ms", &pass, &cur, &max, &device) != 4)
+                if (fscanf(f, "%i %lu %lu %ms", &pass, &cur, &max, &device) != 4) {
+
+                        if (ferror(f))
+                                r = log_warning_errno(errno, "Failed to read from progress pipe: %m");
+                        else if (feof(f))
+                                r = 0;
+                        else {
+                                log_warning("Failed to parse progress pipe data");
+                                r = -EBADMSG;
+                        }
                         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);
@@ -169,42 +235,58 @@ static int process_progress(int fd, pid_t fsck_pid, dev_t device_num) {
 
                 last = t;
 
-                /* send progress to fsckd */
-                progress.devnum = device_num;
-                progress.cur = cur;
-                progress.max = max;
-                progress.pass = pass;
-
-                r = send(fsckd_fd, &progress, sizeof(FsckProgress), 0);
-                if (r < 0 || (size_t) r < sizeof(FsckProgress))
-                        log_warning_errno(errno, "Cannot communicate fsck progress to fsckd: %m");
-
-                /* get fsckd requests, only read when we have coherent size data */
-                r = ioctl(fsckd_fd, FIONREAD, &buflen);
-                if (r == 0 && (size_t) buflen >= sizeof(FsckdMessage)) {
-                        r = recv(fsckd_fd, &fsckd_message, sizeof(FsckdMessage), 0);
-                        if (r > 0 && fsckd_message.cancel == 1) {
-                                log_info("Request to cancel fsck from fsckd");
-                                kill(fsck_pid, SIGTERM);
-                        }
-                }
+                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;
         }
 
-        return 0;
+        if (clear > 0) {
+                unsigned j;
+
+                fputc('\r', console);
+                for (j = 0; j < (unsigned) clear; j++)
+                        fputc(' ', console);
+                fputc('\r', console);
+                fflush(console);
+        }
+
+        return r;
+}
+
+static int fsck_progress_socket(void) {
+        static const union sockaddr_union sa = {
+                .un.sun_family = AF_UNIX,
+                .un.sun_path = "/run/systemd/fsck.progress",
+        };
+
+        int fd, r;
+
+        fd = socket(AF_UNIX, SOCK_STREAM, 0);
+        if (fd < 0)
+                return log_warning_errno(errno, "socket(): %m");
+
+        if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) {
+                r = log_full_errno(IN_SET(errno, ECONNREFUSED, ENOENT) ? LOG_DEBUG : LOG_WARNING,
+                                   errno, "Failed to connect to progress socket %s, ignoring: %m", sa.un.sun_path);
+                safe_close(fd);
+                return r;
+        }
+
+        return fd;
 }
 
 int main(int argc, char *argv[]) {
-        const char *cmdline[9];
-        int i = 0, r = EXIT_FAILURE, q;
-        pid_t pid;
-        int progress_rc;
-        siginfo_t status;
-        _cleanup_device_unref_ sd_device *dev = NULL;
+        _cleanup_close_pair_ int progress_pipe[2] = { -1, -1 };
+        _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
         const char *device, *type;
         bool root_directory;
-        _cleanup_close_pair_ int progress_pipe[2] = { -1, -1 };
-        char dash_c[sizeof("-C")-1 + DECIMAL_STR_MAX(int) + 1];
+        siginfo_t status;
         struct stat st;
+        int r;
+        pid_t pid;
 
         if (argc > 2) {
                 log_error("This program expects one or no arguments.");
@@ -217,9 +299,9 @@ int main(int argc, char *argv[]) {
 
         umask(0022);
 
-        q = parse_proc_cmdline(parse_proc_cmdline_item);
-        if (q < 0)
-                log_warning_errno(q, "Failed to parse kernel command line, ignoring: %m");
+        r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX);
+        if (r < 0)
+                log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
 
         test_files();
 
@@ -230,10 +312,15 @@ int main(int argc, char *argv[]) {
 
         if (argc > 1) {
                 device = argv[1];
-                root_directory = false;
 
                 if (stat(device, &st) < 0) {
-                        r = log_error_errno(errno, "Failed to stat '%s': %m", device);
+                        r = log_error_errno(errno, "Failed to stat %s: %m", device);
+                        goto finish;
+                }
+
+                if (!S_ISBLK(st.st_mode)) {
+                        log_error("%s is not a block device.", device);
+                        r = -EINVAL;
                         goto finish;
                 }
 
@@ -242,6 +329,8 @@ int main(int argc, char *argv[]) {
                         log_error_errno(r, "Failed to detect device %s: %m", device);
                         goto finish;
                 }
+
+                root_directory = false;
         } else {
                 struct timespec times[2];
 
@@ -254,7 +343,7 @@ int main(int argc, char *argv[]) {
 
                 /* Virtual root devices don't need an fsck */
                 if (major(st.st_dev) == 0) {
-                        log_debug("Root directory is virtual, skipping check.");
+                        log_debug("Root directory is virtual or btrfs, skipping check.");
                         r = 0;
                         goto finish;
                 }
@@ -262,6 +351,7 @@ int main(int argc, char *argv[]) {
                 /* check if we are already writable */
                 times[0] = st.st_atim;
                 times[1] = st.st_mtim;
+
                 if (utimensat(AT_FDCWD, "/", times, 0) == 0) {
                         log_info("Root directory is writable, skipping check.");
                         r = 0;
@@ -277,7 +367,6 @@ int main(int argc, char *argv[]) {
                 r = sd_device_get_devname(dev, &device);
                 if (r < 0) {
                         log_error_errno(r, "Failed to detect device node of root directory: %m");
-                        r = -ENXIO;
                         goto finish;
                 }
 
@@ -287,56 +376,82 @@ int main(int argc, char *argv[]) {
         r = sd_device_get_property_value(dev, "ID_FS_TYPE", &type);
         if (r >= 0) {
                 r = fsck_exists(type);
-                if (r == -ENOENT) {
-                        log_info("fsck.%s doesn't exist, not checking file system on %s", type, device);
-                        r = 0;
+                if (r < 0)
+                        log_warning_errno(r, "Couldn't detect if fsck.%s may be used for %s, proceeding: %m", type, device);
+                else if (r == 0) {
+                        log_info("fsck.%s doesn't exist, not checking file system on %s.", type, device);
                         goto finish;
-                } else if (r < 0)
-                        log_warning_errno(r, "fsck.%s cannot be used for %s: %m", type, device);
+                }
         }
 
-        if (pipe(progress_pipe) < 0) {
-                r = log_error_errno(errno, "pipe(): %m");
-                goto finish;
+        if (arg_show_progress) {
+                if (pipe(progress_pipe) < 0) {
+                        r = log_error_errno(errno, "pipe(): %m");
+                        goto finish;
+                }
         }
 
-        cmdline[i++] = "/sbin/fsck";
-        cmdline[i++] =  arg_repair;
-        cmdline[i++] = "-T";
-
-        /*
-         * Since util-linux v2.25 fsck uses /run/fsck/<diskname>.lock files.
-         * The previous versions use flock for the device and conflict with
-         * udevd, see https://bugs.freedesktop.org/show_bug.cgi?id=79576#c5
-         */
-        cmdline[i++] = "-l";
-
-        if (!root_directory)
-                cmdline[i++] = "-M";
-
-        if (arg_force)
-                cmdline[i++] = "-f";
-
-        xsprintf(dash_c, "-C%i", progress_pipe[1]);
-        cmdline[i++] = dash_c;
-
-        cmdline[i++] = device;
-        cmdline[i++] = NULL;
-
         pid = fork();
         if (pid < 0) {
                 r = log_error_errno(errno, "fork(): %m");
                 goto finish;
-        } else if (pid == 0) {
+        }
+        if (pid == 0) {
+                char dash_c[sizeof("-C")-1 + DECIMAL_STR_MAX(int) + 1];
+                int progress_socket = -1;
+                const char *cmdline[9];
+                int i = 0;
+
                 /* Child */
+
+                (void) reset_all_signal_handlers();
+                (void) reset_signal_mask();
+                assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
+
+                /* Close the reading side of the progress pipe */
                 progress_pipe[0] = safe_close(progress_pipe[0]);
+
+                /* Try to connect to a progress management daemon, if there is one */
+                progress_socket = fsck_progress_socket();
+                if (progress_socket >= 0) {
+                        /* If this worked we close the progress pipe early, and just use the socket */
+                        progress_pipe[1] = safe_close(progress_pipe[1]);
+                        xsprintf(dash_c, "-C%i", progress_socket);
+                } else if (progress_pipe[1] >= 0) {
+                        /* Otherwise if we have the progress pipe to our own local handle, we use it */
+                        xsprintf(dash_c, "-C%i", progress_pipe[1]);
+                } else
+                        dash_c[0] = 0;
+
+                cmdline[i++] = "/sbin/fsck";
+                cmdline[i++] =  arg_repair;
+                cmdline[i++] = "-T";
+
+                /*
+                 * Since util-linux v2.25 fsck uses /run/fsck/<diskname>.lock files.
+                 * The previous versions use flock for the device and conflict with
+                 * udevd, see https://bugs.freedesktop.org/show_bug.cgi?id=79576#c5
+                 */
+                cmdline[i++] = "-l";
+
+                if (!root_directory)
+                        cmdline[i++] = "-M";
+
+                if (arg_force)
+                        cmdline[i++] = "-f";
+
+                if (!isempty(dash_c))
+                        cmdline[i++] = dash_c;
+
+                cmdline[i++] = device;
+                cmdline[i++] = NULL;
+
                 execv(cmdline[0], (char**) cmdline);
-                _exit(8); /* Operational error */
+                _exit(FSCK_OPERATIONAL_ERROR);
         }
 
         progress_pipe[1] = safe_close(progress_pipe[1]);
-
-        progress_rc = process_progress(progress_pipe[0], pid, st.st_rdev);
+        (void) process_progress(progress_pipe[0]);
         progress_pipe[0] = -1;
 
         r = wait_for_terminate(pid, &status);
@@ -345,35 +460,33 @@ int main(int argc, char *argv[]) {
                 goto finish;
         }
 
-        if (status.si_code != CLD_EXITED || (status.si_status & ~1) || progress_rc != 0) {
+        if (status.si_code != CLD_EXITED || (status.si_status & ~1)) {
 
-                /* cancel will kill fsck (but process_progress returns 0) */
-                if ((progress_rc != 0 && status.si_code == CLD_KILLED) || status.si_code == CLD_DUMPED)
+                if (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 if (progress_rc != 0)
+                else
                         log_error("fsck failed due to unknown reason.");
 
                 r = -EINVAL;
 
-                if (status.si_code == CLD_EXITED && (status.si_status & 2) && root_directory)
+                if (status.si_code == CLD_EXITED && (status.si_status & FSCK_SYSTEM_SHOULD_REBOOT) && root_directory)
                         /* System should be rebooted. */
-                        start_target(SPECIAL_REBOOT_TARGET);
-                else if (status.si_code == CLD_EXITED && (status.si_status & 6))
+                        start_target(SPECIAL_REBOOT_TARGET, "replace-irreversibly");
+                else if (status.si_code == CLD_EXITED && (status.si_status & (FSCK_SYSTEM_SHOULD_REBOOT | FSCK_ERRORS_LEFT_UNCORRECTED)))
                         /* Some other problem */
-                        start_target(SPECIAL_EMERGENCY_TARGET);
+                        start_target(SPECIAL_EMERGENCY_TARGET, "replace");
                 else {
+                        log_warning("Ignoring error.");
                         r = 0;
-                        if (progress_rc != 0)
-                                log_warning("Ignoring error.");
                 }
 
         } else
                 r = 0;
 
-        if (status.si_code == CLD_EXITED && (status.si_status & 1))
-                touch("/run/systemd/quotacheck");
+        if (status.si_code == CLD_EXITED && (status.si_status & FSCK_ERROR_CORRECTED))
+                (void) touch("/run/systemd/quotacheck");
 
 finish:
         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;