Environment
- fence v0.1.65 (installed via mise
go install, so fence --version reports dev); code path still present on main
- Host: Bluefin (Fedora Atomic desktop), systemd-resolved
- Sandbox host env: distrobox container (
podman-5.8.4, quay.io/fedora/fedora:43), rootless
- Kernel 7.0.12-201.fc44.x86_64
Symptom
Every fence invocation fails at sandbox setup:
$ fence -- true
bwrap: Can't bind mount /oldroot/etc/resolv.conf on /newroot/etc/resolv.conf: Unable to mount source on destination: No such file or directory
The catch is that it doesn't fail immediately after the container starts — it starts failing after the host's /etc/resolv.conf changes (VPN connect/disconnect, Wi-Fi switch, suspend/resume, DHCP renewal). Once it starts, every subsequent fence run in that container fails until the container is restarted.
Root cause
Inside a distrobox/podman container, /etc/resolv.conf is a bind mount of the host's /run/systemd/resolve/stub-resolv.conf, not a regular file:
$ grep resolv /proc/self/mountinfo
3419 2692 0:29 /systemd/resolve/stub-resolv.conf//deleted /etc/resolv.conf ro,nosuid,nodev master:1462 - tmpfs tmpfs rw,seclabel,...
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
systemd-resolved rewrites stub-resolv.conf atomically (write temp file + rename) whenever the DNS configuration changes. That unlinks the inode the container's bind mount was created from. The mount stays alive pointing at the now-unlinked inode, and the kernel marks it by appending //deleted to the mount root field in mountinfo. The file still stats fine, but with zero links:
$ stat -L /etc/resolv.conf
File: /etc/resolv.conf
Size: 948 Blocks: 8 IO Block: 4096 regular file
Device: 0,29 Inode: 5202 Links: 0
^^^^^^^^^
fence then trips over this in appendLinuxReadableHostSubmounts:
for _, root := range linuxReadableHostSubmountRoots() {
if !fileExists(root) || sameDevice("/", root) {
continue
}
bwrapArgs = append(bwrapArgs, "--ro-bind", root, root)
/etc/resolv.conf qualifies as a host submount to preserve — linuxShouldPreserveHostSubmount only excludes /dev, /proc, /sys, /run and /tmp, and /etc is not on that list — and it survives both guards: fileExists succeeds (the unlinked inode still stats), and sameDevice("/", ...) is false because the mount is on the host's /run tmpfs (0:29) rather than the container's root device. So fence emits --ro-bind /etc/resolv.conf /etc/resolv.conf, and bwrap fails with ENOENT because the source mount's root dentry no longer exists.
parseLinuxMountInfoLine only keeps fields[4] (mount point) and the fs type, so the //deleted marker in fields[3] (mount root) is discarded before anything can act on it:
return linuxMountInfoEntry{
MountPoint: linuxUnescapeMountInfoPath(fields[4]),
FSType: fields[separator+1],
}, true
Note this is a different failure from #30 (WSL, /etc/resolv.conf a dangling symlink across a mount boundary) — here it's not a symlink at all, and the resolv.conf handling at linux.go:1164 is skipped entirely. It's the same shape as #195 (/opt -> /var/opt on Silverblue): an entry in a mount list that can't actually be bound.
Workaround
Restart the distrobox container so podman re-creates the bind mount from the current inode:
$ distrobox stop <name> && distrobox enter <name>
This works until the host's DNS configuration changes again.
Environment
go install, sofence --versionreportsdev); code path still present onmainpodman-5.8.4,quay.io/fedora/fedora:43), rootlessSymptom
Every fence invocation fails at sandbox setup:
The catch is that it doesn't fail immediately after the container starts — it starts failing after the host's
/etc/resolv.confchanges (VPN connect/disconnect, Wi-Fi switch, suspend/resume, DHCP renewal). Once it starts, every subsequentfencerun in that container fails until the container is restarted.Root cause
Inside a distrobox/podman container,
/etc/resolv.confis a bind mount of the host's/run/systemd/resolve/stub-resolv.conf, not a regular file:systemd-resolved rewrites
stub-resolv.confatomically (write temp file + rename) whenever the DNS configuration changes. That unlinks the inode the container's bind mount was created from. The mount stays alive pointing at the now-unlinked inode, and the kernel marks it by appending//deletedto the mount root field inmountinfo. The file still stats fine, but with zero links:fence then trips over this in
appendLinuxReadableHostSubmounts:/etc/resolv.confqualifies as a host submount to preserve —linuxShouldPreserveHostSubmountonly excludes/dev,/proc,/sys,/runand/tmp, and/etcis not on that list — and it survives both guards:fileExistssucceeds (the unlinked inode still stats), andsameDevice("/", ...)is false because the mount is on the host's/runtmpfs (0:29) rather than the container's root device. So fence emits--ro-bind /etc/resolv.conf /etc/resolv.conf, and bwrap fails withENOENTbecause the source mount's root dentry no longer exists.parseLinuxMountInfoLineonly keepsfields[4](mount point) and the fs type, so the//deletedmarker infields[3](mount root) is discarded before anything can act on it:Note this is a different failure from #30 (WSL,
/etc/resolv.confa dangling symlink across a mount boundary) — here it's not a symlink at all, and the resolv.conf handling atlinux.go:1164is skipped entirely. It's the same shape as #195 (/opt -> /var/opton Silverblue): an entry in a mount list that can't actually be bound.Workaround
Restart the distrobox container so podman re-creates the bind mount from the current inode:
$ distrobox stop <name> && distrobox enter <name>This works until the host's DNS configuration changes again.