public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 00/11] ftrfs: Fault-Tolerant Radiation-Robust Filesystem
@ 2026-04-13 23:05 Aurelien DESBRIERES
  2026-04-13 23:05 ` [PATCH v2 01/11] ftrfs: add on-disk format and in-memory data structures Aurelien DESBRIERES
                   ` (11 more replies)
  0 siblings, 12 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-13 23:05 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Aurelien DESBRIERES, viro, brauner, willy, djwong, adilger,
	pfalcato

FTRFS is an out-of-tree Linux filesystem designed for embedded systems
operating in radiation-intensive environments. It provides per-block
CRC32 checksumming and a Reed-Solomon FEC encoder (decoder planned for
v3) for in-place correction of silent data corruption caused by
single-event upsets (SEU).

FTRFS does not compete with ext4 for general-purpose use. The target
is embedded critical systems where DO-178C (avionics), ECSS-E-ST-40C
(space), or IEC 61508 (nuclear) certification is a hard requirement.
These standards require complete code auditability. No existing Linux
filesystem can realistically be certified under these frameworks due to
code complexity. FTRFS is designed to stay under 5000 lines of auditable
code with RS FEC as a first-class design constraint.

Changes since v1:
- Implement address_space_operations (Matthew Wilcox)
  ftrfs_get_block now allocates blocks (create=1), returns -EIO for
  holes on read. Set bh_result->b_size correctly.
  Add ftrfs_write_begin, ftrfs_write_end, ftrfs_readahead, ftrfs_bmap,
  dirty_folio, invalidate_folio.
- Fix inode lifecycle: use insert_inode_locked() instead of
  insert_inode_hash(). new_inode() does not set I_NEW.
  Move unlock_new_inode() to callers after d_instantiate().
- On-disk format: inode 128 -> 256 bytes, uid/gid __le16 -> __le32,
  add i_tindirect (~512 GiB), remove i_blocks, BUILD_BUG_ON enforces
  structure sizes at compile time.
- Directory: skip . and .. in readdir data blocks (dir_emit_dots).
- Add ftrfs_inode_is_new compat macro for inode_state_read_once API.
- i_size __le64 is intentional: future-proof for growing MRAM densities.
  The real limit is the block pointer scheme (~512 GiB with tindirect).
  BUILD_BUG_ON documents the structure sizes explicitly (Darrick J. Wong).

Known gaps to be addressed in v3:
- IO path: migrate from buffer_head to iomap (Matthew Wilcox)
- rename: not yet implemented
- xfstests: no test run yet
- RS FEC decoder: encoder present, decoder skeleton only

Tested on arm64 kernel 7.0-rc7 (Yocto KVM, Slurm HPC cluster):
  mount, write, mkdir, read: functional
  0 BUG/WARN/Oops in dmesg

Yocto integration: https://github.com/roastercode/yocto-hardened/tree/arm64-ftrfs
RFC paper: https://doi.org/10.1007/978-3-319-16086-3_8

Aurelien DESBRIERES (11):
  ftrfs: add on-disk format and in-memory data structures
  ftrfs: add superblock operations
  ftrfs: add inode operations
  ftrfs: add directory operations
  ftrfs: add file operations
  ftrfs: add block and inode allocator
  ftrfs: add filename and directory entry operations
  ftrfs: add CRC32 checksumming and Reed-Solomon FEC skeleton
  ftrfs: add Kconfig, Makefile and fs/ tree integration
  MAINTAINERS: add entry for FTRFS filesystem
  ftrfs: v2 fixes — write path, inode lifecycle, on-disk format

 MAINTAINERS            |   6 +
 fs/ftrfs/Kconfig       |  18 ++
 fs/ftrfs/Makefile      |   9 +
 fs/ftrfs/alloc.c       | 251 ++++++++++++++++++
 fs/ftrfs/dir.c         | 126 +++++++++
 fs/ftrfs/edac.c        |  84 ++++++
 fs/ftrfs/file.c        | 110 ++++++++
 fs/ftrfs/ftrfs.h       | 168 ++++++++++++
 fs/ftrfs/inode.c       | 103 ++++++++
 fs/ftrfs/namei.c       | 428 +++++++++++++++++++++++++++++++
 fs/ftrfs/super.c       | 276 ++++++++++++++++++++
 11 files changed, 1579 insertions(+)

-- 
2.49.0

^ permalink raw reply	[flat|nested] 30+ messages in thread

* [PATCH v2 01/11] ftrfs: add on-disk format and in-memory data structures
  2026-04-13 23:05 [PATCH v2 00/11] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
@ 2026-04-13 23:05 ` Aurelien DESBRIERES
  2026-04-13 23:05 ` [PATCH v2 02/11] ftrfs: add superblock operations Aurelien DESBRIERES
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-13 23:05 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Aurelien DESBRIERES, viro, brauner, willy, djwong, adilger,
	pfalcato

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 <aurelien@hackers.camp>
---
 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 <aurelien@hackers.camp>
+ */
+
+#ifndef _FTRFS_H
+#define _FTRFS_H
+
+#include <linux/fs.h>
+#include <linux/fs_context.h>
+#include <linux/types.h>
+
+/* 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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v2 02/11] ftrfs: add superblock operations
  2026-04-13 23:05 [PATCH v2 00/11] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
  2026-04-13 23:05 ` [PATCH v2 01/11] ftrfs: add on-disk format and in-memory data structures Aurelien DESBRIERES
@ 2026-04-13 23:05 ` Aurelien DESBRIERES
  2026-04-13 23:05 ` [PATCH v2 03/11] ftrfs: add inode operations Aurelien DESBRIERES
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-13 23:05 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Aurelien DESBRIERES, viro, brauner, willy, djwong, adilger,
	pfalcato

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 <aurelien@hackers.camp>
---
 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 <aurelien@hackers.camp>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/fs_context.h>
+#include <linux/slab.h>
+#include <linux/buffer_head.h>
+#include <linux/statfs.h>
+#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 <aurelien@hackers.camp>");
+MODULE_DESCRIPTION("FTRFS: Fault-Tolerant Radiation-Robust Filesystem");
+MODULE_VERSION("0.1.0");
+MODULE_ALIAS_FS("ftrfs");
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v2 03/11] ftrfs: add inode operations
  2026-04-13 23:05 [PATCH v2 00/11] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
  2026-04-13 23:05 ` [PATCH v2 01/11] ftrfs: add on-disk format and in-memory data structures Aurelien DESBRIERES
  2026-04-13 23:05 ` [PATCH v2 02/11] ftrfs: add superblock operations Aurelien DESBRIERES
@ 2026-04-13 23:05 ` Aurelien DESBRIERES
  2026-04-13 23:05 ` [PATCH v2 04/11] ftrfs: add directory operations Aurelien DESBRIERES
                   ` (8 subsequent siblings)
  11 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-13 23:05 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Aurelien DESBRIERES, viro, brauner, willy, djwong, adilger,
	pfalcato

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 <aurelien@hackers.camp>
---
 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 <aurelien@hackers.camp>
+ */
+
+#include <linux/fs.h>
+#include <linux/buffer_head.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v2 04/11] ftrfs: add directory operations
  2026-04-13 23:05 [PATCH v2 00/11] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                   ` (2 preceding siblings ...)
  2026-04-13 23:05 ` [PATCH v2 03/11] ftrfs: add inode operations Aurelien DESBRIERES
@ 2026-04-13 23:05 ` Aurelien DESBRIERES
  2026-04-13 23:05 ` [PATCH v2 05/11] ftrfs: add file operations Aurelien DESBRIERES
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-13 23:05 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Aurelien DESBRIERES, viro, brauner, willy, djwong, adilger,
	pfalcato

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 <aurelien@hackers.camp>
---
 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 <aurelien@hackers.camp>
+ */
+
+#include <linux/fs.h>
+#include <linux/buffer_head.h>
+#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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v2 05/11] ftrfs: add file operations
  2026-04-13 23:05 [PATCH v2 00/11] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                   ` (3 preceding siblings ...)
  2026-04-13 23:05 ` [PATCH v2 04/11] ftrfs: add directory operations Aurelien DESBRIERES
@ 2026-04-13 23:05 ` Aurelien DESBRIERES
  2026-04-13 23:05 ` [PATCH v2 06/11] ftrfs: add block and inode allocator Aurelien DESBRIERES
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-13 23:05 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Aurelien DESBRIERES, viro, brauner, willy, djwong, adilger,
	pfalcato

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 <aurelien@hackers.camp>
---
 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 <aurelien@hackers.camp>
+ *
+ * NOTE: read/write use generic_file_* for now.
+ * The EDAC/RS layer will intercept at the block I/O level (next iteration).
+ */
+
+#include <linux/fs.h>
+#include <linux/mm.h>
+#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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v2 06/11] ftrfs: add block and inode allocator
  2026-04-13 23:05 [PATCH v2 00/11] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                   ` (4 preceding siblings ...)
  2026-04-13 23:05 ` [PATCH v2 05/11] ftrfs: add file operations Aurelien DESBRIERES
@ 2026-04-13 23:05 ` Aurelien DESBRIERES
  2026-04-13 23:05 ` [PATCH v2 07/11] ftrfs: add filename and directory entry operations Aurelien DESBRIERES
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-13 23:05 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Aurelien DESBRIERES, viro, brauner, willy, djwong, adilger,
	pfalcato

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 <aurelien@hackers.camp>
---
 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 <aurelien@hackers.camp>
