fsck: remove fsckd again, but keep the door open for external replacement

For a longer discussion see this:

http://lists.freedesktop.org/archives/systemd-devel/2015-April/030175.html

This introduces /run/systemd/fsck.progress as a simply
AF_UNIX/SOCK_STREAM socket. If it exists and is connectable we'll
connect fsck's -c switch with it. If external programs want to get
progress data they should hence listen on this socket and will get
all they need via that socket. To get information about the connecting
fsck client they should use SO_PEERCRED.

Unless /run/systemd/fsck.progress is around and connectable this change
reverts back to v219 behaviour where we'd forward fsck output to
/dev/console on our own.
This commit is contained in:
Lennart Poettering 2015-04-28 17:13:23 +02:00
parent e7a3aa3df6
commit 96d9117ad2
14 changed files with 164 additions and 999 deletions

1
.gitignore vendored
View File

@ -78,7 +78,6 @@
/systemd-export
/systemd-firstboot
/systemd-fsck
/systemd-fsckd
/systemd-fstab-generator
/systemd-getty-generator
/systemd-gnome-ask-password-agent

View File

@ -66,7 +66,6 @@ MANPAGES += \
man/systemd-efi-boot-generator.8 \
man/systemd-escape.1 \
man/systemd-fsck@.service.8 \
man/systemd-fsckd.service.8 \
man/systemd-fstab-generator.8 \
man/systemd-getty-generator.8 \
man/systemd-gpt-auto-generator.8 \
@ -209,8 +208,6 @@ MANPAGES_ALIAS += \
man/systemd-ask-password-wall.service.8 \
man/systemd-fsck-root.service.8 \
man/systemd-fsck.8 \
man/systemd-fsckd.8 \
man/systemd-fsckd.socket.8 \
man/systemd-hibernate-resume.8 \
man/systemd-hibernate.service.8 \
man/systemd-hybrid-sleep.service.8 \
@ -321,8 +318,6 @@ man/systemd-ask-password-wall.path.8: man/systemd-ask-password-console.service.8
man/systemd-ask-password-wall.service.8: man/systemd-ask-password-console.service.8
man/systemd-fsck-root.service.8: man/systemd-fsck@.service.8
man/systemd-fsck.8: man/systemd-fsck@.service.8
man/systemd-fsckd.8: man/systemd-fsckd.service.8
man/systemd-fsckd.socket.8: man/systemd-fsckd.service.8
man/systemd-hibernate-resume.8: man/systemd-hibernate-resume@.service.8
man/systemd-hibernate.service.8: man/systemd-suspend.service.8
man/systemd-hybrid-sleep.service.8: man/systemd-suspend.service.8
@ -601,12 +596,6 @@ man/systemd-fsck-root.service.html: man/systemd-fsck@.service.html
man/systemd-fsck.html: man/systemd-fsck@.service.html
$(html-alias)
man/systemd-fsckd.html: man/systemd-fsckd.service.html
$(html-alias)
man/systemd-fsckd.socket.html: man/systemd-fsckd.service.html
$(html-alias)
man/systemd-hibernate-resume.html: man/systemd-hibernate-resume@.service.html
$(html-alias)
@ -1764,7 +1753,6 @@ EXTRA_DIST += \
man/systemd-escape.xml \
man/systemd-firstboot.xml \
man/systemd-fsck@.service.xml \
man/systemd-fsckd.service.xml \
man/systemd-fstab-generator.xml \
man/systemd-getty-generator.xml \
man/systemd-gpt-auto-generator.xml \

View File

@ -398,7 +398,6 @@ rootlibexec_PROGRAMS = \
systemd-remount-fs \
systemd-reply-password \
systemd-fsck \
systemd-fsckd \
systemd-machine-id-commit \
systemd-ac-power \
systemd-sysctl \
@ -501,7 +500,6 @@ dist_systemunit_DATA = \
units/slices.target \
units/system.slice \
units/x-.slice \
units/systemd-fsckd.socket \
units/systemd-initctl.socket \
units/syslog.socket \
units/dev-hugepages.mount \
@ -552,7 +550,6 @@ nodist_systemunit_DATA = \
units/systemd-kexec.service \
units/systemd-fsck@.service \
units/systemd-fsck-root.service \
units/systemd-fsckd.service \
units/systemd-machine-id-commit.service \
units/systemd-udevd.service \
units/systemd-udev-trigger.service \
@ -605,7 +602,6 @@ EXTRA_DIST += \
units/user/systemd-exit.service.in \
units/systemd-fsck@.service.in \
units/systemd-fsck-root.service.in \
units/systemd-fsckd.service.in \
units/systemd-machine-id-commit.service.in \
units/user@.service.m4.in \
units/debug-shell.service.in \
@ -2377,19 +2373,6 @@ systemd_fsck_LDADD = \
libsystemd-internal.la \
libsystemd-shared.la
# ------------------------------------------------------------------------------
systemd_fsckd_SOURCES = \
src/fsckd/fsckd.c \
src/fsckd/fsckd.h \
$(NULL)
systemd_fsckd_LDADD = \
libsystemd-internal.la \
libsystemd-label.la \
libsystemd-shared.la \
libudev-internal.la \
$(NULL)
# ------------------------------------------------------------------------------
systemd_machine_id_commit_SOURCES = \
src/machine-id-commit/machine-id-commit.c \

