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