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