summaryrefslogtreecommitdiffstats
path: root/fs/bfs
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/bfs
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/bfs')
-rw-r--r--fs/bfs/Kconfig21
-rw-r--r--fs/bfs/Makefile8
-rw-r--r--fs/bfs/bfs.h69
-rw-r--r--fs/bfs/dir.c356
-rw-r--r--fs/bfs/file.c203
-rw-r--r--fs/bfs/inode.c538
6 files changed, 1195 insertions, 0 deletions
diff --git a/fs/bfs/Kconfig b/fs/bfs/Kconfig
new file mode 100644
index 000000000..8e7ef866b
--- /dev/null
+++ b/fs/bfs/Kconfig
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config BFS_FS
+ tristate "BFS file system support"
+ depends on BLOCK
+ select BUFFER_HEAD
+ help
+ Boot File System (BFS) is a file system used under SCO UnixWare to
+ allow the bootloader access to the kernel image and other important
+ files during the boot process. It is usually mounted under /stand
+ and corresponds to the slice marked as "STAND" in the UnixWare
+ partition. You should say Y if you want to read or write the files
+ on your /stand slice from within Linux. You then also need to say Y
+ to "UnixWare slices support", below. More information about the BFS
+ file system is contained in the file
+ <file:Documentation/filesystems/bfs.rst>.
+
+ If you don't know what this is about, say N.
+
+ To compile this as a module, choose M here: the module will be called
+ bfs. Note that the file system of your root partition (the one
+ containing the directory /) cannot be compiled as a module.
diff --git a/fs/bfs/Makefile b/fs/bfs/Makefile
new file mode 100644
index 000000000..2b6bc5eb4
--- /dev/null
+++ b/fs/bfs/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for BFS filesystem.
+#
+
+obj-$(CONFIG_BFS_FS) += bfs.o
+
+bfs-objs := inode.o file.o dir.o
diff --git a/fs/bfs/bfs.h b/fs/bfs/bfs.h
new file mode 100644
index 000000000..b08afe733
--- /dev/null
+++ b/fs/bfs/bfs.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * fs/bfs/bfs.h
+ * Copyright (C) 1999-2018 Tigran Aivazian <aivazian.tigran@gmail.com>
+ */
+#ifndef _FS_BFS_BFS_H
+#define _FS_BFS_BFS_H
+
+#include <linux/bfs_fs.h>
+
+/* In theory BFS supports up to 512 inodes, numbered from 2 (for /) up to 513 inclusive.
+ In actual fact, attempting to create the 512th inode (i.e. inode No. 513 or file No. 511)
+ will fail with ENOSPC in bfs_add_entry(): the root directory cannot contain so many entries, counting '..'.
+ So, mkfs.bfs(8) should really limit its -N option to 511 and not 512. For now, we just print a warning
+ if a filesystem is mounted with such "impossible to fill up" number of inodes */
+#define BFS_MAX_LASTI 513
+
+/*
+ * BFS file system in-core superblock info
+ */
+struct bfs_sb_info {
+ unsigned long si_blocks;
+ unsigned long si_freeb;
+ unsigned long si_freei;
+ unsigned long si_lf_eblk;
+ unsigned long si_lasti;
+ DECLARE_BITMAP(si_imap, BFS_MAX_LASTI+1);
+ struct mutex bfs_lock;
+};
+
+/*
+ * BFS file system in-core inode info
+ */
+struct bfs_inode_info {
+ unsigned long i_dsk_ino; /* inode number from the disk, can be 0 */
+ unsigned long i_sblock;
+ unsigned long i_eblock;
+ struct mapping_metadata_bhs i_metadata_bhs;
+ struct inode vfs_inode;
+};
+
+static inline struct bfs_sb_info *BFS_SB(struct super_block *sb)
+{
+ return sb->s_fs_info;
+}
+
+static inline struct bfs_inode_info *BFS_I(struct inode *inode)
+{
+ return container_of(inode, struct bfs_inode_info, vfs_inode);
+}
+
+
+#define printf(format, args...) \
+ printk(KERN_ERR "BFS-fs: %s(): " format, __func__, ## args)
+
+/* inode.c */
+extern struct inode *bfs_iget(struct super_block *sb, unsigned long ino);
+extern void bfs_dump_imap(const char *, struct super_block *);
+
+/* file.c */
+extern const struct inode_operations bfs_file_inops;
+extern const struct file_operations bfs_file_operations;
+extern const struct address_space_operations bfs_aops;
+
+/* dir.c */
+extern const struct inode_operations bfs_dir_inops;
+extern const struct file_operations bfs_dir_operations;
+
+#endif /* _FS_BFS_BFS_H */
diff --git a/fs/bfs/dir.c b/fs/bfs/dir.c
new file mode 100644
index 000000000..91a4871fa
--- /dev/null
+++ b/fs/bfs/dir.c
@@ -0,0 +1,356 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * fs/bfs/dir.c
+ * BFS directory operations.
+ * Copyright (C) 1999-2018 Tigran Aivazian <aivazian.tigran@gmail.com>
+ * Made endianness-clean by Andrew Stribblehill <ads@wompom.org> 2005
+ */
+
+#include <linux/time.h>
+#include <linux/string.h>
+#include <linux/fs.h>
+#include <linux/buffer_head.h>
+#include <linux/sched.h>
+#include "bfs.h"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define dprintf(x...) printf(x)
+#else
+#define dprintf(x...)
+#endif
+
+static int bfs_add_entry(struct inode *dir, const struct qstr *child, int ino);
+static struct buffer_head *bfs_find_entry(struct inode *dir,
+ const struct qstr *child,
+ struct bfs_dirent **res_dir);
+
+static int bfs_readdir(struct file *f, struct dir_context *ctx)
+{
+ struct inode *dir = file_inode(f);
+ struct buffer_head *bh;
+ struct bfs_dirent *de;
+ unsigned int offset;
+ int block;
+
+ if (ctx->pos & (BFS_DIRENT_SIZE - 1)) {
+ printf("Bad f_pos=%08lx for %s:%08llx\n",
+ (unsigned long)ctx->pos,
+ dir->i_sb->s_id, dir->i_ino);
+ return -EINVAL;
+ }
+
+ while (ctx->pos < dir->i_size) {
+ offset = ctx->pos & (BFS_BSIZE - 1);
+ block = BFS_I(dir)->i_sblock + (ctx->pos >> BFS_BSIZE_BITS);
+ bh = sb_bread(dir->i_sb, block);
+ if (!bh) {
+ ctx->pos += BFS_BSIZE - offset;
+ continue;
+ }
+ do {
+ de = (struct bfs_dirent *)(bh->b_data + offset);
+ if (de->ino) {
+ int size = strnlen(de->name, BFS_NAMELEN);
+ if (!dir_emit(ctx, de->name, size,
+ le16_to_cpu(de->ino),
+ DT_UNKNOWN)) {
+ brelse(bh);
+ return 0;
+ }
+ }
+ offset += BFS_DIRENT_SIZE;
+ ctx->pos += BFS_DIRENT_SIZE;
+ } while ((offset < BFS_BSIZE) && (ctx->pos < dir->i_size));
+ brelse(bh);
+ }
+ return 0;
+}
+
+const struct file_operations bfs_dir_operations = {
+ .read = generic_read_dir,
+ .iterate_shared = bfs_readdir,
+ .fsync = simple_fsync,
+ .llseek = generic_file_llseek,
+};
+
+static int bfs_create(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *dentry, umode_t mode)
+{
+ int err;
+ struct inode *inode;
+ struct super_block *s = dir->i_sb;
+ struct bfs_sb_info *info = BFS_SB(s);
+ unsigned long ino;
+
+ inode = new_inode(s);
+ if (!inode)
+ return -ENOMEM;
+ mutex_lock(&info->bfs_lock);
+ ino = find_first_zero_bit(info->si_imap, info->si_lasti + 1);
+ if (ino > info->si_lasti) {
+ mutex_unlock(&info->bfs_lock);
+ iput(inode);
+ return -ENOSPC;
+ }
+ set_bit(ino, info->si_imap);
+ info->si_freei--;
+ inode_init_owner(&nop_mnt_idmap, inode, dir, mode);
+ simple_inode_init_ts(inode);
+ inode->i_blocks = 0;
+ inode->i_op = &bfs_file_inops;
+ inode->i_fop = &bfs_file_operations;
+ inode->i_mapping->a_ops = &bfs_aops;
+ inode->i_ino = ino;
+ BFS_I(inode)->i_dsk_ino = ino;
+ BFS_I(inode)->i_sblock = 0;
+ BFS_I(inode)->i_eblock = 0;
+ insert_inode_hash(inode);
+ mark_inode_dirty(inode);
+ bfs_dump_imap("create", s);
+
+ err = bfs_add_entry(dir, &dentry->d_name, inode->i_ino);
+ if (err) {
+ inode_dec_link_count(inode);
+ mutex_unlock(&info->bfs_lock);
+ iput(inode);
+ return err;
+ }
+ mutex_unlock(&info->bfs_lock);
+ d_instantiate(dentry, inode);
+ return 0;
+}
+
+static struct dentry *bfs_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags)
+{
+ struct inode *inode = NULL;
+ struct buffer_head *bh;
+ struct bfs_dirent *de;
+ struct bfs_sb_info *info = BFS_SB(dir->i_sb);
+
+ if (dentry->d_name.len > BFS_NAMELEN)
+ return ERR_PTR(-ENAMETOOLONG);
+
+ mutex_lock(&info->bfs_lock);
+ bh = bfs_find_entry(dir, &dentry->d_name, &de);
+ if (bh) {
+ unsigned long ino = (unsigned long)le16_to_cpu(de->ino);
+ brelse(bh);
+ inode = bfs_iget(dir->i_sb, ino);
+ }
+ mutex_unlock(&info->bfs_lock);
+ return d_splice_alias(inode, dentry);
+}
+
+static int bfs_link(struct dentry *old, struct inode *dir,
+ struct dentry *new)
+{
+ struct inode *inode = d_inode(old);
+ struct bfs_sb_info *info = BFS_SB(inode->i_sb);
+ int err;
+
+ mutex_lock(&info->bfs_lock);
+ err = bfs_add_entry(dir, &new->d_name, inode->i_ino);
+ if (err) {
+ mutex_unlock(&info->bfs_lock);
+ return err;
+ }
+ inc_nlink(inode);
+ inode_set_ctime_current(inode);
+ mark_inode_dirty(inode);
+ ihold(inode);
+ d_instantiate(new, inode);
+ mutex_unlock(&info->bfs_lock);
+ return 0;
+}
+
+static int bfs_unlink(struct inode *dir, struct dentry *dentry)
+{
+ int error = -ENOENT;
+ struct inode *inode = d_inode(dentry);
+ struct buffer_head *bh;
+ struct bfs_dirent *de;
+ struct bfs_sb_info *info = BFS_SB(inode->i_sb);
+
+ mutex_lock(&info->bfs_lock);
+ bh = bfs_find_entry(dir, &dentry->d_name, &de);
+ if (!bh || (le16_to_cpu(de->ino) != inode->i_ino))
+ goto out_brelse;
+
+ if (!inode->i_nlink) {
+ printf("unlinking non-existent file %s:%llu (nlink=%d)\n",
+ inode->i_sb->s_id, inode->i_ino,
+ inode->i_nlink);
+ set_nlink(inode, 1);
+ }
+ de->ino = 0;
+ mmb_mark_buffer_dirty(bh, &BFS_I(dir)->i_metadata_bhs);
+ inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
+ mark_inode_dirty(dir);
+ inode_set_ctime_to_ts(inode, inode_get_ctime(dir));
+ inode_dec_link_count(inode);
+ error = 0;
+
+out_brelse:
+ brelse(bh);
+ mutex_unlock(&info->bfs_lock);
+ return error;
+}
+
+static int bfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
+ struct dentry *old_dentry, struct inode *new_dir,
+ struct dentry *new_dentry, unsigned int flags)
+{
+ struct inode *old_inode, *new_inode;
+ struct buffer_head *old_bh, *new_bh;
+ struct bfs_dirent *old_de, *new_de;
+ struct bfs_sb_info *info;
+ int error = -ENOENT;
+
+ if (flags & ~RENAME_NOREPLACE)
+ return -EINVAL;
+
+ old_bh = new_bh = NULL;
+ old_inode = d_inode(old_dentry);
+ if (S_ISDIR(old_inode->i_mode))
+ return -EINVAL;
+
+ info = BFS_SB(old_inode->i_sb);
+
+ mutex_lock(&info->bfs_lock);
+ old_bh = bfs_find_entry(old_dir, &old_dentry->d_name, &old_de);
+
+ if (!old_bh || (le16_to_cpu(old_de->ino) != old_inode->i_ino))
+ goto end_rename;
+
+ error = -EPERM;
+ new_inode = d_inode(new_dentry);
+ new_bh = bfs_find_entry(new_dir, &new_dentry->d_name, &new_de);
+
+ if (new_bh && !new_inode) {
+ brelse(new_bh);
+ new_bh = NULL;
+ }
+ if (!new_bh) {
+ error = bfs_add_entry(new_dir, &new_dentry->d_name,
+ old_inode->i_ino);
+ if (error)
+ goto end_rename;
+ }
+ old_de->ino = 0;
+ inode_set_mtime_to_ts(old_dir, inode_set_ctime_current(old_dir));
+ mark_inode_dirty(old_dir);
+ if (new_inode) {
+ inode_set_ctime_current(new_inode);
+ inode_dec_link_count(new_inode);
+ }
+ mmb_mark_buffer_dirty(old_bh, &BFS_I(old_dir)->i_metadata_bhs);
+ error = 0;
+
+end_rename:
+ mutex_unlock(&info->bfs_lock);
+ brelse(old_bh);
+ brelse(new_bh);
+ return error;
+}
+
+const struct inode_operations bfs_dir_inops = {
+ .create = bfs_create,
+ .lookup = bfs_lookup,
+ .link = bfs_link,
+ .unlink = bfs_unlink,
+ .rename = bfs_rename,
+};
+
+static int bfs_add_entry(struct inode *dir, const struct qstr *child, int ino)
+{
+ const unsigned char *name = child->name;
+ int namelen = child->len;
+ struct buffer_head *bh;
+ struct bfs_dirent *de;
+ int block, sblock, eblock, off, pos;
+ int i;
+
+ dprintf("name=%s, namelen=%d\n", name, namelen);
+
+ sblock = BFS_I(dir)->i_sblock;
+ eblock = BFS_I(dir)->i_eblock;
+ for (block = sblock; block <= eblock; block++) {
+ bh = sb_bread(dir->i_sb, block);
+ if (!bh)
+ return -EIO;
+ for (off = 0; off < BFS_BSIZE; off += BFS_DIRENT_SIZE) {
+ de = (struct bfs_dirent *)(bh->b_data + off);
+ if (!de->ino) {
+ pos = (block - sblock) * BFS_BSIZE + off;
+ if (pos >= dir->i_size) {
+ dir->i_size += BFS_DIRENT_SIZE;
+ inode_set_ctime_current(dir);
+ }
+ inode_set_mtime_to_ts(dir,
+ inode_set_ctime_current(dir));
+ mark_inode_dirty(dir);
+ de->ino = cpu_to_le16((u16)ino);
+ for (i = 0; i < BFS_NAMELEN; i++)
+ de->name[i] =
+ (i < namelen) ? name[i] : 0;
+ mmb_mark_buffer_dirty(bh,
+ &BFS_I(dir)->i_metadata_bhs);
+ brelse(bh);
+ return 0;
+ }
+ }
+ brelse(bh);
+ }
+ return -ENOSPC;
+}
+
+static inline int bfs_namecmp(int len, const unsigned char *name,
+ const char *buffer)
+{
+ if ((len < BFS_NAMELEN) && buffer[len])
+ return 0;
+ return !memcmp(name, buffer, len);
+}
+
+static struct buffer_head *bfs_find_entry(struct inode *dir,
+ const struct qstr *child,
+ struct bfs_dirent **res_dir)
+{
+ unsigned long block = 0, offset = 0;
+ struct buffer_head *bh = NULL;
+ struct bfs_dirent *de;
+ const unsigned char *name = child->name;
+ int namelen = child->len;
+
+ *res_dir = NULL;
+ if (namelen > BFS_NAMELEN)
+ return NULL;
+
+ while (block * BFS_BSIZE + offset < dir->i_size) {
+ if (!bh) {
+ bh = sb_bread(dir->i_sb, BFS_I(dir)->i_sblock + block);
+ if (!bh) {
+ block++;
+ continue;
+ }
+ }
+ de = (struct bfs_dirent *)(bh->b_data + offset);
+ offset += BFS_DIRENT_SIZE;
+ if (le16_to_cpu(de->ino) &&
+ bfs_namecmp(namelen, name, de->name)) {
+ *res_dir = de;
+ return bh;
+ }
+ if (offset < bh->b_size)
+ continue;
+ brelse(bh);
+ bh = NULL;
+ offset = 0;
+ block++;
+ }
+ brelse(bh);
+ return NULL;
+}
diff --git a/fs/bfs/file.c b/fs/bfs/file.c
new file mode 100644
index 000000000..d33d6bde9
--- /dev/null
+++ b/fs/bfs/file.c
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * fs/bfs/file.c
+ * BFS file operations.
+ * Copyright (C) 1999-2018 Tigran Aivazian <aivazian.tigran@gmail.com>
+ *
+ * Make the file block allocation algorithm understand the size
+ * of the underlying block device.
+ * Copyright (C) 2007 Dmitri Vorobiev <dmitri.vorobiev@gmail.com>
+ *
+ */
+
+#include <linux/fs.h>
+#include <linux/mpage.h>
+#include <linux/buffer_head.h>
+#include "bfs.h"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define dprintf(x...) printf(x)
+#else
+#define dprintf(x...)
+#endif
+
+const struct file_operations bfs_file_operations = {
+ .llseek = generic_file_llseek,
+ .read_iter = generic_file_read_iter,
+ .write_iter = generic_file_write_iter,
+ .mmap_prepare = generic_file_mmap_prepare,
+ .splice_read = filemap_splice_read,
+};
+
+static int bfs_move_block(unsigned long from, unsigned long to,
+ struct super_block *sb)
+{
+ struct buffer_head *bh, *new;
+
+ bh = sb_bread(sb, from);
+ if (!bh)
+ return -EIO;
+ new = sb_getblk(sb, to);
+ memcpy(new->b_data, bh->b_data, bh->b_size);
+ mark_buffer_dirty(new);
+ bforget(bh);
+ brelse(new);
+ return 0;
+}
+
+static int bfs_move_blocks(struct super_block *sb, unsigned long start,
+ unsigned long end, unsigned long where)
+{
+ unsigned long i;
+
+ dprintf("%08lx-%08lx->%08lx\n", start, end, where);
+ for (i = start; i <= end; i++)
+ if(bfs_move_block(i, where + i, sb)) {
+ dprintf("failed to move block %08lx -> %08lx\n", i,
+ where + i);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int bfs_get_block(struct inode *inode, sector_t block,
+ struct buffer_head *bh_result, int create)
+{
+ unsigned long phys;
+ int err;
+ struct super_block *sb = inode->i_sb;
+ struct bfs_sb_info *info = BFS_SB(sb);
+ struct bfs_inode_info *bi = BFS_I(inode);
+
+ phys = bi->i_sblock + block;
+ if (!create) {
+ if (phys <= bi->i_eblock) {
+ dprintf("c=%d, b=%08lx, phys=%09lx (granted)\n",
+ create, (unsigned long)block, phys);
+ map_bh(bh_result, sb, phys);
+ }
+ return 0;
+ }
+
+ /*
+ * If the file is not empty and the requested block is within the
+ * range of blocks allocated for this file, we can grant it.
+ */
+ if (bi->i_sblock && (phys <= bi->i_eblock)) {
+ dprintf("c=%d, b=%08lx, phys=%08lx (interim block granted)\n",
+ create, (unsigned long)block, phys);
+ map_bh(bh_result, sb, phys);
+ return 0;
+ }
+
+ /* The file will be extended, so let's see if there is enough space. */
+ if (phys >= info->si_blocks)
+ return -ENOSPC;
+
+ /* The rest has to be protected against itself. */
+ mutex_lock(&info->bfs_lock);
+
+ /*
+ * If the last data block for this file is the last allocated
+ * block, we can extend the file trivially, without moving it
+ * anywhere.
+ */
+ if (bi->i_eblock == info->si_lf_eblk) {
+ dprintf("c=%d, b=%08lx, phys=%08lx (simple extension)\n",
+ create, (unsigned long)block, phys);
+ map_bh(bh_result, sb, phys);
+ info->si_freeb -= phys - bi->i_eblock;
+ info->si_lf_eblk = bi->i_eblock = phys;
+ mark_inode_dirty(inode);
+ err = 0;
+ goto out;
+ }
+
+ /* Ok, we have to move this entire file to the next free block. */
+ phys = info->si_lf_eblk + 1;
+ if (phys + block >= info->si_blocks) {
+ err = -ENOSPC;
+ goto out;
+ }
+
+ if (bi->i_sblock) {
+ err = bfs_move_blocks(inode->i_sb, bi->i_sblock,
+ bi->i_eblock, phys);
+ if (err) {
+ dprintf("failed to move ino=%08lx -> fs corruption\n",
+ inode->i_ino);
+ goto out;
+ }
+ } else
+ err = 0;
+
+ dprintf("c=%d, b=%08lx, phys=%08lx (moved)\n",
+ create, (unsigned long)block, phys);
+ bi->i_sblock = phys;
+ phys += block;
+ info->si_lf_eblk = bi->i_eblock = phys;
+
+ /*
+ * This assumes nothing can write the inode back while we are here
+ * and thus update inode->i_blocks! (XXX)
+ */
+ info->si_freeb -= bi->i_eblock - bi->i_sblock + 1 - inode->i_blocks;
+ mark_inode_dirty(inode);
+ map_bh(bh_result, sb, phys);
+out:
+ mutex_unlock(&info->bfs_lock);
+ return err;
+}
+
+static int bfs_writepages(struct address_space *mapping,
+ struct writeback_control *wbc)
+{
+ return mpage_writepages(mapping, wbc, bfs_get_block);
+}
+
+static int bfs_read_folio(struct file *file, struct folio *folio)
+{
+ return block_read_full_folio(folio, bfs_get_block);
+}
+
+static void bfs_write_failed(struct address_space *mapping, loff_t to)
+{
+ struct inode *inode = mapping->host;
+
+ if (to > inode->i_size)
+ truncate_pagecache(inode, inode->i_size);
+}
+
+static int bfs_write_begin(const struct kiocb *iocb,
+ struct address_space *mapping,
+ loff_t pos, unsigned len,
+ struct folio **foliop, void **fsdata)
+{
+ int ret;
+
+ ret = block_write_begin(mapping, pos, len, foliop, bfs_get_block);
+ if (unlikely(ret))
+ bfs_write_failed(mapping, pos + len);
+
+ return ret;
+}
+
+static sector_t bfs_bmap(struct address_space *mapping, sector_t block)
+{
+ return generic_block_bmap(mapping, block, bfs_get_block);
+}
+
+const struct address_space_operations bfs_aops = {
+ .dirty_folio = block_dirty_folio,
+ .invalidate_folio = block_invalidate_folio,
+ .read_folio = bfs_read_folio,
+ .writepages = bfs_writepages,
+ .write_begin = bfs_write_begin,
+ .write_end = generic_write_end,
+ .migrate_folio = buffer_migrate_folio,
+ .bmap = bfs_bmap,
+};
+
+const struct inode_operations bfs_file_inops;
diff --git a/fs/bfs/inode.c b/fs/bfs/inode.c
new file mode 100644
index 000000000..06e3a848b
--- /dev/null
+++ b/fs/bfs/inode.c
@@ -0,0 +1,538 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * fs/bfs/inode.c
+ * BFS superblock and inode operations.
+ * Copyright (C) 1999-2018 Tigran Aivazian <aivazian.tigran@gmail.com>
+ * From fs/minix, Copyright (C) 1991, 1992 Linus Torvalds.
+ * Made endianness-clean by Andrew Stribblehill <ads@wompom.org>, 2005.
+ */
+
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/buffer_head.h>
+#include <linux/vfs.h>
+#include <linux/writeback.h>
+#include <linux/uio.h>
+#include <linux/uaccess.h>
+#include <linux/fs_context.h>
+#include "bfs.h"
+
+MODULE_AUTHOR("Tigran Aivazian <aivazian.tigran@gmail.com>");
+MODULE_DESCRIPTION("SCO UnixWare BFS filesystem for Linux");
+MODULE_LICENSE("GPL");
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define dprintf(x...) printf(x)
+#else
+#define dprintf(x...)
+#endif
+
+struct inode *bfs_iget(struct super_block *sb, unsigned long ino)
+{
+ struct bfs_inode *di;
+ struct inode *inode;
+ struct buffer_head *bh;
+ int block, off;
+
+ inode = iget_locked(sb, ino);
+ if (!inode)
+ return ERR_PTR(-ENOMEM);
+ if (!(inode_state_read_once(inode) & I_NEW))
+ return inode;
+
+ if ((ino < BFS_ROOT_INO) || (ino > BFS_SB(inode->i_sb)->si_lasti)) {
+ printf("Bad inode number %s:%08lx\n", inode->i_sb->s_id, ino);
+ goto error;
+ }
+
+ block = (ino - BFS_ROOT_INO) / BFS_INODES_PER_BLOCK + 1;
+ bh = sb_bread(inode->i_sb, block);
+ if (!bh) {
+ printf("Unable to read inode %s:%08lx\n", inode->i_sb->s_id,
+ ino);
+ goto error;
+ }
+
+ off = (ino - BFS_ROOT_INO) % BFS_INODES_PER_BLOCK;
+ di = (struct bfs_inode *)bh->b_data + off;
+
+ /*
+ * https://martin.hinner.info/fs/bfs/bfs-structure.html explains that
+ * BFS in SCO UnixWare environment used only lower 9 bits of di->i_mode
+ * value. This means that, although bfs_write_inode() saves whole
+ * inode->i_mode bits (which include S_IFMT bits and S_IS{UID,GID,VTX}
+ * bits), middle 7 bits of di->i_mode value can be garbage when these
+ * bits were not saved by bfs_write_inode().
+ * Since we can't tell whether middle 7 bits are garbage, use only
+ * lower 12 bits (i.e. tolerate S_IS{UID,GID,VTX} bits possibly being
+ * garbage) and reconstruct S_IFMT bits for Linux environment from
+ * di->i_vtype value.
+ */
+ inode->i_mode = 0x00000FFF & le32_to_cpu(di->i_mode);
+ if (le32_to_cpu(di->i_vtype) == BFS_VDIR) {
+ inode->i_mode |= S_IFDIR;
+ inode->i_op = &bfs_dir_inops;
+ inode->i_fop = &bfs_dir_operations;
+ } else if (le32_to_cpu(di->i_vtype) == BFS_VREG) {
+ inode->i_mode |= S_IFREG;
+ inode->i_op = &bfs_file_inops;
+ inode->i_fop = &bfs_file_operations;
+ inode->i_mapping->a_ops = &bfs_aops;
+ } else {
+ brelse(bh);
+ printf("Unknown vtype=%u %s:%08lx\n",
+ le32_to_cpu(di->i_vtype), inode->i_sb->s_id, ino);
+ goto error;
+ }
+
+ BFS_I(inode)->i_sblock = le32_to_cpu(di->i_sblock);
+ BFS_I(inode)->i_eblock = le32_to_cpu(di->i_eblock);
+ BFS_I(inode)->i_dsk_ino = le16_to_cpu(di->i_ino);
+ i_uid_write(inode, le32_to_cpu(di->i_uid));
+ i_gid_write(inode, le32_to_cpu(di->i_gid));
+ set_nlink(inode, le32_to_cpu(di->i_nlink));
+ inode->i_size = BFS_FILESIZE(di);
+ inode->i_blocks = BFS_FILEBLOCKS(di);
+ inode_set_atime(inode, le32_to_cpu(di->i_atime), 0);
+ inode_set_mtime(inode, le32_to_cpu(di->i_mtime), 0);
+ inode_set_ctime(inode, le32_to_cpu(di->i_ctime), 0);
+
+ brelse(bh);
+ unlock_new_inode(inode);
+ return inode;
+
+error:
+ iget_failed(inode);
+ return ERR_PTR(-EIO);
+}
+
+static struct bfs_inode *find_inode(struct super_block *sb, u16 ino, struct buffer_head **p)
+{
+ if ((ino < BFS_ROOT_INO) || (ino > BFS_SB(sb)->si_lasti)) {
+ printf("Bad inode number %s:%08x\n", sb->s_id, ino);
+ return ERR_PTR(-EIO);
+ }
+
+ ino -= BFS_ROOT_INO;
+
+ *p = sb_bread(sb, 1 + ino / BFS_INODES_PER_BLOCK);
+ if (!*p) {
+ printf("Unable to read inode %s:%08x\n", sb->s_id, ino);
+ return ERR_PTR(-EIO);
+ }
+
+ return (struct bfs_inode *)(*p)->b_data + ino % BFS_INODES_PER_BLOCK;
+}
+
+static int bfs_write_inode(struct inode *inode, struct writeback_control *wbc)
+{
+ struct bfs_sb_info *info = BFS_SB(inode->i_sb);
+ unsigned int ino = (u16)inode->i_ino;
+ unsigned long i_sblock;
+ struct bfs_inode *di;
+ struct buffer_head *bh;
+
+ dprintf("ino=%08x\n", ino);
+
+ di = find_inode(inode->i_sb, ino, &bh);
+ if (IS_ERR(di))
+ return PTR_ERR(di);
+
+ mutex_lock(&info->bfs_lock);
+
+ if (ino == BFS_ROOT_INO)
+ di->i_vtype = cpu_to_le32(BFS_VDIR);
+ else
+ di->i_vtype = cpu_to_le32(BFS_VREG);
+
+ di->i_ino = cpu_to_le16(ino);
+ di->i_mode = cpu_to_le32(inode->i_mode);
+ di->i_uid = cpu_to_le32(i_uid_read(inode));
+ di->i_gid = cpu_to_le32(i_gid_read(inode));
+ di->i_nlink = cpu_to_le32(inode->i_nlink);
+ di->i_atime = cpu_to_le32(inode_get_atime_sec(inode));
+ di->i_mtime = cpu_to_le32(inode_get_mtime_sec(inode));
+ di->i_ctime = cpu_to_le32(inode_get_ctime_sec(inode));
+ i_sblock = BFS_I(inode)->i_sblock;
+ di->i_sblock = cpu_to_le32(i_sblock);
+ di->i_eblock = cpu_to_le32(BFS_I(inode)->i_eblock);
+ di->i_eoffset = cpu_to_le32(i_sblock * BFS_BSIZE + inode->i_size - 1);
+
+ mark_buffer_dirty(bh);
+ brelse(bh);
+ mutex_unlock(&info->bfs_lock);
+ set_inode_metadata_writeback(inode);
+ return 0;
+}
+
+static int bfs_sync_inode_metadata(struct inode *inode,
+ struct writeback_control *wbc)
+{
+ int err = 0;
+ struct bfs_inode *di;
+ struct buffer_head *bh;
+
+ di = find_inode(inode->i_sb, (u16)inode->i_ino, &bh);
+ if (IS_ERR(di))
+ return PTR_ERR(di);
+
+ sync_dirty_buffer(bh);
+ if (buffer_write_io_error(bh)) {
+ err = -EIO;
+ goto out;
+ }
+ err = mmb_sync(&BFS_I(inode)->i_metadata_bhs);
+out:
+ brelse(bh);
+ return err;
+}
+
+static void bfs_evict_inode(struct inode *inode)
+{
+ unsigned long ino = inode->i_ino;
+ struct bfs_inode *di;
+ struct buffer_head *bh;
+ struct super_block *s = inode->i_sb;
+ struct bfs_sb_info *info = BFS_SB(s);
+ struct bfs_inode_info *bi = BFS_I(inode);
+
+ dprintf("ino=%08lx\n", ino);
+
+ truncate_inode_pages_final(&inode->i_data);
+ if (inode->i_nlink)
+ mmb_sync(&BFS_I(inode)->i_metadata_bhs);
+ mmb_invalidate(&BFS_I(inode)->i_metadata_bhs);
+ clear_inode(inode);
+
+ if (inode->i_nlink)
+ return;
+
+ di = find_inode(s, inode->i_ino, &bh);
+ if (IS_ERR(di))
+ return;
+
+ mutex_lock(&info->bfs_lock);
+ /* clear on-disk inode */
+ memset(di, 0, sizeof(struct bfs_inode));
+ mark_buffer_dirty(bh);
+ brelse(bh);
+
+ if (bi->i_dsk_ino) {
+ if (bi->i_sblock)
+ info->si_freeb += bi->i_eblock + 1 - bi->i_sblock;
+ info->si_freei++;
+ clear_bit(ino, info->si_imap);
+ bfs_dump_imap("evict_inode", s);
+ }
+
+ /*
+ * If this was the last file, make the previous block
+ * "last block of the last file" even if there is no
+ * real file there, saves us 1 gap.
+ */
+ if (info->si_lf_eblk == bi->i_eblock)
+ info->si_lf_eblk = bi->i_sblock - 1;
+ mutex_unlock(&info->bfs_lock);
+}
+
+static void bfs_put_super(struct super_block *s)
+{
+ struct bfs_sb_info *info = BFS_SB(s);
+
+ if (!info)
+ return;
+
+ mutex_destroy(&info->bfs_lock);
+ kfree(info);
+ s->s_fs_info = NULL;
+}
+
+static int bfs_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+ struct super_block *s = dentry->d_sb;
+ struct bfs_sb_info *info = BFS_SB(s);
+ u64 id = huge_encode_dev(s->s_bdev->bd_dev);
+ buf->f_type = BFS_MAGIC;
+ buf->f_bsize = s->s_blocksize;
+ buf->f_blocks = info->si_blocks;
+ buf->f_bfree = buf->f_bavail = info->si_freeb;
+ buf->f_files = info->si_lasti + 1 - BFS_ROOT_INO;
+ buf->f_ffree = info->si_freei;
+ buf->f_fsid = u64_to_fsid(id);
+ buf->f_namelen = BFS_NAMELEN;
+ return 0;
+}
+
+static struct kmem_cache *bfs_inode_cachep;
+
+static struct inode *bfs_alloc_inode(struct super_block *sb)
+{
+ struct bfs_inode_info *bi;
+ bi = alloc_inode_sb(sb, bfs_inode_cachep, GFP_KERNEL);
+ if (!bi)
+ return NULL;
+ mmb_init(&bi->i_metadata_bhs, &bi->vfs_inode.i_data);
+
+ return &bi->vfs_inode;
+}
+
+static void bfs_free_inode(struct inode *inode)
+{
+ kmem_cache_free(bfs_inode_cachep, BFS_I(inode));
+}
+
+static void init_once(void *foo)
+{
+ struct bfs_inode_info *bi = foo;
+
+ inode_init_once(&bi->vfs_inode);
+}
+
+static int __init init_inodecache(void)
+{
+ bfs_inode_cachep = kmem_cache_create("bfs_inode_cache",
+ sizeof(struct bfs_inode_info),
+ 0, (SLAB_RECLAIM_ACCOUNT|
+ SLAB_ACCOUNT),
+ init_once);
+ if (bfs_inode_cachep == NULL)
+ 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(bfs_inode_cachep);
+}
+
+static const struct super_operations bfs_sops = {
+ .alloc_inode = bfs_alloc_inode,
+ .free_inode = bfs_free_inode,
+ .write_inode = bfs_write_inode,
+ .sync_inode_metadata = bfs_sync_inode_metadata,
+ .evict_inode = bfs_evict_inode,
+ .put_super = bfs_put_super,
+ .statfs = bfs_statfs,
+};
+
+void bfs_dump_imap(const char *prefix, struct super_block *s)
+{
+#ifdef DEBUG
+ int i;
+ char *tmpbuf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+
+ if (!tmpbuf)
+ return;
+ for (i = BFS_SB(s)->si_lasti; i >= 0; i--) {
+ if (i > PAGE_SIZE - 100) break;
+ if (test_bit(i, BFS_SB(s)->si_imap))
+ strcat(tmpbuf, "1");
+ else
+ strcat(tmpbuf, "0");
+ }
+ printf("%s: lasti=%08lx <%s>\n", prefix, BFS_SB(s)->si_lasti, tmpbuf);
+ kfree(tmpbuf);
+#endif
+}
+
+static int bfs_fill_super(struct super_block *s, struct fs_context *fc)
+{
+ struct buffer_head *bh, *sbh;
+ struct bfs_super_block *bfs_sb;
+ struct inode *inode;
+ unsigned i;
+ struct bfs_sb_info *info;
+ int ret = -EINVAL;
+ unsigned long i_sblock, i_eblock, i_eoff, s_size;
+ int silent = fc->sb_flags & SB_SILENT;
+
+ info = kzalloc_obj(*info);
+ if (!info)
+ return -ENOMEM;
+ mutex_init(&info->bfs_lock);
+ s->s_fs_info = info;
+ s->s_time_min = 0;
+ s->s_time_max = U32_MAX;
+
+ if (!sb_set_blocksize(s, BFS_BSIZE))
+ goto out;
+
+ sbh = sb_bread(s, 0);
+ if (!sbh)
+ goto out;
+ bfs_sb = (struct bfs_super_block *)sbh->b_data;
+ if (le32_to_cpu(bfs_sb->s_magic) != BFS_MAGIC) {
+ if (!silent)
+ printf("No BFS filesystem on %s (magic=%08x)\n", s->s_id, le32_to_cpu(bfs_sb->s_magic));
+ goto out1;
+ }
+ if (BFS_UNCLEAN(bfs_sb, s) && !silent)
+ printf("%s is unclean, continuing\n", s->s_id);
+
+ s->s_magic = BFS_MAGIC;
+
+ if (le32_to_cpu(bfs_sb->s_start) > le32_to_cpu(bfs_sb->s_end) ||
+ le32_to_cpu(bfs_sb->s_start) < sizeof(struct bfs_super_block) + sizeof(struct bfs_dirent)) {
+ printf("Superblock is corrupted on %s\n", s->s_id);
+ goto out1;
+ }
+
+ info->si_lasti = (le32_to_cpu(bfs_sb->s_start) - BFS_BSIZE) / sizeof(struct bfs_inode) + BFS_ROOT_INO - 1;
+ if (info->si_lasti == BFS_MAX_LASTI)
+ printf("NOTE: filesystem %s was created with 512 inodes, the real maximum is 511, mounting anyway\n", s->s_id);
+ else if (info->si_lasti > BFS_MAX_LASTI) {
+ printf("Impossible last inode number %lu > %d on %s\n", info->si_lasti, BFS_MAX_LASTI, s->s_id);
+ goto out1;
+ }
+ for (i = 0; i < BFS_ROOT_INO; i++)
+ set_bit(i, info->si_imap);
+
+ s->s_op = &bfs_sops;
+ inode = bfs_iget(s, BFS_ROOT_INO);
+ if (IS_ERR(inode)) {
+ ret = PTR_ERR(inode);
+ goto out1;
+ }
+ s->s_root = d_make_root(inode);
+ if (!s->s_root) {
+ ret = -ENOMEM;
+ goto out1;
+ }
+
+ info->si_blocks = (le32_to_cpu(bfs_sb->s_end) + 1) >> BFS_BSIZE_BITS;
+ info->si_freeb = (le32_to_cpu(bfs_sb->s_end) + 1 - le32_to_cpu(bfs_sb->s_start)) >> BFS_BSIZE_BITS;
+ info->si_freei = 0;
+ info->si_lf_eblk = 0;
+
+ /* can we read the last block? */
+ bh = sb_bread(s, info->si_blocks - 1);
+ if (!bh) {
+ printf("Last block not available on %s: %lu\n", s->s_id, info->si_blocks - 1);
+ ret = -EIO;
+ goto out2;
+ }
+ brelse(bh);
+
+ bh = NULL;
+ for (i = BFS_ROOT_INO; i <= info->si_lasti; i++) {
+ struct bfs_inode *di;
+ int block = (i - BFS_ROOT_INO) / BFS_INODES_PER_BLOCK + 1;
+ int off = (i - BFS_ROOT_INO) % BFS_INODES_PER_BLOCK;
+ unsigned long eblock;
+
+ if (!off) {
+ brelse(bh);
+ bh = sb_bread(s, block);
+ }
+
+ if (!bh)
+ continue;
+
+ di = (struct bfs_inode *)bh->b_data + off;
+
+ /* test if filesystem is not corrupted */
+
+ i_eoff = le32_to_cpu(di->i_eoffset);
+ i_sblock = le32_to_cpu(di->i_sblock);
+ i_eblock = le32_to_cpu(di->i_eblock);
+ s_size = le32_to_cpu(bfs_sb->s_end);
+
+ if (i_sblock > info->si_blocks ||
+ i_eblock > info->si_blocks ||
+ i_sblock > i_eblock ||
+ (i_eoff != le32_to_cpu(-1) && i_eoff > s_size) ||
+ i_sblock * BFS_BSIZE > i_eoff) {
+
+ printf("Inode 0x%08x corrupted on %s\n", i, s->s_id);
+
+ brelse(bh);
+ ret = -EIO;
+ goto out2;
+ }
+
+ if (!di->i_ino) {
+ info->si_freei++;
+ continue;
+ }
+ set_bit(i, info->si_imap);
+ info->si_freeb -= BFS_FILEBLOCKS(di);
+
+ eblock = le32_to_cpu(di->i_eblock);
+ if (eblock > info->si_lf_eblk)
+ info->si_lf_eblk = eblock;
+ }
+ brelse(bh);
+ brelse(sbh);
+ bfs_dump_imap("fill_super", s);
+ return 0;
+
+out2:
+ dput(s->s_root);
+ s->s_root = NULL;
+out1:
+ brelse(sbh);
+out:
+ mutex_destroy(&info->bfs_lock);
+ kfree(info);
+ s->s_fs_info = NULL;
+ return ret;
+}
+
+static int bfs_get_tree(struct fs_context *fc)
+{
+ return get_tree_bdev(fc, bfs_fill_super);
+}
+
+static const struct fs_context_operations bfs_context_ops = {
+ .get_tree = bfs_get_tree,
+};
+
+static int bfs_init_fs_context(struct fs_context *fc)
+{
+ fc->ops = &bfs_context_ops;
+
+ return 0;
+}
+
+static struct file_system_type bfs_fs_type = {
+ .owner = THIS_MODULE,
+ .name = "bfs",
+ .init_fs_context = bfs_init_fs_context,
+ .kill_sb = kill_block_super,
+ .fs_flags = FS_REQUIRES_DEV,
+};
+MODULE_ALIAS_FS("bfs");
+
+static int __init init_bfs_fs(void)
+{
+ int err = init_inodecache();
+ if (err)
+ goto out1;
+ err = register_filesystem(&bfs_fs_type);
+ if (err)
+ goto out;
+ return 0;
+out:
+ destroy_inodecache();
+out1:
+ return err;
+}
+
+static void __exit exit_bfs_fs(void)
+{
+ unregister_filesystem(&bfs_fs_type);
+ destroy_inodecache();
+}
+
+module_init(init_bfs_fs)
+module_exit(exit_bfs_fs)