summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStanislav Brabec2016-01-21 22:58:31 +0100
committerKarel Zak2016-01-26 11:14:25 +0100
commit2cd28fc82d0c947472a4700d5e764265916fba1e (patch)
tree6ed461930795e9ae8ab84a6193d1248c40f9f3bf
parentdoap: fix download URL (diff)
downloadkernel-qcow2-util-linux-2cd28fc82d0c947472a4700d5e764265916fba1e.tar.gz
kernel-qcow2-util-linux-2cd28fc82d0c947472a4700d5e764265916fba1e.tar.xz
kernel-qcow2-util-linux-2cd28fc82d0c947472a4700d5e764265916fba1e.zip
libmount: handle btrfs default subvolume mount
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>
-rw-r--r--libmount/docs/libmount-sections.txt1
-rw-r--r--libmount/src/Makemodule.am2
-rw-r--r--libmount/src/btrfs.c102
-rw-r--r--libmount/src/btrfs.h122
-rw-r--r--libmount/src/init.c1
-rw-r--r--libmount/src/libmount.h.in2
-rw-r--r--libmount/src/libmount.sym4
-rw-r--r--libmount/src/mountP.h6
-rw-r--r--libmount/src/tab.c95
9 files changed, 331 insertions, 4 deletions
diff --git a/libmount/docs/libmount-sections.txt b/libmount/docs/libmount-sections.txt
index e89f84f1f..03aa4cebe 100644
--- a/libmount/docs/libmount-sections.txt
+++ b/libmount/docs/libmount-sections.txt
@@ -309,6 +309,7 @@ mnt_table_find_source
mnt_table_find_srcpath
mnt_table_find_tag
mnt_table_find_target
+mnt_table_find_target_with_option
mnt_table_first_fs
mnt_table_get_cache
mnt_table_get_intro_comment
diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am
index 11c6324d3..39d42d524 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 000000000..3c61a2a03
--- /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 000000000..ca49df511
--- /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/init.c b/libmount/src/init.c
index 1765d3b25..917008dc2 100644
--- a/libmount/src/init.c
+++ b/libmount/src/init.c
@@ -30,6 +30,7 @@ UL_DEBUG_DEFINE_MASKNAMES(libmount) =
{ "update", MNT_DEBUG_UPDATE, "mtab, utab updates" },
{ "utils", MNT_DEBUG_UTILS, "misc library utils" },
{ "monitor", MNT_DEBUG_MONITOR, "mount tables monitor" },
+ { "btrfs", MNT_DEBUG_BTRFS, "btrfs specific routines" },
{ NULL, 0 }
};
diff --git a/libmount/src/libmount.h.in b/libmount/src/libmount.h.in
index 2338cc604..91b1698ca 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/libmount.sym b/libmount/src/libmount.sym
index ebb596349..7da969e17 100644
--- a/libmount/src/libmount.sym
+++ b/libmount/src/libmount.sym
@@ -310,3 +310,7 @@ MOUNT_2.26 {
mnt_ref_monitor;
mnt_unref_monitor;
} MOUNT_2.25;
+
+MOUNT_2.28 {
+ mnt_table_find_target_with_option;
+} MOUNT_2.26;
diff --git a/libmount/src/mountP.h b/libmount/src/mountP.h
index 25418a2e4..0bc67e5f7 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 951fe8c40..31b177fec 100644
--- a/libmount/src/tab.c
+++ b/libmount/src/tab.c
@@ -1059,6 +1059,50 @@ 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 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
@@ -1241,9 +1285,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
@@ -1277,10 +1322,52 @@ struct libmnt_fs *mnt_table_get_fs_root(struct libmnt_table *tb,
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 != '/')