Skip to content

Commit cbce508

Browse files
committed
fix(image-factory): create the loop device node when there is none
In a container the kernel hands out a free loop number through /dev/loop-control, but the matching /dev/loopN node only exists if something creates it, and there is no udev to do so. losetup gets as far as /dev/loop5 and then fails with ENOENT, reporting the node as "lost". So create it. Where the container may use block devices at all, that is enough; where it may not, the message now says what is actually missing instead of blaming the loop module, which was loaded all along - and points at the host or a VM, since device-mapper is the next thing this script needs and has the same kind of problem. The three silent retries stay as a loop, but they were never the answer here: a device node that does not exist does not appear after sleeping two seconds. Signed-off-by: Simon Walz <contact@simonwalz.de>
1 parent ce8f055 commit cbce508

1 file changed

Lines changed: 55 additions & 7 deletions

File tree

‎image-factory/create_image.sh‎

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -164,14 +164,62 @@ fi
164164
sync
165165

166166
# Setup loop device
167-
if ! LOOPDEV="$(losetup --find --show "${IMAGE}")"; then
168-
sleep 2
169-
if ! LOOPDEV="$(losetup --find --show "${IMAGE}")"; then
170-
sleep 5
171-
if ! LOOPDEV="$(losetup --find --show "${IMAGE}")"; then
172-
raise "Failed to setup loop device. Are you inside a LXC container? Ensure loop module is loaded."
173-
fi
167+
#
168+
# The kernel hands out a free loop number through /dev/loop-control, but the
169+
# matching /dev/loopN node only exists if something creates it - and in a
170+
# container there is no udev to do that. losetup then fails with ENOENT and
171+
# reports the device node as "lost". Creating the node is enough to get past
172+
# that, provided the container may use block devices at all.
173+
attach_loop_device() {
174+
local dev
175+
dev="$(losetup --find --show "${IMAGE}" 2>/dev/null)" || dev=""
176+
if [[ -n "${dev}" ]]; then
177+
printf '%s' "${dev}"
178+
return 0
179+
fi
180+
181+
local free num
182+
free="$(losetup --find 2>/dev/null)" || free=""
183+
num="${free##*/loop}"
184+
if [[ -n "${num}" && "${num}" =~ ^[0-9]+$ && ! -e "${free}" ]]; then
185+
echo "Creating the missing device node ${free} ..." >&2
186+
mknod "${free}" b 7 "${num}" 2>/dev/null || true
187+
chmod 0660 "${free}" 2>/dev/null || true
174188
fi
189+
190+
losetup --find --show "${IMAGE}" 2>/dev/null
191+
}
192+
193+
LOOPDEV=""
194+
for _attempt in 1 2 3; do
195+
LOOPDEV="$(attach_loop_device)" && [[ -n "${LOOPDEV}" ]] && break
196+
sleep 2
197+
done
198+
199+
if [[ -z "${LOOPDEV}" ]]; then
200+
raise "Could not attach ${IMAGE} to a loop device.
201+
202+
Building a disk image needs loop devices, device-mapper and the right to
203+
mount filesystems. All three belong to the host kernel, and an LXC
204+
container has none of them by default - a 'device node /dev/loopN is
205+
lost' above means the kernel did hand out a loop device, but this
206+
container has no node for it.
207+
208+
Build on a host or in a VM instead. If it has to be this container, it
209+
must be privileged and unconfined, and its config needs at least:
210+
211+
lxc.apparmor.profile: unconfined
212+
lxc.cgroup2.devices.allow: b 7:* rwm
213+
lxc.cgroup2.devices.allow: c 10:237 rwm
214+
lxc.mount.entry: /dev/loop-control dev/loop-control none bind,create=file
215+
lxc.mount.entry: /dev/loop0 dev/loop0 none bind,create=file
216+
217+
plus one bind entry per loop device you want available. Note that
218+
device-mapper is the next thing this script needs, and it has the same
219+
kind of problem.
220+
221+
Building for '--platform lxc' needs none of this: it exports a container
222+
rather than partitioning a disk."
175223
fi
176224

177225
partprobe "${LOOPDEV}"

0 commit comments

Comments
 (0)