diff --git a/TODO b/TODO index a9902ddff2d..4a6c8d3512d 100644 --- a/TODO +++ b/TODO @@ -44,6 +44,9 @@ Features: * cryptsetup: allow encoding key directly in /etc/crypttab, maybe with a "base64:" prefix. Useful in particular for pkcs11 mode. +* cryptsetup: reimplement the mkswap/mke2fs in cryptsetup-generator to use + systemd-makefs.service instead. + * socket units: allow creating a udev monitor socket with ListenDevices= or so, with matches, then actviate app thorugh that passing socket oveer diff --git a/man/systemd.special.xml b/man/systemd.special.xml index c839af1842c..f393283a1d5 100644 --- a/man/systemd.special.xml +++ b/man/systemd.special.xml @@ -26,6 +26,7 @@ cryptsetup-pre.target, cryptsetup.target, ctrl-alt-del.target, + blockdev@.target, boot-complete.target, default.target, emergency.target, @@ -845,6 +846,23 @@ not useful as only unit within a transaction. + + blockdev@.target + This template unit may be used to order mount units and other consumers of block + devices against services that synthesize these block devices. This is intended to be used to order + storage services (such as + systemd-cryptsetup@.service5) + that allocate and manage a virtual block device against mount units and other consumers of + it. Specifically, the storage services are supposed to be orderd before an instance of + blockdev@.target, and the mount unit (or other consuming unit, such as a swap + unit) after it. The ordering is particular relevant during shutdown, as it ensures that the mount + is deactivated first and the service backing the mount only deactivated after that completed. The + blockdev@.target instance should be pulled in via a + dependency of the storage daemon and thus generally not be part of any transaction unless a storage + daemon is used. The instance name for instances of this template unit is supposed to be the + properly escaped bock device node path, e.g. blockdev@dev-mapper-foobar.target + for a storage device /dev/mapper/foobar. + cryptsetup-pre.target diff --git a/src/core/mount.c b/src/core/mount.c index 668c4d7e895..1c4aefd734f 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -217,7 +217,7 @@ static void mount_done(Unit *u) { m->timer_event_source = sd_event_source_unref(m->timer_event_source); } -_pure_ static MountParameters* get_mount_parameters_fragment(Mount *m) { +static MountParameters* get_mount_parameters_fragment(Mount *m) { assert(m); if (m->from_fragment) @@ -226,7 +226,7 @@ _pure_ static MountParameters* get_mount_parameters_fragment(Mount *m) { return NULL; } -_pure_ static MountParameters* get_mount_parameters(Mount *m) { +static MountParameters* get_mount_parameters(Mount *m) { assert(m); if (m->from_proc_self_mountinfo) @@ -342,20 +342,18 @@ static int mount_add_device_dependencies(Mount *m) { if (!is_device_path(p->what)) return 0; - /* /dev/root is a really weird thing, it's not a real device, - * but just a path the kernel exports for the root file system - * specified on the kernel command line. Ignore it here. */ - if (path_equal(p->what, "/dev/root")) + /* /dev/root is a really weird thing, it's not a real device, but just a path the kernel exports for + * the root file system specified on the kernel command line. Ignore it here. */ + if (PATH_IN_SET(p->what, "/dev/root", "/dev/nfs")) return 0; if (path_equal(m->where, "/")) return 0; - /* Mount units from /proc/self/mountinfo are not bound to devices - * by default since they're subject to races when devices are - * unplugged. But the user can still force this dep with an - * appropriate option (or udev property) so the mount units are - * automatically stopped when the device disappears suddenly. */ + /* Mount units from /proc/self/mountinfo are not bound to devices by default since they're subject to + * races when devices are unplugged. But the user can still force this dep with an appropriate option + * (or udev property) so the mount units are automatically stopped when the device disappears + * suddenly. */ dep = mount_is_bound_to_device(m) ? UNIT_BINDS_TO : UNIT_REQUIRES; /* We always use 'what' from /proc/self/mountinfo if mounted */ @@ -365,7 +363,7 @@ static int mount_add_device_dependencies(Mount *m) { if (r < 0) return r; - return 0; + return unit_add_blockdev_dependency(UNIT(m), p->what, mask); } static int mount_add_quota_dependencies(Mount *m) { diff --git a/src/core/swap.c b/src/core/swap.c index 473b3483ae7..fd2fa557dbf 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -184,21 +184,45 @@ static int swap_arm_timer(Swap *s, usec_t usec) { return 0; } +static SwapParameters* swap_get_parameters(Swap *s) { + assert(s); + + if (s->from_proc_swaps) + return &s->parameters_proc_swaps; + + if (s->from_fragment) + return &s->parameters_fragment; + + return NULL; +} + static int swap_add_device_dependencies(Swap *s) { + UnitDependencyMask mask; + SwapParameters *p; + int r; + assert(s); if (!s->what) return 0; - if (!s->from_fragment) + p = swap_get_parameters(s); + if (!p) return 0; - if (is_device_path(s->what)) - return unit_add_node_dependency(UNIT(s), s->what, UNIT_BINDS_TO, UNIT_DEPENDENCY_FILE); + mask = s->from_proc_swaps ? UNIT_DEPENDENCY_PROC_SWAP : UNIT_DEPENDENCY_FILE; - /* File based swap devices need to be ordered after systemd-remount-fs.service, - * since they might need a writable file system. */ - return unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_REMOUNT_FS_SERVICE, true, UNIT_DEPENDENCY_FILE); + if (is_device_path(p->what)) { + r = unit_add_node_dependency(UNIT(s), p->what, UNIT_REQUIRES, mask); + if (r < 0) + return r; + + return unit_add_blockdev_dependency(UNIT(s), p->what, mask); + } + + /* File based swap devices need to be ordered after systemd-remount-fs.service, since they might need + * a writable file system. */ + return unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_REMOUNT_FS_SERVICE, true, mask); } static int swap_add_default_dependencies(Swap *s) { diff --git a/src/core/unit.c b/src/core/unit.c index 6b97c35ffa1..3f616e7acf1 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -3866,8 +3866,8 @@ int unit_deserialize_skip(FILE *f) { } int unit_add_node_dependency(Unit *u, const char *what, UnitDependency dep, UnitDependencyMask mask) { - Unit *device; _cleanup_free_ char *e = NULL; + Unit *device; int r; assert(u); @@ -3879,8 +3879,7 @@ int unit_add_node_dependency(Unit *u, const char *what, UnitDependency dep, Unit if (!is_device_path(what)) return 0; - /* When device units aren't supported (such as in a - * container), don't create dependencies on them. */ + /* When device units aren't supported (such as in a container), don't create dependencies on them. */ if (!unit_type_supported(UNIT_DEVICE)) return 0; @@ -3900,6 +3899,33 @@ int unit_add_node_dependency(Unit *u, const char *what, UnitDependency dep, Unit device, true, mask); } +int unit_add_blockdev_dependency(Unit *u, const char *what, UnitDependencyMask mask) { + _cleanup_free_ char *escaped = NULL, *target = NULL; + int r; + + assert(u); + + if (isempty(what)) + return 0; + + if (!path_startswith(what, "/dev/")) + return 0; + + /* If we don't support devices, then also don't bother with blockdev@.target */ + if (!unit_type_supported(UNIT_DEVICE)) + return 0; + + r = unit_name_path_escape(what, &escaped); + if (r < 0) + return r; + + r = unit_name_build("blockdev", escaped, ".target", &target); + if (r < 0) + return r; + + return unit_add_dependency_by_name(u, UNIT_AFTER, target, true, mask); +} + int unit_coldplug(Unit *u) { int r = 0, q; char **i; diff --git a/src/core/unit.h b/src/core/unit.h index 68de900b0d5..38d681dfb86 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -743,6 +743,7 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds); int unit_deserialize_skip(FILE *f); int unit_add_node_dependency(Unit *u, const char *what, UnitDependency d, UnitDependencyMask mask); +int unit_add_blockdev_dependency(Unit *u, const char *what, UnitDependencyMask mask); int unit_coldplug(Unit *u); void unit_catchup(Unit *u); diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c index b87abe9ece0..1deab765fbb 100644 --- a/src/cryptsetup/cryptsetup-generator.c +++ b/src/cryptsetup/cryptsetup-generator.c @@ -99,7 +99,14 @@ static int split_keyspec(const char *keyspec, char **ret_keyfile, char **ret_key return 0; } -static int generate_keydev_mount(const char *name, const char *keydev, const char *keydev_timeout, bool canfail, char **unit, char **mount) { +static int generate_keydev_mount( + const char *name, + const char *keydev, + const char *keydev_timeout, + bool canfail, + char **unit, + char **mount) { + _cleanup_free_ char *u = NULL, *where = NULL, *name_escaped = NULL, *device_unit = NULL; _cleanup_fclose_ FILE *f = NULL; int r; @@ -223,8 +230,8 @@ static int create_disk( const char *options) { _cleanup_free_ char *n = NULL, *d = NULL, *u = NULL, *e = NULL, - *keydev_mount = NULL, *keyfile_timeout_value = NULL, *password_escaped = NULL, - *filtered = NULL, *u_escaped = NULL, *filtered_escaped = NULL, *name_escaped = NULL, *header_path = NULL; + *keydev_mount = NULL, *keyfile_timeout_value = NULL, + *filtered = NULL, *u_escaped = NULL, *name_escaped = NULL, *header_path = NULL, *password_buffer = NULL; _cleanup_fclose_ FILE *f = NULL; const char *dmname; bool noauto, nofail, tmp, swap, netdev, attach_in_initrd; @@ -285,39 +292,29 @@ static int create_disk( if (r < 0) return r; - 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" - "SourcePath=%s\n" - "DefaultDependencies=no\n" - "IgnoreOnIsolate=true\n" - "After=%s\n", - arg_crypttab, - netdev ? "remote-fs-pre.target" : "cryptsetup-pre.target"); + r = generator_write_cryptsetup_unit_section(f, arg_crypttab); + if (r < 0) + return r; + + if (netdev) + fprintf(f, "After=remote-fs-pre.target\n"); /* If initrd takes care of attaching the disk then it should also detach it during shutdown. */ if (!attach_in_initrd) fprintf(f, "Conflicts=umount.target\n"); - if (password) { - password_escaped = specifier_escape(password); - if (!password_escaped) - return log_oom(); - } - if (keydev) { - _cleanup_free_ char *unit = NULL, *p = NULL; + _cleanup_free_ char *unit = NULL; r = generate_keydev_mount(name, keydev, keyfile_timeout_value, keyfile_can_timeout > 0, &unit, &keydev_mount); if (r < 0) return log_error_errno(r, "Failed to generate keydev mount unit: %m"); - p = path_join(keydev_mount, password_escaped); - if (!p) + password_buffer = path_join(keydev_mount, password); + if (!password_buffer) return log_oom(); - free_and_replace(password_escaped, p); + password = password_buffer; fprintf(f, "After=%s\n", unit); if (keyfile_can_timeout > 0) @@ -344,17 +341,13 @@ static int create_disk( return r; } - if (path_startswith(u, "/dev/")) { + if (path_startswith(u, "/dev/")) fprintf(f, "BindsTo=%s\n" "After=%s\n" "Before=umount.target\n", d, d); - - if (swap) - fputs("Before=dev-mapper-%i.swap\n", - f); - } else + else /* For loopback devices, add systemd-tmpfiles-setup-dev.service dependency to ensure that loopback support is available in the kernel (/dev/loop-control needs to exist) */ @@ -368,23 +361,9 @@ static int create_disk( if (r < 0) log_warning_errno(r, "Failed to write device timeout drop-in: %m"); - if (filtered) { - filtered_escaped = specifier_escape(filtered); - if (!filtered_escaped) - return log_oom(); - } - - fprintf(f, - "\n[Service]\n" - "Type=oneshot\n" - "RemainAfterExit=yes\n" - "TimeoutSec=0\n" /* the binary handles timeouts anyway */ - "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 */ - "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n" - "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n", - name_escaped, u_escaped, strempty(password_escaped), strempty(filtered_escaped), - name_escaped); + r = generator_write_cryptsetup_service_section(f, name, u, password, filtered); + if (r < 0) + return r; if (tmp) fprintf(f, diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index e0f1076326f..5a0a871759e 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -118,11 +118,18 @@ static int add_swap( fprintf(f, "[Unit]\n" - "SourcePath=%s\n" - "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n\n" - "[Swap]\n", + "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n" + "SourcePath=%s\n", fstab_path()); + r = generator_write_blockdev_dependency(f, what); + if (r < 0) + return r; + + fprintf(f, + "\n" + "[Swap]\n"); + r = write_what(f, what); if (r < 0) return r; @@ -174,8 +181,13 @@ static bool mount_in_initrd(struct mntent *me) { streq(me->mnt_dir, "/usr"); } -static int write_timeout(FILE *f, const char *where, const char *opts, - const char *filter, const char *variable) { +static int write_timeout( + FILE *f, + const char *where, + const char *opts, + const char *filter, + const char *variable) { + _cleanup_free_ char *timeout = NULL; char timespan[FORMAT_TIMESPAN_MAX]; usec_t u; @@ -208,8 +220,12 @@ static int write_mount_timeout(FILE *f, const char *where, const char *opts) { "x-systemd.mount-timeout\0", "TimeoutSec"); } -static int write_dependency(FILE *f, const char *opts, - const char *filter, const char *format) { +static int write_dependency( + FILE *f, + const char *opts, + const char *filter, + const char *format) { + _cleanup_strv_free_ char **names = NULL, **units = NULL; _cleanup_free_ char *res = NULL; char **s; @@ -230,6 +246,7 @@ static int write_dependency(FILE *f, const char *opts, r = unit_name_mangle_with_suffix(*s, "as dependency", 0, ".mount", &x); if (r < 0) return log_error_errno(r, "Failed to generate unit name: %m"); + r = strv_consume(&units, x); if (r < 0) return log_oom(); @@ -249,7 +266,8 @@ static int write_dependency(FILE *f, const char *opts, } static int write_after(FILE *f, const char *opts) { - return write_dependency(f, opts, "x-systemd.after", "After=%1$s\n"); + return write_dependency(f, opts, + "x-systemd.after", "After=%1$s\n"); } static int write_requires_after(FILE *f, const char *opts) { @@ -363,8 +381,8 @@ static int add_mount( fprintf(f, "[Unit]\n" - "SourcePath=%s\n" - "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n", + "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n" + "SourcePath=%s\n", source); /* All mounts under /sysroot need to happen later, at initrd-fs.target time. IOW, it's not @@ -411,7 +429,14 @@ static int add_mount( return r; } - fprintf(f, "\n[Mount]\n"); + r = generator_write_blockdev_dependency(f, what); + if (r < 0) + return r; + + fprintf(f, + "\n" + "[Mount]\n"); + if (original_where) fprintf(f, "# Canonicalized from %s\n", original_where); diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index e03cdbd5c02..2067eeaf89a 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -105,9 +105,8 @@ static int open_parent_block_device(dev_t devnum, int *ret_fd) { } static int add_cryptsetup(const char *id, const char *what, bool rw, bool require, char **device) { - _cleanup_free_ char *e = NULL, *n = NULL, *d = NULL, *id_escaped = NULL, *what_escaped = NULL; + _cleanup_free_ char *e = NULL, *n = NULL, *d = NULL; _cleanup_fclose_ FILE *f = NULL; - const char *p; int r; assert(id); @@ -125,44 +124,28 @@ static int add_cryptsetup(const char *id, const char *what, bool rw, bool requir if (r < 0) return log_error_errno(r, "Failed to generate unit name: %m"); - id_escaped = specifier_escape(id); - if (!id_escaped) - return log_oom(); + r = generator_open_unit_file(arg_dest, NULL, n, &f); + if (r < 0) + return r; - what_escaped = specifier_escape(what); - if (!what_escaped) - return log_oom(); - - p = prefix_roota(arg_dest, n); - f = fopen(p, "wxe"); - if (!f) - return log_error_errno(errno, "Failed to create unit file %s: %m", p); + r = generator_write_cryptsetup_unit_section(f, NULL); + if (r < 0) + return r; fprintf(f, - "# Automatically generated by systemd-gpt-auto-generator\n\n" - "[Unit]\n" - "Description=Cryptography Setup for %%I\n" - "Documentation=man:systemd-gpt-auto-generator(8) man:systemd-cryptsetup@.service(8)\n" - "DefaultDependencies=no\n" - "Conflicts=umount.target\n" - "BindsTo=dev-mapper-%%i.device %s\n" "Before=umount.target cryptsetup.target\n" - "After=%s\n" - "IgnoreOnIsolate=true\n" - "[Service]\n" - "Type=oneshot\n" - "RemainAfterExit=yes\n" - "TimeoutSec=0\n" /* the binary handles timeouts anyway */ - "KeyringMode=shared\n" /* make sure we can share cached keys among instances */ - "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '' '%s'\n" - "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n", - d, d, - id_escaped, what_escaped, rw ? "" : "read-only", - id_escaped); + "Conflicts=umount.target\n" + "BindsTo=%s\n" + "After=%s\n", + d, d); + + r = generator_write_cryptsetup_service_section(f, id, what, NULL, rw ? NULL : "read-only"); + if (r < 0) + return r; r = fflush_and_check(f); if (r < 0) - return log_error_errno(r, "Failed to write file %s: %m", p); + return log_error_errno(r, "Failed to write file %s: %m", n); r = generator_add_symlink(arg_dest, d, "wants", n); if (r < 0) @@ -227,7 +210,6 @@ static int add_mount( log_debug("Adding %s: %s fstype=%s", where, what, fstype ?: "(any)"); if (streq_ptr(fstype, "crypto_LUKS")) { - r = add_cryptsetup(id, what, rw, true, &crypto_what); if (r < 0) return r; @@ -262,6 +244,10 @@ static int add_mount( if (r < 0) return r; + r = generator_write_blockdev_dependency(f, what); + if (r < 0) + return r; + fprintf(f, "\n" "[Mount]\n" @@ -370,7 +356,14 @@ static int add_swap(const char *path) { "# Automatically generated by systemd-gpt-auto-generator\n\n" "[Unit]\n" "Description=Swap Partition\n" - "Documentation=man:systemd-gpt-auto-generator(8)\n\n" + "Documentation=man:systemd-gpt-auto-generator(8)\n"); + + r = generator_write_blockdev_dependency(f, path); + if (r < 0) + return r; + + fprintf(f, + "\n" "[Swap]\n" "What=%s\n", path); diff --git a/src/shared/generator.c b/src/shared/generator.c index 1cf5887a60d..acdd0096f14 100644 --- a/src/shared/generator.c +++ b/src/shared/generator.c @@ -519,6 +519,103 @@ int generator_enable_remount_fs_service(const char *dir) { SYSTEM_DATA_UNIT_PATH "/" 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, + "DefaultDependencies=no\n" + "IgnoreOnIsolate=true\n" + "After=cryptsetup-pre.target\n" + "Before=blockdev@dev-mapper-%%i.target\n" + "Wants=blockdev@dev-mapper-%%i.target\n"); + + return 0; +} + +int generator_write_cryptsetup_service_section( + FILE *f, + const char *name, + const char *what, + const char *password, + const char *options) { + + _cleanup_free_ char *name_escaped = NULL, *what_escaped = NULL, *password_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 (password) { + password_escaped = specifier_escape(password); + if (!password_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=0\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 */ + "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n" + "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n", + name_escaped, what_escaped, strempty(password_escaped), strempty(options_escaped), + name_escaped); + + return 0; +} + void log_setup_generator(void) { log_set_prohibit_ipc(true); log_setup_service(); diff --git a/src/shared/generator.h b/src/shared/generator.h index fa002a91143..579e291fe8a 100644 --- a/src/shared/generator.h +++ b/src/shared/generator.h @@ -27,6 +27,21 @@ int generator_write_timeouts( const char *opts, char **filtered); +int generator_write_blockdev_dependency( + FILE *f, + const char *what); + +int generator_write_cryptsetup_unit_section( + FILE *f, + const char *source); + +int generator_write_cryptsetup_service_section( + FILE *f, + const char *name, + const char *what, + const char *password, + const char *options); + int generator_write_device_deps( const char *dir, const char *what, diff --git a/units/blockdev@.target b/units/blockdev@.target new file mode 100644 index 00000000000..22a9a5bb2bf --- /dev/null +++ b/units/blockdev@.target @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: LGPL-2.1+ +# +# 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=Block Device Preparation for %f +Documentation=man:systemd.special(7) +StopWhenUnneeded=yes diff --git a/units/meson.build b/units/meson.build index 0dc85ac64a3..f3145a1099e 100644 --- a/units/meson.build +++ b/units/meson.build @@ -2,6 +2,7 @@ units = [ ['basic.target', ''], + ['blockdev@.target', ''], ['bluetooth.target', ''], ['boot-complete.target', ''], ['cryptsetup-pre.target', 'HAVE_LIBCRYPTSETUP'],