All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] libmount: handle btrfs default subvolume mount
@ 2016-01-20 20:34 Stanislav Brabec
  2016-01-20 21:51 ` Stanislav Brabec
  0 siblings, 1 reply; 18+ messages in thread
From: Stanislav Brabec @ 2016-01-20 20:34 UTC (permalink / raw)
  To: util-linux; +Cc: David Štěrba

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, libmount has to be capable to detect
default subvolume.

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

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

Signed-off-by: Stanislav Brabec <sbrabec@suse.cz>
Cc: David Štěrba <dsterba@suse.cz>
---
 libmount/src/Makemodule.am |   2 +
 libmount/src/btrfs.c       | 101 ++++++++++++++++++++++++++++++++
 libmount/src/btrfs.h       | 141 +++++++++++++++++++++++++++++++++++++++++++++
 libmount/src/mountP.h      |   9 +++
 libmount/src/tab.c         |  83 ++++++++++++++++++++++++--
 5 files changed, 332 insertions(+), 4 deletions(-)
 create mode 100644 libmount/src/btrfs.c
 create mode 100644 libmount/src/btrfs.h

diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am
index 11c6324..39d42d5 100644
--- a/libmount/src/Makemodule.am
+++ b/libmount/src/Makemodule.am
@@ -27,6 +27,8 @@ libmount_la_SOURCES = \
 
 if LINUX
 libmount_la_SOURCES += \
+	libmount/src/btrfs.c \
+	libmount/src/btrfs.h \
 	libmount/src/context.c \
 	libmount/src/context_loopdev.c \
 	libmount/src/context_mount.c \
diff --git a/libmount/src/btrfs.c b/libmount/src/btrfs.c
new file mode 100644
index 0000000..9122551
--- /dev/null
+++ b/libmount/src/btrfs.c
@@ -0,0 +1,101 @@
+/*
+ * 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 -1 in case of no default
+ * subvolume or error. In case of error, errno is set properly.
+ */
+__u64 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;
+	__u64 found = (__u64)-1;
+
+	dirstream = opendir(path);
+	if (!dirstream) {
+		DBG(BTRFS, ul_debug("opendir() failed for \"%s\" [errno=%d %m]", path, errno));
+		return (__u64)-1;
+	}
+	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 = (__u64)-1;
+	sk->max_transid = (__u64)-1;
+	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;
+}
diff --git a/libmount/src/btrfs.h b/libmount/src/btrfs.h
new file mode 100644
index 0000000..949664a
--- /dev/null
+++ b/libmount/src/btrfs.h
@@ -0,0 +1,141 @@
+/* This is an excerpt from btrfs-progs-v4.3.1
+ * differences: u64 replaced by __u64 */
+
+/*
+ * 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 <linux/btrfs.h>
+#include "mountP.h"
+
+
+/* from kerncompat.h */
+
+#ifdef __CHECKER__
+#define __force    __attribute__((force))
+#else
+#define __force
+#endif
+
+#ifndef cpu_to_le64
+#if __BYTE_ORDER == __BIG_ENDIAN
+#define cpu_to_le64(x) ((__force __le64)(__u64)(bswap_64(x)))
+#define le64_to_cpu(x) ((__force __u64)(__le64)(bswap_64(x)))
+#else
+#define cpu_to_le64(x) ((__force __le64)(__u64)(x))
+#define le64_to_cpu(x) ((__force __u64)(__le64)(x))
+#endif
+#endif
+
+/* 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 {
+	__le64 objectid;
+	__u8 type;
+	__le64 offset;
+} __attribute__ ((__packed__));
+
+struct btrfs_dir_item {
+	struct btrfs_disk_key location;
+	__le64 transid;
+	__le16 data_len;
+	__le16 name_len;
+	__u8 type;
+} __attribute__ ((__packed__));
+
+#define BTRFS_SETGET_STACK_FUNCS(name, type, member, bits)		\
+static inline __u##bits btrfs_##name(const type *s)			\
+{									\
+	return le##bits##_to_cpu(s->member);				\
+}									\
+static inline void btrfs_set_##name(type *s, __u##bits 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);
+
+static inline __u16 btrfs_stack_dir_name_len(const struct btrfs_dir_item *s)
+{
+	return ((__u16)(__le16)(s->name_len));
+}
+
+
+/* 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
diff --git a/libmount/src/mountP.h b/libmount/src/mountP.h
index 25418a2..5362b2b 100644
--- a/libmount/src/mountP.h
+++ b/libmount/src/mountP.h
@@ -19,6 +19,9 @@
 #include <unistd.h>
 #include <stdio.h>
 #include <stdarg.h>
+#if __linux__
+#include <linux/types.h>
+#endif
 
 #include "c.h"
 #include "list.h"
@@ -40,6 +43,7 @@
 #define MNT_DEBUG_CXT		(1 << 9)
 #define MNT_DEBUG_DIFF		(1 << 10)
 #define MNT_DEBUG_MONITOR	(1 << 11)
+#define MNT_DEBUG_BTRFS		(1 << 12)
 
 #define MNT_DEBUG_ALL		0xFFFF
 
@@ -411,4 +415,9 @@ extern int mnt_update_set_filename(struct libmnt_update *upd,
 extern int mnt_update_already_done(struct libmnt_update *upd,
 				   struct libmnt_lock *lc);
 
+#if __linux__
+/* btrfs.c */
+extern __u64 btrfs_get_default_subvol_id(const char *path);
+#endif
+
 #endif /* _LIBMOUNT_PRIVATE_H */
diff --git a/libmount/src/tab.c b/libmount/src/tab.c
index 951fe8c..d33ab70 100644
--- a/libmount/src/tab.c
+++ b/libmount/src/tab.c
@@ -1059,6 +1059,49 @@ struct libmnt_fs *mnt_table_find_tag(struct libmnt_table *tb, const char *tag,
 }
 
 /**
+ * mnt_table_find_target_with_option:
+ * @tb: tab pointer
+ * @path: mountpoint directory
+ * @option: option name (e.g "subvol", "subvolid", ...)
+ * @val: option value
+ * @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.
+ */
+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 = strlen(val);
+
+	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))
+		{
+			if (!mnt_fs_get_option(fs, option, &optval, &optvalsz))
+				if ((optvalsz == valsz) &&
+				    !strncmp(optval, val, optvalsz))
+					return fs;
+		}
+	}
+	return NULL;
+}
+
+/**
  * mnt_table_find_source:
  * @tb: tab pointer
  * @source: TAG or path
@@ -1241,9 +1284,10 @@ struct libmnt_fs *mnt_table_get_fs_root(struct libmnt_table *tb,
 		}
 
 		/* 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
@@ -1278,9 +1322,40 @@ struct libmnt_fs *mnt_table_get_fs_root(struct libmnt_table *tb,
 		size_t sz, volsz = 0;
 
 		if (mnt_fs_get_option(fs, "subvol", &vol, &volsz))
-			goto dflt;
-
-		DBG(TAB, ul_debug("setting FS root: btrfs subvol"));
+		{
+			/* If fstab entry does not contain "subvol", we have to
+			 * check, whether btrfs has default subvolume
+			 * defined. */
+
+			__u64 default_id;
+			const char *target;
+			char default_id_str[16]; /* should be safe for u64 */
+
+			default_id = btrfs_get_default_subvol_id(mnt_fs_get_target(fs));
+			if (default_id == (__u64)-1)
+				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);
+			snprintf(default_id_str, 16, "%llu", (unsigned long long int)default_id);
+			DBG(TAB, ul_debug("target = %s subvolid = %s", target, &default_id_str));
+			struct libmnt_fs *f = mnt_table_find_target_with_option(tb, target, "subvolid", default_id_str, MNT_ITER_BACKWARD);
+			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(TAB, 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(TAB, ul_debug("setting FS root: btrfs subvol"));
 
 		sz = volsz;
 		if (*vol != '/')
-- 
2.7.0

-- 
Best Regards / S pozdravem,

Stanislav Brabec
software developer
---------------------------------------------------------------------
SUSE LINUX, s. r. o.                         e-mail: sbrabec@suse.com
Lihovarská 1060/12                            tel: +49 911 7405384547
190 00 Praha 9                                 fax:  +420 284 084 001
Czech Republic                                    http://www.suse.cz/
PGP: 830B 40D5 9E05 35D8 5E27 6FA3 717C 209F A04F CD76

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [PATCH] libmount: handle btrfs default subvolume mount
  2016-01-20 20:34 [PATCH] libmount: handle btrfs default subvolume mount Stanislav Brabec
@ 2016-01-20 21:51 ` Stanislav Brabec
  2016-01-20 21:57   ` Stanislav Brabec
  0 siblings, 1 reply; 18+ messages in thread
From: Stanislav Brabec @ 2016-01-20 21:51 UTC (permalink / raw)
  To: util-linux; +Cc: David Štěrba

Stanislav Brabec wrote:

 > +#ifndef cpu_to_le64
 > +#if __BYTE_ORDER == __BIG_ENDIAN
 > +#define cpu_to_le64(x) ((__force __le64)(__u64)(bswap_64(x)))
 > +#define le64_to_cpu(x) ((__force __u64)(__le64)(bswap_64(x)))

I just found that my patch does not compile on big endian.
I will send a new patch that uses bitops.h to fix that.

-- 
Best Regards / S pozdravem,

Stanislav Brabec
software developer
---------------------------------------------------------------------
SUSE LINUX, s. r. o.                         e-mail: sbrabec@suse.com
Lihovarská 1060/12                            tel: +49 911 7405384547
190 00 Praha 9                                 fax:  +420 284 084 001
Czech Republic                                    http://www.suse.cz/
PGP: 830B 40D5 9E05 35D8 5E27 6FA3 717C 209F A04F CD76

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH] libmount: handle btrfs default subvolume mount
  2016-01-20 21:51 ` Stanislav Brabec
