diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml
index 7e59a1aaed..52939deec0 100644
--- a/man/kernel-command-line.xml
+++ b/man/kernel-command-line.xml
@@ -273,7 +273,8 @@
rd.udev.event_timeout=udev.timeout_signal=rd.udev.timeout_signal=
-
+ udev.blockdev_read_only
+ rd.udev.blockdev_read_onlynet.ifnames=net.naming-scheme=
diff --git a/man/systemd-udevd.service.xml b/man/systemd-udevd.service.xml
index 55edc17353..c6c1d9bcc6 100644
--- a/man/systemd-udevd.service.xml
+++ b/man/systemd-udevd.service.xml
@@ -77,7 +77,7 @@
-
+ Limit the number of events executed in parallel.
@@ -85,7 +85,7 @@
-
+ Delay the execution of RUN
@@ -97,7 +97,7 @@
-
+ Set the number of seconds to wait for events to finish. After
@@ -121,7 +121,7 @@
-
+ Specify when systemd-udevd should resolve names of users and groups.
@@ -140,8 +140,8 @@
Kernel command line
- Parameters starting with "rd." will be read when
- systemd-udevd is used in an initrd.
+ Parameters prefixed with "rd." will be read when systemd-udevd is used in an
+ initrd, those without will be processed both in the initrd and on the host.udev.log_priority=rd.udev.log_priority=
@@ -184,6 +184,22 @@
setting in the configuration file and the one on the program command line.
+
+ udev.blockdev_read_only
+ rd.udev.blockdev_read_only
+
+ If specified, mark all physical block devices read-only as they appear. Synthetic block
+ devices (such as loopback block devices or device mapper devices) are left as they are. This is
+ useful to guarantee that the contents of physical block devices remains unmodified during runtime,
+ for example to implement fully stateless systems, for testing or for recovery situations where
+ corrupted file systems shall not be corrupted further through accidental modification.
+
+ A block device may be marked writable again by issuing the blockdev
+ --setrw command, see blockdev8
+ for details.
+
+ net.ifnames=
diff --git a/src/udev/udevd.c b/src/udev/udevd.c
index a2b8c6162c..6e0ce72553 100644
--- a/src/udev/udevd.c
+++ b/src/udev/udevd.c
@@ -76,6 +76,7 @@ static unsigned arg_children_max = 0;
static usec_t arg_exec_delay_usec = 0;
static usec_t arg_event_timeout_usec = 180 * USEC_PER_SEC;
static int arg_timeout_signal = SIGKILL;
+static bool arg_blockdev_read_only = false;
typedef struct Manager {
sd_event *event;
@@ -383,6 +384,56 @@ static int worker_lock_block_device(sd_device *dev, int *ret_fd) {
return 1;
}
+static int worker_mark_block_device_read_only(sd_device *dev) {
+ _cleanup_close_ int fd = -1;
+ const char *val;
+ int state = 1, r;
+
+ assert(dev);
+
+ if (!arg_blockdev_read_only)
+ return 0;
+
+ /* Do this only once, when the block device is new. If the device is later retriggered let's not
+ * toggle the bit again, so that people can boot up with full read-only mode and then unset the bit
+ * for specific devices only. */
+ if (!device_for_action(dev, DEVICE_ACTION_ADD))
+ return 0;
+
+ r = sd_device_get_subsystem(dev, &val);
+ if (r < 0)
+ return log_device_debug_errno(dev, r, "Failed to get subsystem: %m");
+
+ if (!streq(val, "block"))
+ return 0;
+
+ r = sd_device_get_sysname(dev, &val);
+ if (r < 0)
+ return log_device_debug_errno(dev, r, "Failed to get sysname: %m");
+
+ /* Exclude synthetic devices for now, this is supposed to be a safety feature to avoid modification
+ * of physical devices, and what sits on top of those doesn't really matter if we don't allow the
+ * underlying block devices to recieve changes. */
+ if (STARTSWITH_SET(val, "dm-", "md", "drbd", "loop", "nbd", "zram"))
+ return 0;
+
+ r = sd_device_get_devname(dev, &val);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return log_device_debug_errno(dev, r, "Failed to get devname: %m");
+
+ fd = open(val, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
+ if (fd < 0)
+ return log_device_debug_errno(dev, errno, "Failed to open '%s', ignoring: %m", val);
+
+ if (ioctl(fd, BLKROSET, &state) < 0)
+ return log_device_warning_errno(dev, errno, "Failed to mark block device '%s' read-only: %m", val);
+
+ log_device_info(dev, "Successfully marked block device '%s' read-only.", val);
+ return 0;
+}
+
static int worker_process_device(Manager *manager, sd_device *dev) {
_cleanup_(udev_event_freep) UdevEvent *udev_event = NULL;
_cleanup_close_ int fd_lock = -1;
@@ -412,6 +463,8 @@ static int worker_process_device(Manager *manager, sd_device *dev) {
if (r < 0)
return r;
+ (void) worker_mark_block_device_read_only(dev);
+
/* apply rules, create node, symlinks */
r = udev_event_execute_rules(udev_event, arg_event_timeout_usec, arg_timeout_signal, manager->properties, manager->rules);
if (r < 0)
@@ -1417,15 +1470,13 @@ static int listen_fds(int *ret_ctrl, int *ret_netlink) {
* udev.children_max= events are fully serialized if set to 1
* udev.exec_delay= delay execution of every executed program
* udev.event_timeout= seconds to wait before terminating an event
+ * udev.blockdev_read_only<=bool> mark all block devices read-only when they appear
*/
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
- int r = 0;
+ int r;
assert(key);
- if (!value)
- return 0;
-
if (proc_cmdline_key_streq(key, "udev.log_priority")) {
if (proc_cmdline_value_missing(key, value))
@@ -1457,14 +1508,37 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
r = parse_sec(value, &arg_exec_delay_usec);
} else if (proc_cmdline_key_streq(key, "udev.timeout_signal")) {
+
if (proc_cmdline_value_missing(key, value))
return 0;
r = signal_from_string(value);
if (r > 0)
arg_timeout_signal = r;
- } else if (startswith(key, "udev."))
- log_warning("Unknown udev kernel command line option \"%s\", ignoring", key);
+
+ } else if (proc_cmdline_key_streq(key, "udev.blockdev_read_only")) {
+
+ if (!value)
+ arg_blockdev_read_only = true;
+ else {
+ r = parse_boolean(value);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse udev.blockdev-read-only argument, ignoring: %s", value);
+ else
+ arg_blockdev_read_only = r;
+ }
+
+ if (arg_blockdev_read_only)
+ log_notice("All physical block devices will be marked read-only.");
+
+ return 0;
+
+ } else {
+ if (startswith(key, "udev."))
+ log_warning("Unknown udev kernel command line option \"%s\", ignoring.", key);
+
+ return 0;
+ }
if (r < 0)
log_warning_errno(r, "Failed to parse \"%s=%s\", ignoring: %m", key, value);
diff --git a/units/initrd-udevadm-cleanup-db.service b/units/initrd-udevadm-cleanup-db.service
index ad2f2a5b35..810cf5775e 100644
--- a/units/initrd-udevadm-cleanup-db.service
+++ b/units/initrd-udevadm-cleanup-db.service
@@ -8,7 +8,7 @@
# (at your option) any later version.
[Unit]
-Description=Cleanup udevd DB
+Description=Cleanup udev Database
DefaultDependencies=no
ConditionPathExists=/etc/initrd-release
Conflicts=systemd-udevd.service systemd-udevd-control.socket systemd-udevd-kernel.socket systemd-udev-trigger.service systemd-udev-settle.service
diff --git a/units/systemd-udev-settle.service b/units/systemd-udev-settle.service
index ed6a68b864..9352c6f598 100644
--- a/units/systemd-udev-settle.service
+++ b/units/systemd-udev-settle.service
@@ -12,7 +12,7 @@
# expect a populated /dev during bootup.
[Unit]
-Description=udev Wait for Complete Device Initialization
+Description=Wait for udev To Complete Device Initialization
Documentation=man:systemd-udev-settle.service(8)
DefaultDependencies=no
Wants=systemd-udevd.service
diff --git a/units/systemd-udev-trigger.service b/units/systemd-udev-trigger.service
index 8a625b6305..cfe8d61c2a 100644
--- a/units/systemd-udev-trigger.service
+++ b/units/systemd-udev-trigger.service
@@ -8,7 +8,7 @@
# (at your option) any later version.
[Unit]
-Description=udev Coldplug all Devices
+Description=Coldplug All udev Devices
Documentation=man:udev(7) man:systemd-udevd.service(8)
DefaultDependencies=no
Wants=systemd-udevd.service