[arch-projects] [devtools] [PATCH] makechrootpkg: Be recursive when deleting btrfs subvolumes.

Luke Shumaker lukeshu at lukeshu.com
Fri Feb 10 20:30:01 UTC 2017


On Fri, 10 Feb 2017 09:57:32 -0500,
Dave Reisner wrote:
> > +	LC_ALL=C btrfs subvolume show "$1" | sed -n 's/^\tSubvolume ID:\s*//p'
> 
> This looks like you're parsing human readable output to get the subvol
> ID. Is this really the only way to get this information?

As far as I can tell, yes.

> > +)
> > +
> > +# Usage: btrfs_subvolume_list_all $FILEPATH
> > +#
> > +# Given $FILEPATH somewhere on a mounted btrfs filesystem, print the
> > +# ID and full path of every subvolume on the filesystem, one per line
> > +# in the format "$ID $PATH".
> > +btrfs_subvolume_list_all() (
> > +	set -o pipefail
> > +	local mountpoint
> > +	mountpoint="$(df --output=target "$1" | sed 1d)" || return $?
> 
> "return $?" is a long winded way of just "return". The status is
> implied.

Huh.  TIL.  Thanks.

> > +	LC_ALL=C btrfs subvolume list -a "$mountpoint" | sed -r 's|^ID ([0-9]+) .* path (<FS_TREE>/)?(\S*).*|\1 \3|'
> 
> You might want to validate that you actually get an integer back here,
> rather than just assuming the calls succeed. You cannot rely on sed to
> provide a useful exit status.

I suppose that would be a good idea.  The `set -o pipefail` a couple
of lines up will catch the cases where the failure is from `btrfs`;
the check would be for if btrfs's output format changes.

I really should have called it out in a comment and the commit message
that that regex is the place where I most expect btrfs to break us in
the future.  The output format is a space-separated sequence of "key
value key value..."; the problem is that both keys and values can
contain space, and there's no escaping or indication of when this
happens.  The reason I tacked `^` in the expression is that there's
already a "parent ID" key.  The continued success of the regex assumes
that there will never be another key or value that contains " path",
and that there is no space in the path value.

So yeah, I probably should add a check.

> > +	id="$(btrfs_subvolume_id "$subvolume")" || return $?
> > +	all="$(btrfs_subvolume_list_all "$subvolume")" || return $?
> > +	path="$(sed -n "s/^$id //p" <<<"$all")"
> 
> Rather than injecting unvalidated data into a sed program, I'd suggest
> using awk:
> 
> path=$(awk -v id="$1" '$1 == id { sub($1 FS, ""); print }' <<<"$all")

Sure.  Sed was simpler, and I figured that if `btrfs` starts
misbehaving, the whole thing is hosed anyway.

> > +	subvolumes=($(btrfs_subvolume_list "$dir")) || return $?
> 
> This is a broken way to read lines into an array, because you're not
> reading lines at all -- you're reading words.
> 
> > +	for subvolume in "${subvolumes[@]}"; do
> > +		btrfs subvolume delete "$dir/$subvolume" || return $?
> > +	done

Yeah, but the lines are guaranteed to be words because of the `(\S*)`
in the regexp in `btrfs_subvolume_list_all`.

But I should have written it as

	subvolumes="$(btrfs_subvolume_list "$dir")" || return
	for read -r subvolume; do
		btrfs subvolume delete "$dir/$subvolume" || return
	done <<<"$subvolumes"

-- 
Happy hacking,
~ Luke Shumaker


More information about the arch-projects mailing list