Shell – How to atomically allocate a loop device

concurrencyloop-deviceshell-script

I am writing some shell scripts to handle some disk image stuff, and I need to use loop devices to access some disk images. However, I am not sure how to properly allocate a loop device without exposing my program to a race condition.

I know that I can use losetup -f to get the next unallocated loop device, and then allocate that loop device like this:

ld=$(losetup -f)
sudo losetup $ld myfile.img
dostuffwith $ld

However, in the case where I want to run multiple instances of the program at the same time, this is almost a textbook example of a race condition, and that bothers me quite a lot. If I had multiple instance of this program running, or other programs trying to also get a loop device, then each process might not be able to allocate the loop device before the next one calls losetup -f, in which case both processes would think that the same loop device is available, but only one can get it.

I could use external synchronization for this, but I would like to (if possible) avoid additional complexity. Also, other programs that use loop devices wouldn't likely respect whatever synchronization I might come up with.

How can I avoid this potential race condition? Ideally, I'd like to be able to discover and bind the loop device atomically, for instance with a command like:

ld=$(sudo losetup -f myfile.img)
dostuffwith $ld

However, when I do that, $ld does not get assigned to the loop device path, and moving the sudo out, as in sudo ld=$(losetup -f myfile.img) gives permission errors.

Best Answer

This is a classic problem in concurrency: when allocating a resource, you need to atomically determine that the resource is free and reserve it, otherwise another process could reserve the resource between the time you check that it's free and the time you reserve it.

Do use losetup's automatic allocation mode (-f), and pass the --show option to make it print the loop device path.

ld=$(sudo losetup --show -f /tmp/1m)

This option has been present in util-linux since version 2.13 (initially added as -s, but --show has been supported in all released versions and recent versions have dropped the -s option name). Unfortunately the BusyBox version doesn't have it.

Version 3.1 of the Linux kernel introduced a method to perform the loop device allocation operation directly in the kernel, via the new /dev/loop-control device. This method is only supported since util-linux 2.21. With kernel <3.1 or util-linux <2.21, the losetup program enumerates the loop device entries to reserve one. I can't see a race condition in the code though; it should be safe but it might have a small window during which it will incorrectly report that all devices are allocated even though this is not the case.

Related Question