+ *
+ * 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 <linux/fs.h>
+#include <linux/buffer_head.h>
+#include <linux/bitmap.h>
+#include <linux/slab.h>
+#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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v2 07/11] ftrfs: add filename and directory entry operations
  2026-04-13 23:05 [PATCH v2 00/11] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                   ` (5 preceding siblings ...)
  2026-04-13 23:05 ` [PATCH v2 06/11] ftrfs: add block and inode allocator Aurelien DESBRIERES
@ 2026-04-13 23:05 ` Aurelien DESBRIERES
  2026-04-13 23:05 ` [PATCH v2 08/11] ftrfs: add CRC32 checksumming and Reed-Solomon FEC skeleton Aurelien DESBRIERES
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-13 23:05 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Aurelien DESBRIERES, viro, brauner, willy, djwong, adilger,
	pfalcato

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 <aurelien@hackers.camp>
---
 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 <aurelien@hackers.camp>
+ *
+ * Implements: create, mkdir, unlink, rmdir, link, rename
+ */
+
+#include <linux/fs.h>
+#include <linux/buffer_head.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v2 08/11] ftrfs: add CRC32 checksumming and Reed-Solomon FEC skeleton
  2026-04-13 23:05 [PATCH v2 00/11] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                   ` (6 preceding siblings ...)
  2026-04-13 23:05 ` [PATCH v2 07/11] ftrfs: add filename and directory entry operations Aurelien DESBRIERES
@ 2026-04-13 23:05 ` Aurelien DESBRIERES
  2026-04-14 17:34   ` Eric Biggers
  2026-04-13 23:05 ` [PATCH v2 09/11] ftrfs: add Kconfig, Makefile and fs/ tree integration Aurelien DESBRIERES
                   ` (3 subsequent siblings)
  11 siblings, 1 reply; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-13 23:05 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Aurelien DESBRIERES, viro, brauner, willy, djwong, adilger,
	pfalcato

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 <aurelien@hackers.camp>
---
 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 <aurelien@hackers.camp>
+ */
+
+#include <linux/kernel.h>
+#include <linux/crc32.h>
+#include <linux/slab.h>
+#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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v2 09/11] ftrfs: add Kconfig, Makefile and fs/ tree integration
  2026-04-13 23:05 [PATCH v2 00/11] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                   ` (7 preceding siblings ...)
  2026-04-13 23:05 ` [PATCH v2 08/11] ftrfs: add CRC32 checksumming and Reed-Solomon FEC skeleton Aurelien DESBRIERES
@ 2026-04-13 23:05 ` Aurelien DESBRIERES
  2026-04-13 23:05 ` [PATCH v2 10/11] MAINTAINERS: add entry for FTRFS filesystem Aurelien DESBRIERES
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-13 23:05 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Aurelien DESBRIERES, viro, brauner, willy, djwong, adilger,
	pfalcato

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 <aurelien@hackers.camp>
---
 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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v2 10/11] MAINTAINERS: add entry for FTRFS filesystem
  2026-04-13 23:05 [PATCH v2 00/11] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                   ` (8 preceding siblings ...)
  2026-04-13 23:05 ` [PATCH v2 09/11] ftrfs: add Kconfig, Makefile and fs/ tree integration Aurelien DESBRIERES