@ 2016-01-20 21:57   ` Stanislav Brabec
  2016-01-21  9:48     ` Karel Zak
  0 siblings, 1 reply; 18+ messages in thread
From: Stanislav Brabec @ 2016-01-20 21:57 UTC (permalink / raw)
  To: util-linux; +Cc: David Štěrba

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, libmount has to be capable to detect
default subvolume.

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

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

Signed-off-by: Stanislav Brabec <sbrabec@suse.cz>
Cc: David Štěrba <dsterba@suse.cz>
---
 libmount/src/Makemodule.am |   2 +
 libmount/src/btrfs.c       | 101 ++++++++++++++++++++++++++++++++++
 libmount/src/btrfs.h       | 132 +++++++++++++++++++++++++++++++++++++++++++++
 libmount/src/mountP.h      |   9 ++++
 libmount/src/tab.c         |  83 ++++++++++++++++++++++++++--
 5 files changed, 323 insertions(+), 4 deletions(-)
 create mode 100644 libmount/src/btrfs.c
 create mode 100644 libmount/src/btrfs.h

diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am
index 11c6324..39d42d5 100644
--- a/libmount/src/Makemodule.am
+++ b/libmount/src/Makemodule.am
@@ -27,6 +27,8 @@ libmount_la_SOURCES = \
 
 if LINUX
 libmount_la_SOURCES += \
+	libmount/src/btrfs.c \
+	libmount/src/btrfs.h \
 	libmount/src/context.c \
 	libmount/src/context_loopdev.c \
 	libmount/src/context_mount.c \
diff --git a/libmount/src/btrfs.c b/libmount/src/btrfs.c
new file mode 100644
index 0000000..9122551
--- /dev/null
+++ b/libmount/src/btrfs.c
@@ -0,0 +1,101 @@
+/*
+ * 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 -1 in case of no default
+ * subvolume or error. In case of error, errno is set properly.
+ */
+__u64 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;
+	__u64 found = (__u64)-1;
+
+	dirstream = opendir(path);
+	if (!dirstream) {
+		DBG(BTRFS, ul_debug("opendir() failed for \"%s\" [errno=%d %m]", path, errno));
+		return (__u64)-1;
+	}
+	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 = (__u64)-1;
+	sk->max_transid = (__u64)-1;
+	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;
+}
diff --git a/libmount/src/btrfs.h b/libmount/src/btrfs.h
new file mode 100644
index 0000000..d78cf35
--- /dev/null
+++ b/libmount/src/btrfs.h
@@ -0,0 +1,132 @@
+/* This is an excerpt from btrfs-progs-v4.3.1
+ * differences: u64 replaced by __u64 */
+
+/*
+ * 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 <linux/btrfs.h>
+#include "mountP.h"
+#include "bitops.h"
+
+
+/* from kerncompat.h */
+
+#ifdef __CHECKER__
+#define __force    __attribute__((force))
+#else
+#define __force
+#endif
+
+/* 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 {
+	__le64 objectid;
+	__u8 type;
+	__le64 offset;
+} __attribute__ ((__packed__));
+
+struct btrfs_dir_item {
+	struct btrfs_disk_key location;
+	__le64 transid;
+	__le16 data_len;
+	__le16 name_len;
+	__u8 type;
+} __attribute__ ((__packed__));
+
+#define BTRFS_SETGET_STACK_FUNCS(name, type, member, bits)		\
+static inline __u##bits btrfs_##name(const type *s)			\
+{									\
+	return le##bits##_to_cpu(s->member);				\
+}									\
+static inline void btrfs_set_##name(type *s, __u##bits 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);
+
+static inline __u16 btrfs_stack_dir_name_len(const struct btrfs_dir_item *s)
+{
+	return ((__u16)(__le16)(s->name_len));
+}
+
+
+/* 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
diff --git a/libmount/src/mountP.h b/libmount/src/mountP.h
index 25418a2..5362b2b 100644
--- a/libmount/src/mountP.h
+++ b/libmount/src/mountP.h
@@ -19,6 +19,9 @@
 #include <unistd.h>
 #include <stdio.h>
 #include <stdarg.h>
+#if __linux__
+#include <linux/types.h>
+#endif
 
 #include "c.h"
 #include "list.h"
@@ -40,6 +43,7 @@
 #define MNT_DEBUG_CXT		(1 << 9)
 #define MNT_DEBUG_DIFF		(1 << 10)
 #define MNT_DEBUG_MONITOR	(1 << 11)
+#define MNT_DEBUG_BTRFS		(1 << 12)
 
 #define MNT_DEBUG_ALL		0xFFFF
 
@@ -411,4 +415,9 @@ extern int mnt_update_set_filename(struct libmnt_update *upd,
 extern int mnt_update_already_done(struct libmnt_update *upd,
 				   struct libmnt_lock *lc);
 
+#if __linux__
+/* btrfs.c */
+extern __u64 btrfs_get_default_subvol_id(const char *path);
+#endif
+
 #endif /* _LIBMOUNT_PRIVATE_H */
