]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/fsck/fsck.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / fsck / fsck.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2010 Lennart Poettering
6 Copyright 2014 Holger Hans Peter Freyther
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <stdbool.h>
25 #include <stdio.h>
26 #include <sys/file.h>
27 #include <sys/prctl.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30
31 #include "sd-bus.h"
32 #include "sd-device.h"
33
34 #include "alloc-util.h"
35 #include "bus-common-errors.h"
36 #include "bus-error.h"
37 #include "bus-util.h"
38 #include "device-util.h"
39 #include "fd-util.h"
40 #include "fs-util.h"
41 #include "parse-util.h"
42 #include "path-util.h"
43 #include "proc-cmdline.h"
44 #include "process-util.h"
45 #include "signal-util.h"
46 #include "socket-util.h"
47 #include "special.h"
48 #include "stdio-util.h"
49 #include "util.h"
50
51 /* exit codes as defined in fsck(8) */
52 enum {
53 FSCK_SUCCESS = 0,
54 FSCK_ERROR_CORRECTED = 1,
55 FSCK_SYSTEM_SHOULD_REBOOT = 2,
56 FSCK_ERRORS_LEFT_UNCORRECTED = 4,
57 FSCK_OPERATIONAL_ERROR = 8,
58 FSCK_USAGE_OR_SYNTAX_ERROR = 16,
59 FSCK_USER_CANCELLED = 32,
60 FSCK_SHARED_LIB_ERROR = 128,
61 };
62
63 static bool arg_skip = false;
64 static bool arg_force = false;
65 static bool arg_show_progress = false;
66 static const char *arg_repair = "-a";
67
68 static void start_target(const char *target, const char *mode) {
69 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
70 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
71 int r;
72
73 assert(target);
74
75 r = bus_connect_system_systemd(&bus);
76 if (r < 0) {
77 log_error_errno(r, "Failed to get D-Bus connection: %m");
78 return;
79 }
80
81 log_info("Running request %s/start/replace", target);
82
83 /* Start these units only if we can replace base.target with it */
84 r = sd_bus_call_method(bus,
85 "org.freedesktop.systemd1",
86 "/org/freedesktop/systemd1",
87 "org.freedesktop.systemd1.Manager",
88 "StartUnitReplace",
89 &error,
90 NULL,
91 "sss", "basic.target", target, mode);
92
93 /* Don't print a warning if we aren't called during startup */
94 if (r < 0 && !sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_JOB))
95 log_error("Failed to start unit: %s", bus_error_message(&error, r));
96 }
97
98 static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
99 int r;
100
101 assert(key);
102
103 if (streq(key, "fsck.mode")) {
104
105 if (proc_cmdline_value_missing(key, value))
106 return 0;
107
108 if (streq(value, "auto"))
109 arg_force = arg_skip = false;
110 else if (streq(value, "force"))
111 arg_force = true;
112 else if (streq(value, "skip"))
113 arg_skip = true;
114 else
115 log_warning("Invalid fsck.mode= parameter '%s'. Ignoring.", value);
116
117 } else if (streq(key, "fsck.repair")) {
118
119 if (proc_cmdline_value_missing(key, value))
120 return 0;
121
122 if (streq(value, "preen"))
123 arg_repair = "-a";
124 else {
125 r = parse_boolean(value);
126 if (r > 0)
127 arg_repair = "-y";
128 else if (r == 0)
129 arg_repair = "-n";
130 else
131 log_warning("Invalid fsck.repair= parameter '%s'. Ignoring.", value);
132 }
133 }
134
135 #if HAVE_SYSV_COMPAT
136 else if (streq(key, "fastboot") && !value) {
137 log_warning("Please pass 'fsck.mode=skip' rather than 'fastboot' on the kernel command line.");
138 arg_skip = true;
139
140 } else if (streq(key, "forcefsck") && !value) {
141 log_warning("Please pass 'fsck.mode=force' rather than 'forcefsck' on the kernel command line.");
142 arg_force = true;
143 }
144 #endif
145
146 return 0;
147 }
148
149 static void test_files(void) {
150
151 #if HAVE_SYSV_COMPAT
152 if (access("/fastboot", F_OK) >= 0) {
153 log_error("Please pass 'fsck.mode=skip' on the kernel command line rather than creating /fastboot on the root file system.");
154 arg_skip = true;
155 }
156
157 if (access("/forcefsck", F_OK) >= 0) {
158 log_error("Please pass 'fsck.mode=force' on the kernel command line rather than creating /forcefsck on the root file system.");
159 arg_force = true;
160 }
161 #endif
162
163 arg_show_progress = access("/run/systemd/show-status", F_OK) >= 0;
164 }
165
166 static double percent(int pass, unsigned long cur, unsigned long max) {
167 /* Values stolen from e2fsck */
168
169 static const int pass_table[] = {
170 0, 70, 90, 92, 95, 100
171 };
172
173 if (pass <= 0)
174 return 0.0;
175
176 if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
177 return 100.0;
178
179 return (double) pass_table[pass-1] +
180 ((double) pass_table[pass] - (double) pass_table[pass-1]) *
181 (double) cur / (double) max;
182 }
183
184 static int process_progress(int fd) {
185 _cleanup_fclose_ FILE *console = NULL, *f = NULL;
186 usec_t last = 0;
187 bool locked = false;
188 int clear = 0, r;
189
190 /* No progress pipe to process? Then we are a NOP. */
191 if (fd < 0)
192 return 0;
193
194 f = fdopen(fd, "re");
195 if (!f) {
196 safe_close(fd);
197 return -errno;
198 }
199
200 console = fopen("/dev/console", "we");
201 if (!console)
202 return -ENOMEM;
203
204 for (;;) {
205 int pass, m;
206 unsigned long cur, max;
207 _cleanup_free_ char *device = NULL;
208 double p;
209 usec_t t;
210
211 if (fscanf(f, "%i %lu %lu %ms", &pass, &cur, &max, &device) != 4) {
212
213 if (ferror(f))
214 r = log_warning_errno(errno, "Failed to read from progress pipe: %m");
215 else if (feof(f))
216 r = 0;
217 else {
218 log_warning("Failed to parse progress pipe data");
219 r = -EBADMSG;
220 }
221 break;
222 }
223
224 /* Only show one progress counter at max */
225 if (!locked) {
226 if (flock(fileno(console), LOCK_EX|LOCK_NB) < 0)
227 continue;
228
229 locked = true;
230 }
231
232 /* Only update once every 50ms */
233 t = now(CLOCK_MONOTONIC);
234 if (last + 50 * USEC_PER_MSEC > t)
235 continue;
236
237 last = t;
238
239 p = percent(pass, cur, max);
240 fprintf(console, "\r%s: fsck %3.1f%% complete...\r%n", device, p, &m);
241 fflush(console);
242
243 if (m > clear)
244 clear = m;
245 }
246
247 if (clear > 0) {
248 unsigned j;
249
250 fputc('\r', console);
251 for (j = 0; j < (unsigned) clear; j++)
252 fputc(' ', console);
253 fputc('\r', console);
254 fflush(console);
255 }
256
257 return r;
258 }
259
260 static int fsck_progress_socket(void) {
261 static const union sockaddr_union sa = {
262 .un.sun_family = AF_UNIX,
263 .un.sun_path = "/run/systemd/fsck.progress",
264 };
265
266 int fd, r;
267
268 fd = socket(AF_UNIX, SOCK_STREAM, 0);
269 if (fd < 0)
270 return log_warning_errno(errno, "socket(): %m");
271
272 if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) {
273 r = log_full_errno(IN_SET(errno, ECONNREFUSED, ENOENT) ? LOG_DEBUG : LOG_WARNING,
274 errno, "Failed to connect to progress socket %s, ignoring: %m", sa.un.sun_path);
275 safe_close(fd);
276 return r;
277 }
278
279 return fd;
280 }
281
282 int main(int argc, char *argv[]) {
283 _cleanup_close_pair_ int progress_pipe[2] = { -1, -1 };
284 _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
285 const char *device, *type;
286 bool root_directory;
287 siginfo_t status;
288 struct stat st;
289 int r;
290 pid_t pid;
291
292 if (argc > 2) {
293 log_error("This program expects one or no arguments.");
294 return EXIT_FAILURE;
295 }
296
297 log_set_target(LOG_TARGET_AUTO);
298 log_parse_environment();
299 log_open();
300
301 umask(0022);
302
303 r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX);
304 if (r < 0)
305 log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
306
307 test_files();
308
309 if (!arg_force && arg_skip) {
310 r = 0;
311 goto finish;
312 }
313
314 if (argc > 1) {
315 device = argv[1];
316
317 if (stat(device, &st) < 0) {
318 r = log_error_errno(errno, "Failed to stat %s: %m", device);
319 goto finish;
320 }
321
322 if (!S_ISBLK(st.st_mode)) {
323 log_error("%s is not a block device.", device);
324 r = -EINVAL;
325 goto finish;
326 }
327
328 r = sd_device_new_from_devnum(&dev, 'b', st.st_rdev);
329 if (r < 0) {
330 log_error_errno(r, "Failed to detect device %s: %m", device);
331 goto finish;
332 }
333
334 root_directory = false;
335 } else {
336 struct timespec times[2];
337
338 /* Find root device */
339
340 if (stat("/", &st) < 0) {
341 r = log_error_errno(errno, "Failed to stat() the root directory: %m");
342 goto finish;
343 }
344
345 /* Virtual root devices don't need an fsck */
346 if (major(st.st_dev) == 0) {
347 log_debug("Root directory is virtual or btrfs, skipping check.");
348 r = 0;
349 goto finish;
350 }
351
352 /* check if we are already writable */
353 times[0] = st.st_atim;
354 times[1] = st.st_mtim;
355
356 if (utimensat(AT_FDCWD, "/", times, 0) == 0) {
357 log_info("Root directory is writable, skipping check.");
358 r = 0;
359 goto finish;
360 }
361
362 r = sd_device_new_from_devnum(&dev, 'b', st.st_dev);
363 if (r < 0) {
364 log_error_errno(r, "Failed to detect root device: %m");
365 goto finish;
366 }
367
368 r = sd_device_get_devname(dev, &device);
369 if (r < 0) {
370 log_error_errno(r, "Failed to detect device node of root directory: %m");
371 goto finish;
372 }
373
374 root_directory = true;
375 }
376
377 r = sd_device_get_property_value(dev, "ID_FS_TYPE", &type);
378 if (r >= 0) {
379 r = fsck_exists(type);
380 if (r < 0)
381 log_warning_errno(r, "Couldn't detect if fsck.%s may be used for %s, proceeding: %m", type, device);
382 else if (r == 0) {
383 log_info("fsck.%s doesn't exist, not checking file system on %s.", type, device);
384 goto finish;
385 }
386 }
387
388 if (arg_show_progress) {
389 if (pipe(progress_pipe) < 0) {
390 r = log_error_errno(errno, "pipe(): %m");
391 goto finish;
392 }
393 }
394
395 pid = fork();
396 if (pid < 0) {
397 r = log_error_errno(errno, "fork(): %m");
398 goto finish;
399 }
400 if (pid == 0) {
401 char dash_c[sizeof("-C")-1 + DECIMAL_STR_MAX(int) + 1];
402 int progress_socket = -1;
403 const char *cmdline[9];
404 int i = 0;
405
406 /* Child */
407
408 (void) reset_all_signal_handlers();
409 (void) reset_signal_mask();
410 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
411
412 /* Close the reading side of the progress pipe */
413 progress_pipe[0] = safe_close(progress_pipe[0]);
414
415 /* Try to connect to a progress management daemon, if there is one */
416 progress_socket = fsck_progress_socket();
417 if (progress_socket >= 0) {
418 /* If this worked we close the progress pipe early, and just use the socket */
419 progress_pipe[1] = safe_close(progress_pipe[1]);
420 xsprintf(dash_c, "-C%i", progress_socket);
421 } else if (progress_pipe[1] >= 0) {
422 /* Otherwise if we have the progress pipe to our own local handle, we use it */
423 xsprintf(dash_c, "-C%i", progress_pipe[1]);
424 } else
425 dash_c[0] = 0;
426
427 cmdline[i++] = "/sbin/fsck";
428 cmdline[i++] = arg_repair;
429 cmdline[i++] = "-T";
430
431 /*
432 * Since util-linux v2.25 fsck uses /run/fsck/<diskname>.lock files.
433 * The previous versions use flock for the device and conflict with
434 * udevd, see https://bugs.freedesktop.org/show_bug.cgi?id=79576#c5
435 */
436 cmdline[i++] = "-l";
437
438 if (!root_directory)
439 cmdline[i++] = "-M";
440
441 if (arg_force)
442 cmdline[i++] = "-f";
443
444 if (!isempty(dash_c))
445 cmdline[i++] = dash_c;
446
447 cmdline[i++] = device;
448 cmdline[i++] = NULL;
449
450 execv(cmdline[0], (char**) cmdline);
451 _exit(FSCK_OPERATIONAL_ERROR);
452 }
453
454 progress_pipe[1] = safe_close(progress_pipe[1]);
455 (void) process_progress(progress_pipe[0]);
456 progress_pipe[0] = -1;
457
458 r = wait_for_terminate(pid, &status);
459 if (r < 0) {
460 log_error_errno(r, "waitid(): %m");
461 goto finish;
462 }
463
464 if (status.si_code != CLD_EXITED || (status.si_status & ~1)) {
465
466 if (IN_SET(status.si_code, CLD_KILLED, CLD_DUMPED))
467 log_error("fsck terminated by signal %s.", signal_to_string(status.si_status));
468 else if (status.si_code == CLD_EXITED)
469 log_error("fsck failed with error code %i.", status.si_status);
470 else
471 log_error("fsck failed due to unknown reason.");
472
473 r = -EINVAL;
474
475 if (status.si_code == CLD_EXITED && (status.si_status & FSCK_SYSTEM_SHOULD_REBOOT) && root_directory)
476 /* System should be rebooted. */
477 start_target(SPECIAL_REBOOT_TARGET, "replace-irreversibly");
478 else if (status.si_code == CLD_EXITED && (status.si_status & (FSCK_SYSTEM_SHOULD_REBOOT | FSCK_ERRORS_LEFT_UNCORRECTED)))
479 /* Some other problem */
480 start_target(SPECIAL_EMERGENCY_TARGET, "replace");
481 else {
482 log_warning("Ignoring error.");
483 r = 0;
484 }
485
486 } else
487 r = 0;
488
489 if (status.si_code == CLD_EXITED && (status.si_status & FSCK_ERROR_CORRECTED))
490 (void) touch("/run/systemd/quotacheck");
491
492 finish:
493 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
494 }