# Locking Block Device Access
*TL;DR: Use BSD file locks
-[(`flock(2)`)](http://man7.org/linux/man-pages/man2/flock.2.html) on block
+[(`flock(2)`)](https://man7.org/linux/man-pages/man2/flock.2.html) on block
device nodes to synchronize access for partitioning and file system formatting
tools.*
from processing rules on it — and thus bar it from probing the device — by
taking a BSD file lock on the block device node. Specifically, whenever
`systemd-udevd` starts processing a block device it takes a `LOCK_SH|LOCK_NB`
-lock using [`flock(2)`](http://man7.org/linux/man-pages/man2/flock.2.html) on
+lock using [`flock(2)`](https://man7.org/linux/man-pages/man2/flock.2.html) on
the main block device (i.e. never on any partition block device, but on the
device the partition belongs to). If this lock cannot be taken (i.e. `flock()`
-returns `EBUSY`), it refrains from processing the device. If it manages to take
+returns `EAGAIN`), it refrains from processing the device. If it manages to take
the lock it is kept for the entire time the device is processed.
Note that `systemd-udevd` also watches all block device nodes it manages for
orthogonal. The scheme discussed above uses the former and not the latter,
because these types of locks more closely match the required semantics.
+If multiple devices are to be locked at the same time (for example in order to
+format a RAID file system), the devices should be locked in the order of the
+the device nodes' major numbers (primary ordering key, ascending) and minor
+numbers (secondary ordering key, ditto), in order to avoid ABBA locking issues
+between subsystems.
+
+Note that the locks should only be taken while the device is repartitioned,
+file systems formatted or `dd`'ed in, and similar cases that
+apply/remove/change superblocks/partition information. It should not be held
+during normal operation, i.e. while file systems on it are mounted for
+application use.
+
+The [`udevadm
+lock`](https://www.freedesktop.org/software/systemd/man/udevadm.html) command
+is provided to lock block devices following this scheme from the command line,
+for the use in scripts and similar. (Note though that it's typically preferable
+to use native support for block device locking in tools where that's
+available.)
+
Summarizing: it is recommended to take `LOCK_EX` BSD file locks when
manipulating block devices in all tools that change file system block devices
(`mkfs`, `fsck`, …) or partition tables (`fdisk`, `parted`, …), right after
opening the node.
+
+# Example of Locking The Whole Disk
+
+The following is an example to leverage `libsystemd` infrastructure to get the whole disk of a block device and take a BSD lock on it.
+
+## Compile and Execute
+**Note that this example requires `libsystemd` version 251 or newer.**
+
+Place the code in a source file, e.g. `take_BSD_lock.c` and run the following commands:
+```
+$ gcc -o take_BSD_lock -lsystemd take_BSD_lock.c
+
+$ ./take_BSD_lock /dev/sda1
+Successfully took a BSD lock: /dev/sda
+
+$ flock -x /dev/sda ./take_BSD_lock /dev/sda1
+Failed to take a BSD lock on /dev/sda: Resource temporarily unavailable
+```
+
+## Code
+```c
+/* SPDX-License-Identifier: MIT-0 */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/file.h>
+#include <systemd/sd-device.h>
+#include <unistd.h>
+
+static inline void closep(int *fd) {
+ if (*fd >= 0)
+ close(*fd);
+}
+
+/**
+ * lock_whole_disk_from_devname
+ * @devname: devname of a block device, e.g., /dev/sda or /dev/sda1
+ * @open_flags: the flags to open the device, e.g., O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY
+ * @flock_operation: the operation to call flock, e.g., LOCK_EX|LOCK_NB
+ *
+ * given the devname of a block device, take a BSD lock of the whole disk
+ *
+ * Returns: negative errno value on error, or non-negative fd if the lock was taken successfully.
+ **/
+int lock_whole_disk_from_devname(const char *devname, int open_flags, int flock_operation) {
+ __attribute__((cleanup(sd_device_unrefp))) sd_device *dev = NULL;
+ sd_device *whole_dev;
+ const char *whole_disk_devname, *subsystem, *devtype;
+ int r;
+
+ // create a sd_device instance from devname
+ r = sd_device_new_from_devname(&dev, devname);
+ if (r < 0) {
+ errno = -r;
+ fprintf(stderr, "Failed to create sd_device: %m\n");
+ return r;
+ }
+
+ // if the subsystem of dev is block, but its devtype is not disk, find its parent
+ r = sd_device_get_subsystem(dev, &subsystem);
+ if (r < 0) {
+ errno = -r;
+ fprintf(stderr, "Failed to get the subsystem: %m\n");
+ return r;
+ }
+ if (strcmp(subsystem, "block") != 0) {
+ fprintf(stderr, "%s is not a block device, refusing.\n", devname);
+ return -EINVAL;
+ }
+
+ r = sd_device_get_devtype(dev, &devtype);
+ if (r < 0) {
+ errno = -r;
+ fprintf(stderr, "Failed to get the devtype: %m\n");
+ return r;
+ }
+ if (strcmp(devtype, "disk") == 0)
+ whole_dev = dev;
+ else {
+ r = sd_device_get_parent_with_subsystem_devtype(dev, "block", "disk", &whole_dev);
+ if (r < 0) {
+ errno = -r;
+ fprintf(stderr, "Failed to get the parent device: %m\n");
+ return r;
+ }
+ }
+
+ // open the whole disk device node
+ __attribute__((cleanup(closep))) int fd = sd_device_open(whole_dev, open_flags);
+ if (fd < 0) {
+ errno = -fd;
+ fprintf(stderr, "Failed to open the device: %m\n");
+ return fd;
+ }
+
+ // get the whole disk devname
+ r = sd_device_get_devname(whole_dev, &whole_disk_devname);
+ if (r < 0) {
+ errno = -r;
+ fprintf(stderr, "Failed to get the whole disk name: %m\n");
+ return r;
+ }
+
+ // take a BSD lock of the whole disk device node
+ if (flock(fd, flock_operation) < 0) {
+ r = -errno;
+ fprintf(stderr, "Failed to take a BSD lock on %s: %m\n", whole_disk_devname);
+ return r;
+ }
+
+ printf("Successfully took a BSD lock: %s\n", whole_disk_devname);
+
+ // take the fd to avoid automatic cleanup
+ int ret_fd = fd;
+ fd = -EBADF;
+ return ret_fd;
+}
+
+int main(int argc, char **argv) {
+ if (argc != 2) {
+ fprintf(stderr, "Invalid number of parameters.\n");
+ return EXIT_FAILURE;
+ }
+
+ // try to take an exclusive and nonblocking BSD lock
+ __attribute__((cleanup(closep))) int fd =
+ lock_whole_disk_from_devname(
+ argv[1],
+ O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY,
+ LOCK_EX|LOCK_NB);
+
+ if (fd < 0)
+ return EXIT_FAILURE;
+
+ /**
+ * The device is now locked until the return below.
+ * Now you can safely manipulate the block device.
+ **/
+
+ return EXIT_SUCCESS;
+}
+```