summaryrefslogtreecommitdiffstats
path: root/fs/qnx6
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
commit034dd340b08be1f2f0477ad16131d609f9dbd53c (patch)
tree2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /fs/qnx6
downloadlinux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz
linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt: - Fix error output of boot instance creation failure Currently if a boot instance creation fails, instead of printing out the name of the instance that failed, it prints "(null)". That is because it prints "cur_str" that had already been processed by strsep(). Print the saved name instead. While at it, print the error code of the failure. - Fix use-after-free for same named historgrams Histograms can be named so that they can be used in multiple events. But if the named histogram has a variable attached, the second event that uses the named histogram which duplicates it and needs to free the original after duplication leaves the old variable in place and still visible. If another histogram uses than variable, it will use the stale one which will try to reference the freed duplicate histogram and crash the kernel. Free the duplicate variables along with the duplicated histogram data. - Check return value of kthread_run() in event self test The events self tests uses a kthread for testing but does not check if it succeeded in creating a kthread. If the kthread creation were to fail, the code will still try to call kthread_stop() on the error returned. - Fix race between reading trace_pipe and updating subbuffer size If a user is reading the trace_pipe file at the same time they update the ring buffer sub-buffer size, can cause the trace_pipe read to read stale data. Add trace_access_lock() around updating the ring buffer sub-buffer size. - Fix eventfs_inode on failure path in creation of the events directory In the creation of the "events" directory, if after allocating the eventfs_inode a failure is detected, it calls cleanup_ei() which calls free_ei(). The free_ei() will test if eventfs_inode being freed has no children. It is a bug if it does. But on the failure case of the creation of the "events" directory, the children lists have not yet been initialized and the free will trigger a warning because list_empty() on an uninitialized list returns false. Move the initialization into init_ei() where it makes more sense and makes sure that a created eventfs_inode has its lists initialized upon creation. - Check return value of kthread_run() in ftrace direct sample code The sample code that shows how to use the ftrace direct calls does not test the return of kthread_run() to see if it succeeds. Return a failure if the kthread_run() doesn't succeed. - Clear user events state on fork in case of alloc failure On fork, the child gets a pointer to the parent's user events state. It makes a copy of it then updates the child's pointer to it. But if the allocation fails, the duplication function leaves the child with a pointer to its parent's descriptor. When the child cleans up its data, it will free the parent's descriptor while the parent is still using it. In the duplication function, set the child's user_event_mm to NULL before testing if the allocation succeeded, and when it exits it will not free the parent's descriptor. - Fix retry exhaustion in simple ring buffer reader swap simple_ring_buffer_swap_reader_page() starts with retry set to 8 and post-decrements it only after a failed link replacement. On the final attempt, a successful replacement leaves retry at zero, while a failed replacement leaves it at -1. But the check for success expects the retry value to be non-zero and exits with an error on zero. This is the opposite result. Fix it. - Fail nicely when the remote swap_reader_page() returns an error Currently, if the swap_reader_page() of a remote buffer fails, it triggers a WARN_ON_ONCE() and continues normally. Instead, have it exit with an error and a pr_warn() print instead of a full WARNING. * tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: ring-buffer: Stop remote reader update when page swap fails tracing: Fix retry exhaustion in simple ring buffer reader swap tracing/user_events: Clear copied tracing state before fork duplication samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify eventfs: Initialize ei->children and ei->list in init_ei() tracing: Fix use-after-free in trace_pipe read on sub-buffer order change tracing: Fix crash passing ERR_PTR to kthread_stop() tracing: Fix use-after-free with same-name named triggers tracing: Fix logged instance name on creation failure
Diffstat (limited to 'fs/qnx6')
-rw-r--r--fs/qnx6/Kconfig28
-rw-r--r--fs/qnx6/Makefile9
-rw-r--r--fs/qnx6/dir.c284
-rw-r--r--fs/qnx6/inode.c694
-rw-r--r--fs/qnx6/namei.c37
-rw-r--r--fs/qnx6/qnx6.h129
-rw-r--r--fs/qnx6/super_mmi.c149
7 files changed, 1330 insertions, 0 deletions
diff --git a/fs/qnx6/Kconfig b/fs/qnx6/Kconfig
new file mode 100644
index 000000000..8e865d722
--- /dev/null
+++ b/fs/qnx6/Kconfig
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config QNX6FS_FS
+ tristate "QNX6 file system support (read only)"
+ depends on BLOCK && CRC32
+ select BUFFER_HEAD
+ help
+ This is the file system used by the real-time operating systems
+ QNX 6 (also called QNX RTP).
+ Further information is available at <http://www.qnx.com/>.
+ Say Y if you intend to mount QNX hard disks or floppies formatted
+ with a mkqnx6fs.
+ However, keep in mind that this currently is a readonly driver!
+
+ To compile this file system support as a module, choose M here: the
+ module will be called qnx6.
+
+ If you don't know whether you need it, then you don't need it:
+ answer N.
+
+config QNX6FS_DEBUG
+ bool "QNX6 debugging information"
+ depends on QNX6FS_FS
+ help
+ Turns on extended debugging output.
+
+ If you are not a developer working on the QNX6FS, you probably don't
+ want this:
+ answer N.
diff --git a/fs/qnx6/Makefile b/fs/qnx6/Makefile
new file mode 100644
index 000000000..bd4e6c8ef
--- /dev/null
+++ b/fs/qnx6/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the linux qnx4-filesystem routines.
+#
+
+obj-$(CONFIG_QNX6FS_FS) += qnx6.o
+
+qnx6-objs := inode.o dir.o namei.o super_mmi.o
+ccflags-$(CONFIG_QNX6FS_DEBUG) += -DDEBUG
diff --git a/fs/qnx6/dir.c b/fs/qnx6/dir.c
new file mode 100644
index 000000000..56bbaffe4
--- /dev/null
+++ b/fs/qnx6/dir.c
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * QNX6 file system, Linux implementation.
+ *
+ * Version : 1.0.0
+ *
+ * History :
+ *
+ * 01-02-2012 by Kai Bankett (chaosman@ontika.net) : first release.
+ * 16-02-2012 pagemap extension by Al Viro
+ *
+ */
+
+#include <linux/filelock.h>
+#include "qnx6.h"
+
+static unsigned qnx6_lfile_checksum(char *name, unsigned size)
+{
+ unsigned crc = 0;
+ char *end = name + size;
+ while (name < end) {
+ crc = ((crc >> 1) + *(name++)) ^
+ ((crc & 0x00000001) ? 0x80000000 : 0);
+ }
+ return crc;
+}
+
+static void *qnx6_get_folio(struct inode *dir, unsigned long n,
+ struct folio **foliop)
+{
+ struct folio *folio = read_mapping_folio(dir->i_mapping, n, NULL);
+
+ if (IS_ERR(folio))
+ return folio;
+ *foliop = folio;
+ return kmap_local_folio(folio, 0);
+}
+
+static unsigned last_entry(struct inode *inode, unsigned long page_nr)
+{
+ unsigned long last_byte = inode->i_size;
+ last_byte -= page_nr << PAGE_SHIFT;
+ if (last_byte > PAGE_SIZE)
+ last_byte = PAGE_SIZE;
+ return last_byte / QNX6_DIR_ENTRY_SIZE;
+}
+
+static struct qnx6_long_filename *qnx6_longname(struct super_block *sb,
+ struct qnx6_long_dir_entry *de,
+ struct folio **foliop)
+{
+ struct qnx6_sb_info *sbi = QNX6_SB(sb);
+ u32 s = fs32_to_cpu(sbi, de->de_long_inode); /* in block units */
+ u32 n = s >> (PAGE_SHIFT - sb->s_blocksize_bits); /* in pages */
+ u32 offs;
+ struct address_space *mapping = sbi->longfile->i_mapping;
+ struct folio *folio = read_mapping_folio(mapping, n, NULL);
+
+ if (IS_ERR(folio))
+ return ERR_CAST(folio);
+ offs = offset_in_folio(folio, s << sb->s_blocksize_bits);
+ *foliop = folio;
+ return kmap_local_folio(folio, offs);
+}
+
+static int qnx6_dir_longfilename(struct inode *inode,
+ struct qnx6_long_dir_entry *de,
+ struct dir_context *ctx,
+ unsigned de_inode)
+{
+ struct qnx6_long_filename *lf;
+ struct super_block *s = inode->i_sb;
+ struct qnx6_sb_info *sbi = QNX6_SB(s);
+ struct folio *folio;
+ int lf_size;
+
+ if (de->de_size != 0xff) {
+ /* error - long filename entries always have size 0xff
+ in direntry */
+ pr_err("invalid direntry size (%i).\n", de->de_size);
+ return 0;
+ }
+ lf = qnx6_longname(s, de, &folio);
+ if (IS_ERR(lf)) {
+ pr_err("Error reading longname\n");
+ return 0;
+ }
+
+ lf_size = fs16_to_cpu(sbi, lf->lf_size);
+
+ if (lf_size > QNX6_LONG_NAME_MAX) {
+ pr_debug("file %s\n", lf->lf_fname);
+ pr_err("Filename too long (%i)\n", lf_size);
+ folio_release_kmap(folio, lf);
+ return 0;
+ }
+
+ /* calc & validate longfilename checksum
+ mmi 3g filesystem does not have that checksum */
+ if (!test_opt(s, MMI_FS) && fs32_to_cpu(sbi, de->de_checksum) !=
+ qnx6_lfile_checksum(lf->lf_fname, lf_size))
+ pr_info("long filename checksum error.\n");
+
+ pr_debug("qnx6_readdir:%.*s inode:%u\n",
+ lf_size, lf->lf_fname, de_inode);
+ if (!dir_emit(ctx, lf->lf_fname, lf_size, de_inode, DT_UNKNOWN)) {
+ folio_release_kmap(folio, lf);
+ return 0;
+ }
+
+ folio_release_kmap(folio, lf);
+ /* success */
+ return 1;
+}
+
+static int qnx6_readdir(struct file *file, struct dir_context *ctx)
+{
+ struct inode *inode = file_inode(file);
+ struct super_block *s = inode->i_sb;
+ struct qnx6_sb_info *sbi = QNX6_SB(s);
+ loff_t pos = ctx->pos & ~(QNX6_DIR_ENTRY_SIZE - 1);
+ unsigned long npages = dir_pages(inode);
+ unsigned long n = pos >> PAGE_SHIFT;
+ unsigned offset = (pos & ~PAGE_MASK) / QNX6_DIR_ENTRY_SIZE;
+ bool done = false;
+
+ ctx->pos = pos;
+ if (ctx->pos >= inode->i_size)
+ return 0;
+
+ for ( ; !done && n < npages; n++, offset = 0) {
+ struct qnx6_dir_entry *de;
+ struct folio *folio;
+ char *kaddr = qnx6_get_folio(inode, n, &folio);
+ struct qnx6_dir_entry *limit;
+
+ if (IS_ERR(kaddr)) {
+ pr_err("%s(): read failed\n", __func__);
+ ctx->pos = (n + 1) << PAGE_SHIFT;
+ return PTR_ERR(kaddr);
+ }
+ de = (struct qnx6_dir_entry *)kaddr + offset;
+ limit = (struct qnx6_dir_entry *)kaddr + last_entry(inode, n);
+ for (; de < limit; de++, ctx->pos += QNX6_DIR_ENTRY_SIZE) {
+ int size = de->de_size;
+ u32 no_inode = fs32_to_cpu(sbi, de->de_inode);
+
+ if (!no_inode || !size)
+ continue;
+
+ if (size > QNX6_SHORT_NAME_MAX) {
+ /* long filename detected
+ get the filename from long filename
+ structure / block */
+ if (!qnx6_dir_longfilename(inode,
+ (struct qnx6_long_dir_entry *)de,
+ ctx, no_inode)) {
+ done = true;
+ break;
+ }
+ } else {
+ pr_debug("%s():%.*s inode:%u\n",
+ __func__, size, de->de_fname,
+ no_inode);
+ if (!dir_emit(ctx, de->de_fname, size,
+ no_inode, DT_UNKNOWN)) {
+ done = true;
+ break;
+ }
+ }
+ }
+ folio_release_kmap(folio, kaddr);
+ }
+ return 0;
+}
+
+/*
+ * check if the long filename is correct.
+ */
+static unsigned qnx6_long_match(int len, const char *name,
+ struct qnx6_long_dir_entry *de, struct inode *dir)
+{
+ struct super_block *s = dir->i_sb;
+ struct qnx6_sb_info *sbi = QNX6_SB(s);
+ struct folio *folio;
+ int thislen;
+ struct qnx6_long_filename *lf = qnx6_longname(s, de, &folio);
+
+ if (IS_ERR(lf))
+ return 0;
+
+ thislen = fs16_to_cpu(sbi, lf->lf_size);
+ if (len != thislen) {
+ folio_release_kmap(folio, lf);
+ return 0;
+ }
+ if (memcmp(name, lf->lf_fname, len) == 0) {
+ folio_release_kmap(folio, lf);
+ return fs32_to_cpu(sbi, de->de_inode);
+ }
+ folio_release_kmap(folio, lf);
+ return 0;
+}
+
+/*
+ * check if the filename is correct.
+ */
+static unsigned qnx6_match(struct super_block *s, int len, const char *name,
+ struct qnx6_dir_entry *de)
+{
+ struct qnx6_sb_info *sbi = QNX6_SB(s);
+ if (memcmp(name, de->de_fname, len) == 0)
+ return fs32_to_cpu(sbi, de->de_inode);
+ return 0;
+}
+
+
+unsigned qnx6_find_ino(int len, struct inode *dir, const char *name)
+{
+ struct super_block *s = dir->i_sb;
+ struct qnx6_inode_info *ei = QNX6_I(dir);
+ struct folio *folio;
+ unsigned long start, n;
+ unsigned long npages = dir_pages(dir);
+ unsigned ino;
+ struct qnx6_dir_entry *de;
+ struct qnx6_long_dir_entry *lde;
+
+ if (npages == 0)
+ return 0;
+ start = ei->i_dir_start_lookup;
+ if (start >= npages)
+ start = 0;
+ n = start;
+
+ do {
+ de = qnx6_get_folio(dir, n, &folio);
+ if (!IS_ERR(de)) {
+ int limit = last_entry(dir, n);
+ int i;
+
+ for (i = 0; i < limit; i++, de++) {
+ if (len <= QNX6_SHORT_NAME_MAX) {
+ /* short filename */
+ if (len != de->de_size)
+ continue;
+ ino = qnx6_match(s, len, name, de);
+ if (ino)
+ goto found;
+ } else if (de->de_size == 0xff) {
+ /* deal with long filename */
+ lde = (struct qnx6_long_dir_entry *)de;
+ ino = qnx6_long_match(len,
+ name, lde, dir);
+ if (ino)
+ goto found;
+ } else
+ pr_err("undefined filename size in inode.\n");
+ }
+ folio_release_kmap(folio, de - i);
+ }
+
+ if (++n >= npages)
+ n = 0;
+ } while (n != start);
+ return 0;
+
+found:
+ ei->i_dir_start_lookup = n;
+ folio_release_kmap(folio, de);
+ return ino;
+}
+
+const struct file_operations qnx6_dir_operations = {
+ .llseek = generic_file_llseek,
+ .read = generic_read_dir,
+ .iterate_shared = qnx6_readdir,
+ .fsync = simple_fsync,
+ .setlease = generic_setlease,
+};
+
+const struct inode_operations qnx6_dir_inode_operations = {
+ .lookup = qnx6_lookup,
+};
diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
new file mode 100644
index 000000000..6de49333a
--- /dev/null
+++ b/fs/qnx6/inode.c
@@ -0,0 +1,694 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * QNX6 file system, Linux implementation.
+ *
+ * Version : 1.0.0
+ *
+ * History :
+ *
+ * 01-02-2012 by Kai Bankett (chaosman@ontika.net) : first release.
+ * 16-02-2012 pagemap extension by Al Viro
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/highuid.h>
+#include <linux/pagemap.h>
+#include <linux/buffer_head.h>
+#include <linux/writeback.h>
+#include <linux/statfs.h>
+#include <linux/seq_file.h>
+#include <linux/crc32.h>
+#include <linux/mpage.h>
+#include <linux/fs_parser.h>
+#include <linux/fs_context.h>
+#include "qnx6.h"
+
+static const struct super_operations qnx6_sops;
+
+static void qnx6_put_super(struct super_block *sb);
+static struct inode *qnx6_alloc_inode(struct super_block *sb);
+static void qnx6_free_inode(struct inode *inode);
+static int qnx6_reconfigure(struct fs_context *fc);
+static int qnx6_statfs(struct dentry *dentry, struct kstatfs *buf);
+static int qnx6_show_options(struct seq_file *seq, struct dentry *root);
+
+static const struct super_operations qnx6_sops = {
+ .alloc_inode = qnx6_alloc_inode,
+ .free_inode = qnx6_free_inode,
+ .put_super = qnx6_put_super,
+ .statfs = qnx6_statfs,
+ .show_options = qnx6_show_options,
+};
+
+static int qnx6_show_options(struct seq_file *seq, struct dentry *root)
+{
+ struct super_block *sb = root->d_sb;
+ struct qnx6_sb_info *sbi = QNX6_SB(sb);
+
+ if (sbi->s_mount_opt & QNX6_MOUNT_MMI_FS)
+ seq_puts(seq, ",mmi_fs");
+ return 0;
+}
+
+static int qnx6_reconfigure(struct fs_context *fc)
+{
+ struct super_block *sb = fc->root->d_sb;
+
+ sync_filesystem(sb);
+ fc->sb_flags |= SB_RDONLY;
+ return 0;
+}
+
+static unsigned qnx6_get_devblock(struct super_block *sb, __fs32 block)
+{
+ struct qnx6_sb_info *sbi = QNX6_SB(sb);
+ return fs32_to_cpu(sbi, block) + sbi->s_blks_off;
+}
+
+static unsigned qnx6_block_map(struct inode *inode, unsigned iblock);
+
+static int qnx6_get_block(struct inode *inode, sector_t iblock,
+ struct buffer_head *bh, int create)
+{
+ unsigned phys;
+
+ pr_debug("qnx6_get_block inode=[%llu] iblock=[%ld]\n",
+ inode->i_ino, (unsigned long)iblock);
+
+ phys = qnx6_block_map(inode, iblock);
+ if (phys) {
+ /* logical block is before EOF */
+ map_bh(bh, inode->i_sb, phys);
+ }
+ return 0;
+}
+
+static int qnx6_check_blockptr(__fs32 ptr)
+{
+ if (ptr == ~(__fs32)0) {
+ pr_err("hit unused blockpointer.\n");
+ return 0;
+ }
+ return 1;
+}
+
+static int qnx6_read_folio(struct file *file, struct folio *folio)
+{
+ return mpage_read_folio(folio, qnx6_get_block);
+}
+
+static void qnx6_readahead(struct readahead_control *rac)
+{
+ mpage_readahead(rac, qnx6_get_block);
+}
+
+/*
+ * returns the block number for the no-th element in the tree
+ * inodebits requred as there are multiple inodes in one inode block
+ */
+static unsigned qnx6_block_map(struct inode *inode, unsigned no)
+{
+ struct super_block *s = inode->i_sb;
+ struct qnx6_sb_info *sbi = QNX6_SB(s);
+ struct qnx6_inode_info *ei = QNX6_I(inode);
+ unsigned block = 0;
+ struct buffer_head *bh;
+ __fs32 ptr;
+ int levelptr;
+ int ptrbits = sbi->s_ptrbits;
+ int bitdelta;
+ u32 mask = (1 << ptrbits) - 1;
+ int depth = ei->di_filelevels;
+ int i;
+
+ bitdelta = ptrbits * depth;
+ levelptr = no >> bitdelta;
+
+ if (levelptr > QNX6_NO_DIRECT_POINTERS - 1) {
+ pr_err("Requested file block number (%u) too big.", no);
+ return 0;
+ }
+
+ block = qnx6_get_devblock(s, ei->di_block_ptr[levelptr]);
+
+ for (i = 0; i < depth; i++) {
+ bh = sb_bread(s, block);
+ if (!bh) {
+ pr_err("Error reading block (%u)\n", block);
+ return 0;
+ }
+ bitdelta -= ptrbits;
+ levelptr = (no >> bitdelta) & mask;
+ ptr = ((__fs32 *)bh->b_data)[levelptr];
+
+ if (!qnx6_check_blockptr(ptr))
+ return 0;
+
+ block = qnx6_get_devblock(s, ptr);
+ brelse(bh);
+ }
+ return block;
+}
+
+static int qnx6_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+ struct super_block *sb = dentry->d_sb;
+ struct qnx6_sb_info *sbi = QNX6_SB(sb);
+ u64 id = huge_encode_dev(sb->s_bdev->bd_dev);
+
+ buf->f_type = sb->s_magic;
+ buf->f_bsize = sb->s_blocksize;
+ buf->f_blocks = fs32_to_cpu(sbi, sbi->sb->sb_num_blocks);
+ buf->f_bfree = fs32_to_cpu(sbi, sbi->sb->sb_free_blocks);
+ buf->f_files = fs32_to_cpu(sbi, sbi->sb->sb_num_inodes);
+ buf->f_ffree = fs32_to_cpu(sbi, sbi->sb->sb_free_inodes);
+ buf->f_bavail = buf->f_bfree;
+ buf->f_namelen = QNX6_LONG_NAME_MAX;
+ buf->f_fsid = u64_to_fsid(id);
+
+ return 0;
+}
+
+/*
+ * Check the root directory of the filesystem to make sure
+ * it really _is_ a qnx6 filesystem, and to check the size
+ * of the directory entry.
+ */
+static const char *qnx6_checkroot(struct super_block *s)
+{
+ int error = 0;
+ struct qnx6_dir_entry *dir_entry;
+ struct inode *root = d_inode(s->s_root);
+ struct address_space *mapping = root->i_mapping;
+ struct folio *folio = read_mapping_folio(mapping, 0, NULL);
+
+ if (IS_ERR(folio))
+ return "error reading root directory";
+ dir_entry = kmap_local_folio(folio, 0);
+ if (memcmp(dir_entry[0].de_fname, ".", 2) ||
+ memcmp(dir_entry[1].de_fname, "..", 3))
+ error = 1;
+ folio_release_kmap(folio, dir_entry);
+ if (error)
+ return "error reading root directory.";
+ return NULL;
+}
+
+#ifdef CONFIG_QNX6FS_DEBUG
+void qnx6_superblock_debug(struct qnx6_super_block *sb, struct super_block *s)
+{
+ struct qnx6_sb_info *sbi = QNX6_SB(s);
+
+ pr_debug("magic: %08x\n", fs32_to_cpu(sbi, sb->sb_magic));
+ pr_debug("checksum: %08x\n", fs32_to_cpu(sbi, sb->sb_checksum));
+ pr_debug("serial: %llx\n", fs64_to_cpu(sbi, sb->sb_serial));
+ pr_debug("flags: %08x\n", fs32_to_cpu(sbi, sb->sb_flags));
+ pr_debug("blocksize: %08x\n", fs32_to_cpu(sbi, sb->sb_blocksize));
+ pr_debug("num_inodes: %08x\n", fs32_to_cpu(sbi, sb->sb_num_inodes));
+ pr_debug("free_inodes: %08x\n", fs32_to_cpu(sbi, sb->sb_free_inodes));
+ pr_debug("num_blocks: %08x\n", fs32_to_cpu(sbi, sb->sb_num_blocks));
+ pr_debug("free_blocks: %08x\n", fs32_to_cpu(sbi, sb->sb_free_blocks));
+ pr_debug("inode_levels: %02x\n", sb->Inode.levels);
+}
+#endif
+
+enum {
+ Opt_mmifs
+};
+
+struct qnx6_context {
+ unsigned long s_mount_opts;
+};
+
+static const struct fs_parameter_spec qnx6_param_spec[] = {
+ fsparam_flag ("mmi_fs", Opt_mmifs),
+ {}
+};
+
+static int qnx6_parse_param(struct fs_context *fc, struct fs_parameter *param)
+{
+ struct qnx6_context *ctx = fc->fs_private;
+ struct fs_parse_result result;
+ int opt;
+
+ opt = fs_parse(fc, qnx6_param_spec, param, &result);
+ if (opt < 0)
+ return opt;
+
+ switch (opt) {
+ case Opt_mmifs:
+ ctx->s_mount_opts |= QNX6_MOUNT_MMI_FS;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static struct buffer_head *qnx6_check_first_superblock(struct super_block *s,
+ int offset, int silent)
+{
+ struct qnx6_sb_info *sbi = QNX6_SB(s);
+ struct buffer_head *bh;
+ struct qnx6_super_block *sb;
+
+ /* Check the superblock signatures
+ start with the first superblock */
+ bh = sb_bread(s, offset);
+ if (!bh) {
+ pr_err("unable to read the first superblock\n");
+ return NULL;
+ }
+ sb = (struct qnx6_super_block *)bh->b_data;
+ if (fs32_to_cpu(sbi, sb->sb_magic) != QNX6_SUPER_MAGIC) {
+ sbi->s_bytesex = BYTESEX_BE;
+ if (fs32_to_cpu(sbi, sb->sb_magic) == QNX6_SUPER_MAGIC) {
+ /* we got a big endian fs */
+ pr_debug("fs got different endianness.\n");
+ return bh;
+ } else
+ sbi->s_bytesex = BYTESEX_LE;
+ if (!silent) {
+ if (offset == 0) {
+ pr_err("wrong signature (magic) in superblock #1.\n");
+ } else {
+ pr_info("wrong signature (magic) at position (0x%lx) - will try alternative position (0x0000).\n",
+ offset * s->s_blocksize);
+ }
+ }
+ brelse(bh);
+ return NULL;
+ }
+ return bh;
+}
+
+static struct inode *qnx6_private_inode(struct super_block *s,
+ struct qnx6_root_node *p);
+
+static int qnx6_fill_super(struct super_block *s, struct fs_context *fc)
+{
+ struct buffer_head *bh1 = NULL, *bh2 = NULL;
+ struct qnx6_super_block *sb1 = NULL, *sb2 = NULL;
+ struct qnx6_sb_info *sbi;
+ struct qnx6_context *ctx = fc->fs_private;
+ struct inode *root;
+ const char *errmsg;
+ struct qnx6_sb_info *qs;
+ int ret = -EINVAL;
+ u64 offset;
+ int bootblock_offset = QNX6_BOOTBLOCK_SIZE;
+ int silent = fc->sb_flags & SB_SILENT;
+
+ qs = kzalloc_obj(struct qnx6_sb_info);
+ if (!qs)
+ return -ENOMEM;
+ s->s_fs_info = qs;
+ qs->s_mount_opt = ctx->s_mount_opts;
+
+ /* Superblock always is 512 Byte long */
+ if (!sb_set_blocksize(s, QNX6_SUPERBLOCK_SIZE)) {
+ pr_err("unable to set blocksize\n");
+ goto outnobh;
+ }
+
+ if (qs->s_mount_opt == QNX6_MOUNT_MMI_FS) {
+ sb1 = qnx6_mmi_fill_super(s, silent);
+ if (sb1)
+ goto mmi_success;
+ else
+ goto outnobh;
+ }
+ sbi = QNX6_SB(s);
+ sbi->s_bytesex = BYTESEX_LE;
+ /* Check the superblock signatures
+ start with the first superblock */
+ bh1 = qnx6_check_first_superblock(s,
+ bootblock_offset / QNX6_SUPERBLOCK_SIZE, silent);
+ if (!bh1) {
+ /* try again without bootblock offset */
+ bh1 = qnx6_check_first_superblock(s, 0, silent);
+ if (!bh1) {
+ pr_err("unable to read the first superblock\n");
+ goto outnobh;
+ }
+ /* seems that no bootblock at partition start */
+ bootblock_offset = 0;
+ }
+ sb1 = (struct qnx6_super_block *)bh1->b_data;
+
+#ifdef CONFIG_QNX6FS_DEBUG
+ qnx6_superblock_debug(sb1, s);
+#endif
+
+ /* checksum check - start at byte 8 and end at byte 512 */
+ if (fs32_to_cpu(sbi, sb1->sb_checksum) !=
+ crc32_be(0, (char *)(bh1->b_data + 8), 504)) {
+ pr_err("superblock #1 checksum error\n");
+ goto out;
+ }
+
+ /* set new blocksize */
+ if (!sb_set_blocksize(s, fs32_to_cpu(sbi, sb1->sb_blocksize))) {
+ pr_err("unable to set blocksize\n");
+ goto out;
+ }
+ /* blocksize invalidates bh - pull it back in */
+ brelse(bh1);
+ bh1 = sb_bread(s, bootblock_offset >> s->s_blocksize_bits);
+ if (!bh1)
+ goto outnobh;
+ sb1 = (struct qnx6_super_block *)bh1->b_data;
+
+ /* calculate second superblock blocknumber */
+ offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) +
+ (bootblock_offset >> s->s_blocksize_bits) +
+ (QNX6_SUPERBLOCK_AREA >> s->s_blocksize_bits);
+
+ /* set bootblock offset */
+ sbi->s_blks_off = (bootblock_offset >> s->s_blocksize_bits) +
+ (QNX6_SUPERBLOCK_AREA >> s->s_blocksize_bits);
+
+ /* next the second superblock */
+ bh2 = sb_bread(s, offset);
+ if (!bh2) {
+ pr_err("unable to read the second superblock\n");
+ goto out;
+ }
+ sb2 = (struct qnx6_super_block *)bh2->b_data;
+ if (fs32_to_cpu(sbi, sb2->sb_magic) != QNX6_SUPER_MAGIC) {
+ if (!silent)
+ pr_err("wrong signature (magic) in superblock #2.\n");
+ goto out;
+ }
+
+ /* checksum check - start at byte 8 and end at byte 512 */
+ if (fs32_to_cpu(sbi, sb2->sb_checksum) !=
+ crc32_be(0, (char *)(bh2->b_data + 8), 504)) {
+ pr_err("superblock #2 checksum error\n");
+ goto out;
+ }
+
+ if (fs64_to_cpu(sbi, sb1->sb_serial) >=
+ fs64_to_cpu(sbi, sb2->sb_serial)) {
+ /* superblock #1 active */
+ sbi->sb_buf = bh1;
+ sbi->sb = (struct qnx6_super_block *)bh1->b_data;
+ brelse(bh2);
+ pr_info("superblock #1 active\n");
+ } else {
+ /* superblock #2 active */
+ sbi->sb_buf = bh2;
+ sbi->sb = (struct qnx6_super_block *)bh2->b_data;
+ brelse(bh1);
+ pr_info("superblock #2 active\n");
+ }
+mmi_success:
+ /* sanity check - limit maximum indirect pointer levels */
+ if (sb1->Inode.levels > QNX6_PTR_MAX_LEVELS) {
+ pr_err("too many inode levels (max %i, sb %i)\n",
+ QNX6_PTR_MAX_LEVELS, sb1->Inode.levels);
+ goto out;
+ }
+ if (sb1->Longfile.levels > QNX6_PTR_MAX_LEVELS) {
+ pr_err("too many longfilename levels (max %i, sb %i)\n",
+ QNX6_PTR_MAX_LEVELS, sb1->Longfile.levels);
+ goto out;
+ }
+ s->s_op = &qnx6_sops;
+ s->s_magic = QNX6_SUPER_MAGIC;
+ s->s_flags |= SB_RDONLY; /* Yup, read-only yet */
+ s->s_time_min = 0;
+ s->s_time_max = U32_MAX;
+
+ /* ease the later tree level calculations */
+ sbi = QNX6_SB(s);
+ sbi->s_ptrbits = ilog2(s->s_blocksize / 4);
+ sbi->inodes = qnx6_private_inode(s, &sb1->Inode);
+ if (!sbi->inodes)
+ goto out;
+ sbi->longfile = qnx6_private_inode(s, &sb1->Longfile);
+ if (!sbi->longfile)
+ goto out1;
+
+ /* prefetch root inode */
+ root = qnx6_iget(s, QNX6_ROOT_INO);
+ if (IS_ERR(root)) {
+ pr_err("get inode failed\n");
+ ret = PTR_ERR(root);
+ goto out2;
+ }
+
+ ret = -ENOMEM;
+ s->s_root = d_make_root(root);
+ if (!s->s_root)
+ goto out2;
+
+ ret = -EINVAL;
+ errmsg = qnx6_checkroot(s);
+ if (errmsg != NULL) {
+ if (!silent)
+ pr_err("%s\n", errmsg);
+ goto out3;
+ }
+ return 0;
+
+out3:
+ dput(s->s_root);
+ s->s_root = NULL;
+out2:
+ iput(sbi->longfile);
+out1:
+ iput(sbi->inodes);
+out:
+ brelse(bh1);
+ brelse(bh2);
+outnobh:
+ kfree(qs);
+ s->s_fs_info = NULL;
+ return ret;
+}
+
+static void qnx6_put_super(struct super_block *sb)
+{
+ struct qnx6_sb_info *qs = QNX6_SB(sb);
+ brelse(qs->sb_buf);
+ iput(qs->longfile);
+ iput(qs->inodes);
+ kfree(qs);
+ sb->s_fs_info = NULL;
+ return;
+}
+
+static sector_t qnx6_bmap(struct address_space *mapping, sector_t block)
+{
+ return generic_block_bmap(mapping, block, qnx6_get_block);
+}
+static const struct address_space_operations qnx6_aops = {
+ .read_folio = qnx6_read_folio,
+ .readahead = qnx6_readahead,
+ .bmap = qnx6_bmap
+};
+
+static struct inode *qnx6_private_inode(struct super_block *s,
+ struct qnx6_root_node *p)
+{
+ struct inode *inode = new_inode(s);
+ if (inode) {
+ struct qnx6_inode_info *ei = QNX6_I(inode);
+ struct qnx6_sb_info *sbi = QNX6_SB(s);
+ inode->i_size = fs64_to_cpu(sbi, p->size);
+ memcpy(ei->di_block_ptr, p->ptr, sizeof(p->ptr));
+ ei->di_filelevels = p->levels;
+ inode->i_mode = S_IFREG | S_IRUSR; /* probably wrong */
+ inode->i_mapping->a_ops = &qnx6_aops;
+ }
+ return inode;
+}
+
+struct inode *qnx6_iget(struct super_block *sb, unsigned ino)
+{
+ struct qnx6_sb_info *sbi = QNX6_SB(sb);
+ struct qnx6_inode_entry *raw_inode;
+ struct inode *inode;
+ struct qnx6_inode_info *ei;
+ struct address_space *mapping;
+ struct folio *folio;
+ u32 n, offs;
+
+ inode = iget_locked(sb, ino);
+ if (!inode)
+ return ERR_PTR(-ENOMEM);
+ if (!(inode_state_read_once(inode) & I_NEW))
+ return inode;
+
+ ei = QNX6_I(inode);
+
+ inode->i_mode = 0;
+
+ if (ino == 0) {
+ pr_err("bad inode number on dev %s: %u is out of range\n",
+ sb->s_id, ino);
+ iget_failed(inode);
+ return ERR_PTR(-EIO);
+ }
+ n = (ino - 1) >> (PAGE_SHIFT - QNX6_INODE_SIZE_BITS);
+ mapping = sbi->inodes->i_mapping;
+ folio = read_mapping_folio(mapping, n, NULL);
+ if (IS_ERR(folio)) {
+ pr_err("major problem: unable to read inode from dev %s\n",
+ sb->s_id);
+ iget_failed(inode);
+ return ERR_CAST(folio);
+ }
+ offs = offset_in_folio(folio, (ino - 1) << QNX6_INODE_SIZE_BITS);
+ raw_inode = kmap_local_folio(folio, offs);
+
+ inode->i_mode = fs16_to_cpu(sbi, raw_inode->di_mode);
+ i_uid_write(inode, (uid_t)fs32_to_cpu(sbi, raw_inode->di_uid));
+ i_gid_write(inode, (gid_t)fs32_to_cpu(sbi, raw_inode->di_gid));
+ inode->i_size = fs64_to_cpu(sbi, raw_inode->di_size);
+ inode_set_mtime(inode, fs32_to_cpu(sbi, raw_inode->di_mtime), 0);
+ inode_set_atime(inode, fs32_to_cpu(sbi, raw_inode->di_atime), 0);
+ inode_set_ctime(inode, fs32_to_cpu(sbi, raw_inode->di_ctime), 0);
+
+ /* calc blocks based on 512 byte blocksize */
+ inode->i_blocks = (inode->i_size + 511) >> 9;
+
+ memcpy(&ei->di_block_ptr, &raw_inode->di_block_ptr,
+ sizeof(raw_inode->di_block_ptr));
+ ei->di_filelevels = raw_inode->di_filelevels;
+
+ if (S_ISREG(inode->i_mode)) {
+ inode->i_fop = &generic_ro_fops;
+ inode->i_mapping->a_ops = &qnx6_aops;
+ } else if (S_ISDIR(inode->i_mode)) {
+ inode->i_op = &qnx6_dir_inode_operations;
+ inode->i_fop = &qnx6_dir_operations;
+ inode->i_mapping->a_ops = &qnx6_aops;
+ } else if (S_ISLNK(inode->i_mode)) {
+ inode->i_op = &page_symlink_inode_operations;
+ inode_nohighmem(inode);
+ inode->i_mapping->a_ops = &qnx6_aops;
+ } else
+ init_special_inode(inode, inode->i_mode, 0);
+ folio_release_kmap(folio, raw_inode);
+ unlock_new_inode(inode);
+ return inode;
+}
+
+static struct kmem_cache *qnx6_inode_cachep;
+
+static struct inode *qnx6_alloc_inode(struct super_block *sb)
+{
+ struct qnx6_inode_info *ei;
+ ei = alloc_inode_sb(sb, qnx6_inode_cachep, GFP_KERNEL);
+ if (!ei)
+ return NULL;
+ return &ei->vfs_inode;
+}
+
+static void qnx6_free_inode(struct inode *inode)
+{
+ kmem_cache_free(qnx6_inode_cachep, QNX6_I(inode));
+}
+
+static void init_once(void *foo)
+{
+ struct qnx6_inode_info *ei = (struct qnx6_inode_info *) foo;
+
+ inode_init_once(&ei->vfs_inode);
+}
+
+static int init_inodecache(void)
+{
+ qnx6_inode_cachep = kmem_cache_create("qnx6_inode_cache",
+ sizeof(struct qnx6_inode_info),
+ 0, (SLAB_RECLAIM_ACCOUNT|
+ SLAB_ACCOUNT),
+ init_once);
+ if (!qnx6_inode_cachep)
+ return -ENOMEM;
+ return 0;
+}
+
+static void destroy_inodecache(void)
+{
+ /*
+ * Make sure all delayed rcu free inodes are flushed before we
+ * destroy cache.
+ */
+ rcu_barrier();
+ kmem_cache_destroy(qnx6_inode_cachep);
+}
+
+static int qnx6_get_tree(struct fs_context *fc)
+{
+ return get_tree_bdev(fc, qnx6_fill_super);
+}
+
+static void qnx6_free_fc(struct fs_context *fc)
+{
+ kfree(fc->fs_private);
+}
+
+static const struct fs_context_operations qnx6_context_ops = {
+ .parse_param = qnx6_parse_param,
+ .get_tree = qnx6_get_tree,
+ .reconfigure = qnx6_reconfigure,
+ .free = qnx6_free_fc,
+};
+
+static int qnx6_init_fs_context(struct fs_context *fc)
+{
+ struct qnx6_context *ctx;
+
+ ctx = kzalloc_obj(struct qnx6_context);
+ if (!ctx)
+ return -ENOMEM;
+ fc->ops = &qnx6_context_ops;
+ fc->fs_private = ctx;
+
+ return 0;
+}
+
+static struct file_system_type qnx6_fs_type = {
+ .owner = THIS_MODULE,
+ .name = "qnx6",
+ .kill_sb = kill_block_super,
+ .fs_flags = FS_REQUIRES_DEV,
+ .init_fs_context = qnx6_init_fs_context,
+ .parameters = qnx6_param_spec,
+};
+MODULE_ALIAS_FS("qnx6");
+
+static int __init init_qnx6_fs(void)
+{
+ int err;
+
+ err = init_inodecache();
+ if (err)
+ return err;
+
+ err = register_filesystem(&qnx6_fs_type);
+ if (err) {
+ destroy_inodecache();
+ return err;
+ }
+
+ pr_info("QNX6 filesystem 1.0.0 registered.\n");
+ return 0;
+}
+
+static void __exit exit_qnx6_fs(void)
+{
+ unregister_filesystem(&qnx6_fs_type);
+ destroy_inodecache();
+}
+
+module_init(init_qnx6_fs)
+module_exit(exit_qnx6_fs)
+MODULE_DESCRIPTION("QNX6 file system");
+MODULE_LICENSE("GPL");
diff --git a/fs/qnx6/namei.c b/fs/qnx6/namei.c
new file mode 100644
index 000000000..0f0755a9e
--- /dev/null
+++ b/fs/qnx6/namei.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * QNX6 file system, Linux implementation.
+ *
+ * Version : 1.0.0
+ *
+ * History :
+ *
+ * 01-02-2012 by Kai Bankett (chaosman@ontika.net) : first release.
+ * 16-02-2012 pagemap extension by Al Viro
+ *
+ */
+
+#include "qnx6.h"
+
+struct dentry *qnx6_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags)
+{
+ unsigned ino;
+ struct inode *foundinode = NULL;
+ const char *name = dentry->d_name.name;
+ int len = dentry->d_name.len;
+
+ if (len > QNX6_LONG_NAME_MAX)
+ return ERR_PTR(-ENAMETOOLONG);
+
+ ino = qnx6_find_ino(len, dir, name);
+ if (ino) {
+ foundinode = qnx6_iget(dir->i_sb, ino);
+ if (IS_ERR(foundinode))
+ pr_debug("lookup->iget -> error %ld\n",
+ PTR_ERR(foundinode));
+ } else {
+ pr_debug("%s(): not found %s\n", __func__, name);
+ }
+ return d_splice_alias(foundinode, dentry);
+}
diff --git a/fs/qnx6/qnx6.h b/fs/qnx6/qnx6.h
new file mode 100644
index 000000000..56ed13674
--- /dev/null
+++ b/fs/qnx6/qnx6.h
@@ -0,0 +1,129 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * QNX6 file system, Linux implementation.
+ *
+ * Version : 1.0.0
+ *
+ * History :
+ *
+ * 01-02-2012 by Kai Bankett (chaosman@ontika.net) : first release.
+ * 16-02-2012 page map extension by Al Viro
+ *
+ */
+
+#ifdef pr_fmt
+#undef pr_fmt
+#endif
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/fs.h>
+#include <linux/pagemap.h>
+
+typedef __u16 __bitwise __fs16;
+typedef __u32 __bitwise __fs32;
+typedef __u64 __bitwise __fs64;
+
+#include <linux/qnx6_fs.h>
+
+struct qnx6_sb_info {
+ struct buffer_head *sb_buf; /* superblock buffer */
+ struct qnx6_super_block *sb; /* our superblock */
+ int s_blks_off; /* blkoffset fs-startpoint */
+ int s_ptrbits; /* indirect pointer bitfield */
+ unsigned long s_mount_opt; /* all mount options */
+ int s_bytesex; /* holds endianess info */
+ struct inode * inodes;
+ struct inode * longfile;
+};
+
+struct qnx6_inode_info {
+ __fs32 di_block_ptr[QNX6_NO_DIRECT_POINTERS];
+ __u8 di_filelevels;
+ __u32 i_dir_start_lookup;
+ struct inode vfs_inode;
+};
+
+extern struct inode *qnx6_iget(struct super_block *sb, unsigned ino);
+extern struct dentry *qnx6_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags);
+
+#ifdef CONFIG_QNX6FS_DEBUG
+extern void qnx6_superblock_debug(struct qnx6_super_block *,
+ struct super_block *);
+#endif
+
+extern const struct inode_operations qnx6_dir_inode_operations;
+extern const struct file_operations qnx6_dir_operations;
+
+static inline struct qnx6_sb_info *QNX6_SB(struct super_block *sb)
+{
+ return sb->s_fs_info;
+}
+
+static inline struct qnx6_inode_info *QNX6_I(struct inode *inode)
+{
+ return container_of(inode, struct qnx6_inode_info, vfs_inode);
+}
+
+#define clear_opt(o, opt) (o &= ~(QNX6_MOUNT_##opt))
+#define set_opt(o, opt) (o |= (QNX6_MOUNT_##opt))
+#define test_opt(sb, opt) (QNX6_SB(sb)->s_mount_opt & \
+ QNX6_MOUNT_##opt)
+enum {
+ BYTESEX_LE,
+ BYTESEX_BE,
+};
+
+static inline __u64 fs64_to_cpu(struct qnx6_sb_info *sbi, __fs64 n)
+{
+ if (sbi->s_bytesex == BYTESEX_LE)
+ return le64_to_cpu((__force __le64)n);
+ else
+ return be64_to_cpu((__force __be64)n);
+}
+
+static inline __fs64 cpu_to_fs64(struct qnx6_sb_info *sbi, __u64 n)
+{
+ if (sbi->s_bytesex == BYTESEX_LE)
+ return (__force __fs64)cpu_to_le64(n);
+ else
+ return (__force __fs64)cpu_to_be64(n);
+}
+
+static inline __u32 fs32_to_cpu(struct qnx6_sb_info *sbi, __fs32 n)
+{
+ if (sbi->s_bytesex == BYTESEX_LE)
+ return le32_to_cpu((__force __le32)n);
+ else
+ return be32_to_cpu((__force __be32)n);
+}
+
+static inline __fs32 cpu_to_fs32(struct qnx6_sb_info *sbi, __u32 n)
+{
+ if (sbi->s_bytesex == BYTESEX_LE)
+ return (__force __fs32)cpu_to_le32(n);
+ else
+ return (__force __fs32)cpu_to_be32(n);
+}
+
+static inline __u16 fs16_to_cpu(struct qnx6_sb_info *sbi, __fs16 n)
+{
+ if (sbi->s_bytesex == BYTESEX_LE)
+ return le16_to_cpu((__force __le16)n);
+ else
+ return be16_to_cpu((__force __be16)n);
+}
+
+static inline __fs16 cpu_to_fs16(struct qnx6_sb_info *sbi, __u16 n)
+{
+ if (sbi->s_bytesex == BYTESEX_LE)
+ return (__force __fs16)cpu_to_le16(n);
+ else
+ return (__force __fs16)cpu_to_be16(n);
+}
+
+extern struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s,
+ int silent);
+
+unsigned qnx6_find_ino(int len, struct inode *dir, const char *name);
diff --git a/fs/qnx6/super_mmi.c b/fs/qnx6/super_mmi.c
new file mode 100644
index 000000000..b8afb6f38
--- /dev/null
+++ b/fs/qnx6/super_mmi.c
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * QNX6 file system, Linux implementation.
+ *
+ * Version : 1.0.0
+ *
+ * History :
+ *
+ * 01-02-2012 by Kai Bankett (chaosman@ontika.net) : first release.
+ *
+ */
+
+#include <linux/buffer_head.h>
+#include <linux/slab.h>
+#include <linux/crc32.h>
+#include "qnx6.h"
+
+static void qnx6_mmi_copy_sb(struct qnx6_super_block *qsb,
+ struct qnx6_mmi_super_block *sb)
+{
+ qsb->sb_magic = sb->sb_magic;
+ qsb->sb_checksum = sb->sb_checksum;
+ qsb->sb_serial = sb->sb_serial;
+ qsb->sb_blocksize = sb->sb_blocksize;
+ qsb->sb_num_inodes = sb->sb_num_inodes;
+ qsb->sb_free_inodes = sb->sb_free_inodes;
+ qsb->sb_num_blocks = sb->sb_num_blocks;
+ qsb->sb_free_blocks = sb->sb_free_blocks;
+
+ /* the rest of the superblock is the same */
+ memcpy(&qsb->Inode, &sb->Inode, sizeof(sb->Inode));
+ memcpy(&qsb->Bitmap, &sb->Bitmap, sizeof(sb->Bitmap));
+ memcpy(&qsb->Longfile, &sb->Longfile, sizeof(sb->Longfile));
+}
+
+struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent)
+{
+ struct buffer_head *bh1, *bh2 = NULL;
+ struct qnx6_mmi_super_block *sb1, *sb2;
+ struct qnx6_super_block *qsb = NULL;
+ struct qnx6_sb_info *sbi;
+ __u64 offset;
+
+ /* Check the superblock signatures
+ start with the first superblock */
+ bh1 = sb_bread(s, 0);
+ if (!bh1) {
+ pr_err("Unable to read first mmi superblock\n");
+ return NULL;
+ }
+ sb1 = (struct qnx6_mmi_super_block *)bh1->b_data;
+ sbi = QNX6_SB(s);
+ if (fs32_to_cpu(sbi, sb1->sb_magic) != QNX6_SUPER_MAGIC) {
+ if (!silent) {
+ pr_err("wrong signature (magic) in superblock #1.\n");
+ goto out;
+ }
+ }
+
+ /* checksum check - start at byte 8 and end at byte 512 */
+ if (fs32_to_cpu(sbi, sb1->sb_checksum) !=
+ crc32_be(0, (char *)(bh1->b_data + 8), 504)) {
+ pr_err("superblock #1 checksum error\n");
+ goto out;
+ }
+
+ /* calculate second superblock blocknumber */
+ offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + QNX6_SUPERBLOCK_AREA /
+ fs32_to_cpu(sbi, sb1->sb_blocksize);
+
+ /* set new blocksize */
+ if (!sb_set_blocksize(s, fs32_to_cpu(sbi, sb1->sb_blocksize))) {
+ pr_err("unable to set blocksize\n");
+ goto out;
+ }
+ /* blocksize invalidates bh - pull it back in */
+ brelse(bh1);
+ bh1 = sb_bread(s, 0);
+ if (!bh1)
+ goto out;
+ sb1 = (struct qnx6_mmi_super_block *)bh1->b_data;
+
+ /* read second superblock */
+ bh2 = sb_bread(s, offset);
+ if (!bh2) {
+ pr_err("unable to read the second superblock\n");
+ goto out;
+ }
+ sb2 = (struct qnx6_mmi_super_block *)bh2->b_data;
+ if (fs32_to_cpu(sbi, sb2->sb_magic) != QNX6_SUPER_MAGIC) {
+ if (!silent)
+ pr_err("wrong signature (magic) in superblock #2.\n");
+ goto out;
+ }
+
+ /* checksum check - start at byte 8 and end at byte 512 */
+ if (fs32_to_cpu(sbi, sb2->sb_checksum)
+ != crc32_be(0, (char *)(bh2->b_data + 8), 504)) {
+ pr_err("superblock #1 checksum error\n");
+ goto out;
+ }
+
+ qsb = kmalloc_obj(*qsb);
+ if (!qsb) {
+ pr_err("unable to allocate memory.\n");
+ goto out;
+ }
+
+ if (fs64_to_cpu(sbi, sb1->sb_serial) >
+ fs64_to_cpu(sbi, sb2->sb_serial)) {
+ /* superblock #1 active */
+ qnx6_mmi_copy_sb(qsb, sb1);
+#ifdef CONFIG_QNX6FS_DEBUG
+ qnx6_superblock_debug(qsb, s);
+#endif
+ memcpy(bh1->b_data, qsb, sizeof(struct qnx6_super_block));
+
+ sbi->sb_buf = bh1;
+ sbi->sb = (struct qnx6_super_block *)bh1->b_data;
+ brelse(bh2);
+ pr_info("superblock #1 active\n");
+ } else {
+ /* superblock #2 active */
+ qnx6_mmi_copy_sb(qsb, sb2);
+#ifdef CONFIG_QNX6FS_DEBUG
+ qnx6_superblock_debug(qsb, s);
+#endif
+ memcpy(bh2->b_data, qsb, sizeof(struct qnx6_super_block));
+
+ sbi->sb_buf = bh2;
+ sbi->sb = (struct qnx6_super_block *)bh2->b_data;
+ brelse(bh1);
+ pr_info("superblock #2 active\n");
+ }
+ kfree(qsb);
+
+ /* offset for mmi_fs is just SUPERBLOCK_AREA bytes */
+ sbi->s_blks_off = QNX6_SUPERBLOCK_AREA / s->s_blocksize;
+
+ /* success */
+ return sbi->sb;
+
+out:
+ if (bh1 != NULL)
+ brelse(bh1);
+ if (bh2 != NULL)
+ brelse(bh2);
+ return NULL;
+}