diff --git a/libmount/src/tab.c b/libmount/src/tab.c
index 951fe8c..d33ab70 100644
--- a/libmount/src/tab.c
+++ b/libmount/src/tab.c
@@ -1059,6 +1059,49 @@ struct libmnt_fs *mnt_table_find_tag(struct libmnt_table *tb, const char *tag,
 }
 
 /**
+ * mnt_table_find_target_with_option:
+ * @tb: tab pointer
+ * @path: mountpoint directory
+ * @option: option name (e.g "subvol", "subvolid", ...)
+ * @val: option value
+ * @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.
+ */
+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 = strlen(val);
+
+	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))
+		{
+			if (!mnt_fs_get_option(fs, option, &optval, &optvalsz))
+				if ((optvalsz == valsz) &&
+				    !strncmp(optval, val, optvalsz))
+					return fs;
+		}
+	}
+	return NULL;
+}
+
+/**
  * mnt_table_find_source:
  * @tb: tab pointer
  * @source: TAG or path
@@ -1241,9 +1284,10 @@ struct libmnt_fs *mnt_table_get_fs_root(struct libmnt_table *tb,
 		}
 
 		/* 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
@@ -1278,9 +1322,40 @@ struct libmnt_fs *mnt_table_get_fs_root(struct libmnt_table *tb,
 		size_t sz, volsz = 0;
 
 		if (mnt_fs_get_option(fs, "subvol", &vol, &volsz))
-			goto dflt;
-
-		DBG(TAB, ul_debug("setting FS root: btrfs subvol"));
+		{
+			/* If fstab entry does not contain "subvol", we have to
+			 * check, whether btrfs has default subvolume
+			 * defined. */
+
+			__u64 default_id;
+			const char *target;
+			char default_id_str[16]; /* should be safe for u64 */
+
+			default_id = btrfs_get_default_subvol_id(mnt_fs_get_target(fs));
+			if (default_id == (__u64)-1)
+				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);
+			snprintf(default_id_str, 16, "%llu", (unsigned long long int)default_id);
+			DBG(TAB, ul_debug("target = %s subvolid = %s", target, &default_id_str));
+			struct libmnt_fs *f = mnt_table_find_target_with_option(tb, target, "subvolid", default_id_str, MNT_ITER_BACKWARD);
+			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(TAB, 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(TAB, ul_debug("setting FS root: btrfs subvol"));
 
 		sz = volsz;
 		if (*vol != '/')
-- 
2.7.0

-- 
Best Regards / S pozdravem,

Stanislav Brabec
software developer
---------------------------------------------------------------------
SUSE LINUX, s. r. o.                         e-mail: sbrabec@suse.com
Lihovarská 1060/12                            tel: +49 911 7405384547
190 00 Praha 9                                 fax:  +420 284 084 001
Czech Republic                                    http://www.suse.cz/
PGP: 830B 40D5 9E05 35D8 5E27 6FA3 717C 209F A04F CD76

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [PATCH] libmount: handle btrfs default subvolume mount
  2016-01-20 21:57   ` Stanislav Brabec
@ 2016-01-21  9:48     ` Karel Zak
  2016-01-21 15:24       ` Stanislav Brabec
  2016-01-21 21:58       ` Stanislav Brabec
  0 siblings, 2 replies; 18+ messages in thread
From: Karel Zak @ 2016-01-21  9:48 UTC (permalink / raw)
  To: Stanislav Brabec; +Cc: util-linux, David Štěrba

On Wed, Jan 20, 2016 at 10:57:07PM +0100, Stanislav Brabec wrote:
> +/**
> + * btrfs_get_default_subvol_id:
> + * @path: Path to mounted btrfs volume

this comment formatting indicate API docs, for private library functions
we don't use "/**".

> + * Searches for the btrfs default subvolume id.
> + *
> + * Returns: default subvolume id or -1 in case of no default
> + * subvolume or error. In case of error, errno is set properly.
> + */
> +__u64 btrfs_get_default_subvol_id(const char *path)

Please, use standard userspace types, for example uint64_t. This is
generic rule, kernel-like types are unacceptable.

> +{
> +	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;
> +	__u64 found = (__u64)-1;

   = UINT64_MAX

> +struct btrfs_disk_key {
> +	__le64 objectid;
> +	__u8 type;
> +	__le64 offset;
> +} __attribute__ ((__packed__));
> +
> +struct btrfs_dir_item {
> +	struct btrfs_disk_key location;
> +	__le64 transid;
> +	__le16 data_len;
> +	__le16 name_len;
> +	__u8 type;
> +} __attribute__ ((__packed__));

again, use int64_t, uint8_t, etc.

>  /**
> + * mnt_table_find_target_with_option:
> + * @tb: tab pointer
> + * @path: mountpoint directory
> + * @option: option name (e.g "subvol", "subvolid", ...)
> + * @val: option value
> + * @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.
> + */

 if you want to export this function by API then it's necessary to add
 to libmount.h.in and libmount.sym, otherwise don't use "/**".

> -		DBG(TAB, ul_debug("setting FS root: btrfs subvol"));
> +		{
> +			/* If fstab entry does not contain "subvol", we have to
> +			 * check, whether btrfs has default subvolume
> +			 * defined. */
> +
> +			__u64 default_id;
> +			const char *target;
> +			char default_id_str[16]; /* should be safe for u64 */

   char default_id_str[sizeof(stringify_value(UINT64_MAX))];

> +
> +			default_id = btrfs_get_default_subvol_id(mnt_fs_get_target(fs));
> +			if (default_id == (__u64)-1)
> +				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);
> +			snprintf(default_id_str, 16, "%llu", (unsigned long long int)default_id);

    snprintf(default_id_str, sizeof(default_id_str), ....


> +			DBG(TAB, ul_debug("target = %s subvolid = %s", target, &default_id_str));
> +			struct libmnt_fs *f = mnt_table_find_target_with_option(tb, target, "subvolid", default_id_str, MNT_ITER_BACKWARD);
> +			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(TAB, 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(TAB, ul_debug("setting FS root: btrfs subvol"));

    Karel

-- 
 Karel Zak  <kzak@redhat.com>
 http://karelzak.blogspot.com

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] libmount: handle btrfs default subvolume mount
  2016-01-21  9:48     ` Karel Zak
@ 2016-01-21 15:24       ` Stanislav Brabec
  2016-01-21 15:37         ` Karel Zak
  2016-01-21 21:58       ` Stanislav Brabec
  1 sibling, 1 reply; 18+ messages in thread
From: Stanislav Brabec @ 2016-01-21 15:24 UTC (permalink / raw)
  To: Karel Zak; +Cc: util-linux, David Štěrba

Karel Zak wrote:
> On Wed, Jan 20, 2016 at 10:57:07PM +0100, Stanislav Brabec wrote:
>> +/**
>> + * btrfs_get_default_subvol_id:
>> + * @path: Path to mounted btrfs volume
>
> this comment formatting indicate API docs, for private library functions
> we don't use "/**".
>
Should I export this? I think that no.

Well, looking again into libbtrfs, it is also possible to use
btrfs_list_get_default_subvolume() from libbtrfs, as is nearly exactly 
the same. But I am not sure, whether it is good to introduce such a
dependency just for a single small function.

P. S.:

David also wrote btrfs_get_default_subvolume_path(), but then we found,
that there is probably better to use subvolid and path saved in
procinfo:

+ The tab.c patch would be much smaller and straightforward.
- Kernel evaluates it when creating procinfo entries, this would
   duplicate it.
x Both subvol and subvolid in procinfo were introduced together in the
   mainline kernel, and btrfs_get_default_subvolume_path() needs more
   ioctl() calls.

-- 
Best Regards / S pozdravem,

Stanislav Brabec
software developer
---------------------------------------------------------------------
SUSE LINUX, s. r. o.                         e-mail: sbrabec@suse.com
Lihovarská 1060/12                            tel: +49 911 7405384547
190 00 Praha 9                                 fax:  +420 284 084 001
Czech Republic                                    http://www.suse.cz/
PGP: 830B 40D5 9E05 35D8 5E27 6FA3 717C 209F A04F CD76

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] libmount: handle btrfs default subvolume mount
  2016-01-21 15:24       ` Stanislav Brabec
@ 2016-01-21 15:37         ` Karel Zak
  2016-01-21 15:45           ` Karel Zak
  0 siblings, 1 reply; 18+ messages in thread
From: Karel Zak @ 2016-01-21 15:37 UTC (permalink / raw)
  To: Stanislav Brabec; +Cc: util-linux, David Štěrba

On Thu, Jan 21, 2016 at 04:24:55PM +0100, Stanislav Brabec wrote:
> Karel Zak wrote:
> >On Wed, Jan 20, 2016 at 10:57:07PM +0100, Stanislav Brabec wrote:
> >>+/**
> >>+ * btrfs_get_default_subvol_id:
> >>+ * @path: Path to mounted btrfs volume
> >
> >this comment formatting indicate API docs, for private library functions
> >we don't use "/**".
> >
> Should I export this? I think that no.

It's private function.

> 
> Well, looking again into libbtrfs, it is also possible to use
> btrfs_list_get_default_subvolume() from libbtrfs, as is nearly exactly the
> same. But I am not sure, whether it is good to introduce such a
> dependency just for a single small function.

I really don't want to depend on libbtrfs.

> P. S.:
> 
> David also wrote btrfs_get_default_subvolume_path(), but then we found,
> that there is probably better to use subvolid and path saved in
> procinfo:

Do you mean /proc/self/mountinfo ?

> 
> + The tab.c patch would be much smaller and straightforward.
> - Kernel evaluates it when creating procinfo entries, this would
>   duplicate it.
> x Both subvol and subvolid in procinfo were introduced together in the
>   mainline kernel, and btrfs_get_default_subvolume_path() needs more
>   ioctl() calls.

No sure if I follow.

    Karel

-- 
 Karel Zak  <kzak@redhat.com>
 http://karelzak.blogspot.com

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] libmount: handle btrfs default subvolume mount
  2016-01-21 15:37         ` Karel Zak
@ 2016-01-21 15:45           ` Karel Zak
  2016-01-21 17:24             ` Stanislav Brabec
  2016-01-22  8:42             ` David Sterba
  0 siblings, 2 replies; 18+ messages in thread
From: Karel Zak @ 2016-01-21 15:45 UTC (permalink / raw)
  To: Stanislav Brabec; +Cc: util-linux, David Štěrba

On Thu, Jan 21, 2016 at 04:37:13PM +0100, Karel Zak wrote:
> > David also wrote btrfs_get_default_subvolume_path(), but then we found,
> > that there is probably better to use subvolid and path saved in
> > procinfo:
> 
> Do you mean /proc/self/mountinfo ?
> 
> > 
> > + The tab.c patch would be much smaller and straightforward.
> > - Kernel evaluates it when creating procinfo entries, this would
> >   duplicate it.
> > x Both subvol and subvolid in procinfo were introduced together in the
> >   mainline kernel, and btrfs_get_default_subvolume_path() needs more
> >   ioctl() calls.
> 
> No sure if I follow.

Read it and the patch again, and now it makes sense. Yes, the ioctl and 
subvolid= from mountinfo seems better than dependence on libbtrfs.

    Karel.

-- 
 Karel Zak  <kzak@redhat.com>
 http://karelzak.blogspot.com

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] libmount: handle btrfs default subvolume mount
  2016-01-21 15:45           ` Karel Zak
@ 2016-01-21 17:24             ` Stanislav Brabec
  2016-01-22  8:42             ` David Sterba
  1 sibling, 0 replies; 18+ messages in thread
From: Stanislav Brabec @ 2016-01-21 17:24 UTC (permalink / raw)
  To: Karel Zak; +Cc: util-linux, David Štěrba

Karel Zak wrote:
> On Thu, Jan 21, 2016 at 04:37:13PM +0100, Karel Zak wrote:
>>> David also wrote btrfs_get_default_subvolume_path(), but then we found,
>>> that there is probably better to use subvolid and path saved in
>>> procinfo:
>>
>> Do you mean /proc/self/mountinfo ?
Yes.

> Read it and the patch again, and now it makes sense. Yes, the ioctl and
> subvolid= from mountinfo seems better than dependence on libbtrfs.

Well, the function btrfs_get_default_subvolume_path() does not exist in
libbtrfs.

Our first implementation did not use subvolid search in mountinfo and
constructed the default path by using BACKREF chain evaluation using
btrfs specific ioctl().

The required tab.c change was then very small:
 		if (mnt_fs_get_option(fs, "subvol", &vol, &volsz))
-			goto dflt;
+		{
+			vol = btrfs_get_default_subvolume_path(mnt_fs_get_target(fs));
+			if (!vol)
+				goto dflt;
+			volsz = strlen(vol);
+		} else

But btrfs_get_default_subvolume_path() was complicated. It has to been
written from scratch, as btfs-progs read the whole subvolume table to
memory and performs evaluation in the memory. It is something we really
don't want on system with thousands snapshots.

The patch I sent uses ioctl() to get subvolid, and the rest is found in
mountinfo. It looks better to me.

If somebody is interested in the function that went to the trash, here
it is. Maybe it could be useful for somebody else.

static inline bool prepend_path(char subvol_buffer[PATH_MAX], int *subvol_buffer_index, char *name, int namelen)
{
	if (namelen == 0)
		return true;
	while (name[namelen - 1] == '/') {
		namelen--;
		if (namelen == 0)
			return true;
	}
	/* PATH_MAX - 1 could be fatal here, but it would fail elsewhere anyway. */
	if (*subvol_buffer_index < namelen + 1) {
		printf("PATH_MAX exceeded\n");
		return false;
	}
	/* Is not it the last item? */
	if (*subvol_buffer_index != PATH_MAX - 1) {
		subvol_buffer[*subvol_buffer_index - 1] = '/';
		(*subvol_buffer_index)--;
	}
	*subvol_buffer_index -= namelen;
	memcpy(&subvol_buffer[*subvol_buffer_index], name, namelen);
	return true;
}
	
/**
 * btrfs_get_default_subvolume_path:
 * @path: Path to mounted btrfs volume
 *
 * Evaluates btrfs tree and returns path to the default subvolume
 * relative to the volume root.
 *
 * Returns: default subvolume path or NULL in case of no default
 * subvolume or error. In case of error, errno is set properly.
 */
char *btrfs_get_default_subvolume_path(const char *path)
{
	int iocret;
	int fd = -1;
	DIR *dirstream = NULL;
	struct btrfs_ioctl_search_args args;
	struct btrfs_ioctl_search_key *sk = &args.key;
	struct btrfs_ioctl_search_header *sh;
	u64 found = 0;
	u64 parent = 0;
	u64 dirid = 0;
	struct btrfs_ioctl_ino_lookup_args lookup_args;
	struct btrfs_root_ref *root_ref;
	char subvol_path[PATH_MAX];
	int subvol_path_index = PATH_MAX-1;
	int namelen;
	char *ret_path = NULL;

	errno = 0;

	subvol_path[PATH_MAX - 1] = 0;

	fd = btrfs_open_dir(path, &dirstream, 1);
	if (fd < 0) {
		printf("ERROR: open\n");
		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 = (u64)-1;
	sk->max_transid = (u64)-1;
	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;
	}

next_level:
	/* look up parent */
	DBG(BTRFS, ul_debug("lookup BACKREFs for %llu", (unsigned long long)found));
	memset(&args, 0, sizeof(args));

	sk->tree_id = BTRFS_ROOT_TREE_OBJECTID;
	sk->min_objectid = found;
	sk->max_objectid = found;
	sk->min_type = BTRFS_ROOT_BACKREF_KEY;
	sk->max_type = BTRFS_ROOT_BACKREF_KEY;
	sk->min_offset = 0;
	sk->max_offset = (u64)-1;
	sk->max_transid = (u64)-1;
	sk->nr_items = 4096;

	iocret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
	if (iocret < 0) {
		DBG(BTRFS, ul_debug("ioctl() failed [errno=%d %m]", errno));
		goto out;
	}

	/* the ioctl returns the number of items it found in nr_items */
	if (sk->nr_items == 0) {
		DBG(BTRFS, ul_debug("BACKREF not found"));
		goto out;
	}

	sh = (struct btrfs_ioctl_search_header *)args.buf;

	if (sh->type == BTRFS_ROOT_BACKREF_KEY) {
		char *name;

		parent = sh->offset;
		root_ref = (struct btrfs_root_ref*)(sh + 1);
		dirid = btrfs_stack_root_ref_dirid(root_ref);
		DBG(BTRFS, ul_debug("BACKREF points to %llu dirid %llu",
				    (unsigned long long)sh->offset,
				    (unsigned long long)dirid));
		namelen = btrfs_stack_root_ref_name_len(root_ref);
		name = (char *)(root_ref + 1);
		DBG(BTRFS, ul_debug("path component: %*s", namelen, name));

		/* The current implementation never adds trailing '/'
		   here. But it is an implementation detail and we
		   should not depend on it. */
		if (!prepend_path(subvol_path, &subvol_path_index, name, namelen)) {
			goto out;
		}
	} else {
		DBG(BTRFS, ul_debug("BACKREF key not found: %d", (int)sh->type));
		goto out;
	}

	memset(&lookup_args, 0, sizeof(lookup_args));
	lookup_args.treeid = parent;
	lookup_args.objectid = dirid;
	DBG(BTRFS, ul_debug("DIRID resolve for %llu", (unsigned long long)dirid));
	iocret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &lookup_args);
	if (iocret < 0) {
		DBG(BTRFS, ul_debug("DIRID search ioctl() failed %llu [errno=%d %m]", (unsigned long long)dirid, errno));
		goto out;
	}
	DBG(BTRFS, ul_debug("path component: %s", lookup_args.name));
	namelen = strlen(lookup_args.name);
	/* The current implementation always adds trailing '/' here.
	   But it is an implementation detail and we should not depend
	   on it. */
		if (!prepend_path(subvol_path, &subvol_path_index, lookup_args.name, namelen)) {
			goto out;
		}
	if (parent != BTRFS_FS_TREE_OBJECTID) {
		DBG(BTRFS, ul_debug("NOT at the toplevel, looping"));
		found = parent;
		goto next_level;
	}

	close_file_or_dir(fd, dirstream);

	return strdup(&subvol_path[subvol_path_index]);
out:
	return NULL;
}


-- 
Best Regards / S pozdravem,

Stanislav Brabec
software developer
---------------------------------------------------------------------
SUSE LINUX, s. r. o.                         e-mail: sbrabec@suse.com
Lihovarská 1060/12                            tel: +49 911 7405384547
190 00 Praha 9                                 fax:  +420 284 084 001
Czech Republic                                    http://www.suse.cz/
PGP: 830B 40D5 9E05 35D8 5E27 6FA3 717C 209F A04F CD76

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH] libmount: handle btrfs default subvolume mount
  2016-01-21  9:48     ` Karel Zak
  2016-01-21 15:24       ` Stanislav Brabec
