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