summaryrefslogtreecommitdiffstats
path: root/fs/romfs
diff options
context:
space:
mode:
authorJoel Fernandes <joelagnelf@nvidia.com>2026-07-18 13:15:36 -0400
committerPaul E. McKenney <paulmck@kernel.org>2026-07-23 10:39:57 -0700
commit34ff636f322c6d5e4f0a52338fa63a7dff09a495 (patch)
tree7629759c1ec7148b95bc8ab3080df20a5ecd2273 /fs/romfs
downloadlinux-stable-34ff636f322c6d5e4f0a52338fa63a7dff09a495.tar.gz
linux-stable-34ff636f322c6d5e4f0a52338fa63a7dff09a495.zip
scftorture: Count single_rpc offline failures in statistics outputgrafted
scf_torture_stats_print() aggregates each invoker thread's counters into a local scf_statistics structure before printing, but the n_single_rpc_ofl field is missing from the aggregation loop. As a result, the "single_rpc_ofl" value printed in the statistics line is always zero, even when smp_call_function_single() invocations for the RPC test have failed due to offline CPUs and been counted by the invoker threads. Add the missing accumulation so that the printed value reflects the actual counts. Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com> Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Diffstat (limited to 'fs/romfs')
-rw-r--r--fs/romfs/Kconfig64
-rw-r--r--fs/romfs/Makefile13
-rw-r--r--fs/romfs/internal.h43
-rw-r--r--fs/romfs/mmap-nommu.c85
-rw-r--r--fs/romfs/storage.c287
-rw-r--r--fs/romfs/super.c664
6 files changed, 1156 insertions, 0 deletions
diff --git a/fs/romfs/Kconfig b/fs/romfs/Kconfig
new file mode 100644
index 000000000..f24a96a33
--- /dev/null
+++ b/fs/romfs/Kconfig
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config ROMFS_FS
+ tristate "ROM file system support"
+ depends on BLOCK || MTD
+ help
+ This is a very small read-only file system mainly intended for
+ initial ram disks of installation disks, but it could be used for
+ other read-only media as well. Read
+ <file:Documentation/filesystems/romfs.rst> for details.
+
+ To compile this file system support as a module, choose M here: the
+ module will be called romfs. Note that the file system of your
+ root partition (the one containing the directory /) cannot be a
+ module.
+
+ If you don't know whether you need it, then you don't need it:
+ answer N.
+
+#
+# Select the backing stores to be supported
+#
+choice
+ prompt "RomFS backing stores"
+ depends on ROMFS_FS
+ default ROMFS_BACKED_BY_BLOCK
+ help
+ Select the backing stores to be supported.
+
+config ROMFS_BACKED_BY_BLOCK
+ bool "Block device-backed ROM file system support"
+ depends on BLOCK
+ help
+ This permits ROMFS to use block devices buffered through the page
+ cache as the medium from which to retrieve data. It does not allow
+ direct mapping of the medium.
+
+ If unsure, answer Y.
+
+config ROMFS_BACKED_BY_MTD
+ bool "MTD-backed ROM file system support"
+ depends on MTD=y || (ROMFS_FS=m && MTD)
+ help
+ This permits ROMFS to use MTD based devices directly, without the
+ intercession of the block layer (which may have been disabled). It
+ also allows direct mapping of MTD devices through romfs files under
+ NOMMU conditions if the underlying device is directly addressable by
+ the CPU.
+
+ If unsure, answer Y.
+
+config ROMFS_BACKED_BY_BOTH
+ bool "Both the above"
+ depends on BLOCK && (MTD=y || (ROMFS_FS=m && MTD))
+endchoice
+
+
+config ROMFS_ON_BLOCK
+ bool
+ default y if ROMFS_BACKED_BY_BLOCK || ROMFS_BACKED_BY_BOTH
+ select BUFFER_HEAD
+
+config ROMFS_ON_MTD
+ bool
+ default y if ROMFS_BACKED_BY_MTD || ROMFS_BACKED_BY_BOTH
diff --git a/fs/romfs/Makefile b/fs/romfs/Makefile
new file mode 100644
index 000000000..844928f15
--- /dev/null
+++ b/fs/romfs/Makefile
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the linux RomFS filesystem routines.
+#
+
+obj-$(CONFIG_ROMFS_FS) += romfs.o
+
+romfs-y := storage.o super.o
+
+ifneq ($(CONFIG_MMU),y)
+romfs-$(CONFIG_ROMFS_ON_MTD) += mmap-nommu.o
+endif
+
diff --git a/fs/romfs/internal.h b/fs/romfs/internal.h
new file mode 100644
index 000000000..e90df22bd
--- /dev/null
+++ b/fs/romfs/internal.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* RomFS internal definitions
+ *
+ * Copyright © 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#include <linux/romfs_fs.h>
+
+struct romfs_inode_info {
+ struct inode vfs_inode;
+ unsigned long i_metasize; /* size of non-data area */
+ unsigned long i_dataoffset; /* from the start of fs */
+};
+
+static inline size_t romfs_maxsize(struct super_block *sb)
+{
+ return (size_t) (unsigned long) sb->s_fs_info;
+}
+
+static inline struct romfs_inode_info *ROMFS_I(struct inode *inode)
+{
+ return container_of(inode, struct romfs_inode_info, vfs_inode);
+}
+
+/*
+ * mmap-nommu.c
+ */
+#if !defined(CONFIG_MMU) && defined(CONFIG_ROMFS_ON_MTD)
+extern const struct file_operations romfs_ro_fops;
+#else
+#define romfs_ro_fops generic_ro_fops
+#endif
+
+/*
+ * storage.c
+ */
+extern int romfs_dev_read(struct super_block *sb, unsigned long pos,
+ void *buf, size_t buflen);
+extern ssize_t romfs_dev_strnlen(struct super_block *sb,
+ unsigned long pos, size_t maxlen);
+extern int romfs_dev_strcmp(struct super_block *sb, unsigned long pos,
+ const char *str, size_t size);
diff --git a/fs/romfs/mmap-nommu.c b/fs/romfs/mmap-nommu.c
new file mode 100644
index 000000000..7c3a1a7fe
--- /dev/null
+++ b/fs/romfs/mmap-nommu.c
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* NOMMU mmap support for RomFS on MTD devices
+ *
+ * Copyright © 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#include <linux/mm.h>
+#include <linux/mtd/super.h>
+#include "internal.h"
+
+/*
+ * try to determine where a shared mapping can be made
+ * - only supported for NOMMU at the moment (MMU can't doesn't copy private
+ * mappings)
+ * - attempts to map through to the underlying MTD device
+ */
+static unsigned long romfs_get_unmapped_area(struct file *file,
+ unsigned long addr,
+ unsigned long len,
+ unsigned long pgoff,
+ unsigned long flags)
+{
+ struct inode *inode = file->f_mapping->host;
+ struct mtd_info *mtd = inode->i_sb->s_mtd;
+ unsigned long isize, offset, maxpages, lpages;
+ int ret;
+
+ if (!mtd)
+ return (unsigned long) -ENOSYS;
+
+ /* the mapping mustn't extend beyond the EOF */
+ lpages = (len + PAGE_SIZE - 1) >> PAGE_SHIFT;
+ isize = i_size_read(inode);
+ offset = pgoff << PAGE_SHIFT;
+
+ maxpages = (isize + PAGE_SIZE - 1) >> PAGE_SHIFT;
+ if ((pgoff >= maxpages) || (maxpages - pgoff < lpages))
+ return (unsigned long) -EINVAL;
+
+ if (addr != 0)
+ return (unsigned long) -EINVAL;
+
+ if (len > mtd->size || pgoff >= (mtd->size >> PAGE_SHIFT))
+ return (unsigned long) -EINVAL;
+
+ offset += ROMFS_I(inode)->i_dataoffset;
+ if (offset >= mtd->size)
+ return (unsigned long) -EINVAL;
+ /* the mapping mustn't extend beyond the EOF */
+ if ((offset + len) > mtd->size)
+ len = mtd->size - offset;
+
+ ret = mtd_get_unmapped_area(mtd, len, offset, flags);
+ if (ret == -EOPNOTSUPP)
+ ret = -ENOSYS;
+ return (unsigned long) ret;
+}
+
+/*
+ * permit a R/O mapping to be made directly through onto an MTD device if
+ * possible
+ */
+static int romfs_mmap_prepare(struct vm_area_desc *desc)
+{
+ return is_nommu_shared_vma_flags(&desc->vma_flags) ? 0 : -ENOSYS;
+}
+
+static unsigned romfs_mmap_capabilities(struct file *file)
+{
+ struct mtd_info *mtd = file_inode(file)->i_sb->s_mtd;
+
+ if (!mtd)
+ return NOMMU_MAP_COPY;
+ return mtd_mmap_capabilities(mtd);
+}
+
+const struct file_operations romfs_ro_fops = {
+ .llseek = generic_file_llseek,
+ .read_iter = generic_file_read_iter,
+ .splice_read = filemap_splice_read,
+ .mmap_prepare = romfs_mmap_prepare,
+ .get_unmapped_area = romfs_get_unmapped_area,
+ .mmap_capabilities = romfs_mmap_capabilities,
+};
diff --git a/fs/romfs/storage.c b/fs/romfs/storage.c
new file mode 100644
index 000000000..b57b3ffcb
--- /dev/null
+++ b/fs/romfs/storage.c
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* RomFS storage access routines
+ *
+ * Copyright © 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#include <linux/fs.h>
+#include <linux/mtd/super.h>
+#include <linux/buffer_head.h>
+#include "internal.h"
+
+#if !defined(CONFIG_ROMFS_ON_MTD) && !defined(CONFIG_ROMFS_ON_BLOCK)
+#error no ROMFS backing store interface configured
+#endif
+
+#ifdef CONFIG_ROMFS_ON_MTD
+#define ROMFS_MTD_READ(sb, ...) mtd_read((sb)->s_mtd, ##__VA_ARGS__)
+
+/*
+ * read data from an romfs image on an MTD device
+ */
+static int romfs_mtd_read(struct super_block *sb, unsigned long pos,
+ void *buf, size_t buflen)
+{
+ size_t rlen;
+ int ret;
+
+ ret = ROMFS_MTD_READ(sb, pos, buflen, &rlen, buf);
+ return (ret < 0 || rlen != buflen) ? -EIO : 0;
+}
+
+/*
+ * determine the length of a string in a romfs image on an MTD device
+ */
+static ssize_t romfs_mtd_strnlen(struct super_block *sb,
+ unsigned long pos, size_t maxlen)
+{
+ ssize_t n = 0;
+ size_t segment;
+ u_char buf[16], *p;
+ size_t len;
+ int ret;
+
+ /* scan the string up to 16 bytes at a time */
+ while (maxlen > 0) {
+ segment = min_t(size_t, maxlen, 16);
+ ret = ROMFS_MTD_READ(sb, pos, segment, &len, buf);
+ if (ret < 0)
+ return ret;
+ p = memchr(buf, 0, len);
+ if (p)
+ return n + (p - buf);
+ maxlen -= len;
+ pos += len;
+ n += len;
+ }
+
+ return n;
+}
+
+/*
+ * compare a string to one in a romfs image on MTD
+ * - return 1 if matched, 0 if differ, -ve if error
+ */
+static int romfs_mtd_strcmp(struct super_block *sb, unsigned long pos,
+ const char *str, size_t size)
+{
+ u_char buf[17];
+ size_t len, segment;
+ int ret;
+
+ /* scan the string up to 16 bytes at a time, and attempt to grab the
+ * trailing NUL whilst we're at it */
+ buf[0] = 0xff;
+
+ while (size > 0) {
+ segment = min_t(size_t, size + 1, 17);
+ ret = ROMFS_MTD_READ(sb, pos, segment, &len, buf);
+ if (ret < 0)
+ return ret;
+ len--;
+ if (memcmp(buf, str, len) != 0)
+ return 0;
+ buf[0] = buf[len];
+ size -= len;
+ pos += len;
+ str += len;
+ }
+
+ /* check the trailing NUL was */
+ if (buf[0])
+ return 0;
+
+ return 1;
+}
+#endif /* CONFIG_ROMFS_ON_MTD */
+
+#ifdef CONFIG_ROMFS_ON_BLOCK
+/*
+ * read data from an romfs image on a block device
+ */
+static int romfs_blk_read(struct super_block *sb, unsigned long pos,
+ void *buf, size_t buflen)
+{
+ struct buffer_head *bh;
+ unsigned long offset;
+ size_t segment;
+
+ /* copy the string up to blocksize bytes at a time */
+ while (buflen > 0) {
+ offset = pos & (ROMBSIZE - 1);
+ segment = min_t(size_t, buflen, ROMBSIZE - offset);
+ bh = sb_bread(sb, pos >> ROMBSBITS);
+ if (!bh)
+ return -EIO;
+ memcpy(buf, bh->b_data + offset, segment);
+ brelse(bh);
+ buf += segment;
+ buflen -= segment;
+ pos += segment;
+ }
+
+ return 0;
+}
+
+/*
+ * determine the length of a string in romfs on a block device
+ */
+static ssize_t romfs_blk_strnlen(struct super_block *sb,
+ unsigned long pos, size_t limit)
+{
+ struct buffer_head *bh;
+ unsigned long offset;
+ ssize_t n = 0;
+ size_t segment;
+ u_char *buf, *p;
+
+ /* scan the string up to blocksize bytes at a time */
+ while (limit > 0) {
+ offset = pos & (ROMBSIZE - 1);
+ segment = min_t(size_t, limit, ROMBSIZE - offset);
+ bh = sb_bread(sb, pos >> ROMBSBITS);
+ if (!bh)
+ return -EIO;
+ buf = bh->b_data + offset;
+ p = memchr(buf, 0, segment);
+ brelse(bh);
+ if (p)
+ return n + (p - buf);
+ limit -= segment;
+ pos += segment;
+ n += segment;
+ }
+
+ return n;
+}
+
+/*
+ * compare a string to one in a romfs image on a block device
+ * - return 1 if matched, 0 if differ, -ve if error
+ */
+static int romfs_blk_strcmp(struct super_block *sb, unsigned long pos,
+ const char *str, size_t size)
+{
+ struct buffer_head *bh;
+ unsigned long offset;
+ size_t segment;
+ bool matched, terminated = false;
+
+ /* compare string up to a block at a time */
+ while (size > 0) {
+ offset = pos & (ROMBSIZE - 1);
+ segment = min_t(size_t, size, ROMBSIZE - offset);
+ bh = sb_bread(sb, pos >> ROMBSBITS);
+ if (!bh)
+ return -EIO;
+ matched = (memcmp(bh->b_data + offset, str, segment) == 0);
+
+ size -= segment;
+ pos += segment;
+ str += segment;
+ if (matched && size == 0 && offset + segment < ROMBSIZE) {
+ if (!bh->b_data[offset + segment])
+ terminated = true;
+ else
+ matched = false;
+ }
+ brelse(bh);
+ if (!matched)
+ return 0;
+ }
+
+ if (!terminated) {
+ /* the terminating NUL must be on the first byte of the next
+ * block */
+ BUG_ON((pos & (ROMBSIZE - 1)) != 0);
+ bh = sb_bread(sb, pos >> ROMBSBITS);
+ if (!bh)
+ return -EIO;
+ matched = !bh->b_data[0];
+ brelse(bh);
+ if (!matched)
+ return 0;
+ }
+
+ return 1;
+}
+#endif /* CONFIG_ROMFS_ON_BLOCK */
+
+/*
+ * read data from the romfs image
+ */
+int romfs_dev_read(struct super_block *sb, unsigned long pos,
+ void *buf, size_t buflen)
+{
+ size_t limit;
+
+ limit = romfs_maxsize(sb);
+ if (pos >= limit || buflen > limit - pos)
+ return -EIO;
+
+#ifdef CONFIG_ROMFS_ON_MTD
+ if (sb->s_mtd)
+ return romfs_mtd_read(sb, pos, buf, buflen);
+#endif
+#ifdef CONFIG_ROMFS_ON_BLOCK
+ if (sb->s_bdev)
+ return romfs_blk_read(sb, pos, buf, buflen);
+#endif
+ return -EIO;
+}
+
+/*
+ * determine the length of a string in romfs
+ */
+ssize_t romfs_dev_strnlen(struct super_block *sb,
+ unsigned long pos, size_t maxlen)
+{
+ size_t limit;
+
+ limit = romfs_maxsize(sb);
+ if (pos >= limit)
+ return -EIO;
+ if (maxlen > limit - pos)
+ maxlen = limit - pos;
+
+#ifdef CONFIG_ROMFS_ON_MTD
+ if (sb->s_mtd)
+ return romfs_mtd_strnlen(sb, pos, maxlen);
+#endif
+#ifdef CONFIG_ROMFS_ON_BLOCK
+ if (sb->s_bdev)
+ return romfs_blk_strnlen(sb, pos, maxlen);
+#endif
+ return -EIO;
+}
+
+/*
+ * compare a string to one in romfs
+ * - the string to be compared to, str, may not be NUL-terminated; instead the
+ * string is of the specified size
+ * - return 1 if matched, 0 if differ, -ve if error
+ */
+int romfs_dev_strcmp(struct super_block *sb, unsigned long pos,
+ const char *str, size_t size)
+{
+ size_t limit;
+
+ limit = romfs_maxsize(sb);
+ if (pos >= limit)
+ return -EIO;
+ if (size > ROMFS_MAXFN)
+ return -ENAMETOOLONG;
+ if (size + 1 > limit - pos)
+ return -EIO;
+
+#ifdef CONFIG_ROMFS_ON_MTD
+ if (sb->s_mtd)
+ return romfs_mtd_strcmp(sb, pos, str, size);
+#endif
+#ifdef CONFIG_ROMFS_ON_BLOCK
+ if (sb->s_bdev)
+ return romfs_blk_strcmp(sb, pos, str, size);
+#endif
+ return -EIO;
+}
diff --git a/fs/romfs/super.c b/fs/romfs/super.c
new file mode 100644
index 000000000..ac55193bf
--- /dev/null
+++ b/fs/romfs/super.c
@@ -0,0 +1,664 @@
+/* Block- or MTD-based romfs
+ *
+ * Copyright © 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * Derived from: ROMFS file system, Linux implementation
+ *
+ * Copyright © 1997-1999 Janos Farkas <chexum@shadow.banki.hu>
+ *
+ * Using parts of the minix filesystem
+ * Copyright © 1991, 1992 Linus Torvalds
+ *
+ * and parts of the affs filesystem additionally
+ * Copyright © 1993 Ray Burr
+ * Copyright © 1996 Hans-Joachim Widmaier
+ *
+ * Changes
+ * Changed for 2.1.19 modules
+ * Jan 1997 Initial release
+ * Jun 1997 2.1.43+ changes
+ * Proper page locking in read_folio
+ * Changed to work with 2.1.45+ fs
+ * Jul 1997 Fixed follow_link
+ * 2.1.47
+ * lookup shouldn't return -ENOENT
+ * from Horst von Brand:
+ * fail on wrong checksum
+ * double unlock_super was possible
+ * correct namelen for statfs
+ * spotted by Bill Hawes:
+ * readlink shouldn't iput()
+ * Jun 1998 2.1.106 from Avery Pennarun: glibc scandir()
+ * exposed a problem in readdir
+ * 2.1.107 code-freeze spellchecker run
+ * Aug 1998 2.1.118+ VFS changes
+ * Sep 1998 2.1.122 another VFS change (follow_link)
+ * Apr 1999 2.2.7 no more EBADF checking in
+ * lookup/readdir, use ERR_PTR
+ * Jun 1999 2.3.6 d_alloc_root use changed
+ * 2.3.9 clean up usage of ENOENT/negative
+ * dentries in lookup
+ * clean up page flags setting
+ * (error, uptodate, locking) in
+ * in read_folio
+ * use init_special_inode for
+ * fifos/sockets (and streamline) in
+ * read_inode, fix _ops table order
+ * Aug 1999 2.3.16 __initfunc() => __init change
+ * Oct 1999 2.3.24 page->owner hack obsoleted
+ * Nov 1999 2.3.27 2.3.25+ page->offset => index change
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/fs.h>
+#include <linux/time.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/blkdev.h>
+#include <linux/fs_context.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/statfs.h>
+#include <linux/mtd/super.h>
+#include <linux/ctype.h>
+#include <linux/highmem.h>
+#include <linux/pagemap.h>
+#include <linux/uaccess.h>
+#include <linux/major.h>
+#include "internal.h"
+
+static struct kmem_cache *romfs_inode_cachep;
+
+static const umode_t romfs_modemap[8] = {
+ 0, /* hard link */
+ S_IFDIR | 0644, /* directory */
+ S_IFREG | 0644, /* regular file */
+ S_IFLNK | 0777, /* symlink */
+ S_IFBLK | 0600, /* blockdev */
+ S_IFCHR | 0600, /* chardev */
+ S_IFSOCK | 0644, /* socket */
+ S_IFIFO | 0644 /* FIFO */
+};
+
+static const unsigned char romfs_dtype_table[] = {
+ DT_UNKNOWN, DT_DIR, DT_REG, DT_LNK, DT_BLK, DT_CHR, DT_SOCK, DT_FIFO
+};
+
+static struct inode *romfs_iget(struct super_block *sb, unsigned long pos);
+
+/*
+ * read a page worth of data from the image
+ */
+static int romfs_read_folio(struct file *file, struct folio *folio)
+{
+ struct inode *inode = folio->mapping->host;
+ loff_t offset, size;
+ unsigned long fillsize, pos;
+ void *buf;
+ int ret;
+
+ buf = kmap_local_folio(folio, 0);
+
+ offset = folio_pos(folio);
+ size = i_size_read(inode);
+ fillsize = 0;
+ ret = 0;
+ if (offset < size) {
+ size -= offset;
+ fillsize = size > PAGE_SIZE ? PAGE_SIZE : size;
+
+ pos = ROMFS_I(inode)->i_dataoffset + offset;
+
+ ret = romfs_dev_read(inode->i_sb, pos, buf, fillsize);
+ if (ret < 0) {
+ fillsize = 0;
+ ret = -EIO;
+ }
+ }
+
+ buf = folio_zero_tail(folio, fillsize, buf + fillsize);
+ kunmap_local(buf);
+ folio_end_read(folio, ret == 0);
+ return ret;
+}
+
+static const struct address_space_operations romfs_aops = {
+ .read_folio = romfs_read_folio
+};
+
+/*
+ * read the entries from a directory
+ */
+static int romfs_readdir(struct file *file, struct dir_context *ctx)
+{
+ struct inode *i = file_inode(file);
+ struct romfs_inode ri;
+ unsigned long offset, maxoff;
+ int j, ino, nextfh;
+ char fsname[ROMFS_MAXFN]; /* XXX dynamic? */
+ int ret;
+
+ maxoff = romfs_maxsize(i->i_sb);
+
+ offset = ctx->pos;
+ if (!offset) {
+ offset = i->i_ino & ROMFH_MASK;
+ ret = romfs_dev_read(i->i_sb, offset, &ri, ROMFH_SIZE);
+ if (ret < 0)
+ goto out;
+ offset = be32_to_cpu(ri.spec) & ROMFH_MASK;
+ }
+
+ /* Not really failsafe, but we are read-only... */
+ for (;;) {
+ if (!offset || offset >= maxoff) {
+ offset = maxoff;
+ ctx->pos = offset;
+ goto out;
+ }
+ ctx->pos = offset;
+
+ /* Fetch inode info */
+ ret = romfs_dev_read(i->i_sb, offset, &ri, ROMFH_SIZE);
+ if (ret < 0)
+ goto out;
+
+ j = romfs_dev_strnlen(i->i_sb, offset + ROMFH_SIZE,
+ sizeof(fsname) - 1);
+ if (j < 0)
+ goto out;
+
+ ret = romfs_dev_read(i->i_sb, offset + ROMFH_SIZE, fsname, j);
+ if (ret < 0)
+ goto out;
+ fsname[j] = '\0';
+
+ ino = offset;
+ nextfh = be32_to_cpu(ri.next);
+ if ((nextfh & ROMFH_TYPE) == ROMFH_HRD)
+ ino = be32_to_cpu(ri.spec);
+ if (!dir_emit(ctx, fsname, j, ino,
+ romfs_dtype_table[nextfh & ROMFH_TYPE]))
+ goto out;
+
+ offset = nextfh & ROMFH_MASK;
+ }
+out:
+ return 0;
+}
+
+/*
+ * look up an entry in a directory
+ */
+static struct dentry *romfs_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags)
+{
+ unsigned long offset, maxoff;
+ struct inode *inode = NULL;
+ struct romfs_inode ri;
+ const char *name; /* got from dentry */
+ int len, ret;
+
+ offset = dir->i_ino & ROMFH_MASK;
+ ret = romfs_dev_read(dir->i_sb, offset, &ri, ROMFH_SIZE);
+ if (ret < 0)
+ goto error;
+
+ /* search all the file entries in the list starting from the one
+ * pointed to by the directory's special data */
+ maxoff = romfs_maxsize(dir->i_sb);
+ offset = be32_to_cpu(ri.spec) & ROMFH_MASK;
+
+ name = dentry->d_name.name;
+ len = dentry->d_name.len;
+
+ for (;;) {
+ if (!offset || offset >= maxoff)
+ break;
+
+ ret = romfs_dev_read(dir->i_sb, offset, &ri, sizeof(ri));
+ if (ret < 0)
+ goto error;
+
+ /* try to match the first 16 bytes of name */
+ ret = romfs_dev_strcmp(dir->i_sb, offset + ROMFH_SIZE, name,
+ len);
+ if (ret < 0)
+ goto error;
+ if (ret == 1) {
+ /* Hard link handling */
+ if ((be32_to_cpu(ri.next) & ROMFH_TYPE) == ROMFH_HRD)
+ offset = be32_to_cpu(ri.spec) & ROMFH_MASK;
+ inode = romfs_iget(dir->i_sb, offset);
+ break;
+ }
+
+ /* next entry */
+ offset = be32_to_cpu(ri.next) & ROMFH_MASK;
+ }
+
+ return d_splice_alias(inode, dentry);
+error:
+ return ERR_PTR(ret);
+}
+
+static const struct file_operations romfs_dir_operations = {
+ .read = generic_read_dir,
+ .iterate_shared = romfs_readdir,
+ .llseek = generic_file_llseek,
+};
+
+static const struct inode_operations romfs_dir_inode_operations = {
+ .lookup = romfs_lookup,
+};
+
+/*
+ * get a romfs inode based on its position in the image (which doubles as the
+ * inode number)
+ */
+static struct inode *romfs_iget(struct super_block *sb, unsigned long pos)
+{
+ struct romfs_inode_info *inode;
+ struct romfs_inode ri;
+ struct inode *i;
+ unsigned long nlen;
+ unsigned nextfh;
+ int ret;
+ umode_t mode;
+
+ /* we might have to traverse a chain of "hard link" file entries to get
+ * to the actual file */
+ for (;;) {
+ ret = romfs_dev_read(sb, pos, &ri, sizeof(ri));
+ if (ret < 0)
+ goto error;
+
+ /* XXX: do romfs_checksum here too (with name) */
+
+ nextfh = be32_to_cpu(ri.next);
+ if ((nextfh & ROMFH_TYPE) != ROMFH_HRD)
+ break;
+
+ pos = be32_to_cpu(ri.spec) & ROMFH_MASK;
+ }
+
+ /* determine the length of the filename */
+ nlen = romfs_dev_strnlen(sb, pos + ROMFH_SIZE, ROMFS_MAXFN);
+ if (IS_ERR_VALUE(nlen))
+ goto eio;
+
+ /* get an inode for this image position */
+ i = iget_locked(sb, pos);
+ if (!i)
+ return ERR_PTR(-ENOMEM);
+
+ if (!(inode_state_read_once(i) & I_NEW))
+ return i;
+
+ /* precalculate the data offset */
+ inode = ROMFS_I(i);
+ inode->i_metasize = (ROMFH_SIZE + nlen + 1 + ROMFH_PAD) & ROMFH_MASK;
+ inode->i_dataoffset = pos + inode->i_metasize;
+
+ set_nlink(i, 1); /* Hard to decide.. */
+ i->i_size = be32_to_cpu(ri.size);
+ inode_set_mtime_to_ts(i,
+ inode_set_atime_to_ts(i, inode_set_ctime(i, 0, 0)));
+
+ /* set up mode and ops */
+ mode = romfs_modemap[nextfh & ROMFH_TYPE];
+
+ switch (nextfh & ROMFH_TYPE) {
+ case ROMFH_DIR:
+ i->i_size = ROMFS_I(i)->i_metasize;
+ i->i_op = &romfs_dir_inode_operations;
+ i->i_fop = &romfs_dir_operations;
+ if (nextfh & ROMFH_EXEC)
+ mode |= S_IXUGO;
+ break;
+ case ROMFH_REG:
+ i->i_fop = &romfs_ro_fops;
+ i->i_data.a_ops = &romfs_aops;
+ if (nextfh & ROMFH_EXEC)
+ mode |= S_IXUGO;
+ break;
+ case ROMFH_SYM:
+ i->i_op = &page_symlink_inode_operations;
+ inode_nohighmem(i);
+ i->i_data.a_ops = &romfs_aops;
+ mode |= S_IRWXUGO;
+ break;
+ default:
+ /* depending on MBZ for sock/fifos */
+ nextfh = be32_to_cpu(ri.spec);
+ init_special_inode(i, mode, MKDEV(nextfh >> 16,
+ nextfh & 0xffff));
+ break;
+ }
+
+ i->i_mode = mode;
+ i->i_blocks = (i->i_size + 511) >> 9;
+
+ unlock_new_inode(i);
+ return i;
+
+eio:
+ ret = -EIO;
+error:
+ pr_err("read error for inode 0x%lx\n", pos);
+ return ERR_PTR(ret);
+}
+
+/*
+ * allocate a new inode
+ */
+static struct inode *romfs_alloc_inode(struct super_block *sb)
+{
+ struct romfs_inode_info *inode;
+
+ inode = alloc_inode_sb(sb, romfs_inode_cachep, GFP_KERNEL);
+ return inode ? &inode->vfs_inode : NULL;
+}
+
+/*
+ * return a spent inode to the slab cache
+ */
+static void romfs_free_inode(struct inode *inode)
+{
+ kmem_cache_free(romfs_inode_cachep, ROMFS_I(inode));
+}
+
+/*
+ * get filesystem statistics
+ */
+static int romfs_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+ struct super_block *sb = dentry->d_sb;
+ u64 id = 0;
+
+ /* When calling huge_encode_dev(),
+ * use sb->s_bdev->bd_dev when,
+ * - CONFIG_ROMFS_ON_BLOCK defined
+ * use sb->s_dev when,
+ * - CONFIG_ROMFS_ON_BLOCK undefined and
+ * - CONFIG_ROMFS_ON_MTD defined
+ * leave id as 0 when,
+ * - CONFIG_ROMFS_ON_BLOCK undefined and
+ * - CONFIG_ROMFS_ON_MTD undefined
+ */
+ if (sb->s_bdev)
+ id = huge_encode_dev(sb->s_bdev->bd_dev);
+ else if (sb->s_dev)
+ id = huge_encode_dev(sb->s_dev);
+
+ buf->f_type = ROMFS_MAGIC;
+ buf->f_namelen = ROMFS_MAXFN;
+ buf->f_bsize = ROMBSIZE;
+ buf->f_bfree = buf->f_bavail = buf->f_ffree;
+ buf->f_blocks =
+ (romfs_maxsize(dentry->d_sb) + ROMBSIZE - 1) >> ROMBSBITS;
+ buf->f_fsid = u64_to_fsid(id);
+ return 0;
+}
+
+/*
+ * remounting must involve read-only
+ */
+static int romfs_reconfigure(struct fs_context *fc)
+{
+ sync_filesystem(fc->root->d_sb);
+ fc->sb_flags |= SB_RDONLY;
+ return 0;
+}
+
+static const struct super_operations romfs_super_ops = {
+ .alloc_inode = romfs_alloc_inode,
+ .free_inode = romfs_free_inode,
+ .statfs = romfs_statfs,
+};
+
+/*
+ * checksum check on part of a romfs filesystem
+ */
+static __u32 romfs_checksum(const void *data, int size)
+{
+ const __be32 *ptr = data;
+ __u32 sum;
+
+ sum = 0;
+ size >>= 2;
+ while (size > 0) {
+ sum += be32_to_cpu(*ptr++);
+ size--;
+ }
+ return sum;
+}
+
+/*
+ * fill in the superblock
+ */
+static int romfs_fill_super(struct super_block *sb, struct fs_context *fc)
+{
+ struct romfs_super_block *rsb;
+ struct inode *root;
+ unsigned long pos, img_size;
+ const char *storage;
+ size_t len;
+ int ret;
+
+#ifdef CONFIG_BLOCK
+ if (!sb->s_mtd) {
+ if (!sb_set_blocksize(sb, ROMBSIZE)) {
+ errorf(fc, "romfs: unable to set blocksize\n");
+ return -EINVAL;
+ }
+ } else {
+ sb->s_blocksize = ROMBSIZE;
+ sb->s_blocksize_bits = blksize_bits(ROMBSIZE);
+ }
+#endif
+
+ sb->s_maxbytes = 0xFFFFFFFF;
+ sb->s_magic = ROMFS_MAGIC;
+ sb->s_flags |= SB_RDONLY | SB_NOATIME;
+ sb->s_time_min = 0;
+ sb->s_time_max = 0;
+ sb->s_op = &romfs_super_ops;
+
+#ifdef CONFIG_ROMFS_ON_MTD
+ /* Use same dev ID from the underlying mtdblock device */
+ if (sb->s_mtd)
+ sb->s_dev = MKDEV(MTD_BLOCK_MAJOR, sb->s_mtd->index);
+#endif
+ /* read the image superblock and check it */
+ rsb = kmalloc(512, GFP_KERNEL);
+ if (!rsb)
+ return -ENOMEM;
+
+ sb->s_fs_info = (void *) 512;
+ ret = romfs_dev_read(sb, 0, rsb, 512);
+ if (ret < 0)
+ goto error_rsb;
+
+ img_size = be32_to_cpu(rsb->size);
+
+ if (sb->s_mtd && img_size > sb->s_mtd->size)
+ goto error_rsb_inval;
+
+ sb->s_fs_info = (void *) img_size;
+
+ if (rsb->word0 != ROMSB_WORD0 || rsb->word1 != ROMSB_WORD1 ||
+ img_size < ROMFH_SIZE) {
+ if (!(fc->sb_flags & SB_SILENT))
+ errorf(fc, "VFS: Can't find a romfs filesystem on dev %s.\n",
+ sb->s_id);
+ goto error_rsb_inval;
+ }
+
+ if (romfs_checksum(rsb, min_t(size_t, img_size, 512))) {
+ pr_err("bad initial checksum on dev %s.\n", sb->s_id);
+ goto error_rsb_inval;
+ }
+
+ storage = sb->s_mtd ? "MTD" : "the block layer";
+
+ len = strnlen(rsb->name, ROMFS_MAXFN);
+ if (!(fc->sb_flags & SB_SILENT))
+ pr_notice("Mounting image '%*.*s' through %s\n",
+ (unsigned) len, (unsigned) len, rsb->name, storage);
+
+ kfree(rsb);
+ rsb = NULL;
+
+ /* find the root directory */
+ pos = (ROMFH_SIZE + len + 1 + ROMFH_PAD) & ROMFH_MASK;
+
+ root = romfs_iget(sb, pos);
+ if (IS_ERR(root))
+ return PTR_ERR(root);
+
+ sb->s_root = d_make_root(root);
+ if (!sb->s_root)
+ return -ENOMEM;
+
+ return 0;
+
+error_rsb_inval:
+ ret = -EINVAL;
+error_rsb:
+ kfree(rsb);
+ return ret;
+}
+
+/*
+ * get a superblock for mounting
+ */
+static int romfs_get_tree(struct fs_context *fc)
+{
+ int ret = -EINVAL;
+
+#ifdef CONFIG_ROMFS_ON_MTD
+ ret = get_tree_mtd(fc, romfs_fill_super);
+#endif
+#ifdef CONFIG_ROMFS_ON_BLOCK
+ if (ret == -EINVAL)
+ ret = get_tree_bdev(fc, romfs_fill_super);
+#endif
+ return ret;
+}
+
+static const struct fs_context_operations romfs_context_ops = {
+ .get_tree = romfs_get_tree,
+ .reconfigure = romfs_reconfigure,
+};
+
+/*
+ * Set up the filesystem mount context.
+ */
+static int romfs_init_fs_context(struct fs_context *fc)
+{
+ fc->ops = &romfs_context_ops;
+ return 0;
+}
+
+/*
+ * destroy a romfs superblock in the appropriate manner
+ */
+static void romfs_kill_sb(struct super_block *sb)
+{
+ generic_shutdown_super(sb);
+
+#ifdef CONFIG_ROMFS_ON_MTD
+ if (sb->s_mtd) {
+ put_mtd_device(sb->s_mtd);
+ sb->s_mtd = NULL;
+ }
+#endif
+#ifdef CONFIG_ROMFS_ON_BLOCK
+ if (sb->s_bdev) {
+ sync_blockdev(sb->s_bdev);
+ bdev_fput(sb->s_bdev_file);
+ }
+#endif
+}
+
+static struct file_system_type romfs_fs_type = {
+ .owner = THIS_MODULE,
+ .name = "romfs",
+ .init_fs_context = romfs_init_fs_context,
+ .kill_sb = romfs_kill_sb,
+ .fs_flags = FS_REQUIRES_DEV,
+};
+MODULE_ALIAS_FS("romfs");
+
+/*
+ * inode storage initialiser
+ */
+static void romfs_i_init_once(void *_inode)
+{
+ struct romfs_inode_info *inode = _inode;
+
+ inode_init_once(&inode->vfs_inode);
+}
+
+/*
+ * romfs module initialisation
+ */
+static int __init init_romfs_fs(void)
+{
+ int ret;
+
+ pr_info("ROMFS MTD (C) 2007 Red Hat, Inc.\n");
+
+ romfs_inode_cachep =
+ kmem_cache_create("romfs_i",
+ sizeof(struct romfs_inode_info), 0,
+ SLAB_RECLAIM_ACCOUNT | SLAB_ACCOUNT,
+ romfs_i_init_once);
+
+ if (!romfs_inode_cachep) {
+ pr_err("Failed to initialise inode cache\n");
+ return -ENOMEM;
+ }
+ ret = register_filesystem(&romfs_fs_type);
+ if (ret) {
+ pr_err("Failed to register filesystem\n");
+ goto error_register;
+ }
+ return 0;
+
+error_register:
+ kmem_cache_destroy(romfs_inode_cachep);
+ return ret;
+}
+
+/*
+ * romfs module removal
+ */
+static void __exit exit_romfs_fs(void)
+{
+ unregister_filesystem(&romfs_fs_type);
+ /*
+ * Make sure all delayed rcu free inodes are flushed before we
+ * destroy cache.
+ */
+ rcu_barrier();
+ kmem_cache_destroy(romfs_inode_cachep);
+}
+
+module_init(init_romfs_fs);
+module_exit(exit_romfs_fs);
+
+MODULE_DESCRIPTION("Direct-MTD Capable RomFS");
+MODULE_AUTHOR("Red Hat, Inc.");
+MODULE_LICENSE("GPL"); /* Actually dual-licensed, but it doesn't matter for */