@ 2016-01-21 21:58       ` Stanislav Brabec
  2016-01-26 10:15         ` Karel Zak
  2016-01-28 14:22         ` Stanislav Brabec
  1 sibling, 2 replies; 18+ messages in thread
From: Stanislav Brabec @ 2016-01-21 21:58 UTC (permalink / raw)
  To: Karel Zak; +Cc: util-linux, David Štěrba

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, libmount has to be capable to detect
default subvolume.

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

This patch adds one new function to libmount API. Please update SONAME
before release.

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

Signed-off-by: Stanislav Brabec <sbrabec@suse.cz>
Cc: David Štěrba <dsterba@suse.cz>
---
 libmount/src/Makemodule.am |   2 +
 libmount/src/btrfs.c       | 102 +++++++++++++++++++++++++++++++++++++
 libmount/src/btrfs.h       | 122 +++++++++++++++++++++++++++++++++++++++++++++
 libmount/src/libmount.h.in |   2 +
 libmount/src/mountP.h      |   6 +++
 libmount/src/tab.c         |  83 ++++++++++++++++++++++++++++--
 6 files changed, 313 insertions(+), 4 deletions(-)
 create mode 100644 libmount/src/btrfs.c
 create mode 100644 libmount/src/btrfs.h

diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am
index 11c6324..39d42d5 100644
--- a/libmount/src/Makemodule.am
+++ b/libmount/src/Makemodule.am
@@ -27,6 +27,8 @@ libmount_la_SOURCES = \
 
 if LINUX
 libmount_la_SOURCES += \
+	libmount/src/btrfs.c \
+	libmount/src/btrfs.h \
 	libmount/src/context.c \
 	libmount/src/context_loopdev.c \
 	libmount/src/context_mount.c \
diff --git a/libmount/src/btrfs.c b/libmount/src/btrfs.c
new file mode 100644
index 0000000..3c61a2a
--- /dev/null
+++ b/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;
+}
diff --git a/libmount/src/btrfs.h b/libmount/src/btrfs.h
new file mode 100644
index 0000000..ca49df5
--- /dev/null
+++ b/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
diff --git a/libmount/src/libmount.h.in b/libmount/src/libmount.h.in
index 2338cc6..91b1698 100644
--- a/libmount/src/libmount.h.in
+++ b/libmount/src/libmount.h.in
@@ -483,6 +483,8 @@ extern struct libmnt_fs *mnt_table_find_srcpath(struct libmnt_table *tb,
 				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,
diff --git a/libmount/src/mountP.h b/libmount/src/mountP.h
index 25418a2..0bc67e5 100644
--- a/libmount/src/mountP.h
+++ b/libmount/src/mountP.h
@@ -40,6 +40,7 @@
 #define MNT_DEBUG_CXT		(1 << 9)
 #define MNT_DEBUG_DIFF		(1 << 10)
 #define MNT_DEBUG_MONITOR	(1 << 11)
+#define MNT_DEBUG_BTRFS		(1 << 12)
 
 #define MNT_DEBUG_ALL		0xFFFF
 
@@ -411,4 +412,9 @@ extern int mnt_update_set_filename(struct libmnt_update *upd,
 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 */
diff --git a/libmount/src/tab.c b/libmount/src/tab.c
index 951fe8c..b55d07e 100644
--- a/libmount/src/tab.c
+++ b/libmount/src/tab.c
@@ -1059,6 +1059,49 @@ struct libmnt_fs *mnt_table_find_tag(struct libmnt_table *tb, const char *tag,
 }
 
 /**
+ * mnt_table_find_target_with_option:
+ * @tb: tab pointer
+ * @path: mountpoint directory
+ * @option: option name (e.g "subvol", "subvolid", ...)
+ * @val: option value
+ * @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.
+ */
+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 = strlen(val);
+
+	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))
+		{
+			if (!mnt_fs_get_option(fs, option, &optval, &optvalsz))
+				if ((optvalsz == valsz) &&
+				    !strncmp(optval, val, optvalsz))
+					return fs;
+		}
+	}
+	return NULL;
+}
+
+/**
  * mnt_table_find_source:
  * @tb: tab pointer
  * @source: TAG or path
@@ -1241,9 +1284,10 @@ struct libmnt_fs *mnt_table_get_fs_root(struct libmnt_table *tb,
 		}
 
 		/* 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
@@ -1278,9 +1322,40 @@ struct libmnt_fs *mnt_table_get_fs_root(struct libmnt_table *tb,
 		size_t sz, volsz = 0;
 
 		if (mnt_fs_get_option(fs, "subvol", &vol, &volsz))
-			goto dflt;
-
-		DBG(TAB, ul_debug("setting FS root: btrfs subvol"));
+		{
+			/* If fstab entry does not contain "subvol", we have to
+			 * check, whether btrfs has default subvolume
+			 * defined. */
+
+			uint64_t default_id;
+			const char *target;
+			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);
+			snprintf(default_id_str, sizeof(default_id_str), "%llu", (unsigned long long int)default_id);
+			DBG(TAB, ul_debug("target = %s subvolid = %s", target, &default_id_str));
+			struct libmnt_fs *f = mnt_table_find_target_with_option(tb, target, "subvolid", default_id_str, MNT_ITER_BACKWARD);
+			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(TAB, 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(TAB, ul_debug("setting FS root: btrfs subvol"));
 
 		sz = volsz;
 		if (*vol != '/')
-- 
2.7.0

-- 
Best Regards / S pozdravem,

Stanislav Brabec
software developer
---------------------------------------------------------------------
SUSE LINUX, s. r. o.                         e-mail: sbrabec@suse.com
Lihovarská 1060/12                            tel: +49 911 7405384547
190 00 Praha 9                                 fax:  +420 284 084 001
Czech Republic                                    http://www.suse.cz/
PGP: 830B 40D5 9E05 35D8 5E27 6FA3 717C 209F A04F CD76

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [PATCH] libmount: handle btrfs default subvolume mount
  2016-01-21 15:45           ` Karel Zak
  2016-01-21 17:24             ` Stanislav Brabec
@ 2016-01-22  8:42             ` David Sterba
  1 sibling, 0 replies; 18+ messages in thread
From: David Sterba @ 2016-01-22  8:42 UTC (permalink / raw)
  To: Karel Zak; +Cc: Stanislav Brabec, util-linux

On Thu, Jan 21, 2016 at 04:45:29PM +0100, Karel Zak wrote:
> On Thu, Jan 21, 2016 at 04:37:13PM +0100, Karel Zak wrote:
> > > David also wrote btrfs_get_default_subvolume_path(), but then we found,
> > > that there is probably better to use subvolid and path saved in
> > > procinfo:
> > 
> > Do you mean /proc/self/mountinfo ?
> > 
> > > 
> > > + The tab.c patch would be much smaller and straightforward.
> > > - Kernel evaluates it when creating procinfo entries, this would
> > >   duplicate it.
> > > x Both subvol and subvolid in procinfo were introduced together in the
> > >   mainline kernel, and btrfs_get_default_subvolume_path() needs more
> > >   ioctl() calls.
> > 
> > No sure if I follow.
> 
> Read it and the patch again, and now it makes sense. Yes, the ioctl and 
> subvolid= from mountinfo seems better than dependence on libbtrfs.

Also because util-linux is a build dependency for btrfs-progs.

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] libmount: handle btrfs default subvolume mount
  2016-01-21 21:58       ` Stanislav Brabec
@ 2016-01-26 10:15         ` Karel Zak
  2016-01-28 14:22         ` Stanislav Brabec
  1 sibling, 0 replies; 18+ messages in thread
From: Karel Zak @ 2016-01-26 10:15 UTC (permalink / raw)
  To: Stanislav Brabec; +Cc: util-linux, David Štěrba

On Thu, Jan 21, 2016 at 10:58:31PM +0100, Stanislav Brabec wrote:
>  libmount/src/Makemodule.am |   2 +
>  libmount/src/btrfs.c       | 102 +++++++++++++++++++++++++++++++++++++
>  libmount/src/btrfs.h       | 122 +++++++++++++++++++++++++++++++++++++++++++++
>  libmount/src/libmount.h.in |   2 +
>  libmount/src/mountP.h      |   6 +++
>  libmount/src/tab.c         |  83 ++++++++++++++++++++++++++++--
>  6 files changed, 313 insertions(+), 4 deletions(-)
>  create mode 100644 libmount/src/btrfs.c
>  create mode 100644 libmount/src/btrfs.h

 Merged with some minor changes. Thanks.

    Karel

-- 
 Karel Zak  <kzak@redhat.com>
 http://karelzak.blogspot.com

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] libmount: handle btrfs default subvolume mount
  2016-01-21 21:58       ` Stanislav Brabec
  2016-01-26 10:15         ` Karel Zak
@ 2016-01-28 14:22         ` Stanislav Brabec
  2016-02-01 12:18           ` Karel Zak
  1 sibling, 1 reply; 18+ messages in thread
From: Stanislav Brabec @ 2016-01-28 14:22 UTC (permalink / raw)
  To: Karel Zak; +Cc: util-linux, David Štěrba

Stanislav Brabec wrote:
> Known problems not covered by this patch:
>
> - Use of subvolid= in fstab is not yet handled.

Patch already created, see mails:
[PATCH 1/2] libmount: run btrfs subvol checks for "subvolid" option
[PATCH 2/2] libmount: code re-indentation

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

Patch already created, see:
[PATCH] libmount: run btrfs subvol checks for "auto" fs type

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

With all patches above, I cannot reproduce any more.
Tested situations:
- with and without "loop" option
- with "subvol" or "subvolid" options referring to non-default subvolume
- without "subvol" or "subvolid" referring to default subvolume
- with "btrfs" and "auto" fs type
=> I think that it is now fixed as well.

> - 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).

I have no plans to write a fix for that now. Too obscure situation.

> - 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.)

There is no reasonable way to detect whether default subvolume was mounted.

In case of mounting with subvolid, there is a way to detect subvolume 
path even before kernel 4.2: If mountinfo lookup fails, then use 
btrfs_get_default_subvolume_path(). (See my mail dated
Thu, 21 Jan 2016 18:24:59 +0100 in this thread.) However fix is possible 
here, I have no plan to extent
[PATCH 1/2] libmount: run btrfs subvol checks for "subvolid" option
and implement it. It would be a fix of obscure situation in a kernel 
with unfixable issues of more common cases.


Summary: After applying of all patches mentioned here, I consider btrfs 
support as fixed in new kernels.

I strongly discourage advanced use of btrfs in fstab before kernel 4.2.

-- 
Best Regards / S pozdravem,

Stanislav Brabec
software developer
---------------------------------------------------------------------
SUSE LINUX, s. r. o.                         e-mail: sbrabec@suse.com
Lihovarská 1060/12                            tel: +49 911 7405384547
190 00 Praha 9                                 fax:  +420 284 084 001
Czech Republic                                    http://www.suse.cz/
PGP: 830B 40D5 9E05 35D8 5E27 6FA3 717C 209F A04F CD76

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] libmount: handle btrfs default subvolume mount
  2016-01-28 14:22         ` Stanislav Brabec
