diff --git a/docs/BLOCK_DEVICE_LOCKING.md b/docs/BLOCK_DEVICE_LOCKING.md index 61beff0ff2..87aad67045 100644 --- a/docs/BLOCK_DEVICE_LOCKING.md +++ b/docs/BLOCK_DEVICE_LOCKING.md @@ -98,3 +98,146 @@ 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 +``` +/* SPDX-License-Identifier: MIT-0 */ + +#include +#include +#include +#include +#include +#include + +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 = -1; + 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; +} +```