* [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