@ 2026-04-13 23:05 ` Aurelien DESBRIERES
  2026-04-13 23:05 ` [PATCH v2 11/11] ftrfs: v2 fixes — write path, inode lifecycle, on-disk format Aurelien DESBRIERES
  2026-04-14 12:07 ` [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
  11 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-13 23:05 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Aurelien DESBRIERES, viro, brauner, willy, djwong, adilger,
	pfalcato

Signed-off-by: Aurelien DESBRIERES <aurelien@hackers.camp>
---
 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 <aurelien@hackers.camp>
+L:	linux-fsdevel@vger.kernel.org
+S:	Maintained
+F:	fs/ftrfs/
+
 EXT2 FILE SYSTEM
 M:	Jan Kara <jack@suse.com>
 L:	linux-ext4@vger.kernel.org
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v2 11/11] ftrfs: v2 fixes — write path, inode lifecycle, on-disk format
  2026-04-13 23:05 [PATCH v2 00/11] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                   ` (9 preceding siblings ...)
  2026-04-13 23:05 ` [PATCH v2 10/11] MAINTAINERS: add entry for FTRFS filesystem Aurelien DESBRIERES
@ 2026-04-13 23:05 ` Aurelien DESBRIERES
  2026-04-14 12:07 ` [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
  11 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-13 23:05 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Aurelien DESBRIERES, viro, brauner, willy, djwong, adilger,
	pfalcato

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 <aurelien@hackers.camp>
---
 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 <aurelien@hackers.camp>
  */
-
 #include <linux/fs.h>
 #include <linux/buffer_head.h>
 #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 <aurelien@hackers.camp>
- *
- * NOTE: read/write use generic_file_* for now.
- * The EDAC/RS layer will intercept at the block I/O level (next iteration).
  */
-
 #include <linux/fs.h>
 #include <linux/mm.h>
+#include <linux/buffer_head.h>
+#include <linux/mpage.h>
 #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 <linux/fs_context.h>
 #include <linux/types.h>
 
+/* 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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* Re: [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem
  2026-04-14 12:07 ` [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
@ 2026-04-14 10:22   ` Pedro Falcato
  2026-04-14 11:05     ` Joshua Peisach
  2026-04-14 12:07   ` [PATCH v3 01/12] ftrfs: add on-disk format and in-memory data structures Aurelien DESBRIERES
                     ` (11 subsequent siblings)
  12 siblings, 1 reply; 30+ messages in thread
From: Pedro Falcato @ 2026-04-14 10:22 UTC (permalink / raw)
  To: Aurelien DESBRIERES
  Cc: linux-fsdevel, linux-kernel, torvalds, willy, djwong,
	adilger.kernel, pedro.falcato, xiang

On Tue, Apr 14, 2026 at 02:07:13PM +0200, Aurelien DESBRIERES wrote:
> This is v3 of the FTRFS patch series. FTRFS is a Linux kernel filesystem
> designed for dependable storage in radiation-intensive environments,
> implementing Reed-Solomon FEC at the filesystem block level for in-place
> correction of silent bit flips on single-device embedded systems (MRAM,
> NOR flash) without external redundancy.
> 
> Based on: Fuchs, Langer, Trinitis - ARCS 2015, TU Munich / MOVE-II CubeSat.
> Full text: https://www.cfuchs.net/chris/publication-list/ARCS2015/FTRFS.pdf
> 
> Changes since v2 (<20260413230601.525400-1-aurelien@hackers.camp>):
> 
>   - iomap IO path: replace buffer_head based read/write with iomap API
>     as requested by Matthew Wilcox. Implements ftrfs_iomap_begin/end,
>     iomap_writeback_ops (writeback_range/writeback_submit), and uses
>     iomap_bio_read_ops for the read path. buffer_head is retained for
>     metadata IO (inode table, directory blocks) as discussed.
> 
>   - rename: implement ftrfs_rename in namei.c. Handles same-dir and
>     cross-dir rename for files and directories, updates '..' entries
>     and nlink counts. RENAME_EXCHANGE and RENAME_WHITEOUT return
>     -EINVAL (not supported in v3).
> 
>   - RS FEC decoder: implement full RS(255,239) decoder in edac.c using
>     Berlekamp-Massey error locator polynomial, Chien search, and Forney
>     algorithm for in-place correction. Corrects up to 8 symbol errors
>     per 255-byte subblock. Returns -EBADMSG if uncorrectable.
> 
>   - Radiation Event Journal: persistent ring buffer of 64 x 24 bytes
>     in the superblock reserved area. Records block number, timestamp
>     (ns), symbols corrected, and per-entry CRC32 for each RS correction
>     event. Written under spinlock via ftrfs_log_rs_event(). No existing
>     Linux filesystem provides this filesystem-level radiation event
>     history - VxWorks HRFS, btrfs scrub, and NVMe SMART all operate
>     at different layers.
> 
> AI tooling disclosure (Documentation/process/coding-assistants.rst):
>   Assisted-by: Claude:claude-sonnet-4-6
>   The submitter takes full responsibility for all code, has reviewed,
>   tested, and debugged every patch.

I was going to ask if you were on the world's strongest stimulant but, yeah,
this also makes sense.

3 versions in a day and no reply to review comments, no usecase, just vibes,
AI ones at that. This latest batch even got the LLM to hallucinate my old
email address.

Naturally,
Nacked-by: Pedro Falcato <pfalcato@suse.de>

Please pick this up to every patch if you get to send more slop versions.

-- 
Pedro

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem
  2026-04-14 10:22   ` Pedro Falcato
@ 2026-04-14 11:05     ` Joshua Peisach
  2026-04-14 11:28       ` Pedro Falcato
  0 siblings, 1 reply; 30+ messages in thread
From: Joshua Peisach @ 2026-04-14 11:05 UTC (permalink / raw)
  To: Pedro Falcato, Aurelien DESBRIERES
  Cc: linux-fsdevel, linux-kernel, torvalds, willy, djwong,
	adilger.kernel, pedro.falcato, xiang

On Tue Apr 14, 2026 at 6:22 AM EDT, Pedro Falcato wrote:
> On Tue, Apr 14, 2026 at 02:07:13PM +0200, Aurelien DESBRIERES wrote:
>> 
>> Based on: Fuchs, Langer, Trinitis - ARCS 2015, TU Munich / MOVE-II CubeSat.
>> Full text: https://www.cfuchs.net/chris/publication-list/ARCS2015/FTRFS.pdf
>> 

>> AI tooling disclosure (Documentation/process/coding-assistants.rst):
>>   Assisted-by: Claude:claude-sonnet-4-6
>>   The submitter takes full responsibility for all code, has reviewed,
>>   tested, and debugged every patch.
>
> I was going to ask if you were on the world's strongest stimulant but, yeah,
> this also makes sense.
>
> 3 versions in a day and no reply to review comments, no usecase, just vibes,
> AI ones at that. This latest batch even got the LLM to hallucinate my old
> email address.
>
> Naturally,
> Nacked-by: Pedro Falcato <pfalcato@suse.de>
>
> Please pick this up to every patch if you get to send more slop versions.

I'm not enthusiastic about vibe coding either, so I understand why you
wanted to NACK the series. But I feel obligated to say that you
can't claim there is "no usecase" when in the cover letter, a paper was
provided, and you even quoted it in the reply.

-Josh

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem
  2026-04-14 11:05     ` Joshua Peisach
@ 2026-04-14 11:28       ` Pedro Falcato
  2026-04-14 13:46         ` Aurelien DESBRIERES
  0 siblings, 1 reply; 30+ messages in thread
From: Pedro Falcato @ 2026-04-14 11:28 UTC (permalink / raw)
  To: Joshua Peisach
  Cc: Aurelien DESBRIERES, linux-fsdevel, linux-kernel, torvalds, willy,
	djwong, adilger.kernel, pedro.falcato, xiang

On Tue, Apr 14, 2026 at 07:05:30AM -0400, Joshua Peisach wrote:
> On Tue Apr 14, 2026 at 6:22 AM EDT, Pedro Falcato wrote:
> > On Tue, Apr 14, 2026 at 02:07:13PM +0200, Aurelien DESBRIERES wrote:
> > > 
> > > Based on: Fuchs, Langer, Trinitis - ARCS 2015, TU Munich / MOVE-II CubeSat.
> > > Full text: https://www.cfuchs.net/chris/publication-list/ARCS2015/FTRFS.pdf
> > > 
> 
> > > AI tooling disclosure (Documentation/process/coding-assistants.rst):
> > >   Assisted-by: Claude:claude-sonnet-4-6
> > >   The submitter takes full responsibility for all code, has reviewed,
> > >   tested, and debugged every patch.
> > 
> > I was going to ask if you were on the world's strongest stimulant but, yeah,
> > this also makes sense.
> > 
> > 3 versions in a day and no reply to review comments, no usecase, just vibes,
> > AI ones at that. This latest batch even got the LLM to hallucinate my old
> > email address.
> > 
> > Naturally,
> > Nacked-by: Pedro Falcato <pfalcato@suse.de>
> > 
> > Please pick this up to every patch if you get to send more slop versions.
> 
> I'm not enthusiastic about vibe coding either, so I understand why you
> wanted to NACK the series. But I feel obligated to say that you
> can't claim there is "no usecase" when in the cover letter, a paper was
> provided, and you even quoted it in the reply.

Yes, a paper is provided. However, when I asked for a usecase I really did mean:
"is anyone going to actually use this?". Which is a very non-trivial question to
answer, particularly when it comes to "space stuff" (how commonly is linux even
used for those?).

Good answers (IMHO):
- "Yes, we have a couple of planned users for this"
- "Yes, this has been maintained out-of-tree for X months/years, known to work
well for a variety of users"
- "Yes, we are using it"

IMO it's not sufficient to say this _could_ be used in space, or was used
by the paper authors. And instead of a straight up answer, there was radio
silence.

-- 
Pedro

^ permalink raw reply	[flat|nested] 30+ messages in thread

* [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem
  2026-04-13 23:05 [PATCH v2 00/11] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                   ` (10 preceding siblings ...)
  2026-04-13 23:05 ` [PATCH v2 11/11] ftrfs: v2 fixes — write path, inode lifecycle, on-disk format Aurelien DESBRIERES
@ 2026-04-14 12:07 ` Aurelien DESBRIERES
  2026-04-14 10:22   ` Pedro Falcato
                     ` (12 more replies)
  11 siblings, 13 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-14 12:07 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-kernel, torvalds, willy, djwong, adilger.kernel,
	pedro.falcato, xiang, Aurelien DESBRIERES

This is v3 of the FTRFS patch series. FTRFS is a Linux kernel filesystem
designed for dependable storage in radiation-intensive environments,
implementing Reed-Solomon FEC at the filesystem block level for in-place
correction of silent bit flips on single-device embedded systems (MRAM,
NOR flash) without external redundancy.

Based on: Fuchs, Langer, Trinitis - ARCS 2015, TU Munich / MOVE-II CubeSat.
Full text: https://www.cfuchs.net/chris/publication-list/ARCS2015/FTRFS.pdf

Changes since v2 (<20260413230601.525400-1-aurelien@hackers.camp>):

  - iomap IO path: replace buffer_head based read/write with iomap API
    as requested by Matthew Wilcox. Implements ftrfs_iomap_begin/end,
    iomap_writeback_ops (writeback_range/writeback_submit), and uses
    iomap_bio_read_ops for the read path. buffer_head is retained for
    metadata IO (inode table, directory blocks) as discussed.

  - rename: implement ftrfs_rename in namei.c. Handles same-dir and
    cross-dir rename for files and directories, updates '..' entries
    and nlink counts. RENAME_EXCHANGE and RENAME_WHITEOUT return
    -EINVAL (not supported in v3).

  - RS FEC decoder: implement full RS(255,239) decoder in edac.c using
    Berlekamp-Massey error locator polynomial, Chien search, and Forney
    algorithm for in-place correction. Corrects up to 8 symbol errors
    per 255-byte subblock. Returns -EBADMSG if uncorrectable.

  - Radiation Event Journal: persistent ring buffer of 64 x 24 bytes
    in the superblock reserved area. Records block number, timestamp
    (ns), symbols corrected, and per-entry CRC32 for each RS correction
    event. Written under spinlock via ftrfs_log_rs_event(). No existing
    Linux filesystem provides this filesystem-level radiation event
    history - VxWorks HRFS, btrfs scrub, and NVMe SMART all operate
    at different layers.

AI tooling disclosure (Documentation/process/coding-assistants.rst):
  Assisted-by: Claude:claude-sonnet-4-6
  The submitter takes full responsibility for all code, has reviewed,
  tested, and debugged every patch.

Testing:
  - qemuarm64, kernel 7.0 final (Yocto Styhead 5.1, cortex-a57 TCG)
  - mount, write, read, rename, umount: 0 BUG/WARN/Oops
  - xfstests generic/001, 002, 010 equivalent: all pass
  - Slurm 25.11.4 HPC cluster validation (3-node parallel jobs)
  - checkpatch.pl: 0 errors, 0 warnings on all modified files

HPC/space use case:
  FTRFS is validated in an arm64 Slurm cluster built with Yocto
  (github.com/roastercode/yocto-hardened, branch arm64-ftrfs).
  This demonstrates the filesystem operating in a real HPC embedded
  context, not just a test image - the target environment for
  space-grade embedded Linux systems.

Remaining work (v4):
  - kthread scrubber with RT priority and sysfs interface
  - Full xfstests Yocto recipe
  - iomap for metadata IO path (currently buffer_head)

Aurelien DESBRIERES (12):
  ftrfs: add on-disk format and in-memory data structures
  ftrfs: add superblock operations
  ftrfs: add inode operations
  ftrfs: add directory operations
  ftrfs: add file operations
  ftrfs: add block and inode allocator
  ftrfs: add filename and directory entry operations
  ftrfs: add CRC32 checksumming and Reed-Solomon FEC skeleton
  ftrfs: add Kconfig, Makefile and fs/ tree integration
  MAINTAINERS: add entry for FTRFS filesystem
  ftrfs: v2 fixes - write path, inode lifecycle, on-disk format
  ftrfs: v3 - iomap IO path, rename, RS decoder, Radiation Event Journal

 MAINTAINERS       |   6 +
 fs/ftrfs/Kconfig  |  49 +++++
 fs/ftrfs/Makefile |  46 ++++
 fs/ftrfs/alloc.c  | 251 +++++++++++++++++++++
 fs/ftrfs/dir.c    | 126 +++++++++++
 fs/ftrfs/edac.c   | 277 +++++++++++++++++++++++
 fs/ftrfs/file.c   | 197 +++++++++++++++++
 fs/ftrfs/ftrfs.h  | 201 +++++++++++++++++
 fs/ftrfs/inode.c  | 104 +++++++++
 fs/ftrfs/namei.c  | 548 ++++++++++++++++++++++++++++++++++++++++++++++
 fs/ftrfs/super.c  | 317 +++++++++++++++++++++++++++
 11 files changed, 2122 insertions(+)
 create mode 100644 fs/ftrfs/Kconfig
 create mode 100644 fs/ftrfs/Makefile
 create mode 100644 fs/ftrfs/alloc.c
 create mode 100644 fs/ftrfs/dir.c
 create mode 100644 fs/ftrfs/edac.c
 create mode 100644 fs/ftrfs/file.c
 create mode 100644 fs/ftrfs/ftrfs.h
 create mode 100644 fs/ftrfs/inode.c
 create mode 100644 fs/ftrfs/namei.c
 create mode 100644 fs/ftrfs/super.c

-- 
2.52.0


^ permalink raw reply	[flat|nested] 30+ messages in thread

* [PATCH v3 01/12] ftrfs: add on-disk format and in-memory data structures
  2026-04-14 12:07 ` [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
  2026-04-14 10:22   ` Pedro Falcato
@ 2026-04-14 12:07   ` Aurelien DESBRIERES
  2026-04-14 12:07   ` [PATCH v3 02/12] ftrfs: add superblock operations Aurelien DESBRIERES
                     ` (10 subsequent siblings)
  12 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-14 12:07 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-kernel, torvalds, willy, djwong, adilger.kernel,
	pedro.falcato, xiang, Aurelien DESBRIERES

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 <aurelien@hackers.camp>
---
 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 000000000000..82502c9fb07d
--- /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 <aurelien@hackers.camp>
+ */
+
+#ifndef _FTRFS_H
+#define _FTRFS_H
+
+#include <linux/fs.h>
+#include <linux/fs_context.h>
+#include <linux/types.h>
+
+/* 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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v3 02/12] ftrfs: add superblock operations
  2026-04-14 12:07 ` [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
  2026-04-14 10:22   ` Pedro Falcato
  2026-04-14 12:07   ` [PATCH v3 01/12] ftrfs: add on-disk format and in-memory data structures Aurelien DESBRIERES
@ 2026-04-14 12:07   ` Aurelien DESBRIERES
  2026-04-14 12:07   ` [PATCH v3 03/12] ftrfs: add inode operations Aurelien DESBRIERES
                     ` (9 subsequent siblings)
  12 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-14 12:07 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-kernel, torvalds, willy, djwong, adilger.kernel,
	pedro.falcato, xiang, Aurelien DESBRIERES

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 <aurelien@hackers.camp>
---
 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 000000000000..8acc6292124b
--- /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 <aurelien@hackers.camp>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/fs_context.h>
+#include <linux/slab.h>
+#include <linux/buffer_head.h>
+#include <linux/statfs.h>
+#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 <aurelien@hackers.camp>");
+MODULE_DESCRIPTION("FTRFS: Fault-Tolerant Radiation-Robust Filesystem");
+MODULE_VERSION("0.1.0");
+MODULE_ALIAS_FS("ftrfs");
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v3 03/12] ftrfs: add inode operations
  2026-04-14 12:07 ` [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                     ` (2 preceding siblings ...)
  2026-04-14 12:07   ` [PATCH v3 02/12] ftrfs: add superblock operations Aurelien DESBRIERES
@ 2026-04-14 12:07   ` Aurelien DESBRIERES
  2026-04-14 12:07   ` [PATCH v3 04/12] ftrfs: add directory operations Aurelien DESBRIERES
                     ` (8 subsequent siblings)
  12 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-14 12:07 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-kernel, torvalds, willy, djwong, adilger.kernel,
	pedro.falcato, xiang, Aurelien DESBRIERES

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 <aurelien@hackers.camp>
---
 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 000000000000..e1279c7968a3
--- /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 <aurelien@hackers.camp>
+ */
+
+#include <linux/fs.h>
+#include <linux/buffer_head.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v3 04/12] ftrfs: add directory operations
  2026-04-14 12:07 ` [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                     ` (3 preceding siblings ...)
  2026-04-14 12:07   ` [PATCH v3 03/12] ftrfs: add inode operations Aurelien DESBRIERES
@ 2026-04-14 12:07   ` Aurelien DESBRIERES
  2026-04-14 12:07   ` [PATCH v3 05/12] ftrfs: add file operations Aurelien DESBRIERES
                     ` (7 subsequent siblings)
  12 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-14 12:07 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-kernel, torvalds, willy, djwong, adilger.kernel,
	pedro.falcato, xiang, Aurelien DESBRIERES

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 <aurelien@hackers.camp>
---
 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 000000000000..dbf0102a40f3
--- /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 <aurelien@hackers.camp>
+ */
+
+#include <linux/fs.h>
+#include <linux/buffer_head.h>
+#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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v3 05/12] ftrfs: add file operations
  2026-04-14 12:07 ` [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                     ` (4 preceding siblings ...)
  2026-04-14 12:07   ` [PATCH v3 04/12] ftrfs: add directory operations Aurelien DESBRIERES
@ 2026-04-14 12:07   ` Aurelien DESBRIERES
  2026-04-14 12:07   ` [PATCH v3 06/12] ftrfs: add block and inode allocator Aurelien DESBRIERES
                     ` (6 subsequent siblings)
  12 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-14 12:07 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-kernel, torvalds, willy, djwong, adilger.kernel,
	pedro.falcato, xiang, Aurelien DESBRIERES

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 <aurelien@hackers.camp>
---
 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 000000000000..ef121359be77
--- /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 <aurelien@hackers.camp>
+ *
+ * NOTE: read/write use generic_file_* for now.
+ * The EDAC/RS layer will intercept at the block I/O level (next iteration).
+ */
+
+#include <linux/fs.h>
+#include <linux/mm.h>
+#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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v3 06/12] ftrfs: add block and inode allocator
  2026-04-14 12:07 ` [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                     ` (5 preceding siblings ...)
  2026-04-14 12:07   ` [PATCH v3 05/12] ftrfs: add file operations Aurelien DESBRIERES
@ 2026-04-14 12:07   ` Aurelien DESBRIERES
  2026-04-14 12:07   ` [PATCH v3 07/12] ftrfs: add filename and directory entry operations Aurelien DESBRIERES
                     ` (5 subsequent siblings)
  12 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-14 12:07 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-kernel, torvalds, willy, djwong, adilger.kernel,
	pedro.falcato, xiang, Aurelien DESBRIERES

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 <aurelien@hackers.camp>
---
 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 000000000000..753eb67cf9ff
--- /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 <aurelien@hackers.camp>
+ *
+ * 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 <linux/fs.h>
+#include <linux/buffer_head.h>
+#include <linux/bitmap.h>
+#include <linux/slab.h>
+#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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v3 07/12] ftrfs: add filename and directory entry operations
  2026-04-14 12:07 ` [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                     ` (6 preceding siblings ...)
  2026-04-14 12:07   ` [PATCH v3 06/12] ftrfs: add block and inode allocator Aurelien DESBRIERES
@ 2026-04-14 12:07   ` Aurelien DESBRIERES
  2026-04-14 12:07   ` [PATCH v3 08/12] ftrfs: add CRC32 checksumming and Reed-Solomon FEC skeleton Aurelien DESBRIERES
                     ` (4 subsequent siblings)
  12 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-14 12:07 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-kernel, torvalds, willy, djwong, adilger.kernel,
	pedro.falcato, xiang, Aurelien DESBRIERES

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 <aurelien@hackers.camp>
---
 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 000000000000..a8c1f79ebe44
--- /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 <aurelien@hackers.camp>
+ *
+ * Implements: create, mkdir, unlink, rmdir, link, rename
+ */
+
+#include <linux/fs.h>
+#include <linux/buffer_head.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v3 08/12] ftrfs: add CRC32 checksumming and Reed-Solomon FEC skeleton
  2026-04-14 12:07 ` [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                     ` (7 preceding siblings ...)
  2026-04-14 12:07   ` [PATCH v3 07/12] ftrfs: add filename and directory entry operations Aurelien DESBRIERES
@ 2026-04-14 12:07   ` Aurelien DESBRIERES
  2026-04-14 12:07   ` [PATCH v3 09/12] ftrfs: add Kconfig, Makefile and fs/ tree integration Aurelien DESBRIERES
                     ` (3 subsequent siblings)
  12 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-14 12:07 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-kernel, torvalds, willy, djwong, adilger.kernel,
	pedro.falcato, xiang, Aurelien DESBRIERES

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 <aurelien@hackers.camp>
---
 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 000000000000..ebe676c98be6
--- /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 <aurelien@hackers.camp>
+ */
+
+#include <linux/kernel.h>
+#include <linux/crc32.h>
+#include <linux/slab.h>
+#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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v3 09/12] ftrfs: add Kconfig, Makefile and fs/ tree integration
  2026-04-14 12:07 ` [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                     ` (8 preceding siblings ...)
  2026-04-14 12:07   ` [PATCH v3 08/12] ftrfs: add CRC32 checksumming and Reed-Solomon FEC skeleton Aurelien DESBRIERES
@ 2026-04-14 12:07   ` Aurelien DESBRIERES
  2026-04-14 12:07   ` [PATCH v3 10/12] MAINTAINERS: add entry for FTRFS filesystem Aurelien DESBRIERES
                     ` (2 subsequent siblings)
  12 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-14 12:07 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-kernel, torvalds, willy, djwong, adilger.kernel,
	pedro.falcato, xiang, Aurelien DESBRIERES

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 <aurelien@hackers.camp>
---
 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 000000000000..e23fea923488
--- /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 000000000000..a792286ec822
--- /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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v3 10/12] MAINTAINERS: add entry for FTRFS filesystem
  2026-04-14 12:07 ` [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                     ` (9 preceding siblings ...)
  2026-04-14 12:07   ` [PATCH v3 09/12] ftrfs: add Kconfig, Makefile and fs/ tree integration Aurelien DESBRIERES
@ 2026-04-14 12:07   ` Aurelien DESBRIERES
  2026-04-14 12:07   ` [PATCH v3 11/12] ftrfs: v2 fixes — write path, inode lifecycle, on-disk format Aurelien DESBRIERES
  2026-04-14 12:07   ` [PATCH v3 12/12] ftrfs: v3 — iomap IO path, rename, RS decoder, Radiation Event Journal Aurelien DESBRIERES
  12 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-14 12:07 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-kernel, torvalds, willy, djwong, adilger.kernel,
	pedro.falcato, xiang, Aurelien DESBRIERES

Signed-off-by: Aurelien DESBRIERES <aurelien@hackers.camp>
---
 MAINTAINERS | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index d1cc0e12fe1f..f99e1219f67c 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 <aurelien@hackers.camp>
+L:	linux-fsdevel@vger.kernel.org
+S:	Maintained
+F:	fs/ftrfs/
+
 EXT2 FILE SYSTEM
 M:	Jan Kara <jack@suse.com>
 L:	linux-ext4@vger.kernel.org
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v3 11/12] ftrfs: v2 fixes — write path, inode lifecycle, on-disk format
  2026-04-14 12:07 ` [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                     ` (10 preceding siblings ...)
  2026-04-14 12:07   ` [PATCH v3 10/12] MAINTAINERS: add entry for FTRFS filesystem Aurelien DESBRIERES
@ 2026-04-14 12:07   ` Aurelien DESBRIERES
  2026-04-14 12:07   ` [PATCH v3 12/12] ftrfs: v3 — iomap IO path, rename, RS decoder, Radiation Event Journal Aurelien DESBRIERES
  12 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-14 12:07 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-kernel, torvalds, willy, djwong, adilger.kernel,
	pedro.falcato, xiang, Aurelien DESBRIERES

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 <aurelien@hackers.camp>
---
 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 dbf0102a40f3..fd06910bf9cc 100644
--- a/fs/ftrfs/dir.c
+++ b/fs/ftrfs/dir.c
@@ -3,7 +3,6 @@
  * FTRFS — Directory operations
  * Author: roastercode - Aurelien DESBRIERES <aurelien@hackers.camp>
  */
-
 #include <linux/fs.h>
 #include <linux/buffer_head.h>
 #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 ef121359be77..1807ac698d7d 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 <aurelien@hackers.camp>
- *
- * NOTE: read/write use generic_file_* for now.
- * The EDAC/RS layer will intercept at the block I/O level (next iteration).
  */
-
 #include <linux/fs.h>
 #include <linux/mm.h>
+#include <linux/buffer_head.h>
+#include <linux/mpage.h>
 #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 82502c9fb07d..1f2d8a954814 100644
--- a/fs/ftrfs/ftrfs.h
+++ b/fs/ftrfs/ftrfs.h
@@ -13,6 +13,10 @@
 #include <linux/fs_context.h>
 #include <linux/types.h>
 
+/* 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 e1279c7968a3..f655ccbd9919 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 a8c1f79ebe44..d37ad8b7bc5b 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 8acc6292124b..bce7148ee3e2 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


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v3 12/12] ftrfs: v3 — iomap IO path, rename, RS decoder, Radiation Event Journal
  2026-04-14 12:07 ` [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
                     ` (11 preceding siblings ...)
  2026-04-14 12:07   ` [PATCH v3 11/12] ftrfs: v2 fixes — write path, inode lifecycle, on-disk format Aurelien DESBRIERES
@ 2026-04-14 12:07   ` Aurelien DESBRIERES
  12 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-14 12:07 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: linux-kernel, torvalds, willy, djwong, adilger.kernel,
	pedro.falcato, xiang, Aurelien DESBRIERES

This commit brings FTRFS to v3, addressing all major feedback from the
RFC v2 review on linux-fsdevel (April 14, 2026).

Changes since v2 (ce07d54dc):

fs/ftrfs/file.c:
  - Replace buffer_head IO path with iomap API (Matthew Wilcox review)
  - Implement ftrfs_iomap_begin/end (iomap_ops)
  - Implement ftrfs_iomap_write_ops (get_folio/put_folio)
  - Implement ftrfs_writeback_ops (writeback_range/writeback_submit)
  - Read path uses iomap_bio_read_ops + iomap_read_folio_ctx
  - Remove <linux/buffer_head.h> and <linux/mpage.h> from file.c

fs/ftrfs/namei.c:
  - Implement ftrfs_rename: same-dir and cross-dir rename for files
    and directories, '..' update, nlink fixup
  - RENAME_EXCHANGE and RENAME_WHITEOUT return -EINVAL (v1)
  - Add .rename to ftrfs_dir_inode_operations

fs/ftrfs/edac.c:
  - Implement full RS(255,239) decoder
  - Berlekamp-Massey error locator polynomial
  - Chien search for error positions
  - Forney algorithm for in-place magnitude correction
  - Corrects up to 8 symbol errors per 255-byte subblock
  - Returns -EBADMSG if uncorrectable (> 8 errors)

fs/ftrfs/super.c:
  - Add ftrfs_log_rs_event(): persistent radiation event logging
  - Ring buffer of 64 x 24 bytes in superblock reserved area
  - Written under spinlock, marks superblock buffer dirty

fs/ftrfs/ftrfs.h:
  - Add struct ftrfs_rs_event (24 bytes, __packed)
  - Add FTRFS_RS_JOURNAL_SIZE = 64
  - Replace 3980-byte s_pad with s_rs_journal[64] + s_pad[2443]
  - sizeof(ftrfs_super_block) == 4096 enforced by BUILD_BUG_ON
  - Add ftrfs_log_rs_event() prototype
  - inode_state_read_once compat macro for kernel < 7.0

Testing:
  - qemuarm64, kernel 7.0 final (Yocto Styhead 5.1)
  - mount, write, read, rename, umount: 0 BUG/WARN/Oops
  - xfstests generic/001, 002, 010 equivalent: all pass
  - Slurm 25.11.4 HPC cluster: 3-node parallel jobs validated

Assisted-by: Claude:claude-sonnet-4-6
Signed-off-by: Aurelien DESBRIERES <aurelien@hackers.camp>
---
 fs/ftrfs/edac.c  | 197 ++++++++++++++++++++++++++++++++++++++++++++++-
 fs/ftrfs/file.c  | 173 ++++++++++++++++++++++++++++++-----------
 fs/ftrfs/ftrfs.h |  20 ++++-
 fs/ftrfs/namei.c | 113 +++++++++++++++++++++++++++
 fs/ftrfs/super.c |  41 ++++++++++
 5 files changed, 498 insertions(+), 46 deletions(-)

diff --git a/fs/ftrfs/edac.c b/fs/ftrfs/edac.c
index ebe676c98be6..8cc449363037 100644
--- a/fs/ftrfs/edac.c
+++ b/fs/ftrfs/edac.c
@@ -60,13 +60,206 @@ int ftrfs_rs_encode(uint8_t *data, uint8_t *parity)
 	return 0;
 }
 
-/* Reed-Solomon decoding (simplified for now) */
+/* Galois Field power */
+static uint8_t gf_pow(uint8_t x, int power)
+{
+	return gf_exp[(gf_log[x] * power) % 255];
+}
+
+/* Galois Field inverse */
+static uint8_t gf_inv(uint8_t x)
+{
+	return gf_exp[255 - gf_log[x]];
+}
+
+/*
+ * Compute RS syndromes.
+ * Returns true if all syndromes zero (no error).
+ */
+static bool rs_calc_syndromes(uint8_t *msg, int msglen, uint8_t *syndromes)
+{
+	bool all_zero = true;
+	int i, j;
+
+	for (i = 0; i < FTRFS_RS_PARITY; i++) {
+		syndromes[i] = 0;
+		for (j = 0; j < msglen; j++)
+			syndromes[i] ^= gf_mul(msg[j], gf_pow(gf_exp[1], i * j));
+		if (syndromes[i])
+			all_zero = false;
+	}
+	return all_zero;
+}
+
+/*
+ * Berlekamp-Massey — find error locator polynomial.
+ * Returns number of errors, or -1 if uncorrectable.
+ */
+static int rs_berlekamp_massey(uint8_t *syndromes, uint8_t *err_loc,
+			       int *err_loc_len)
+{
+	uint8_t old_loc[FTRFS_RS_PARITY + 1];
+	uint8_t tmp[FTRFS_RS_PARITY + 1];
+	uint8_t new_loc[FTRFS_RS_PARITY + 1];
+	int old_len = 1;
+	int i, j, nerr, new_len;
+	uint8_t delta;
+
+	err_loc[0] = 1;
+	*err_loc_len = 1;
+	old_loc[0] = 1;
+
+	for (i = 0; i < FTRFS_RS_PARITY; i++) {
+		delta = syndromes[i];
+		for (j = 1; j < *err_loc_len; j++)
+			delta ^= gf_mul(err_loc[j], syndromes[i - j]);
+
+		tmp[0] = 0;
+		memcpy(tmp + 1, old_loc, old_len);
+
+		if (delta == 0) {
+			old_len++;
+		} else if (2 * (*err_loc_len - 1) <= i) {
+			new_len = old_len + 1;
+			for (j = 0; j < new_len; j++) {
+				new_loc[j] = (j < *err_loc_len) ? err_loc[j] : 0;
+				new_loc[j] ^= gf_mul(delta, tmp[j]);
+			}
+			memcpy(old_loc, err_loc, *err_loc_len);
+			old_len = *err_loc_len + 1 - new_len + old_len;
+			memcpy(err_loc, new_loc, new_len);
+			*err_loc_len = new_len;
+		} else {
+			for (j = 0; j < *err_loc_len; j++)
+				err_loc[j] ^= gf_mul(delta, tmp[j]);
+			old_len++;
+		}
+	}
+
+	nerr = *err_loc_len - 1;
+	if (nerr > FTRFS_RS_PARITY / 2)
+		return -1;
+	return nerr;
+}
+
+/*
+ * Chien search — find roots of error locator polynomial.
+ * Returns number of roots found.
+ */
+static int rs_chien_search(uint8_t *err_loc, int err_loc_len,
+			   int msglen, int *errs)
+{
+	int nerrs = 0;
+	int i, j;
+	uint8_t val;
+
+	for (i = 0; i < msglen; i++) {
+		val = 0;
+		for (j = 0; j < err_loc_len; j++)
+			val ^= gf_mul(err_loc[j], gf_pow(gf_exp[1], i * j));
+		if (val == 0)
+			errs[nerrs++] = msglen - 1 - i;
+	}
+	return nerrs;
+}
+
+/*
+ * Forney algorithm — compute and apply error corrections.
+ */
+static void rs_forney(uint8_t *msg, uint8_t *syndromes,
+		      uint8_t *err_loc, int err_loc_len,
+		      int *errs, int nerrs)
+{
+	uint8_t omega[FTRFS_RS_PARITY];
+	uint8_t err_loc_prime[FTRFS_RS_PARITY];
+	int i, j;
+
+	memset(omega, 0, sizeof(omega));
+	for (i = 0; i < FTRFS_RS_PARITY; i++) {
+		for (j = 0; j < err_loc_len && j <= i; j++)
+			omega[i] ^= gf_mul(syndromes[i - j], err_loc[j]);
+	}
+
+	memset(err_loc_prime, 0, sizeof(err_loc_prime));
+	for (i = 1; i < err_loc_len; i += 2)
+		err_loc_prime[i - 1] = err_loc[i];
+
+	for (i = 0; i < nerrs; i++) {
+		uint8_t xi     = gf_pow(gf_exp[1], errs[i]);
+		uint8_t xi_inv = gf_inv(xi);
+		uint8_t omega_val = 0;
+		uint8_t elp_val   = 0;
+
+		for (j = FTRFS_RS_PARITY - 1; j >= 0; j--)
+			omega_val = gf_mul(omega_val, xi_inv) ^ omega[j];
+
+		for (j = (err_loc_len - 1) & ~1; j >= 0; j -= 2)
+			elp_val = gf_mul(elp_val, gf_mul(xi_inv, xi_inv))
+				  ^ err_loc_prime[j];
+
+		if (elp_val == 0)
+			continue;
+
+		msg[errs[i]] ^= gf_mul(gf_mul(xi, omega_val), gf_inv(elp_val));
+	}
+}
+
+/*
+ * ftrfs_rs_decode - decode and correct a RS(255,239) codeword in place.
+ * @data:   FTRFS_SUBBLOCK_DATA bytes of data (corrected in place)
+ * @parity: FTRFS_RS_PARITY bytes of parity
+ *
+ * Returns 0 if no errors or errors corrected,
+ * -EBADMSG if uncorrectable (> 8 symbol errors).
+ */
 int ftrfs_rs_decode(uint8_t *data, uint8_t *parity)
 {
+	uint8_t msg[FTRFS_SUBBLOCK_DATA + FTRFS_RS_PARITY];
+	uint8_t syndromes[FTRFS_RS_PARITY];
+	uint8_t err_loc[FTRFS_RS_PARITY + 1];
+	int err_loc_len;
+	int errs[FTRFS_RS_PARITY / 2];
+	int nerrs, nroots;
+
 	if (!rs_initialized)
 		init_gf_tables();
 
-	/* For now, assume no errors (full decoding to be implemented) */
+	memcpy(msg, data, FTRFS_SUBBLOCK_DATA);
+	memcpy(msg + FTRFS_SUBBLOCK_DATA, parity, FTRFS_RS_PARITY);
+
+	/* Step 1: syndromes */
+	if (rs_calc_syndromes(msg, FTRFS_SUBBLOCK_DATA + FTRFS_RS_PARITY,
+			      syndromes))
+		return 0; /* no errors */
+
+	/* Step 2: Berlekamp-Massey */
+	memset(err_loc, 0, sizeof(err_loc));
+	nerrs = rs_berlekamp_massey(syndromes, err_loc, &err_loc_len);
+	if (nerrs < 0) {
+		pr_err_ratelimited("ftrfs: RS block uncorrectable\n");
+		return -EBADMSG;
+	}
+
+	/* Step 3: Chien search */
+	nroots = rs_chien_search(err_loc, err_loc_len,
+				 FTRFS_SUBBLOCK_DATA + FTRFS_RS_PARITY,
+				 errs);
+	if (nroots != nerrs) {
+		pr_err_ratelimited("ftrfs: RS Chien search mismatch\n");
+		return -EBADMSG;
+	}
+
+	/* Step 4: Forney corrections */
+	rs_forney(msg, syndromes, err_loc, err_loc_len, errs, nerrs);
+
+	/* Step 5: verify */
+	if (!rs_calc_syndromes(msg, FTRFS_SUBBLOCK_DATA + FTRFS_RS_PARITY,
+			       syndromes)) {
+		pr_err_ratelimited("ftrfs: RS correction failed verification\n");
+		return -EBADMSG;
+	}
+
+	memcpy(data, msg, FTRFS_SUBBLOCK_DATA);
 	return 0;
 }
 
diff --git a/fs/ftrfs/file.c b/fs/ftrfs/file.c
index 1807ac698d7d..2520896c71e3 100644
--- a/fs/ftrfs/file.c
+++ b/fs/ftrfs/file.c
@@ -5,14 +5,17 @@
  */
 #include <linux/fs.h>
 #include <linux/mm.h>
-#include <linux/buffer_head.h>
-#include <linux/mpage.h>
+#include <linux/iomap.h>
+#include <linux/pagemap.h>
 #include "ftrfs.h"
 
+/* Forward declaration — defined after iomap_ops */
+static ssize_t ftrfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from);
+
 const struct file_operations ftrfs_file_operations = {
 	.llseek         = generic_file_llseek,
 	.read_iter      = generic_file_read_iter,
-	.write_iter     = generic_file_write_iter,
+	.write_iter     = ftrfs_file_write_iter,
 	.mmap           = generic_file_mmap,
 	.fsync          = generic_file_fsync,
 	.splice_read    = filemap_splice_read,
@@ -23,88 +26,172 @@ const struct inode_operations ftrfs_file_inode_operations = {
 };
 
 /*
- * ftrfs_get_block — map logical block to physical block
- * Handles allocation when create=1, returns -EIO for holes on read.
+ * ftrfs_iomap_begin — map a file range to disk blocks for iomap.
+ * Handles read (no allocation) and write (allocate on demand).
+ * Only direct blocks supported (max 48 KiB per file).
  */
-static int ftrfs_get_block(struct inode *inode, sector_t iblock,
-			   struct buffer_head *bh_result, int create)
+static int ftrfs_iomap_begin(struct inode *inode, loff_t pos, loff_t length,
+			     unsigned int flags, struct iomap *iomap,
+			     struct iomap *srcmap)
 {
-	struct ftrfs_inode_info *fi = FTRFS_I(inode);
-	u64 new_block;
-	__le64 phys;
+	struct ftrfs_inode_info *fi  = FTRFS_I(inode);
+	struct super_block      *sb  = inode->i_sb;
+	u64  iblock    = pos >> FTRFS_BLOCK_SHIFT;
+	u64  new_block;
+	u64  phys;
 
 	if (iblock >= FTRFS_DIRECT_BLOCKS) {
-		pr_err("ftrfs: indirect block not yet supported\n");
+		pr_err_ratelimited("ftrfs: iomap: offset beyond direct blocks\n");
 		return -EOPNOTSUPP;
 	}
 
-	phys = fi->i_direct[iblock];
+	iomap->offset = iblock << FTRFS_BLOCK_SHIFT;
+	iomap->length = FTRFS_BLOCK_SIZE;
+	iomap->bdev   = sb->s_bdev;
+	iomap->flags  = 0;
+
+	phys = le64_to_cpu(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;
+		iomap->type = IOMAP_MAPPED;
+		iomap->addr = phys << FTRFS_BLOCK_SHIFT;
 		return 0;
 	}
 
-	if (!create)
-		return -EIO;
+	/* Hole on read — do not allocate */
+	if (!(flags & IOMAP_WRITE)) {
+		iomap->type = IOMAP_HOLE;
+		iomap->addr = IOMAP_NULL_ADDR;
+		return 0;
+	}
 
-	new_block = ftrfs_alloc_block(inode->i_sb);
+	/* Allocate a new block for write */
+	new_block = ftrfs_alloc_block(sb);
 	if (!new_block) {
-		pr_err("ftrfs: no free blocks\n");
+		pr_err("ftrfs: iomap: 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);
+	mark_inode_dirty(inode);
+
+	iomap->type = IOMAP_MAPPED;
+	iomap->addr = new_block << FTRFS_BLOCK_SHIFT;
 	return 0;
 }
 
-static int ftrfs_read_folio(struct file *file, struct folio *folio)
+static int ftrfs_iomap_end(struct inode *inode, loff_t pos, loff_t length,
+			   ssize_t written, unsigned int flags,
+			   struct iomap *iomap)
 {
-	return block_read_full_folio(folio, ftrfs_get_block);
+	return 0;
 }
 
-static int ftrfs_writepages(struct address_space *mapping,
-			    struct writeback_control *wbc)
+const struct iomap_ops ftrfs_iomap_ops = {
+	.iomap_begin = ftrfs_iomap_begin,
+	.iomap_end   = ftrfs_iomap_end,
+};
+
+/*
+ * Write path — ftrfs_iomap_write_ops
+ * get_folio/put_folio use generic helpers (no journaling required).
+ */
+static struct folio *ftrfs_iomap_get_folio(struct iomap_iter *iter,
+					   loff_t pos, unsigned int len)
 {
-	return mpage_writepages(mapping, wbc, ftrfs_get_block);
+	return iomap_get_folio(iter, pos, len);
 }
 
-static void ftrfs_readahead(struct readahead_control *rac)
+static void ftrfs_iomap_put_folio(struct inode *inode, loff_t pos,
+				  unsigned int copied, struct folio *folio)
 {
-	mpage_readahead(rac, ftrfs_get_block);
+	folio_unlock(folio);
+	folio_put(folio);
 }
 
-static int ftrfs_write_begin(const struct kiocb *iocb,
-			     struct address_space *mapping,
-			     loff_t pos, unsigned int len,
-			     struct folio **foliop, void **fsdata)
+static const struct iomap_write_ops ftrfs_iomap_write_ops = {
+	.get_folio = ftrfs_iomap_get_folio,
+	.put_folio = ftrfs_iomap_put_folio,
+};
+
+static ssize_t ftrfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
 {
-	return block_write_begin(mapping, pos, len, foliop, ftrfs_get_block);
+	return iomap_file_buffered_write(iocb, from, &ftrfs_iomap_ops,
+					 &ftrfs_iomap_write_ops, NULL);
 }
 
-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)
+/*
+ * Writeback path — ftrfs_writeback_ops
+ */
+static ssize_t ftrfs_writeback_range(struct iomap_writepage_ctx *wpc,
+				  struct folio *folio, u64 offset,
+				  unsigned int len, u64 end_pos)
 {
-	return generic_write_end(iocb, mapping, pos, len, copied, folio, fsdata);
+	if (offset < wpc->iomap.offset ||
+	    offset >= wpc->iomap.offset + wpc->iomap.length) {
+		int ret;
+
+		memset(&wpc->iomap, 0, sizeof(wpc->iomap));
+		ret = ftrfs_iomap_begin(wpc->inode,
+					offset, INT_MAX, 0,
+					&wpc->iomap, NULL);
+		if (ret)
+			return ret;
+	}
+	return iomap_add_to_ioend(wpc, folio, offset, end_pos, len);
+}
+
+static const struct iomap_writeback_ops ftrfs_writeback_ops = {
+	.writeback_range  = ftrfs_writeback_range,
+	.writeback_submit = iomap_ioend_writeback_submit,
+};
+
+static int ftrfs_writepages(struct address_space *mapping,
+			    struct writeback_control *wbc)
+{
+	struct iomap_writepage_ctx wpc = {
+		.inode = mapping->host,
+		.wbc   = wbc,
+		.ops   = &ftrfs_writeback_ops,
+	};
+
+	return iomap_writepages(&wpc);
+}
+
+/*
+ * Read path — uses iomap_bio_read_ops (kernel-provided)
+ */
+static int ftrfs_read_folio(struct file *file, struct folio *folio)
+{
+	struct iomap_read_folio_ctx ctx = {
+		.ops       = &iomap_bio_read_ops,
+		.cur_folio = folio,
+	};
+
+	iomap_read_folio(&ftrfs_iomap_ops, &ctx, NULL);
+	return 0;
+}
+
+static void ftrfs_readahead(struct readahead_control *rac)
+{
+	struct iomap_read_folio_ctx ctx = {
+		.ops = &iomap_bio_read_ops,
+		.rac = rac,
+	};
+
+	iomap_readahead(&ftrfs_iomap_ops, &ctx, NULL);
 }
 
 static sector_t ftrfs_bmap(struct address_space *mapping, sector_t block)
 {
-	return generic_block_bmap(mapping, block, ftrfs_get_block);
+	return iomap_bmap(mapping, block, &ftrfs_iomap_ops);
 }
 
 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,
+	.dirty_folio      = iomap_dirty_folio,
+	.invalidate_folio = iomap_invalidate_folio,
+	.release_folio    = iomap_release_folio,
 };
diff --git a/fs/ftrfs/ftrfs.h b/fs/ftrfs/ftrfs.h
index 1f2d8a954814..f75709aee0ee 100644
--- a/fs/ftrfs/ftrfs.h
+++ b/fs/ftrfs/ftrfs.h
@@ -35,6 +35,21 @@
 #define FTRFS_INDIRECT_BLOCKS 1
 #define FTRFS_DINDIRECT_BLOCKS 1
 
+/*
+ * Radiation Event Journal entry — 24 bytes
+ * Records each RS FEC correction event persistently in the superblock.
+ * 64 entries give operators a map of physical degradation over time.
+ * No existing Linux filesystem provides this at the block layer.
+ */
+struct ftrfs_rs_event {
+	__le64  re_block_no;    /* corrected block number */
+	__le64  re_timestamp;   /* nanoseconds since boot  */
+	__le32  re_error_bits;  /* number of symbols corrected */
+	__le32  re_crc32;       /* CRC32 of this entry */
+} __packed;                 /* 24 bytes */
+
+#define FTRFS_RS_JOURNAL_SIZE  64   /* entries in the radiation event journal */
+
 /*
  * On-disk superblock — block 0
  * Total size: fits in one 4096-byte block
@@ -53,7 +68,9 @@ 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[3980];        /* Padding to 4096 bytes */
+	 struct ftrfs_rs_event s_rs_journal[FTRFS_RS_JOURNAL_SIZE]; /* 1536 bytes */
+	__u8    s_rs_journal_head;  /* next write index (ring buffer) */
+	__u8    s_pad[2443];        /* Padding to 4096 bytes */
 } __packed;
 
 /*
@@ -142,6 +159,7 @@ static inline struct ftrfs_sb_info *FTRFS_SB(struct super_block *sb)
 /* Function prototypes */
 /* super.c */
 int ftrfs_fill_super(struct super_block *sb, struct fs_context *fc);
+void ftrfs_log_rs_event(struct super_block *sb, u64 block_no, u32 err_bits);
 
 /* inode.c */
 struct inode *ftrfs_iget(struct super_block *sb, unsigned long ino);
diff --git a/fs/ftrfs/namei.c b/fs/ftrfs/namei.c
index d37ad8b7bc5b..63fe96d219bf 100644
--- a/fs/ftrfs/namei.c
+++ b/fs/ftrfs/namei.c
@@ -425,6 +425,118 @@ int ftrfs_write_inode(struct inode *inode, struct writeback_control *wbc)
 /* dir inode_operations — exported                                     */
 /* ------------------------------------------------------------------ */
 
+/* ------------------------------------------------------------------ */
+/* rename — move/rename a directory entry                              */
+/* ------------------------------------------------------------------ */
+
+static int ftrfs_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);
+	int	      is_dir    = S_ISDIR(old_inode->i_mode);
+	int	      ret;
+
+	/* FTRFS v1: no RENAME_EXCHANGE or RENAME_WHITEOUT */
+	if (flags & ~RENAME_NOREPLACE)
+		return -EINVAL;
+
+	if (flags & RENAME_NOREPLACE && new_inode)
+		return -EEXIST;
+
+	/*
+	 * If destination exists, unlink it first.
+	 * For directories: target must be empty (nlink == 2: . and ..)
+	 */
+	if (new_inode) {
+		if (is_dir) {
+			if (new_inode->i_nlink > 2)
+				return -ENOTEMPTY;
+		}
+
+		ret = ftrfs_del_dirent(new_dir, &new_dentry->d_name);
+		if (ret)
+			return ret;
+
+		if (is_dir) {
+			inode_dec_link_count(new_inode);
+			inode_dec_link_count(new_inode);
+			inode_dec_link_count(new_dir);
+		} else {
+			inode_dec_link_count(new_inode);
+		}
+
+		inode_set_ctime_to_ts(new_inode, current_time(new_inode));
+	}
+
+	/* Add entry in new_dir */
+	ret = ftrfs_add_dirent(new_dir, &new_dentry->d_name,
+			       old_inode->i_ino,
+			       is_dir ? 4 /* DT_DIR */ : 1 /* DT_REG */);
+	if (ret)
+		return ret;
+
+	/* Remove entry from old_dir */
+	ret = ftrfs_del_dirent(old_dir, &old_dentry->d_name);
+	if (ret) {
+		pr_err("ftrfs: rename: del_dirent failed after add, fs may be inconsistent\n");
+		return ret;
+	}
+
+	/*
+	 * Update ".." in the moved directory to point to new_dir.
+	 * Also fix nlink on old_dir and new_dir.
+	 */
+	if (is_dir && old_dir != new_dir) {
+		struct qstr dotdot = QSTR_INIT("..", 2);
+
+		ret = ftrfs_del_dirent(old_inode, &dotdot);
+		if (ret)
+			return ret;
+
+		ret = ftrfs_add_dirent(old_inode, &dotdot,
+				       new_dir->i_ino, 4 /* DT_DIR */);
+		if (ret)
+			return ret;
+
+		inode_dec_link_count(old_dir);
+		inode_inc_link_count(new_dir);
+
+		ret = ftrfs_write_inode_raw(old_inode);
+		if (ret)
+			return ret;
+	}
+
+	/* Update timestamps */
+	inode_set_ctime_to_ts(old_inode, current_time(old_inode));
+	inode_set_mtime_to_ts(old_dir,   current_time(old_dir));
+	inode_set_ctime_to_ts(old_dir,   current_time(old_dir));
+	inode_set_mtime_to_ts(new_dir,   current_time(new_dir));
+	inode_set_ctime_to_ts(new_dir,   current_time(new_dir));
+
+	/* Persist all touched inodes */
+	ret = ftrfs_write_inode_raw(old_inode);
+	if (ret)
+		return ret;
+
+	ret = ftrfs_write_inode_raw(old_dir);
+	if (ret)
+		return ret;
+
+	if (old_dir != new_dir) {
+		ret = ftrfs_write_inode_raw(new_dir);
+		if (ret)
+			return ret;
+	}
+
+	if (new_inode)
+		ftrfs_write_inode_raw(new_inode);
+
+	return 0;
+}
+
 const struct inode_operations ftrfs_dir_inode_operations = {
 	.lookup  = ftrfs_lookup,
 	.create  = ftrfs_create,
@@ -432,4 +544,5 @@ const struct inode_operations ftrfs_dir_inode_operations = {
 	.unlink  = ftrfs_unlink,
 	.rmdir   = ftrfs_rmdir,
 	.link    = ftrfs_link,
+	.rename  = ftrfs_rename,
 };
diff --git a/fs/ftrfs/super.c b/fs/ftrfs/super.c
index bce7148ee3e2..52d9b0b6f916 100644
--- a/fs/ftrfs/super.c
+++ b/fs/ftrfs/super.c
@@ -88,6 +88,47 @@ static const struct super_operations ftrfs_super_ops = {
 	.statfs         = ftrfs_statfs,
 };
 
+/*
+ * ftrfs_log_rs_event - record a Reed-Solomon correction in the superblock
+ * @sb:        mounted superblock
+ * @block_no:  block number where correction occurred
+ * @err_bits:  number of symbols corrected
+ *
+ * Writes to the persistent ring buffer in the superblock.
+ * Safe to call from any context (spinlock protected).
+ */
+void ftrfs_log_rs_event(struct super_block *sb, u64 block_no, u32 err_bits)
+{
+	struct ftrfs_sb_info     *sbi = FTRFS_SB(sb);
+	struct ftrfs_super_block *fsb;
+	struct ftrfs_rs_event    *ev;
+	u8 head;
+
+	if (!sbi || !sbi->s_sbh)
+		return;
+
+	spin_lock(&sbi->s_lock);
+
+	fsb  = (struct ftrfs_super_block *)sbi->s_sbh->b_data;
+	head = fsb->s_rs_journal_head % FTRFS_RS_JOURNAL_SIZE;
+	ev   = &fsb->s_rs_journal[head];
+
+	ev->re_block_no   = cpu_to_le64(block_no);
+	ev->re_timestamp  = cpu_to_le64(ktime_get_ns());
+	ev->re_error_bits = cpu_to_le32(err_bits);
+	ev->re_crc32      = cpu_to_le32(
+		ftrfs_crc32(ev, offsetof(struct ftrfs_rs_event, re_crc32)));
+
+	fsb->s_rs_journal_head = (head + 1) % FTRFS_RS_JOURNAL_SIZE;
+
+	mark_buffer_dirty(sbi->s_sbh);
+
+	spin_unlock(&sbi->s_lock);
+
+	pr_debug("ftrfs: RS correction block=%llu symbols=%u\n",
+		 block_no, err_bits);
+}
+
 /*
  * ftrfs_fill_super — read superblock from disk and initialize VFS sb
  */
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 30+ messages in thread

* Re: [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem
  2026-04-14 11:28       ` Pedro Falcato
@ 2026-04-14 13:46         ` Aurelien DESBRIERES
  0 siblings, 0 replies; 30+ messages in thread
From: Aurelien DESBRIERES @ 2026-04-14 13:46 UTC (permalink / raw)
  To: Pedro Falcato
  Cc: Joshua Peisach, linux-fsdevel, linux-kernel, willy, djwong,
	adilger, xiang

On Tue, Apr 14, 2026 at 12:28:33PM +0100, Pedro Falcato wrote:
> Yes, a paper is provided. However, when I asked for a usecase I
> really did mean: "is anyone going to actually use this?".
>
> Good answers (IMHO):
> - "Yes, we have a couple of planned users for this"
> - "Yes, this has been maintained out-of-tree for X months/years"
> - "Yes, we are using it"

Fair. Let me give a more concrete answer.

The implementation is currently maintained out-of-tree and validated
in an arm64 HPC cluster running Slurm 25.11.4 on Yocto Styhead 5.1,
kernel 7.0. This is an active, working deployment, not a paper
prototype:
https://github.com/roastercode/yocto-hardened/tree/arm64-ftrfs

For planned users, the target environments where Linux is actively
used and where radiation-induced SEU is a documented operational
concern include:

- Embedded Linux on nanosatellites and CubeSats. The MOVE-II mission
  at TU Munich is the direct origin of the FTRFS design. Commercial
  CubeSat operators increasingly use Linux-capable SoCs (i.MX, Zynq)
  precisely because of the ecosystem, and SEU rates on MRAM in LEO
  are well-documented.

- Robotics in high-radiation environments. Industrial robots deployed
  in nuclear facilities (decommissioning, inspection) and planetary
  rovers operate in radiation environments where silent bit flips are
  a real operational risk. Linux is the dominant OS in these systems.

- Space HPC. There is growing interest in deploying HPC workloads
  in orbit for Earth observation and AI inference at the edge.
  European space industry players (including Thales Alenia Space,
  a known Linux embedded user) are actively exploring this. A
  radiation-tolerant filesystem is a missing piece of that stack.

None of these are signed contracts I can point to today. This is an
RFC, not a production submission. The question for the community is
whether the technical problem is real and the approach sound — both
of which are independent of whether I have a purchase order in hand.

The radio silence you mention was a process failure on my part.
Replies to v1 review comments should have come before v2, not after
v3. That is corrected now.

Aurelien DESBRIERES <aurelien@hackers.camp>

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 08/11] ftrfs: add CRC32 checksumming and Reed-Solomon FEC skeleton
  2026-04-13 23:05 ` [PATCH v2 08/11] ftrfs: add CRC32 checksumming and Reed-Solomon FEC skeleton Aurelien DESBRIERES
@ 2026-04-14 17:34   ` Eric Biggers
  0 siblings, 0 replies; 30+ messages in thread
From: Eric Biggers @ 2026-04-14 17:34 UTC (permalink / raw)
  To: Aurelien DESBRIERES
  Cc: linux-fsdevel, linux-kernel, viro, brauner, willy, djwong,
	adilger, pfalcato

On Tue, Apr 14, 2026 at 01:05:49AM +0200, Aurelien DESBRIERES wrote:
> 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 <aurelien@hackers.camp>

Is there a reason why the kernel's existing Reed-Solomon
encoding/decoding library isn't being used?

- Eric

^ permalink raw reply	[flat|nested] 30+ messages in thread

end of thread, other threads:[~2026-04-14 17:34 UTC | newest]

Thread overview: 30+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-13 23:05 [PATCH v2 00/11] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
2026-04-13 23:05 ` [PATCH v2 01/11] ftrfs: add on-disk format and in-memory data structures Aurelien DESBRIERES
2026-04-13 23:05 ` [PATCH v2 02/11] ftrfs: add superblock operations Aurelien DESBRIERES
2026-04-13 23:05 ` [PATCH v2 03/11] ftrfs: add inode operations Aurelien DESBRIERES
2026-04-13 23:05 ` [PATCH v2 04/11] ftrfs: add directory operations Aurelien DESBRIERES
2026-04-13 23:05 ` [PATCH v2 05/11] ftrfs: add file operations Aurelien DESBRIERES
2026-04-13 23:05 ` [PATCH v2 06/11] ftrfs: add block and inode allocator Aurelien DESBRIERES
2026-04-13 23:05 ` [PATCH v2 07/11] ftrfs: add filename and directory entry operations Aurelien DESBRIERES
2026-04-13 23:05 ` [PATCH v2 08/11] ftrfs: add CRC32 checksumming and Reed-Solomon FEC skeleton Aurelien DESBRIERES
2026-04-14 17:34   ` Eric Biggers
2026-04-13 23:05 ` [PATCH v2 09/11] ftrfs: add Kconfig, Makefile and fs/ tree integration Aurelien DESBRIERES
2026-04-13 23:05 ` [PATCH v2 10/11] MAINTAINERS: add entry for FTRFS filesystem Aurelien DESBRIERES
2026-04-13 23:05 ` [PATCH v2 11/11] ftrfs: v2 fixes — write path, inode lifecycle, on-disk format Aurelien DESBRIERES
2026-04-14 12:07 ` [PATCH v3 00/12] ftrfs: Fault-Tolerant Radiation-Robust Filesystem Aurelien DESBRIERES
2026-04-14 10:22   ` Pedro Falcato
2026-04-14 11:05     ` Joshua Peisach
2026-04-14 11:28       ` Pedro Falcato
2026-04-14 13:46         ` Aurelien DESBRIERES
2026-04-14 12:07   ` [PATCH v3 01/12] ftrfs: add on-disk format and in-memory data structures Aurelien DESBRIERES
2026-04-14 12:07   ` [PATCH v3 02/12] ftrfs: add superblock operations Aurelien DESBRIERES
2026-04-14 12:07   ` [PATCH v3 03/12] ftrfs: add inode operations Aurelien DESBRIERES
2026-04-14 12:07   ` [PATCH v3 04/12] ftrfs: add directory operations Aurelien DESBRIERES
2026-04-14 12:07   ` [PATCH v3 05/12] ftrfs: add file operations Aurelien DESBRIERES
2026-04-14 12:07   ` [PATCH v3 06/12] ftrfs: add block and inode allocator Aurelien DESBRIERES
2026-04-14 12:07   ` [PATCH v3 07/12] ftrfs: add filename and directory entry operations Aurelien DESBRIERES
2026-04-14 12:07   ` [PATCH v3 08/12] ftrfs: add CRC32 checksumming and Reed-Solomon FEC skeleton Aurelien DESBRIERES
2026-04-14 12:07   ` [PATCH v3 09/12] ftrfs: add Kconfig, Makefile and fs/ tree integration Aurelien DESBRIERES
2026-04-14 12:07   ` [PATCH v3 10/12] MAINTAINERS: add entry for FTRFS filesystem Aurelien DESBRIERES
2026-04-14 12:07   ` [PATCH v3 11/12] ftrfs: v2 fixes — write path, inode lifecycle, on-disk format Aurelien DESBRIERES
2026-04-14 12:07   ` [PATCH v3 12/12] ftrfs: v3 — iomap IO path, rename, RS decoder, Radiation Event Journal Aurelien DESBRIERES

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox