chiark / gitweb /
Connect to plymouth and support cancellation of in progress fsck
[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, pid_t fsck_pid, 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 buflen;
157                 size_t cur, max;
158                 ssize_t r;
159                 usec_t t;
160                 _cleanup_free_ char *device = NULL;
161                 FsckProgress progress;
162                 FsckdMessage fsckd_message;
163
164                 if (fscanf(f, "%i %lu %lu %ms", &pass, &cur, &max, &device) != 4)
165                         break;
166
167                 /* Only update once every 50ms */
168                 t = now(CLOCK_MONOTONIC);
169                 if (last + 50 * USEC_PER_MSEC > t)
170                         continue;
171
172                 last = t;
173
174                 /* send progress to fsckd */
175                 progress.devnum = device_num;
176                 progress.cur = cur;
177                 progress.max = max;
178                 progress.pass = pass;
179
180                 r = send(fsckd_fd, &progress, sizeof(FsckProgress), 0);
181                 if (r < 0 || (size_t) r < sizeof(FsckProgress))
182                         log_warning_errno(errno, "Cannot communicate fsck progress to fsckd: %m");
183
184                 /* get fsckd requests, only read when we have coherent size data */
185                 r = ioctl(fsckd_fd, FIONREAD, &buflen);
186                 if (r == 0 && (size_t) buflen >= sizeof(FsckdMessage)) {
187                         r = recv(fsckd_fd, &fsckd_message, sizeof(FsckdMessage), 0);
188                         if (r > 0 && fsckd_message.cancel == 1) {
189                                 log_info("Request to cancel fsck from fsckd");
190                                 kill(fsck_pid, SIGTERM);
191                         }
192                 }
193         }
194
195         return 0;
196 }
197
198 int main(int argc, char *argv[]) {
199         const char *cmdline[9];
200         int i = 0, r = EXIT_FAILURE, q;
201         pid_t pid;
202         int progress_rc;
203         siginfo_t status;
204         _cleanup_udev_unref_ struct udev *udev = NULL;
205         _cleanup_udev_device_unref_ struct udev_device *udev_device = NULL;
206         const char *device, *type;
207         bool root_directory;
208         int progress_pipe[2] = { -1, -1 };
209         char dash_c[sizeof("-C")-1 + DECIMAL_STR_MAX(int) + 1];
210         struct stat st;
211
212         if (argc > 2) {
213                 log_error("This program expects one or no arguments.");
214                 return EXIT_FAILURE;
215         }
216
217         log_set_target(LOG_TARGET_AUTO);
218         log_parse_environment();
219         log_open();
220
221         umask(0022);
222
223         q = parse_proc_cmdline(parse_proc_cmdline_item);
224         if (q < 0)
225                 log_warning_errno(q, "Failed to parse kernel command line, ignoring: %m");
226
227         test_files();
228
229         if (!arg_force && arg_skip)
230                 return 0;
231
232         udev = udev_new();
233         if (!udev) {
234                 log_oom();
235                 return EXIT_FAILURE;
236         }
237
238         if (argc > 1) {
239                 device = argv[1];
240                 root_directory = false;
241
242                 if (stat(device, &st) < 0) {
243                         log_error_errno(errno, "Failed to stat '%s': %m", device);
244                         return EXIT_FAILURE;
245                 }
246
247                 udev_device = udev_device_new_from_devnum(udev, 'b', st.st_rdev);
248                 if (!udev_device) {
249                         log_error("Failed to detect device %s", device);
250                         return EXIT_FAILURE;
251                 }
252         } else {
253                 struct timespec times[2];
254
255                 /* Find root device */
256
257                 if (stat("/", &st) < 0) {
258                         log_error_errno(errno, "Failed to stat() the root directory: %m");
259                         return EXIT_FAILURE;
260                 }
261
262                 /* Virtual root devices don't need an fsck */
263                 if (major(st.st_dev) == 0)
264                         return EXIT_SUCCESS;
265
266                 /* check if we are already writable */
267                 times[0] = st.st_atim;
268                 times[1] = st.st_mtim;
269                 if (utimensat(AT_FDCWD, "/", times, 0) == 0) {
270                         log_info("Root directory is writable, skipping check.");
271                         return EXIT_SUCCESS;
272                 }
273
274                 udev_device = udev_device_new_from_devnum(udev, 'b', st.st_dev);
275                 if (!udev_device) {
276                         log_error("Failed to detect root device.");
277                         return EXIT_FAILURE;
278                 }
279
280                 device = udev_device_get_devnode(udev_device);
281                 if (!device) {
282                         log_error("Failed to detect device node of root directory.");
283                         return EXIT_FAILURE;
284                 }
285
286                 root_directory = true;
287         }
288
289         type = udev_device_get_property_value(udev_device, "ID_FS_TYPE");
290         if (type) {
291                 r = fsck_exists(type);
292                 if (r == -ENOENT) {
293                         log_info("fsck.%s doesn't exist, not checking file system on %s", type, device);
294                         return EXIT_SUCCESS;
295                 } else if (r < 0)
296                         log_warning_errno(r, "fsck.%s cannot be used for %s: %m", type, device);
297         }
298
299         if (pipe(progress_pipe) < 0) {
300                 log_error_errno(errno, "pipe(): %m");
301                 return EXIT_FAILURE;
302         }
303
304         cmdline[i++] = "/sbin/fsck";
305         cmdline[i++] =  arg_repair;
306         cmdline[i++] = "-T";
307
308         /*
309          * Since util-linux v2.25 fsck uses /run/fsck/<diskname>.lock files.
310          * The previous versions use flock for the device and conflict with
311          * udevd, see https://bugs.freedesktop.org/show_bug.cgi?id=79576#c5
312          */
313         cmdline[i++] = "-l";
314
315         if (!root_directory)
316                 cmdline[i++] = "-M";
317
318         if (arg_force)
319                 cmdline[i++] = "-f";
320
321         if (progress_pipe[1] >= 0) {
322                 xsprintf(dash_c, "-C%i", progress_pipe[1]);
323                 cmdline[i++] = dash_c;
324         }
325
326         cmdline[i++] = device;
327         cmdline[i++] = NULL;
328
329         pid = fork();
330         if (pid < 0) {
331                 log_error_errno(errno, "fork(): %m");
332                 goto finish;
333         } else if (pid == 0) {
334                 /* Child */
335                 if (progress_pipe[0] >= 0)
336                         safe_close(progress_pipe[0]);
337                 execv(cmdline[0], (char**) cmdline);
338                 _exit(8); /* Operational error */
339         }
340
341         progress_pipe[1] = safe_close(progress_pipe[1]);
342
343         if (progress_pipe[0] >= 0) {
344                 progress_rc = process_progress(progress_pipe[0], pid, st.st_rdev);
345                 progress_pipe[0] = -1;
346         }
347
348         q = wait_for_terminate(pid, &status);
349         if (q < 0) {
350                 log_error_errno(q, "waitid(): %m");
351                 goto finish;
352         }
353
354         if (status.si_code != CLD_EXITED || (status.si_status & ~1) || progress_rc != 0) {
355
356                 /* cancel will kill fsck (but process_progress returns 0) */
357                 if ((progress_rc != 0 && status.si_code == CLD_KILLED) || status.si_code == CLD_DUMPED)
358                         log_error("fsck terminated by signal %s.", signal_to_string(status.si_status));
359                 else if (status.si_code == CLD_EXITED)
360                         log_error("fsck failed with error code %i.", status.si_status);
361                 else if (progress_rc != 0)
362                         log_error("fsck failed due to unknown reason.");
363
364                 if (status.si_code == CLD_EXITED && (status.si_status & 2) && root_directory)
365                         /* System should be rebooted. */
366                         start_target(SPECIAL_REBOOT_TARGET);
367                 else if (status.si_code == CLD_EXITED && (status.si_status & 6))
368                         /* Some other problem */
369                         start_target(SPECIAL_EMERGENCY_TARGET);
370                 else {
371                         r = EXIT_SUCCESS;
372                         if (progress_rc != 0)
373                                 log_warning("Ignoring error.");
374                 }
375
376         } else
377                 r = EXIT_SUCCESS;
378
379         if (status.si_code == CLD_EXITED && (status.si_status & 1))
380                 touch("/run/systemd/quotacheck");
381
382 finish:
383         safe_close_pair(progress_pipe);
384
385         return r;
386 }