@ 2016-02-01 12:18           ` Karel Zak
  2016-02-01 15:38             ` Stanislav Brabec
  0 siblings, 1 reply; 18+ messages in thread
From: Karel Zak @ 2016-02-01 12:18 UTC (permalink / raw)
  To: Stanislav Brabec; +Cc: util-linux, David Štěrba

On Thu, Jan 28, 2016 at 03:22:07PM +0100, Stanislav Brabec wrote:
> Stanislav Brabec wrote:
> >Known problems not covered by this patch:
> >
> >- Use of subvolid= in fstab is not yet handled.
> 
> Patch already created, see mails:
> [PATCH 1/2] libmount: run btrfs subvol checks for "subvolid" option
> [PATCH 2/2] libmount: code re-indentation

I did some changes to the code:

 * use mnt_resolve_target() rather than mnt_resolve_spec() for target
 * move all to get_btrfs_fs_root()
 * make the basic if/else more readable

Please, please, test it.

It would be nice to have tests/ts/mount/btrfs with all the "mount -a"
variants you have in commit messages.

> >- 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.)
> 
> There is no reasonable way to detect whether default subvolume was mounted.
> 
> In case of mounting with subvolid, there is a way to detect subvolume path
> even before kernel 4.2: If mountinfo lookup fails, then use
> btrfs_get_default_subvolume_path(). (See my mail dated
> Thu, 21 Jan 2016 18:24:59 +0100 in this thread.) However fix is possible
> here, I have no plan to extent
> [PATCH 1/2] libmount: run btrfs subvol checks for "subvolid" option
> and implement it. It would be a fix of obscure situation in a kernel with
> unfixable issues of more common cases.

