Files
systemd/src/shared/generator.c
Lennart Poettering f872373a26 generators: hook in validatefs on gpt-auto and fstab generator mounts
Let's turn on validatefs automatically for all auto-discovered
partitions.

Let's add an x-systemd.validatefs option to optionally turn this on for
fstab listed file systems.
2025-03-31 15:14:28 +02:00

1101 lines
38 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <errno.h>
#include <unistd.h>
#include "alloc-util.h"
#include "cgroup-util.h"
#include "dropin.h"
#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
#include "fstab-util.h"
#include "generator.h"
#include "initrd-util.h"
#include "log.h"
#include "macro.h"
#include "mkdir-label.h"
#include "mountpoint-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
#include "special.h"
#include "specifier.h"
#include "string-util.h"
#include "time-util.h"
#include "tmpfile-util.h"
#include "unit-name.h"
int generator_open_unit_file_full(
const char *dir,
const char *source,
const char *fn,
FILE **ret_file,
char **ret_final_path,
char **ret_temp_path) {
_cleanup_free_ char *p = NULL;
FILE *f;
int r;
assert(dir);
assert(ret_file);
/* If <ret_temp_path> is specified, it creates a temporary unit file and also returns its
* temporary path. */
if (ret_temp_path) {
r = fopen_temporary(dir, &f, &p);
if (r < 0)
return log_error_errno(r, "Failed to create temporary unit file in '%s': %m", dir);
(void) fchmod(fileno(f), 0644);
*ret_temp_path = TAKE_PTR(p);
} else {
assert(fn);
p = path_join(dir, fn);
if (!p)
return log_oom();
r = fopen_unlocked(p, "wxe", &f);
if (r < 0) {
if (source && r == -EEXIST)
return log_error_errno(r,
"Failed to create unit file '%s', as it already exists. Duplicate entry in '%s'?",
p, source);
return log_error_errno(r, "Failed to create unit file '%s': %m", p);
}
}
fprintf(f,
"# Automatically generated by %s\n\n",
program_invocation_short_name);
*ret_file = f;
if (ret_final_path)
*ret_final_path = TAKE_PTR(p);
return 0;
}
int generator_add_symlink_full(
const char *dir,
const char *dst,
const char *dep_type,
const char *src,
const char *instance) {
_cleanup_free_ char *dn = NULL, *fn = NULL, *instantiated = NULL, *to = NULL, *from = NULL;
int r;
assert(dir);
assert(dst);
assert(src);
/* If 'dep_type' is specified adds a symlink from <dst>.<dep_type>/ to <src> (if src is absolute) or ../<src> (otherwise).
*
* If 'dep_type' is NULL, it will create a symlink to <src> (i.e. create an alias.
*
* If <instance> is specified, then <src> must be a template unit name, and we'll instantiate it. */
r = path_extract_directory(src, &dn);
if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → just a file name was passed */
return log_error_errno(r, "Failed to extract directory name from '%s': %m", src);
r = path_extract_filename(src, &fn);
if (r < 0)
return log_error_errno(r, "Failed to extract file name from '%s': %m", src);
if (r == O_DIRECTORY)
return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Expected path to regular file name, but got '%s', refusing.", src);
if (instance) {
r = unit_name_replace_instance(fn, instance, &instantiated);
if (r < 0)
return log_error_errno(r, "Failed to instantiate '%s' for '%s': %m", fn, instance);
}
if (dep_type) { /* Create a .wants/ style dep */
from = path_join(dn ?: "..", fn);
if (!from)
return log_oom();
to = strjoin(dir, "/", dst, ".", dep_type, "/", instantiated ?: fn);
} else { /* or create an alias */
from = dn ? path_join(dn, fn) : strdup(fn);
if (!from)
return log_oom();
to = strjoin(dir, "/", dst);
}
if (!to)
return log_oom();
(void) mkdir_parents_label(to, 0755);
if (symlink(from, to) < 0 && errno != EEXIST)
return log_error_errno(errno, "Failed to create symlink \"%s\": %m", to);
return 0;
}
static int generator_add_ordering(
const char *dir,
const char *src,
const char *order,
const char *dst,
const char *instance) {
_cleanup_free_ char *instantiated = NULL, *p = NULL, *fn = NULL;
_cleanup_fclose_ FILE *f = NULL;
const char *to;
int r;
assert(dir);
assert(src);
assert(order);
assert(dst);
/* Adds in an explicit ordering dependency of type <order> from <src> to <dst>. If <instance> is
* specified, it is inserted into <dst>. */
if (instance) {
r = unit_name_replace_instance(dst, instance, &instantiated);
if (r < 0)
return log_error_errno(r, "Failed to instantiate '%s' for '%s': %m", dst, instance);
to = instantiated;
} else
to = dst;
fn = strjoin(src, ".d/50-order-", to, ".conf");
if (!fn)
return log_oom();
p = path_join(dir, fn);
if (!p)
return log_oom();
(void) mkdir_parents_label(p, 0755);
r = fopen_unlocked(p, "wxe", &f);
if (r < 0)
return log_error_errno(r, "Failed to create '%s': %m", p);
fprintf(f,
"# Automatically generated by %s\n\n"
"[Unit]\n"
"%s=%s\n",
program_invocation_short_name,
order,
to);
r = fflush_and_check(f);
if (r < 0)
return log_error_errno(r, "Failed to write drop-in '%s': %m", p);
return 0;
}
static int write_fsck_sysroot_service(
const char *unit, /* Either SPECIAL_FSCK_ROOT_SERVICE or SPECIAL_FSCK_USR_SERVICE */
const char *dir,
const char *what,
const char *extra_after) {
_cleanup_free_ char *device = NULL, *escaped = NULL, *escaped2 = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
assert(unit);
assert(dir);
assert(what);
/* Writes out special versions of systemd-fsck-root.service and systemd-fsck-usr.service for use in
* the initrd. The regular statically shipped versions of these unit files use / and /usr for as
* paths, which doesn't match what we need for the initrd (where the dirs are /sysroot +
* /sysusr/usr), hence we overwrite those versions here. */
escaped = specifier_escape(what);
if (!escaped)
return log_oom();
escaped2 = cescape(escaped);
if (!escaped2)
return log_oom();
r = unit_name_from_path(what, ".device", &device);
if (r < 0)
return log_error_errno(r, "Failed to convert device \"%s\" to unit name: %m", what);
r = generator_open_unit_file(dir, /* source = */ NULL, unit, &f);
if (r < 0)
return r;
fprintf(f,
"[Unit]\n"
"Description=File System Check on %1$s\n"
"Documentation=man:%2$s(8)\n"
"\n"
"DefaultDependencies=no\n"
"BindsTo=%3$s\n"
"Conflicts=shutdown.target\n"
"After=%4$s%5$slocal-fs-pre.target %3$s\n"
"Before=shutdown.target\n"
"\n"
"[Service]\n"
"Type=oneshot\n"
"RemainAfterExit=yes\n"
"ExecStart=" SYSTEMD_FSCK_PATH " %6$s\n"
"TimeoutSec=infinity\n",
escaped,
unit,
device,
strempty(extra_after),
isempty(extra_after) ? "" : " ",
escaped2);
r = fflush_and_check(f);
if (r < 0)
return log_error_errno(r, "Failed to write unit %s: %m", unit);
return 0;
}
int generator_write_fsck_deps(
FILE *f,
const char *dir,
const char *what,
const char *where,
const char *fstype) {
int r;
assert(f);
assert(dir);
assert(what);
assert(where);
/* Let's do an early exit if we are invoked for the root and /usr/ trees in the initrd, to avoid
* generating confusing log messages */
if (in_initrd() && PATH_IN_SET(where, "/", "/usr")) {
log_debug("Skipping fsck for %s in initrd.", where);
return 0;
}
if (fstype) {
if (!fstype_is_blockdev_backed(fstype)) {
log_debug("Skipping file system check for non-block based file system '%s'.", what);
return 0;
}
if (fstype_is_ro(fstype)) {
log_debug("Skipping file system check for read-only file system '%s'.", what);
return 0;
}
}
if (!is_device_path(what)) {
log_warning("Checking was requested for \"%s\", but it is not a device.", what);
return 0;
}
if (!isempty(fstype) && !streq(fstype, "auto")) {
r = fsck_exists_for_fstype(fstype);
if (r < 0)
log_warning_errno(r, "Checking was requested for %s, but couldn't detect if fsck.%s may be used, proceeding: %m", what, fstype);
else if (r == 0) {
/* treat missing check as essentially OK */
log_debug("Checking was requested for %s, but fsck.%s does not exist.", what, fstype);
return 0;
}
} else {
r = fsck_exists();
if (r < 0)
log_warning_errno(r, "Checking was requested for %s, but couldn't detect if the fsck command may be used, proceeding: %m", what);
else if (r == 0) {
/* treat missing fsck as essentially OK */
log_debug("Checking was requested for %s, but the fsck command does not exist.", what);
return 0;
}
}
if (path_equal(where, "/")) {
const char *lnk;
/* We support running the fsck instance for the root fs while it is already mounted, for
* compatibility with non-initrd boots. It's ugly, but it is how it is. Since unlike for
* regular file systems this means the ordering is reversed (i.e. mount *before* fsck) we
* have a separate fsck unit for this, independent of systemd-fsck@.service. */
lnk = strjoina(dir, "/" SPECIAL_LOCAL_FS_TARGET ".wants/" SPECIAL_FSCK_ROOT_SERVICE);
(void) mkdir_parents(lnk, 0755);
if (symlink(SYSTEM_DATA_UNIT_DIR "/" SPECIAL_FSCK_ROOT_SERVICE, lnk) < 0)
return log_error_errno(errno, "Failed to create symlink %s: %m", lnk);
} else {
_cleanup_free_ char *_fsck = NULL;
const char *fsck, *dep;
if (in_initrd() && path_equal(where, "/sysroot")) {
r = write_fsck_sysroot_service(SPECIAL_FSCK_ROOT_SERVICE, dir, what, SPECIAL_INITRD_ROOT_DEVICE_TARGET);
if (r < 0)
return r;
fsck = SPECIAL_FSCK_ROOT_SERVICE;
dep = "Requires";
} else if (in_initrd() && path_equal(where, "/sysusr/usr")) {
r = write_fsck_sysroot_service(SPECIAL_FSCK_USR_SERVICE, dir, what, NULL);
if (r < 0)
return r;
fsck = SPECIAL_FSCK_USR_SERVICE;
dep = "Requires";
} else {
/* When this is /usr, then let's add a Wants= dependency, otherwise a Requires=
* dependency. Why? We can't possibly unmount /usr during shutdown, but if we have a
* Requires= from /usr onto a fsck@.service unit and that unit is shut down, then
* we'd have to unmount /usr too. */
dep = path_equal(where, "/usr") ? "Wants" : "Requires";
r = unit_name_from_path_instance("systemd-fsck", what, ".service", &_fsck);
if (r < 0)
return log_error_errno(r, "Failed to create fsck service name: %m");
fsck = _fsck;
}
fprintf(f,
"%1$s=%2$s\n"
"After=%2$s\n",
dep, fsck);
}
return 0;
}
int generator_write_device_timeout(
const char *dir,
const char *what,
const char *opts,
char **filtered) {
/* Configure how long we wait for a device that backs a mount point or a
* swap partition to show up. This is useful to support endless device timeouts
* for devices that show up only after user input, like crypto devices. */
_cleanup_free_ char *node = NULL, *unit = NULL, *timeout = NULL;
usec_t u;
int r;
assert(dir);
assert(what);
r = fstab_filter_options(opts, "comment=systemd.device-timeout\0"
"x-systemd.device-timeout\0",
NULL, &timeout, NULL, filtered);
if (r < 0) {
log_warning_errno(r, "Failed to parse fstab options, ignoring: %m");
return 0;
}
if (r == 0)
return 0;
r = parse_sec_fix_0(timeout, &u);
if (r < 0) {
log_warning("Failed to parse timeout for device '%s', ignoring: %s", what, timeout);
return 0;
}
node = fstab_node_to_udev_node(what);
if (!node)
return log_oom();
if (!is_device_path(node)) {
log_warning("'%s' is not a device path, ignoring x-systemd.device-timeout= option.", node);
return 0;
}
r = unit_name_from_path(node, ".device", &unit);
if (r < 0)
return log_error_errno(r, "Failed to make unit name from device path '%s': %m", node);
r = write_drop_in_format(dir, unit, 50, "device-timeout",
"# Automatically generated by %s\n"
"# from supplied options \"%s\"\n\n"
"[Unit]\n"
"JobRunningTimeoutSec=%s",
program_invocation_short_name,
opts,
timeout);
if (r < 0)
return r;
return 1;
}
int generator_write_unit_timeout(
FILE *f,
const char *where,
const char *opts,
const char *filter,
const char *unit_setting) {
_cleanup_free_ char *timeout = NULL;
usec_t u;
int r;
assert(f);
assert(where);
assert(filter);
assert(unit_setting);
r = fstab_filter_options(opts, filter, NULL, &timeout, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to parse options for '%s': %m", where);
if (r == 0)
return 0;
r = parse_sec_fix_0(timeout, &u);
if (r < 0) {
log_warning_errno(r, "Failed to parse timeout '%s' for '%s', ignoring: %m", timeout, where);
return 0;
}
fprintf(f, "%s=%s\n", unit_setting, FORMAT_TIMESPAN(u, 0));
return 0;
}
int generator_write_network_device_deps(
const char *dir,
const char *what,
const char *where,
const char *opts) {
/* fstab records that specify _netdev option should apply the network
* ordering on the actual device depending on network connection. If we
* are not mounting real device (NFS, CIFS), we rely on _netdev effect
* on the mount unit itself. */
_cleanup_free_ char *node = NULL, *unit = NULL;
int r;
assert(dir);
assert(what);
assert(where);
if (fstab_is_extrinsic(where, opts))
return 0;
if (!fstab_test_option(opts, "_netdev\0"))
return 0;
node = fstab_node_to_udev_node(what);
if (!node)
return log_oom();
/* Nothing to apply dependencies to. */
if (!is_device_path(node))
return 0;
r = unit_name_from_path(node, ".device", &unit);
if (r < 0)
return log_error_errno(r, "Failed to make unit name from path \"%s\": %m", node);
/* See mount_add_default_dependencies for explanation why we create such
* dependencies. */
return write_drop_in_format(dir, unit, 50, "netdev-dependencies",
"# Automatically generated by %s\n\n"
"[Unit]\n"
"After=" SPECIAL_NETWORK_ONLINE_TARGET " " SPECIAL_NETWORK_TARGET "\n"
"Wants=" SPECIAL_NETWORK_ONLINE_TARGET "\n",
program_invocation_short_name);
}
int generator_write_initrd_root_device_deps(const char *dir, const char *what) {
_cleanup_free_ char *unit = NULL;
int r;
r = unit_name_from_path(what, ".device", &unit);
if (r < 0)
return log_error_errno(r, "Failed to make unit name from path \"%s\": %m",
what);
return write_drop_in_format(dir, SPECIAL_INITRD_ROOT_DEVICE_TARGET, 50, "root-device",
"# Automatically generated by %s\n\n"
"[Unit]\n"
"Requires=%s\n"
"After=%s",
program_invocation_short_name,
unit,
unit);
}
int generator_hook_up_mkswap(
const char *dir,
const char *what) {
_cleanup_free_ char *node = NULL, *unit = NULL, *escaped = NULL, *where_unit = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
assert(dir);
assert(what);
node = fstab_node_to_udev_node(what);
if (!node)
return log_oom();
/* Nothing to work on. */
if (!is_device_path(node))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Cannot format something that is not a device node: %s",
node);
r = unit_name_from_path_instance("systemd-mkswap", node, ".service", &unit);
if (r < 0)
return log_error_errno(r, "Failed to make unit instance name from path \"%s\": %m",
node);
escaped = cescape(node);
if (!escaped)
return log_oom();
r = unit_name_from_path(what, ".swap", &where_unit);
if (r < 0)
return log_error_errno(r, "Failed to make unit name from path \"%s\": %m",
what);
r = generator_open_unit_file(dir, /* source = */ NULL, unit, &f);
if (r < 0)
return r;
fprintf(f,
"[Unit]\n"
"Description=Make Swap on %%f\n"
"Documentation=man:systemd-mkswap@.service(8)\n"
"\n"
"DefaultDependencies=no\n"
"BindsTo=%%i.device\n"
"After=%%i.device\n"
"Before=%s\n"
"Conflicts=shutdown.target\n"
"Before=shutdown.target\n"
"\n"
"[Service]\n"
"Type=oneshot\n"
"RemainAfterExit=yes\n"
"ExecStart="SYSTEMD_MAKEFS_PATH " swap %s\n"
"TimeoutSec=infinity\n",
where_unit,
escaped);
r = fflush_and_check(f);
if (r < 0)
return log_error_errno(r, "Failed to write unit %s: %m", unit);
return generator_add_symlink(dir, where_unit, "requires", unit);
}
int generator_hook_up_mkfs(
const char *dir,
const char *what,
const char *where,
const char *type) {
_cleanup_free_ char *node = NULL, *unit = NULL, *escaped = NULL, *where_unit = NULL;
_cleanup_fclose_ FILE *f = NULL;
const char *fsck_unit;
int r;
assert(dir);
assert(what);
assert(where);
node = fstab_node_to_udev_node(what);
if (!node)
return log_oom();
/* Nothing to work on. */
if (!is_device_path(node))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Cannot format something that is not a device node: %s",
node);
if (!type || streq(type, "auto"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Cannot format partition %s, filesystem type is not specified",
node);
r = unit_name_from_path_instance("systemd-makefs", node, ".service", &unit);
if (r < 0)
return log_error_errno(r, "Failed to make unit instance name from path \"%s\": %m",
node);
if (in_initrd() && path_equal(where, "/sysroot"))
fsck_unit = SPECIAL_FSCK_ROOT_SERVICE;
else if (in_initrd() && path_equal(where, "/sysusr/usr"))
fsck_unit = SPECIAL_FSCK_USR_SERVICE;
else
fsck_unit = "systemd-fsck@%i.service";
escaped = cescape(node);
if (!escaped)
return log_oom();
r = unit_name_from_path(where, ".mount", &where_unit);
if (r < 0)
return log_error_errno(r, "Failed to make unit name from path \"%s\": %m",
where);
r = generator_open_unit_file(dir, /* source = */ NULL, unit, &f);
if (r < 0)
return r;
fprintf(f,
"[Unit]\n"
"Description=Make File System on %%f\n"
"Documentation=man:systemd-makefs@.service(8)\n"
"\n"
"DefaultDependencies=no\n"
"BindsTo=%%i.device\n"
"After=%%i.device\n"
/* fsck might or might not be used, so let's be safe and order
* ourselves before both systemd-fsck@.service and the mount unit. */
"Before=%s %s\n"
"Conflicts=shutdown.target\n"
"Before=shutdown.target\n"
"\n"
"[Service]\n"
"Type=oneshot\n"
"RemainAfterExit=yes\n"
"ExecStart="SYSTEMD_MAKEFS_PATH " %s %s\n"
"TimeoutSec=infinity\n",
fsck_unit,
where_unit,
type,
escaped);
// XXX: what about local-fs-pre.target?
r = fflush_and_check(f);
if (r < 0)
return log_error_errno(r, "Failed to write unit %s: %m", unit);
return generator_add_symlink(dir, where_unit, "requires", unit);
}
int generator_hook_up_growfs(
const char *dir,
const char *where,
const char *target) {
const char *growfs_unit, *growfs_unit_path;
_cleanup_free_ char *where_unit = NULL, *instance = NULL;
int r;
assert(dir);
assert(where);
r = unit_name_from_path(where, ".mount", &where_unit);
if (r < 0)
return log_error_errno(r, "Failed to make unit name from path '%s': %m", where);
if (empty_or_root(where)) {
growfs_unit = SPECIAL_GROWFS_ROOT_SERVICE;
growfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_GROWFS_ROOT_SERVICE;
} else {
growfs_unit = SPECIAL_GROWFS_SERVICE;
growfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_GROWFS_SERVICE;
r = unit_name_path_escape(where, &instance);
if (r < 0)
return log_error_errno(r, "Failed to escape path '%s': %m", where);
}
if (target) {
r = generator_add_ordering(dir, target, "After", growfs_unit, instance);
if (r < 0)
return r;
}
return generator_add_symlink_full(dir, where_unit, "wants", growfs_unit_path, instance);
}
int generator_hook_up_pcrfs(
const char *dir,
const char *where,
const char *target) {
const char *pcrfs_unit, *pcrfs_unit_path;
_cleanup_free_ char *where_unit = NULL, *instance = NULL;
int r;
assert(dir);
assert(where);
r = unit_name_from_path(where, ".mount", &where_unit);
if (r < 0)
return log_error_errno(r, "Failed to make unit name from path '%s': %m", where);
if (empty_or_root(where)) {
pcrfs_unit = SPECIAL_PCRFS_ROOT_SERVICE;
pcrfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_PCRFS_ROOT_SERVICE;
} else {
pcrfs_unit = SPECIAL_PCRFS_SERVICE;
pcrfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_PCRFS_SERVICE;
r = unit_name_path_escape(where, &instance);
if (r < 0)
return log_error_errno(r, "Failed to escape path '%s': %m", where);
}
if (target) {
r = generator_add_ordering(dir, target, "After", pcrfs_unit, instance);
if (r < 0)
return r;
}
return generator_add_symlink_full(dir, where_unit, "wants", pcrfs_unit_path, instance);
}
int generator_hook_up_validatefs(
const char *dir,
const char *where,
const char *target) {
_cleanup_free_ char *where_unit = NULL, *instance = NULL;
const char *validatefs_unit, *validatefs_unit_path;
int r;
assert(dir);
assert(where);
/* never hook this in for the actual root fs, because it's too late then, we already are running from
* the root fs, it makes no sense to validate it anymore */
if (empty_or_root(where))
return 0;
r = unit_name_from_path(where, ".mount", &where_unit);
if (r < 0)
return log_error_errno(r, "Failed to make unit name from path '%s': %m", where);
validatefs_unit = SPECIAL_VALIDATEFS_SERVICE;
validatefs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_VALIDATEFS_SERVICE;
r = unit_name_path_escape(where, &instance);
if (r < 0)
return log_error_errno(r, "Failed to escape path '%s': %m", where);
if (target) {
r = generator_add_ordering(dir, target, "After", validatefs_unit, instance);
if (r < 0)
return r;
}
return generator_add_symlink_full(dir, where_unit, "wants", validatefs_unit_path, instance);
}
int generator_hook_up_quotacheck(
const char *dir,
const char *what,
const char *where,
const char *target,
const char *fstype) {
_cleanup_free_ char *where_unit = NULL, *instance = NULL;
int r;
assert(dir);
assert(where);
if (isempty(fstype) || streq(fstype, "auto"))
return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Couldn't determine filesystem type for %s, quota cannot be activated", what);
if (!fstype_needs_quota(fstype))
return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Quota was requested for %s, but not supported, ignoring: %s", what, fstype);
/* quotacheck unit for system root */
if (path_equal(where, "/"))
return generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_ROOT_SERVICE);
r = unit_name_path_escape(where, &instance);
if (r < 0)
return log_error_errno(r, "Failed to escape path '%s': %m", where);
if (target) {
r = generator_add_ordering(dir, target, "After", SPECIAL_QUOTACHECK_SERVICE, instance);
if (r < 0)
return r;
}
r = unit_name_from_path(where, ".mount", &where_unit);
if (r < 0)
return log_error_errno(r, "Failed to make unit name from path '%s': %m", where);
return generator_add_symlink_full(dir, where_unit, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_SERVICE, instance);
}
int generator_hook_up_quotaon(
const char *dir,
const char *where,
const char *target) {
_cleanup_free_ char *where_unit = NULL, *instance = NULL;
int r;
assert(dir);
assert(where);
/* quotaon unit for system root is not instantiated */
if (path_equal(where, "/"))
return generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTAON_ROOT_SERVICE);
r = unit_name_path_escape(where, &instance);
if (r < 0)
return log_error_errno(r, "Failed to escape path '%s': %m", where);
if (target) {
r = generator_add_ordering(dir, target, "After", SPECIAL_QUOTAON_SERVICE, instance);
if (r < 0)
return r;
}
r = unit_name_from_path(where, ".mount", &where_unit);
if (r < 0)
return log_error_errno(r, "Failed to make unit name from path '%s': %m", where);
return generator_add_symlink_full(dir, where_unit, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTAON_SERVICE, instance);
}
int generator_enable_remount_fs_service(const char *dir) {
/* Pull in systemd-remount-fs.service */
return generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants",
SYSTEM_DATA_UNIT_DIR "/" SPECIAL_REMOUNT_FS_SERVICE);
}
int generator_write_blockdev_dependency(FILE *f, const char *what) {
_cleanup_free_ char *escaped = NULL;
int r;
assert(f);
assert(what);
if (!path_startswith(what, "/dev/"))
return 0;
r = unit_name_path_escape(what, &escaped);
if (r < 0)
return log_error_errno(r, "Failed to escape device node path %s: %m", what);
fprintf(f,
"After=blockdev@%s.target\n",
escaped);
return 0;
}
int generator_write_cryptsetup_unit_section(FILE *f, const char *source) {
assert(f);
fprintf(f,
"[Unit]\n"
"Description=Cryptography Setup for %%I\n"
"Documentation=man:crypttab(5) man:systemd-cryptsetup-generator(8) man:systemd-cryptsetup@.service(8)\n");
if (source)
fprintf(f, "SourcePath=%s\n", source);
fprintf(f,
"\n"
"DefaultDependencies=no\n"
"After=cryptsetup-pre.target systemd-udevd-kernel.socket systemd-tpm2-setup-early.service\n"
"Before=blockdev@dev-mapper-%%i.target\n"
"Wants=blockdev@dev-mapper-%%i.target\n"
"IgnoreOnIsolate=true\n");
return 0;
}
int generator_write_cryptsetup_service_section(
FILE *f,
const char *name,
const char *what,
const char *key_file,
const char *options) {
_cleanup_free_ char *name_escaped = NULL, *what_escaped = NULL, *key_file_escaped = NULL, *options_escaped = NULL;
assert(f);
assert(name);
assert(what);
name_escaped = specifier_escape(name);
if (!name_escaped)
return log_oom();
what_escaped = specifier_escape(what);
if (!what_escaped)
return log_oom();
if (key_file) {
key_file_escaped = specifier_escape(key_file);
if (!key_file_escaped)
return log_oom();
}
if (options) {
options_escaped = specifier_escape(options);
if (!options_escaped)
return log_oom();
}
fprintf(f,
"\n"
"[Service]\n"
"Type=oneshot\n"
"RemainAfterExit=yes\n"
"TimeoutSec=infinity\n" /* The binary handles timeouts on its own */
"KeyringMode=shared\n" /* Make sure we can share cached keys among instances */
"OOMScoreAdjust=500\n" /* Unlocking can allocate a lot of memory if Argon2 is used */
"ImportCredential=cryptsetup.*\n"
"ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n"
"ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n",
name_escaped, what_escaped, strempty(key_file_escaped), strempty(options_escaped),
name_escaped);
return 0;
}
int generator_write_veritysetup_unit_section(FILE *f, const char *source) {
assert(f);
fprintf(f,
"[Unit]\n"
"Description=Integrity Protection Setup for %%I\n"
"Documentation=man:veritytab(5) man:systemd-veritysetup-generator(8) man:systemd-veritysetup@.service(8)\n");
if (source)
fprintf(f, "SourcePath=%s\n", source);
fprintf(f,
"DefaultDependencies=no\n"
"IgnoreOnIsolate=true\n"
"After=veritysetup-pre.target systemd-udevd-kernel.socket\n"
"Before=blockdev@dev-mapper-%%i.target\n"
"Wants=blockdev@dev-mapper-%%i.target\n");
return 0;
}
int generator_write_veritysetup_service_section(
FILE *f,
const char *name,
const char *data_what,
const char *hash_what,
const char *roothash,
const char *options) {
_cleanup_free_ char *name_escaped = NULL, *data_what_escaped = NULL, *hash_what_escaped = NULL,
*roothash_escaped = NULL, *options_escaped = NULL;
assert(f);
assert(name);
assert(data_what);
assert(hash_what);
name_escaped = specifier_escape(name);
if (!name_escaped)
return log_oom();
data_what_escaped = specifier_escape(data_what);
if (!data_what_escaped)
return log_oom();
hash_what_escaped = specifier_escape(hash_what);
if (!hash_what_escaped)
return log_oom();
roothash_escaped = specifier_escape(roothash);
if (!roothash_escaped)
return log_oom();
if (options) {
options_escaped = specifier_escape(options);
if (!options_escaped)
return log_oom();
}
fprintf(f,
"\n"
"[Service]\n"
"Type=oneshot\n"
"RemainAfterExit=yes\n"
"ExecStart=" SYSTEMD_VERITYSETUP_PATH " attach '%s' '%s' '%s' '%s' '%s'\n"
"ExecStop=" SYSTEMD_VERITYSETUP_PATH " detach '%s'\n",
name_escaped, data_what_escaped, hash_what_escaped, roothash_escaped, strempty(options_escaped),
name_escaped);
return 0;
}
void log_setup_generator(void) {
if (invoked_by_systemd()) {
/* Disable talking to syslog/journal (i.e. the two IPC-based loggers) if we run in system context. */
if (cg_pid_get_owner_uid(0, NULL) == -ENXIO /* not running in a per-user slice */)
log_set_prohibit_ipc(true);
/* This effectively means: journal for per-user generators, kmsg otherwise */
log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
} else
log_set_target(LOG_TARGET_AUTO);
log_parse_environment();
log_open();
}
bool generator_soft_rebooted(void) {
static int cached = -1;
int r;
if (cached >= 0)
return cached;
const char *e = secure_getenv("SYSTEMD_SOFT_REBOOTS_COUNT");
if (!e)
return (cached = false);
unsigned u;
r = safe_atou(e, &u);
if (r < 0) {
log_debug_errno(r, "Failed to parse $SYSTEMD_SOFT_REBOOTS_COUNT, assuming the system hasn't soft-rebooted: %m");
return (cached = false);
}
return (cached = (u > 0));
}
GptAutoRoot parse_gpt_auto_root(const char *value) {
assert(value);
/* Parses the 'gpt-auto'/'gpt-auto-root' parameters to root= */
if (streq(value, "gpt-auto")) {
log_debug("Enabling root partition auto-detection (respecting factory reset mode), root= is explicitly set to 'gpt-auto'.");
return GPT_AUTO_ROOT_ON;
}
if (streq(value, "gpt-auto-force")) {
log_debug("Enabling root partition auto-detection (ignoring factory reset mode), root= is explicitly set to 'gpt-auto-force'.");
return GPT_AUTO_ROOT_FORCE;
}
log_debug("Disabling root partition auto-detection, root= is neither unset, nor set to 'gpt-auto' or 'gpt-auto-force'.");
return GPT_AUTO_ROOT_OFF;
}