diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-23 13:28:57 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-23 13:28:57 -0700 |
| commit | 4352b8aee98005853aa63f57d6377282de17a33f (patch) | |
| tree | 27614d7a9ef05c32d089004e104364ce42324837 /fs/minix | |
| download | linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.tar.gz linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.zip | |
Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linuxgrafted
Pull I3C updates from Alexandre Belloni:
"A new driver this cycle, for the AMD AXI I3C controller.
There are also new features such as IBI wakeup support, SETAASA device
discovery and ACPI support for the the DAA methods, meaning we can now
communicate with the SPD devices on DDR5 modules.
New driver:
- AMD AXI I3C master controller
Subsystem:
- I3C Common Command Code (CCC) handling improvements, especially
around GET CCCs
- SETAASA device discovery support
- ACPI support for all existing DAA methods like SETDASA, SETNEWDA as
well as I2C devices on I3C bus
- IBI-based wakeup support
Drivers:
- dw: SETAASA support
- mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C
controller support, PIO queue management support for HCI v1.2
- renesas: improve suspend to RAM support, add runtime PM support"
* tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits)
i3c: dw: reduce do_daa time if there's no client
i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller
i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2
i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode
i3c: dw: rename "pclk" to "apb" to match dt-binding
i3c: dw: make struct dw_i3c_cmd smaller
i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding
i3c: renesas: Don't register devices when ENTDAA times out
i3c: master: dw-i3c-master: fix OD timing for first broadcast
i3c: mipi-i3c-hci: Advertise IBI wakeup capability
i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev()
i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI
i3c: master: Reject IBI requests from non-IBI-capable devices
i3c: master: Add helper to query bus wakeup requirements
i3c: master: Report wakeup events for IBIs
i3c: master: Support IBI-based wakeup capability
i3c: master: Fix potential UAF in i3c_device_match()
i3c: master: Fix potential UAF in i3c_device_uevent()
i3c: Make dev->desc locking assumptions explicit
i3c: master: Fix use-after-free of master->this
...
Diffstat (limited to 'fs/minix')
| -rw-r--r-- | fs/minix/Kconfig | 27 | ||||
| -rw-r--r-- | fs/minix/Makefile | 8 | ||||
| -rw-r--r-- | fs/minix/bitmap.c | 269 | ||||
| -rw-r--r-- | fs/minix/dir.c | 458 | ||||
| -rw-r--r-- | fs/minix/file.c | 54 | ||||
| -rw-r--r-- | fs/minix/inode.c | 789 | ||||
| -rw-r--r-- | fs/minix/itree_common.c | 374 | ||||
| -rw-r--r-- | fs/minix/itree_v1.c | 67 | ||||
| -rw-r--r-- | fs/minix/itree_v2.c | 75 | ||||
| -rw-r--r-- | fs/minix/minix.h | 180 | ||||
| -rw-r--r-- | fs/minix/namei.c | 291 |
11 files changed, 2592 insertions, 0 deletions
diff --git a/fs/minix/Kconfig b/fs/minix/Kconfig new file mode 100644 index 000000000..90ddfad2a --- /dev/null +++ b/fs/minix/Kconfig @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0-only +config MINIX_FS + tristate "Minix file system support" + depends on BLOCK + select BUFFER_HEAD + 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..abec43833 --- /dev/null +++ b/fs/minix/bitmap.c @@ -0,0 +1,269 @@ +// 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) +{ + 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) + return ERR_PTR(-ENOMEM); + j = bits_per_zone; + bh = NULL; + 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 ERR_PTR(-ENOSPC); + } + 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 ERR_PTR(-ENOSPC); + } + spin_unlock(&bitmap_lock); + mark_buffer_dirty(bh); + j += i * bits_per_zone; + if (!j || j > sbi->s_ninodes) { + iput(inode); + return ERR_PTR(-EFSCORRUPTED); + } + inode_init_owner(&nop_mnt_idmap, inode, dir, mode); + inode->i_ino = j; + simple_inode_init_ts(inode); + inode->i_blocks = 0; + memset(&minix_i(inode)->u, 0, sizeof(minix_i(inode)->u)); + insert_inode_hash(inode); + mark_inode_dirty(inode); + + 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..2ca16f849 --- /dev/null +++ b/fs/minix/dir.c @@ -0,0 +1,458 @@ +// 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 = simple_fsync, +}; + +/* + * 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 void dir_commit_chunk(struct folio *folio, loff_t pos, unsigned len) +{ + struct address_space *mapping = folio->mapping; + struct inode *dir = mapping->host; + + block_write_end(pos, len, len, folio); + + if (pos+len > dir->i_size) { + i_size_write(dir, pos+len); + mark_inode_dirty(dir); + } + folio_unlock(folio); +} + +static int minix_handle_dirsync(struct inode *dir) +{ + int err; + + err = filemap_write_and_wait(dir->i_mapping); + if (!err) + err = sync_inode_metadata(dir, 1); + return err; +} + +static void *dir_get_folio(struct inode *dir, unsigned long n, + struct folio **foliop) +{ + struct folio *folio = read_mapping_folio(dir->i_mapping, n, NULL); + + if (IS_ERR(folio)) + return ERR_CAST(folio); + *foliop = folio; + return kmap_local_folio(folio, 0); +} + +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 folio *folio; + + kaddr = dir_get_folio(inode, n, &folio); + if (IS_ERR(kaddr)) + continue; + 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)) { + folio_release_kmap(folio, p); + return 0; + } + } + ctx->pos += chunk_size; + } + folio_release_kmap(folio, kaddr); + } + 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 does NOT read the inode of the + * entry - you'll have to do that yourself if you want to. + * + * On Success folio_release_kmap() should be called on *foliop. + */ +minix_dirent *minix_find_entry(struct dentry *dentry, struct folio **foliop) +{ + 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); + char *p; + + char *namx; + __u32 inumber; + + for (n = 0; n < npages; n++) { + char *kaddr, *limit; + + kaddr = dir_get_folio(dir, n, foliop); + if (IS_ERR(kaddr)) + continue; + + 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; + } + folio_release_kmap(*foliop, kaddr); + } + return NULL; + +found: + 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 folio *folio = 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; + + kaddr = dir_get_folio(dir, n, &folio); + if (IS_ERR(kaddr)) + return PTR_ERR(kaddr); + folio_lock(folio); + 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; + } + folio_unlock(folio); + folio_release_kmap(folio, kaddr); + } + BUG(); + return -EINVAL; + +got_it: + pos = folio_pos(folio) + offset_in_folio(folio, p); + err = minix_prepare_chunk(folio, 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; + } + dir_commit_chunk(folio, pos, sbi->s_dirsize); + inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); + mark_inode_dirty(dir); + err = minix_handle_dirsync(dir); +out_put: + folio_release_kmap(folio, kaddr); + return err; +out_unlock: + folio_unlock(folio); + goto out_put; +} + +int minix_delete_entry(struct minix_dir_entry *de, struct folio *folio) +{ + struct inode *inode = folio->mapping->host; + loff_t pos = folio_pos(folio) + offset_in_folio(folio, de); + struct minix_sb_info *sbi = minix_sb(inode->i_sb); + unsigned len = sbi->s_dirsize; + int err; + + folio_lock(folio); + err = minix_prepare_chunk(folio, pos, len); + if (err) { + folio_unlock(folio); + return err; + } + if (sbi->s_version == MINIX_V3) + ((minix3_dirent *)de)->inode = 0; + else + de->inode = 0; + dir_commit_chunk(folio, pos, len); + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); + mark_inode_dirty(inode); + return minix_handle_dirsync(inode); +} + +int minix_make_empty(struct inode *inode, struct inode *dir) +{ + struct folio *folio = filemap_grab_folio(inode->i_mapping, 0); + struct minix_sb_info *sbi = minix_sb(inode->i_sb); + char *kaddr; + int err; + + if (IS_ERR(folio)) + return PTR_ERR(folio); + err = minix_prepare_chunk(folio, 0, 2 * sbi->s_dirsize); + if (err) { + folio_unlock(folio); + goto fail; + } + + kaddr = kmap_local_folio(folio, 0); + memset(kaddr, 0, folio_size(folio)); + + 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_local(kaddr); + + dir_commit_chunk(folio, 0, 2 * sbi->s_dirsize); + err = minix_handle_dirsync(inode); +fail: + folio_put(folio); + return err; +} + +/* + * routine to check that the specified directory is empty (for rmdir) + */ +int minix_empty_dir(struct inode * inode) +{ + struct folio *folio = NULL; + unsigned long i, npages = dir_pages(inode); + struct minix_sb_info *sbi = minix_sb(inode->i_sb); + char *name, *kaddr; + __u32 inumber; + + for (i = 0; i < npages; i++) { + char *p, *limit; + + kaddr = dir_get_folio(inode, i, &folio); + if (IS_ERR(kaddr)) + continue; + + 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; + } + } + folio_release_kmap(folio, kaddr); + } + return 1; + +not_empty: + folio_release_kmap(folio, kaddr); + return 0; +} + +/* Releases the page */ +int minix_set_link(struct minix_dir_entry *de, struct folio *folio, + struct inode *inode) +{ + struct inode *dir = folio->mapping->host; + struct minix_sb_info *sbi = minix_sb(dir->i_sb); + loff_t pos = folio_pos(folio) + offset_in_folio(folio, de); + int err; + + folio_lock(folio); + err = minix_prepare_chunk(folio, pos, sbi->s_dirsize); + if (err) { + folio_unlock(folio); + return err; + } + if (sbi->s_version == MINIX_V3) + ((minix3_dirent *)de)->inode = inode->i_ino; + else + de->inode = inode->i_ino; + dir_commit_chunk(folio, pos, sbi->s_dirsize); + inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); + mark_inode_dirty(dir); + return minix_handle_dirsync(dir); +} + +struct minix_dir_entry *minix_dotdot(struct inode *dir, struct folio **foliop) +{ + struct minix_sb_info *sbi = minix_sb(dir->i_sb); + struct minix_dir_entry *de = dir_get_folio(dir, 0, foliop); + + if (!IS_ERR(de)) + return minix_next_entry(de, sbi); + return NULL; +} + +ino_t minix_inode_by_name(struct dentry *dentry) +{ + struct folio *folio; + struct minix_dir_entry *de = minix_find_entry(dentry, &folio); + ino_t res = 0; + + if (de) { + struct inode *inode = folio->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; + folio_release_kmap(folio, de); + } + return res; +} diff --git a/fs/minix/file.c b/fs/minix/file.c new file mode 100644 index 000000000..02aabbdb5 --- /dev/null +++ b/fs/minix/file.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * linux/fs/minix/file.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + * + * minix regular file handling primitives + */ + +#include <linux/buffer_head.h> +#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_prepare = generic_file_mmap_prepare, + .fsync = simple_fsync, + .splice_read = filemap_splice_read, +}; + +static int minix_setattr(struct mnt_idmap *idmap, + struct dentry *dentry, struct iattr *attr) +{ + struct inode *inode = d_inode(dentry); + int error; + + error = setattr_prepare(&nop_mnt_idmap, 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(&nop_mnt_idmap, 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..daf83e4ff --- /dev/null +++ b/fs/minix/inode.c @@ -0,0 +1,789 @@ +// 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/mpage.h> +#include <linux/vfs.h> +#include <linux/writeback.h> +#include <linux/fs_context.h> + +static int minix_write_inode(struct inode *inode, + struct writeback_control *wbc); +static int minix_sync_inode_metadata(struct inode *inode, + struct writeback_control *wbc); +static int minix_statfs(struct dentry *dentry, struct kstatfs *buf); + +void __minix_error_inode(struct inode *inode, const char *function, + unsigned int line, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + printk(KERN_CRIT "minix-fs error (device %s): %s:%d: " + "inode #%llu: comm %s: %pV\n", + inode->i_sb->s_id, function, line, inode->i_ino, + current->comm, &vaf); + va_end(args); +} + +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); + } else { + mmb_sync(&minix_i(inode)->i_metadata_bhs); + } + mmb_invalidate(&minix_i(inode)->i_metadata_bhs); + 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; + mmb_init(&ei->i_metadata_bhs, &ei->vfs_inode.i_data); + + 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_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, + .sync_inode_metadata = minix_sync_inode_metadata, + .evict_inode = minix_evict_inode, + .put_super = minix_put_super, + .statfs = minix_statfs, +}; + +static int minix_reconfigure(struct fs_context *fc) +{ + struct minix_super_block * ms; + struct super_block *sb = fc->root->d_sb; + struct minix_sb_info * sbi = sb->s_fs_info; + + sync_filesystem(sb); + ms = sbi->s_ms; + if ((bool)(fc->sb_flags & SB_RDONLY) == sb_rdonly(sb)) + return 0; + if (fc->sb_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); + unsigned long block; + + if (sbi->s_log_zone_size != 0) { + printk("minix-fs error: zone size must equal block size. " + "s_log_zone_size > 0 is not supported.\n"); + return false; + } + + if (sbi->s_ninodes < 1 || sbi->s_firstdatazone <= 4 || + sbi->s_firstdatazone >= sbi->s_nzones) + return false; + + /* 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, sb->s_blocksize); + if (sbi->s_imap_blocks < block) { + printk("MINIX-fs: file system does not have enough " + "imap blocks allocated. Refusing to mount.\n"); + return false; + } + + block = minix_blocks_needed( + (sbi->s_nzones - sbi->s_firstdatazone + 1), + sb->s_blocksize); + if (sbi->s_zmap_blocks < block) { + printk("MINIX-fs: file system does not have enough " + "zmap blocks allocated. Refusing to mount.\n"); + 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, struct fs_context *fc) +{ + 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; + int silent = fc->sb_flags & SB_SILENT; + + sbi = kzalloc_obj(struct minix_sb_info); + 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; + if (!sb_set_blocksize(s, m3s->s_blocksize)) + goto out_release; + 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); + + /* 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_get_tree(struct fs_context *fc) +{ + return get_tree_bdev(fc, minix_fill_super); +} + +static const struct fs_context_operations minix_context_ops = { + .get_tree = minix_get_tree, + .reconfigure = minix_reconfigure, +}; + +static int minix_init_fs_context(struct fs_context *fc) +{ + fc->ops = &minix_context_ops; + + return 0; +} + +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_writepages(struct address_space *mapping, + struct writeback_control *wbc) +{ + return mpage_writepages(mapping, wbc, minix_get_block); +} + +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 folio *folio, loff_t pos, unsigned len) +{ + return __block_write_begin(folio, 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(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, 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, + .writepages = minix_writepages, + .write_begin = minix_write_begin, + .write_end = generic_write_end, + .migrate_folio = buffer_migrate_folio, + .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 if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) || + S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) { + init_special_inode(inode, inode->i_mode, rdev); + } else { + printk(KERN_DEBUG "MINIX-fs: Invalid file type 0%04o for inode %llu.\n", + inode->i_mode, inode->i_ino); + make_bad_inode(inode); + } +} + +/* + * 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: %llu\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_set_mtime_to_ts(inode, + inode_set_atime_to_ts(inode, inode_set_ctime(inode, raw_inode->i_time, 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: %llu\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_set_mtime(inode, raw_inode->i_mtime, 0); + inode_set_atime(inode, raw_inode->i_atime, 0); + inode_set_ctime(inode, raw_inode->i_ctime, 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_state_read_once(inode) & 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 int 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 -EIO; + 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_get_mtime_sec(inode); + 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); + brelse(bh); + set_inode_metadata_writeback(inode); + return 0; +} + +/* + * The minix V2 function to synchronize an inode. + */ +static int 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 -EIO; + 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_get_mtime_sec(inode); + raw_inode->i_atime = inode_get_atime_sec(inode); + raw_inode->i_ctime = inode_get_ctime_sec(inode); + 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); + brelse(bh); + set_inode_metadata_writeback(inode); + return 0; +} + +static int minix_write_inode(struct inode *inode, struct writeback_control *wbc) +{ + if (INODE_VERSION(inode) == MINIX_V1) + return V1_minix_update_inode(inode); + return V2_minix_update_inode(inode); +} + +static int minix_sync_inode_metadata(struct inode *inode, + struct writeback_control *wbc) +{ + int err = 0; + struct buffer_head *bh; + void *raw_inode; + + if (INODE_VERSION(inode) == MINIX_V1) + raw_inode = minix_V1_raw_inode(inode->i_sb, inode->i_ino, &bh); + else + raw_inode = minix_V2_raw_inode(inode->i_sb, inode->i_ino, &bh); + if (!raw_inode) + return -EIO; + err = mmb_sync(&minix_i(inode)->i_metadata_bhs); + if (err) + goto out; + sync_dirty_buffer(bh); + if (buffer_write_io_error(bh)) { + printk("IO error syncing minix inode [%s:%08llx]\n", + inode->i_sb->s_id, inode->i_ino); + err = -EIO; + } +out: + brelse(bh); + return err; +} + +int minix_getattr(struct mnt_idmap *idmap, 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(&nop_mnt_idmap, request_mask, 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 file_system_type minix_fs_type = { + .owner = THIS_MODULE, + .name = "minix", + .kill_sb = kill_block_super, + .fs_flags = FS_REQUIRES_DEV, + .init_fs_context = minix_init_fs_context, +}; +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_DESCRIPTION("Minix file system"); +MODULE_LICENSE("GPL"); + diff --git a/fs/minix/itree_common.c b/fs/minix/itree_common.c new file mode 100644 index 000000000..c3cd2c75a --- /dev/null +++ b/fs/minix/itree_common.c @@ -0,0 +1,374 @@ +// 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); + mmb_mark_buffer_dirty(bh, &minix_i(inode)->i_metadata_bhs); + 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_set_ctime_current(inode); + + /* had we spliced it onto indirect block? */ + if (where->bh) + mmb_mark_buffer_dirty(where->bh, + &minix_i(inode)->i_metadata_bhs); + + 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 + mmb_mark_buffer_dirty(partial->bh, + &minix_i(inode)->i_metadata_bhs); + 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); + mmb_mark_buffer_dirty(partial->bh, + &minix_i(inode)->i_metadata_bhs); + 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_set_mtime_to_ts(inode, inode_set_ctime_current(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..78722ce22 --- /dev/null +++ b/fs/minix/minix.h @@ -0,0 +1,180 @@ +/* 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 mapping_metadata_bhs i_metadata_bhs; + 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; +}; + +void __minix_error_inode(struct inode *inode, const char *function, + unsigned int line, const char *fmt, ...); + +struct inode *minix_iget(struct super_block *, unsigned long); +struct minix_inode *minix_V1_raw_inode(struct super_block *, ino_t, struct buffer_head **); +struct minix2_inode *minix_V2_raw_inode(struct super_block *, ino_t, struct buffer_head **); +struct inode *minix_new_inode(const struct inode *, umode_t); +void minix_free_inode(struct inode *inode); +unsigned long minix_count_free_inodes(struct super_block *sb); +int minix_new_block(struct inode *inode); +void minix_free_block(struct inode *inode, unsigned long block); +unsigned long minix_count_free_blocks(struct super_block *sb); +int minix_getattr(struct mnt_idmap *, const struct path *, + struct kstat *, u32, unsigned int); +int minix_prepare_chunk(struct folio *folio, loff_t pos, unsigned len); +struct mapping_metadata_bhs *minix_get_metadata_bhs(struct inode *inode); + +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 *); + +struct minix_dir_entry *minix_find_entry(struct dentry *, struct folio **); +int minix_add_link(struct dentry*, struct inode*); +int minix_delete_entry(struct minix_dir_entry *, struct folio *); +int minix_make_empty(struct inode*, struct inode*); +int minix_empty_dir(struct inode*); +int minix_set_link(struct minix_dir_entry *de, struct folio *folio, + struct inode *inode); +struct minix_dir_entry *minix_dotdot(struct inode*, struct folio **); +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_POW2(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 + +#define minix_error_inode(inode, fmt, ...) \ + __minix_error_inode((inode), __func__, __LINE__, \ + (fmt), ##__VA_ARGS__) + +#endif /* FS_MINIX_H */ diff --git a/fs/minix/namei.c b/fs/minix/namei.c new file mode 100644 index 000000000..5525ba367 --- /dev/null +++ b/fs/minix/namei.c @@ -0,0 +1,291 @@ +// 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 mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t rdev) +{ + struct inode *inode; + + if (!old_valid_dev(rdev)) + return -EINVAL; + + inode = minix_new_inode(dir, mode); + if (IS_ERR(inode)) + return PTR_ERR(inode); + + minix_set_inode(inode, rdev); + mark_inode_dirty(inode); + return add_nondir(dentry, inode); +} + +static int minix_tmpfile(struct mnt_idmap *idmap, struct inode *dir, + struct file *file, umode_t mode) +{ + struct inode *inode = minix_new_inode(dir, mode); + + if (IS_ERR(inode)) + return finish_open_simple(file, PTR_ERR(inode)); + minix_set_inode(inode, 0); + mark_inode_dirty(inode); + d_tmpfile(file, inode); + return finish_open_simple(file, 0); +} + +static int minix_create(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) +{ + return minix_mknod(&nop_mnt_idmap, dir, dentry, mode, 0); +} + +static int minix_symlink(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *symname) +{ + int i = strlen(symname)+1; + struct inode * inode; + int err; + + if (i > dir->i_sb->s_blocksize) + return -ENAMETOOLONG; + + inode = minix_new_inode(dir, S_IFLNK | 0777); + if (IS_ERR(inode)) + return PTR_ERR(inode); + + minix_set_inode(inode, 0); + err = page_symlink(inode, symname, i); + if (unlikely(err)) { + inode_dec_link_count(inode); + iput(inode); + return err; + } + return add_nondir(dentry, inode); +} + +static int minix_link(struct dentry * old_dentry, struct inode * dir, + struct dentry *dentry) +{ + struct inode *inode = d_inode(old_dentry); + + inode_set_ctime_current(inode); + inode_inc_link_count(inode); + ihold(inode); + return add_nondir(dentry, inode); +} + +static struct dentry *minix_mkdir(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) +{ + struct inode * inode; + int err; + + inode = minix_new_inode(dir, mode); + if (IS_ERR(inode)) + return ERR_CAST(inode); + + inode_inc_link_count(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_PTR(err); + +out_fail: + inode_dec_link_count(inode); + inode_dec_link_count(inode); + iput(inode); + inode_dec_link_count(dir); + goto out; +} + +static int minix_unlink(struct inode * dir, struct dentry *dentry) +{ + struct inode * inode = d_inode(dentry); + struct folio *folio; + struct minix_dir_entry * de; + int err; + + if (inode->i_nlink == 0) { + minix_error_inode(inode, "inode has corrupted nlink"); + return -EFSCORRUPTED; + } + + de = minix_find_entry(dentry, &folio); + if (!de) + return -ENOENT; + err = minix_delete_entry(de, folio); + folio_release_kmap(folio, de); + + if (err) + return err; + inode_set_ctime_to_ts(inode, inode_get_ctime(dir)); + inode_dec_link_count(inode); + return 0; +} + +static int minix_rmdir(struct inode * dir, struct dentry *dentry) +{ + struct inode * inode = d_inode(dentry); + int err = -EFSCORRUPTED; + + if (dir->i_nlink <= 2) { + minix_error_inode(dir, "inode has corrupted nlink"); + goto out; + } + + err = -ENOTEMPTY; + if (!minix_empty_dir(inode)) + goto out; + + err = minix_unlink(dir, dentry); + if (!err) { + inode_dec_link_count(dir); + inode_dec_link_count(inode); + } + +out: + return err; +} + +static int minix_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 = d_inode(old_dentry); + struct inode * new_inode = d_inode(new_dentry); + struct folio * dir_folio = NULL; + struct minix_dir_entry * dir_de = NULL; + struct folio *old_folio; + struct minix_dir_entry * old_de; + int err = -ENOENT; + + if (flags & ~RENAME_NOREPLACE) + return -EINVAL; + + old_de = minix_find_entry(old_dentry, &old_folio); + if (!old_de) + goto out; + + if (S_ISDIR(old_inode->i_mode)) { + err = -EIO; + dir_de = minix_dotdot(old_inode, &dir_folio); + if (!dir_de) + goto out_old; + } + + if (new_inode) { + struct folio *new_folio; + struct minix_dir_entry * new_de; + + err = -ENOTEMPTY; + if (dir_de && !minix_empty_dir(new_inode)) + goto out_dir; + + err = -EFSCORRUPTED; + if (new_inode->i_nlink == 0 || (dir_de && new_inode->i_nlink != 2)) { + minix_error_inode(new_inode, "inode has corrupted nlink"); + goto out_dir; + } + + if (dir_de && old_dir->i_nlink <= 2) { + minix_error_inode(old_dir, "inode has corrupted nlink"); + goto out_dir; + } + + err = -ENOENT; + new_de = minix_find_entry(new_dentry, &new_folio); + if (!new_de) + goto out_dir; + err = minix_set_link(new_de, new_folio, old_inode); + folio_release_kmap(new_folio, new_de); + if (err) + goto out_dir; + inode_set_ctime_current(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); + } + + err = minix_delete_entry(old_de, old_folio); + if (err) + goto out_dir; + + mark_inode_dirty(old_inode); + + if (dir_de) { + err = minix_set_link(dir_de, dir_folio, new_dir); + if (!err) + inode_dec_link_count(old_dir); + } +out_dir: + if (dir_de) + folio_release_kmap(dir_folio, dir_de); +out_old: + folio_release_kmap(old_folio, old_de); +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, +}; |
