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