From 2cd28fc82d0c947472a4700d5e764265916fba1e Mon Sep 17 00:00:00 2001
From: Stanislav Brabec <sbrabec@suse.cz>
Date: Thu, 21 Jan 2016 22:58:31 +0100
Subject: [PATCH] libmount: handle btrfs default subvolume mount
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When mounting btrfs volume without subvol= and subvolid=, and the
btrfs volume has default subvolume defined, mount() mounts the default
subvolume and not the volume root as other filesystems do.

To handle this situation correctly (for example for "mount -a"),
libmount has to be capable to detect default subvolume.

Add btrfs.c and btrfs.h that implement needed functions.

This patch adds mnt_table_find_target_with_option() to the library API.

Known problems not covered by this patch:

- Use of subvolid= in fstab is not yet handled.

- Use of type auto in combination with subvol= in fstab is not yet
  handled.

- Use of btrfs in loop devices, where image file is specified in fstab is
  not yet handled (use of /dev/loop0 in fstab works).

- If fstab uses subvol=, and subvol path changes since last "mount -a",
  subsequent "mount -a" will not recognize that it is already mounted,
  and it will attempt to mount it second time. To fix it, libmount should
  remember subvolid in time of mount (subvolid is unique for the
  subvolume, subvol is not).

- mountinfo contains subvol and subvolid since kernel 4.2. Before kernel
  4.2, there is no reasonable way to solve this situation. (One would
  create temporary mount point, mount the default, call needed ioctl() to
  determine what was mounted, deduce the default subvolume, compare it
  with subvolume of mounted volume, unmount and return result.)

How to reproduce:
	truncate -s1G btrfs_test.img
	mkdir -p btrfs_mnt
	/sbin/mkfs.btrfs -f -d single -m single ./btrfs_test.img
	mount -o loop btrfs_test.img btrfs_mnt
	pushd .
	cd btrfs_mnt
	mkdir -p d0/dd0/ddd0
	cd d0/dd0/ddd0
	touch file{1..5}
	btrfs subvol create s1
	cd s1
	touch file{1..5}
	mkdir -p d1/dd1/ddd1
	cd d1/dd1/ddd1
	btrfs subvol create s2
	rid=$(btrfs inspect rootid s2)
	echo new default $rid
	btrfs subvol get-default .
	btrfs subvol set-default $rid .
	popd
	umount btrfs_mnt
	losetup /dev/loop0 $PWD/btrfs_test.img
	echo "/dev/loop0 $PWD/btrfs_mnt btrfs defaults 0 0" >>/etc/fstab
	mount -a
	mount -a
	umount btrfs_mnt
	sed -i "/\/dev\/loop0/d" /etc/fstab
	losetup -d /dev/loop0
	rm btrfs_test.img
	rmdir btrfs_mnt

Current behavior:
	mount: /dev/loop0 is already mounted or /root/btrfs_mnt busy
	       /dev/loop0 is already mounted on /root/btrfs_mnt

Expected behavior is to ignore already mounted FS.

[kzak@redhat.com: - make 'var' optional for mnt_table_find_target_with_option(),
                  - add mnt_table_find_target_with_option() to symbols table and docs
                  - add "btrfs" string between supported debug modes
                  - minor coding style changes]

Signed-off-by: Stanislav Brabec <sbrabec@suse.cz>
Cc: David Štěrba <dsterba@suse.cz>
Signed-off-by: Karel Zak <kzak@redhat.com>
---
 libmount/docs/libmount-sections.txt |   1 +
 libmount/src/Makemodule.am          |   2 +
 libmount/src/btrfs.c                | 102 ++++++++++++++++++++++++++++++
 libmount/src/btrfs.h                | 122 ++++++++++++++++++++++++++++++++++++
 libmount/src/init.c                 |   1 +
 libmount/src/libmount.h.in          |   2 +
 libmount/src/libmount.sym           |   4 ++
 libmount/src/mountP.h               |   6 ++
 libmount/src/tab.c                  |  95 ++++++++++++++++++++++++++--
 9 files changed, 331 insertions(+), 4 deletions(-)
 create mode 100644 libmount/src/btrfs.c
 create mode 100644 libmount/src/btrfs.h

Index: util-linux-2.25/libmount/src/Makemodule.am
===================================================================
--- util-linux-2.25.orig/libmount/src/Makemodule.am
+++ util-linux-2.25/libmount/src/Makemodule.am
@@ -7,6 +7,8 @@ usrlib_exec_LTLIBRARIES += libmount.la
 libmount_la_SOURCES = \
 	include/list.h \
 	\
+	libmount/src/btrfs.c \
+	libmount/src/btrfs.h \
 	libmount/src/cache.c \
 	libmount/src/context.c \
 	libmount/src/context_loopdev.c \
