]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
storage: add systemd-storage-block@.service provider
authorLennart Poettering <lennart@amutable.com>
Thu, 23 Apr 2026 07:00:06 +0000 (09:00 +0200)
committerLennart Poettering <lennart@amutable.com>
Wed, 29 Apr 2026 10:25:47 +0000 (12:25 +0200)
First implementation of io.systemd.StorageProvider, exposing all block
devices known to udev (disks, partitions, dm nodes, …) as volumes of
type "blk". Names are picked from stable /dev/mapper and /dev/disk/by-*
symlinks; content-derived identifiers (by-uuid, by-label, …) are
intentionally avoided for security. Volume creation is not supported by
this backend.

Socket-activated via /run/systemd/io.systemd.StorageProvider/block.
Also adds shared storage-util.[ch] (VolumeType / CreateMode helpers)
that subsequent providers reuse.

man/rules/meson.build
man/systemd-storage-block@.service.xml [new file with mode: 0644]
meson.build
src/storage/io.systemd.storage.policy [new file with mode: 0644]
src/storage/meson.build [new file with mode: 0644]
src/storage/storage-block.c [new file with mode: 0644]
src/storage/storage-util.c [new file with mode: 0644]
src/storage/storage-util.h [new file with mode: 0644]
units/meson.build
units/systemd-storage-block.socket [new file with mode: 0644]
units/systemd-storage-block@.service.in [new file with mode: 0644]

