8

In bash scripting, how could I check elegantly if a specific location is a btrfs subvolume?

I do NOT want to know if the given location is in a btrfs file system (or subvolume). I want to know if the given location is the head of a subvolume.

Ideally, the solution could be written in a bash function so I could write:

if is_btrfs_subvolume $LOCATION; then
    # ... stuff ...
fi 

An 'elegant' solution would be readable, small in code, small in resource consumption.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
vaab
  • 9,685
  • 7
  • 55
  • 60

2 Answers2

17

The subvolume is identified by inode number 256, so you can check it simply by

if [ `stat --format=%i /path` -eq 256 ]; then ...; fi

There's also a so called empty-subvolume, ie. if a nested subvolume is snapshotted, this entity will exist in place of the original subvolume. Its inode number is 2.

For a generally reliable check wheter any directory is a subvolume, the filesystem type should be verified as well

stat -f --format=%T /path
kdave
  • 171
  • 1
  • 3
  • All this seems overly complicated compared to the current provided solution, and brittle if any new inode number gets used, isn't it ?. Anyway, I like the additional peripheral information you provided, there might be actually a good solution hidding not so far. – vaab Oct 02 '15 at 07:30
  • 2
    The command "btrfs subvolume list" requires root privileges because it uses a privileged ioctl to read the information about subvolumes, this was not menionted in the original answer. The inode number based test does not have such limitation. Next, I don't understand what do you mean by "brittle if any new inode number gets used". Each subvolume has inode number 256, this will not change. Regular files and directories in a subvolume have inode number 257 and higher. – kdave Oct 03 '15 at 13:27
  • You are right. Your answer is nice and misses only the bash function implementation (that was originally asked) and it would also be a nice TLDR summary of the test. – vaab Mar 17 '16 at 11:51
  • Looking at the inode number does not allow you to distinguish a subvolume of the same btrfs filesystem from a different btrfs filesystem mounted in a directory. Both appear with inode 256. – Vladimir Panteleev Mar 25 '23 at 06:30
  • Can a file or directory in other filesystems have inode 256? And if so, I suppose we will need to perform an additional check. (Perhaps to confirm from `/etc/mtab` that the path is btrfs mounted, which also doesn't need root access.) – KalEl Apr 13 '23 at 16:04
5

Solution1: Using @kdave suggestions:

is_btrfs_subvolume() {
    local dir=$1
    [ "$(stat -f --format="%T" "$dir")" == "btrfs" ] || return 1
    inode="$(stat --format="%i" "$dir")"
    case "$inode" in
        2|256)
            return 0;;
        *)
            return 1;;
    esac
}

Solution2: What I used before (only one call, but probably brittle):

is_btrfs_subvolume() {
    btrfs subvolume show "$1" >/dev/null 2>&1
}

EDIT: Corrected and replaced list by show as the behavior of list would not answer correctly on any normal btrfs directory.

EDIT2: as @kdave didn't post a full version of his superior answer, I added it to my answer.

vaab
  • 9,685
  • 7
  • 55
  • 60
  • 1
    This function is not worked. For my case, $1 is any directory exist in a btrfs partition, will always return all subvolume list in this partition, altough, $1 is just a normal directory. if current directory is a subvolume, this directory will list as the last line of list, otherwise, not. I don't know how to down the answer ... – zw963 Aug 02 '16 at 07:48
  • Oh, yes, good catch. Sorry, I replaced 'list' by 'show'. Is it working as it should now ? – vaab Aug 02 '16 at 08:26
  • You should run your script as root. – ceremcem May 14 '17 at 10:53
  • Is this still working for you? The command correctly identifies subvolumes but is always returning 0 for me meaning I can't use it programatically. – Tom Wadley Jun 25 '17 at 19:05
  • @TomWadley still working here. What version of ``btrfs-tools`` are you using ? (``btrfs --version``... I'm on ``v4.10.1``.) – vaab Jun 26 '17 at 08:35
  • @vaab I'm on `v3.17` (Debian 8.7) so that might explain it. I can only get it to return 1 by passing in a path that doesn't exist at all. @kdave's `stat` solution works fine though. – Tom Wadley Jun 26 '17 at 20:05