You know, perfect is the enemy of good :-)

Thanks
    Karel

-- 
 Karel Zak  <kzak@redhat.com>
 http://karelzak.blogspot.com

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] libmount: handle btrfs default subvolume mount
  2016-02-01 12:18           ` Karel Zak
@ 2016-02-01 15:38             ` Stanislav Brabec
  2016-02-02 10:11               ` Karel Zak
  0 siblings, 1 reply; 18+ messages in thread
From: Stanislav Brabec @ 2016-02-01 15:38 UTC (permalink / raw)
  To: Karel Zak; +Cc: util-linux, David Štěrba

Karel Zak wrote:

> I did some changes to the code:
>
>   * use mnt_resolve_target() rather than mnt_resolve_spec() for target
>   * move all to get_btrfs_fs_root()
>   * make the basic if/else more readable

By the way, I am just fixing next btrfs issue. Now it is in blkid. And 
it seems, that I will need some changes in the code: move the btrfs 
default subvol function into the public API, and change it to return the 
subvolid string.

What do you think? Should I make this function linux or linux+btrfs 
specific (missing in the API/ABI otherwise, where include file would 
contain #define MOUNT_HAS_BTRFS_SUPPORT or so), or implement it for all 
systems. On unsupported systems it would always return NULL (the define 
could exist in this case as well, but it would be optional).

> Please, please, test it.

I'll test it. I think that the attached reproducer should work on any 
system with btrfs support.

> It would be nice to have tests/ts/mount/btrfs with all the "mount -a"
> variants you have in commit messages.

Are there any rules/framework for such tests that need root and editing 
of /etc/fstab?

I think that use of losetup and /dev/loopX can be used for most 
testcases. I am not sure about kernel partition table support inside 
loop devices.


And by the way, loop it the only device that can be used for TRIM 
testing: TRIM is internally converted to making unused blocks sparse.

Here is an example of TRIM testcase using loop:

dd if=/dev/null of=loop.img seek=1000000
mkfs.btrfs /root/loop.img
mount -oloop /root/loop.img /mnt
filefrag -e loop.img
for i in $(seq 0 100) ; do dd if=/dev/urandom of=/mnt/file$i bs=1M 
count=10 ; done
filefrag -e loop.img
rm /mnt/file*
fstrim /mnt
# wait some time to complete
filefrag -e loop.img

-- 
Best Regards / S pozdravem,

Stanislav Brabec
software developer
---------------------------------------------------------------------
SUSE LINUX, s. r. o.                         e-mail: sbrabec@suse.com
Lihovarská 1060/12                            tel: +49 911 7405384547
190 00 Praha 9                                 fax:  +420 284 084 001
Czech Republic                                    http://www.suse.cz/
PGP: 830B 40D5 9E05 35D8 5E27 6FA3 717C 209F A04F CD76

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] libmount: handle btrfs default subvolume mount
  2016-02-01 15:38             ` Stanislav Brabec
@ 2016-02-02 10:11               ` Karel Zak
  2016-02-02 15:04                 ` Stanislav Brabec
  0 siblings, 1 reply; 18+ messages in thread
From: Karel Zak @ 2016-02-02 10:11 UTC (permalink / raw)
  To: Stanislav Brabec; +Cc: util-linux, David Štěrba

On Mon, Feb 01, 2016 at 04:38:17PM +0100, Stanislav Brabec wrote:
> Karel Zak wrote:
> 
> >I did some changes to the code:
> >
> >  * use mnt_resolve_target() rather than mnt_resolve_spec() for target
> >  * move all to get_btrfs_fs_root()
> >  * make the basic if/else more readable
> 
> By the way, I am just fixing next btrfs issue. Now it is in blkid. And it
> seems, that I will need some changes in the code: move the btrfs default
> subvol function into the public API, and change it to return the subvolid
> string.

Why we need the function in public API?

Add subvolume ID to the probing result is no problem. It's just 
another NAME=value.

