Use autoclear for overlay loopback device master
authorDavid Woodhouse <dwmw2@infradead.org>
Wed, 17 Jun 2020 18:29:56 +0000 (19:29 +0100)
committerJo-Philipp Wich <jo@mein.io>
Wed, 17 Jun 2020 21:20:48 +0000 (23:20 +0200)
During a sysupgrade on a block-based device, the partition table might
get updated.

The partitions have to be completely unused by the time partx is
invoked, or it fails thus:
partx: /dev/mmcblk1: error deleting partition 3
partx: /dev/mmcblk1: error adding partition 3

That's cosmetic in some cases, but in others where the old root
partition overlaps with the new partition where the config is stored
during the reboot, it causes a sysugprade failure (resulting in the
backup being lost and a completely clean system image).

Although we carefully unmount the root and overlay file systems, the
problem is that the loopback device used for the overlay isn't being
torn down, and it still has a refcount on the root block partition (in
the above case, /dev/mmcblk1p3).

Installing losetup and adding 'losetup -D' to the switch_to_ramfs()
function makes it work nicely. But the better option that doesn't add a
new dependency is to use the autoclear flag when setting up the loop
device, so it goes away automatically when the overlay file system is
unmounted.

To make that work sanely, we have to *not* close the fd right after
configuring it â€” or it'll go away immediately. We could store the fd in
the volume struct and either add destructor method or close it after
performing the mount… but honestly it just seems simpler and saner to
"leak" the fd in the knowledge that it'll get closed when the process
exits in a few milliseconds anyway. We can revisit that if anyone
really feels strongly about it. Dissent is best expressed in 'diff -up'
form.

Signed-off-by: David Woodhouse <dwmw2@infradead.org>
libfstools/rootdisk.c

index 5a6dcb9fc9aa66987630d0ff53e6796c3fda263b..dc861a96caaa73dea6d94337b6e76a4abef00f0f 100644 (file)
@@ -231,12 +231,19 @@ static int rootdisk_create_loop(struct rootdev_volume *p)
                snprintf((char *) info.lo_file_name, sizeof(info.lo_file_name), "%s",
                         rootdev);
                info.lo_offset = p->offset;
                snprintf((char *) info.lo_file_name, sizeof(info.lo_file_name), "%s",
                         rootdev);
                info.lo_offset = p->offset;
+               info.lo_flags |= LO_FLAGS_AUTOCLEAR;
 
                if (ioctl(fd, LOOP_SET_STATUS64, &info) != 0) {
                        ioctl(fd, LOOP_CLR_FD, 0);
                        continue;
                }
 
 
                if (ioctl(fd, LOOP_SET_STATUS64, &info) != 0) {
                        ioctl(fd, LOOP_CLR_FD, 0);
                        continue;
                }
 
+               /*
+                * Don't close fd. Leave it open until this process exits, to avoid
+                * the autoclear from happening too soon.
+                */
+               fd = -1;
+
                ret = 0;
                break;
        }
                ret = 0;
                break;
        }