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