convenience helpers: vfs_get_super() and sget_fc()
[muen/linux.git] / fs / super.c
index 76b3181..0ebb5c1 100644 (file)
@@ -476,6 +476,94 @@ void generic_shutdown_super(struct super_block *sb)
 
 EXPORT_SYMBOL(generic_shutdown_super);
 
+/**
+ * sget_fc - Find or create a superblock
+ * @fc:        Filesystem context.
+ * @test: Comparison callback
+ * @set: Setup callback
+ *
+ * Find or create a superblock using the parameters stored in the filesystem
+ * context and the two callback functions.
+ *
+ * If an extant superblock is matched, then that will be returned with an
+ * elevated reference count that the caller must transfer or discard.
+ *
+ * If no match is made, a new superblock will be allocated and basic
+ * initialisation will be performed (s_type, s_fs_info and s_id will be set and
+ * the set() callback will be invoked), the superblock will be published and it
+ * will be returned in a partially constructed state with SB_BORN and SB_ACTIVE
+ * as yet unset.
+ */
+struct super_block *sget_fc(struct fs_context *fc,
+                           int (*test)(struct super_block *, struct fs_context *),
+                           int (*set)(struct super_block *, struct fs_context *))
+{
+       struct super_block *s = NULL;
+       struct super_block *old;
+       struct user_namespace *user_ns = fc->global ? &init_user_ns : fc->user_ns;
+       int err;
+
+       if (!(fc->sb_flags & SB_KERNMOUNT) &&
+           fc->purpose != FS_CONTEXT_FOR_SUBMOUNT) {
+               /* Don't allow mounting unless the caller has CAP_SYS_ADMIN
+                * over the namespace.
+                */
+               if (!(fc->fs_type->fs_flags & FS_USERNS_MOUNT)) {
+                       if (!capable(CAP_SYS_ADMIN))
+                               return ERR_PTR(-EPERM);
+               } else {
+                       if (!ns_capable(fc->user_ns, CAP_SYS_ADMIN))
+                               return ERR_PTR(-EPERM);
+               }
+       }
+
+retry:
+       spin_lock(&sb_lock);
+       if (test) {
+               hlist_for_each_entry(old, &fc->fs_type->fs_supers, s_instances) {
+                       if (test(old, fc))
+                               goto share_extant_sb;
+               }
+       }
+       if (!s) {
+               spin_unlock(&sb_lock);
+               s = alloc_super(fc->fs_type, fc->sb_flags, user_ns);
+               if (!s)
+                       return ERR_PTR(-ENOMEM);
+               goto retry;
+       }
+
+       s->s_fs_info = fc->s_fs_info;
+       err = set(s, fc);
+       if (err) {
+               s->s_fs_info = NULL;
+               spin_unlock(&sb_lock);
+               destroy_unused_super(s);
+               return ERR_PTR(err);
+       }
+       fc->s_fs_info = NULL;
+       s->s_type = fc->fs_type;
+       strlcpy(s->s_id, s->s_type->name, sizeof(s->s_id));
+       list_add_tail(&s->s_list, &super_blocks);
+       hlist_add_head(&s->s_instances, &s->s_type->fs_supers);
+       spin_unlock(&sb_lock);
+       get_filesystem(s->s_type);
+       register_shrinker_prepared(&s->s_shrink);
+       return s;
+
+share_extant_sb:
+       if (user_ns != old->s_user_ns) {
+               spin_unlock(&sb_lock);
+               destroy_unused_super(s);
+               return ERR_PTR(-EBUSY);
+       }
+       if (!grab_super(old))
+               goto retry;
+       destroy_unused_super(s);
+       return old;
+}
+EXPORT_SYMBOL(sget_fc);
+
 /**
  *     sget_userns -   find or create a superblock
  *     @type:  filesystem type superblock should belong to
@@ -1103,6 +1191,89 @@ struct dentry *mount_ns(struct file_system_type *fs_type,
 
 EXPORT_SYMBOL(mount_ns);
 
+int set_anon_super_fc(struct super_block *sb, struct fs_context *fc)
+{
+       return set_anon_super(sb, NULL);
+}
+EXPORT_SYMBOL(set_anon_super_fc);
+
+static int test_keyed_super(struct super_block *sb, struct fs_context *fc)
+{
+       return sb->s_fs_info == fc->s_fs_info;
+}
+
+static int test_single_super(struct super_block *s, struct fs_context *fc)
+{
+       return 1;
+}
+
+/**
+ * vfs_get_super - Get a superblock with a search key set in s_fs_info.
+ * @fc: The filesystem context holding the parameters
+ * @keying: How to distinguish superblocks
+ * @fill_super: Helper to initialise a new superblock
+ *
+ * Search for a superblock and create a new one if not found.  The search
+ * criterion is controlled by @keying.  If the search fails, a new superblock
+ * is created and @fill_super() is called to initialise it.
+ *
+ * @keying can take one of a number of values:
+ *
+ * (1) vfs_get_single_super - Only one superblock of this type may exist on the
+ *     system.  This is typically used for special system filesystems.
+ *
+ * (2) vfs_get_keyed_super - Multiple superblocks may exist, but they must have
+ *     distinct keys (where the key is in s_fs_info).  Searching for the same
+ *     key again will turn up the superblock for that key.
+ *
+ * (3) vfs_get_independent_super - Multiple superblocks may exist and are
+ *     unkeyed.  Each call will get a new superblock.
+ *
+ * A permissions check is made by sget_fc() unless we're getting a superblock
+ * for a kernel-internal mount or a submount.
+ */
+int vfs_get_super(struct fs_context *fc,
+                 enum vfs_get_super_keying keying,
+                 int (*fill_super)(struct super_block *sb,
+                                   struct fs_context *fc))
+{
+       int (*test)(struct super_block *, struct fs_context *);
+       struct super_block *sb;
+
+       switch (keying) {
+       case vfs_get_single_super:
+               test = test_single_super;
+               break;
+       case vfs_get_keyed_super:
+               test = test_keyed_super;
+               break;
+       case vfs_get_independent_super:
+               test = NULL;
+               break;
+       default:
+               BUG();
+       }
+
+       sb = sget_fc(fc, test, set_anon_super_fc);
+       if (IS_ERR(sb))
+               return PTR_ERR(sb);
+
+       if (!sb->s_root) {
+               int err = fill_super(sb, fc);
+               if (err) {
+                       deactivate_locked_super(sb);
+                       return err;
+               }
+
+               sb->s_flags |= SB_ACTIVE;
+       }
+
+       BUG_ON(fc->root);
+       fc->root = dget(sb->s_root);
+       return 0;
+}
+EXPORT_SYMBOL(vfs_get_super);
+
 #ifdef CONFIG_BLOCK
 static int set_bdev_super(struct super_block *s, void *data)
 {