From 547ecf16d4b34ffb1f8ad204d0e0cdf55aaba3e8 Mon Sep 17 00:00:00 2001 From: Markus Hilger Date: Mon, 27 Jul 2026 03:10:31 +0200 Subject: [PATCH] Release the crypt device if encrypt_image is interrupted Nothing unwound the loop device and dm-crypt mapping when the copy loop raised, so interrupting a pack stranded both, still holding the profile's rootimg.sfs. Tear them down from a finally. The retry loop moves with them, so also honour its tries counter, as unpack_image already does; spinning forever inside a finally would hang the interrupt it is meant to clean up after. A bounded retry loop can also give up, and the detach that follows would then fail with EBUSY and, raising from a finally, replace the exception that brought us here. Warn and leave both in place instead. --- imgutil/imgutil | 51 +++++++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/imgutil/imgutil b/imgutil/imgutil index 00500bbc..96d01083 100644 --- a/imgutil/imgutil +++ b/imgutil/imgutil @@ -385,28 +385,37 @@ def encrypt_image(plainfile, cryptfile, keyfile): neededblocks += 1 loopdev = subprocess.check_output(['losetup', '-f']).decode('utf8').strip() subprocess.check_call(['losetup', loopdev, cryptfile]) - subprocess.check_call(['dmsetup', 'create', dmname, '--table', '0 {} crypt aes-xts-plain64 {} 0 {} 8'.format(neededblocks, key, loopdev)]) - subprocess.check_call(['dmsetup', 'mknodes', dmname]) - with open('/dev/mapper/{}'.format(dmname), 'wb') as cryptout: - with open(plainfile, 'rb+') as plainin: - lastoffset = 0 - chunk = plainin.read(2097152) - while chunk: - fallocate(plainin.fileno(), FALLOC_FL_KEEP_SIZE|FALLOC_FL_PUNCH_HOLE, lastoffset, len(chunk)) - lastoffset = plainin.tell() - cryptout.write(chunk) + try: + subprocess.check_call(['dmsetup', 'create', dmname, '--table', '0 {} crypt aes-xts-plain64 {} 0 {} 8'.format(neededblocks, key, loopdev)]) + subprocess.check_call(['dmsetup', 'mknodes', dmname]) + with open('/dev/mapper/{}'.format(dmname), 'wb') as cryptout: + with open(plainfile, 'rb+') as plainin: + lastoffset = 0 chunk = plainin.read(2097152) - mounted = True - tries = 30 - time.sleep(0.1) - while mounted: - tries -= 1 - try: - subprocess.check_call(['dmsetup', 'remove', dmname]) - mounted = False - except subprocess.CalledProcessError: - time.sleep(0.1) - subprocess.check_call(['losetup', '-d', loopdev]) + while chunk: + fallocate(plainin.fileno(), FALLOC_FL_KEEP_SIZE|FALLOC_FL_PUNCH_HOLE, lastoffset, len(chunk)) + lastoffset = plainin.tell() + cryptout.write(chunk) + chunk = plainin.read(2097152) + finally: + mounted = True + tries = 30 + time.sleep(0.1) + while mounted and tries: + tries -= 1 + try: + subprocess.check_call(['dmsetup', 'remove', dmname]) + mounted = False + except subprocess.CalledProcessError: + time.sleep(0.1) + if mounted: + sys.stderr.write( + 'Warning: unable to remove {0}, it and {1} are left behind\n'.format(dmname, loopdev)) + else: + try: + subprocess.check_call(['losetup', '-d', loopdev]) + except subprocess.CalledProcessError: + sys.stderr.write('Warning: unable to detach {0}\n'.format(loopdev)) oum = os.umask(0o077) with open(keyfile, 'w') as keyout: keyout.write('aes-xts-plain64\n{}\n'.format(key))