View File

@ -80,9 +80,7 @@
the filesystem should actually be checked based on the time since
last check, number of mounts, unclean unmount, etc.</para>
<para><filename>systemd-fsck</filename> will forward file system
checking progress to <filename>systemd-fsckd.service</filename>
socket. If a file system check fails for a service without
<para>If a file system check fails for a service without
<option>nofail</option>, emergency mode is activated, by isolating
to <filename>emergency.target</filename>.</para>
</refsect1>

View File

@ -1,162 +0,0 @@
<?xml version="1.0"?>
<!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!--
This file is part of systemd.
Copyright 2015 Canonical
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
-->
<refentry id="systemd-fsckd.service" xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>systemd-fsckd.service</title>
<productname>systemd</productname>
<authorgroup>
<author>
<contrib>Developer</contrib>
<firstname>Didier</firstname>
<surname>Roche</surname>
<email>didrocks@ubuntu.com</email>
</author>
</authorgroup>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-fsckd.service</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-fsckd.service</refname>
<refname>systemd-fsckd.socket</refname>
<refname>systemd-fsckd</refname>
<refpurpose>File system check progress reporting</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>systemd-fsckd.service</filename></para>
<para><filename>systemd-fsckd.socket</filename></para>
<para><filename>/usr/lib/systemd/systemd-fsckd</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><filename>systemd-fsckd.service</filename> is a service responsible
for receiving file system check progress, and communicating some
consolidated data to console and plymouth (if running). It also handles
possible check cancellations.</para>
<para><command>systemd-fsckd</command> receives messages about file
system check progress from <command>systemd-fsck</command> through a
UNIX domain socket. It can display the progress of the least advanced
fsck as well as the total number of devices being checked in parallel
to the console. It will also send progress messages to plymouth.
Both the raw data and translated messages are sent, so compiled
plymouth themes can use the raw data to display custom messages, and
scripted themes, not supporting i18n, can display the translated
versions.</para>
<para><command>systemd-fsckd</command> will instruct plymouth to grab
Control+C keypresses. When the key is pressed, running checks will be
terminated. It will also cancel any newly connected fsck instances for
the lifetime of <filename>systemd-fsckd</filename>.</para>
</refsect1>
<refsect1>
<title>Protocol for communication with plymouth</title>
<para><filename>systemd-fsckd</filename> passes the
following messages to the theme:</para>
<para>Progress update, sent as a plymouth update message:
<literal>fsckd:&lt;num_devices&gt;:&lt;progress&gt;:&lt;string&gt;</literal>
<variablelist>
<varlistentry>
<term><literal>&lt;num_devices&gt;</literal></term>
<listitem><para>the current number of devices
being checked (int)</para></listitem>
</varlistentry>
<varlistentry>
<term><literal>&lt;progress&gt;</literal></term>
<listitem><para>the current minimum percentage of
all devices being checking (float, from 0 to 100)</para></listitem>
</varlistentry>
<varlistentry>
<term><literal>&lt;string&gt;</literal></term>
<listitem><para>a translated message ready to be displayed
by the plymouth theme displaying the data above. It can be overriden
by themes supporting i18n.</para></listitem>
</varlistentry>
</variablelist>
</para>
<para>Cancel message, sent as a traditional plymouth message:
<literal>fsckd-cancel-msg:&lt;string&gt;</literal>
<variablelist>
<varlistentry>
<term><literal>&lt;strings&gt;</literal></term>
<listitem><para>a translated string ready to be displayed
by the plymouth theme indicating that Control+C can be used to cancel
current checks. It can be overriden (matching only
<literal>fsckd-cancel-msg</literal> prefix)
by themes supporting i18n.</para></listitem>
</varlistentry>
</variablelist>
</para>
</refsect1>
<refsect1>
<title>Options</title>
<para>The following options are understood:</para>
<variablelist>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
</refsect1>
<refsect1>
<title>Exit status</title>
<para>On success, 0 is returned, a non-zero failure
code otherwise. Note that the daemon stays idle for
a while to accept new <filename>systemd-fsck</filename>
connections before exiting.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-fsck</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>fsck</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-quotacheck.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>fsck.btrfs</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>fsck.cramfs</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>fsck.ext4</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>fsck.fat</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>fsck.hfsplus</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>fsck.minix</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>fsck.ntfs</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>fsck.xfs</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View File

