+/*
+ * This will strip out the subvol=%s argument for an argument string and add
+ * subvolid=0 to make sure we get the actual tree root for path walking to the
+ * subvol we want.
+ */
+static char *setup_root_args(char *args)
+{
+ unsigned copied = 0;
+ unsigned len = strlen(args) + 2;
+ char *pos;
+ char *ret;
+
+ /*
+ * We need the same args as before, but minus
+ *
+ * subvol=a
+ *
+ * and add
+ *
+ * subvolid=0
+ *
+ * which is a difference of 2 characters, so we allocate strlen(args) +
+ * 2 characters.
+ */
+ ret = kzalloc(len * sizeof(char), GFP_NOFS);
+ if (!ret)
+ return NULL;
+ pos = strstr(args, "subvol=");
+
+ /* This shouldn't happen, but just in case.. */
+ if (!pos) {
+ kfree(ret);
+ return NULL;
+ }
+
+ /*
+ * The subvol=<> arg is not at the front of the string, copy everybody
+ * up to that into ret.
+ */
+ if (pos != args) {
+ *pos = '\0';
+ strcpy(ret, args);
+ copied += strlen(args);
+ pos++;
+ }
+
+ strncpy(ret + copied, "subvolid=0", len - copied);
+
+ /* Length of subvolid=0 */
+ copied += 10;
+
+ /*
+ * If there is no , after the subvol= option then we know there's no
+ * other options and we can just return.
+ */
+ pos = strchr(pos, ',');
+ if (!pos)
+ return ret;
+
+ /* Copy the rest of the arguments into our buffer */
+ strncpy(ret + copied, pos, len - copied);
+ copied += strlen(pos);
+
+ return ret;
+}
+
+static struct dentry *mount_subvol(const char *subvol_name, int flags,
+ const char *device_name, char *data)
+{
+ struct super_block *s;
+ struct dentry *root;
+ struct vfsmount *mnt;
+ struct mnt_namespace *ns_private;
+ char *newargs;
+ struct path path;
+ int error;
+
+ newargs = setup_root_args(data);
+ if (!newargs)
+ return ERR_PTR(-ENOMEM);
+ mnt = vfs_kern_mount(&btrfs_fs_type, flags, device_name,
+ newargs);
+ kfree(newargs);
+ if (IS_ERR(mnt))
+ return ERR_CAST(mnt);
+
+ ns_private = create_mnt_ns(mnt);
+ if (IS_ERR(ns_private)) {
+ mntput(mnt);
+ return ERR_CAST(ns_private);
+ }
+
+ /*
+ * This will trigger the automount of the subvol so we can just
+ * drop the mnt we have here and return the dentry that we
+ * found.
+ */
+ error = vfs_path_lookup(mnt->mnt_root, mnt, subvol_name,
+ LOOKUP_FOLLOW, &path);
+ put_mnt_ns(ns_private);
+ if (error)
+ return ERR_PTR(error);
+
+ /* Get a ref to the sb and the dentry we found and return it */
+ s = path.mnt->mnt_sb;
+ atomic_inc(&s->s_active);
+ root = dget(path.dentry);
+ path_put(&path);
+ down_write(&s->s_umount);
+
+ return root;
+}