chiark / gitweb /
fsck: Search for fsck.type in PATH
[elogind.git] / src / fsck / fsck.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
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 <stdio.h>
23 #include <stdbool.h>
24 #include <string.h>
25 #include <errno.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include <sys/file.h>
29
30 #include "sd-bus.h"
31 #include "libudev.h"
32
33 #include "util.h"
34 #include "special.h"
35 #include "bus-util.h"
36 #include "bus-error.h"
37 #include "bus-errors.h"
38 #include "fileio.h"
39 #include "udev-util.h"
40 #include "path-util.h"
41
42 static bool arg_skip = false;
43 static bool arg_force = false;
44 static bool arg_show_progress = false;
45
46 static void start_target(const char *target) {
47         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
48         _cleanup_bus_unref_ sd_bus *bus = NULL;
49         int r;
50
51         assert(target);
52
53         r = bus_open_system_systemd(&bus);
54         if (r < 0) {
55                 log_error("Failed to get D-Bus connection: %s", strerror(-r));
56                 return;
57         }
58
59         log_info("Running request %s/start/replace", target);
60
61         /* Start these units only if we can replace base.target with it */
62         r = sd_bus_call_method(bus,
63                                "org.freedesktop.systemd1",
64                                "/org/freedesktop/systemd1",
65                                "org.freedesktop.systemd1.Manager",
66                                "StartUnitReplace",
67                                &error,
68                                NULL,
69                                "sss", "basic.target", target, "replace");
70
71         /* Don't print a warning if we aren't called during startup */
72         if (r < 0 && !sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_JOB))
73                 log_error("Failed to start unit: %s", bus_error_message(&error, -r));
74 }
75
76 static int parse_proc_cmdline_item(const char *key, const char *value) {
77
78         if (streq(key, "fsck.mode") && value) {
79
80                 if (streq(value, "auto"))
81                         arg_force = arg_skip = false;
82                 else if (streq(value, "force"))
83                         arg_force = true;
84                 else if (streq(value, "skip"))
85                         arg_skip = true;
86                 else
87                         log_warning("Invalid fsck.mode= parameter. Ignoring.");
88         } else if (startswith(key, "fsck."))
89                 log_warning("Invalid fsck parameter. Ignoring.");
90 #ifdef HAVE_SYSV_COMPAT
91         else if (streq(key, "fastboot") && !value) {
92                 log_warning("Please pass 'fsck.mode=skip' rather than 'fastboot' on the kernel command line.");
93                 arg_skip = true;
94         } else if (streq(key, "forcefsck") && !value) {
95                 log_warning("Please pass 'fsck.mode=force' rather than 'forcefsck' on the kernel command line.");
96                 arg_force = true;
97         }
98 #endif
99
100         return 0;
101 }
102
103 static void test_files(void) {
104 #ifdef HAVE_SYSV_COMPAT
105         if (access("/fastboot", F_OK) >= 0) {
106                 log_error("Please pass 'fsck.mode=skip' on the kernel command line rather than creating /fastboot on the root file system.");
107                 arg_skip = true;
108         }
109
110         if (access("/forcefsck", F_OK) >= 0) {
111                 log_error("Please pass 'fsck.mode=force' on the kernel command line rather than creating /forcefsck on the root file system.");
112                 arg_force = true;
113         }
114 #endif
115
116         if (access("/run/systemd/show-status", F_OK) >= 0 || plymouth_running())
117                 arg_show_progress = true;
118 }
119
120 static double percent(int pass, unsigned long cur, unsigned long max) {
121         /* Values stolen from e2fsck */
122
123         static const int pass_table[] = {
124                 0, 70, 90, 92, 95, 100
125         };
126
127         if (pass <= 0)
128                 return 0.0;
129
130         if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
131                 return 100.0;
132
133         return (double) pass_table[pass-1] +
134                 ((double) pass_table[pass] - (double) pass_table[pass-1]) *
135                 (double) cur / (double) max;
136 }
137
138 static int process_progress(int fd) {
139         _cleanup_fclose_ FILE *console = NULL, *f = NULL;
140         usec_t last = 0;
141         bool locked = false;
142         int clear = 0;
143
144         f = fdopen(fd, "r");
145         if (!f) {
146                 safe_close(fd);
147                 return -errno;
148         }
149
150         console = fopen("/dev/console", "we");
151         if (!console)
152                 return -ENOMEM;
153
154         while (!feof(f)) {
155                 int pass, m;
156                 unsigned long cur, max;
157                 _cleanup_free_ char *device = NULL;
158                 double p;
159                 usec_t t;
160
161                 if (fscanf(f, "%i %lu %lu %ms", &pass, &cur, &max, &device) != 4)
162                         break;
163
164                 /* Only show one progress counter at max */
165                 if (!locked) {
166                         if (flock(fileno(console), LOCK_EX|LOCK_NB) < 0)
167                                 continue;
168
169                         locked = true;
170                 }
171
172                 /* Only update once every 50ms */
173                 t = now(CLOCK_MONOTONIC);
174                 if (last + 50 * USEC_PER_MSEC > t)
175                         continue;
176
177                 last = t;
178
179                 p = percent(pass, cur, max);
180                 fprintf(console, "\r%s: fsck %3.1f%% complete...\r%n", device, p, &m);
181                 fflush(console);
182
183                 if (m > clear)
184                         clear = m;
185         }
186
187         if (clear > 0) {
188                 unsigned j;
189
190                 fputc('\r', console);
191                 for (j = 0; j < (unsigned) clear; j++)
192                         fputc(' ', console);
193                 fputc('\r', console);
194                 fflush(console);
195         }
196
197         return 0;
198 }
199
200 int main(int argc, char *argv[]) {
201         const char *cmdline[9];
202         int i = 0, r = EXIT_FAILURE, q;
203         pid_t pid;
204         siginfo_t status;
205         _cleanup_udev_unref_ struct udev *udev = NULL;
206         _cleanup_udev_device_unref_ struct udev_device *udev_device = NULL;
207         const char *device, *type;
208         bool root_directory;
209         int progress_pipe[2] = { -1, -1 };
210         char dash_c[2+10+1];
211         struct stat st;
212
213         if (argc > 2) {
214                 log_error("This program expects one or no arguments.");
215                 return EXIT_FAILURE;
216         }
217
218         log_set_target(LOG_TARGET_AUTO);
219         log_parse_environment();
220         log_open();
221
222         umask(0022);
223
224         parse_proc_cmdline(parse_proc_cmdline_item);
225         test_files();
226
227         if (!arg_force && arg_skip)
228                 return 0;
229
230         udev = udev_new();
231         if (!udev) {
232                 log_oom();
233                 return EXIT_FAILURE;
234         }
235
236         if (argc > 1) {
237                 device = argv[1];
238                 root_directory = false;
239
240                 if (stat(device, &st) < 0) {
241                         log_error("Failed to stat '%s': %m", device);
242                         return EXIT_FAILURE;
243                 }
244
245                 udev_device = udev_device_new_from_devnum(udev, 'b', st.st_rdev);
246                 if (!udev_device) {
247                         log_error("Failed to detect device %s", device);
248                         return EXIT_FAILURE;
249                 }
250         } else {
251                 struct timespec times[2];
252
253                 /* Find root device */
254
255                 if (stat("/", &st) < 0) {
256                         log_error("Failed to stat() the root directory: %m");
257                         return EXIT_FAILURE;
258                 }
259
260                 /* Virtual root devices don't need an fsck */
261                 if (major(st.st_dev) == 0)
262                         return EXIT_SUCCESS;
263
264                 /* check if we are already writable */
265                 times[0] = st.st_atim;
266                 times[1] = st.st_mtim;
267                 if (utimensat(AT_FDCWD, "/", times, 0) == 0) {
268                         log_info("Root directory is writable, skipping check.");
269                         return EXIT_SUCCESS;
270                 }
271
272                 udev_device = udev_device_new_from_devnum(udev, 'b', st.st_dev);
273                 if (!udev_device) {
274                         log_error("Failed to detect root device.");
275                         return EXIT_FAILURE;
276                 }
277
278                 device = udev_device_get_devnode(udev_device);
279                 if (!device) {
280                         log_error("Failed to detect device node of root directory.");
281                         return EXIT_FAILURE;
282                 }
283
284                 root_directory = true;
285         }
286
287         type = udev_device_get_property_value(udev_device, "ID_FS_TYPE");
288         if (type) {
289                 r = fsck_exists(type);
290                 if (r < 0) {
291                         if (r == -ENOENT) {
292                                 log_info("fsck.%s doesn't exist, not checking file system.", type);
293                                 return EXIT_SUCCESS;
294                         } else
295                                 log_warning("fsck.%s cannot be used: %s", type, strerror(-r));
296                 }
297         }
298
299         if (arg_show_progress)
300                 if (pipe(progress_pipe) < 0) {
301                         log_error("pipe(): %m");
302                         return EXIT_FAILURE;
303                 }
304
305         cmdline[i++] = "/sbin/fsck";
306         cmdline[i++] = "-a";
307         cmdline[i++] = "-T";
308         cmdline[i++] = "-l";
309
310         if (!root_directory)
311                 cmdline[i++] = "-M";
312
313         if (arg_force)
314                 cmdline[i++] = "-f";
315
316         if (progress_pipe[1] >= 0) {
317                 snprintf(dash_c, sizeof(dash_c), "-C%i", progress_pipe[1]);
318                 char_array_0(dash_c);
319                 cmdline[i++] = dash_c;
320         }
321
322         cmdline[i++] = device;
323         cmdline[i++] = NULL;
324
325         pid = fork();
326         if (pid < 0) {
327                 log_error("fork(): %m");
328                 goto finish;
329         } else if (pid == 0) {
330                 /* Child */
331                 if (progress_pipe[0] >= 0)
332                         safe_close(progress_pipe[0]);
333                 execv(cmdline[0], (char**) cmdline);
334                 _exit(8); /* Operational error */
335         }
336
337         progress_pipe[1] = safe_close(progress_pipe[1]);
338
339         if (progress_pipe[0] >= 0) {
340                 process_progress(progress_pipe[0]);
341                 progress_pipe[0] = -1;
342         }
343
344         q = wait_for_terminate(pid, &status);
345         if (q < 0) {
346                 log_error("waitid(): %s", strerror(-q));
347                 goto finish;
348         }
349
350         if (status.si_code != CLD_EXITED || (status.si_status & ~1)) {
351
352                 if (status.si_code == CLD_KILLED || status.si_code == CLD_DUMPED)
353                         log_error("fsck terminated by signal %s.", signal_to_string(status.si_status));
354                 else if (status.si_code == CLD_EXITED)
355                         log_error("fsck failed with error code %i.", status.si_status);
356                 else
357                         log_error("fsck failed due to unknown reason.");
358
359                 if (status.si_code == CLD_EXITED && (status.si_status & 2) && root_directory)
360                         /* System should be rebooted. */
361                         start_target(SPECIAL_REBOOT_TARGET);
362                 else if (status.si_code == CLD_EXITED && (status.si_status & 6))
363                         /* Some other problem */
364                         start_target(SPECIAL_EMERGENCY_TARGET);
365                 else {
366                         r = EXIT_SUCCESS;
367                         log_warning("Ignoring error.");
368                 }
369
370         } else
371                 r = EXIT_SUCCESS;
372
373         if (status.si_code == CLD_EXITED && (status.si_status & 1))
374                 touch("/run/systemd/quotacheck");
375
376 finish:
377         safe_close_pair(progress_pipe);
378
379         return r;
380 }