@ -27,6 +27,7 @@
#include <fcntl.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#include "sd-bus.h"
#include "sd-device.h"
@ -40,10 +41,10 @@
#include "device-util.h"
#include "path-util.h"
#include "socket-util.h"
#include "fsckd/fsckd.h"
static bool arg_skip = false;
static bool arg_force = false;
static bool arg_show_progress = false;
static const char *arg_repair = "-a";
static void start_target(const char *target) {
@ -135,39 +136,74 @@ static void test_files(void) {
}
#endif
arg_show_progress = access("/run/systemd/show-status", F_OK) >= 0;
}
static int process_progress(int fd, pid_t fsck_pid, dev_t device_num) {
_cleanup_fclose_ FILE *f = NULL;
usec_t last = 0;
_cleanup_close_ int fsckd_fd = -1;
static const union sockaddr_union sa = {
.un.sun_family = AF_UNIX,
.un.sun_path = FSCKD_SOCKET_PATH,
static double percent(int pass, unsigned long cur, unsigned long max) {
/* Values stolen from e2fsck */
static const int pass_table[] = {
0, 70, 90, 92, 95, 100
};
fsckd_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (fsckd_fd < 0)
return log_warning_errno(errno, "Cannot open fsckd socket, we won't report fsck progress: %m");
if (connect(fsckd_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
return log_warning_errno(errno, "Cannot connect to fsckd socket, we won't report fsck progress: %m");
if (pass <= 0)
return 0.0;
f = fdopen(fd, "r");
if (!f)
return log_warning_errno(errno, "Cannot connect to fsck, we won't report fsck progress: %m");
if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
return 100.0;
while (!feof(f)) {
int pass;
size_t buflen;
size_t cur, max;
ssize_t r;
usec_t t;
return (double) pass_table[pass-1] +
((double) pass_table[pass] - (double) pass_table[pass-1]) *
(double) cur / (double) max;
}
static int process_progress(int fd) {
_cleanup_fclose_ FILE *console = NULL, *f = NULL;
usec_t last = 0;
bool locked = false;
int clear = 0, r;
/* No progress pipe to process? Then we are a NOP. */
if (fd < 0)
return 0;
f = fdopen(fd, "re");
if (!f) {
safe_close(fd);
return -errno;
}
console = fopen("/dev/console", "we");
if (!console)
return -ENOMEM;
for (;;) {
int pass, m;
unsigned long cur, max;
_cleanup_free_ char *device = NULL;
FsckProgress progress;
FsckdMessage fsckd_message;
double p;
usec_t t;
if (fscanf(f, "%i %zu %zu %ms", &pass, &cur, &max, &device) != 4)
if (fscanf(f, "%i %lu %lu %ms", &pass, &cur, &max, &device) != 4) {
if (ferror(f))
r = log_warning_errno(errno, "Failed to read from progress pipe: %m");
else if (feof(f))
r = 0;
else {
log_warning("Failed to parse progress pipe data");
r = -EBADMSG;
}
break;
}
/* Only show one progress counter at max */
if (!locked) {
if (flock(fileno(console), LOCK_EX|LOCK_NB) < 0)
continue;
locked = true;
}
/* Only update once every 50ms */
t = now(CLOCK_MONOTONIC);
@ -176,42 +212,58 @@ static int process_progress(int fd, pid_t fsck_pid, dev_t device_num) {
last = t;
/* send progress to fsckd */
progress.devnum = device_num;
progress.cur = cur;
progress.max = max;
progress.pass = pass;
p = percent(pass, cur, max);
fprintf(console, "\r%s: fsck %3.1f%% complete...\r%n", device, p, &m);
fflush(console);
r = send(fsckd_fd, &progress, sizeof(FsckProgress), 0);
if (r < 0 || (size_t) r < sizeof(FsckProgress))
log_warning_errno(errno, "Cannot communicate fsck progress to fsckd: %m");
/* get fsckd requests, only read when we have coherent size data */
r = ioctl(fsckd_fd, FIONREAD, &buflen);
if (r == 0 && (size_t) buflen >= sizeof(FsckdMessage)) {
r = recv(fsckd_fd, &fsckd_message, sizeof(FsckdMessage), 0);
if (r > 0 && fsckd_message.cancel == 1) {
log_info("Request to cancel fsck from fsckd");
kill(fsck_pid, SIGTERM);
}
}
if (m > clear)
clear = m;
}
return 0;
if (clear > 0) {
unsigned j;
fputc('\r', console);
for (j = 0; j < (unsigned) clear; j++)
fputc(' ', console);
fputc('\r', console);
fflush(console);
}
return r;
}
static int fsck_progress_socket(void) {
static const union sockaddr_union sa = {
.un.sun_family = AF_UNIX,
.un.sun_path = "/run/systemd/fsck.progress",
};
int fd, r;
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0)
return log_warning_errno(errno, "socket(): %m");
if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) {
r = log_full_errno(errno == ECONNREFUSED || errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
errno, "Failed to connect to progress socket %s, ignoring: %m", sa.un.sun_path);
safe_close(fd);
return r;
}
return fd;
}
int main(int argc, char *argv[]) {
const char *cmdline[9];
int i = 0, r = EXIT_FAILURE, q;
pid_t pid;
int progress_rc;
siginfo_t status;
_cleanup_close_pair_ int progress_pipe[2] = { -1, -1 };
_cleanup_device_unref_ sd_device *dev = NULL;
const char *device, *type;
bool root_directory;
_cleanup_close_pair_ int progress_pipe[2] = { -1, -1 };
char dash_c[sizeof("-C")-1 + DECIMAL_STR_MAX(int) + 1];
siginfo_t status;
struct stat st;
int r;
pid_t pid;
if (argc > 2) {
log_error("This program expects one or no arguments.");
@ -309,48 +361,74 @@ int main(int argc, char *argv[]) {
log_warning_errno(r, "Couldn't detect if fsck.%s may be used for %s: %m", type, device);
}
if (pipe(progress_pipe) < 0) {
r = log_error_errno(errno, "pipe(): %m");
goto finish;
if (arg_show_progress) {
if (pipe(progress_pipe) < 0) {
r = log_error_errno(errno, "pipe(): %m");
goto finish;
}
}
cmdline[i++] = "/sbin/fsck";
cmdline[i++] = arg_repair;
cmdline[i++] = "-T";
/*
* Since util-linux v2.25 fsck uses /run/fsck/<diskname>.lock files.
* The previous versions use flock for the device and conflict with
* udevd, see https://bugs.freedesktop.org/show_bug.cgi?id=79576#c5
*/
cmdline[i++] = "-l";
if (!root_directory)
cmdline[i++] = "-M";
if (arg_force)
cmdline[i++] = "-f";
xsprintf(dash_c, "-C%i", progress_pipe[1]);
cmdline[i++] = dash_c;
cmdline[i++] = device;
cmdline[i++] = NULL;
pid = fork();
if (pid < 0) {
r = log_error_errno(errno, "fork(): %m");
goto finish;
} else if (pid == 0) {
}
if (pid == 0) {
char dash_c[sizeof("-C")-1 + DECIMAL_STR_MAX(int) + 1];
int progress_socket = -1;
const char *cmdline[9];
int i = 0;
/* Child */
reset_all_signal_handlers();
reset_signal_mask();
assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
/* Close the reading side of the progress pipe */
progress_pipe[0] = safe_close(progress_pipe[0]);
/* Try to connect to a progress management daemon, if there is one */
progress_socket = fsck_progress_socket();
if (progress_socket >= 0) {
/* If this worked we close the progress pipe early, and just use the socket */
progress_pipe[1] = safe_close(progress_pipe[1]);
xsprintf(dash_c, "-C%i", progress_socket);
} else if (progress_pipe[1] >= 0) {
/* Otherwise if we have the progress pipe to our own local handle, we use it */
xsprintf(dash_c, "-C%i", progress_pipe[1]);
} else
dash_c[0] = 0;
cmdline[i++] = "/sbin/fsck";
cmdline[i++] = arg_repair;
cmdline[i++] = "-T";
/*
* Since util-linux v2.25 fsck uses /run/fsck/<diskname>.lock files.
* The previous versions use flock for the device and conflict with
* udevd, see https://bugs.freedesktop.org/show_bug.cgi?id=79576#c5
*/
cmdline[i++] = "-l";
if (!root_directory)
cmdline[i++] = "-M";
if (arg_force)
cmdline[i++] = "-f";
if (!isempty(dash_c))
cmdline[i++] = dash_c;
cmdline[i++] = device;
cmdline[i++] = NULL;
execv(cmdline[0], (char**) cmdline);
_exit(8); /* Operational error */
}
progress_pipe[1] = safe_close(progress_pipe[1]);
progress_rc = process_progress(progress_pipe[0], pid, st.st_rdev);
(void) process_progress(progress_pipe[0]);
progress_pipe[0] = -1;
r = wait_for_terminate(pid, &status);
@ -359,14 +437,13 @@ int main(int argc, char *argv[]) {
goto finish;
}
if (status.si_code != CLD_EXITED || (status.si_status & ~1) || progress_rc != 0) {
if (status.si_code != CLD_EXITED || (status.si_status & ~1)) {
/* cancel will kill fsck (but process_progress returns 0) */
if ((progress_rc != 0 && status.si_code == CLD_KILLED) || status.si_code == CLD_DUMPED)
if (status.si_code == CLD_KILLED || status.si_code == CLD_DUMPED)
log_error("fsck terminated by signal %s.", signal_to_string(status.si_status));
else if (status.si_code == CLD_EXITED)
log_error("fsck failed with error code %i.", status.si_status);
else if (progress_rc != 0)
else
log_error("fsck failed due to unknown reason.");
r = -EINVAL;
@ -378,9 +455,8 @@ int main(int argc, char *argv[]) {
/* Some other problem */
start_target(SPECIAL_EMERGENCY_TARGET);
else {
log_warning("Ignoring error.");
r = 0;
if (progress_rc != 0)
log_warning("Ignoring error.");
}
} else

View File

@ -1 +0,0 @@
../Makefile

View File

@ -1,642 +0,0 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2015 Canonical
Author:
Didier Roche <didrocks@ubuntu.com>
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <getopt.h>
#include <errno.h>
#include <libintl.h>
#include <math.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include "sd-daemon.h"
#include "build.h"
#include "def.h"
#include "event-util.h"
#include "log.h"
#include "list.h"
#include "macro.h"
#include "socket-util.h"
#include "util.h"
#include "fsckd.h"
#define IDLE_TIME_SECONDS 30
#define PLYMOUTH_REQUEST_KEY "K\2\2\3"
#define CLIENTS_MAX 128
struct Manager;
typedef struct Client {
struct Manager *manager;
int fd;
dev_t devnum;
size_t cur;
size_t max;
int pass;
double percent;
size_t buflen;
bool cancelled;
sd_event_source *event_source;
LIST_FIELDS(struct Client, clients);
} Client;
typedef struct Manager {
sd_event *event;
LIST_HEAD(Client, clients);
unsigned n_clients;
size_t clear;
int connection_fd;
sd_event_source *connection_event_source;
bool show_status_console;
double percent;
int numdevices;
int plymouth_fd;
sd_event_source *plymouth_event_source;
bool plymouth_cancel_sent;
bool cancel_requested;
} Manager;
static void client_free(Client *c);
static void manager_free(Manager *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(Client*, client_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
static int manager_write_console(Manager *m, const char *message) {
_cleanup_fclose_ FILE *console = NULL;
int l;
size_t j;
assert(m);
if (!m->show_status_console)
return 0;
/* Reduce the SAK window by opening and closing console on every request */
console = fopen("/dev/console", "we");
if (!console)
return -errno;
if (message) {
fprintf(console, "\r%s\r%n", message, &l);
if (m->clear < (size_t)l)
m->clear = (size_t)l;
} else {
fputc('\r', console);
for (j = 0; j < m->clear; j++)
fputc(' ', console);
fputc('\r', console);
}
fflush(console);
return 0;
}
static double compute_percent(int pass, size_t cur, size_t max) {
/* Values stolen from e2fsck */
static const double pass_table[] = {
0, 70, 90, 92, 95, 100
};
if (pass <= 0)
return 0.0;
if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
return 100.0;
return pass_table[pass-1] +
(pass_table[pass] - pass_table[pass-1]) *
(double) cur / max;
}
static int client_request_cancel(Client *c) {
FsckdMessage cancel_msg = {
.cancel = 1,
};
ssize_t n;
assert(c);
if (c->cancelled)
return 0;
n = send(c->fd, &cancel_msg, sizeof(FsckdMessage), 0);
if (n < 0)
return log_warning_errno(errno, "Cannot send cancel to fsck on (%u:%u): %m", major(c->devnum), minor(c->devnum));
if ((size_t) n < sizeof(FsckdMessage)) {
log_warning("Short send when sending cancel to fsck on (%u:%u).", major(c->devnum), minor(c->devnum));
return -EIO;
}
c->cancelled = true;
return 1;
}
static void client_free(Client *c) {
assert(c);
if (c->manager) {
LIST_REMOVE(clients, c->manager->clients, c);
c->manager->n_clients--;
}
sd_event_source_unref(c->event_source);
safe_close(c->fd);
free(c);
}
static void manager_disconnect_plymouth(Manager *m) {
assert(m);
m->plymouth_event_source = sd_event_source_unref(m->plymouth_event_source);
m->plymouth_fd = safe_close(m->plymouth_fd);
m->plymouth_cancel_sent = false;
}
static int manager_plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
Manager *m = userdata;
Client *current;
char buffer[6];
ssize_t l;
assert(m);
l = read(m->plymouth_fd, buffer, sizeof(buffer));
if (l < 0) {
log_warning_errno(errno, "Got error while reading from plymouth: %m");
manager_disconnect_plymouth(m);
return -errno;
}
if (l == 0) {
manager_disconnect_plymouth(m);
return 0;
}
if (l > 1 && buffer[0] == '\15')
log_error("Message update to plymouth wasn't delivered successfully");
/* the only answer support type we requested is a key interruption */
if (l > 2 && buffer[0] == '\2' && buffer[5] == '\3') {
m->cancel_requested = true;
/* cancel all connected clients */
LIST_FOREACH(clients, current, m->clients)
client_request_cancel(current);
}
return 0;
}
static int manager_connect_plymouth(Manager *m) {
union sockaddr_union sa = PLYMOUTH_SOCKET;
int r;
if (!plymouth_running())
return 0;
/* try to connect or reconnect if sending a message */
if (m->plymouth_fd >= 0)
return 1;
m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (m->plymouth_fd < 0)
return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
r = log_warning_errno(errno, "Couldn't connect to plymouth: %m");
goto fail;
}
r = sd_event_add_io(m->event, &m->plymouth_event_source, m->plymouth_fd, EPOLLIN, manager_plymouth_feedback_handler, m);
if (r < 0) {
log_warning_errno(r, "Can't listen to plymouth socket: %m");
goto fail;
}
return 1;
fail:
manager_disconnect_plymouth(m);
return r;
}
static int plymouth_send_message(int plymouth_fd, const char *message, bool update) {
_cleanup_free_ char *packet = NULL;
int n;
char mode = 'M';
if (update)
mode = 'U';
if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0)
return log_oom();
return loop_write(plymouth_fd, packet, n + 1, true);
}
static int manager_send_plymouth_message(Manager *m, const char *message) {
const char *plymouth_cancel_message = NULL, *l10n_cancel_message = NULL;
int r;
r = manager_connect_plymouth(m);
if (r < 0)
return r;
/* 0 means that plymouth isn't running, do not send any message yet */
else if (r == 0)
return 0;
if (!m->plymouth_cancel_sent) {
/* Indicate to plymouth that we listen to Ctrl+C */
r = loop_write(m->plymouth_fd, PLYMOUTH_REQUEST_KEY, sizeof(PLYMOUTH_REQUEST_KEY), true);
if (r < 0)
return log_warning_errno(r, "Can't send to plymouth cancel key: %m");
m->plymouth_cancel_sent = true;
l10n_cancel_message = _("Press Ctrl+C to cancel all filesystem checks in progress");
plymouth_cancel_message = strjoina("fsckd-cancel-msg:", l10n_cancel_message);
r = plymouth_send_message(m->plymouth_fd, plymouth_cancel_message, false);
if (r < 0)
log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m");
} else if (m->numdevices == 0) {
m->plymouth_cancel_sent = false;
r = plymouth_send_message(m->plymouth_fd, "", false);
if (r < 0)
log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
}
r = plymouth_send_message(m->plymouth_fd, message, true);
if (r < 0)
return log_warning_errno(r, "Couldn't send \"%s\" to plymouth: %m", message);
return 0;
}
static int manager_update_global_progress(Manager *m) {
Client *current = NULL;
_cleanup_free_ char *console_message = NULL;
_cleanup_free_ char *fsck_message = NULL;
int current_numdevices = 0, r;
double current_percent = 100;
/* get the overall percentage */
LIST_FOREACH(clients, current, m->clients) {
current_numdevices++;
/* right now, we only keep the minimum % of all fsckd processes. We could in the future trying to be
linear, but max changes and corresponds to the pass. We have all the informations into fsckd
already if we can treat that in a smarter way. */
current_percent = MIN(current_percent, current->percent);
}
/* update if there is anything user-visible to update */
if (fabs(current_percent - m->percent) > 0.001 || current_numdevices != m->numdevices) {
m->numdevices = current_numdevices;
m->percent = current_percent;
if (asprintf(&console_message,
ngettext("Checking in progress on %d disk (%3.1f%% complete)",
"Checking in progress on %d disks (%3.1f%% complete)", m->numdevices),
m->numdevices, m->percent) < 0)
return -ENOMEM;
if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
return -ENOMEM;
r = manager_write_console(m, console_message);
if (r < 0)
return r;
/* try to connect to plymouth and send message */
r = manager_send_plymouth_message(m, fsck_message);
if (r < 0)
return r;
}
return 0;
}
static int client_progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
Client *client = userdata;
FsckProgress fsck_data;
size_t buflen;
Manager *m;
int r;
assert(client);
m = client->manager;
/* check first if we need to cancel this client */
if (m->cancel_requested)
client_request_cancel(client);
/* ensure we have enough data to read */
r = ioctl(fd, FIONREAD, &buflen);
if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
if (client->buflen != buflen)
client->buflen = buflen;
/* we got twice the same size from a bad behaving client, kick it off the list */
else {
log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
client_free(client);
manager_update_global_progress(m);
}
return 0;
}
/* read actual data */
r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
if (r == 0) {
log_debug("Fsck client connected to fd %d disconnected", client->fd);
client_free(client);
} else if (r > 0 && r != sizeof(FsckProgress))
log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
else if (r > 0 && r == sizeof(FsckProgress)) {
client->devnum = fsck_data.devnum;
client->cur = fsck_data.cur;
client->max = fsck_data.max;
client->pass = fsck_data.pass;
client->percent = compute_percent(client->pass, client->cur, client->max);
log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
major(client->devnum), minor(client->devnum),
client->cur, client->max, client->pass, client->percent);
} else
log_error_errno(r, "Unknown error while trying to read fsck data: %m");
manager_update_global_progress(m);
return 0;
}
static int manager_new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_(client_freep) Client *c = NULL;
_cleanup_close_ int new_client_fd = -1;
Manager *m = userdata;
int r;
assert(m);
/* Initialize and list new clients */
new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
if (new_client_fd < 0)
return log_error_errno(errno, "Couldn't accept a new connection: %m");
if (m->n_clients >= CLIENTS_MAX) {
log_error("Too many clients, refusing connection.");
return 0;
}
log_debug("New fsck client connected to fd: %d", new_client_fd);
c = new0(Client, 1);
if (!c) {
log_oom();
return 0;
}
c->fd = new_client_fd;
new_client_fd = -1;
r = sd_event_add_io(m->event, &c->event_source, c->fd, EPOLLIN, client_progress_handler, c);
if (r < 0) {
log_oom();
return 0;
}
LIST_PREPEND(clients, m->clients, c);
m->n_clients++;
c->manager = m;
/* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
if (m->cancel_requested)
client_request_cancel(c);
c = NULL;
return 0;
}
static void manager_free(Manager *m) {
if (!m)
return;
/* clear last line */
manager_write_console(m, NULL);
sd_event_source_unref(m->connection_event_source);
safe_close(m->connection_fd);
while (m->clients)
client_free(m->clients);
manager_disconnect_plymouth(m);
sd_event_unref(m->event);
free(m);
}
static int manager_new(Manager **ret, int fd) {
_cleanup_(manager_freep) Manager *m = NULL;
int r;
assert(ret);
m = new0(Manager, 1);
if (!m)
return -ENOMEM;
m->plymouth_fd = -1;
m->connection_fd = fd;
m->percent = 100;
r = sd_event_default(&m->event);
if (r < 0)
return r;
if (access("/run/systemd/show-status", F_OK) >= 0)
m->show_status_console = true;
r = sd_event_add_io(m->event, &m->connection_event_source, fd, EPOLLIN, manager_new_connection_handler, m);
if (r < 0)
return r;
*ret = m;
m = NULL;
return 0;
}
static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
int r, code;
assert(e);
for (;;) {
r = sd_event_get_state(e);
if (r < 0)
return r;
if (r == SD_EVENT_FINISHED)
break;
r = sd_event_run(e, timeout);
if (r < 0)
return r;
/* timeout reached */
if (r == 0) {
sd_event_exit(e, 0);
break;
}
}
r = sd_event_get_exit_code(e, &code);
if (r < 0)
return r;
return code;
}
static void help(void) {
printf("%s [OPTIONS...]\n\n"
"Capture fsck progress and forward one stream to plymouth\n\n"
" -h --help Show this help\n"
" --version Show package version\n",
program_invocation_short_name);
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_ROOT,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{}
};
int c;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
switch (c) {
case 'h':
help();
return 0;
case ARG_VERSION:
puts(PACKAGE_STRING);
puts(SYSTEMD_FEATURES);
return 0;
case '?':
return -EINVAL;
default:
assert_not_reached("Unhandled option");
}
if (optind < argc) {
log_error("Extraneous arguments");
return -EINVAL;
}
return 1;
}
int main(int argc, char *argv[]) {
_cleanup_(manager_freep) Manager *m = NULL;
int fd = -1;
int r, n;
log_set_target(LOG_TARGET_AUTO);
log_parse_environment();
log_open();
init_gettext();
r = parse_argv(argc, argv);
if (r <= 0)
goto finish;
n = sd_listen_fds(0);
if (n > 1) {
log_error("Too many file descriptors received.");
r = -EINVAL;
goto finish;
} else if (n == 1)
fd = SD_LISTEN_FDS_START + 0;
else {
fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
if (fd < 0) {
r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
goto finish;
}
}
r = manager_new(&m, fd);
if (r < 0) {
log_error_errno(r, "Failed to allocate manager: %m");
goto finish;
}
r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
if (r < 0) {
log_error_errno(r, "Failed to run event loop: %m");
goto finish;
}
sd_event_get_exit_code(m->event, &r);
finish:
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

View File

@ -1,38 +0,0 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2015 Canonical
Author:
Didier Roche <didrocks@ubuntu.com>
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#define FSCKD_SOCKET_PATH "/run/systemd/fsckd"
#include "libudev.h"
typedef struct FsckProgress {
dev_t devnum;
size_t cur;
size_t max;
int pass;
} FsckProgress;
typedef struct FsckdMessage {
uint8_t cancel;
} FsckdMessage;

1
units/.gitignore vendored
View File

@ -28,7 +28,6 @@
/systemd-firstboot.service
/systemd-fsck-root.service
/systemd-fsck@.service
/systemd-fsckd.service
/systemd-machine-id-commit.service
/systemd-halt.service
/systemd-hibernate.service

View File

@ -9,9 +9,7 @@
Description=File System Check on Root Device
Documentation=man:systemd-fsck-root.service(8)
DefaultDependencies=no
Wants=systemd-fsckd.socket
Before=local-fs.target shutdown.target
After=systemd-fsckd.socket
ConditionPathIsReadWrite=!/
[Service]

View File

@ -10,8 +10,7 @@ Description=File System Check on %f
Documentation=man:systemd-fsck@.service(8)
DefaultDependencies=no
BindsTo=%i.device
Wants=systemd-fsckd.socket
After=%i.device systemd-fsck-root.service local-fs-pre.target systemd-fsckd.socket
After=%i.device systemd-fsck-root.service local-fs-pre.target
Before=shutdown.target
[Service]

View File

@ -1,17 +0,0 @@
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=File System Check Daemon to report status
Documentation=man:systemd-fsckd.service(8)
DefaultDependencies=no
Requires=systemd-fsckd.socket
Before=shutdown.target
[Service]
ExecStart=@rootlibexecdir@/systemd-fsckd
StandardOutput=journal+console

View File

@ -1,15 +0,0 @@
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=fsck to fsckd communication Socket
Documentation=man:systemd-fsckd.service(8) man:systemd-fsck@.service(8) man:systemd-fsck-root.service(8)
DefaultDependencies=no
[Socket]
ListenStream=/run/systemd/fsckd
SocketMode=0600