summaryrefslogtreecommitdiffstats
path: root/fs/minix
diff options
context:
space:
mode:
Diffstat (limited to 'fs/minix')
-rw-r--r--fs/minix/Kconfig26
-rw-r--r--fs/minix/Makefile8
-rw-r--r--fs/minix/bitmap.c273
-rw-r--r--fs/minix/dir.c469
-rw-r--r--fs/minix/file.c53
-rw-r--r--fs/minix/inode.c724
-rw-r--r--fs/minix/itree_common.c371
-rw-r--r--fs/minix/itree_v1.c67
-rw-r--r--fs/minix/itree_v2.c75
-rw-r--r--fs/minix/minix.h170
-rw-r--r--fs/minix/namei.c275
11 files changed, 2511 insertions, 0 deletions
diff --git a/fs/minix/Kconfig b/fs/minix/Kconfig
new file mode 100644
index 000000000..de2003974
--- /dev/null
+++ b/fs/minix/Kconfig
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config MINIX_FS
+ tristate "Minix file system support"
+ depends on BLOCK
+ help
+ Minix is a simple operating system used in many classes about OS's.
+ The minix file system (method to organize files on a hard disk
+ partition or a floppy disk) was the original file system for Linux,
+ but has been superseded by the second extended file system ext2fs.
+ You don't want to use the minix file system on your hard disk
+ because of certain built-in restrictions, but it is sometimes found
+ on older Linux floppy disks. This option will enlarge your kernel
+ by about 28 KB. If unsure, say N.
+
+ To compile this file system support as a module, choose M here: the
+ module will be called minix. Note that the file system of your root
+ partition (the one containing the directory /) cannot be compiled as
+ a module.
+
+config MINIX_FS_NATIVE_ENDIAN
+ def_bool MINIX_FS
+ depends on MICROBLAZE || MIPS || S390 || SUPERH || SPARC || XTENSA || (M68K && !MMU)
+
+config MINIX_FS_BIG_ENDIAN_16BIT_INDEXED
+ def_bool MINIX_FS
+ depends on M68K && MMU
diff --git a/fs/minix/Makefile b/fs/minix/Makefile
new file mode 100644
index 000000000..a2d3ab58d
--- /dev/null
+++ b/fs/minix/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the Linux minix filesystem routines.
+#
+
+obj-$(CONFIG_MINIX_FS) += minix.o
+
+minix-objs := bitmap.o itree_v1.o itree_v2.o namei.o inode.o file.o dir.o
diff --git a/fs/minix/bitmap.c b/fs/minix/bitmap.c
new file mode 100644
index 000000000..9115948c6
--- /dev/null
+++ b/fs/minix/bitmap.c
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/minix/bitmap.c
+ *
+ * Copyright (C) 1991, 1992 Linus Torvalds
+ */
+
+/*
+ * Modified for 680x0 by Hamish Macdonald
+ * Fixed for 680x0 by Andreas Schwab
+ */
+
+/* bitmap.c contains the code that handles the inode and block bitmaps */
+
+#include "minix.h"
+#include <linux/buffer_head.h>
+#include <linux/bitops.h>
+#include <linux/sched.h>
+
+static DEFINE_SPINLOCK(bitmap_lock);
+
+/*
+ * bitmap consists of blocks filled with 16bit words
+ * bit set == busy, bit clear == free
+ * endianness is a mess, but for counting zero bits it really doesn't matter...
+ */
+static __u32 count_free(struct buffer_head *map[], unsigned blocksize, __u32 numbits)
+{
+ __u32 sum = 0;
+ unsigned blocks = DIV_ROUND_UP(numbits, blocksize * 8);
+
+ while (blocks--) {
+ unsigned words = blocksize / 2;
+ __u16 *p = (__u16 *)(*map++)->b_data;
+ while (words--)
+ sum += 16 - hweight16(*p++);
+ }
+
+ return sum;
+}
+
+void minix_free_block(struct inode *inode, unsigned long block)
+{
+ struct super_block *sb = inode->i_sb;
+ struct minix_sb_info *sbi = minix_sb(sb);
+ struct buffer_head *bh;
+ int k = sb->s_blocksize_bits + 3;
+ unsigned long bit, zone;
+
+ if (block < sbi->s_firstdatazone || block >= sbi->s_nzones) {
+ printk("Trying to free block not in datazone\n");
+ return;
+ }
+ zone = block - sbi->s_firstdatazone + 1;
+ bit = zone & ((1<<k) - 1);
+ zone >>= k;
+ if (zone >= sbi->s_zmap_blocks) {
+ printk("minix_free_block: nonexistent bitmap buffer\n");
+ return;
+ }
+ bh = sbi->s_zmap[zone];
+ spin_lock(&bitmap_lock);
+ if (!minix_test_and_clear_bit(bit, bh->b_data))
+ printk("minix_free_block (%s:%lu): bit already cleared\n",
+ sb->s_id, block);
+ spin_unlock(&bitmap_lock);
+ mark_buffer_dirty(bh);
+ return;
+}
+
+int minix_new_block(struct inode * inode)
+{
+ struct minix_sb_info *sbi = minix_sb(inode->i_sb);
+ int bits_per_zone = 8 * inode->i_sb->s_blocksize;
+ int i;
+
+ for (i = 0; i < sbi->s_zmap_blocks; i++) {
+ struct buffer_head *bh = sbi->s_zmap[i];
+ int j;
+
+ spin_lock(&bitmap_lock);
+ j = minix_find_first_zero_bit(bh->b_data, bits_per_zone);
+ if (j < bits_per_zone) {
+ minix_set_bit(j, bh->b_data);
+ spin_unlock(&bitmap_lock);
+ mark_buffer_dirty(bh);
+ j += i * bits_per_zone + sbi->s_firstdatazone-1;
+ if (j < sbi->s_firstdatazone || j >= sbi->s_nzones)
+ break;
+ return j;
+ }
+ spin_unlock(&bitmap_lock);
+ }
+ return 0;
+}
+
+unsigned long minix_count_free_blocks(struct super_block *sb)
+{
+ struct minix_sb_info *sbi = minix_sb(sb);
+ u32 bits = sbi->s_nzones - sbi->s_firstdatazone + 1;
+
+ return (count_free(sbi->s_zmap, sb->s_blocksize, bits)
+ << sbi->s_log_zone_size);
+}
+
+struct minix_inode *
+minix_V1_raw_inode(struct super_block *sb, ino_t ino, struct buffer_head **bh)
+{
+ int block;
+ struct minix_sb_info *sbi = minix_sb(sb);
+ struct minix_inode *p;
+
+ if (!ino || ino > sbi->s_ninodes) {
+ printk("Bad inode number on dev %s: %ld is out of range\n",
+ sb->s_id, (long)ino);
+ return NULL;
+ }
+ ino--;
+ block = 2 + sbi->s_imap_blocks + sbi->s_zmap_blocks +
+ ino / MINIX_INODES_PER_BLOCK;
+ *bh = sb_bread(sb, block);
+ if (!*bh) {
+ printk("Unable to read inode block\n");
+ return NULL;
+ }
+ p = (void *)(*bh)->b_data;
+ return p + ino % MINIX_INODES_PER_BLOCK;
+}
+
+struct minix2_inode *
+minix_V2_raw_inode(struct super_block *sb, ino_t ino, struct buffer_head **bh)
+{
+ int block;
+ struct minix_sb_info *sbi = minix_sb(sb);
+ struct minix2_inode *p;
+ int minix2_inodes_per_block = sb->s_blocksize / sizeof(struct minix2_inode);
+
+ *bh = NULL;
+ if (!ino || ino > sbi->s_ninodes) {
+ printk("Bad inode number on dev %s: %ld is out of range\n",
+ sb->s_id, (long)ino);
+ return NULL;
+ }
+ ino--;
+ block = 2 + sbi->s_imap_blocks + sbi->s_zmap_blocks +
+ ino / minix2_inodes_per_block;
+ *bh = sb_bread(sb, block);
+ if (!*bh) {
+ printk("Unable to read inode block\n");
+ return NULL;
+ }
+ p = (void *)(*bh)->b_data;
+ return p + ino % minix2_inodes_per_block;
+}
+
+/* Clear the link count and mode of a deleted inode on disk. */
+
+static void minix_clear_inode(struct inode *inode)
+{
+ struct buffer_head *bh = NULL;
+
+ if (INODE_VERSION(inode) == MINIX_V1) {
+ struct minix_inode *raw_inode;
+ raw_inode = minix_V1_raw_inode(inode->i_sb, inode->i_ino, &bh);
+ if (raw_inode) {
+ raw_inode->i_nlinks = 0;
+ raw_inode->i_mode = 0;
+ }
+ } else {
+ struct minix2_inode *raw_inode;
+ raw_inode = minix_V2_raw_inode(inode->i_sb, inode->i_ino, &bh);
+ if (raw_inode) {
+ raw_inode->i_nlinks = 0;
+ raw_inode->i_mode = 0;
+ }
+ }
+ if (bh) {
+ mark_buffer_dirty(bh);
+ brelse (bh);
+ }
+}
+
+void minix_free_inode(struct inode * inode)
+{
+ struct super_block *sb = inode->i_sb;
+ struct minix_sb_info *sbi = minix_sb(inode->i_sb);
+ struct buffer_head *bh;
+ int k = sb->s_blocksize_bits + 3;
+ unsigned long ino, bit;
+
+ ino = inode->i_ino;
+ if (ino < 1 || ino > sbi->s_ninodes) {
+ printk("minix_free_inode: inode 0 or nonexistent inode\n");
+ return;
+ }
+ bit = ino & ((1<<k) - 1);
+ ino >>= k;
+ if (ino >= sbi->s_imap_blocks) {
+ printk("minix_free_inode: nonexistent imap in superblock\n");
+ return;
+ }
+
+ minix_clear_inode(inode); /* clear on-disk copy */
+
+ bh = sbi->s_imap[ino];
+ spin_lock(&bitmap_lock);
+ if (!minix_test_and_clear_bit(bit, bh->b_data))
+ printk("minix_free_inode: bit %lu already cleared\n", bit);
+ spin_unlock(&bitmap_lock);
+ mark_buffer_dirty(bh);
+}
+
+struct inode *minix_new_inode(const struct inode *dir, umode_t mode, int *error)
+{
+ struct super_block *sb = dir->i_sb;
+ struct minix_sb_info *sbi = minix_sb(sb);
+ struct inode *inode = new_inode(sb);
+ struct buffer_head * bh;
+ int bits_per_zone = 8 * sb->s_blocksize;
+ unsigned long j;
+ int i;
+
+ if (!inode) {
+ *error = -ENOMEM;
+ return NULL;
+ }
+ j = bits_per_zone;
+ bh = NULL;
+ *error = -ENOSPC;
+ spin_lock(&bitmap_lock);
+ for (i = 0; i < sbi->s_imap_blocks; i++) {
+ bh = sbi->s_imap[i];
+ j = minix_find_first_zero_bit(bh->b_data, bits_per_zone);
+ if (j < bits_per_zone)
+ break;
+ }
+ if (!bh || j >= bits_per_zone) {
+ spin_unlock(&bitmap_lock);
+ iput(inode);
+ return NULL;
+ }
+ if (minix_test_and_set_bit(j, bh->b_data)) { /* shouldn't happen */
+ spin_unlock(&bitmap_lock);
+ printk("minix_new_inode: bit already set\n");
+ iput(inode);
+ return NULL;
+ }
+ spin_unlock(&bitmap_lock);
+ mark_buffer_dirty(bh);
+ j += i * bits_per_zone;
+ if (!j || j > sbi->s_ninodes) {
+ iput(inode);
+ return NULL;
+ }
+ inode_init_owner(&init_user_ns, inode, dir, mode);
+ inode->i_ino = j;
+ inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode);
+ inode->i_blocks = 0;
+ memset(&minix_i(inode)->u, 0, sizeof(minix_i(inode)->u));
+ insert_inode_hash(inode);
+ mark_inode_dirty(inode);
+
+ *error = 0;
+ return inode;
+}
+
+unsigned long minix_count_free_inodes(struct super_block *sb)
+{
+ struct minix_sb_info *sbi = minix_sb(sb);
+ u32 bits = sbi->s_ninodes + 1;
+
+ return count_free(sbi->s_imap, sb->s_blocksize, bits);
+}
diff --git a/fs/minix/dir.c b/fs/minix/dir.c
new file mode 100644
index 000000000..dcfe5b253
--- /dev/null
+++ b/fs/minix/dir.c
@@ -0,0 +1,469 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/minix/dir.c
+ *
+ * Copyright (C) 1991, 1992 Linus Torvalds
+ *
+ * minix directory handling functions
+ *
+ * Updated to filesystem version 3 by Daniel Aragones
+ */
+
+#include "minix.h"
+#include <linux/buffer_head.h>
+#include <linux/highmem.h>
+#include <linux/swap.h>
+
+typedef struct minix_dir_entry minix_dirent;
+typedef struct minix3_dir_entry minix3_dirent;
+
+static int minix_readdir(struct file *, struct dir_context *);
+
+const struct file_operations minix_dir_operations = {
+ .llseek = generic_file_llseek,
+ .read = generic_read_dir,
+ .iterate_shared = minix_readdir,
+ .fsync = generic_file_fsync,
+};
+
+static inline void dir_put_page(struct page *page)
+{
+ kunmap(page);
+ put_page(page);
+}
+
+/*
+ * Return the offset into page `page_nr' of the last valid
+ * byte in that page, plus one.
+ */
+static unsigned
+minix_last_byte(struct inode *inode, unsigned long page_nr)
+{
+ unsigned last_byte = PAGE_SIZE;
+
+ if (page_nr == (inode->i_size >> PAGE_SHIFT))
+ last_byte = inode->i_size & (PAGE_SIZE - 1);
+ return last_byte;
+}
+
+static int dir_commit_chunk(struct page *page, loff_t pos, unsigned len)
+{
+ struct address_space *mapping = page->mapping;
+ struct inode *dir = mapping->host;
+ int err = 0;
+ block_write_end(NULL, mapping, pos, len, len, page, NULL);
+
+ if (pos+len > dir->i_size) {
+ i_size_write(dir, pos+len);
+ mark_inode_dirty(dir);
+ }
+ if (IS_DIRSYNC(dir))
+ err = write_one_page(page);
+ else
+ unlock_page(page);
+ return err;
+}
+
+static struct page * dir_get_page(struct inode *dir, unsigned long n)
+{
+ struct address_space *mapping = dir->i_mapping;
+ struct page *page = read_mapping_page(mapping, n, NULL);
+ if (!IS_ERR(page))
+ kmap(page);
+ return page;
+}
+
+static inline void *minix_next_entry(void *de, struct minix_sb_info *sbi)
+{
+ return (void*)((char*)de + sbi->s_dirsize);
+}
+
+static int minix_readdir(struct file *file, struct dir_context *ctx)
+{
+ struct inode *inode = file_inode(file);
+ struct super_block *sb = inode->i_sb;
+ struct minix_sb_info *sbi = minix_sb(sb);
+ unsigned chunk_size = sbi->s_dirsize;
+ unsigned long npages = dir_pages(inode);
+ unsigned long pos = ctx->pos;
+ unsigned offset;
+ unsigned long n;
+
+ ctx->pos = pos = ALIGN(pos, chunk_size);
+ if (pos >= inode->i_size)
+ return 0;
+
+ offset = pos & ~PAGE_MASK;
+ n = pos >> PAGE_SHIFT;
+
+ for ( ; n < npages; n++, offset = 0) {
+ char *p, *kaddr, *limit;
+ struct page *page = dir_get_page(inode, n);
+
+ if (IS_ERR(page))
+ continue;
+ kaddr = (char *)page_address(page);
+ p = kaddr+offset;
+ limit = kaddr + minix_last_byte(inode, n) - chunk_size;
+ for ( ; p <= limit; p = minix_next_entry(p, sbi)) {
+ const char *name;
+ __u32 inumber;
+ if (sbi->s_version == MINIX_V3) {
+ minix3_dirent *de3 = (minix3_dirent *)p;
+ name = de3->name;
+ inumber = de3->inode;
+ } else {
+ minix_dirent *de = (minix_dirent *)p;
+ name = de->name;
+ inumber = de->inode;
+ }
+ if (inumber) {
+ unsigned l = strnlen(name, sbi->s_namelen);
+ if (!dir_emit(ctx, name, l,
+ inumber, DT_UNKNOWN)) {
+ dir_put_page(page);
+ return 0;
+ }
+ }
+ ctx->pos += chunk_size;
+ }
+ dir_put_page(page);
+ }
+ return 0;
+}
+
+static inline int namecompare(int len, int maxlen,
+ const char * name, const char * buffer)
+{
+ if (len < maxlen && buffer[len])
+ return 0;
+ return !memcmp(name, buffer, len);
+}
+
+/*
+ * minix_find_entry()
+ *
+ * finds an entry in the specified directory with the wanted name. It
+ * returns the cache buffer in which the entry was found, and the entry
+ * itself (as a parameter - res_dir). It does NOT read the inode of the
+ * entry - you'll have to do that yourself if you want to.
+ */
+minix_dirent *minix_find_entry(struct dentry *dentry, struct page **res_page)
+{
+ const char * name = dentry->d_name.name;
+ int namelen = dentry->d_name.len;
+ struct inode * dir = d_inode(dentry->d_parent);
+ struct super_block * sb = dir->i_sb;
+ struct minix_sb_info * sbi = minix_sb(sb);
+ unsigned long n;
+ unsigned long npages = dir_pages(dir);
+ struct page *page = NULL;
+ char *p;
+
+ char *namx;
+ __u32 inumber;
+ *res_page = NULL;
+
+ for (n = 0; n < npages; n++) {
+ char *kaddr, *limit;
+
+ page = dir_get_page(dir, n);
+ if (IS_ERR(page))
+ continue;
+
+ kaddr = (char*)page_address(page);
+ limit = kaddr + minix_last_byte(dir, n) - sbi->s_dirsize;
+ for (p = kaddr; p <= limit; p = minix_next_entry(p, sbi)) {
+ if (sbi->s_version == MINIX_V3) {
+ minix3_dirent *de3 = (minix3_dirent *)p;
+ namx = de3->name;
+ inumber = de3->inode;
+ } else {
+ minix_dirent *de = (minix_dirent *)p;
+ namx = de->name;
+ inumber = de->inode;
+ }
+ if (!inumber)
+ continue;
+ if (namecompare(namelen, sbi->s_namelen, name, namx))
+ goto found;
+ }
+ dir_put_page(page);
+ }
+ return NULL;
+
+found:
+ *res_page = page;
+ return (minix_dirent *)p;
+}
+
+int minix_add_link(struct dentry *dentry, struct inode *inode)
+{
+ struct inode *dir = d_inode(dentry->d_parent);
+ const char * name = dentry->d_name.name;
+ int namelen = dentry->d_name.len;
+ struct super_block * sb = dir->i_sb;
+ struct minix_sb_info * sbi = minix_sb(sb);
+ struct page *page = NULL;
+ unsigned long npages = dir_pages(dir);
+ unsigned long n;
+ char *kaddr, *p;
+ minix_dirent *de;
+ minix3_dirent *de3;
+ loff_t pos;
+ int err;
+ char *namx = NULL;
+ __u32 inumber;
+
+ /*
+ * We take care of directory expansion in the same loop
+ * This code plays outside i_size, so it locks the page
+ * to protect that region.
+ */
+ for (n = 0; n <= npages; n++) {
+ char *limit, *dir_end;
+
+ page = dir_get_page(dir, n);
+ err = PTR_ERR(page);
+ if (IS_ERR(page))
+ goto out;
+ lock_page(page);
+ kaddr = (char*)page_address(page);
+ dir_end = kaddr + minix_last_byte(dir, n);
+ limit = kaddr + PAGE_SIZE - sbi->s_dirsize;
+ for (p = kaddr; p <= limit; p = minix_next_entry(p, sbi)) {
+ de = (minix_dirent *)p;
+ de3 = (minix3_dirent *)p;
+ if (sbi->s_version == MINIX_V3) {
+ namx = de3->name;
+ inumber = de3->inode;
+ } else {
+ namx = de->name;
+ inumber = de->inode;
+ }
+ if (p == dir_end) {
+ /* We hit i_size */
+ if (sbi->s_version == MINIX_V3)
+ de3->inode = 0;
+ else
+ de->inode = 0;
+ goto got_it;
+ }
+ if (!inumber)
+ goto got_it;
+ err = -EEXIST;
+ if (namecompare(namelen, sbi->s_namelen, name, namx))
+ goto out_unlock;
+ }
+ unlock_page(page);
+ dir_put_page(page);
+ }
+ BUG();
+ return -EINVAL;
+
+got_it:
+ pos = page_offset(page) + p - (char *)page_address(page);
+ err = minix_prepare_chunk(page, pos, sbi->s_dirsize);
+ if (err)
+ goto out_unlock;
+ memcpy (namx, name, namelen);
+ if (sbi->s_version == MINIX_V3) {
+ memset (namx + namelen, 0, sbi->s_dirsize - namelen - 4);
+ de3->inode = inode->i_ino;
+ } else {
+ memset (namx + namelen, 0, sbi->s_dirsize - namelen - 2);
+ de->inode = inode->i_ino;
+ }
+ err = dir_commit_chunk(page, pos, sbi->s_dirsize);
+ dir->i_mtime = dir->i_ctime = current_time(dir);
+ mark_inode_dirty(dir);
+out_put:
+ dir_put_page(page);
+out:
+ return err;
+out_unlock:
+ unlock_page(page);
+ goto out_put;
+}
+
+int minix_delete_entry(struct minix_dir_entry *de, struct page *page)
+{
+ struct inode *inode = page->mapping->host;
+ char *kaddr = page_address(page);
+ loff_t pos = page_offset(page) + (char*)de - kaddr;
+ struct minix_sb_info *sbi = minix_sb(inode->i_sb);
+ unsigned len = sbi->s_dirsize;
+ int err;
+
+ lock_page(page);
+ err = minix_prepare_chunk(page, pos, len);
+ if (err == 0) {
+ if (sbi->s_version == MINIX_V3)
+ ((minix3_dirent *) de)->inode = 0;
+ else
+ de->inode = 0;
+ err = dir_commit_chunk(page, pos, len);
+ } else {
+ unlock_page(page);
+ }
+ dir_put_page(page);
+ inode->i_ctime = inode->i_mtime = current_time(inode);
+ mark_inode_dirty(inode);
+ return err;
+}
+
+int minix_make_empty(struct inode *inode, struct inode *dir)
+{
+ struct page *page = grab_cache_page(inode->i_mapping, 0);
+ struct minix_sb_info *sbi = minix_sb(inode->i_sb);
+ char *kaddr;
+ int err;
+
+ if (!page)
+ return -ENOMEM;
+ err = minix_prepare_chunk(page, 0, 2 * sbi->s_dirsize);
+ if (err) {
+ unlock_page(page);
+ goto fail;
+ }
+
+ kaddr = kmap_atomic(page);
+ memset(kaddr, 0, PAGE_SIZE);
+
+ if (sbi->s_version == MINIX_V3) {
+ minix3_dirent *de3 = (minix3_dirent *)kaddr;
+
+ de3->inode = inode->i_ino;
+ strcpy(de3->name, ".");
+ de3 = minix_next_entry(de3, sbi);
+ de3->inode = dir->i_ino;
+ strcpy(de3->name, "..");
+ } else {
+ minix_dirent *de = (minix_dirent *)kaddr;
+
+ de->inode = inode->i_ino;
+ strcpy(de->name, ".");
+ de = minix_next_entry(de, sbi);
+ de->inode = dir->i_ino;
+ strcpy(de->name, "..");
+ }
+ kunmap_atomic(kaddr);
+
+ err = dir_commit_chunk(page, 0, 2 * sbi->s_dirsize);
+fail:
+ put_page(page);
+ return err;
+}
+
+/*
+ * routine to check that the specified directory is empty (for rmdir)
+ */
+int minix_empty_dir(struct inode * inode)
+{
+ struct page *page = NULL;
+ unsigned long i, npages = dir_pages(inode);
+ struct minix_sb_info *sbi = minix_sb(inode->i_sb);
+ char *name;
+ __u32 inumber;
+
+ for (i = 0; i < npages; i++) {
+ char *p, *kaddr, *limit;
+
+ page = dir_get_page(inode, i);
+ if (IS_ERR(page))
+ continue;
+
+ kaddr = (char *)page_address(page);
+ limit = kaddr + minix_last_byte(inode, i) - sbi->s_dirsize;
+ for (p = kaddr; p <= limit; p = minix_next_entry(p, sbi)) {
+ if (sbi->s_version == MINIX_V3) {
+ minix3_dirent *de3 = (minix3_dirent *)p;
+ name = de3->name;
+ inumber = de3->inode;
+ } else {
+ minix_dirent *de = (minix_dirent *)p;
+ name = de->name;
+ inumber = de->inode;
+ }
+
+ if (inumber != 0) {
+ /* check for . and .. */
+ if (name[0] != '.')
+ goto not_empty;
+ if (!name[1]) {
+ if (inumber != inode->i_ino)
+ goto not_empty;
+ } else if (name[1] != '.')
+ goto not_empty;
+ else if (name[2])
+ goto not_empty;
+ }
+ }
+ dir_put_page(page);
+ }
+ return 1;
+
+not_empty:
+ dir_put_page(page);
+ return 0;
+}
+
+/* Releases the page */
+void minix_set_link(struct minix_dir_entry *de, struct page *page,
+ struct inode *inode)
+{
+ struct inode *dir = page->mapping->host;
+ struct minix_sb_info *sbi = minix_sb(dir->i_sb);
+ loff_t pos = page_offset(page) +
+ (char *)de-(char*)page_address(page);
+ int err;
+
+ lock_page(page);
+
+ err = minix_prepare_chunk(page, pos, sbi->s_dirsize);
+ if (err == 0) {
+ if (sbi->s_version == MINIX_V3)
+ ((minix3_dirent *) de)->inode = inode->i_ino;
+ else
+ de->inode = inode->i_ino;
+ err = dir_commit_chunk(page, pos, sbi->s_dirsize);
+ } else {
+ unlock_page(page);
+ }
+ dir_put_page(page);
+ dir->i_mtime = dir->i_ctime = current_time(dir);
+ mark_inode_dirty(dir);
+}
+
+struct minix_dir_entry * minix_dotdot (struct inode *dir, struct page **p)
+{
+ struct page *page = dir_get_page(dir, 0);
+ struct minix_sb_info *sbi = minix_sb(dir->i_sb);
+ struct minix_dir_entry *de = NULL;
+
+ if (!IS_ERR(page)) {
+ de = minix_next_entry(page_address(page), sbi);
+ *p = page;
+ }
+ return de;
+}
+
+ino_t minix_inode_by_name(struct dentry *dentry)
+{
+ struct page *page;
+ struct minix_dir_entry *de = minix_find_entry(dentry, &page);
+ ino_t res = 0;
+
+ if (de) {
+ struct address_space *mapping = page->mapping;
+ struct inode *inode = mapping->host;
+ struct minix_sb_info *sbi = minix_sb(inode->i_sb);
+
+ if (sbi->s_version == MINIX_V3)
+ res = ((minix3_dirent *) de)->inode;
+ else
+ res = de->inode;
+ dir_put_page(page);
+ }
+ return res;
+}
diff --git a/fs/minix/file.c b/fs/minix/file.c
new file mode 100644
index 000000000..6a7bd2d9e
--- /dev/null
+++ b/fs/minix/file.c
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/minix/file.c
+ *
+ * Copyright (C) 1991, 1992 Linus Torvalds
+ *
+ * minix regular file handling primitives
+ */
+
+#include "minix.h"
+
+/*
+ * We have mostly NULLs here: the current defaults are OK for
+ * the minix filesystem.
+ */
+const struct file_operations minix_file_operations = {
+ .llseek = generic_file_llseek,
+ .read_iter = generic_file_read_iter,
+ .write_iter = generic_file_write_iter,
+ .mmap = generic_file_mmap,
+ .fsync = generic_file_fsync,
+ .splice_read = generic_file_splice_read,
+};
+
+static int minix_setattr(struct user_namespace *mnt_userns,
+ struct dentry *dentry, struct iattr *attr)
+{
+ struct inode *inode = d_inode(dentry);
+ int error;
+
+ error = setattr_prepare(&init_user_ns, dentry, attr);
+ if (error)
+ return error;
+
+ if ((attr->ia_valid & ATTR_SIZE) &&
+ attr->ia_size != i_size_read(inode)) {
+ error = inode_newsize_ok(inode, attr->ia_size);
+ if (error)
+ return error;
+
+ truncate_setsize(inode, attr->ia_size);
+ minix_truncate(inode);
+ }
+
+ setattr_copy(&init_user_ns, inode, attr);
+ mark_inode_dirty(inode);
+ return 0;
+}
+
+const struct inode_operations minix_file_inode_operations = {
+ .setattr = minix_setattr,
+ .getattr = minix_getattr,
+};
diff --git a/fs/minix/inode.c b/fs/minix/inode.c
new file mode 100644
index 000000000..da8bdd171
--- /dev/null
+++ b/fs/minix/inode.c
@@ -0,0 +1,724 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/fs/minix/inode.c
+ *
+ * Copyright (C) 1991, 1992 Linus Torvalds
+ *
+ * Copyright (C) 1996 Gertjan van Wingerde
+ * Minix V2 fs support.
+ *
+ * Modified for 680x0 by Andreas Schwab
+ * Updated to filesystem version 3 by Daniel Aragones
+ */
+
+#include <linux/module.h>
+#include "minix.h"
+#include <linux/buffer_head.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/highuid.h>
+#include <linux/vfs.h>
+#include <linux/writeback.h>
+
+static int minix_write_inode(struct inode *inode,
+ struct writeback_control *wbc);
+static int minix_statfs(struct dentry *dentry, struct kstatfs *buf);
+static int minix_remount (struct super_block * sb, int * flags, char * data);
+
+static void minix_evict_inode(struct inode *inode)
+{
+ truncate_inode_pages_final(&inode->i_data);
+ if (!inode->i_nlink) {
+ inode->i_size = 0;
+ minix_truncate(inode);
+ }
+ invalidate_inode_buffers(inode);
+ clear_inode(inode);
+ if (!inode->i_nlink)
+ minix_free_inode(inode);
+}
+
+static void minix_put_super(struct super_block *sb)
+{
+ int i;
+ struct minix_sb_info *sbi = minix_sb(sb);
+
+ if (!sb_rdonly(sb)) {
+ if (sbi->s_version != MINIX_V3) /* s_state is now out from V3 sb */
+ sbi->s_ms->s_state = sbi->s_mount_state;
+ mark_buffer_dirty(sbi->s_sbh);
+ }
+ for (i = 0; i < sbi->s_imap_blocks; i++)
+ brelse(sbi->s_imap[i]);
+ for (i = 0; i < sbi->s_zmap_blocks; i++)
+ brelse(sbi->s_zmap[i]);
+ brelse (sbi->s_sbh);
+ kfree(sbi->s_imap);
+ sb->s_fs_info = NULL;
+ kfree(sbi);
+}
+
+static struct kmem_cache * minix_inode_cachep;
+
+static struct inode *minix_alloc_inode(struct super_block *sb)
+{
+ struct minix_inode_info *ei;
+ ei = alloc_inode_sb(sb, minix_inode_cachep, GFP_KERNEL);
+ if (!ei)
+ return NULL;
+ return &ei->vfs_inode;
+}
+
+static void minix_free_in_core_inode(struct inode *inode)
+{
+ kmem_cache_free(minix_inode_cachep, minix_i(inode));
+}
+
+static void init_once(void *foo)
+{
+ struct minix_inode_info *ei = (struct minix_inode_info *) foo;
+
+ inode_init_once(&ei->vfs_inode);
+}
+
+static int __init init_inodecache(void)
+{
+ minix_inode_cachep = kmem_cache_create("minix_inode_cache",
+ sizeof(struct minix_inode_info),
+ 0, (SLAB_RECLAIM_ACCOUNT|
+ SLAB_MEM_SPREAD|SLAB_ACCOUNT),
+ init_once);
+ if (minix_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(minix_inode_cachep);
+}
+
+static const struct super_operations minix_sops = {
+ .alloc_inode = minix_alloc_inode,
+ .free_inode = minix_free_in_core_inode,
+ .write_inode = minix_write_inode,
+ .evict_inode = minix_evict_inode,
+ .put_super = minix_put_super,
+ .statfs = minix_statfs,
+ .remount_fs = minix_remount,
+};
+
+static int minix_remount (struct super_block * sb, int * flags, char * data)
+{
+ struct minix_sb_info * sbi = minix_sb(sb);
+ struct minix_super_block * ms;
+
+ sync_filesystem(sb);
+ ms = sbi->s_ms;
+ if ((bool)(*flags & SB_RDONLY) == sb_rdonly(sb))
+ return 0;
+ if (*flags & SB_RDONLY) {
+ if (ms->s_state & MINIX_VALID_FS ||
+ !(sbi->s_mount_state & MINIX_VALID_FS))
+ return 0;
+ /* Mounting a rw partition read-only. */
+ if (sbi->s_version != MINIX_V3)
+ ms->s_state = sbi->s_mount_state;
+ mark_buffer_dirty(sbi->s_sbh);
+ } else {
+ /* Mount a partition which is read-only, read-write. */
+ if (sbi->s_version != MINIX_V3) {
+ sbi->s_mount_state = ms->s_state;
+ ms->s_state &= ~MINIX_VALID_FS;
+ } else {
+ sbi->s_mount_state = MINIX_VALID_FS;
+ }
+ mark_buffer_dirty(sbi->s_sbh);
+
+ if (!(sbi->s_mount_state & MINIX_VALID_FS))
+ printk("MINIX-fs warning: remounting unchecked fs, "
+ "running fsck is recommended\n");
+ else if ((sbi->s_mount_state & MINIX_ERROR_FS))
+ printk("MINIX-fs warning: remounting fs with errors, "
+ "running fsck is recommended\n");
+ }
+ return 0;
+}
+
+static bool minix_check_superblock(struct super_block *sb)
+{
+ struct minix_sb_info *sbi = minix_sb(sb);
+
+ if (sbi->s_imap_blocks == 0 || sbi->s_zmap_blocks == 0)
+ return false;
+
+ /*
+ * s_max_size must not exceed the block mapping limitation. This check
+ * is only needed for V1 filesystems, since V2/V3 support an extra level
+ * of indirect blocks which places the limit well above U32_MAX.
+ */
+ if (sbi->s_version == MINIX_V1 &&
+ sb->s_maxbytes > (7 + 512 + 512*512) * BLOCK_SIZE)
+ return false;
+
+ return true;
+}
+
+static int minix_fill_super(struct super_block *s, void *data, int silent)
+{
+ struct buffer_head *bh;
+ struct buffer_head **map;
+ struct minix_super_block *ms;
+ struct minix3_super_block *m3s = NULL;
+ unsigned long i, block;
+ struct inode *root_inode;
+ struct minix_sb_info *sbi;
+ int ret = -EINVAL;
+
+ sbi = kzalloc(sizeof(struct minix_sb_info), GFP_KERNEL);
+ if (!sbi)
+ return -ENOMEM;
+ s->s_fs_info = sbi;
+
+ BUILD_BUG_ON(32 != sizeof (struct minix_inode));
+ BUILD_BUG_ON(64 != sizeof(struct minix2_inode));
+
+ if (!sb_set_blocksize(s, BLOCK_SIZE))
+ goto out_bad_hblock;
+
+ if (!(bh = sb_bread(s, 1)))
+ goto out_bad_sb;
+
+ ms = (struct minix_super_block *) bh->b_data;
+ sbi->s_ms = ms;
+ sbi->s_sbh = bh;
+ sbi->s_mount_state = ms->s_state;
+ sbi->s_ninodes = ms->s_ninodes;
+ sbi->s_nzones = ms->s_nzones;
+ sbi->s_imap_blocks = ms->s_imap_blocks;
+ sbi->s_zmap_blocks = ms->s_zmap_blocks;
+ sbi->s_firstdatazone = ms->s_firstdatazone;
+ sbi->s_log_zone_size = ms->s_log_zone_size;
+ s->s_maxbytes = ms->s_max_size;
+ s->s_magic = ms->s_magic;
+ if (s->s_magic == MINIX_SUPER_MAGIC) {
+ sbi->s_version = MINIX_V1;
+ sbi->s_dirsize = 16;
+ sbi->s_namelen = 14;
+ s->s_max_links = MINIX_LINK_MAX;
+ } else if (s->s_magic == MINIX_SUPER_MAGIC2) {
+ sbi->s_version = MINIX_V1;
+ sbi->s_dirsize = 32;
+ sbi->s_namelen = 30;
+ s->s_max_links = MINIX_LINK_MAX;
+ } else if (s->s_magic == MINIX2_SUPER_MAGIC) {
+ sbi->s_version = MINIX_V2;
+ sbi->s_nzones = ms->s_zones;
+ sbi->s_dirsize = 16;
+ sbi->s_namelen = 14;
+ s->s_max_links = MINIX2_LINK_MAX;
+ } else if (s->s_magic == MINIX2_SUPER_MAGIC2) {
+ sbi->s_version = MINIX_V2;
+ sbi->s_nzones = ms->s_zones;
+ sbi->s_dirsize = 32;
+ sbi->s_namelen = 30;
+ s->s_max_links = MINIX2_LINK_MAX;
+ } else if ( *(__u16 *)(bh->b_data + 24) == MINIX3_SUPER_MAGIC) {
+ m3s = (struct minix3_super_block *) bh->b_data;
+ s->s_magic = m3s->s_magic;
+ sbi->s_imap_blocks = m3s->s_imap_blocks;
+ sbi->s_zmap_blocks = m3s->s_zmap_blocks;
+ sbi->s_firstdatazone = m3s->s_firstdatazone;
+ sbi->s_log_zone_size = m3s->s_log_zone_size;
+ s->s_maxbytes = m3s->s_max_size;
+ sbi->s_ninodes = m3s->s_ninodes;
+ sbi->s_nzones = m3s->s_zones;
+ sbi->s_dirsize = 64;
+ sbi->s_namelen = 60;
+ sbi->s_version = MINIX_V3;
+ sbi->s_mount_state = MINIX_VALID_FS;
+ sb_set_blocksize(s, m3s->s_blocksize);
+ s->s_max_links = MINIX2_LINK_MAX;
+ } else
+ goto out_no_fs;
+
+ if (!minix_check_superblock(s))
+ goto out_illegal_sb;
+
+ /*
+ * Allocate the buffer map to keep the superblock small.
+ */
+ i = (sbi->s_imap_blocks + sbi->s_zmap_blocks) * sizeof(bh);
+ map = kzalloc(i, GFP_KERNEL);
+ if (!map)
+ goto out_no_map;
+ sbi->s_imap = &map[0];
+ sbi->s_zmap = &map[sbi->s_imap_blocks];
+
+ block=2;
+ for (i=0 ; i < sbi->s_imap_blocks ; i++) {
+ if (!(sbi->s_imap[i]=sb_bread(s, block)))
+ goto out_no_bitmap;
+ block++;
+ }
+ for (i=0 ; i < sbi->s_zmap_blocks ; i++) {
+ if (!(sbi->s_zmap[i]=sb_bread(s, block)))
+ goto out_no_bitmap;
+ block++;
+ }
+
+ minix_set_bit(0,sbi->s_imap[0]->b_data);
+ minix_set_bit(0,sbi->s_zmap[0]->b_data);
+
+ /* Apparently minix can create filesystems that allocate more blocks for
+ * the bitmaps than needed. We simply ignore that, but verify it didn't
+ * create one with not enough blocks and bail out if so.
+ */
+ block = minix_blocks_needed(sbi->s_ninodes, s->s_blocksize);
+ if (sbi->s_imap_blocks < block) {
+ printk("MINIX-fs: file system does not have enough "
+ "imap blocks allocated. Refusing to mount.\n");
+ goto out_no_bitmap;
+ }
+
+ block = minix_blocks_needed(
+ (sbi->s_nzones - sbi->s_firstdatazone + 1),
+ s->s_blocksize);
+ if (sbi->s_zmap_blocks < block) {
+ printk("MINIX-fs: file system does not have enough "
+ "zmap blocks allocated. Refusing to mount.\n");
+ goto out_no_bitmap;
+ }
+
+ /* set up enough so that it can read an inode */
+ s->s_op = &minix_sops;
+ s->s_time_min = 0;
+ s->s_time_max = U32_MAX;
+ root_inode = minix_iget(s, MINIX_ROOT_INO);
+ if (IS_ERR(root_inode)) {
+ ret = PTR_ERR(root_inode);
+ goto out_no_root;
+ }
+
+ ret = -ENOMEM;
+ s->s_root = d_make_root(root_inode);
+ if (!s->s_root)
+ goto out_no_root;
+
+ if (!sb_rdonly(s)) {
+ if (sbi->s_version != MINIX_V3) /* s_state is now out from V3 sb */
+ ms->s_state &= ~MINIX_VALID_FS;
+ mark_buffer_dirty(bh);
+ }
+ if (!(sbi->s_mount_state & MINIX_VALID_FS))
+ printk("MINIX-fs: mounting unchecked file system, "
+ "running fsck is recommended\n");
+ else if (sbi->s_mount_state & MINIX_ERROR_FS)
+ printk("MINIX-fs: mounting file system with errors, "
+ "running fsck is recommended\n");
+
+ return 0;
+
+out_no_root:
+ if (!silent)
+ printk("MINIX-fs: get root inode failed\n");
+ goto out_freemap;
+
+out_no_bitmap:
+ printk("MINIX-fs: bad superblock or unable to read bitmaps\n");
+out_freemap:
+ for (i = 0; i < sbi->s_imap_blocks; i++)
+ brelse(sbi->s_imap[i]);
+ for (i = 0; i < sbi->s_zmap_blocks; i++)
+ brelse(sbi->s_zmap[i]);
+ kfree(sbi->s_imap);
+ goto out_release;
+
+out_no_map:
+ ret = -ENOMEM;
+ if (!silent)
+ printk("MINIX-fs: can't allocate map\n");
+ goto out_release;
+
+out_illegal_sb:
+ if (!silent)
+ printk("MINIX-fs: bad superblock\n");
+ goto out_release;
+
+out_no_fs:
+ if (!silent)
+ printk("VFS: Can't find a Minix filesystem V1 | V2 | V3 "
+ "on device %s.\n", s->s_id);
+out_release:
+ brelse(bh);
+ goto out;
+
+out_bad_hblock:
+ printk("MINIX-fs: blocksize too small for device\n");
+ goto out;
+
+out_bad_sb:
+ printk("MINIX-fs: unable to read superblock\n");
+out:
+ s->s_fs_info = NULL;
+ kfree(sbi);
+ return ret;
+}
+
+static int minix_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+ struct super_block *sb = dentry->d_sb;
+ struct minix_sb_info *sbi = minix_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 = (sbi->s_nzones - sbi->s_firstdatazone) << sbi->s_log_zone_size;
+ buf->f_bfree = minix_count_free_blocks(sb);
+ buf->f_bavail = buf->f_bfree;
+ buf->f_files = sbi->s_ninodes;
+ buf->f_ffree = minix_count_free_inodes(sb);
+ buf->f_namelen = sbi->s_namelen;
+ buf->f_fsid = u64_to_fsid(id);
+
+ return 0;
+}
+
+static int minix_get_block(struct inode *inode, sector_t block,
+ struct buffer_head *bh_result, int create)
+{
+ if (INODE_VERSION(inode) == MINIX_V1)
+ return V1_minix_get_block(inode, block, bh_result, create);
+ else
+ return V2_minix_get_block(inode, block, bh_result, create);
+}
+
+static int minix_writepage(struct page *page, struct writeback_control *wbc)
+{
+ return block_write_full_page(page, minix_get_block, wbc);
+}
+
+static int minix_read_folio(struct file *file, struct folio *folio)
+{
+ return block_read_full_folio(folio, minix_get_block);
+}
+
+int minix_prepare_chunk(struct page *page, loff_t pos, unsigned len)
+{
+ return __block_write_begin(page, pos, len, minix_get_block);
+}
+
+static void minix_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);
+ minix_truncate(inode);
+ }
+}
+
+static int minix_write_begin(struct file *file, struct address_space *mapping,
+ loff_t pos, unsigned len,
+ struct page **pagep, void **fsdata)
+{
+ int ret;
+
+ ret = block_write_begin(mapping, pos, len, pagep, minix_get_block);
+ if (unlikely(ret))
+ minix_write_failed(mapping, pos + len);
+
+ return ret;
+}
+
+static sector_t minix_bmap(struct address_space *mapping, sector_t block)
+{
+ return generic_block_bmap(mapping,block,minix_get_block);
+}
+
+static const struct address_space_operations minix_aops = {
+ .dirty_folio = block_dirty_folio,
+ .invalidate_folio = block_invalidate_folio,
+ .read_folio = minix_read_folio,
+ .writepage = minix_writepage,
+ .write_begin = minix_write_begin,
+ .write_end = generic_write_end,
+ .bmap = minix_bmap,
+ .direct_IO = noop_direct_IO
+};
+
+static const struct inode_operations minix_symlink_inode_operations = {
+ .get_link = page_get_link,
+ .getattr = minix_getattr,
+};
+
+void minix_set_inode(struct inode *inode, dev_t rdev)
+{
+ if (S_ISREG(inode->i_mode)) {
+ inode->i_op = &minix_file_inode_operations;
+ inode->i_fop = &minix_file_operations;
+ inode->i_mapping->a_ops = &minix_aops;
+ } else if (S_ISDIR(inode->i_mode)) {
+ inode->i_op = &minix_dir_inode_operations;
+ inode->i_fop = &minix_dir_operations;
+ inode->i_mapping->a_ops = &minix_aops;
+ } else if (S_ISLNK(inode->i_mode)) {
+ inode->i_op = &minix_symlink_inode_operations;
+ inode_nohighmem(inode);
+ inode->i_mapping->a_ops = &minix_aops;
+ } else
+ init_special_inode(inode, inode->i_mode, rdev);
+}
+
+/*
+ * The minix V1 function to read an inode.
+ */
+static struct inode *V1_minix_iget(struct inode *inode)
+{
+ struct buffer_head * bh;
+ struct minix_inode * raw_inode;
+ struct minix_inode_info *minix_inode = minix_i(inode);
+ int i;
+
+ raw_inode = minix_V1_raw_inode(inode->i_sb, inode->i_ino, &bh);
+ if (!raw_inode) {
+ iget_failed(inode);
+ return ERR_PTR(-EIO);
+ }
+ if (raw_inode->i_nlinks == 0) {
+ printk("MINIX-fs: deleted inode referenced: %lu\n",
+ inode->i_ino);
+ brelse(bh);
+ iget_failed(inode);
+ return ERR_PTR(-ESTALE);
+ }
+ inode->i_mode = raw_inode->i_mode;
+ i_uid_write(inode, raw_inode->i_uid);
+ i_gid_write(inode, raw_inode->i_gid);
+ set_nlink(inode, raw_inode->i_nlinks);
+ inode->i_size = raw_inode->i_size;
+ inode->i_mtime.tv_sec = inode->i_atime.tv_sec = inode->i_ctime.tv_sec = raw_inode->i_time;
+ inode->i_mtime.tv_nsec = 0;
+ inode->i_atime.tv_nsec = 0;
+ inode->i_ctime.tv_nsec = 0;
+ inode->i_blocks = 0;
+ for (i = 0; i < 9; i++)
+ minix_inode->u.i1_data[i] = raw_inode->i_zone[i];
+ minix_set_inode(inode, old_decode_dev(raw_inode->i_zone[0]));
+ brelse(bh);
+ unlock_new_inode(inode);
+ return inode;
+}
+
+/*
+ * The minix V2 function to read an inode.
+ */
+static struct inode *V2_minix_iget(struct inode *inode)
+{
+ struct buffer_head * bh;
+ struct minix2_inode * raw_inode;
+ struct minix_inode_info *minix_inode = minix_i(inode);
+ int i;
+
+ raw_inode = minix_V2_raw_inode(inode->i_sb, inode->i_ino, &bh);
+ if (!raw_inode) {
+ iget_failed(inode);
+ return ERR_PTR(-EIO);
+ }
+ if (raw_inode->i_nlinks == 0) {
+ printk("MINIX-fs: deleted inode referenced: %lu\n",
+ inode->i_ino);
+ brelse(bh);
+ iget_failed(inode);
+ return ERR_PTR(-ESTALE);
+ }
+ inode->i_mode = raw_inode->i_mode;
+ i_uid_write(inode, raw_inode->i_uid);
+ i_gid_write(inode, raw_inode->i_gid);
+ set_nlink(inode, raw_inode->i_nlinks);
+ inode->i_size = raw_inode->i_size;
+ inode->i_mtime.tv_sec = raw_inode->i_mtime;
+ inode->i_atime.tv_sec = raw_inode->i_atime;
+ inode->i_ctime.tv_sec = raw_inode->i_ctime;
+ inode->i_mtime.tv_nsec = 0;
+ inode->i_atime.tv_nsec = 0;
+ inode->i_ctime.tv_nsec = 0;
+ inode->i_blocks = 0;
+ for (i = 0; i < 10; i++)
+ minix_inode->u.i2_data[i] = raw_inode->i_zone[i];
+ minix_set_inode(inode, old_decode_dev(raw_inode->i_zone[0]));
+ brelse(bh);
+ unlock_new_inode(inode);
+ return inode;
+}
+
+/*
+ * The global function to read an inode.
+ */
+struct inode *minix_iget(struct super_block *sb, unsigned long ino)
+{
+ struct inode *inode;
+
+ inode = iget_locked(sb, ino);
+ if (!inode)
+ return ERR_PTR(-ENOMEM);
+ if (!(inode->i_state & I_NEW))
+ return inode;
+
+ if (INODE_VERSION(inode) == MINIX_V1)
+ return V1_minix_iget(inode);
+ else
+ return V2_minix_iget(inode);
+}
+
+/*
+ * The minix V1 function to synchronize an inode.
+ */
+static struct buffer_head * V1_minix_update_inode(struct inode * inode)
+{
+ struct buffer_head * bh;
+ struct minix_inode * raw_inode;
+ struct minix_inode_info *minix_inode = minix_i(inode);
+ int i;
+
+ raw_inode = minix_V1_raw_inode(inode->i_sb, inode->i_ino, &bh);
+ if (!raw_inode)
+ return NULL;
+ raw_inode->i_mode = inode->i_mode;
+ raw_inode->i_uid = fs_high2lowuid(i_uid_read(inode));
+ raw_inode->i_gid = fs_high2lowgid(i_gid_read(inode));
+ raw_inode->i_nlinks = inode->i_nlink;
+ raw_inode->i_size = inode->i_size;
+ raw_inode->i_time = inode->i_mtime.tv_sec;
+ if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode))
+ raw_inode->i_zone[0] = old_encode_dev(inode->i_rdev);
+ else for (i = 0; i < 9; i++)
+ raw_inode->i_zone[i] = minix_inode->u.i1_data[i];
+ mark_buffer_dirty(bh);
+ return bh;
+}
+
+/*
+ * The minix V2 function to synchronize an inode.
+ */
+static struct buffer_head * V2_minix_update_inode(struct inode * inode)
+{
+ struct buffer_head * bh;
+ struct minix2_inode * raw_inode;
+ struct minix_inode_info *minix_inode = minix_i(inode);
+ int i;
+
+ raw_inode = minix_V2_raw_inode(inode->i_sb, inode->i_ino, &bh);
+ if (!raw_inode)
+ return NULL;
+ raw_inode->i_mode = inode->i_mode;
+ raw_inode->i_uid = fs_high2lowuid(i_uid_read(inode));
+ raw_inode->i_gid = fs_high2lowgid(i_gid_read(inode));
+ raw_inode->i_nlinks = inode->i_nlink;
+ raw_inode->i_size = inode->i_size;
+ raw_inode->i_mtime = inode->i_mtime.tv_sec;
+ raw_inode->i_atime = inode->i_atime.tv_sec;
+ raw_inode->i_ctime = inode->i_ctime.tv_sec;
+ if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode))
+ raw_inode->i_zone[0] = old_encode_dev(inode->i_rdev);
+ else for (i = 0; i < 10; i++)
+ raw_inode->i_zone[i] = minix_inode->u.i2_data[i];
+ mark_buffer_dirty(bh);
+ return bh;
+}
+
+static int minix_write_inode(struct inode *inode, struct writeback_control *wbc)
+{
+ int err = 0;
+ struct buffer_head *bh;
+
+ if (INODE_VERSION(inode) == MINIX_V1)
+ bh = V1_minix_update_inode(inode);
+ else
+ bh = V2_minix_update_inode(inode);
+ if (!bh)
+ return -EIO;
+ if (wbc->sync_mode == WB_SYNC_ALL && buffer_dirty(bh)) {
+ sync_dirty_buffer(bh);
+ if (buffer_req(bh) && !buffer_uptodate(bh)) {
+ printk("IO error syncing minix inode [%s:%08lx]\n",
+ inode->i_sb->s_id, inode->i_ino);
+ err = -EIO;
+ }
+ }
+ brelse (bh);
+ return err;
+}
+
+int minix_getattr(struct user_namespace *mnt_userns, const struct path *path,
+ struct kstat *stat, u32 request_mask, unsigned int flags)
+{
+ struct super_block *sb = path->dentry->d_sb;
+ struct inode *inode = d_inode(path->dentry);
+
+ generic_fillattr(&init_user_ns, inode, stat);
+ if (INODE_VERSION(inode) == MINIX_V1)
+ stat->blocks = (BLOCK_SIZE / 512) * V1_minix_blocks(stat->size, sb);
+ else
+ stat->blocks = (sb->s_blocksize / 512) * V2_minix_blocks(stat->size, sb);
+ stat->blksize = sb->s_blocksize;
+ return 0;
+}
+
+/*
+ * The function that is called for file truncation.
+ */
+void minix_truncate(struct inode * inode)
+{
+ if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode)))
+ return;
+ if (INODE_VERSION(inode) == MINIX_V1)
+ V1_minix_truncate(inode);
+ else
+ V2_minix_truncate(inode);
+}
+
+static struct dentry *minix_mount(struct file_system_type *fs_type,
+ int flags, const char *dev_name, void *data)
+{
+ return mount_bdev(fs_type, flags, dev_name, data, minix_fill_super);
+}
+
+static struct file_system_type minix_fs_type = {
+ .owner = THIS_MODULE,
+ .name = "minix",
+ .mount = minix_mount,
+ .kill_sb = kill_block_super,
+ .fs_flags = FS_REQUIRES_DEV,
+};
+MODULE_ALIAS_FS("minix");
+
+static int __init init_minix_fs(void)
+{
+ int err = init_inodecache();
+ if (err)
+ goto out1;
+ err = register_filesystem(&minix_fs_type);
+ if (err)
+ goto out;
+ return 0;
+out:
+ destroy_inodecache();
+out1:
+ return err;
+}
+
+static void __exit exit_minix_fs(void)
+{
+ unregister_filesystem(&minix_fs_type);
+ destroy_inodecache();
+}
+
+module_init(init_minix_fs)
+module_exit(exit_minix_fs)
+MODULE_LICENSE("GPL");
+
diff --git a/fs/minix/itree_common.c b/fs/minix/itree_common.c
new file mode 100644
index 000000000..446148792
--- /dev/null
+++ b/fs/minix/itree_common.c
@@ -0,0 +1,371 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Generic part */
+
+typedef struct {
+ block_t *p;
+ block_t key;
+ struct buffer_head *bh;
+} Indirect;
+
+static DEFINE_RWLOCK(pointers_lock);
+
+static inline void add_chain(Indirect *p, struct buffer_head *bh, block_t *v)
+{
+ p->key = *(p->p = v);
+ p->bh = bh;
+}
+
+static inline int verify_chain(Indirect *from, Indirect *to)
+{
+ while (from <= to && from->key == *from->p)
+ from++;
+ return (from > to);
+}
+
+static inline block_t *block_end(struct buffer_head *bh)
+{
+ return (block_t *)((char*)bh->b_data + bh->b_size);
+}
+
+static inline Indirect *get_branch(struct inode *inode,
+ int depth,
+ int *offsets,
+ Indirect chain[DEPTH],
+ int *err)
+{
+ struct super_block *sb = inode->i_sb;
+ Indirect *p = chain;
+ struct buffer_head *bh;
+
+ *err = 0;
+ /* i_data is not going away, no lock needed */
+ add_chain (chain, NULL, i_data(inode) + *offsets);
+ if (!p->key)
+ goto no_block;
+ while (--depth) {
+ bh = sb_bread(sb, block_to_cpu(p->key));
+ if (!bh)
+ goto failure;
+ read_lock(&pointers_lock);
+ if (!verify_chain(chain, p))
+ goto changed;
+ add_chain(++p, bh, (block_t *)bh->b_data + *++offsets);
+ read_unlock(&pointers_lock);
+ if (!p->key)
+ goto no_block;
+ }
+ return NULL;
+
+changed:
+ read_unlock(&pointers_lock);
+ brelse(bh);
+ *err = -EAGAIN;
+ goto no_block;
+failure:
+ *err = -EIO;
+no_block:
+ return p;
+}
+
+static int alloc_branch(struct inode *inode,
+ int num,
+ int *offsets,
+ Indirect *branch)
+{
+ int n = 0;
+ int i;
+ int parent = minix_new_block(inode);
+ int err = -ENOSPC;
+
+ branch[0].key = cpu_to_block(parent);
+ if (parent) for (n = 1; n < num; n++) {
+ struct buffer_head *bh;
+ /* Allocate the next block */
+ int nr = minix_new_block(inode);
+ if (!nr)
+ break;
+ branch[n].key = cpu_to_block(nr);
+ bh = sb_getblk(inode->i_sb, parent);
+ if (!bh) {
+ minix_free_block(inode, nr);
+ err = -ENOMEM;
+ break;
+ }
+ lock_buffer(bh);
+ memset(bh->b_data, 0, bh->b_size);
+ branch[n].bh = bh;
+ branch[n].p = (block_t*) bh->b_data + offsets[n];
+ *branch[n].p = branch[n].key;
+ set_buffer_uptodate(bh);
+ unlock_buffer(bh);
+ mark_buffer_dirty_inode(bh, inode);
+ parent = nr;
+ }
+ if (n == num)
+ return 0;
+
+ /* Allocation failed, free what we already allocated */
+ for (i = 1; i < n; i++)
+ bforget(branch[i].bh);
+ for (i = 0; i < n; i++)
+ minix_free_block(inode, block_to_cpu(branch[i].key));
+ return err;
+}
+
+static inline int splice_branch(struct inode *inode,
+ Indirect chain[DEPTH],
+ Indirect *where,
+ int num)
+{
+ int i;
+
+ write_lock(&pointers_lock);
+
+ /* Verify that place we are splicing to is still there and vacant */
+ if (!verify_chain(chain, where-1) || *where->p)
+ goto changed;
+
+ *where->p = where->key;
+
+ write_unlock(&pointers_lock);
+
+ /* We are done with atomic stuff, now do the rest of housekeeping */
+
+ inode->i_ctime = current_time(inode);
+
+ /* had we spliced it onto indirect block? */
+ if (where->bh)
+ mark_buffer_dirty_inode(where->bh, inode);
+
+ mark_inode_dirty(inode);
+ return 0;
+
+changed:
+ write_unlock(&pointers_lock);
+ for (i = 1; i < num; i++)
+ bforget(where[i].bh);
+ for (i = 0; i < num; i++)
+ minix_free_block(inode, block_to_cpu(where[i].key));
+ return -EAGAIN;
+}
+
+static int get_block(struct inode * inode, sector_t block,
+ struct buffer_head *bh, int create)
+{
+ int err = -EIO;
+ int offsets[DEPTH];
+ Indirect chain[DEPTH];
+ Indirect *partial;
+ int left;
+ int depth = block_to_path(inode, block, offsets);
+
+ if (depth == 0)
+ goto out;
+
+reread:
+ partial = get_branch(inode, depth, offsets, chain, &err);
+
+ /* Simplest case - block found, no allocation needed */
+ if (!partial) {
+got_it:
+ map_bh(bh, inode->i_sb, block_to_cpu(chain[depth-1].key));
+ /* Clean up and exit */
+ partial = chain+depth-1; /* the whole chain */
+ goto cleanup;
+ }
+
+ /* Next simple case - plain lookup or failed read of indirect block */
+ if (!create || err == -EIO) {
+cleanup:
+ while (partial > chain) {
+ brelse(partial->bh);
+ partial--;
+ }
+out:
+ return err;
+ }
+
+ /*
+ * Indirect block might be removed by truncate while we were
+ * reading it. Handling of that case (forget what we've got and
+ * reread) is taken out of the main path.
+ */
+ if (err == -EAGAIN)
+ goto changed;
+
+ left = (chain + depth) - partial;
+ err = alloc_branch(inode, left, offsets+(partial-chain), partial);
+ if (err)
+ goto cleanup;
+
+ if (splice_branch(inode, chain, partial, left) < 0)
+ goto changed;
+
+ set_buffer_new(bh);
+ goto got_it;
+
+changed:
+ while (partial > chain) {
+ brelse(partial->bh);
+ partial--;
+ }
+ goto reread;
+}
+
+static inline int all_zeroes(block_t *p, block_t *q)
+{
+ while (p < q)
+ if (*p++)
+ return 0;
+ return 1;
+}
+
+static Indirect *find_shared(struct inode *inode,
+ int depth,
+ int offsets[DEPTH],
+ Indirect chain[DEPTH],
+ block_t *top)
+{
+ Indirect *partial, *p;
+ int k, err;
+
+ *top = 0;
+ for (k = depth; k > 1 && !offsets[k-1]; k--)
+ ;
+ partial = get_branch(inode, k, offsets, chain, &err);
+
+ write_lock(&pointers_lock);
+ if (!partial)
+ partial = chain + k-1;
+ if (!partial->key && *partial->p) {
+ write_unlock(&pointers_lock);
+ goto no_top;
+ }
+ for (p=partial;p>chain && all_zeroes((block_t*)p->bh->b_data,p->p);p--)
+ ;
+ if (p == chain + k - 1 && p > chain) {
+ p->p--;
+ } else {
+ *top = *p->p;
+ *p->p = 0;
+ }
+ write_unlock(&pointers_lock);
+
+ while(partial > p)
+ {
+ brelse(partial->bh);
+ partial--;
+ }
+no_top:
+ return partial;
+}
+
+static inline void free_data(struct inode *inode, block_t *p, block_t *q)
+{
+ unsigned long nr;
+
+ for ( ; p < q ; p++) {
+ nr = block_to_cpu(*p);
+ if (nr) {
+ *p = 0;
+ minix_free_block(inode, nr);
+ }
+ }
+}
+
+static void free_branches(struct inode *inode, block_t *p, block_t *q, int depth)
+{
+ struct buffer_head * bh;
+ unsigned long nr;
+
+ if (depth--) {
+ for ( ; p < q ; p++) {
+ nr = block_to_cpu(*p);
+ if (!nr)
+ continue;
+ *p = 0;
+ bh = sb_bread(inode->i_sb, nr);
+ if (!bh)
+ continue;
+ free_branches(inode, (block_t*)bh->b_data,
+ block_end(bh), depth);
+ bforget(bh);
+ minix_free_block(inode, nr);
+ mark_inode_dirty(inode);
+ }
+ } else
+ free_data(inode, p, q);
+}
+
+static inline void truncate (struct inode * inode)
+{
+ struct super_block *sb = inode->i_sb;
+ block_t *idata = i_data(inode);
+ int offsets[DEPTH];
+ Indirect chain[DEPTH];
+ Indirect *partial;
+ block_t nr = 0;
+ int n;
+ int first_whole;
+ long iblock;
+
+ iblock = (inode->i_size + sb->s_blocksize -1) >> sb->s_blocksize_bits;
+ block_truncate_page(inode->i_mapping, inode->i_size, get_block);
+
+ n = block_to_path(inode, iblock, offsets);
+ if (!n)
+ return;
+
+ if (n == 1) {
+ free_data(inode, idata+offsets[0], idata + DIRECT);
+ first_whole = 0;
+ goto do_indirects;
+ }
+
+ first_whole = offsets[0] + 1 - DIRECT;
+ partial = find_shared(inode, n, offsets, chain, &nr);
+ if (nr) {
+ if (partial == chain)
+ mark_inode_dirty(inode);
+ else
+ mark_buffer_dirty_inode(partial->bh, inode);
+ free_branches(inode, &nr, &nr+1, (chain+n-1) - partial);
+ }
+ /* Clear the ends of indirect blocks on the shared branch */
+ while (partial > chain) {
+ free_branches(inode, partial->p + 1, block_end(partial->bh),
+ (chain+n-1) - partial);
+ mark_buffer_dirty_inode(partial->bh, inode);
+ brelse (partial->bh);
+ partial--;
+ }
+do_indirects:
+ /* Kill the remaining (whole) subtrees */
+ while (first_whole < DEPTH-1) {
+ nr = idata[DIRECT+first_whole];
+ if (nr) {
+ idata[DIRECT+first_whole] = 0;
+ mark_inode_dirty(inode);
+ free_branches(inode, &nr, &nr+1, first_whole+1);
+ }
+ first_whole++;
+ }
+ inode->i_mtime = inode->i_ctime = current_time(inode);
+ mark_inode_dirty(inode);
+}
+
+static inline unsigned nblocks(loff_t size, struct super_block *sb)
+{
+ int k = sb->s_blocksize_bits - 10;
+ unsigned blocks, res, direct = DIRECT, i = DEPTH;
+ blocks = (size + sb->s_blocksize - 1) >> (BLOCK_SIZE_BITS + k);
+ res = blocks;
+ while (--i && blocks > direct) {
+ blocks -= direct;
+ blocks += sb->s_blocksize/sizeof(block_t) - 1;
+ blocks /= sb->s_blocksize/sizeof(block_t);
+ res += blocks;
+ direct = 1;
+ }
+ return res;
+}
diff --git a/fs/minix/itree_v1.c b/fs/minix/itree_v1.c
new file mode 100644
index 000000000..1fed90604
--- /dev/null
+++ b/fs/minix/itree_v1.c
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/buffer_head.h>
+#include <linux/slab.h>
+#include "minix.h"
+
+enum {DEPTH = 3, DIRECT = 7}; /* Only double indirect */
+
+typedef u16 block_t; /* 16 bit, host order */
+
+static inline unsigned long block_to_cpu(block_t n)
+{
+ return n;
+}
+
+static inline block_t cpu_to_block(unsigned long n)
+{
+ return n;
+}
+
+static inline block_t *i_data(struct inode *inode)
+{
+ return (block_t *)minix_i(inode)->u.i1_data;
+}
+
+static int block_to_path(struct inode * inode, long block, int offsets[DEPTH])
+{
+ int n = 0;
+
+ if (block < 0) {
+ printk("MINIX-fs: block_to_path: block %ld < 0 on dev %pg\n",
+ block, inode->i_sb->s_bdev);
+ return 0;
+ }
+ if ((u64)block * BLOCK_SIZE >= inode->i_sb->s_maxbytes)
+ return 0;
+
+ if (block < 7) {
+ offsets[n++] = block;
+ } else if ((block -= 7) < 512) {
+ offsets[n++] = 7;
+ offsets[n++] = block;
+ } else {
+ block -= 512;
+ offsets[n++] = 8;
+ offsets[n++] = block>>9;
+ offsets[n++] = block & 511;
+ }
+ return n;
+}
+
+#include "itree_common.c"
+
+int V1_minix_get_block(struct inode * inode, long block,
+ struct buffer_head *bh_result, int create)
+{
+ return get_block(inode, block, bh_result, create);
+}
+
+void V1_minix_truncate(struct inode * inode)
+{
+ truncate(inode);
+}
+
+unsigned V1_minix_blocks(loff_t size, struct super_block *sb)
+{
+ return nblocks(size, sb);
+}
diff --git a/fs/minix/itree_v2.c b/fs/minix/itree_v2.c
new file mode 100644
index 000000000..9d00f31a2
--- /dev/null
+++ b/fs/minix/itree_v2.c
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/buffer_head.h>
+#include "minix.h"
+
+enum {DIRECT = 7, DEPTH = 4}; /* Have triple indirect */
+
+typedef u32 block_t; /* 32 bit, host order */
+
+static inline unsigned long block_to_cpu(block_t n)
+{
+ return n;
+}
+
+static inline block_t cpu_to_block(unsigned long n)
+{
+ return n;
+}
+
+static inline block_t *i_data(struct inode *inode)
+{
+ return (block_t *)minix_i(inode)->u.i2_data;
+}
+
+#define DIRCOUNT 7
+#define INDIRCOUNT(sb) (1 << ((sb)->s_blocksize_bits - 2))
+
+static int block_to_path(struct inode * inode, long block, int offsets[DEPTH])
+{
+ int n = 0;
+ struct super_block *sb = inode->i_sb;
+
+ if (block < 0) {
+ printk("MINIX-fs: block_to_path: block %ld < 0 on dev %pg\n",
+ block, sb->s_bdev);
+ return 0;
+ }
+ if ((u64)block * (u64)sb->s_blocksize >= sb->s_maxbytes)
+ return 0;
+
+ if (block < DIRCOUNT) {
+ offsets[n++] = block;
+ } else if ((block -= DIRCOUNT) < INDIRCOUNT(sb)) {
+ offsets[n++] = DIRCOUNT;
+ offsets[n++] = block;
+ } else if ((block -= INDIRCOUNT(sb)) < INDIRCOUNT(sb) * INDIRCOUNT(sb)) {
+ offsets[n++] = DIRCOUNT + 1;
+ offsets[n++] = block / INDIRCOUNT(sb);
+ offsets[n++] = block % INDIRCOUNT(sb);
+ } else {
+ block -= INDIRCOUNT(sb) * INDIRCOUNT(sb);
+ offsets[n++] = DIRCOUNT + 2;
+ offsets[n++] = (block / INDIRCOUNT(sb)) / INDIRCOUNT(sb);
+ offsets[n++] = (block / INDIRCOUNT(sb)) % INDIRCOUNT(sb);
+ offsets[n++] = block % INDIRCOUNT(sb);
+ }
+ return n;
+}
+
+#include "itree_common.c"
+
+int V2_minix_get_block(struct inode * inode, long block,
+ struct buffer_head *bh_result, int create)
+{
+ return get_block(inode, block, bh_result, create);
+}
+
+void V2_minix_truncate(struct inode * inode)
+{
+ truncate(inode);
+}
+
+unsigned V2_minix_blocks(loff_t size, struct super_block *sb)
+{
+ return nblocks(size, sb);
+}
diff --git a/fs/minix/minix.h b/fs/minix/minix.h
new file mode 100644
index 000000000..202173368
--- /dev/null
+++ b/fs/minix/minix.h
@@ -0,0 +1,170 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef FS_MINIX_H
+#define FS_MINIX_H
+
+#include <linux/fs.h>
+#include <linux/pagemap.h>
+#include <linux/minix_fs.h>
+
+#define INODE_VERSION(inode) minix_sb(inode->i_sb)->s_version
+#define MINIX_V1 0x0001 /* original minix fs */
+#define MINIX_V2 0x0002 /* minix V2 fs */
+#define MINIX_V3 0x0003 /* minix V3 fs */
+
+/*
+ * minix fs inode data in memory
+ */
+struct minix_inode_info {
+ union {
+ __u16 i1_data[16];
+ __u32 i2_data[16];
+ } u;
+ struct inode vfs_inode;
+};
+
+/*
+ * minix super-block data in memory
+ */
+struct minix_sb_info {
+ unsigned long s_ninodes;
+ unsigned long s_nzones;
+ unsigned long s_imap_blocks;
+ unsigned long s_zmap_blocks;
+ unsigned long s_firstdatazone;
+ unsigned long s_log_zone_size;
+ int s_dirsize;
+ int s_namelen;
+ struct buffer_head ** s_imap;
+ struct buffer_head ** s_zmap;
+ struct buffer_head * s_sbh;
+ struct minix_super_block * s_ms;
+ unsigned short s_mount_state;
+ unsigned short s_version;
+};
+
+extern struct inode *minix_iget(struct super_block *, unsigned long);
+extern struct minix_inode * minix_V1_raw_inode(struct super_block *, ino_t, struct buffer_head **);
+extern struct minix2_inode * minix_V2_raw_inode(struct super_block *, ino_t, struct buffer_head **);
+extern struct inode * minix_new_inode(const struct inode *, umode_t, int *);
+extern void minix_free_inode(struct inode * inode);
+extern unsigned long minix_count_free_inodes(struct super_block *sb);
+extern int minix_new_block(struct inode * inode);
+extern void minix_free_block(struct inode *inode, unsigned long block);
+extern unsigned long minix_count_free_blocks(struct super_block *sb);
+extern int minix_getattr(struct user_namespace *, const struct path *,
+ struct kstat *, u32, unsigned int);
+extern int minix_prepare_chunk(struct page *page, loff_t pos, unsigned len);
+
+extern void V1_minix_truncate(struct inode *);
+extern void V2_minix_truncate(struct inode *);
+extern void minix_truncate(struct inode *);
+extern void minix_set_inode(struct inode *, dev_t);
+extern int V1_minix_get_block(struct inode *, long, struct buffer_head *, int);
+extern int V2_minix_get_block(struct inode *, long, struct buffer_head *, int);
+extern unsigned V1_minix_blocks(loff_t, struct super_block *);
+extern unsigned V2_minix_blocks(loff_t, struct super_block *);
+
+extern struct minix_dir_entry *minix_find_entry(struct dentry*, struct page**);
+extern int minix_add_link(struct dentry*, struct inode*);
+extern int minix_delete_entry(struct minix_dir_entry*, struct page*);
+extern int minix_make_empty(struct inode*, struct inode*);
+extern int minix_empty_dir(struct inode*);
+extern void minix_set_link(struct minix_dir_entry*, struct page*, struct inode*);
+extern struct minix_dir_entry *minix_dotdot(struct inode*, struct page**);
+extern ino_t minix_inode_by_name(struct dentry*);
+
+extern const struct inode_operations minix_file_inode_operations;
+extern const struct inode_operations minix_dir_inode_operations;
+extern const struct file_operations minix_file_operations;
+extern const struct file_operations minix_dir_operations;
+
+static inline struct minix_sb_info *minix_sb(struct super_block *sb)
+{
+ return sb->s_fs_info;
+}
+
+static inline struct minix_inode_info *minix_i(struct inode *inode)
+{
+ return container_of(inode, struct minix_inode_info, vfs_inode);
+}
+
+static inline unsigned minix_blocks_needed(unsigned bits, unsigned blocksize)
+{
+ return DIV_ROUND_UP(bits, blocksize * 8);
+}
+
+#if defined(CONFIG_MINIX_FS_NATIVE_ENDIAN) && \
+ defined(CONFIG_MINIX_FS_BIG_ENDIAN_16BIT_INDEXED)
+
+#error Minix file system byte order broken
+
+#elif defined(CONFIG_MINIX_FS_NATIVE_ENDIAN)
+
+/*
+ * big-endian 32 or 64 bit indexed bitmaps on big-endian system or
+ * little-endian bitmaps on little-endian system
+ */
+
+#define minix_test_and_set_bit(nr, addr) \
+ __test_and_set_bit((nr), (unsigned long *)(addr))
+#define minix_set_bit(nr, addr) \
+ __set_bit((nr), (unsigned long *)(addr))
+#define minix_test_and_clear_bit(nr, addr) \
+ __test_and_clear_bit((nr), (unsigned long *)(addr))
+#define minix_test_bit(nr, addr) \
+ test_bit((nr), (unsigned long *)(addr))
+#define minix_find_first_zero_bit(addr, size) \
+ find_first_zero_bit((unsigned long *)(addr), (size))
+
+#elif defined(CONFIG_MINIX_FS_BIG_ENDIAN_16BIT_INDEXED)
+
+/*
+ * big-endian 16bit indexed bitmaps
+ */
+
+static inline int minix_find_first_zero_bit(const void *vaddr, unsigned size)
+{
+ const unsigned short *p = vaddr, *addr = vaddr;
+ unsigned short num;
+
+ if (!size)
+ return 0;
+
+ size >>= 4;
+ while (*p++ == 0xffff) {
+ if (--size == 0)
+ return (p - addr) << 4;
+ }
+
+ num = *--p;
+ return ((p - addr) << 4) + ffz(num);
+}
+
+#define minix_test_and_set_bit(nr, addr) \
+ __test_and_set_bit((nr) ^ 16, (unsigned long *)(addr))
+#define minix_set_bit(nr, addr) \
+ __set_bit((nr) ^ 16, (unsigned long *)(addr))
+#define minix_test_and_clear_bit(nr, addr) \
+ __test_and_clear_bit((nr) ^ 16, (unsigned long *)(addr))
+
+static inline int minix_test_bit(int nr, const void *vaddr)
+{
+ const unsigned short *p = vaddr;
+ return (p[nr >> 4] & (1U << (nr & 15))) != 0;
+}
+
+#else
+
+/*
+ * little-endian bitmaps
+ */
+
+#define minix_test_and_set_bit __test_and_set_bit_le
+#define minix_set_bit __set_bit_le
+#define minix_test_and_clear_bit __test_and_clear_bit_le
+#define minix_test_bit test_bit_le
+#define minix_find_first_zero_bit find_first_zero_bit_le
+
+#endif
+
+#endif /* FS_MINIX_H */
diff --git a/fs/minix/namei.c b/fs/minix/namei.c
new file mode 100644
index 000000000..8afdc408c
--- /dev/null
+++ b/fs/minix/namei.c
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/minix/namei.c
+ *
+ * Copyright (C) 1991, 1992 Linus Torvalds
+ */
+
+#include "minix.h"
+
+static int add_nondir(struct dentry *dentry, struct inode *inode)
+{
+ int err = minix_add_link(dentry, inode);
+ if (!err) {
+ d_instantiate(dentry, inode);
+ return 0;
+ }
+ inode_dec_link_count(inode);
+ iput(inode);
+ return err;
+}
+
+static struct dentry *minix_lookup(struct inode * dir, struct dentry *dentry, unsigned int flags)
+{
+ struct inode * inode = NULL;
+ ino_t ino;
+
+ if (dentry->d_name.len > minix_sb(dir->i_sb)->s_namelen)
+ return ERR_PTR(-ENAMETOOLONG);
+
+ ino = minix_inode_by_name(dentry);
+ if (ino)
+ inode = minix_iget(dir->i_sb, ino);
+ return d_splice_alias(inode, dentry);
+}
+
+static int minix_mknod(struct user_namespace *mnt_userns, struct inode *dir,
+ struct dentry *dentry, umode_t mode, dev_t rdev)
+{
+ int error;
+ struct inode *inode;
+
+ if (!old_valid_dev(rdev))
+ return -EINVAL;
+
+ inode = minix_new_inode(dir, mode, &error);
+
+ if (inode) {
+ minix_set_inode(inode, rdev);
+ mark_inode_dirty(inode);
+ error = add_nondir(dentry, inode);
+ }
+ return error;
+}
+
+static int minix_tmpfile(struct user_namespace *mnt_userns, struct inode *dir,
+ struct file *file, umode_t mode)
+{
+ int error;
+ struct inode *inode = minix_new_inode(dir, mode, &error);
+ if (inode) {
+ minix_set_inode(inode, 0);
+ mark_inode_dirty(inode);
+ d_tmpfile(file, inode);
+ }
+ return finish_open_simple(file, error);
+}
+
+static int minix_create(struct user_namespace *mnt_userns, struct inode *dir,
+ struct dentry *dentry, umode_t mode, bool excl)
+{
+ return minix_mknod(mnt_userns, dir, dentry, mode, 0);
+}
+
+static int minix_symlink(struct user_namespace *mnt_userns, struct inode *dir,
+ struct dentry *dentry, const char *symname)
+{
+ int err = -ENAMETOOLONG;
+ int i = strlen(symname)+1;
+ struct inode * inode;
+
+ if (i > dir->i_sb->s_blocksize)
+ goto out;
+
+ inode = minix_new_inode(dir, S_IFLNK | 0777, &err);
+ if (!inode)
+ goto out;
+
+ minix_set_inode(inode, 0);
+ err = page_symlink(inode, symname, i);
+ if (err)
+ goto out_fail;
+
+ err = add_nondir(dentry, inode);
+out:
+ return err;
+
+out_fail:
+ inode_dec_link_count(inode);
+ iput(inode);
+ goto out;
+}
+
+static int minix_link(struct dentry * old_dentry, struct inode * dir,
+ struct dentry *dentry)
+{
+ struct inode *inode = d_inode(old_dentry);
+
+ inode->i_ctime = current_time(inode);
+ inode_inc_link_count(inode);
+ ihold(inode);
+ return add_nondir(dentry, inode);
+}
+
+static int minix_mkdir(struct user_namespace *mnt_userns, struct inode *dir,
+ struct dentry *dentry, umode_t mode)
+{
+ struct inode * inode;
+ int err;
+
+ inode_inc_link_count(dir);
+
+ inode = minix_new_inode(dir, S_IFDIR | mode, &err);
+ if (!inode)
+ goto out_dir;
+
+ minix_set_inode(inode, 0);
+
+ inode_inc_link_count(inode);
+
+ err = minix_make_empty(inode, dir);
+ if (err)
+ goto out_fail;
+
+ err = minix_add_link(dentry, inode);
+ if (err)
+ goto out_fail;
+
+ d_instantiate(dentry, inode);
+out:
+ return err;
+
+out_fail:
+ inode_dec_link_count(inode);
+ inode_dec_link_count(inode);
+ iput(inode);
+out_dir:
+ inode_dec_link_count(dir);
+ goto out;
+}
+
+static int minix_unlink(struct inode * dir, struct dentry *dentry)
+{
+ int err = -ENOENT;
+ struct inode * inode = d_inode(dentry);
+ struct page * page;
+ struct minix_dir_entry * de;
+
+ de = minix_find_entry(dentry, &page);
+ if (!de)
+ goto end_unlink;
+
+ err = minix_delete_entry(de, page);
+ if (err)
+ goto end_unlink;
+
+ inode->i_ctime = dir->i_ctime;
+ inode_dec_link_count(inode);
+end_unlink:
+ return err;
+}
+
+static int minix_rmdir(struct inode * dir, struct dentry *dentry)
+{
+ struct inode * inode = d_inode(dentry);
+ int err = -ENOTEMPTY;
+
+ if (minix_empty_dir(inode)) {
+ err = minix_unlink(dir, dentry);
+ if (!err) {
+ inode_dec_link_count(dir);
+ inode_dec_link_count(inode);
+ }
+ }
+ return err;
+}
+
+static int minix_rename(struct user_namespace *mnt_userns,
+ struct inode *old_dir, struct dentry *old_dentry,
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
+{
+ struct inode * old_inode = d_inode(old_dentry);
+ struct inode * new_inode = d_inode(new_dentry);
+ struct page * dir_page = NULL;
+ struct minix_dir_entry * dir_de = NULL;
+ struct page * old_page;
+ struct minix_dir_entry * old_de;
+ int err = -ENOENT;
+
+ if (flags & ~RENAME_NOREPLACE)
+ return -EINVAL;
+
+ old_de = minix_find_entry(old_dentry, &old_page);
+ if (!old_de)
+ goto out;
+
+ if (S_ISDIR(old_inode->i_mode)) {
+ err = -EIO;
+ dir_de = minix_dotdot(old_inode, &dir_page);
+ if (!dir_de)
+ goto out_old;
+ }
+
+ if (new_inode) {
+ struct page * new_page;
+ struct minix_dir_entry * new_de;
+
+ err = -ENOTEMPTY;
+ if (dir_de && !minix_empty_dir(new_inode))
+ goto out_dir;
+
+ err = -ENOENT;
+ new_de = minix_find_entry(new_dentry, &new_page);
+ if (!new_de)
+ goto out_dir;
+ minix_set_link(new_de, new_page, old_inode);
+ new_inode->i_ctime = current_time(new_inode);
+ if (dir_de)
+ drop_nlink(new_inode);
+ inode_dec_link_count(new_inode);
+ } else {
+ err = minix_add_link(new_dentry, old_inode);
+ if (err)
+ goto out_dir;
+ if (dir_de)
+ inode_inc_link_count(new_dir);
+ }
+
+ minix_delete_entry(old_de, old_page);
+ mark_inode_dirty(old_inode);
+
+ if (dir_de) {
+ minix_set_link(dir_de, dir_page, new_dir);
+ inode_dec_link_count(old_dir);
+ }
+ return 0;
+
+out_dir:
+ if (dir_de) {
+ kunmap(dir_page);
+ put_page(dir_page);
+ }
+out_old:
+ kunmap(old_page);
+ put_page(old_page);
+out:
+ return err;
+}
+
+/*
+ * directories can handle most operations...
+ */
+const struct inode_operations minix_dir_inode_operations = {
+ .create = minix_create,
+ .lookup = minix_lookup,
+ .link = minix_link,
+ .unlink = minix_unlink,
+ .symlink = minix_symlink,
+ .mkdir = minix_mkdir,
+ .rmdir = minix_rmdir,
+ .mknod = minix_mknod,
+ .rename = minix_rename,
+ .getattr = minix_getattr,
+ .tmpfile = minix_tmpfile,
+};