public inbox for linux-fsdevel@vger.kernel.org
 help / color / mirror / Atom feed
From: Aurelien DESBRIERES <aurelien@hackers.camp>
To: linux-fsdevel@vger.kernel.org
Cc: linux-kernel@vger.kernel.org, torvalds@linux-foundation.org,
	willy@infradead.org, djwong@kernel.org, adilger.kernel@dilger.ca,
	pedro.falcato@gmail.com, xiang@kernel.org,
	Aurelien DESBRIERES <aurelien@hackers.camp>
Subject: [PATCH v3 12/12] ftrfs: v3 — iomap IO path, rename, RS decoder, Radiation Event Journal
Date: Tue, 14 Apr 2026 14:07:25 +0200	[thread overview]
Message-ID: <20260414120726.5713-13-aurelien@hackers.camp> (raw)
In-Reply-To: <20260414120726.5713-1-aurelien@hackers.camp>

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


      parent reply	other threads:[~2026-04-14 10:07 UTC|newest]

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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260414120726.5713-13-aurelien@hackers.camp \
    --to=aurelien@hackers.camp \
    --cc=adilger.kernel@dilger.ca \
    --cc=djwong@kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=pedro.falcato@gmail.com \
    --cc=torvalds@linux-foundation.org \
    --cc=willy@infradead.org \
    --cc=xiang@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox