11

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.

13

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.

  • What's the </dev/tty for? – Stéphane Chazelas May 25 '15 at 20:02
  • 1
    Last time I tried, even losetup --find --show races. for i in {1..100}; do losetup -f -s $i & done didn't give me 100 loop devices. Loop devices are uncommon enough for it not to matter normally; if it does your only option is to make your own locks and/or check that the correct loop device was created as an afterthought. – frostschutz May 25 '15 at 20:03
  • @frostschutz losetup might fail (e.g. because you've run out of loop entries), but if it reports a device name, that's the device that it successfully allocated. Did earlier versions have a bug that caused it to write out a device name even though the allocation had failed? I see in the source code that the interface for in-kernel allocation only exists since kernel 3.1, is it maybe a bug with the older interface that requires the losetup utility to do the search? – Gilles May 25 '15 at 20:11
  • Oh, it seems indeed to be fixed in more recent versions. util-linux-2.26.2 seems to work, util-linux-2.24.1 repeatedly prints /dev/loop14 and such and the device may be missing altogether in the end. Maybe the fix is courtesy of /dev/loop-control, it used to just look at /proc/partitions... – frostschutz May 25 '15 at 20:20
  • @frostschutz Since util-linux 2.21, losetup uses /dev/loop-control if present, and that doesn't look like it could have a race condition: the allocation happens in the kernel and printing the device path is the last thing the utility does. – Gilles May 25 '15 at 20:29
5

I figured it out. While I am not sure how the issue with the permission thing is, I can instead shoot first and ask later like this:

sudo losetup -f myfile.img
ld=$(losetup -j myfile.img | grep -o "/dev/loop[0-9]*")
dostuffwith $ld
2

You could use flock:

  tryagain=1
  while [[ $tryagain -ne 0 ]]; do
    ld=`losetup -f`
    flock -n $ld -c "losetup $ld myfile.img"
    tryagain=$?
  done

The idea here is that you try and flock the loop device file; if another instance of the same script acquires it first, it will get to call losetup $ld myfile.img and flock will return 0. For the script that loses the race, losetup will not be called and flock will return 1, causing the loop to repeat.

For more see man flock.

0

If all you want to do with the image as a loopback device is mount it as a filesystem and work with the contents, the mount command can take care of this automatically.

mount -o loop myfile.img /tmp/mountpoint
  • Actually, in my case, I in particular want it not mounted - I am using it as a block device not a filesystem. – AJMansfield May 25 '15 at 21:39
  • @AJMansfield Other than mount it, what can you do with a block device that you can't do with a file? – Random832 May 25 '15 at 21:40
  • You can't format it as swap space on a full-disk btrfs setup. I was intending to use the file for swap space, since btrfs doesn't support the operations needed for swap files, and I can't have a real swap partition with a full-disk btrfs setup. – AJMansfield May 25 '15 at 21:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.