summaryrefslogtreecommitdiff
path: root/fs/btrfs/super.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/btrfs/super.c')
-rw-r--r--fs/btrfs/super.c243
1 files changed, 185 insertions, 58 deletions
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index 8a1ea6e6457..9ac612e6ca6 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -63,10 +63,10 @@ static void btrfs_put_super(struct super_block *sb)
}
enum {
- Opt_degraded, Opt_subvol, Opt_device, Opt_nodatasum, Opt_nodatacow,
- Opt_max_extent, Opt_max_inline, Opt_alloc_start, Opt_nobarrier,
- Opt_ssd, Opt_nossd, Opt_ssd_spread, Opt_thread_pool, Opt_noacl,
- Opt_compress, Opt_compress_force, Opt_notreelog, Opt_ratio,
+ Opt_degraded, Opt_subvol, Opt_subvolid, Opt_device, Opt_nodatasum,
+ Opt_nodatacow, Opt_max_extent, Opt_max_inline, Opt_alloc_start,
+ Opt_nobarrier, Opt_ssd, Opt_nossd, Opt_ssd_spread, Opt_thread_pool,
+ Opt_noacl, Opt_compress, Opt_compress_force, Opt_notreelog, Opt_ratio,
Opt_flushoncommit,
Opt_discard, Opt_err,
};
@@ -74,6 +74,7 @@ enum {
static match_table_t tokens = {
{Opt_degraded, "degraded"},
{Opt_subvol, "subvol=%s"},
+ {Opt_subvolid, "subvolid=%d"},
{Opt_device, "device=%s"},
{Opt_nodatasum, "nodatasum"},
{Opt_nodatacow, "nodatacow"},
@@ -95,31 +96,6 @@ static match_table_t tokens = {
{Opt_err, NULL},
};
-u64 btrfs_parse_size(char *str)
-{
- u64 res;
- int mult = 1;
- char *end;
- char last;
-
- res = simple_strtoul(str, &end, 10);
-
- last = end[0];
- if (isalpha(last)) {
- last = tolower(last);
- switch (last) {
- case 'g':
- mult *= 1024;
- case 'm':
- mult *= 1024;
- case 'k':
- mult *= 1024;
- }
- res = res * mult;
- }
- return res;
-}
-
/*
* Regular mount options parser. Everything that is needed only when
* reading in a new superblock is parsed here.
@@ -128,7 +104,7 @@ int btrfs_parse_options(struct btrfs_root *root, char *options)
{
struct btrfs_fs_info *info = root->fs_info;
substring_t args[MAX_OPT_ARGS];
- char *p, *num;
+ char *p, *num, *orig;
int intarg;
int ret = 0;
@@ -143,6 +119,7 @@ int btrfs_parse_options(struct btrfs_root *root, char *options)
if (!options)
return -ENOMEM;
+ orig = options;
while ((p = strsep(&options, ",")) != NULL) {
int token;
@@ -156,6 +133,7 @@ int btrfs_parse_options(struct btrfs_root *root, char *options)
btrfs_set_opt(info->mount_opt, DEGRADED);
break;
case Opt_subvol:
+ case Opt_subvolid:
case Opt_device:
/*
* These are parsed by btrfs_parse_early_options
@@ -213,7 +191,7 @@ int btrfs_parse_options(struct btrfs_root *root, char *options)
case Opt_max_extent:
num = match_strdup(&args[0]);
if (num) {
- info->max_extent = btrfs_parse_size(num);
+ info->max_extent = memparse(num, NULL);
kfree(num);
info->max_extent = max_t(u64,
@@ -225,7 +203,7 @@ int btrfs_parse_options(struct btrfs_root *root, char *options)
case Opt_max_inline:
num = match_strdup(&args[0]);
if (num) {
- info->max_inline = btrfs_parse_size(num);
+ info->max_inline = memparse(num, NULL);
kfree(num);
if (info->max_inline) {
@@ -240,7 +218,7 @@ int btrfs_parse_options(struct btrfs_root *root, char *options)
case Opt_alloc_start:
num = match_strdup(&args[0]);
if (num) {
- info->alloc_start = btrfs_parse_size(num);
+ info->alloc_start = memparse(num, NULL);
kfree(num);
printk(KERN_INFO
"btrfs: allocations start at %llu\n",
@@ -280,7 +258,7 @@ int btrfs_parse_options(struct btrfs_root *root, char *options)
}
}
out:
- kfree(options);
+ kfree(orig);
return ret;
}
@@ -291,12 +269,13 @@ out:
* only when we need to allocate a new super block.
*/
static int btrfs_parse_early_options(const char *options, fmode_t flags,
- void *holder, char **subvol_name,
+ void *holder, char **subvol_name, u64 *subvol_objectid,
struct btrfs_fs_devices **fs_devices)
{
substring_t args[MAX_OPT_ARGS];
char *opts, *p;
int error = 0;
+ int intarg;
if (!options)
goto out;
@@ -319,6 +298,18 @@ static int btrfs_parse_early_options(const char *options, fmode_t flags,
case Opt_subvol:
*subvol_name = match_strdup(&args[0]);
break;
+ case Opt_subvolid:
+ intarg = 0;
+ error = match_int(&args[0], &intarg);
+ if (!error) {
+ /* we want the original fs_tree */
+ if (!intarg)
+ *subvol_objectid =
+ BTRFS_FS_TREE_OBJECTID;
+ else
+ *subvol_objectid = intarg;
+ }
+ break;
case Opt_device:
error = btrfs_scan_one_device(match_strdup(&args[0]),
flags, holder, fs_devices);
@@ -346,6 +337,110 @@ static int btrfs_parse_early_options(const char *options, fmode_t flags,
return error;
}
+static struct dentry *get_default_root(struct super_block *sb,
+ u64 subvol_objectid)
+{
+ struct btrfs_root *root = sb->s_fs_info;
+ struct btrfs_root *new_root;
+ struct btrfs_dir_item *di;
+ struct btrfs_path *path;
+ struct btrfs_key location;
+ struct inode *inode;
+ struct dentry *dentry;
+ u64 dir_id;
+ int new = 0;
+
+ /*
+ * We have a specific subvol we want to mount, just setup location and
+ * go look up the root.
+ */
+ if (subvol_objectid) {
+ location.objectid = subvol_objectid;
+ location.type = BTRFS_ROOT_ITEM_KEY;
+ location.offset = (u64)-1;
+ goto find_root;
+ }
+
+ path = btrfs_alloc_path();
+ if (!path)
+ return ERR_PTR(-ENOMEM);
+ path->leave_spinning = 1;
+
+ /*
+ * Find the "default" dir item which points to the root item that we
+ * will mount by default if we haven't been given a specific subvolume
+ * to mount.
+ */
+ dir_id = btrfs_super_root_dir(&root->fs_info->super_copy);
+ di = btrfs_lookup_dir_item(NULL, root, path, dir_id, "default", 7, 0);
+ if (!di) {
+ /*
+ * Ok the default dir item isn't there. This is weird since
+ * it's always been there, but don't freak out, just try and
+ * mount to root most subvolume.
+ */
+ btrfs_free_path(path);
+ dir_id = BTRFS_FIRST_FREE_OBJECTID;
+ new_root = root->fs_info->fs_root;
+ goto setup_root;
+ }
+
+ btrfs_dir_item_key_to_cpu(path->nodes[0], di, &location);
+ btrfs_free_path(path);
+
+find_root:
+ new_root = btrfs_read_fs_root_no_name(root->fs_info, &location);
+ if (IS_ERR(new_root))
+ return ERR_PTR(PTR_ERR(new_root));
+
+ if (btrfs_root_refs(&new_root->root_item) == 0)
+ return ERR_PTR(-ENOENT);
+
+ dir_id = btrfs_root_dirid(&new_root->root_item);
+setup_root:
+ location.objectid = dir_id;
+ location.type = BTRFS_INODE_ITEM_KEY;
+ location.offset = 0;
+
+ inode = btrfs_iget(sb, &location, new_root, &new);
+ if (!inode)
+ return ERR_PTR(-ENOMEM);
+
+ /*
+ * If we're just mounting the root most subvol put the inode and return
+ * a reference to the dentry. We will have already gotten a reference
+ * to the inode in btrfs_fill_super so we're good to go.
+ */
+ if (!new && sb->s_root->d_inode == inode) {
+ iput(inode);
+ return dget(sb->s_root);
+ }
+
+ if (new) {
+ const struct qstr name = { .name = "/", .len = 1 };
+
+ /*
+ * New inode, we need to make the dentry a sibling of s_root so
+ * everything gets cleaned up properly on unmount.
+ */
+ dentry = d_alloc(sb->s_root, &name);
+ if (!dentry) {
+ iput(inode);
+ return ERR_PTR(-ENOMEM);
+ }
+ d_splice_alias(inode, dentry);
+ } else {
+ /*
+ * We found the inode in cache, just find a dentry for it and
+ * put the reference to the inode we just got.
+ */
+ dentry = d_find_alias(inode);
+ iput(inode);
+ }
+
+ return dentry;
+}
+
static int btrfs_fill_super(struct super_block *sb,
struct btrfs_fs_devices *fs_devices,
void *data, int silent)
@@ -379,7 +474,7 @@ static int btrfs_fill_super(struct super_block *sb,
key.objectid = BTRFS_FIRST_FREE_OBJECTID;
key.type = BTRFS_INODE_ITEM_KEY;
key.offset = 0;
- inode = btrfs_iget(sb, &key, tree_root->fs_info->fs_root);
+ inode = btrfs_iget(sb, &key, tree_root->fs_info->fs_root, NULL);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
goto fail_close;
@@ -391,12 +486,6 @@ static int btrfs_fill_super(struct super_block *sb,
err = -ENOMEM;
goto fail_close;
}
-#if 0
- /* this does the super kobj at the same time */
- err = btrfs_sysfs_add_super(tree_root->fs_info);
- if (err)
- goto fail_close;
-#endif
sb->s_root = root_dentry;
@@ -488,19 +577,22 @@ static int btrfs_test_super(struct super_block *s, void *data)
static int btrfs_get_sb(struct file_system_type *fs_type, int flags,
const char *dev_name, void *data, struct vfsmount *mnt)
{
- char *subvol_name = NULL;
struct block_device *bdev = NULL;
struct super_block *s;
struct dentry *root;
struct btrfs_fs_devices *fs_devices = NULL;
fmode_t mode = FMODE_READ;
+ char *subvol_name = NULL;
+ u64 subvol_objectid = 0;
int error = 0;
+ int found = 0;
if (!(flags & MS_RDONLY))
mode |= FMODE_WRITE;
error = btrfs_parse_early_options(data, mode, fs_type,
- &subvol_name, &fs_devices);
+ &subvol_name, &subvol_objectid,
+ &fs_devices);
if (error)
return error;
@@ -529,6 +621,7 @@ static int btrfs_get_sb(struct file_system_type *fs_type, int flags,
goto error_close_devices;
}
+ found = 1;
btrfs_close_devices(fs_devices);
} else {
char b[BDEVNAME_SIZE];
@@ -546,25 +639,35 @@ static int btrfs_get_sb(struct file_system_type *fs_type, int flags,
s->s_flags |= MS_ACTIVE;
}
- if (!strcmp(subvol_name, "."))
- root = dget(s->s_root);
- else {
- mutex_lock(&s->s_root->d_inode->i_mutex);
- root = lookup_one_len(subvol_name, s->s_root,
+ root = get_default_root(s, subvol_objectid);
+ if (IS_ERR(root)) {
+ error = PTR_ERR(root);
+ deactivate_locked_super(s);
+ goto error;
+ }
+ /* if they gave us a subvolume name bind mount into that */
+ if (strcmp(subvol_name, ".")) {
+ struct dentry *new_root;
+ mutex_lock(&root->d_inode->i_mutex);
+ new_root = lookup_one_len(subvol_name, root,
strlen(subvol_name));
- mutex_unlock(&s->s_root->d_inode->i_mutex);
+ mutex_unlock(&root->d_inode->i_mutex);
- if (IS_ERR(root)) {
+ if (IS_ERR(new_root)) {
deactivate_locked_super(s);
- error = PTR_ERR(root);
- goto error_free_subvol_name;
+ error = PTR_ERR(new_root);
+ dput(root);
+ goto error_close_devices;
}
- if (!root->d_inode) {
+ if (!new_root->d_inode) {
dput(root);
+ dput(new_root);
deactivate_locked_super(s);
error = -ENXIO;
- goto error_free_subvol_name;
+ goto error_close_devices;
}
+ dput(root);
+ root = new_root;
}
mnt->mnt_sb = s;
@@ -579,6 +682,7 @@ error_close_devices:
btrfs_close_devices(fs_devices);
error_free_subvol_name:
kfree(subvol_name);
+error:
return error;
}
@@ -623,14 +727,37 @@ static int btrfs_statfs(struct dentry *dentry, struct kstatfs *buf)
{
struct btrfs_root *root = btrfs_sb(dentry->d_sb);
struct btrfs_super_block *disk_super = &root->fs_info->super_copy;
+ struct list_head *head = &root->fs_info->space_info;
+ struct btrfs_space_info *found;
+ u64 total_used = 0;
+ u64 data_used = 0;
int bits = dentry->d_sb->s_blocksize_bits;
__be32 *fsid = (__be32 *)root->fs_info->fsid;
+ rcu_read_lock();
+ list_for_each_entry_rcu(found, head, list) {
+ if (found->flags & (BTRFS_BLOCK_GROUP_DUP|
+ BTRFS_BLOCK_GROUP_RAID10|
+ BTRFS_BLOCK_GROUP_RAID1)) {
+ total_used += found->bytes_used;
+ if (found->flags & BTRFS_BLOCK_GROUP_DATA)
+ data_used += found->bytes_used;
+ else
+ data_used += found->total_bytes;
+ }
+
+ total_used += found->bytes_used;
+ if (found->flags & BTRFS_BLOCK_GROUP_DATA)
+ data_used += found->bytes_used;
+ else
+ data_used += found->total_bytes;
+ }
+ rcu_read_unlock();
+
buf->f_namelen = BTRFS_NAME_LEN;
buf->f_blocks = btrfs_super_total_bytes(disk_super) >> bits;
- buf->f_bfree = buf->f_blocks -
- (btrfs_super_bytes_used(disk_super) >> bits);
- buf->f_bavail = buf->f_bfree;
+ buf->f_bfree = buf->f_blocks - (total_used >> bits);
+ buf->f_bavail = buf->f_blocks - (data_used >> bits);
buf->f_bsize = dentry->d_sb->s_blocksize;
buf->f_type = BTRFS_SUPER_MAGIC;