makechrootpkg: Be recursive when deleting btrfs subvolumes.
Motivation:
tmpfiles.d(5) has directives to create btrfs subvolumes. This means
that systemd-tmpfiles (which may be called by an ALPM hook) might
create subvolumes. For instance, systemd's systemd-nspawn.conf
creates a subvolume at `/var/lib/machines/`.
This causes a problem when we go to delete the chroot. The command
`btrfs subvolume delete` won't recursively delete subvolumes; if a
child subvolume was created, it will fail with the fairly unhelpful
error message "directory not empty".
Solution:
Because the subvolume that gets mounted isn't necessarily the
toplevel subvolume, and `btrfs subvolume list` gives us paths
relative to the toplevel; we need to figure out how our path relates
to the toplevel. Figure out the mountpoint (which turns out to be
slightly tricky; see below), and call `btrfs subvolume list -a` on
it to get the list of subvolumes that are visible to us (and quite
possibly some that aren't; the logic for determining which ones it
shows is... absurd). This gives us a list of subvolumes with
numeric IDs, and paths relative to the toplevel (actually it gives
us more than that, and we use a hopefully-correct `sed` expression
to trim it down) So then we look at that list of pairs and find the
one that matches the ID of the subvolume we're trying to delete
(which is easy to get with `btrfs subvolume show`); once we've found
the path of our subvolume, we can use that to filter and trim the
complete list of paths. From there the remainder of the solution is
obvious.
Now, back to "figure out the mountpoint"; the normal `stat -c %m`
doesn't work. It gives the mounted path of the subvolume closest to
the path we give it, not the actual mountpoint. Now, it turns out
that `df` can figure out the correct mountpoint (though I haven't
investigated how it knows when stat doesn't; but I suspect it parses
`/proc/mounts`). So we are reduced to parsing `df`'s output.
Now, back to "hopefully-correct `sed` expression"; the output of
`btrfs subvolume list -a` is a space-separated sequence of
"key value key value...". Unfortunately both keys and values can
contain space, and there's no escaping or indication of when this
happens. With how we choose to parse it, a path containing a space
is truncated at the first space. This means that at least the
prefix is correct; if a path gets mangled, it just means that the
deletion fails. As "path" is (currently) the last key, it seems
tempting to allow it to simply run until the end of the line.
However, this creates the possibility of a path containing " path ",
which would cause the *prefix* to be trimmed, which means our
failure case is now unpredictable, and potentially harmful. While
we pretty much trust the user, that's still scary.
0 files changed, 0 insertions, 0 deletions