chiark / gitweb /
9ecba99bb1a7b4c28f11d21658412945163709ab
[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   Copyright 2014 Holger Hans Peter Freyther
8
9   systemd is free software; you can redistribute it and/or modify it
10   under the terms of the GNU Lesser General Public License as published by
11   the Free Software Foundation; either version 2.1 of the License, or
12   (at your option) any later version.
13
14   systemd is distributed in the hope that it will be useful, but
15   WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   Lesser General Public License for more details.
18
19   You should have received a copy of the GNU Lesser General Public License
20   along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <stdio.h>
24 #include <stdbool.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29 #include <sys/file.h>
30 #include <sys/stat.h>
31
32 #include "sd-bus.h"
33 #include "libudev.h"
34
35 #include "util.h"
36 #include "special.h"
37 #include "bus-util.h"
38 #include "bus-error.h"
39 #include "bus-common-errors.h"
40 #include "fileio.h"
41 #include "udev-util.h"
42 #include "path-util.h"
43 #include "socket-util.h"
44 #include "fsckd/fsckd.h"
45
46 static bool arg_skip = false;
47 static bool arg_force = false;
48 static const char *arg_repair = "-a";
49
50 static void start_target(const char *target) {
51         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
52         _cleanup_bus_close_unref_ sd_bus *bus = NULL;
53         int r;
54
55         assert(target);
56
57         r = bus_open_system_systemd(&bus);
58         if (r < 0) {
59                 log_error_errno(r, "Failed to get D-Bus connection: %m");
60                 return;
61         }
62
63         log_info("Running request %s/start/replace", target);
64
65         /* Start these units only if we can replace base.target with it */
66         r = sd_bus_call_method(bus,
67                                "org.freedesktop.systemd1",
68                                "/org/freedesktop/systemd1",
69                                "org.freedesktop.systemd1.Manager",
70                                "StartUnitReplace",
71                                &error,
72                                NULL,
73                                "sss", "basic.target", target, "replace");
74
75         /* Don't print a warning if we aren't called during startup */
76         if (r < 0 && !sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_JOB))
77                 log_error("Failed to start unit: %s", bus_error_message(&error, -r));
78 }
79
80 static int parse_proc_cmdline_item(const char *key, const char *value) {
81
82         if (streq(key, "fsck.mode") && value) {
83
84                 if (streq(value, "auto"))
85                         arg_force = arg_skip = false;
86                 else if (streq(value, "force"))
87                         arg_force = true;
88                 else if (streq(value, "skip"))
89                         arg_skip = true;
90                 else
91                         log_warning("Invalid fsck.mode= parameter '%s'. Ignoring.", value);
92
93         } else if (streq(key, "fsck.repair") && value) {
94
95                 if (streq(value, "preen"))
96                         arg_repair = "-a";
97                 else if (streq(value, "yes"))
98                         arg_repair = "-y";
99                 else if (streq(value, "no"))
100                         arg_repair = "-n";
101                 else
102                         log_warning("Invalid fsck.repair= parameter '%s'. Ignoring.", value);
103         }
104
105 #ifdef HAVE_SYSV_COMPAT
106         else if (streq(key, "fastboot") && !value) {
107                 log_warning("Please pass 'fsck.mode=skip' rather than 'fastboot' on the kernel command line.");
108                 arg_skip = true;
109
110         } else if (streq(key, "forcefsck") && !value) {
111                 log_warning("Please pass 'fsck.mode=force' rather than 'forcefsck' on the kernel command line.");
112                 arg_force = true;
113         }
114 #endif
115
116         return 0;
117 }
118
119 static void test_files(void) {
120
121 #ifdef HAVE_SYSV_COMPAT
122         if (access("/fastboot", F_OK) >= 0) {
123                 log_error("Please pass 'fsck.mode=skip' on the kernel command line rather than creating /fastboot on the root file system.");
124                 arg_skip = true;
125         }
126
127         if (access("/forcefsck", F_OK) >= 0) {
128                 log_error("Please pass 'fsck.mode=force' on the kernel command line rather than creating /forcefsck on the root file system.");
129                 arg_force = true;
130         }
131 #endif
132
133 }
134
135 static int process_progress(int fd, dev_t device_num) {
136         _cleanup_fclose_ FILE *f = NULL;
137         usec_t last = 0;
138         _cleanup_close_ int fsckd_fd = -1;
139         static const union sockaddr_union sa = {
140                 .un.sun_family = AF_UNIX,
141                 .un.sun_path = FSCKD_SOCKET_PATH,
142         };
143
144         fsckd_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
145         if (fsckd_fd < 0)
146                 return log_warning_errno(errno, "Cannot open fsckd socket, we won't report fsck progress: %m");
147         if (connect(fsckd_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
148                 return log_warning_errno(errno, "Cannot connect to fsckd socket, we won't report fsck progress: %m");
149
150         f = fdopen(fd, "r");
151         if (!f)
152                 return log_warning_errno(errno, "Cannot connect to fsck, we won't report fsck progress: %m");
153
154         while (!feof(f)) {
155                 int pass;
156                 size_t cur, max;
157                 ssize_t n;
158                 usec_t t;
159                 _cleanup_free_ char *device = NULL;
160                 FsckProgress progress;
161
162                 if (fscanf(f, "%i %lu %lu %ms", &pass, &cur, &max, &device) != 4)
163                         break;
164
165                 /* Only update once every 50ms */
166                 t = now(CLOCK_MONOTONIC);
167                 if (last + 50 * USEC_PER_MSEC > t)
168                         continue;
169
170                 last = t;
171
172                 /* send progress to fsckd */
173                 progress.devnum = device_num;
174                 progress.cur = cur;
175                 progress.max = max;
176                 progress.pass = pass;
177
178                 n = send(fsckd_fd, &progress, sizeof(FsckProgress), 0);
179                 if (n < 0 || (size_t) n < sizeof(FsckProgress))
180                         log_warning_errno(errno, "Cannot communicate fsck progress to fsckd: %m");
181         }
182
183         return 0;
184 }
185
186 int main(int argc, char *argv[]) {
187         const char *cmdline[9];
188         int i = 0, r = EXIT_FAILURE, q;
189         pid_t pid;
190         siginfo_t status;
191         _cleanup_udev_unref_ struct udev *udev = NULL;
192         _cleanup_udev_device_unref_ struct udev_device *udev_device = NULL;
193         const char *device, *type;
194         bool root_directory;
195         int progress_pipe[2] = { -1, -1 };
196         char dash_c[sizeof("-C")-1 + DECIMAL_STR_MAX(int) + 1];
197         struct stat st;
198
199         if (argc > 2) {
200                 log_error("This program expects one or no arguments.");
201                 return EXIT_FAILURE;
202         }
203
204         log_set_target(LOG_TARGET_AUTO);
205         log_parse_environment();
206         log_open();
207
208         umask(0022);
209
210         q = parse_proc_cmdline(parse_proc_cmdline_item);
211         if (q < 0)
212                 log_warning_errno(q, "Failed to parse kernel command line, ignoring: %m");
213
214         test_files();
215
216         if (!arg_force && arg_skip)
217                 return 0;
218
219         udev = udev_new();
220         if (!udev) {
221                 log_oom();
222                 return EXIT_FAILURE;
223         }
224
225         if (argc > 1) {
226                 device = argv[1];
227                 root_directory = false;
228
229                 if (stat(device, &st) < 0) {
230                         log_error_errno(errno, "Failed to stat '%s': %m", device);
231                         return EXIT_FAILURE;
232                 }
233
234                 udev_device = udev_device_new_from_devnum(udev, 'b', st.st_rdev);
235                 if (!udev_device) {
236                         log_error("Failed to detect device %s", device);
237                         return EXIT_FAILURE;
238                 }
239         } else {
240                 struct timespec times[2];
241
242                 /* Find root device */
243
244                 if (stat("/", &st) < 0) {
245                         log_error_errno(errno, "Failed to stat() the root directory: %m");
246                         return EXIT_FAILURE;
247                 }
248
249                 /* Virtual root devices don't need an fsck */
250                 if (major(st.st_dev) == 0)
251                         return EXIT_SUCCESS;
252
253                 /* check if we are already writable */
254                 times[0] = st.st_atim;
255                 times[1] = st.st_mtim;
256                 if (utimensat(AT_FDCWD, "/", times, 0) == 0) {
257                         log_info("Root directory is writable, skipping check.");
258                         return EXIT_SUCCESS;
259                 }
260
261                 udev_device = udev_device_new_from_devnum(udev, 'b', st.st_dev);
262                 if (!udev_device) {
263                         log_error("Failed to detect root device.");
264                         return EXIT_FAILURE;
265                 }
266
267                 device = udev_device_get_devnode(udev_device);
268                 if (!device) {
269                         log_error("Failed to detect device node of root directory.");
270                         return EXIT_FAILURE;
271                 }
272
273                 root_directory = true;
274         }
275
276         type = udev_device_get_property_value(udev_device, "ID_FS_TYPE");
277         if (type) {
278                 r = fsck_exists(type);
279                 if (r == -ENOENT) {
280                         log_info("fsck.%s doesn't exist, not checking file system on %s", type, device);
281                         return EXIT_SUCCESS;
282                 } else if (r < 0)
283                         log_warning_errno(r, "fsck.%s cannot be used for %s: %m", type, device);
284         }
285
286         if (pipe(progress_pipe) < 0) {
287                 log_error_errno(errno, "pipe(): %m");
288                 return EXIT_FAILURE;
289         }
290
291         cmdline[i++] = "/sbin/fsck";
292         cmdline[i++] =  arg_repair;
293         cmdline[i++] = "-T";
294
295         /*
296          * Since util-linux v2.25 fsck uses /run/fsck/<diskname>.lock files.
297          * The previous versions use flock for the device and conflict with
298          * udevd, see https://bugs.freedesktop.org/show_bug.cgi?id=79576#c5
299          */
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                 xsprintf(dash_c, "-C%i", progress_pipe[1]);
310                 cmdline[i++] = dash_c;
311         }
312
313         cmdline[i++] = device;
314         cmdline[i++] = NULL;
315
316         pid = fork();
317         if (pid < 0) {
318                 log_error_errno(errno, "fork(): %m");
319                 goto finish;
320         } else if (pid == 0) {
321                 /* Child */
322                 if (progress_pipe[0] >= 0)
323                         safe_close(progress_pipe[0]);
324                 execv(cmdline[0], (char**) cmdline);
325                 _exit(8); /* Operational error */
326         }
327
328         progress_pipe[1] = safe_close(progress_pipe[1]);
329
330         if (progress_pipe[0] >= 0) {
331                 process_progress(progress_pipe[0], st.st_rdev);
332                 progress_pipe[0] = -1;
333         }
334
335         q = wait_for_terminate(pid, &status);
336         if (q < 0) {
337                 log_error_errno(q, "waitid(): %m");
338                 goto finish;
339         }
340
341         if (status.si_code != CLD_EXITED || (status.si_status & ~1)) {
342
343                 if (status.si_code == CLD_KILLED || status.si_code == CLD_DUMPED)
344                         log_error("fsck terminated by signal %s.", signal_to_string(status.si_status));
345                 else if (status.si_code == CLD_EXITED)
346                         log_error("fsck failed with error code %i.", status.si_status);
347                 else
348                         log_error("fsck failed due to unknown reason.");
349
350                 if (status.si_code == CLD_EXITED && (status.si_status & 2) && root_directory)
351                         /* System should be rebooted. */
352                         start_target(SPECIAL_REBOOT_TARGET);
353                 else if (status.si_code == CLD_EXITED && (status.si_status & 6))
354                         /* Some other problem */
355                         start_target(SPECIAL_EMERGENCY_TARGET);
356                 else {
357                         r = EXIT_SUCCESS;
358                         log_warning("Ignoring error.");
359                 }
360
361         } else
362                 r = EXIT_SUCCESS;
363
364         if (status.si_code == CLD_EXITED && (status.si_status & 1))
365                 touch("/run/systemd/quotacheck");
366
367 finish:
368         safe_close_pair(progress_pipe);
369
370         return r;
371 }