Provide a function, vfs_get_mtd_super(), to replace mount_mtd(), using an
fs_context struct to hold the parameters.  The mtd device pointer is cached
in the struct, along with a destructor pointer as the mtd core may be in a
module and, as such, unreachable by the core code that would release the
fs_context.

Signed-off-by: David Howells <[email protected]>
cc: David Woodhouse <[email protected]>
cc: Brian Norris <[email protected]>
cc: Boris Brezillon <[email protected]>
cc: Marek Vasut <[email protected]>
cc: Richard Weinberger <[email protected]>
cc: [email protected]
---

 drivers/mtd/mtdcore.h      |    1 
 drivers/mtd/mtdsuper.c     |  192 ++++++++++++++++++++++++++++++++++++++++++++
 fs/fs_context.c            |    2 
 include/linux/fs_context.h |    2 
 include/linux/mtd/super.h  |    3 +
 5 files changed, 198 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/mtdcore.h b/drivers/mtd/mtdcore.h
index b31c868019ad..b5eefeabf310 100644
--- a/drivers/mtd/mtdcore.h
+++ b/drivers/mtd/mtdcore.h
@@ -5,6 +5,7 @@
  */
 
 extern struct mutex mtd_table_mutex;
+extern struct backing_dev_info *mtd_bdi;
 
 struct mtd_info *__mtd_next_device(int i);
 int __must_check add_mtd_device(struct mtd_info *mtd);
diff --git a/drivers/mtd/mtdsuper.c b/drivers/mtd/mtdsuper.c
index d58a61c09304..cf223c4aa862 100644
--- a/drivers/mtd/mtdsuper.c
+++ b/drivers/mtd/mtdsuper.c
@@ -19,6 +19,196 @@
 #include <linux/slab.h>
 #include <linux/major.h>
 #include <linux/backing-dev.h>
+#include <linux/fs_context.h>
+#include "mtdcore.h"
+
+static void mtd_fc_destructor(struct fs_context *fc)
+{
+       if (fc->mtd)
+               put_mtd_device(fc->mtd);
+}
+
+/*
+ * compare superblocks to see if they're equivalent
+ * - they are if the underlying MTD device is the same
+ */
+static int mtd_test_super(struct super_block *sb, struct fs_context *fc)
+{
+       struct mtd_info *mtd = fc->mtd;
+
+       if (sb->s_mtd == mtd) {
+               pr_debug("MTDSB: Match on device %d (\"%s\")\n",
+                        mtd->index, mtd->name);
+               return 1;
+       }
+
+       pr_debug("MTDSB: No match, device %d (\"%s\"), device %d (\"%s\")\n",
+                sb->s_mtd->index, sb->s_mtd->name, mtd->index, mtd->name);
+       return 0;
+}
+
+/*
+ * mark the superblock by the MTD device it is using
+ * - set the device number to be the correct MTD block device for pesuperstence
+ *   of NFS exports
+ */
+static int mtd_set_super(struct super_block *sb, struct fs_context *fc)
+{
+       struct mtd_info *mtd = fc->mtd;
+
+       sb->s_mtd = mtd;
+       sb->s_dev = MKDEV(MTD_BLOCK_MAJOR, mtd->index);
+       sb->s_bdi = bdi_get(mtd_bdi);
+       fc->mtd = NULL;
+       return 0;
+}
+
+/*
+ * get a superblock on an MTD-backed filesystem
+ */
+static int mtd_get_sb(struct fs_context *fc,
+                     int (*fill_super)(struct super_block *,
+                                       struct fs_context *))
+{
+       struct super_block *sb;
+       struct mtd_info *mtd = fc->mtd;
+       int ret;
+
+       sb = sget_fc(fc, mtd_test_super, mtd_set_super);
+       if (IS_ERR(sb))
+               return PTR_ERR(sb);
+
+       if (sb->s_root) {
+               /* new mountpoint for an already mounted superblock */
+               pr_debug("MTDSB: Device %d (\"%s\") is already mounted\n",
+                        mtd->index, mtd->name);
+               put_mtd_device(mtd);
+       } else {
+               /* fresh new superblock */
+               pr_debug("MTDSB: New superblock for device %d (\"%s\")\n",
+                        mtd->index, mtd->name);
+
+               ret = fill_super(sb, fc);
+               if (ret < 0)
+                       goto error_sb;
+
+               sb->s_flags |= SB_ACTIVE;
+       }
+
+       BUG_ON(fc->root);
+       fc->root = dget(sb->s_root);
+       return 0;
+
+error_sb:
+       deactivate_locked_super(sb);
+       return ret;
+}
+
+/*
+ * get a superblock on an MTD-backed filesystem by MTD device number
+ */
+static int mtd_get_sb_by_nr(struct fs_context *fc, int mtdnr,
+                           int (*fill_super)(struct super_block *,
+                                             struct fs_context *))
+{
+       struct mtd_info *mtd;
+
+       mtd = get_mtd_device(NULL, mtdnr);
+       if (IS_ERR(mtd)) {
+               errorf(fc, "MTDSB: Device #%u doesn't appear to exist\n", 
mtdnr);
+               return PTR_ERR(mtd);
+       }
+
+       fc->mtd = mtd;
+       fc->destructor = mtd_fc_destructor;
+       return mtd_get_sb(fc, fill_super);
+}
+
+/**
+ * vfs_get_mtd_super - Get a superblock based on a single MTD device
+ * @fc: The filesystem context holding the parameters
+ * @fill_super: Helper to initialise a new superblock
+ */
+int vfs_get_mtd_super(struct fs_context *fc,
+                     int (*fill_super)(struct super_block *sb,
+                                       struct fs_context *fc))
+{
+#ifdef CONFIG_BLOCK
+       struct block_device *bdev;
+       int ret, major;
+#endif
+       int mtdnr;
+
+       if (!fc->source)
+               return invalf(fc, "No source specified");
+
+       pr_debug("MTDSB: dev_name \"%s\"\n", fc->source);
+
+       /* the preferred way of mounting in future; especially when
+        * CONFIG_BLOCK=n - we specify the underlying MTD device by number or
+        * by name, so that we don't require block device support to be present
+        * in the kernel.
+        */
+       if (fc->source[0] == 'm' &&
+           fc->source[1] == 't' &&
+           fc->source[2] == 'd') {
+               if (fc->source[3] == ':') {
+                       struct mtd_info *mtd;
+
+                       /* mount by MTD device name */
+                       pr_debug("MTDSB: mtd:%%s, name \"%s\"\n",
+                                fc->source + 4);
+
+                       mtd = get_mtd_device_nm(fc->source + 4);
+                       if (!IS_ERR(mtd)) {
+                               fc->mtd = mtd;
+                               fc->destructor = mtd_fc_destructor;
+                               return mtd_get_sb(fc, fill_super);
+                       }
+
+                       errorf(fc, "MTD: MTD device with name \"%s\" not found",
+                              fc->source + 4);
+
+               } else if (isdigit(fc->source[3])) {
+                       /* mount by MTD device number name */
+                       char *endptr;
+
+                       mtdnr = simple_strtoul(fc->source + 3, &endptr, 0);
+                       if (!*endptr) {
+                               /* It was a valid number */
+                               pr_debug("MTDSB: mtd%%d, mtdnr %d\n", mtdnr);
+                               return mtd_get_sb_by_nr(fc, mtdnr, fill_super);
+                       }
+               }
+       }
+
+#ifdef CONFIG_BLOCK
+       /* try the old way - the hack where we allowed users to mount
+        * /dev/mtdblock$(n) but didn't actually _use_ the blockdev
+        */
+       bdev = lookup_bdev(fc->source);
+       if (IS_ERR(bdev)) {
+               ret = PTR_ERR(bdev);
+               errorf(fc, "MTD: Couldn't look up '%s': %d", fc->source, ret);
+               return ret;
+       }
+       pr_debug("MTDSB: lookup_bdev() returned 0\n");
+
+       major = MAJOR(bdev->bd_dev);
+       mtdnr = MINOR(bdev->bd_dev);
+       bdput(bdev);
+
+       if (major == MTD_BLOCK_MAJOR)
+               return mtd_get_sb_by_nr(fc, mtdnr, fill_super);
+
+#endif /* CONFIG_BLOCK */
+
+       if (!(fc->sb_flags & SB_SILENT))
+               errorf(fc, "MTD: Attempt to mount non-MTD device \"%s\"",
+                      fc->source);
+       return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(vfs_get_mtd_super);
 
 /*
  * compare superblocks to see if they're equivalent
@@ -39,8 +229,6 @@ static int get_sb_mtd_compare(struct super_block *sb, void 
*_mtd)
        return 0;
 }
 
-extern struct backing_dev_info *mtd_bdi;
-
 /*
  * mark the superblock by the MTD device it is using
  * - set the device number to be the correct MTD block device for pesuperstence
diff --git a/fs/fs_context.c b/fs/fs_context.c
index 3325b435cb6f..65e705d02521 100644
--- a/fs/fs_context.c
+++ b/fs/fs_context.c
@@ -427,6 +427,8 @@ void put_fs_context(struct fs_context *fc)
                fc->ops->free(fc);
        if (fc->bdev)
                blkdev_put(fc->bdev, fc->bdev_mode);
+       if (fc->destructor)
+               fc->destructor(fc);
 
        security_free_mnt_opts(&fc->security);
        put_net(fc->net_ns);
diff --git a/include/linux/fs_context.h b/include/linux/fs_context.h
index 524963c0cedb..8233c873af73 100644
--- a/include/linux/fs_context.h
+++ b/include/linux/fs_context.h
@@ -77,6 +77,7 @@ struct fs_context {
        struct file_system_type *fs_type;
        void                    *fs_private;    /* The filesystem's context */
        struct block_device     *bdev;          /* The backing blockdev (if 
applicable) */
+       struct mtd_info         *mtd;           /* The backing mtd (if 
applicable) */
        struct dentry           *root;          /* The root and superblock */
        struct user_namespace   *user_ns;       /* The user namespace for this 
mount */
        struct net              *net_ns;        /* The network namespace for 
this mount */
@@ -92,6 +93,7 @@ struct fs_context {
        enum fs_context_purpose purpose:8;
        bool                    need_free:1;    /* Need to call ops->free() */
        bool                    global:1;       /* Goes into &init_user_ns */
+       void (*destructor)(struct fs_context *fc); /* For mtd */
 };
 
 struct fs_context_operations {
diff --git a/include/linux/mtd/super.h b/include/linux/mtd/super.h
index f456230f9330..7ebd69e5c640 100644
--- a/include/linux/mtd/super.h
+++ b/include/linux/mtd/super.h
@@ -18,6 +18,9 @@
 #include <linux/fs.h>
 #include <linux/mount.h>
 
+extern int vfs_get_mtd_super(struct fs_context *fc,
+                            int (*fill_super)(struct super_block *sb,
+                                              struct fs_context *fc));
 extern struct dentry *mount_mtd(struct file_system_type *fs_type, int flags,
                      const char *dev_name, void *data,
                      int (*fill_super)(struct super_block *, void *, int));

Reply via email to