2 This file is part of systemd.
4 Copyright 2010 Lennart Poettering
5 Copyright 2014 Holger Hans Peter Freyther
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
26 #include <sys/prctl.h>
31 #include "sd-device.h"
33 #include "alloc-util.h"
34 #include "bus-common-errors.h"
35 #include "bus-error.h"
37 #include "device-util.h"
40 #include "parse-util.h"
41 #include "path-util.h"
42 #include "proc-cmdline.h"
43 #include "process-util.h"
44 #include "signal-util.h"
45 #include "socket-util.h"
47 #include "stdio-util.h"
50 /* exit codes as defined in fsck(8) */
53 FSCK_ERROR_CORRECTED
= 1,
54 FSCK_SYSTEM_SHOULD_REBOOT
= 2,
55 FSCK_ERRORS_LEFT_UNCORRECTED
= 4,
56 FSCK_OPERATIONAL_ERROR
= 8,
57 FSCK_USAGE_OR_SYNTAX_ERROR
= 16,
58 FSCK_USER_CANCELLED
= 32,
59 FSCK_SHARED_LIB_ERROR
= 128,
62 static bool arg_skip
= false;
63 static bool arg_force
= false;
64 static bool arg_show_progress
= false;
65 static const char *arg_repair
= "-a";
67 static void start_target(const char *target
, const char *mode
) {
68 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
69 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
74 r
= bus_connect_system_systemd(&bus
);
76 log_error_errno(r
, "Failed to get D-Bus connection: %m");
80 log_info("Running request %s/start/replace", target
);
82 /* Start these units only if we can replace base.target with it */
83 r
= sd_bus_call_method(bus
,
84 "org.freedesktop.systemd1",
85 "/org/freedesktop/systemd1",
86 "org.freedesktop.systemd1.Manager",
90 "sss", "basic.target", target
, mode
);
92 /* Don't print a warning if we aren't called during startup */
93 if (r
< 0 && !sd_bus_error_has_name(&error
, BUS_ERROR_NO_SUCH_JOB
))
94 log_error("Failed to start unit: %s", bus_error_message(&error
, r
));
97 static int parse_proc_cmdline_item(const char *key
, const char *value
, void *data
) {
102 if (streq(key
, "fsck.mode")) {
104 if (proc_cmdline_value_missing(key
, value
))
107 if (streq(value
, "auto"))
108 arg_force
= arg_skip
= false;
109 else if (streq(value
, "force"))
111 else if (streq(value
, "skip"))
114 log_warning("Invalid fsck.mode= parameter '%s'. Ignoring.", value
);
116 } else if (streq(key
, "fsck.repair")) {
118 if (proc_cmdline_value_missing(key
, value
))
121 if (streq(value
, "preen"))
124 r
= parse_boolean(value
);
130 log_warning("Invalid fsck.repair= parameter '%s'. Ignoring.", value
);
134 #ifdef HAVE_SYSV_COMPAT
135 else if (streq(key
, "fastboot") && !value
) {
136 log_warning("Please pass 'fsck.mode=skip' rather than 'fastboot' on the kernel command line.");
139 } else if (streq(key
, "forcefsck") && !value
) {
140 log_warning("Please pass 'fsck.mode=force' rather than 'forcefsck' on the kernel command line.");
148 static void test_files(void) {
150 #ifdef HAVE_SYSV_COMPAT
151 if (access("/fastboot", F_OK
) >= 0) {
152 log_error("Please pass 'fsck.mode=skip' on the kernel command line rather than creating /fastboot on the root file system.");
156 if (access("/forcefsck", F_OK
) >= 0) {
157 log_error("Please pass 'fsck.mode=force' on the kernel command line rather than creating /forcefsck on the root file system.");
162 arg_show_progress
= access("/run/systemd/show-status", F_OK
) >= 0;
165 static double percent(int pass
, unsigned long cur
, unsigned long max
) {
166 /* Values stolen from e2fsck */
168 static const int pass_table
[] = {
169 0, 70, 90, 92, 95, 100
175 if ((unsigned) pass
>= ELEMENTSOF(pass_table
) || max
== 0)
178 return (double) pass_table
[pass
-1] +
179 ((double) pass_table
[pass
] - (double) pass_table
[pass
-1]) *
180 (double) cur
/ (double) max
;
183 static int process_progress(int fd
) {
184 _cleanup_fclose_
FILE *console
= NULL
, *f
= NULL
;
189 /* No progress pipe to process? Then we are a NOP. */
193 f
= fdopen(fd
, "re");
199 console
= fopen("/dev/console", "we");
205 unsigned long cur
, max
;
206 _cleanup_free_
char *device
= NULL
;
210 if (fscanf(f
, "%i %lu %lu %ms", &pass
, &cur
, &max
, &device
) != 4) {
213 r
= log_warning_errno(errno
, "Failed to read from progress pipe: %m");
217 log_warning("Failed to parse progress pipe data");
223 /* Only show one progress counter at max */
225 if (flock(fileno(console
), LOCK_EX
|LOCK_NB
) < 0)
231 /* Only update once every 50ms */
232 t
= now(CLOCK_MONOTONIC
);
233 if (last
+ 50 * USEC_PER_MSEC
> t
)
238 p
= percent(pass
, cur
, max
);
239 fprintf(console
, "\r%s: fsck %3.1f%% complete...\r%n", device
, p
, &m
);
249 fputc('\r', console
);
250 for (j
= 0; j
< (unsigned) clear
; j
++)
252 fputc('\r', console
);
259 static int fsck_progress_socket(void) {
260 static const union sockaddr_union sa
= {
261 .un
.sun_family
= AF_UNIX
,
262 .un
.sun_path
= "/run/systemd/fsck.progress",
267 fd
= socket(AF_UNIX
, SOCK_STREAM
, 0);
269 return log_warning_errno(errno
, "socket(): %m");
271 if (connect(fd
, &sa
.sa
, SOCKADDR_UN_LEN(sa
.un
)) < 0) {
272 r
= log_full_errno(IN_SET(errno
, ECONNREFUSED
, ENOENT
) ? LOG_DEBUG
: LOG_WARNING
,
273 errno
, "Failed to connect to progress socket %s, ignoring: %m", sa
.un
.sun_path
);
281 int main(int argc
, char *argv
[]) {
282 _cleanup_close_pair_
int progress_pipe
[2] = { -1, -1 };
283 _cleanup_(sd_device_unrefp
) sd_device
*dev
= NULL
;
284 const char *device
, *type
;
292 log_error("This program expects one or no arguments.");
296 log_set_target(LOG_TARGET_AUTO
);
297 log_parse_environment();
302 r
= proc_cmdline_parse(parse_proc_cmdline_item
, NULL
, PROC_CMDLINE_STRIP_RD_PREFIX
);
304 log_warning_errno(r
, "Failed to parse kernel command line, ignoring: %m");
308 if (!arg_force
&& arg_skip
) {
316 if (stat(device
, &st
) < 0) {
317 r
= log_error_errno(errno
, "Failed to stat %s: %m", device
);
321 if (!S_ISBLK(st
.st_mode
)) {
322 log_error("%s is not a block device.", device
);
327 r
= sd_device_new_from_devnum(&dev
, 'b', st
.st_rdev
);
329 log_error_errno(r
, "Failed to detect device %s: %m", device
);
333 root_directory
= false;
335 struct timespec times
[2];
337 /* Find root device */
339 if (stat("/", &st
) < 0) {
340 r
= log_error_errno(errno
, "Failed to stat() the root directory: %m");
344 /* Virtual root devices don't need an fsck */
345 if (major(st
.st_dev
) == 0) {
346 log_debug("Root directory is virtual or btrfs, skipping check.");
351 /* check if we are already writable */
352 times
[0] = st
.st_atim
;
353 times
[1] = st
.st_mtim
;
355 if (utimensat(AT_FDCWD
, "/", times
, 0) == 0) {
356 log_info("Root directory is writable, skipping check.");
361 r
= sd_device_new_from_devnum(&dev
, 'b', st
.st_dev
);
363 log_error_errno(r
, "Failed to detect root device: %m");
367 r
= sd_device_get_devname(dev
, &device
);
369 log_error_errno(r
, "Failed to detect device node of root directory: %m");
373 root_directory
= true;
376 r
= sd_device_get_property_value(dev
, "ID_FS_TYPE", &type
);
378 r
= fsck_exists(type
);
380 log_warning_errno(r
, "Couldn't detect if fsck.%s may be used for %s, proceeding: %m", type
, device
);
382 log_info("fsck.%s doesn't exist, not checking file system on %s.", type
, device
);
387 if (arg_show_progress
) {
388 if (pipe(progress_pipe
) < 0) {
389 r
= log_error_errno(errno
, "pipe(): %m");
396 r
= log_error_errno(errno
, "fork(): %m");
400 char dash_c
[sizeof("-C")-1 + DECIMAL_STR_MAX(int) + 1];
401 int progress_socket
= -1;
402 const char *cmdline
[9];
407 (void) reset_all_signal_handlers();
408 (void) reset_signal_mask();
409 assert_se(prctl(PR_SET_PDEATHSIG
, SIGTERM
) == 0);
411 /* Close the reading side of the progress pipe */
412 progress_pipe
[0] = safe_close(progress_pipe
[0]);
414 /* Try to connect to a progress management daemon, if there is one */
415 progress_socket
= fsck_progress_socket();
416 if (progress_socket
>= 0) {
417 /* If this worked we close the progress pipe early, and just use the socket */
418 progress_pipe
[1] = safe_close(progress_pipe
[1]);
419 xsprintf(dash_c
, "-C%i", progress_socket
);
420 } else if (progress_pipe
[1] >= 0) {
421 /* Otherwise if we have the progress pipe to our own local handle, we use it */
422 xsprintf(dash_c
, "-C%i", progress_pipe
[1]);
426 cmdline
[i
++] = "/sbin/fsck";
427 cmdline
[i
++] = arg_repair
;
431 * Since util-linux v2.25 fsck uses /run/fsck/<diskname>.lock files.
432 * The previous versions use flock for the device and conflict with
433 * udevd, see https://bugs.freedesktop.org/show_bug.cgi?id=79576#c5
443 if (!isempty(dash_c
))
444 cmdline
[i
++] = dash_c
;
446 cmdline
[i
++] = device
;
449 execv(cmdline
[0], (char**) cmdline
);
450 _exit(FSCK_OPERATIONAL_ERROR
);
453 progress_pipe
[1] = safe_close(progress_pipe
[1]);
454 (void) process_progress(progress_pipe
[0]);
455 progress_pipe
[0] = -1;
457 r
= wait_for_terminate(pid
, &status
);
459 log_error_errno(r
, "waitid(): %m");
463 if (status
.si_code
!= CLD_EXITED
|| (status
.si_status
& ~1)) {
465 if (IN_SET(status
.si_code
, CLD_KILLED
, CLD_DUMPED
))
466 log_error("fsck terminated by signal %s.", signal_to_string(status
.si_status
));
467 else if (status
.si_code
== CLD_EXITED
)
468 log_error("fsck failed with error code %i.", status
.si_status
);
470 log_error("fsck failed due to unknown reason.");
474 if (status
.si_code
== CLD_EXITED
&& (status
.si_status
& FSCK_SYSTEM_SHOULD_REBOOT
) && root_directory
)
475 /* System should be rebooted. */
476 start_target(SPECIAL_REBOOT_TARGET
, "replace-irreversibly");
477 else if (status
.si_code
== CLD_EXITED
&& (status
.si_status
& (FSCK_SYSTEM_SHOULD_REBOOT
| FSCK_ERRORS_LEFT_UNCORRECTED
)))
478 /* Some other problem */
479 start_target(SPECIAL_EMERGENCY_TARGET
, "replace");
481 log_warning("Ignoring error.");
488 if (status
.si_code
== CLD_EXITED
&& (status
.si_status
& FSCK_ERROR_CORRECTED
))
489 (void) touch("/run/systemd/quotacheck");
492 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;