]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
udev: optionally mark all block devices read-only as they pop up
authorLennart Poettering <lennart@poettering.net>
Tue, 2 Jun 2020 13:15:24 +0000 (15:15 +0200)
committerLennart Poettering <lennart@poettering.net>
Mon, 8 Jun 2020 14:53:37 +0000 (16:53 +0200)
man/kernel-command-line.xml
man/systemd-udevd.service.xml
src/udev/udevd.c

index 7e59a1aaede8514e9ab55e20ed7612a17b166593..52939deec0b15967d82a9e45816c2cee7ed5ec7b 100644 (file)
         <term><varname>rd.udev.event_timeout=</varname></term>
         <term><varname>udev.timeout_signal=</varname></term>
         <term><varname>rd.udev.timeout_signal=</varname></term>
-
+        <term><varname>udev.blockdev_read_only</varname></term>
+        <term><varname>rd.udev.blockdev_read_only</varname></term>
         <term><varname>net.ifnames=</varname></term>
         <term><varname>net.naming-scheme=</varname></term>
 
index 55edc17353f43f9721c545f1f51116650062a687..302f44aa87a5b08a23b620e58ff2a7bf2e32bd84 100644 (file)
 
   <refsect1><title>Kernel command line</title>
     <variablelist class='kernel-commandline-options'>
-      <para>Parameters starting with "rd." will be read when
-      <command>systemd-udevd</command> is used in an initrd.</para>
+      <para>Parameters prefixed with "rd." will be read when <command>systemd-udevd</command> is used in an
+      initrd, those without will be processed both in the initrd and on the host.</para>
       <varlistentry>
         <term><varname>udev.log_priority=</varname></term>
         <term><varname>rd.udev.log_priority=</varname></term>
           setting in the configuration file and the one on the program command line.</para>
         </listitem>
       </varlistentry>
+      <varlistentry>
+        <term><varname>udev.blockdev_read_only</varname></term>
+        <term><varname>rd.udev.blockdev_read_only</varname></term>
+        <listitem>
+          <para>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.</para>
+
+          <para>A block device may be marked writable again by issuing the <command>blockdev
+          --setrw</command> command, see <citerefentry
+          project='man-pages'><refentrytitle>blockdev</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+          for details.</para>
+        </listitem>
+      </varlistentry>
       <varlistentry>
         <term><varname>net.ifnames=</varname></term>
         <listitem>
index a2b8c6162c16d17a9d12bab98cc89010e0c60659..6e0ce725534cac2859f03d0e346cf951f0cd78d5 100644 (file)
@@ -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=<number of workers>     events are fully serialized if set to 1
  *   udev.exec_delay=<number of seconds>       delay execution of every executed program
  *   udev.event_timeout=<number of seconds>    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);