index 4aae5615129917158482db9a1381c219cfdd518e..439c33d5abdc19cef92b1d284579ec1fe403ec89 100644 (file)
@@ -1186,6 +1186,10 @@ manpages = [
  ['systemd-ssh-issue', '1', [], ''],
  ['systemd-ssh-proxy', '1', [], ''],
  ['systemd-stdio-bridge', '1', [], ''],
+ ['systemd-storage-block@.service',
+  '8',
+  ['systemd-storage-block', 'systemd-storage-block.socket'],
+  ''],
  ['systemd-storagetm.service', '8', ['systemd-storagetm'], 'ENABLE_STORAGETM'],
  ['systemd-stub',
   '7',
diff --git a/man/systemd-storage-block@.service.xml b/man/systemd-storage-block@.service.xml
new file mode 100644 (file)
index 0000000..ee6022a
--- /dev/null
@@ -0,0 +1,97 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+  "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+
+<refentry id="systemd-storage-block_.service"
+          xmlns:xi="http://www.w3.org/2001/XInclude">
+
+  <refentryinfo>
+    <title>systemd-storage-block@.service</title>
+    <productname>systemd</productname>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>systemd-storage-block@.service</refentrytitle>
+    <manvolnum>8</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>systemd-storage-block@.service</refname>
+    <refname>systemd-storage-block.socket</refname>
+    <refname>systemd-storage-block</refname>
+    <refpurpose>Storage provider exposing local block devices as storage volumes</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <para><filename>systemd-storage-block@.service</filename></para>
+    <para><filename>systemd-storage-block.socket</filename></para>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para><filename>systemd-storage-block@.service</filename> is a system service that implements the
+    <constant>io.systemd.StorageProvider</constant> <ulink url="https://varlink.org/">Varlink</ulink>
+    interface, exposing the system's block devices (such as disks, partitions, and device-mapper
+    nodes) as storage volumes that may be acquired by other programs as file descriptors.</para>
+
+    <para>The service is socket-activated via <filename>systemd-storage-block.socket</filename>, which
+    listens on the AF_UNIX socket <filename>/run/systemd/io.systemd.StorageProvider/block</filename>. The
+    socket directory <filename>/run/systemd/io.systemd.StorageProvider/</filename> is the well-known location
+    where storage providers register, see
+    <citerefentry><refentrytitle>storagectl</refentrytitle><manvolnum>1</manvolnum></citerefentry> for an
+    enumeration tool.</para>
+
+    <para>See also
+    <citerefentry><refentrytitle>systemd-storage-fs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+    for a complementary implementation that exposes regular files and directories from a backing file
+    system.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>Volumes</title>
+
+    <para>The volumes exposed via the provider are identified by an absolute path (which must begin with
+    <filename>/dev/</filename>), i.e. as a kernel block device node such as <filename>/dev/sda</filename> or
+    <filename>/dev/disk/by-id/…</filename>. Volume names that are not normalized or that do not begin with
+    <filename>/dev/</filename> are not accepted.</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>Files</title>
+
+    <variablelist>
+      <varlistentry>
+        <term><filename>/run/systemd/io.systemd.StorageProvider/block</filename></term>
+
+        <listitem><para>AF_UNIX socket the service listens on. This is the canonical location
+        for the <literal>block</literal> storage provider, and is enumerated by
+        <command>storagectl providers</command>.</para>
+
+        <xi:include href="version-info.xml" xpointer="v261"/></listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para><simplelist type="inline">
+      <member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+      <member><citerefentry><refentrytitle>storagectl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+      <member><citerefentry><refentrytitle>systemd-storage-fs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
+    </simplelist></para>
+  </refsect1>
+
+</refentry>
index 4f1a791bc7651a3afe06b72861bacf12d090c367..325b954a78b24fb49dc8d5406f409db745f4f1c6 100644 (file)
@@ -2139,6 +2139,7 @@ subdir('src/socket-activate')
 subdir('src/socket-proxy')
 subdir('src/ssh-generator')
 subdir('src/stdio-bridge')
+subdir('src/storage')
 subdir('src/storagetm')
 subdir('src/sulogin-shell')
 subdir('src/sysctl')
diff --git a/src/storage/io.systemd.storage.policy b/src/storage/io.systemd.storage.policy
new file mode 100644 (file)
index 0000000..06af278
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
+<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+        "https://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+
+<!--
+  SPDX-License-Identifier: LGPL-2.1-or-later
+
+  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.
+-->
+
+<policyconfig>
+
+        <vendor>The systemd Project</vendor>
+        <vendor_url>https://systemd.io</vendor_url>
+
+        <action id="io.systemd.storage.block.acquire">
+                <description gettext-domain="systemd">Allow access to block storage volumes</description>
+                <message gettext-domain="systemd">Authentication is required for an application to gain access to block storage volume '$(name)'.</message>
+                <defaults>
+                        <allow_any>auth_admin</allow_any>
+                        <allow_inactive>auth_admin</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+</policyconfig>
diff --git a/src/storage/meson.build b/src/storage/meson.build
new file mode 100644 (file)
index 0000000..714e50a
--- /dev/null
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+executables += [
+        libexec_template + {
+                'name' : 'systemd-storage-block',
+                'sources' : files('storage-block.c', 'storage-util.c'),
+        },
+]
+
+install_data('io.systemd.storage.policy',
+             install_dir : polkitpolicydir)
diff --git a/src/storage/storage-block.c b/src/storage/storage-block.c
new file mode 100644 (file)
index 0000000..4c21795
--- /dev/null
@@ -0,0 +1,439 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fnmatch.h>
+
+#include "sd-device.h"
+#include "sd-json.h"
+#include "sd-varlink.h"
+
+#include "blockdev-list.h"
+#include "build.h"
+#include "bus-polkit.h"
+#include "device-private.h"
+#include "device-util.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "format-table.h"
+#include "hashmap.h"
+#include "help-util.h"
+#include "json-util.h"
+#include "log.h"
+#include "main-func.h"
+#include "options.h"
+#include "path-util.h"
+#include "storage-util.h"
+#include "strv.h"
+#include "varlink-io.systemd.StorageProvider.h"
+#include "varlink-util.h"
+
+static int block_device_pick_name(
+                const BlockDevice *d,
+                const char **ret_name,
+                char ***ret_aliases) {
+
+        int r;
+
+        assert(d);
+        assert(d->node);
+        assert(ret_name);
+        assert(ret_aliases);
+
+        static const char *const prefixes[] = {
+                /* The list of preferred prefixes, in order of preference. Note: for security reasons we only
+                 * use identifiers that do not depend on the *contents* of the device, i.e. we restrict
+                 * ourselves to IDs whose fields are either chosen by whoever created the kernel device or are
+                 * hardware properties, but not names generated from superblock metainformation or similar. */
+                "/dev/mapper",
+                "/dev/disk/by-loop-ref",
+                "/dev/disk/by-id",
+                "/dev/disk/by-path",
+        };
+
+        const char* found[ELEMENTSOF(prefixes)] = {};
+        _cleanup_strv_free_ char **aliases = NULL;
+        size_t best = SIZE_MAX;
+        STRV_FOREACH(sl, d->symlinks) {
+                bool matched = false;
+                for (size_t i = 0; i < ELEMENTSOF(prefixes); i++) {
+                        if (!path_startswith(*sl, prefixes[i]))
+                                continue;
+
+                        if (found[i]) {
+                                /* Two symlinks with the same prefix? Then keep the lower one. */
+                                if (path_compare(*sl, found[i]) > 0)
+                                        continue;
+
+                                r = strv_extend(&aliases, found[i]);
+                                if (r < 0)
+                                        return r;
+                        }
+
+                        found[i] = *sl;
+                        if (i < best)
+                                best = i;
+                        matched = true;
+                }
+
+                if (!matched) {
+                        r = strv_extend(&aliases, *sl);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        if (best == SIZE_MAX) /* No preferred prefix found, use the kernel device name */
+                *ret_name = d->node;
+        else {
+                /* We found a preferred prefix, add the kernel device name to the aliases then. */
+                r = strv_extend(&aliases, d->node);
+                if (r < 0)
+                        return r;
+
+                /* If there are any less preferred prefixes also add them to the aliases array */
+                for (size_t i = best + 1; i < ELEMENTSOF(prefixes); i++) {
+                        if (!found[i])
+                                continue;
+
+                        r = strv_extend(&aliases, found[i]);
+                        if (r < 0)
+                                return r;
+                }
+
+                *ret_name = found[best];
+        }
+
+        strv_sort(aliases);
+        *ret_aliases = TAKE_PTR(aliases);
+
+        return 0;
+}
+
+static bool block_device_match(const BlockDevice *d, const char *match) {
+        assert(d);
+        assert(d->node);
+
+        if (!match)
+                return true;
+
+        if (fnmatch(match, d->node, FNM_NOESCAPE) == 0)
+                return true;
+
+        STRV_FOREACH(sl, d->symlinks)
+                if (fnmatch(match, *sl, FNM_NOESCAPE) == 0)
+                        return true;
+
+        return false;
+}
+
+static int vl_method_list_volumes(
+                sd_varlink *link,
+                sd_json_variant *parameters,
+                sd_varlink_method_flags_t flags,
+                void *userdata) {
+
+        int r;
+
+        assert(link);
+        assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE));
+
+        struct {
+                const char *match_name;
+        } p = {};
+
+        static const sd_json_dispatch_field dispatch_table[] = {
+                { "matchName", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, match_name), 0 },
+                {}
+        };
+
+        r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
+        if (r != 0)
+                return r;
+
+        BlockDevice *l = NULL;
+        size_t n = 0;
+        CLEANUP_ARRAY(l, n, block_device_array_free);
+
+        r = blockdev_list(
+                        BLOCKDEV_LIST_SHOW_SYMLINKS|
+                        BLOCKDEV_LIST_IGNORE_ROOT|
+                        BLOCKDEV_LIST_IGNORE_EMPTY|
+                        BLOCKDEV_LIST_METADATA,
+                        &l,
+                        &n);
+        if (r < 0)
+                return r;
+
+        r = sd_varlink_set_sentinel(link, "io.systemd.StorageProvider.NoSuchVolume");
+        if (r < 0)
+                return r;
+
+        FOREACH_ARRAY(d, l, n) {
+                const char *name = NULL;
+                _cleanup_strv_free_ char **aliases = NULL;
+
+                if (!block_device_match(d, p.match_name))
+                        continue;
+
+                r = block_device_pick_name(d, &name, &aliases);
+                if (r < 0)
+                        return r;
+
+                r = sd_varlink_replybo(
+                                link,
+                                SD_JSON_BUILD_PAIR_STRING("name", name),
+                                JSON_BUILD_PAIR_STRV_NON_EMPTY("aliases", aliases),
+                                SD_JSON_BUILD_PAIR_STRING("type", "blk"),
+                                SD_JSON_BUILD_PAIR_CONDITION(d->read_only >= 0, "readOnly", SD_JSON_BUILD_BOOLEAN(d->read_only)),
+                                JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("sizeBytes", d->size, UINT64_MAX));
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+static int vl_method_list_templates(
+                sd_varlink *link,
+                sd_json_variant *parameters,
+                sd_varlink_method_flags_t flags,
+                void *userdata) {
+
+        /* This storage provider does not support templates */
+        assert(link);
+        assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE));
+
+        return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchTemplate", NULL);
+}
+
+static int device_open_disk_auto_rw(sd_device *d, int *read_only) {
+        assert(d);
+        assert(read_only);
+
+        int fd = sd_device_open(d, *read_only > 0 ? O_RDONLY : O_RDWR);
+        if (fd < 0) {
+                if (!ERRNO_IS_NEG_FS_WRITE_REFUSED(fd) || *read_only >= 0)
+                        return log_device_debug_errno(d, fd, "Failed to open device in %s mode: %m", *read_only > 0 ? "read-only" : "read-write");
+
+                /* Try again in read-only mode */
+                fd = sd_device_open(d, O_RDONLY);
+                if (fd < 0)
+                        return log_device_debug_errno(d, fd, "Failed to open device in read-only mode, too: %m");
+
+                *read_only = true;
+        } else
+                *read_only = *read_only > 0;
+
+        return fd;
+}
+
+static int vl_method_acquire(
+                sd_varlink *link,
+                sd_json_variant *parameters,
+                sd_varlink_method_flags_t flags,
+                void *userdata) {
+
+        Hashmap **polkit_registry = ASSERT_PTR(userdata);
+        int r;
+
+        assert(link);
+
+        struct {
+                const char *name;
+                CreateMode create_mode;
+                const char *template;
+                int read_only;
+                VolumeType request_as;
+                uint64_t create_size;
+        } p = {
+                .create_mode = CREATE_ANY,
+                .read_only = -1,
+                .request_as = _VOLUME_TYPE_INVALID,
+                .create_size = UINT64_MAX, /* never actually used here, just validated; we don't allow creation of block devices here */
+        };
+
+        static const sd_json_dispatch_field dispatch_table[] = {
+                { "name",            SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, voffsetof(p, name),        SD_JSON_MANDATORY },
+                { "createMode",      SD_JSON_VARIANT_STRING,        json_dispatch_create_mode,     voffsetof(p, create_mode), 0                 },
+                { "template",        SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, voffsetof(p, template),    0                 },
+                { "readOnly",        SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,     voffsetof(p, read_only),   0                 },
+                { "requestAs",       SD_JSON_VARIANT_STRING,        json_dispatch_volume_type,     voffsetof(p, request_as),  0                 },
+                { "createSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,       voffsetof(p, create_size), 0                 },
+                VARLINK_DISPATCH_POLKIT_FIELD,
+                {}
+        };
+
+        r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
+        if (r != 0)
+                return r;
+
+        if (!storage_volume_name_is_valid(p.name))
+                return sd_varlink_error_invalid_parameter_name(link, "name");
+        if (!path_startswith(p.name, "/dev") || !path_is_normalized(p.name))
+                return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchVolume", NULL);
+
+        if (!IN_SET(p.create_mode, CREATE_ANY, CREATE_OPEN))
+                return sd_varlink_error(link, "io.systemd.StorageProvider.CreateNotSupported", NULL);
+
+        /* off_t is signed, hence refuse overly long requests */
+        if (p.create_size != UINT64_MAX && p.create_size > INT64_MAX)
+                return sd_varlink_error_invalid_parameter_name(link, "createSizeBytes");
+
+        if (!isempty(p.template)) {
+                if (!storage_template_name_is_valid(p.template))
+                        return sd_varlink_error_invalid_parameter_name(link, "template");
+
+                return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchTemplate", NULL);
+        }
+
+        if (p.request_as >= 0 && p.request_as != VOLUME_BLK)
+                return sd_varlink_error(link, "io.systemd.StorageProvider.TypeNotSupported", NULL);
+
+        const char *details[] = {
+                "name", p.name,
+                NULL
+        };
+
+        r = varlink_verify_polkit_async(
+                        link,
+                        /* bus= */ NULL,
+                        "io.systemd.storage.block.acquire",
+                        details,
+                        polkit_registry);
+        if (r <= 0)
+                return r;
+
+        _cleanup_(sd_device_unrefp) sd_device *d = NULL;
+        r = sd_device_new_from_devname(&d, p.name);
+        if (ERRNO_IS_NEG_DEVICE_ABSENT(r))
+                return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchVolume", NULL);
+        if (r < 0)
+                return r;
+
+        if (!device_in_subsystem(d, "block"))
+                return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchVolume", NULL);
+
+        /* The error returns are sometimes a bit inconclusive (i.e. read-only media might appear as
+         * inaccessible due to a permission issue), hence let's do an explicit check first, to give good
+         * answers */
+        if (p.read_only <= 0) {
+                r = device_get_sysattr_bool(d, "ro");
+                if (r < 0)
+                        log_device_debug_errno(d, r, "Failed to acquire read-only flag of device '%s', ignoring: %m", p.name);
+                else if (r > 0) {
+                        if (p.read_only == 0)
+                                return sd_varlink_error(link, "io.systemd.StorageProvider.ReadOnlyVolume", NULL);
+
+                        p.read_only = true;
+                }
+        }
+
+        _cleanup_close_ int fd = device_open_disk_auto_rw(d, &p.read_only);
+        if (ERRNO_IS_NEG_FS_WRITE_REFUSED(fd))
+                return sd_varlink_error(link, "io.systemd.StorageProvider.ReadOnlyVolume", NULL);
+        if (fd < 0)
+                return fd;
+
+        assert(p.read_only >= 0); /* flag is now definitely initialized to either true or false, not negative anymore */
+
+        int idx = sd_varlink_push_fd(link, fd);
+        if (idx < 0)
+                return idx;
+
+        TAKE_FD(fd);
+
+        return sd_varlink_replybo(
+                        link,
+                        SD_JSON_BUILD_PAIR_INTEGER("fileDescriptorIndex", idx),
+                        SD_JSON_BUILD_PAIR_STRING("type", "blk"),
+                        SD_JSON_BUILD_PAIR_BOOLEAN("readOnly", p.read_only));
+}
+
+static int vl_server(void) {
+        int r;
+
+        _cleanup_(hashmap_freep) Hashmap *polkit_registry = NULL;
+        _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
+        r = varlink_server_new(
+                        &varlink_server,
+                        SD_VARLINK_SERVER_HANDLE_SIGINT|
+                        SD_VARLINK_SERVER_HANDLE_SIGTERM|
+                        SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT|
+                        SD_VARLINK_SERVER_INHERIT_USERDATA,
+                        &polkit_registry);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate Varlink server: %m");
+
+        r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_StorageProvider);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add Varlink interface: %m");
+
+        r = sd_varlink_server_bind_method_many(
+                        varlink_server,
+                        "io.systemd.StorageProvider.Acquire", vl_method_acquire,
+                        "io.systemd.StorageProvider.ListVolumes", vl_method_list_volumes,
+                        "io.systemd.StorageProvider.ListTemplates", vl_method_list_templates);
+        if (r < 0)
+                return log_error_errno(r, "Failed to bind Varlink methods: %m");
+
+        r = sd_varlink_server_loop_auto(varlink_server);
+        if (r < 0)
+                return log_error_errno(r, "Failed to run Varlink event loop: %m");
+
+        return 0;
+}
+
+static int help(void) {
+        int r;
+
+        help_cmdline("[OPTIONS...]");
+        help_abstract("Simple block device backed storage provider");
+
+        _cleanup_(table_unrefp) Table *options = NULL;
+        r = option_parser_get_help_table(&options);
+        if (r < 0)
+                return r;
+
+        help_section("Options:");
+
+        r = table_print_or_warn(options);
+        if (r < 0)
+                return r;
+
+        help_man_page_reference("systemd-storage-block", "8");
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+        assert(argc >= 0);
+        assert(argv);
+
+        OptionParser opts = { argc, argv };
+        FOREACH_OPTION(c, &opts, /* on_error= */ return c)
+                switch (c) {
+
+                OPTION_COMMON_HELP:
+                        return help();
+
+                OPTION_COMMON_VERSION:
+                        return version();
+                }
+
+        if (option_parser_get_n_args(&opts) > 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments.");
+
+        return 1;
+}
+
+static int run(int argc, char* argv[]) {
+        int r;
+
+        log_setup();
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r;
+
+        return vl_server();
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/storage/storage-util.c b/src/storage/storage-util.c
new file mode 100644 (file)
index 0000000..793946c
--- /dev/null
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "json-util.h"
+#include "string-table.h"
+#include "storage-util.h"
+
+static const char *volume_type_table[_VOLUME_TYPE_MAX] = {
+        [VOLUME_BLK] = "blk",
+        [VOLUME_REG] = "reg",
+        [VOLUME_DIR] = "dir",
+};
+
+static const char *create_mode_table[_CREATE_MODE_MAX] = {
+        [CREATE_ANY]  = "any",
+        [CREATE_NEW]  = "new",
+        [CREATE_OPEN] = "open",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(volume_type, VolumeType);
+DEFINE_STRING_TABLE_LOOKUP(create_mode, CreateMode);
+
+JSON_DISPATCH_ENUM_DEFINE(json_dispatch_volume_type, VolumeType, volume_type_from_string);
+JSON_DISPATCH_ENUM_DEFINE(json_dispatch_create_mode, CreateMode, create_mode_from_string);
diff --git a/src/storage/storage-util.h b/src/storage/storage-util.h
new file mode 100644 (file)
index 0000000..f7a62ae
--- /dev/null
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-json.h"
+
+#include "string-table-fundamental.h"
+#include "string-util.h"
+
+/* This closely follows the kernel's inode type naming, i.e. is supposed to be a subset of what
+ * inode_type_from_string() parses. */
+typedef enum VolumeType {
+        VOLUME_BLK,
+        VOLUME_REG,
+        VOLUME_DIR,
+        _VOLUME_TYPE_MAX,
+        _VOLUME_TYPE_INVALID = -EINVAL,
+} VolumeType;
+
+typedef enum CreateMode {
+        CREATE_ANY,
+        CREATE_NEW,
+        CREATE_OPEN,
+        _CREATE_MODE_MAX,
+        _CREATE_MODE_INVALID = -EINVAL,
+} CreateMode;
+
+DECLARE_STRING_TABLE_LOOKUP(volume_type, VolumeType);
+DECLARE_STRING_TABLE_LOOKUP(create_mode, CreateMode);
+
+int json_dispatch_volume_type(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
+int json_dispatch_create_mode(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
+
+static inline bool storage_volume_name_is_valid(const char *n) {
+        return string_is_safe(n, /* flags= */ 0);
+}
+
+static inline bool storage_template_name_is_valid(const char *n) {
+        return string_is_safe(n, /* flags= */ 0);
+}
+
+static inline bool storage_provider_name_is_valid(const char *n) {
+        return string_is_safe(n, STRING_FILENAME);
+}
index 622e1e69cf7c213345644ae0e0aebbd12f48f0a9..3cac3c876ae1c5714ce6f5151235f077f50f3d69 100644 (file)
@@ -804,6 +804,13 @@ units = [
           'conditions' : ['ENABLE_SYSUSERS'],
           'symlinks' : ['sysinit.target.wants/'],
         },
+        {
+          'file' : 'systemd-storage-block.socket',
+          'symlinks' : ['sockets.target.wants/']
+        },
+        {
+          'file' : 'systemd-storage-block@.service.in',
+        },
         {
           'file' : 'systemd-storagetm.service.in',
           'conditions' : ['ENABLE_STORAGETM'],
diff --git a/units/systemd-storage-block.socket b/units/systemd-storage-block.socket
new file mode 100644 (file)
index 0000000..1d18b48
--- /dev/null
@@ -0,0 +1,24 @@
+#  SPDX-License-Identifier: LGPL-2.1-or-later
+#
+#  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=Simple Block Device Backed Storage Provider
+Documentation=man:systemd-storage-block@..service(8)
+DefaultDependencies=no
+Before=sockets.target
+
+[Socket]
+ListenStream=/run/systemd/io.systemd.StorageProvider/block
+FileDescriptorName=varlink
+SocketMode=0666
+Accept=yes
+MaxConnectionsPerSource=16
+
+[Install]
+WantedBy=sockets.target
diff --git a/units/systemd-storage-block@.service.in b/units/systemd-storage-block@.service.in
new file mode 100644 (file)
index 0000000..801551e
--- /dev/null
@@ -0,0 +1,18 @@
+#  SPDX-License-Identifier: LGPL-2.1-or-later
+#
+#  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=Simple Block Device Backed Storage Provider
+Documentation=man:systemd-storage-block@.service(8)
+DefaultDependencies=no
+Conflicts=shutdown.target initrd-switch-root.target
+Before=shutdown.target initrd-switch-root.target
+
+[Service]
+ExecStart=-{{LIBEXECDIR}}/systemd-storage-block