> >It would be nice to have tests/ts/mount/btrfs with all the "mount -a"
> >variants you have in commit messages.
> 
> Are there any rules/framework for such tests that need root and editing of
> /etc/fstab?

See for example tests/ts/mount/fstab-devname, 

    ts_skip_nonroot  -- ignore the test for non-root user
    ts_fstab_add     -- add line to the fstab

the functions are implemented in tests/functions.sh

> I think that use of losetup and /dev/loopX can be used for most testcases. I
> am not sure about kernel partition table support inside loop devices.

We use:
    ts_device_init     -- for loop devies (uses truncate command)
    ts_scsi_debug_init -- for devices by scsi_debug kernel module

scsi_debug is better solution, because it's absolutely without any
side effects, but it's based on RAM, so create 10GiB disk is bad idea :)

For more details see tests/ts/* with many many examples.

    Karel

-- 
 Karel Zak  <kzak@redhat.com>
 http://karelzak.blogspot.com

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] libmount: handle btrfs default subvolume mount
  2016-02-02 10:11               ` Karel Zak
@ 2016-02-02 15:04                 ` Stanislav Brabec
  2016-02-02 18:43                   ` Karel Zak
  0 siblings, 1 reply; 18+ messages in thread
From: Stanislav Brabec @ 2016-02-02 15:04 UTC (permalink / raw)
  To: Karel Zak; +Cc: util-linux, David Štěrba

Karel Zak wrote:
> On Mon, Feb 01, 2016 at 04:38:17PM +0100, Stanislav Brabec wrote:

>> By the way, I am just fixing next btrfs issue. Now it is in blkid. And it
>> seems, that I will need some changes in the code: move the btrfs default
>> subvol function into the public API, and change it to return the subvolid
>> string.
>
> Why we need the function in public API?

Because lsblk does not have access to libmount private functions.

-- 
Best Regards / S pozdravem,

Stanislav Brabec
software developer
---------------------------------------------------------------------
SUSE LINUX, s. r. o.                         e-mail: sbrabec@suse.com
Lihovarská 1060/12                            tel: +49 911 7405384547
190 00 Praha 9                                 fax:  +420 284 084 001
Czech Republic                                    http://www.suse.cz/
PGP: 830B 40D5 9E05 35D8 5E27 6FA3 717C 209F A04F CD76

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] libmount: handle btrfs default subvolume mount
  2016-02-02 15:04                 ` Stanislav Brabec
@ 2016-02-02 18:43                   ` Karel Zak
  2016-02-02 19:36                     ` Stanislav Brabec
  0 siblings, 1 reply; 18+ messages in thread
From: Karel Zak @ 2016-02-02 18:43 UTC (permalink / raw)
  To: Stanislav Brabec; +Cc: util-linux, David Štěrba

On Tue, Feb 02, 2016 at 04:04:16PM +0100, Stanislav Brabec wrote:
> Karel Zak wrote:
> >On Mon, Feb 01, 2016 at 04:38:17PM +0100, Stanislav Brabec wrote:
> 
> >>By the way, I am just fixing next btrfs issue. Now it is in blkid. And it
> >>seems, that I will need some changes in the code: move the btrfs default
> >>subvol function into the public API, and change it to return the subvolid
> >>string.
> >
> >Why we need the function in public API?
> 
> Because lsblk does not have access to libmount private functions.

You have talked about blkid, I thought you want to add SUBVOL= to
libblkid.

For what purpose do you need it in lsblk? When the same device is
mounded on more places?

Anyway, in libmount we can extend mnt_fs_* API and add some additional
functions -- for example add mnt_fs_get_default_root().

I definitely don't want btrfs specific functions. The API should be
generic.

    Karel

-- 
 Karel Zak  <kzak@redhat.com>
 http://karelzak.blogspot.com

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] libmount: handle btrfs default subvolume mount
  2016-02-02 18:43                   ` Karel Zak
@ 2016-02-02 19:36                     ` Stanislav Brabec
  0 siblings, 0 replies; 18+ messages in thread
From: Stanislav Brabec @ 2016-02-02 19:36 UTC (permalink / raw)
  To: Karel Zak; +Cc: util-linux, David Štěrba

Dne 2.2.2016 v 19:43 Karel Zak wrote:

> For what purpose do you need it in lsblk? When the same device is
> mounded on more places?

When lsblk is called on a device with btrfs mounted, the lsblk heuristic 
may return sub-optimal (and even inconsistent) result.

> Anyway, in libmount we can extend mnt_fs_* API and add some additional
> functions -- for example add mnt_fs_get_default_root().
>
> I definitely don't want btrfs specific functions. The API should be
> generic.

The heuristic code is part of lsblk.c: get_device_mountpoint()

https://git.kernel.org/cgit/utils/util-linux/util-linux.git/tree/misc-utils/lsblk.c?id=4c01c98ca20b404940a4be749e729411d861ae97#n486

It already implements btrfs support, but the heurictic sometimes does 
not do what one would expect:

1) Search for ANY mount point that refers to device in question.

2) Check whether it is a fsroot if it is equal to "/" (always true for 
non-btrfs)

3) If not, search through all mounts that match 1).

4) Compare results, and if fsroot is equal to "/", use it.

5) Otherwise return result of 1).


In some installations (e. g. default openSUSE/SLE installation with 
btrfs root), fsroot is not mounted, 4) fails and 5) returns the 
sub-optimal guess 1).

The algorithm could be easily improved to search for the default 
subvolume as well, and only if it is not mounted, use the sub-optimal 
result.


My idea was:

1) Make btrfs_get_default_subvol_id_str() public

2) Add another loop to get_device_mountpoint() (before existing one) 
searching for a volume, where subvol_id_str returned by 
btrfs_get_default_subvol_id_str() matches option in the mountinfo.


Use of mnt_fs_get_default_root() would work as well, with a bit of overhead.

-- 
Best Regards / S pozdravem,

Stanislav Brabec
software developer
---------------------------------------------------------------------
SUSE LINUX, s. r. o.                         e-mail: sbrabec@suse.com
Lihovarská 1060/12                            tel: +49 911 7405384547
190 00 Praha 9                                 fax:  +420 284 084 001
Czech Republic                                    http://www.suse.cz/
PGP: 830B 40D5 9E05 35D8 5E27 6FA3 717C 209F A04F CD76

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2016-02-02 19:36 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-01-20 20:34 [PATCH] libmount: handle btrfs default subvolume mount Stanislav Brabec
2016-01-20 21:51 ` Stanislav Brabec
2016-01-20 21:57   ` Stanislav Brabec
2016-01-21  9:48     ` Karel Zak
2016-01-21 15:24       ` Stanislav Brabec
2016-01-21 15:37         ` Karel Zak
2016-01-21 15:45           ` Karel Zak
2016-01-21 17:24             ` Stanislav Brabec
2016-01-22  8:42             ` David Sterba
2016-01-21 21:58       ` Stanislav Brabec
2016-01-26 10:15         ` Karel Zak
2016-01-28 14:22         ` Stanislav Brabec
2016-02-01 12:18           ` Karel Zak
2016-02-01 15:38             ` Stanislav Brabec
2016-02-02 10:11               ` Karel Zak
2016-02-02 15:04                 ` Stanislav Brabec
2016-02-02 18:43                   ` Karel Zak
2016-02-02 19:36                     ` Stanislav Brabec

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.