Index: util-linux-2.25/libmount/src/btrfs.c
===================================================================
--- /dev/null
+++ util-linux-2.25/libmount/src/btrfs.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2016 David Sterba <dsterba@suse.cz>
+ * Copyright (C) 2016 Stanislav Brabec <sbrabec@suse.cz>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+/*
+ * SECTION: btrfs
+ * @title: btrfs
+ * @short_description: special function for btrfs
+ *
+ * btrfs contains function needed for manipulation with btrfs.
+ */
+#include <dirent.h>
+#include <sys/ioctl.h>
+#include <linux/magic.h>
+#include "btrfs.h"
+
+/*
+ * btrfs_get_default_subvol_id:
+ * @path: Path to mounted btrfs volume
+ *
+ * Searches for the btrfs default subvolume id.
+ *
+ * Returns: default subvolume id or UINT64_MAX (-1) in case of no
+ * default subvolume or error. In case of error, errno is set
+ * properly.
+ */
+uint64_t btrfs_get_default_subvol_id(const char *path)
+{
+	int iocret;
+	int fd;
+	DIR *dirstream = NULL;
+	struct btrfs_ioctl_search_args args;
+	struct btrfs_ioctl_search_key *sk = &args.key;
+	struct btrfs_ioctl_search_header *sh;
+	uint64_t found = UINT64_MAX;
+
+	dirstream = opendir(path);
+	if (!dirstream) {
+		DBG(BTRFS, ul_debug("opendir() failed for \"%s\" [errno=%d %m]", path, errno));
+		return UINT64_MAX;
+	}
+	fd = dirfd(dirstream);
+	if (fd < 0) {
+		DBG(BTRFS, ul_debug("dirfd(opendir()) failed for \"%s\" [errno=%d %m]", path, errno));
+		goto out;
+	}
+
+	memset(&args, 0, sizeof(args));
+	sk->tree_id = BTRFS_ROOT_TREE_OBJECTID;
+	sk->min_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID;
+	sk->max_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID;
+	sk->min_type = BTRFS_DIR_ITEM_KEY;
+	sk->max_type = BTRFS_DIR_ITEM_KEY;
+	sk->max_offset = UINT64_MAX;
+	sk->max_transid = UINT64_MAX;
+	sk->nr_items = 1;
+
+	iocret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
+	if (iocret < 0) {
+		DBG(BTRFS, ul_debug("ioctl() failed for \"%s\" [errno=%d %m]", path, errno));
+		goto out;
+	}
+
+	/* the ioctl returns the number of items it found in nr_items */
+	if (sk->nr_items == 0) {
+		DBG(BTRFS, ul_debug("root tree dir object id not found"));
+		goto out;
+	}
+	DBG(BTRFS, ul_debug("found %d root tree dir object id items", sk->nr_items));
+
+	sh = (struct btrfs_ioctl_search_header *)args.buf;
+
+	if (sh->type == BTRFS_DIR_ITEM_KEY) {
+		struct btrfs_dir_item *di;
+		int name_len;
+		char *name;
+
+		di = (struct btrfs_dir_item *)(sh + 1);
+		name_len = btrfs_stack_dir_name_len(di);
+		name = (char *)(di + 1);
+
+		if (!strncmp("default", name, name_len)) {
+			found = btrfs_disk_key_objectid(&di->location);
+			DBG(BTRFS, ul_debug("\"default\" id is %llu", (unsigned long long)found));
+		} else {
+			DBG(BTRFS, ul_debug("\"default\" id not found in tree root"));
+			goto out;
+		}
+	} else {
+		DBG(BTRFS, ul_debug("unexpected type found: %d", (int)sh->type));
+		goto out;
+	}
+
+out:
+	closedir(dirstream);
+
+	return found;
+}
Index: util-linux-2.25/libmount/src/btrfs.h
===================================================================
--- /dev/null
+++ util-linux-2.25/libmount/src/btrfs.h
@@ -0,0 +1,122 @@
+/* This is an excerpt from btrfs-progs-v4.3.1
+ * All kernel types are converted to stdint.h types. */
+
+/*
+ * Copyright (C) 2007 Oracle.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 021110-1307, USA.
+ */
+
+#include <libio.h>
+#include <stdint.h>
+#include <linux/btrfs.h>
+#include "mountP.h"
+#include "bitops.h"
+
+
+/* linux/btrfs.h lacks large parts of stuff needed for getting default
+ * sub-volume. Suppose that if BTRFS_DIR_ITEM_KEY is not defined, all
+ * declarations are still missing.
+ */
+#ifndef BTRFS_DIR_ITEM_KEY
+
+
+/* from ctree.h */
+
+/*
+ * dir items are the name -> inode pointers in a directory.  There is one
+ * for every name in a directory.
+ */
+#define BTRFS_DIR_ITEM_KEY	84
+
+/* holds pointers to all of the tree roots */
+#define BTRFS_ROOT_TREE_OBJECTID 1ULL
+
+/* directory objectid inside the root tree */
+#define BTRFS_ROOT_TREE_DIR_OBJECTID 6ULL
+
+/*
+ * the key defines the order in the tree, and so it also defines (optimal)
+ * block layout.  objectid corresonds to the inode number.  The flags
+ * tells us things about the object, and is a kind of stream selector.
+ * so for a given inode, keys with flags of 1 might refer to the inode
+ * data, flags of 2 may point to file data in the btree and flags == 3
+ * may point to extents.
+ *
+ * offset is the starting byte offset for this key in the stream.
+ *
+ * btrfs_disk_key is in disk byte order.  struct btrfs_key is always
+ * in cpu native order.  Otherwise they are identical and their sizes
+ * should be the same (ie both packed)
+ */
+struct btrfs_disk_key {
+	uint64_t objectid; /* little endian */
+	uint8_t type;
+	uint64_t offset; /* little endian */
+} __attribute__ ((__packed__));
+
+struct btrfs_dir_item {
+	struct btrfs_disk_key location;
+	uint64_t transid; /* little endian */
+	uint16_t data_len; /* little endian */
+	uint16_t name_len; /* little endian */
+	uint8_t type;
+} __attribute__ ((__packed__));
+
+#define BTRFS_SETGET_STACK_FUNCS(name, type, member, bits)		\
+static inline uint##bits##_t btrfs_##name(const type *s)		\
+{									\
+	return le##bits##_to_cpu(s->member);				\
+}									\
+static inline void btrfs_set_##name(type *s, uint##bits##_t val)	\
+{									\
+	s->member = cpu_to_le##bits(val);				\
+}
+
+/* struct btrfs_disk_key */
+BTRFS_SETGET_STACK_FUNCS(disk_key_objectid, struct btrfs_disk_key,
+			 objectid, 64);
+
+BTRFS_SETGET_STACK_FUNCS(stack_dir_name_len, struct btrfs_dir_item, name_len, 16);
+
+
+/* from rbtree.h */
+
+/*
+  Red Black Trees
+  (C) 1999  Andrea Arcangeli <andrea@suse.de>
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+struct rb_node {
+	unsigned long  __rb_parent_color;
+	struct rb_node *rb_right;
+	struct rb_node *rb_left;
+} __attribute__((aligned(sizeof(long))));
+    /* The alignment might seem pointless, but allegedly CRIS needs it */
+
+#endif
Index: util-linux-2.25/libmount/src/libmount.h.in
===================================================================
--- util-linux-2.25.orig/libmount/src/libmount.h.in
+++ util-linux-2.25/libmount/src/libmount.h.in
@@ -470,6 +470,8 @@ extern struct libmnt_fs *mnt_table_find_
 				const char *path, int direction);
 extern struct libmnt_fs *mnt_table_find_tag(struct libmnt_table *tb, const char *tag,
 				const char *val, int direction);
