Add the core header defining FTRFS on-disk layout and in-memory VFS structures. On-disk layout: Block 0 : superblock (magic 0x46545246, CRC32-protected) Block 1..N : inode table (128 bytes/inode, CRC32 per inode) Block N+1..end : data blocks (CRC32 per block, RS FEC planned) Structures: ftrfs_super_block : on-disk superblock ftrfs_inode : on-disk inode (12 direct + 1 indirect + 1 dindirect) ftrfs_dir_entry : on-disk directory entry (256-byte name) ftrfs_sb_info : in-memory superblock info (VFS sb->s_fs_info) ftrfs_inode_info : in-memory inode (embedded VFS inode) FTRFS targets POSIX-compatible block devices (MRAM, NOR flash, eMMC) for use in radiation-intensive environments (space applications). Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/ftrfs.h | 168 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 fs/ftrfs/ftrfs.h diff --git a/fs/ftrfs/ftrfs.h b/fs/ftrfs/ftrfs.h new file mode 100644 index 000000000..82502c9fb --- /dev/null +++ b/fs/ftrfs/ftrfs.h @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * FTRFS — Fault-Tolerant Radiation-Robust Filesystem + * Based on: Fuchs, Langer, Trinitis — ARCS 2015 + * + * Author: roastercode - Aurelien DESBRIERES + */ + +#ifndef _FTRFS_H +#define _FTRFS_H + +#include +#include +#include + +/* Magic number: 'FTRF' */ +#define FTRFS_MAGIC 0x46545246 + +/* Block size: 4096 bytes */ +#define FTRFS_BLOCK_SIZE 4096 +#define FTRFS_BLOCK_SHIFT 12 + +/* RS FEC: 16 parity bytes per 239-byte subblock (RS(255,239)) */ +#define FTRFS_RS_PARITY 16 +#define FTRFS_SUBBLOCK_DATA 239 +#define FTRFS_SUBBLOCK_TOTAL (FTRFS_SUBBLOCK_DATA + FTRFS_RS_PARITY) + +/* Filesystem limits */ +#define FTRFS_MAX_FILENAME 255 +#define FTRFS_DIRECT_BLOCKS 12 +#define FTRFS_INDIRECT_BLOCKS 1 +#define FTRFS_DINDIRECT_BLOCKS 1 + +/* + * On-disk superblock — block 0 + * Total size: fits in one 4096-byte block + */ +struct ftrfs_super_block { + __le32 s_magic; /* FTRFS_MAGIC */ + __le32 s_block_size; /* Block size in bytes */ + __le64 s_block_count; /* Total blocks */ + __le64 s_free_blocks; /* Free blocks */ + __le64 s_inode_count; /* Total inodes */ + __le64 s_free_inodes; /* Free inodes */ + __le64 s_inode_table_blk; /* Block where inode table starts */ + __le64 s_data_start_blk; /* First data block */ + __le32 s_version; /* Filesystem version */ + __le32 s_flags; /* Flags */ + __le32 s_crc32; /* CRC32 of this superblock */ + __u8 s_uuid[16]; /* UUID */ + __u8 s_label[32]; /* Volume label */ + __u8 s_pad[3948]; /* Padding to 4096 bytes */ +} __packed; + +/* + * On-disk inode + * Size: 128 bytes + */ +struct ftrfs_inode { + __le16 i_mode; /* File mode */ + __le16 i_uid; /* Owner UID */ + __le16 i_gid; /* Owner GID */ + __le16 i_nlink; /* Hard link count */ + __le64 i_size; /* File size in bytes */ + __le64 i_atime; /* Access time (ns) */ + __le64 i_mtime; /* Modification time (ns) */ + __le64 i_ctime; /* Change time (ns) */ + __le32 i_blocks; /* Block count */ + __le32 i_flags; /* Inode flags */ + __le64 i_direct[FTRFS_DIRECT_BLOCKS]; /* Direct block pointers */ + __le64 i_indirect; /* Single indirect */ + __le64 i_dindirect; /* Double indirect */ + __le32 i_crc32; /* CRC32 of inode */ + __u8 i_pad[2]; /* Padding to 128 bytes */ +} __packed; + +/* Inode flags */ +#define FTRFS_INODE_FL_RS_ENABLED 0x0001 /* RS FEC enabled */ +#define FTRFS_INODE_FL_VERIFIED 0x0002 /* Integrity verified */ + +/* + * On-disk directory entry + */ +struct ftrfs_dir_entry { + __le64 d_ino; /* Inode number */ + __le16 d_rec_len; /* Record length */ + __u8 d_name_len; /* Name length */ + __u8 d_file_type; /* File type */ + char d_name[FTRFS_MAX_FILENAME + 1]; /* Filename */ +} __packed; + +/* + * In-memory superblock info (stored in sb->s_fs_info) + */ +struct ftrfs_sb_info { + /* Block allocator */ + unsigned long *s_block_bitmap; /* In-memory free block bitmap */ + unsigned long s_nblocks; /* Number of data blocks */ + unsigned long s_data_start; /* First data block number */ + struct ftrfs_super_block *s_ftrfs_sb; /* On-disk superblock copy */ + struct buffer_head *s_sbh; /* Buffer head for superblock */ + spinlock_t s_lock; /* Superblock lock */ + unsigned long s_free_blocks; + unsigned long s_free_inodes; +}; + +/* + * In-memory inode info (embedded in VFS inode via container_of) + */ +struct ftrfs_inode_info { + __le64 i_direct[FTRFS_DIRECT_BLOCKS]; + __le64 i_indirect; + __le64 i_dindirect; + __u32 i_flags; + struct inode vfs_inode; /* Must be last */ +}; + +static inline struct ftrfs_inode_info *FTRFS_I(struct inode *inode) +{ + return container_of(inode, struct ftrfs_inode_info, vfs_inode); +} + +static inline struct ftrfs_sb_info *FTRFS_SB(struct super_block *sb) +{ + return sb->s_fs_info; +} + +/* Function prototypes */ +/* super.c */ +int ftrfs_fill_super(struct super_block *sb, struct fs_context *fc); + +/* inode.c */ +struct inode *ftrfs_iget(struct super_block *sb, unsigned long ino); +struct inode *ftrfs_new_inode(struct inode *dir, umode_t mode); + +/* dir.c */ +extern const struct file_operations ftrfs_dir_operations; +extern const struct inode_operations ftrfs_dir_inode_operations; + +/* file.c */ +extern const struct file_operations ftrfs_file_operations; +extern const struct inode_operations ftrfs_file_inode_operations; + +/* edac.c */ +__u32 ftrfs_crc32(const void *buf, size_t len); +int ftrfs_rs_encode(uint8_t *data, uint8_t *parity); +int ftrfs_rs_decode(uint8_t *data, uint8_t *parity); + +/* block.c */ + +#endif /* _FTRFS_H */ + +/* + */ + +/* alloc.c */ +int ftrfs_setup_bitmap(struct super_block *sb); +void ftrfs_destroy_bitmap(struct super_block *sb); +u64 ftrfs_alloc_block(struct super_block *sb); +void ftrfs_free_block(struct super_block *sb, u64 block); +u64 ftrfs_alloc_inode_num(struct super_block *sb); + +/* dir.c */ +struct dentry *ftrfs_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags); + +/* namei.c */ +int ftrfs_write_inode(struct inode *inode, struct writeback_control *wbc); -- 2.52.0 Implement VFS superblock operations for FTRFS: - ftrfs_fill_super(): read and validate on-disk superblock (magic, CRC32), allocate ftrfs_sb_info, read root inode, initialize in-memory free block bitmap - ftrfs_put_super(): release bitmap and buffer heads on unmount - ftrfs_statfs(): report filesystem statistics - ftrfs_write_inode(): persist inode to disk via namei.c - ftrfs_init_fs_context() / ftrfs_get_tree(): kernel 5.15+ mount API - ftrfs_free_inode(): kernel 5.9+ inode freeing API Module init/exit registers ftrfs as a filesystem type and allocates a dedicated slab cache for ftrfs_inode_info objects. Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/super.c | 274 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 fs/ftrfs/super.c diff --git a/fs/ftrfs/super.c b/fs/ftrfs/super.c new file mode 100644 index 000000000..8acc62921 --- /dev/null +++ b/fs/ftrfs/super.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTRFS — Superblock operations + * Author: roastercode - Aurelien DESBRIERES + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ftrfs.h" + +/* Inode cache (slab allocator) */ +static struct kmem_cache *ftrfs_inode_cachep; + +/* + * alloc_inode — allocate a new inode with ftrfs_inode_info embedded + */ +static struct inode *ftrfs_alloc_inode(struct super_block *sb) +{ + struct ftrfs_inode_info *fi; + + fi = kmem_cache_alloc(ftrfs_inode_cachep, GFP_KERNEL); + if (!fi) + return NULL; + + memset(fi->i_direct, 0, sizeof(fi->i_direct)); + fi->i_indirect = 0; + fi->i_dindirect = 0; + fi->i_flags = 0; + + return &fi->vfs_inode; +} + +/* + * free_inode — return inode to slab cache (kernel 5.9+ uses free_inode) + */ +static void ftrfs_free_inode(struct inode *inode) +{ + kmem_cache_free(ftrfs_inode_cachep, FTRFS_I(inode)); +} + +/* + * statfs — filesystem statistics + */ +static int ftrfs_statfs(struct dentry *dentry, struct kstatfs *buf) +{ + struct super_block *sb = dentry->d_sb; + struct ftrfs_sb_info *sbi = FTRFS_SB(sb); + + buf->f_type = FTRFS_MAGIC; + buf->f_bsize = sb->s_blocksize; + buf->f_blocks = le64_to_cpu(sbi->s_ftrfs_sb->s_block_count); + buf->f_bfree = sbi->s_free_blocks; + buf->f_bavail = sbi->s_free_blocks; + buf->f_files = le64_to_cpu(sbi->s_ftrfs_sb->s_inode_count); + buf->f_ffree = sbi->s_free_inodes; + buf->f_namelen = FTRFS_MAX_FILENAME; + + return 0; +} + +/* + * put_super — release superblock resources + */ +static void ftrfs_put_super(struct super_block *sb) +{ + struct ftrfs_sb_info *sbi = FTRFS_SB(sb); + + if (sbi) { + ftrfs_destroy_bitmap(sb); + brelse(sbi->s_sbh); + kfree(sbi->s_ftrfs_sb); + kfree(sbi); + sb->s_fs_info = NULL; + } +} + +static const struct super_operations ftrfs_super_ops = { + .alloc_inode = ftrfs_alloc_inode, + .free_inode = ftrfs_free_inode, + .put_super = ftrfs_put_super, + .write_inode = ftrfs_write_inode, + .statfs = ftrfs_statfs, +}; + +/* + * ftrfs_fill_super — read superblock from disk and initialize VFS sb + */ +int ftrfs_fill_super(struct super_block *sb, struct fs_context *fc) +{ + struct ftrfs_sb_info *sbi; + struct ftrfs_super_block *fsb; + struct buffer_head *bh; + struct inode *root_inode; + __u32 crc; + int ret = -EINVAL; + + /* Set block size */ + if (!sb_set_blocksize(sb, FTRFS_BLOCK_SIZE)) { + errorf(fc, "ftrfs: unable to set block size %d", FTRFS_BLOCK_SIZE); + return -EINVAL; + } + + /* Read block 0 — superblock */ + bh = sb_bread(sb, 0); + if (!bh) { + errorf(fc, "ftrfs: unable to read superblock"); + return -EIO; + } + + fsb = (struct ftrfs_super_block *)bh->b_data; + + /* Verify magic */ + if (le32_to_cpu(fsb->s_magic) != FTRFS_MAGIC) { + errorf(fc, "ftrfs: bad magic 0x%08x (expected 0x%08x)", + le32_to_cpu(fsb->s_magic), FTRFS_MAGIC); + goto out_brelse; + } + + /* Verify CRC32 of superblock (excluding the crc32 field itself) */ + crc = ftrfs_crc32(fsb, offsetof(struct ftrfs_super_block, s_crc32)); + if (crc != le32_to_cpu(fsb->s_crc32)) { + errorf(fc, "ftrfs: superblock CRC32 mismatch (got 0x%08x, expected 0x%08x)", + crc, le32_to_cpu(fsb->s_crc32)); + goto out_brelse; + } + + /* Allocate in-memory sb info */ + sbi = kzalloc(sizeof(*sbi), GFP_KERNEL); + if (!sbi) { + ret = -ENOMEM; + goto out_brelse; + } + + sbi->s_ftrfs_sb = kzalloc(sizeof(*sbi->s_ftrfs_sb), GFP_KERNEL); + if (!sbi->s_ftrfs_sb) { + ret = -ENOMEM; + goto out_free_sbi; + } + + memcpy(sbi->s_ftrfs_sb, fsb, sizeof(*fsb)); + sbi->s_sbh = bh; + sbi->s_free_blocks = le64_to_cpu(fsb->s_free_blocks); + sbi->s_free_inodes = le64_to_cpu(fsb->s_free_inodes); + spin_lock_init(&sbi->s_lock); + + sb->s_fs_info = sbi; + sb->s_magic = FTRFS_MAGIC; + sb->s_op = &ftrfs_super_ops; + sb->s_maxbytes = MAX_LFS_FILESIZE; + + /* Read root inode (inode 1) */ + root_inode = ftrfs_iget(sb, 1); + if (IS_ERR(root_inode)) { + ret = PTR_ERR(root_inode); + pr_err("ftrfs: failed to read root inode: %d\n", ret); + goto out_free_fsb; + } + + sb->s_root = d_make_root(root_inode); + if (!sb->s_root) { + ret = -ENOMEM; + goto out_free_fsb; + } + + if (ftrfs_setup_bitmap(sb)) { + ret = -ENOMEM; + goto out_free_fsb; + } + + pr_info("ftrfs: mounted (blocks=%llu free=%lu inodes=%llu)\n", + le64_to_cpu(fsb->s_block_count), + sbi->s_free_blocks, + le64_to_cpu(fsb->s_inode_count)); + + return 0; + +out_free_fsb: + kfree(sbi->s_ftrfs_sb); +out_free_sbi: + kfree(sbi); + sb->s_fs_info = NULL; +out_brelse: + brelse(bh); + return ret; +} + +/* + * fs_context ops — kernel 5.15+ mount API + */ +static int ftrfs_get_tree(struct fs_context *fc) +{ + return get_tree_bdev(fc, ftrfs_fill_super); +} + +static const struct fs_context_operations ftrfs_context_ops = { + .get_tree = ftrfs_get_tree, +}; + +static int ftrfs_init_fs_context(struct fs_context *fc) +{ + fc->ops = &ftrfs_context_ops; + return 0; +} + +static struct file_system_type ftrfs_fs_type = { + .owner = THIS_MODULE, + .name = "ftrfs", + .init_fs_context = ftrfs_init_fs_context, + .kill_sb = kill_block_super, + .fs_flags = FS_REQUIRES_DEV, +}; + +/* + * Inode cache constructor + */ +static void ftrfs_inode_init_once(void *obj) +{ + struct ftrfs_inode_info *fi = obj; + + inode_init_once(&fi->vfs_inode); +} + +/* + * Module init / exit + */ +static int __init ftrfs_init(void) +{ + int ret; + + ftrfs_inode_cachep = kmem_cache_create( + "ftrfs_inode_cache", + sizeof(struct ftrfs_inode_info), + 0, + SLAB_RECLAIM_ACCOUNT | SLAB_ACCOUNT, + ftrfs_inode_init_once); + + if (!ftrfs_inode_cachep) { + pr_err("ftrfs: failed to create inode cache\n"); + return -ENOMEM; + } + + ret = register_filesystem(&ftrfs_fs_type); + if (ret) { + pr_err("ftrfs: failed to register filesystem: %d\n", ret); + kmem_cache_destroy(ftrfs_inode_cachep); + return ret; + } + + pr_info("ftrfs: module loaded (FTRFS Fault-Tolerant Radiation-Robust FS)\n"); + return 0; +} + +static void __exit ftrfs_exit(void) +{ + unregister_filesystem(&ftrfs_fs_type); + rcu_barrier(); + kmem_cache_destroy(ftrfs_inode_cachep); + pr_info("ftrfs: module unloaded\n"); +} + +module_init(ftrfs_init); +module_exit(ftrfs_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("roastercode - Aurelien DESBRIERES "); +MODULE_DESCRIPTION("FTRFS: Fault-Tolerant Radiation-Robust Filesystem"); +MODULE_VERSION("0.1.0"); +MODULE_ALIAS_FS("ftrfs"); -- 2.52.0 Implement ftrfs_iget() to read inodes from the on-disk inode table: - Locate inode block from s_inode_table_blk and inode number - Read raw ftrfs_inode via sb_bread() - Verify per-inode CRC32 checksum - Populate VFS inode fields (mode, uid, gid, size, timestamps) - Copy direct/indirect block pointers to ftrfs_inode_info - Assign inode_operations and file_operations based on file type - Use inode_state_read_once() for I_NEW test (kernel 7.0 API) Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/inode.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 fs/ftrfs/inode.c diff --git a/fs/ftrfs/inode.c b/fs/ftrfs/inode.c new file mode 100644 index 000000000..e1279c796 --- /dev/null +++ b/fs/ftrfs/inode.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTRFS — Inode operations + * Author: roastercode - Aurelien DESBRIERES + */ + +#include +#include +#include +#include +#include "ftrfs.h" + +/* + * ftrfs_iget — read inode from disk into VFS + * @sb: superblock + * @ino: inode number (1-based) + * + * Inode table starts at s_inode_table_blk. + * Each block holds FTRFS_BLOCK_SIZE / sizeof(ftrfs_inode) inodes. + */ +struct inode *ftrfs_iget(struct super_block *sb, unsigned long ino) +{ + struct ftrfs_sb_info *sbi = FTRFS_SB(sb); + struct ftrfs_inode_info *fi; + struct ftrfs_inode *raw; + struct buffer_head *bh; + struct inode *inode; + unsigned long inodes_per_block; + unsigned long block, offset; + __u32 crc; + + inode = iget_locked(sb, ino); + if (!inode) + return ERR_PTR(-ENOMEM); + + /* Already in cache */ + if (!(inode_state_read_once(inode) & I_NEW)) + return inode; + + inodes_per_block = FTRFS_BLOCK_SIZE / sizeof(struct ftrfs_inode); + block = le64_to_cpu(sbi->s_ftrfs_sb->s_inode_table_blk) + + (ino - 1) / inodes_per_block; + offset = (ino - 1) % inodes_per_block; + + bh = sb_bread(sb, block); + if (!bh) { + pr_err("ftrfs: unable to read inode block %lu\n", block); + iget_failed(inode); + return ERR_PTR(-EIO); + } + + raw = (struct ftrfs_inode *)bh->b_data + offset; + /* Verify inode CRC32 */ + crc = ftrfs_crc32(raw, offsetof(struct ftrfs_inode, i_crc32)); + if (crc != le32_to_cpu(raw->i_crc32)) { + pr_err("ftrfs: inode %lu CRC32 mismatch\n", ino); + brelse(bh); + iget_failed(inode); + return ERR_PTR(-EIO); + } + + fi = FTRFS_I(inode); + + /* Populate VFS inode */ + inode->i_mode = le16_to_cpu(raw->i_mode); + inode->i_uid = make_kuid(sb->s_user_ns, le16_to_cpu(raw->i_uid)); + inode->i_gid = make_kgid(sb->s_user_ns, le16_to_cpu(raw->i_gid)); + set_nlink(inode, le16_to_cpu(raw->i_nlink)); + inode->i_size = le64_to_cpu(raw->i_size); + inode->i_blocks = le32_to_cpu(raw->i_blocks); + + inode_set_atime(inode, + le64_to_cpu(raw->i_atime) / NSEC_PER_SEC, + le64_to_cpu(raw->i_atime) % NSEC_PER_SEC); + inode_set_mtime(inode, + le64_to_cpu(raw->i_mtime) / NSEC_PER_SEC, + le64_to_cpu(raw->i_mtime) % NSEC_PER_SEC); + inode_set_ctime(inode, + le64_to_cpu(raw->i_ctime) / NSEC_PER_SEC, + le64_to_cpu(raw->i_ctime) % NSEC_PER_SEC); + + /* Copy block pointers to in-memory inode */ + memcpy(fi->i_direct, raw->i_direct, sizeof(fi->i_direct)); + fi->i_indirect = raw->i_indirect; + fi->i_dindirect = raw->i_dindirect; + fi->i_flags = le32_to_cpu(raw->i_flags); + + /* Set ops based on file type */ + if (S_ISDIR(inode->i_mode)) { + inode->i_op = &ftrfs_dir_inode_operations; + inode->i_fop = &ftrfs_dir_operations; + } else if (S_ISREG(inode->i_mode)) { + inode->i_op = &ftrfs_file_inode_operations; + inode->i_fop = &ftrfs_file_operations; + } else { + /* Special files: use generic */ + init_special_inode(inode, inode->i_mode, 0); + } + + brelse(bh); + unlock_new_inode(inode); + return inode; +} -- 2.52.0 Implement directory iteration and lookup: - ftrfs_readdir(): iterate directory entries from direct blocks, emit via dir_emit_dots() then dir_emit() for real entries, use ctx->pos as entry index with INT_MAX as EOF sentinel - ftrfs_lookup(): search directory blocks for a matching name, call ftrfs_iget() on match and return via d_splice_alias() Both functions scan ftrfs_dir_entry records in direct block pointers only (no indirect block support yet). Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/dir.c | 132 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 fs/ftrfs/dir.c diff --git a/fs/ftrfs/dir.c b/fs/ftrfs/dir.c new file mode 100644 index 000000000..dbf0102a4 --- /dev/null +++ b/fs/ftrfs/dir.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTRFS — Directory operations + * Author: roastercode - Aurelien DESBRIERES + */ + +#include +#include +#include "ftrfs.h" + +/* + * ftrfs_readdir — iterate directory entries + */ +static int ftrfs_readdir(struct file *file, struct dir_context *ctx) +{ + struct inode *inode = file_inode(file); + struct super_block *sb = inode->i_sb; + struct ftrfs_inode_info *fi = FTRFS_I(inode); + struct buffer_head *bh; + struct ftrfs_dir_entry *de; + unsigned long block_idx, block_no; + unsigned int offset; + + /* EOF guard */ + if (ctx->pos == INT_MAX) + return 0; + /* Emit . and .. (ctx->pos: 0=., 1=.., 2+=real entries) */ + if (ctx->pos < 2) { + if (!dir_emit_dots(file, ctx)) + return 0; + } + + /* Iterate over direct blocks only (skeleton: no indirect yet) */ + for (block_idx = 0; block_idx < FTRFS_DIRECT_BLOCKS; block_idx++) { + block_no = le64_to_cpu(fi->i_direct[block_idx]); + if (!block_no) + break; + + bh = sb_bread(sb, block_no); + if (!bh) + continue; + + offset = 0; + while (offset < FTRFS_BLOCK_SIZE) { + de = (struct ftrfs_dir_entry *)(bh->b_data + offset); + + if (!de->d_rec_len) + break; /* end of dir block */ + + if (de->d_ino && de->d_name_len) { + if (!dir_emit(ctx, + de->d_name, + de->d_name_len, + le64_to_cpu(de->d_ino), + de->d_file_type)) { + brelse(bh); + return 0; + } + ctx->pos++; + } + + offset += le16_to_cpu(de->d_rec_len); + } + + brelse(bh); + } + + ctx->pos = INT_MAX; + return 0; +} + +/* + * ftrfs_lookup — find dentry in directory + */ +struct dentry *ftrfs_lookup(struct inode *dir, + struct dentry *dentry, + unsigned int flags) +{ + struct super_block *sb = dir->i_sb; + struct ftrfs_inode_info *fi = FTRFS_I(dir); + struct buffer_head *bh; + struct ftrfs_dir_entry *de; + struct inode *inode = NULL; + unsigned long block_idx, block_no; + unsigned int offset; + + if (dentry->d_name.len > FTRFS_MAX_FILENAME) + return ERR_PTR(-ENAMETOOLONG); + + for (block_idx = 0; block_idx < FTRFS_DIRECT_BLOCKS; block_idx++) { + block_no = le64_to_cpu(fi->i_direct[block_idx]); + if (!block_no) + break; + + bh = sb_bread(sb, block_no); + if (!bh) + continue; + + offset = 0; + while (offset < FTRFS_BLOCK_SIZE) { + de = (struct ftrfs_dir_entry *)(bh->b_data + offset); + + if (!de->d_rec_len) + break; /* end of dir block */ + + if (de->d_ino && + de->d_name_len == dentry->d_name.len && + !memcmp(de->d_name, dentry->d_name.name, + de->d_name_len)) { + unsigned long ino = le64_to_cpu(de->d_ino); + + brelse(bh); + inode = ftrfs_iget(sb, ino); + goto found; + } + + offset += le16_to_cpu(de->d_rec_len); + } + brelse(bh); + } + +found: + return d_splice_alias(inode, dentry); +} + +const struct file_operations ftrfs_dir_operations = { + .llseek = generic_file_llseek, + .read = generic_read_dir, + .iterate_shared = ftrfs_readdir, +}; + + -- 2.52.0 Implement basic file read/write using generic VFS helpers: - ftrfs_file_operations: generic_file_read_iter, generic_file_write_iter - ftrfs_file_inode_operations: getattr via simple_getattr - ftrfs_address_space_operations: readpage via block_read_full_folio, writepage via block_write_full_folio, bmap via generic_block_bmap Read path delegates to the page cache via generic helpers. Write path is wired but depends on the block allocator (alloc.c) and write_inode (namei.c) for persistence. Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/file.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 fs/ftrfs/file.c diff --git a/fs/ftrfs/file.c b/fs/ftrfs/file.c new file mode 100644 index 000000000..ef121359b --- /dev/null +++ b/fs/ftrfs/file.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTRFS — File operations (skeleton) + * Author: roastercode - Aurelien DESBRIERES + * + * NOTE: read/write use generic_file_* for now. + * The EDAC/RS layer will intercept at the block I/O level (next iteration). + */ + +#include +#include +#include "ftrfs.h" + +const struct file_operations ftrfs_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 = filemap_splice_read, +}; + +const struct inode_operations ftrfs_file_inode_operations = { + .getattr = simple_getattr, +}; -- 2.52.0 Implement in-memory bitmap allocator for blocks and inodes: - ftrfs_setup_bitmap(): allocate and initialize the free block bitmap from superblock s_free_blocks count at mount time - ftrfs_destroy_bitmap(): release bitmap at umount - ftrfs_alloc_block(): find-first-bit allocator, updates on-disk superblock s_free_blocks counter via mark_buffer_dirty() - ftrfs_free_block(): return block to pool, double-free detection - ftrfs_alloc_inode_num(): linear scan of inode table for a free slot (i_mode == 0), updates s_free_inodes counter The bitmap is loaded from the superblock free block count at mount and persisted incrementally on each allocation/free. A dedicated on-disk bitmap block is planned for a future revision. Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/alloc.c | 251 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 fs/ftrfs/alloc.c diff --git a/fs/ftrfs/alloc.c b/fs/ftrfs/alloc.c new file mode 100644 index 000000000..753eb67cf --- /dev/null +++ b/fs/ftrfs/alloc.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTRFS — Block and inode allocator + * Author: Aurélien DESBRIERES + * + * Simple bitmap allocator. The free block bitmap is stored in-memory + * (loaded at mount time) and persisted to disk on each allocation/free. + * + * Layout assumption (from mkfs.ftrfs): + * Block 0 : superblock + * Block 1..N : inode table + * Block N+1 : root dir data + * Block N+2..end : data blocks + * + * The bitmap itself is stored in the first data block after the inode + * table. Each bit represents one data block (1 = free, 0 = used). + */ + +#include +#include +#include +#include +#include "ftrfs.h" + +/* + * ftrfs_setup_bitmap — allocate and initialize the in-memory block bitmap + * Called from ftrfs_fill_super() after the superblock is read. + * + * For the skeleton we use a simple in-memory bitmap initialized from + * s_free_blocks. A full implementation would read the on-disk bitmap block. + */ +int ftrfs_setup_bitmap(struct super_block *sb) +{ + struct ftrfs_sb_info *sbi = FTRFS_SB(sb); + unsigned long total_blocks; + unsigned long data_start; + + total_blocks = le64_to_cpu(sbi->s_ftrfs_sb->s_block_count); + data_start = le64_to_cpu(sbi->s_ftrfs_sb->s_data_start_blk); + + if (total_blocks <= data_start) { + pr_err("ftrfs: invalid block layout (total=%lu data_start=%lu)\n", + total_blocks, data_start); + return -EINVAL; + } + + sbi->s_nblocks = total_blocks - data_start; + sbi->s_data_start = data_start; + + /* Allocate bitmap: one bit per data block */ + sbi->s_block_bitmap = bitmap_zalloc(sbi->s_nblocks, GFP_KERNEL); + if (!sbi->s_block_bitmap) + return -ENOMEM; + + /* + * Mark all blocks as free initially. + * A full implementation would read the on-disk bitmap here. + * For now we derive free blocks from s_free_blocks in the superblock. + */ + bitmap_fill(sbi->s_block_bitmap, sbi->s_nblocks); + + /* + * Mark blocks already used (total - free) as allocated. + * We mark from block 0 of the data area upward. + */ + { + unsigned long used = sbi->s_nblocks - sbi->s_free_blocks; + unsigned long i; + + for (i = 0; i < used && i < sbi->s_nblocks; i++) + clear_bit(i, sbi->s_block_bitmap); + } + + pr_info("ftrfs: bitmap initialized (%lu data blocks, %lu free)\n", + sbi->s_nblocks, sbi->s_free_blocks); + + return 0; +} + +/* + * ftrfs_destroy_bitmap — free the in-memory bitmap + * Called from ftrfs_put_super(). + */ +void ftrfs_destroy_bitmap(struct super_block *sb) +{ + struct ftrfs_sb_info *sbi = FTRFS_SB(sb); + + if (sbi->s_block_bitmap) { + bitmap_free(sbi->s_block_bitmap); + sbi->s_block_bitmap = NULL; + } +} + +/* + * ftrfs_alloc_block — allocate a free data block + * @sb: superblock + * + * Returns the absolute block number (>= s_data_start) on success, + * or 0 on failure (0 is the superblock, never a valid data block). + * + * Caller must hold sbi->s_lock. + */ +u64 ftrfs_alloc_block(struct super_block *sb) +{ + struct ftrfs_sb_info *sbi = FTRFS_SB(sb); + unsigned long bit; + + if (!sbi->s_block_bitmap) { + pr_err("ftrfs: bitmap not initialized\n"); + return 0; + } + + spin_lock(&sbi->s_lock); + + if (sbi->s_free_blocks == 0) { + spin_unlock(&sbi->s_lock); + pr_warn("ftrfs: no free blocks\n"); + return 0; + } + + /* Find first free bit (set = free in our convention) */ + bit = find_first_bit(sbi->s_block_bitmap, sbi->s_nblocks); + if (bit >= sbi->s_nblocks) { + spin_unlock(&sbi->s_lock); + pr_err("ftrfs: bitmap inconsistency (free_blocks=%lu but no free bit)\n", + sbi->s_free_blocks); + return 0; + } + + /* Mark as used */ + clear_bit(bit, sbi->s_block_bitmap); + sbi->s_free_blocks--; + + /* Update on-disk superblock counter */ + sbi->s_ftrfs_sb->s_free_blocks = cpu_to_le64(sbi->s_free_blocks); + mark_buffer_dirty(sbi->s_sbh); + + spin_unlock(&sbi->s_lock); + + /* Return absolute block number */ + return (u64)(sbi->s_data_start + bit); +} + +/* + * ftrfs_free_block — release a data block back to the free pool + * @sb: superblock + * @block: absolute block number to free + */ +void ftrfs_free_block(struct super_block *sb, u64 block) +{ + struct ftrfs_sb_info *sbi = FTRFS_SB(sb); + unsigned long bit; + + if (block < sbi->s_data_start) { + pr_err("ftrfs: attempt to free non-data block %llu\n", block); + return; + } + + bit = (unsigned long)(block - sbi->s_data_start); + + if (bit >= sbi->s_nblocks) { + pr_err("ftrfs: block %llu out of range\n", block); + return; + } + + spin_lock(&sbi->s_lock); + + if (test_bit(bit, sbi->s_block_bitmap)) { + pr_warn("ftrfs: double free of block %llu\n", block); + spin_unlock(&sbi->s_lock); + return; + } + + set_bit(bit, sbi->s_block_bitmap); + sbi->s_free_blocks++; + + /* Update on-disk superblock counter */ + sbi->s_ftrfs_sb->s_free_blocks = cpu_to_le64(sbi->s_free_blocks); + mark_buffer_dirty(sbi->s_sbh); + + spin_unlock(&sbi->s_lock); +} + +/* + * ftrfs_alloc_inode_num — allocate a free inode number + * @sb: superblock + * + * Returns inode number >= 2 on success (1 = root, reserved), + * or 0 on failure. + * + * Simple linear scan of the inode table for a free slot. + * A full implementation uses an inode bitmap block. + */ +u64 ftrfs_alloc_inode_num(struct super_block *sb) +{ + struct ftrfs_sb_info *sbi = FTRFS_SB(sb); + struct ftrfs_inode *raw; + struct buffer_head *bh; + unsigned long inodes_per_block; + unsigned long inode_table_blk; + unsigned long total_inodes; + unsigned long block, i; + u64 ino = 0; + + inodes_per_block = FTRFS_BLOCK_SIZE / sizeof(struct ftrfs_inode); + inode_table_blk = le64_to_cpu(sbi->s_ftrfs_sb->s_inode_table_blk); + total_inodes = le64_to_cpu(sbi->s_ftrfs_sb->s_inode_count); + + spin_lock(&sbi->s_lock); + + if (sbi->s_free_inodes == 0) { + spin_unlock(&sbi->s_lock); + return 0; + } + + /* Scan inode table blocks looking for a free inode (i_mode == 0) */ + for (block = 0; block * inodes_per_block < total_inodes; block++) { + bh = sb_bread(sb, inode_table_blk + block); + if (!bh) + continue; + + raw = (struct ftrfs_inode *)bh->b_data; + + for (i = 0; i < inodes_per_block; i++) { + unsigned long ino_num = block * inodes_per_block + i + 1; + + if (ino_num > total_inodes) + break; + + /* inode 1 = root, always reserved */ + if (ino_num == 1) + continue; + + if (le16_to_cpu(raw[i].i_mode) == 0) { + /* Found a free inode slot */ + ino = (u64)ino_num; + sbi->s_free_inodes--; + sbi->s_ftrfs_sb->s_free_inodes = + cpu_to_le64(sbi->s_free_inodes); + mark_buffer_dirty(sbi->s_sbh); + brelse(bh); + goto found; + } + } + brelse(bh); + } + +found: + spin_unlock(&sbi->s_lock); + return ino; +} -- 2.52.0 Implement VFS inode_operations for directories and write path: - ftrfs_create(): allocate inode, write to disk, add dir entry - ftrfs_mkdir(): create directory with . and .. entries - ftrfs_unlink(): remove directory entry, decrement link count - ftrfs_rmdir(): remove empty directory - ftrfs_link(): create hard link - ftrfs_write_inode(): VFS super_op, persist inode via sb_bread/ mark_buffer_dirty with CRC32 update - ftrfs_new_inode(): allocate and initialize a new VFS inode ftrfs_mkdir() returns struct dentry * as required by kernel 7.0 inode_operations.mkdir API change. Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/namei.c | 428 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 428 insertions(+) create mode 100644 fs/ftrfs/namei.c diff --git a/fs/ftrfs/namei.c b/fs/ftrfs/namei.c new file mode 100644 index 000000000..a8c1f79eb --- /dev/null +++ b/fs/ftrfs/namei.c @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTRFS — Filename / directory entry operations + * Author: Aurélien DESBRIERES + * + * Implements: create, mkdir, unlink, rmdir, link, rename + */ + +#include +#include +#include +#include +#include "ftrfs.h" + +/* ------------------------------------------------------------------ */ +/* Helper: write a raw ftrfs_inode to disk */ +/* ------------------------------------------------------------------ */ + +static int ftrfs_write_inode_raw(struct inode *inode) +{ + struct super_block *sb = inode->i_sb; + struct ftrfs_sb_info *sbi = FTRFS_SB(sb); + struct ftrfs_inode_info *fi = FTRFS_I(inode); + struct ftrfs_inode *raw; + struct buffer_head *bh; + unsigned long inodes_per_block; + unsigned long block, offset; + + inodes_per_block = FTRFS_BLOCK_SIZE / sizeof(struct ftrfs_inode); + block = le64_to_cpu(sbi->s_ftrfs_sb->s_inode_table_blk) + + (inode->i_ino - 1) / inodes_per_block; + offset = (inode->i_ino - 1) % inodes_per_block; + + bh = sb_bread(sb, block); + if (!bh) + return -EIO; + + raw = (struct ftrfs_inode *)bh->b_data + offset; + + raw->i_mode = cpu_to_le16(inode->i_mode); + raw->i_uid = cpu_to_le16(i_uid_read(inode)); + raw->i_gid = cpu_to_le16(i_gid_read(inode)); + raw->i_nlink = cpu_to_le16(inode->i_nlink); + raw->i_size = cpu_to_le64(inode->i_size); + raw->i_blocks = cpu_to_le32(inode->i_blocks); + raw->i_atime = cpu_to_le64(inode_get_atime_sec(inode) * NSEC_PER_SEC + + inode_get_atime_nsec(inode)); + raw->i_mtime = cpu_to_le64(inode_get_mtime_sec(inode) * NSEC_PER_SEC + + inode_get_mtime_nsec(inode)); + raw->i_ctime = cpu_to_le64(inode_get_ctime_sec(inode) * NSEC_PER_SEC + + inode_get_ctime_nsec(inode)); + raw->i_flags = cpu_to_le32(fi->i_flags); + + memcpy(raw->i_direct, fi->i_direct, sizeof(fi->i_direct)); + raw->i_indirect = fi->i_indirect; + raw->i_dindirect = fi->i_dindirect; + + raw->i_crc32 = ftrfs_crc32(raw, + offsetof(struct ftrfs_inode, i_crc32)); + + mark_buffer_dirty(bh); + brelse(bh); + + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Helper: add a directory entry to a directory inode */ +/* ------------------------------------------------------------------ */ + +static int ftrfs_add_dirent(struct inode *dir, const struct qstr *name, + u64 ino, unsigned int file_type) +{ + struct super_block *sb = dir->i_sb; + struct ftrfs_inode_info *fi = FTRFS_I(dir); + struct ftrfs_dir_entry *de; + struct buffer_head *bh; + unsigned int offset; + u64 block_no; + int i; + + /* Look for space in existing direct blocks */ + for (i = 0; i < FTRFS_DIRECT_BLOCKS; i++) { + block_no = le64_to_cpu(fi->i_direct[i]); + if (!block_no) + break; + + bh = sb_bread(sb, block_no); + if (!bh) + return -EIO; + + offset = 0; + while (offset + sizeof(*de) <= FTRFS_BLOCK_SIZE) { + de = (struct ftrfs_dir_entry *)(bh->b_data + offset); + + /* Free slot: ino == 0 */ + if (!de->d_ino) { + de->d_ino = cpu_to_le64(ino); + de->d_name_len = name->len; + de->d_file_type = file_type; + de->d_rec_len = cpu_to_le16( + sizeof(struct ftrfs_dir_entry)); + memcpy(de->d_name, name->name, name->len); + de->d_name[name->len] = '\0'; + mark_buffer_dirty(bh); + brelse(bh); + inode_set_mtime_to_ts(dir, + current_time(dir)); + mark_inode_dirty(dir); + return 0; + } + offset += le16_to_cpu(de->d_rec_len); + if (!de->d_rec_len) + break; + } + brelse(bh); + } + + /* Need a new block */ + if (i >= FTRFS_DIRECT_BLOCKS) + return -ENOSPC; + + block_no = ftrfs_alloc_block(sb); + if (!block_no) + return -ENOSPC; + + bh = sb_bread(sb, block_no); + if (!bh) { + ftrfs_free_block(sb, block_no); + return -EIO; + } + + memset(bh->b_data, 0, FTRFS_BLOCK_SIZE); + + de = (struct ftrfs_dir_entry *)bh->b_data; + de->d_ino = cpu_to_le64(ino); + de->d_name_len = name->len; + de->d_file_type = file_type; + de->d_rec_len = cpu_to_le16(sizeof(struct ftrfs_dir_entry)); + memcpy(de->d_name, name->name, name->len); + de->d_name[name->len] = '\0'; + + mark_buffer_dirty(bh); + brelse(bh); + + fi->i_direct[i] = cpu_to_le64(block_no); + dir->i_size += FTRFS_BLOCK_SIZE; + dir->i_blocks++; + inode_set_mtime_to_ts(dir, current_time(dir)); + mark_inode_dirty(dir); + + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Helper: remove a directory entry from a directory */ +/* ------------------------------------------------------------------ */ + +static int ftrfs_del_dirent(struct inode *dir, const struct qstr *name) +{ + struct super_block *sb = dir->i_sb; + struct ftrfs_inode_info *fi = FTRFS_I(dir); + struct ftrfs_dir_entry *de; + struct buffer_head *bh; + unsigned int offset; + u64 block_no; + int i; + + for (i = 0; i < FTRFS_DIRECT_BLOCKS; i++) { + block_no = le64_to_cpu(fi->i_direct[i]); + if (!block_no) + break; + + bh = sb_bread(sb, block_no); + if (!bh) + return -EIO; + + offset = 0; + while (offset + sizeof(*de) <= FTRFS_BLOCK_SIZE) { + de = (struct ftrfs_dir_entry *)(bh->b_data + offset); + + if (de->d_ino && + de->d_name_len == name->len && + !memcmp(de->d_name, name->name, name->len)) { + /* Zero out the entry (mark as free) */ + memset(de, 0, sizeof(*de)); + mark_buffer_dirty(bh); + brelse(bh); + inode_set_mtime_to_ts(dir, + current_time(dir)); + mark_inode_dirty(dir); + return 0; + } + + if (!de->d_rec_len) + break; + offset += le16_to_cpu(de->d_rec_len); + } + brelse(bh); + } + + return -ENOENT; +} + +/* ------------------------------------------------------------------ */ +/* Helper: allocate and initialize a new VFS inode */ +/* ------------------------------------------------------------------ */ + +struct inode *ftrfs_new_inode(struct inode *dir, umode_t mode) +{ + struct super_block *sb = dir->i_sb; + struct inode *inode; + struct ftrfs_inode_info *fi; + u64 ino; + + ino = ftrfs_alloc_inode_num(sb); + if (!ino) + return ERR_PTR(-ENOSPC); + + inode = new_inode(sb); + if (!inode) + return ERR_PTR(-ENOMEM); + + inode_init_owner(&nop_mnt_idmap, inode, dir, mode); + inode->i_ino = ino; + inode->i_blocks = 0; + inode->i_size = 0; + inode_set_atime_to_ts(inode, current_time(inode)); + inode_set_mtime_to_ts(inode, current_time(inode)); + inode_set_ctime_to_ts(inode, current_time(inode)); + + fi = FTRFS_I(inode); + memset(fi->i_direct, 0, sizeof(fi->i_direct)); + fi->i_indirect = 0; + fi->i_dindirect = 0; + fi->i_flags = 0; + + if (S_ISDIR(mode)) { + inode->i_op = &ftrfs_dir_inode_operations; + inode->i_fop = &ftrfs_dir_operations; + set_nlink(inode, 2); + } else { + inode->i_op = &ftrfs_file_inode_operations; + inode->i_fop = &ftrfs_file_operations; + set_nlink(inode, 1); + } + + insert_inode_hash(inode); + mark_inode_dirty(inode); + return ERR_CAST(inode); +} + +/* ------------------------------------------------------------------ */ +/* create — create a regular file */ +/* ------------------------------------------------------------------ */ + +static int ftrfs_create(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, bool excl) +{ + struct inode *inode; + int ret; + + inode = ftrfs_new_inode(dir, mode | S_IFREG); + if (IS_ERR(inode)) + return PTR_ERR(inode); + + ret = ftrfs_write_inode_raw(inode); + if (ret) + goto out_iput; + + ret = ftrfs_add_dirent(dir, &dentry->d_name, inode->i_ino, 1 /* DT_REG */); + if (ret) + goto out_iput; + + ret = ftrfs_write_inode_raw(dir); + if (ret) + goto out_iput; + + d_instantiate(dentry, inode); + return 0; + +out_iput: + iput(inode); + return ret; +} + +/* ------------------------------------------------------------------ */ +/* mkdir — create a directory */ +/* ------------------------------------------------------------------ */ + +static struct dentry *ftrfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) +{ + struct inode *inode; + int ret; + + inode_inc_link_count(dir); + + inode = ftrfs_new_inode(dir, mode | S_IFDIR); + if (IS_ERR(inode)) { + inode_dec_link_count(dir); + return ERR_CAST(inode); + } + + /* Add . and .. entries */ + ret = ftrfs_add_dirent(inode, &(struct qstr)QSTR_INIT(".", 1), + inode->i_ino, 4 /* DT_DIR */); + if (ret) + goto out_fail; + + ret = ftrfs_add_dirent(inode, &(struct qstr)QSTR_INIT("..", 2), + dir->i_ino, 4 /* DT_DIR */); + if (ret) + goto out_fail; + + ret = ftrfs_write_inode_raw(inode); + if (ret) + goto out_fail; + + ret = ftrfs_add_dirent(dir, &dentry->d_name, inode->i_ino, + 4 /* DT_DIR */); + if (ret) + goto out_fail; + + ret = ftrfs_write_inode_raw(dir); + if (ret) + goto out_fail; + + d_instantiate(dentry, inode); + return NULL; + +out_fail: + inode_dec_link_count(inode); + inode_dec_link_count(inode); + iput(inode); + inode_dec_link_count(dir); + return ERR_PTR(ret); +} + +/* ------------------------------------------------------------------ */ +/* unlink — remove a file */ +/* ------------------------------------------------------------------ */ + +static int ftrfs_unlink(struct inode *dir, struct dentry *dentry) +{ + struct inode *inode = d_inode(dentry); + int ret; + + ret = ftrfs_del_dirent(dir, &dentry->d_name); + if (ret) + return ret; + + inode_set_ctime_to_ts(inode, current_time(inode)); + inode_dec_link_count(inode); + ftrfs_write_inode_raw(dir); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* rmdir — remove an empty directory */ +/* ------------------------------------------------------------------ */ + +static int ftrfs_rmdir(struct inode *dir, struct dentry *dentry) +{ + struct inode *inode = d_inode(dentry); + int ret; + + if (inode->i_nlink > 2) + return -ENOTEMPTY; + + ret = ftrfs_del_dirent(dir, &dentry->d_name); + if (ret) + return ret; + + inode_dec_link_count(inode); + inode_dec_link_count(inode); + inode_dec_link_count(dir); + ftrfs_write_inode_raw(dir); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* link — create a hard link */ +/* ------------------------------------------------------------------ */ + +static int ftrfs_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *dentry) +{ + struct inode *inode = d_inode(old_dentry); + int ret; + + inode_set_ctime_to_ts(inode, current_time(inode)); + inode_inc_link_count(inode); + + ret = ftrfs_add_dirent(dir, &dentry->d_name, inode->i_ino, 1); + if (ret) { + inode_dec_link_count(inode); + return ret; + } + + ftrfs_write_inode_raw(inode); + ftrfs_write_inode_raw(dir); + d_instantiate(dentry, inode); + ihold(inode); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* write_inode — VFS super_op: persist inode to disk */ +/* ------------------------------------------------------------------ */ + +int ftrfs_write_inode(struct inode *inode, struct writeback_control *wbc) +{ + return ftrfs_write_inode_raw(inode); +} + +/* ------------------------------------------------------------------ */ +/* dir inode_operations — exported */ +/* ------------------------------------------------------------------ */ + +const struct inode_operations ftrfs_dir_inode_operations = { + .lookup = ftrfs_lookup, + .create = ftrfs_create, + .mkdir = ftrfs_mkdir, + .unlink = ftrfs_unlink, + .rmdir = ftrfs_rmdir, + .link = ftrfs_link, +}; -- 2.52.0 Implement data integrity layer: - ftrfs_crc32(): CRC32 wrapper via kernel crc32_le(), used for per-inode and per-superblock checksums - init_gf_tables(): initialize GF(2^8) Galois Field lookup tables (primitive polynomial 0x1d) for Reed-Solomon arithmetic - gf_mul(): GF(2^8) multiplication via log/exp tables - ftrfs_rs_encode(): systematic Reed-Solomon encoder over FTRFS_SUBBLOCK_DATA bytes with FTRFS_RS_PARITY check symbols The RS encoder operates on sub-blocks within each 4096-byte data block. Decoding (error correction) is not yet implemented. Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/edac.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 fs/ftrfs/edac.c diff --git a/fs/ftrfs/edac.c b/fs/ftrfs/edac.c new file mode 100644 index 000000000..ebe676c98 --- /dev/null +++ b/fs/ftrfs/edac.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTRFS — EDAC layer: CRC32 + Reed-Solomon FEC + * Author: roastercode - Aurelien DESBRIERES + */ + +#include +#include +#include +#include "ftrfs.h" + +/* Reed-Solomon FEC context */ +static uint8_t gf_exp[256]; +static uint8_t gf_log[256]; +static bool rs_initialized; + +/* Initialize Galois Field tables */ +static void init_gf_tables(void) +{ + uint8_t x = 1; + + for (int i = 0; i < 255; i++) { + gf_exp[i] = x; + gf_log[x] = i; + x = (x << 1) ^ ((x & 0x80) ? 0x1d : 0); + } + gf_exp[255] = gf_exp[0]; + rs_initialized = true; +} + +/* Galois Field multiplication */ +static uint8_t gf_mul(uint8_t a, uint8_t b) +{ + if (a == 0 || b == 0) + return 0; + return gf_exp[(gf_log[a] + gf_log[b]) % 255]; +} + +/* Reed-Solomon encoding */ +int ftrfs_rs_encode(uint8_t *data, uint8_t *parity) +{ + uint8_t msg[FTRFS_SUBBLOCK_DATA + FTRFS_RS_PARITY]; + + if (!rs_initialized) + init_gf_tables(); + + memset(msg, 0, sizeof(msg)); + memcpy(msg, data, FTRFS_SUBBLOCK_DATA); + + for (int i = 0; i < FTRFS_SUBBLOCK_DATA; i++) { + uint8_t feedback = gf_mul(msg[i], gf_exp[FTRFS_RS_PARITY]); + + if (feedback != 0) { + for (int j = 1; j <= FTRFS_RS_PARITY; j++) + msg[FTRFS_SUBBLOCK_DATA + j - 1] ^= gf_mul(msg[i], gf_exp[j]); + } + } + + memcpy(parity, msg + FTRFS_SUBBLOCK_DATA, FTRFS_RS_PARITY); + return 0; +} + +/* Reed-Solomon decoding (simplified for now) */ +int ftrfs_rs_decode(uint8_t *data, uint8_t *parity) +{ + if (!rs_initialized) + init_gf_tables(); + + /* For now, assume no errors (full decoding to be implemented) */ + return 0; +} + +/* + * ftrfs_crc32 - compute CRC32 checksum + * @buf: data buffer + * @len: length in bytes + * + * Returns CRC32 checksum. Uses kernel's hardware-accelerated CRC32 + * (same as ext4/btrfs). + */ +__u32 ftrfs_crc32(const void *buf, size_t len) +{ + return crc32_le(0xFFFFFFFF, buf, len) ^ 0xFFFFFFFF; +} -- 2.52.0 Kconfig: - CONFIG_FTRFS_FS: tristate, depends on BLOCK - selects CRC32, REED_SOLOMON, REED_SOLOMON_ENC8/DEC8 - CONFIG_FTRFS_FS_XATTR: extended attributes (SELinux support) - CONFIG_FTRFS_FS_SECURITY: security labels Makefile: - ftrfs.o composed of super.o, inode.o, dir.o, file.o, edac.o, alloc.o, namei.o - xattr.o conditionally compiled via CONFIG_FTRFS_FS_XATTR fs/Kconfig: source fs/ftrfs/Kconfig (after ext2) fs/Makefile: obj-$(CONFIG_FTRFS_FS) += ftrfs/ Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/Kconfig | 49 +++++++++++++++++++++++++++++++++++++++++++++++ fs/ftrfs/Makefile | 46 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 fs/ftrfs/Kconfig create mode 100644 fs/ftrfs/Makefile diff --git a/fs/ftrfs/Kconfig b/fs/ftrfs/Kconfig new file mode 100644 index 000000000..e23fea923 --- /dev/null +++ b/fs/ftrfs/Kconfig @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# FTRFS filesystem configuration +# + +config FTRFS_FS + tristate "FTRFS fault-tolerant radiation-robust filesystem" + depends on BLOCK + select CRC32 + select REED_SOLOMON + select REED_SOLOMON_ENC8 + select REED_SOLOMON_DEC8 + help + FTRFS is a POSIX-compatible filesystem designed for dependable + storage in radiation-intensive environments. It provides: + + - CRC32 checksumming per block and per inode + - Reed-Solomon forward error correction (FEC) + - EDAC-compatible error tracking + + Originally described in: + Fuchs, Langer, Trinitis - ARCS 2015, TU Munich. + Targeting embedded Linux on MRAM/NOR flash for space applications. + + To compile this filesystem support as a module, choose M here. + The module will be called ftrfs. + + If unsure, say N. + +config FTRFS_FS_XATTR + bool "FTRFS extended attributes" + depends on FTRFS_FS + help + Extended attributes are name:value pairs associated with inodes. + They are required for SELinux, POSIX ACLs, and other security + frameworks that store per-file metadata outside the inode. + FTRFS xattrs follow the same namespace model as ext2/ext4. + + If you are not using SELinux or POSIX ACLs, say N. +config FTRFS_FS_SECURITY + bool "FTRFS Security Labels" + depends on FTRFS_FS_XATTR + help + Extended attributes are name:value pairs associated with inodes. + They are required for SELinux, POSIX ACLs, and other security + frameworks that store per-file metadata outside the inode. + FTRFS xattrs follow the same namespace model as ext2/ext4. + + If you are not using SELinux or POSIX ACLs, say N. diff --git a/fs/ftrfs/Makefile b/fs/ftrfs/Makefile new file mode 100644 index 000000000..a792286ec --- /dev/null +++ b/fs/ftrfs/Makefile @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# FTRFS — Fault-Tolerant Radiation-Robust Filesystem +# + +obj-$(CONFIG_FTRFS_FS) += ftrfs.o + +ftrfs-y := super.o \ + inode.o \ + dir.o \ + file.o \ + edac.o \ + alloc.o \ + namei.o + +ftrfs-$(CONFIG_FTRFS_FS_XATTR) += xattr.o + +ifneq ($(KERNELRELEASE),) +else + +ifneq ($(KERNEL_SRC),) + KERNELDIR := $(KERNEL_SRC) +else + KERNELDIR ?= /lib/modules/$(shell uname -r)/build +endif + +ifneq ($(O),) + KBUILD_OUTPUT := O=$(O) +else + KBUILD_OUTPUT := +endif + +PWD := $(shell pwd) + +all: + $(MAKE) -C $(KERNELDIR) $(KBUILD_OUTPUT) M=$(PWD) \ + CONFIG_FTRFS_FS=m CONFIG_FTRFS_FS_XATTR=n CONFIG_FTRFS_FS_SECURITY=n \ + modules + +clean: + $(MAKE) -C $(KERNELDIR) $(KBUILD_OUTPUT) M=$(PWD) clean + +modules_install: + $(MAKE) -C $(KERNELDIR) $(KBUILD_OUTPUT) M=$(PWD) modules_install + +endif -- 2.52.0 Signed-off-by: Aurelien DESBRIERES --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index d1cc0e12f..f99e1219f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9610,6 +9610,12 @@ S: Maintained F: drivers/leds/leds-expresswire.c F: include/linux/leds-expresswire.h +FTRFS FILE SYSTEM +M: Aurélien DESBRIERES +L: linux-fsdevel@vger.kernel.org +S: Maintained +F: fs/ftrfs/ + EXT2 FILE SYSTEM M: Jan Kara L: linux-ext4@vger.kernel.org -- 2.52.0 address_space_operations: - Implement ftrfs_get_block with block allocation (create=1) - Return -EIO for holes on read (create=0), not 0 - Set bh_result->b_size = 1 << i_blkbits on all mapped paths - Add ftrfs_write_begin, ftrfs_write_end, ftrfs_readahead, ftrfs_bmap - Add dirty_folio, invalidate_folio to ftrfs_aops Inode lifecycle: - Use insert_inode_locked() instead of insert_inode_hash() new_inode() does not set I_NEW; insert_inode_locked() does - Move unlock_new_inode() to callers after d_instantiate() - Add unlock_new_inode() on error paths On-disk format: - ftrfs_inode: 128 -> 256 bytes - uid/gid: __le16 -> __le32 (standard kernel convention) - Add i_tindirect: triple indirect (~512 GiB max file size) - Remove i_blocks: redundant, calculable from i_size - i_reserved[84]: explicit padding to 256 bytes - Superblock padding: 3948 -> 3980 bytes (enforce 4096) - Add BUILD_BUG_ON for both structure sizes Directory: - Skip . and .. in readdir data blocks (dir_emit_dots emits them) Compat: - Add ftrfs_inode_is_new macro for inode_state_read_once API Tested on arm64 kernel 7.0-rc7 (Yocto KVM): - mount, write, mkdir, read: all working - 0 BUG/WARN/Oops in dmesg Addresses review feedback from: - Matthew Wilcox: address_space_operations now implemented - Darrick J. Wong: i_size __le64 intentional, BUILD_BUG_ON documents limits - Andreas Dilger: DO-178C/ECSS certification rationale documented Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/dir.c | 88 +++++++++++++++++++++----------------------- fs/ftrfs/file.c | 95 +++++++++++++++++++++++++++++++++++++++++++++--- fs/ftrfs/ftrfs.h | 35 +++++++++++++----- fs/ftrfs/inode.c | 9 +++-- fs/ftrfs/namei.c | 21 +++++++---- fs/ftrfs/super.c | 2 + 6 files changed, 177 insertions(+), 73 deletions(-) diff --git a/fs/ftrfs/dir.c b/fs/ftrfs/dir.c index dbf0102a4..fd06910bf 100644 --- a/fs/ftrfs/dir.c +++ b/fs/ftrfs/dir.c @@ -3,7 +3,6 @@ * FTRFS — Directory operations * Author: roastercode - Aurelien DESBRIERES */ - #include #include #include "ftrfs.h" @@ -13,24 +12,23 @@ */ static int ftrfs_readdir(struct file *file, struct dir_context *ctx) { - struct inode *inode = file_inode(file); - struct super_block *sb = inode->i_sb; + struct inode *inode = file_inode(file); + struct super_block *sb = inode->i_sb; struct ftrfs_inode_info *fi = FTRFS_I(inode); struct buffer_head *bh; struct ftrfs_dir_entry *de; unsigned long block_idx, block_no; - unsigned int offset; + unsigned int offset; - /* EOF guard */ if (ctx->pos == INT_MAX) return 0; + /* Emit . and .. (ctx->pos: 0=., 1=.., 2+=real entries) */ if (ctx->pos < 2) { if (!dir_emit_dots(file, ctx)) return 0; } - /* Iterate over direct blocks only (skeleton: no indirect yet) */ for (block_idx = 0; block_idx < FTRFS_DIRECT_BLOCKS; block_idx++) { block_no = le64_to_cpu(fi->i_direct[block_idx]); if (!block_no) @@ -43,25 +41,28 @@ static int ftrfs_readdir(struct file *file, struct dir_context *ctx) offset = 0; while (offset < FTRFS_BLOCK_SIZE) { de = (struct ftrfs_dir_entry *)(bh->b_data + offset); - if (!de->d_rec_len) - break; /* end of dir block */ - - if (de->d_ino && de->d_name_len) { - if (!dir_emit(ctx, - de->d_name, - de->d_name_len, - le64_to_cpu(de->d_ino), - de->d_file_type)) { - brelse(bh); - return 0; - } - ctx->pos++; + break; + + /* Skip . and .. — emitted by dir_emit_dots */ + if (!de->d_ino || !de->d_name_len) + goto next; + if (de->d_name_len == 1 && de->d_name[0] == '.') + goto next; + if (de->d_name_len == 2 && de->d_name[0] == '.' + && de->d_name[1] == '.') + goto next; + + if (!dir_emit(ctx, de->d_name, de->d_name_len, + le64_to_cpu(de->d_ino), + de->d_file_type)) { + brelse(bh); + return 0; } - + ctx->pos++; +next: offset += le16_to_cpu(de->d_rec_len); } - brelse(bh); } @@ -73,22 +74,19 @@ static int ftrfs_readdir(struct file *file, struct dir_context *ctx) * ftrfs_lookup — find dentry in directory */ struct dentry *ftrfs_lookup(struct inode *dir, - struct dentry *dentry, - unsigned int flags) + struct dentry *dentry, + unsigned int flags) { - struct super_block *sb = dir->i_sb; + struct super_block *sb = dir->i_sb; struct ftrfs_inode_info *fi = FTRFS_I(dir); - struct buffer_head *bh; - struct ftrfs_dir_entry *de; - struct inode *inode = NULL; - unsigned long block_idx, block_no; - unsigned int offset; - - if (dentry->d_name.len > FTRFS_MAX_FILENAME) - return ERR_PTR(-ENAMETOOLONG); + struct ftrfs_dir_entry *de; + struct buffer_head *bh; + unsigned int offset; + unsigned long block_no; + int i; - for (block_idx = 0; block_idx < FTRFS_DIRECT_BLOCKS; block_idx++) { - block_no = le64_to_cpu(fi->i_direct[block_idx]); + for (i = 0; i < FTRFS_DIRECT_BLOCKS; i++) { + block_no = le64_to_cpu(fi->i_direct[i]); if (!block_no) break; @@ -97,30 +95,28 @@ struct dentry *ftrfs_lookup(struct inode *dir, continue; offset = 0; - while (offset < FTRFS_BLOCK_SIZE) { + while (offset + sizeof(*de) <= FTRFS_BLOCK_SIZE) { de = (struct ftrfs_dir_entry *)(bh->b_data + offset); - if (!de->d_rec_len) - break; /* end of dir block */ - + break; if (de->d_ino && de->d_name_len == dentry->d_name.len && !memcmp(de->d_name, dentry->d_name.name, - de->d_name_len)) { - unsigned long ino = le64_to_cpu(de->d_ino); + dentry->d_name.len)) { + u64 ino = le64_to_cpu(de->d_ino); + struct inode *inode; brelse(bh); inode = ftrfs_iget(sb, ino); - goto found; + return d_splice_alias(inode, dentry); } - offset += le16_to_cpu(de->d_rec_len); + if (!de->d_rec_len) + break; } brelse(bh); } - -found: - return d_splice_alias(inode, dentry); + return d_splice_alias(NULL, dentry); } const struct file_operations ftrfs_dir_operations = { @@ -128,5 +124,3 @@ const struct file_operations ftrfs_dir_operations = { .read = generic_read_dir, .iterate_shared = ftrfs_readdir, }; - - diff --git a/fs/ftrfs/file.c b/fs/ftrfs/file.c index ef121359b..1807ac698 100644 --- a/fs/ftrfs/file.c +++ b/fs/ftrfs/file.c @@ -1,14 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * FTRFS — File operations (skeleton) + * FTRFS — File operations * Author: roastercode - Aurelien DESBRIERES - * - * NOTE: read/write use generic_file_* for now. - * The EDAC/RS layer will intercept at the block I/O level (next iteration). */ - #include #include +#include +#include #include "ftrfs.h" const struct file_operations ftrfs_file_operations = { @@ -23,3 +21,90 @@ const struct file_operations ftrfs_file_operations = { const struct inode_operations ftrfs_file_inode_operations = { .getattr = simple_getattr, }; + +/* + * ftrfs_get_block — map logical block to physical block + * Handles allocation when create=1, returns -EIO for holes on read. + */ +static int ftrfs_get_block(struct inode *inode, sector_t iblock, + struct buffer_head *bh_result, int create) +{ + struct ftrfs_inode_info *fi = FTRFS_I(inode); + u64 new_block; + __le64 phys; + + if (iblock >= FTRFS_DIRECT_BLOCKS) { + pr_err("ftrfs: indirect block not yet supported\n"); + return -EOPNOTSUPP; + } + + phys = fi->i_direct[iblock]; + if (phys) { + map_bh(bh_result, inode->i_sb, le64_to_cpu(phys)); + bh_result->b_size = 1 << inode->i_blkbits; + return 0; + } + + if (!create) + return -EIO; + + new_block = ftrfs_alloc_block(inode->i_sb); + if (!new_block) { + pr_err("ftrfs: no free blocks\n"); + return -ENOSPC; + } + + fi->i_direct[iblock] = cpu_to_le64(new_block); + map_bh(bh_result, inode->i_sb, new_block); + bh_result->b_size = 1 << inode->i_blkbits; + set_buffer_new(bh_result); + return 0; +} + +static int ftrfs_read_folio(struct file *file, struct folio *folio) +{ + return block_read_full_folio(folio, ftrfs_get_block); +} + +static int ftrfs_writepages(struct address_space *mapping, + struct writeback_control *wbc) +{ + return mpage_writepages(mapping, wbc, ftrfs_get_block); +} + +static void ftrfs_readahead(struct readahead_control *rac) +{ + mpage_readahead(rac, ftrfs_get_block); +} + +static int ftrfs_write_begin(const struct kiocb *iocb, + struct address_space *mapping, + loff_t pos, unsigned int len, + struct folio **foliop, void **fsdata) +{ + return block_write_begin(mapping, pos, len, foliop, ftrfs_get_block); +} + +static int ftrfs_write_end(const struct kiocb *iocb, + struct address_space *mapping, + loff_t pos, unsigned int len, unsigned int copied, + struct folio *folio, void *fsdata) +{ + return generic_write_end(iocb, mapping, pos, len, copied, folio, fsdata); +} + +static sector_t ftrfs_bmap(struct address_space *mapping, sector_t block) +{ + return generic_block_bmap(mapping, block, ftrfs_get_block); +} + +const struct address_space_operations ftrfs_aops = { + .read_folio = ftrfs_read_folio, + .readahead = ftrfs_readahead, + .write_begin = ftrfs_write_begin, + .write_end = ftrfs_write_end, + .writepages = ftrfs_writepages, + .bmap = ftrfs_bmap, + .dirty_folio = block_dirty_folio, + .invalidate_folio = block_invalidate_folio, +}; diff --git a/fs/ftrfs/ftrfs.h b/fs/ftrfs/ftrfs.h index 82502c9fb..1f2d8a954 100644 --- a/fs/ftrfs/ftrfs.h +++ b/fs/ftrfs/ftrfs.h @@ -13,6 +13,10 @@ #include #include +/* inode_state_read_once returns inode_state_flags in kernel 7.0 */ +#define ftrfs_inode_is_new(inode) \ + (inode_state_read_once(inode) & I_NEW) + /* Magic number: 'FTRF' */ #define FTRFS_MAGIC 0x46545246 @@ -49,29 +53,38 @@ struct ftrfs_super_block { __le32 s_crc32; /* CRC32 of this superblock */ __u8 s_uuid[16]; /* UUID */ __u8 s_label[32]; /* Volume label */ - __u8 s_pad[3948]; /* Padding to 4096 bytes */ + __u8 s_pad[3980]; /* Padding to 4096 bytes */ } __packed; /* * On-disk inode - * Size: 128 bytes + * Size: 256 bytes + * + * Addressing capacity: + * direct (12) = 48 KiB + * indirect (1) = 2 MiB + * dindirect (1) = 1 GiB + * tindirect (1) = 512 GiB + * + * uid/gid: __le32 to support uid > 65535 (standard kernel convention) + * timestamps: __le64 nanoseconds (required for space mission precision) */ struct ftrfs_inode { __le16 i_mode; /* File mode */ - __le16 i_uid; /* Owner UID */ - __le16 i_gid; /* Owner GID */ __le16 i_nlink; /* Hard link count */ - __le64 i_size; /* File size in bytes */ + __le32 i_uid; /* Owner UID */ + __le32 i_gid; /* Owner GID */ + __le64 i_size; /* File size in bytes (64-bit, future-proof) */ __le64 i_atime; /* Access time (ns) */ __le64 i_mtime; /* Modification time (ns) */ __le64 i_ctime; /* Change time (ns) */ - __le32 i_blocks; /* Block count */ __le32 i_flags; /* Inode flags */ + __le32 i_crc32; /* CRC32 of inode (excluding this field) */ __le64 i_direct[FTRFS_DIRECT_BLOCKS]; /* Direct block pointers */ - __le64 i_indirect; /* Single indirect */ - __le64 i_dindirect; /* Double indirect */ - __le32 i_crc32; /* CRC32 of inode */ - __u8 i_pad[2]; /* Padding to 128 bytes */ + __le64 i_indirect; /* Single indirect (~2 MiB) */ + __le64 i_dindirect; /* Double indirect (~1 GiB) */ + __le64 i_tindirect; /* Triple indirect (~512 GiB) */ + __u8 i_reserved[84]; /* Padding to 256 bytes */ } __packed; /* Inode flags */ @@ -111,6 +124,7 @@ struct ftrfs_inode_info { __le64 i_direct[FTRFS_DIRECT_BLOCKS]; __le64 i_indirect; __le64 i_dindirect; + __le64 i_tindirect; __u32 i_flags; struct inode vfs_inode; /* Must be last */ }; @@ -140,6 +154,7 @@ extern const struct inode_operations ftrfs_dir_inode_operations; /* file.c */ extern const struct file_operations ftrfs_file_operations; extern const struct inode_operations ftrfs_file_inode_operations; +extern const struct address_space_operations ftrfs_aops; /* edac.c */ __u32 ftrfs_crc32(const void *buf, size_t len); diff --git a/fs/ftrfs/inode.c b/fs/ftrfs/inode.c index e1279c796..f655ccbd9 100644 --- a/fs/ftrfs/inode.c +++ b/fs/ftrfs/inode.c @@ -34,7 +34,7 @@ struct inode *ftrfs_iget(struct super_block *sb, unsigned long ino) return ERR_PTR(-ENOMEM); /* Already in cache */ - if (!(inode_state_read_once(inode) & I_NEW)) + if (!ftrfs_inode_is_new(inode)) return inode; inodes_per_block = FTRFS_BLOCK_SIZE / sizeof(struct ftrfs_inode); @@ -63,11 +63,10 @@ struct inode *ftrfs_iget(struct super_block *sb, unsigned long ino) /* Populate VFS inode */ inode->i_mode = le16_to_cpu(raw->i_mode); - inode->i_uid = make_kuid(sb->s_user_ns, le16_to_cpu(raw->i_uid)); - inode->i_gid = make_kgid(sb->s_user_ns, le16_to_cpu(raw->i_gid)); + inode->i_uid = make_kuid(sb->s_user_ns, le32_to_cpu(raw->i_uid)); + inode->i_gid = make_kgid(sb->s_user_ns, le32_to_cpu(raw->i_gid)); set_nlink(inode, le16_to_cpu(raw->i_nlink)); inode->i_size = le64_to_cpu(raw->i_size); - inode->i_blocks = le32_to_cpu(raw->i_blocks); inode_set_atime(inode, le64_to_cpu(raw->i_atime) / NSEC_PER_SEC, @@ -83,6 +82,7 @@ struct inode *ftrfs_iget(struct super_block *sb, unsigned long ino) memcpy(fi->i_direct, raw->i_direct, sizeof(fi->i_direct)); fi->i_indirect = raw->i_indirect; fi->i_dindirect = raw->i_dindirect; + fi->i_tindirect = raw->i_tindirect; fi->i_flags = le32_to_cpu(raw->i_flags); /* Set ops based on file type */ @@ -92,6 +92,7 @@ struct inode *ftrfs_iget(struct super_block *sb, unsigned long ino) } else if (S_ISREG(inode->i_mode)) { inode->i_op = &ftrfs_file_inode_operations; inode->i_fop = &ftrfs_file_operations; + inode->i_mapping->a_ops = &ftrfs_aops; } else { /* Special files: use generic */ init_special_inode(inode, inode->i_mode, 0); diff --git a/fs/ftrfs/namei.c b/fs/ftrfs/namei.c index a8c1f79eb..d37ad8b7b 100644 --- a/fs/ftrfs/namei.c +++ b/fs/ftrfs/namei.c @@ -38,11 +38,10 @@ static int ftrfs_write_inode_raw(struct inode *inode) raw = (struct ftrfs_inode *)bh->b_data + offset; raw->i_mode = cpu_to_le16(inode->i_mode); - raw->i_uid = cpu_to_le16(i_uid_read(inode)); - raw->i_gid = cpu_to_le16(i_gid_read(inode)); + raw->i_uid = cpu_to_le32(i_uid_read(inode)); + raw->i_gid = cpu_to_le32(i_gid_read(inode)); raw->i_nlink = cpu_to_le16(inode->i_nlink); raw->i_size = cpu_to_le64(inode->i_size); - raw->i_blocks = cpu_to_le32(inode->i_blocks); raw->i_atime = cpu_to_le64(inode_get_atime_sec(inode) * NSEC_PER_SEC + inode_get_atime_nsec(inode)); raw->i_mtime = cpu_to_le64(inode_get_mtime_sec(inode) * NSEC_PER_SEC @@ -54,6 +53,7 @@ static int ftrfs_write_inode_raw(struct inode *inode) memcpy(raw->i_direct, fi->i_direct, sizeof(fi->i_direct)); raw->i_indirect = fi->i_indirect; raw->i_dindirect = fi->i_dindirect; + raw->i_tindirect = fi->i_tindirect; raw->i_crc32 = ftrfs_crc32(raw, offsetof(struct ftrfs_inode, i_crc32)); @@ -145,7 +145,6 @@ static int ftrfs_add_dirent(struct inode *dir, const struct qstr *name, fi->i_direct[i] = cpu_to_le64(block_no); dir->i_size += FTRFS_BLOCK_SIZE; - dir->i_blocks++; inode_set_mtime_to_ts(dir, current_time(dir)); mark_inode_dirty(dir); @@ -223,7 +222,6 @@ struct inode *ftrfs_new_inode(struct inode *dir, umode_t mode) inode_init_owner(&nop_mnt_idmap, inode, dir, mode); inode->i_ino = ino; - inode->i_blocks = 0; inode->i_size = 0; inode_set_atime_to_ts(inode, current_time(inode)); inode_set_mtime_to_ts(inode, current_time(inode)); @@ -242,12 +240,17 @@ struct inode *ftrfs_new_inode(struct inode *dir, umode_t mode) } else { inode->i_op = &ftrfs_file_inode_operations; inode->i_fop = &ftrfs_file_operations; + inode->i_mapping->a_ops = &ftrfs_aops; set_nlink(inode, 1); } - insert_inode_hash(inode); + if (insert_inode_locked(inode) < 0) { + make_bad_inode(inode); + iput(inode); + return ERR_PTR(-EIO); + } mark_inode_dirty(inode); - return ERR_CAST(inode); + return inode; } /* ------------------------------------------------------------------ */ @@ -277,9 +280,11 @@ static int ftrfs_create(struct mnt_idmap *idmap, struct inode *dir, goto out_iput; d_instantiate(dentry, inode); + unlock_new_inode(inode); return 0; out_iput: + unlock_new_inode(inode); iput(inode); return ret; } @@ -327,9 +332,11 @@ static struct dentry *ftrfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, goto out_fail; d_instantiate(dentry, inode); + unlock_new_inode(inode); return NULL; out_fail: + unlock_new_inode(inode); inode_dec_link_count(inode); inode_dec_link_count(inode); iput(inode); diff --git a/fs/ftrfs/super.c b/fs/ftrfs/super.c index 8acc62921..bce7148ee 100644 --- a/fs/ftrfs/super.c +++ b/fs/ftrfs/super.c @@ -252,6 +252,8 @@ static int __init ftrfs_init(void) return ret; } + BUILD_BUG_ON(sizeof(struct ftrfs_super_block) != FTRFS_BLOCK_SIZE); + BUILD_BUG_ON(sizeof(struct ftrfs_inode) != 256); pr_info("ftrfs: module loaded (FTRFS Fault-Tolerant Radiation-Robust FS)\n"); return 0; } -- 2.52.0