+extern struct libmnt_fs *mnt_table_find_target_with_option(struct libmnt_table *tb, const char *path,
+			const char *option, const char *val, int direction);
 extern struct libmnt_fs *mnt_table_find_source(struct libmnt_table *tb,
 				const char *source, int direction);
 extern struct libmnt_fs *mnt_table_find_pair(struct libmnt_table *tb,
Index: util-linux-2.25/libmount/src/mountP.h
===================================================================
--- util-linux-2.25.orig/libmount/src/mountP.h
+++ util-linux-2.25/libmount/src/mountP.h
@@ -48,6 +48,7 @@
 #define MNT_DEBUG_UTILS		(1 << 9)
 #define MNT_DEBUG_CXT		(1 << 10)
 #define MNT_DEBUG_DIFF		(1 << 11)
+#define MNT_DEBUG_BTRFS		(1 << 12)
 #define MNT_DEBUG_ALL		0xFFFF
 
 UL_DEBUG_DECLARE_MASK(libmount);
@@ -409,4 +410,9 @@ extern int mnt_update_set_filename(struc
 extern int mnt_update_already_done(struct libmnt_update *upd,
 				   struct libmnt_lock *lc);
 
+#if __linux__
+/* btrfs.c */
+extern uint64_t btrfs_get_default_subvol_id(const char *path);
+#endif
+
 #endif /* _LIBMOUNT_PRIVATE_H */
Index: util-linux-2.25/libmount/src/tab.c
===================================================================
--- util-linux-2.25.orig/libmount/src/tab.c
+++ util-linux-2.25/libmount/src/tab.c
@@ -1107,6 +1107,50 @@ struct libmnt_fs *mnt_table_find_tag(str
 }
 
 /**
+ * mnt_table_find_target_with_option:
+ * @tb: tab pointer
+ * @path: mountpoint directory
+ * @option: option name (e.g "subvol", "subvolid", ...)
+ * @val: option value or NULL
+ * @direction: MNT_ITER_{FORWARD,BACKWARD}
+ *
+ * Try to lookup an entry in the given tab that matches combination of @path
+ * and @option. In difference to mnt_table_find_target(), only @path iteration
+ * is done. No lookup by device name, no canonicalization.
+ *
+ * Returns: a tab entry or NULL.
+ *
+ * Since: 2.28
+ */
+struct libmnt_fs *mnt_table_find_target_with_option(
+			struct libmnt_table *tb, const char *path,
+			const char *option, const char *val, int direction)
+{
+	struct libmnt_iter itr;
+	struct libmnt_fs *fs = NULL;
+	char *optval = NULL;
+	size_t optvalsz = 0, valsz = val ? strlen(val) : 0;
+
+	if (!tb || !path || !*path || !option || !*option || !val)
+		return NULL;
+	if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
+		return NULL;
+
+	DBG(TAB, ul_debugobj(tb, "lookup TARGET: '%s' with OPTION %s %s", path, option, val));
+
+	/* look up by native @target with OPTION */
+	mnt_reset_iter(&itr, direction);
+	while (mnt_table_next_fs(tb, &itr, &fs) == 0) {
+		if (mnt_fs_streq_target(fs, path)
+		    && mnt_fs_get_option(fs, option, &optval, &optvalsz) == 0
+		    && (!val || (optvalsz == valsz
+				 && strncmp(optval, val, optvalsz) == 0)))
+			return fs;
+	}
+	return NULL;
+}
+
+/**
  * mnt_table_find_source:
  * @tb: tab pointer
  * @source: TAG or path
@@ -1296,9 +1340,10 @@ struct libmnt_fs *mnt_table_get_fs_root(
 		}
 
 		/* It's possible that fstab_fs source is subdirectory on btrfs
-		 * subvolume or anothe bind mount. For example:
+		 * subvolume or another bind mount. For example:
 		 *
 		 * /dev/sdc        /mnt/test       btrfs   subvol=/anydir
+		 * /dev/sdc        /mnt/test       btrfs   defaults
 		 * /mnt/test/foo   /mnt/test2      auto    bind
 		 *
 		 * in this case, the root for /mnt/test2 will be /anydir/foo on
@@ -1332,10 +1377,52 @@ struct libmnt_fs *mnt_table_get_fs_root(
 		char *vol = NULL, *p;
 		size_t sz, volsz = 0;
 
-		if (mnt_fs_get_option(fs, "subvol", &vol, &volsz))
-			goto dflt;
+		DBG(BTRFS, ul_debug("lookup for FS root"));
 
-		DBG(TAB, ul_debug("setting FS root: btrfs subvol"));
+		if (mnt_fs_get_option(fs, "subvol", &vol, &volsz)) {
+			/* If fstab entry does not contain "subvol", we have to
+			 * check, whether btrfs has default subvolume defined.
+			 */
+			uint64_t default_id;
+			char *target;
+			struct libmnt_fs *f;
+			char default_id_str[sizeof(stringify_value(UINT64_MAX))];
+
+			default_id = btrfs_get_default_subvol_id(mnt_fs_get_target(fs));
+			if (default_id == UINT64_MAX)
+				goto dflt;
+
+			/* Volume has default subvolume. Check if it matches to
+			 * the one in mountinfo.
+			 *
+			 * Only kernel >= 4.2 reports subvolid. On older
+			 * kernels, there is no reasonable way to detect which
+			 * subvolume was mounted.
+			 */
+			target = mnt_resolve_spec(mnt_fs_get_target(fs), tb->cache);
+			if (!target)
+				goto err;
+
+			snprintf(default_id_str, sizeof(default_id_str), "%llu",
+					(unsigned long long int) default_id);
+
+			DBG(BTRFS, ul_debug("target=%s subvolid=%s", target, default_id_str));
+			f = mnt_table_find_target_with_option(tb, target,
+						"subvolid", default_id_str,
+						MNT_ITER_BACKWARD);
+			if (!tb->cache)
+				free(target);
+			if (!f)
+				goto dflt;
+
+			/* Instead of set of BACKREF queries constructing
+			 * subvol path, use the one in mountinfo. Kernel does
+			 * the evaluation for us. */
+			DBG(BTRFS, ul_debug("setting FS root: btrfs default subvolid = %s", default_id_str));
+			if (mnt_fs_get_option(f, "subvol", &vol, &volsz))
+				goto dflt;
+		} else
+			DBG(BTRFS, ul_debug("setting FS root: btrfs subvol"));
 
 		sz = volsz;
 		if (*vol != '/')
