From nobody Mon Jun 15 20:33:15 2026 Received: from mail-wr1-f50.google.com (mail-wr1-f50.google.com [209.85.221.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6E8A83A0E80 for ; Tue, 14 Apr 2026 10:07:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161253; cv=none; b=LjJrRWJDt76vP5proIHtb2Tf6lYPtDEZ2mAxn67ZjnTf3uc22iiWSbPdoYpYXbZH4Da2jeu/NXyuxaAx0SnkJMmC6P+nxlbV0NwcGVJSU1P78RxXYDM8N+YCC/S7t6qhQL/NCIWjr3VCsWNNYMPFWCb6aq9Jlz2I/rBwI6n8Iwo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161253; c=relaxed/simple; bh=itrD03klQ9bA3/sAEfiPEAkXjueLaEUBe2quSltxtGA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=VoPZiIjbZ18q+0JE/ytRLLJbSvwhed6Hnv3r54UW6eeOl1fYHRaHvfsWB6a9d7Ja4SNQ66xSEGvILeRMGHuW2QD+Kwe41bXkC+uLBCGd8ol42Xt4u/4W3g5aG9rkemIQMANowJu6Q7i/ItyXbtkleL7RzHUCcCAKgnEwgoKQ+u0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.221.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-wr1-f50.google.com with SMTP id ffacd0b85a97d-43d6fc30460so124482f8f.1 for ; Tue, 14 Apr 2026 03:07:31 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776161250; x=1776766050; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=wIn2axq2pi3zG85dKaQsEJL0Ag0qW3bpo8I7SAoaVl4=; b=jTCAWyn+pRtKe19Thub+8Xy6W8+Bnj6iwh9CS3do22tDCbYWN/uBdxm+ovxD7KKdAj lec59XxR118LKNfIf3Ip596G5bEuhsyafK3VvA3U+YZSHb87PxfKp8g0llGgkpjWw4pu pnDnWzgBoI86MEJaCA6t+8vwkm0z0Tfv3iIdzA200LxVooBR9N3/nQ+fVzN67H89pmyF tH5lMu02RZRUYKcmpA4QfbcIgHS4+ujshmLF/fT0GwzphZis5sZeCE7yiakcUuOPm4RP 8t2+L5TUyhWTxXRbCQzVofVpBDw/WaAI5KlUPUoKkhxGUbxKkkhK0vyeuj0aDAVxuBZC 94hg== X-Gm-Message-State: AOJu0YxLP6rI8xnFJ9BqpraW5tjE3h4154OY6jJD5wXdVdXfEF1tsm1S GUFUqOTUTtOH4ynVxV4VGIpDyAUADr8QOe1cGEIVpSkzN2gIzY2NJDle X-Gm-Gg: AeBDiet86nXoMnkDtmtQ9SxBtZGDMqxSh1xM2c/7/LeWGYUdTwF4M+IrRdrsKe1ePD1 m8RGg0az0kJ7mjuSpxnIjjgq9phWmevqOTuTQT/GoGd//o9Qg+L+h0EPo6YfbT27ZR6npYDGPgx Yq7DVspdifEl4rhp5TZI6x++pI2aJEgcxWnesZPsT/AFhmqxQLPiSmMVBF7oO6+hPtxLfa6+gEn O0p2+/3LawqRM0de4aZTIEdIGzW9zcvVT69pNa0KHUJwmzeyrBCoZQYAZw/D8UK0qCB+cQsmMNw XHEqJOZCn6Ptlgd65w+CUG1xQHqGRnWwRxoCV6GuemiB87ukj6f2hvSnLElvXAm/0va12XhgdV6 qAcgQUmjJJQ82uGkw44zsW/+KIUEyUBtWVsBXSr0afKwfTf4t4c5OJE9hJLjGDvxPmcXxpQXeCd 1sUvg62u4e4iBGSlN3FtHj1MG2gTh97XiaJ2G4/0I+D+Khy+/KOiM1M8lgOyz08jE83wXHNMXvo NQ= X-Received: by 2002:a05:600c:4689:b0:485:358b:e7ee with SMTP id 5b1f17b1804b1-488d6655c82mr138443305e9.0.1776161249684; Tue, 14 Apr 2026 03:07:29 -0700 (PDT) Received: from spartian-1.home ([2a01:cb1c:784:2f00:708:2805:7128:7a75]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-488ede1e050sm65870545e9.5.2026.04.14.03.07.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Apr 2026 03:07:29 -0700 (PDT) From: Aurelien DESBRIERES 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 Subject: [PATCH v3 01/12] ftrfs: add on-disk format and in-memory data structures Date: Tue, 14 Apr 2026 14:07:14 +0200 Message-ID: <20260414120726.5713-2-aurelien@hackers.camp> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260414120726.5713-1-aurelien@hackers.camp> References: <20260413230601.525400-1-aurelien@hackers.camp> <20260414120726.5713-1-aurelien@hackers.camp> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Add the core header defining FTRFS on-disk layout and in-memory VFS structures. On-disk layout: Block 0 : superblock (magic 0x46545246, CRC32-protected) Block 1..N : inode table (128 bytes/inode, CRC32 per inode) Block N+1..end : data blocks (CRC32 per block, RS FEC planned) Structures: ftrfs_super_block : on-disk superblock ftrfs_inode : on-disk inode (12 direct + 1 indirect + 1 dindirect) ftrfs_dir_entry : on-disk directory entry (256-byte name) ftrfs_sb_info : in-memory superblock info (VFS sb->s_fs_info) ftrfs_inode_info : in-memory inode (embedded VFS inode) FTRFS targets POSIX-compatible block devices (MRAM, NOR flash, eMMC) for use in radiation-intensive environments (space applications). Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/ftrfs.h | 168 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 fs/ftrfs/ftrfs.h diff --git a/fs/ftrfs/ftrfs.h b/fs/ftrfs/ftrfs.h new file mode 100644 index 000000000000..82502c9fb07d --- /dev/null +++ b/fs/ftrfs/ftrfs.h @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * FTRFS =E2=80=94 Fault-Tolerant Radiation-Robust Filesystem + * Based on: Fuchs, Langer, Trinitis =E2=80=94 ARCS 2015 + * + * Author: roastercode - Aurelien DESBRIERES + */ + +#ifndef _FTRFS_H +#define _FTRFS_H + +#include +#include +#include + +/* Magic number: 'FTRF' */ +#define FTRFS_MAGIC 0x46545246 + +/* Block size: 4096 bytes */ +#define FTRFS_BLOCK_SIZE 4096 +#define FTRFS_BLOCK_SHIFT 12 + +/* RS FEC: 16 parity bytes per 239-byte subblock (RS(255,239)) */ +#define FTRFS_RS_PARITY 16 +#define FTRFS_SUBBLOCK_DATA 239 +#define FTRFS_SUBBLOCK_TOTAL (FTRFS_SUBBLOCK_DATA + FTRFS_RS_PARITY) + +/* Filesystem limits */ +#define FTRFS_MAX_FILENAME 255 +#define FTRFS_DIRECT_BLOCKS 12 +#define FTRFS_INDIRECT_BLOCKS 1 +#define FTRFS_DINDIRECT_BLOCKS 1 + +/* + * On-disk superblock =E2=80=94 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); --=20 2.52.0 From nobody Mon Jun 15 20:33:15 2026 Received: from mail-wr1-f45.google.com (mail-wr1-f45.google.com [209.85.221.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8F2D931326B for ; Tue, 14 Apr 2026 10:07:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161254; cv=none; b=c6UChh81Ye5F1Ojv4ZZTOw0WYMXLMfOolAPfScE079/BV0FsA1gyM2e0RW4zWxRuTHFo9QVwETaOnhEvUUHEhcTjcxadUSBMbh4fwKhuy/rwOfwzASnaaaNwqsXq0xy4iE1QAcqwkCwjSWiysm6YuhrGxlzVSPQ5GMbnJwX4Lsw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161254; c=relaxed/simple; bh=1uSkaXzm2P0Wj7bPdxog4gc8N/ZNllWGiJvhmN98OSk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=o9yhLgUPEmoBUVfWMteBYn6zoDupnUqjvaPzAVjdUxEcWtVQzKsP8/bt1kL+uJY92V2sFiK+HxUvO9wsn9WdDXRgD0HWxpcus8ckrnj3CcX22C9tjKNP771eGxcsFySpyvCkTq60XR+NOKQBEgltXEMpVfaG3Or8KRcCxhZGXe4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.221.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-wr1-f45.google.com with SMTP id ffacd0b85a97d-43cf5f6d2eeso258358f8f.0 for ; Tue, 14 Apr 2026 03:07:32 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776161251; x=1776766051; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=ukvcN/hPEzbhoZ6n0snMbQkF8J76XlbxpRq52+vBCOk=; b=mpwYcB8G6HeYAtorJf3n47QEv3EGjfwmLPFWtwxF9LzK/galcVhyOAPySXmyP6/00e O7taUKdkoWt87DGtZPPIDo1sG7C2Ch7PhEfhjIxF7ELxWNXGT4V0K4YcR17KOzWclO0B Nq21BSmt7NcN0vCRWhJdmNpqzmyVE7CoRjzZANByn2ppixaV2zYbntK83s3MpZxx0ssb rBqgrnG+572wCRIJUg24dDFv1NQRBHvpOuWMWGKXzEKQnl5u2g2yCQKTyLk63zEj4gQP p9vtB3IChQkEDFKI51YTc3g2TL0Kqsvta1snVGdFfj1F8U5MvW69fYE/UvBh1WtnNI4v iYMg== X-Gm-Message-State: AOJu0YzYDPdMFYmWbn7BSlNo+5f0Dg+SJctCNSx7k9bvQk7fuwXjLs8t J05LBr93d+RhATdexh45gJXtqRCn3IkrL92JK2KbmLtmJrZRRQEAGlAF X-Gm-Gg: AeBDievcHS+56ZG/ow2/cJLvPDaV1wPTP3NDYDfuyEkj9HqBK8mGhBUs0guHASRB4GZ ms7VoNysBViYGMYo42/ce7GwQhAk4TWw30OZG/bhYBByijz7QjptbMsXMd3tUxZmBDbKQXb9/Xb qCNgupOErsLS0KDY+L0qW+KzlDRM3wmm6mkonm7Xqbah3clzn01krHuQ0dM+sbO+BX69UwSQB/Y Yiza/etQeiC935v32JfDXpn+weNLNVKL18+jNEEtRwOCpF3rySherxEu2s/y9cCXYMsSyX0qv4D 7awW4eChtSuKkLMBFUrYIFG32GT3H1RO+yJHg23Twm+6ftPGPfgl4eym918wbbte3knmEoAooP6 RzWgQhUbpiei6jrDlzs/KmzKzdqXYYNHAyEH1u2H7ZhNsMS9gJ7hAt7zHVTJ9f7Z362QMnvg7K0 ckMlAjFwhnzp0raAwKI0jpv5rI7V6AgCe0BqmzEsUZ2uHLpK1idlCgbo+NY16JIKPFOeoAgOUDM nU= X-Received: by 2002:a05:600c:c4a2:b0:486:fdc0:4504 with SMTP id 5b1f17b1804b1-488d6864e49mr132033995e9.4.1776161250841; Tue, 14 Apr 2026 03:07:30 -0700 (PDT) Received: from spartian-1.home ([2a01:cb1c:784:2f00:708:2805:7128:7a75]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-488ede1e050sm65870545e9.5.2026.04.14.03.07.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Apr 2026 03:07:30 -0700 (PDT) From: Aurelien DESBRIERES 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 Subject: [PATCH v3 02/12] ftrfs: add superblock operations Date: Tue, 14 Apr 2026 14:07:15 +0200 Message-ID: <20260414120726.5713-3-aurelien@hackers.camp> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260414120726.5713-1-aurelien@hackers.camp> References: <20260413230601.525400-1-aurelien@hackers.camp> <20260414120726.5713-1-aurelien@hackers.camp> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Implement VFS superblock operations for FTRFS: - ftrfs_fill_super(): read and validate on-disk superblock (magic, CRC32), allocate ftrfs_sb_info, read root inode, initialize in-memory free block bitmap - ftrfs_put_super(): release bitmap and buffer heads on unmount - ftrfs_statfs(): report filesystem statistics - ftrfs_write_inode(): persist inode to disk via namei.c - ftrfs_init_fs_context() / ftrfs_get_tree(): kernel 5.15+ mount API - ftrfs_free_inode(): kernel 5.9+ inode freeing API Module init/exit registers ftrfs as a filesystem type and allocates a dedicated slab cache for ftrfs_inode_info objects. Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/super.c | 274 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 fs/ftrfs/super.c diff --git a/fs/ftrfs/super.c b/fs/ftrfs/super.c new file mode 100644 index 000000000000..8acc6292124b --- /dev/null +++ b/fs/ftrfs/super.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTRFS =E2=80=94 Superblock operations + * Author: roastercode - Aurelien DESBRIERES + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ftrfs.h" + +/* Inode cache (slab allocator) */ +static struct kmem_cache *ftrfs_inode_cachep; + +/* + * alloc_inode =E2=80=94 allocate a new inode with ftrfs_inode_info embedd= ed + */ +static struct inode *ftrfs_alloc_inode(struct super_block *sb) +{ + struct ftrfs_inode_info *fi; + + fi =3D kmem_cache_alloc(ftrfs_inode_cachep, GFP_KERNEL); + if (!fi) + return NULL; + + memset(fi->i_direct, 0, sizeof(fi->i_direct)); + fi->i_indirect =3D 0; + fi->i_dindirect =3D 0; + fi->i_flags =3D 0; + + return &fi->vfs_inode; +} + +/* + * free_inode =E2=80=94 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 =E2=80=94 filesystem statistics + */ +static int ftrfs_statfs(struct dentry *dentry, struct kstatfs *buf) +{ + struct super_block *sb =3D dentry->d_sb; + struct ftrfs_sb_info *sbi =3D FTRFS_SB(sb); + + buf->f_type =3D FTRFS_MAGIC; + buf->f_bsize =3D sb->s_blocksize; + buf->f_blocks =3D le64_to_cpu(sbi->s_ftrfs_sb->s_block_count); + buf->f_bfree =3D sbi->s_free_blocks; + buf->f_bavail =3D sbi->s_free_blocks; + buf->f_files =3D le64_to_cpu(sbi->s_ftrfs_sb->s_inode_count); + buf->f_ffree =3D sbi->s_free_inodes; + buf->f_namelen =3D FTRFS_MAX_FILENAME; + + return 0; +} + +/* + * put_super =E2=80=94 release superblock resources + */ +static void ftrfs_put_super(struct super_block *sb) +{ + struct ftrfs_sb_info *sbi =3D FTRFS_SB(sb); + + if (sbi) { + ftrfs_destroy_bitmap(sb); + brelse(sbi->s_sbh); + kfree(sbi->s_ftrfs_sb); + kfree(sbi); + sb->s_fs_info =3D NULL; + } +} + +static const struct super_operations ftrfs_super_ops =3D { + .alloc_inode =3D ftrfs_alloc_inode, + .free_inode =3D ftrfs_free_inode, + .put_super =3D ftrfs_put_super, + .write_inode =3D ftrfs_write_inode, + .statfs =3D ftrfs_statfs, +}; + +/* + * ftrfs_fill_super =E2=80=94 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 =3D -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 =E2=80=94 superblock */ + bh =3D sb_bread(sb, 0); + if (!bh) { + errorf(fc, "ftrfs: unable to read superblock"); + return -EIO; + } + + fsb =3D (struct ftrfs_super_block *)bh->b_data; + + /* Verify magic */ + if (le32_to_cpu(fsb->s_magic) !=3D 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 =3D ftrfs_crc32(fsb, offsetof(struct ftrfs_super_block, s_crc32)); + if (crc !=3D le32_to_cpu(fsb->s_crc32)) { + errorf(fc, "ftrfs: superblock CRC32 mismatch (got 0x%08x, expected 0x%08= x)", + crc, le32_to_cpu(fsb->s_crc32)); + goto out_brelse; + } + + /* Allocate in-memory sb info */ + sbi =3D kzalloc(sizeof(*sbi), GFP_KERNEL); + if (!sbi) { + ret =3D -ENOMEM; + goto out_brelse; + } + + sbi->s_ftrfs_sb =3D kzalloc(sizeof(*sbi->s_ftrfs_sb), GFP_KERNEL); + if (!sbi->s_ftrfs_sb) { + ret =3D -ENOMEM; + goto out_free_sbi; + } + + memcpy(sbi->s_ftrfs_sb, fsb, sizeof(*fsb)); + sbi->s_sbh =3D bh; + sbi->s_free_blocks =3D le64_to_cpu(fsb->s_free_blocks); + sbi->s_free_inodes =3D le64_to_cpu(fsb->s_free_inodes); + spin_lock_init(&sbi->s_lock); + + sb->s_fs_info =3D sbi; + sb->s_magic =3D FTRFS_MAGIC; + sb->s_op =3D &ftrfs_super_ops; + sb->s_maxbytes =3D MAX_LFS_FILESIZE; + + /* Read root inode (inode 1) */ + root_inode =3D ftrfs_iget(sb, 1); + if (IS_ERR(root_inode)) { + ret =3D PTR_ERR(root_inode); + pr_err("ftrfs: failed to read root inode: %d\n", ret); + goto out_free_fsb; + } + + sb->s_root =3D d_make_root(root_inode); + if (!sb->s_root) { + ret =3D -ENOMEM; + goto out_free_fsb; + } + + if (ftrfs_setup_bitmap(sb)) { + ret =3D -ENOMEM; + goto out_free_fsb; + } + + pr_info("ftrfs: mounted (blocks=3D%llu free=3D%lu inodes=3D%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 =3D NULL; +out_brelse: + brelse(bh); + return ret; +} + +/* + * fs_context ops =E2=80=94 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 =3D { + .get_tree =3D ftrfs_get_tree, +}; + +static int ftrfs_init_fs_context(struct fs_context *fc) +{ + fc->ops =3D &ftrfs_context_ops; + return 0; +} + +static struct file_system_type ftrfs_fs_type =3D { + .owner =3D THIS_MODULE, + .name =3D "ftrfs", + .init_fs_context =3D ftrfs_init_fs_context, + .kill_sb =3D kill_block_super, + .fs_flags =3D FS_REQUIRES_DEV, +}; + +/* + * Inode cache constructor + */ +static void ftrfs_inode_init_once(void *obj) +{ + struct ftrfs_inode_info *fi =3D obj; + + inode_init_once(&fi->vfs_inode); +} + +/* + * Module init / exit + */ +static int __init ftrfs_init(void) +{ + int ret; + + ftrfs_inode_cachep =3D 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 =3D register_filesystem(&ftrfs_fs_type); + if (ret) { + pr_err("ftrfs: failed to register filesystem: %d\n", ret); + kmem_cache_destroy(ftrfs_inode_cachep); + return ret; + } + + pr_info("ftrfs: module loaded (FTRFS Fault-Tolerant Radiation-Robust FS)\= n"); + return 0; +} + +static void __exit ftrfs_exit(void) +{ + unregister_filesystem(&ftrfs_fs_type); + rcu_barrier(); + kmem_cache_destroy(ftrfs_inode_cachep); + pr_info("ftrfs: module unloaded\n"); +} + +module_init(ftrfs_init); +module_exit(ftrfs_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("roastercode - Aurelien DESBRIERES "); +MODULE_DESCRIPTION("FTRFS: Fault-Tolerant Radiation-Robust Filesystem"); +MODULE_VERSION("0.1.0"); +MODULE_ALIAS_FS("ftrfs"); --=20 2.52.0 From nobody Mon Jun 15 20:33:15 2026 Received: from mail-wm1-f42.google.com (mail-wm1-f42.google.com [209.85.128.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AE0E83CB2FF for ; Tue, 14 Apr 2026 10:07:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161255; cv=none; b=qZBGVYfrVPgYwpAa8bjnupSF8SIUva4fS3axux6H4hpQu0oOO5Hy5sOKzXtirUdAF4nftYFkW9ki2GMexjbD6jPECbC8djJOgnqTSqTzzHkC6KFH+QVln1lEEmYlY9Kx5eKDZLpgqhwyguvqwEVKsrgXNMTK6IrEo4evQGzouEI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161255; c=relaxed/simple; bh=xU5n+mIIoZXtzCpNbCf/Cc4DkEYifetPRkM1IVS+p6Y=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=RVgEJrRCX5lxFSkVVNCTacs1DkpLZtfLuivsUch1Z9vydTaapBGU9FLmu/MehLFYXOztIeocAuHn7zgK/roB2H3S4jM9lE113LWuNb1fTu8uEmrD3Ff3WWj+xxS3MUB/rj3UcO/u6NN/qx2hHUMfzlt6s+1/jsBoz0FOsIV/4Ic= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.128.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-wm1-f42.google.com with SMTP id 5b1f17b1804b1-4832c8f9d87so7905005e9.3 for ; Tue, 14 Apr 2026 03:07:33 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776161252; x=1776766052; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=oLsOWgSgd0+sji5zrVI71lwx4+8AIBuQ+XeZ7/IKXtA=; b=ZrhBWqvO5AGx8Z+eBFppM3+UHlTAENnNva+jTeNDX3BA53U92g5mhtlDUTwprFDc0r aEFtZJ2PggFjZYCsW3QYgyTMAYTsUxbvBuVmwlaQ0yqKiaVIbP2LNWEdUm++cBpwPxmL uvE4O7zWxDCqwk47CZtz4Fe0uVkoMxxfxEbdSzi9WsHx1oH1KqG/Ote6FrqMRvVyGpzT FZgVnNFAujue9Boalgk6sIZcO5d5nlMO7yMGNZExACwKifTMqdN72KoATJ/so7hScTdA +w8eVwIZmPFJhw+U/fvbsQci69Zw7H3pclXjg9/xzFJ8UnGaNuhQ8MmFY8Hk5lt4fBAw eHdw== X-Gm-Message-State: AOJu0YyRqKFSqgm4VvVwI0a06EUO2i11yRuRm4/5pIZrP1bnsYi5gqLv 8MTiA11md9ebbLWKPttghaA5WsmYj88oHkNGrLUq3tv4IjKFC+dexXa1 X-Gm-Gg: AeBDietMx9vhuntieRobZhSho6h6LIc6kAbLySzPh4Wf8xMlvtR9ajA3PgLZ+qegdas BTrWxaN982/mCP1sAxYl6J7jx3aV5PN6PuhLrbI1PFzddAZdRitcGhcdvLsIBkIL2MtHA1MBr/K TzzrL05w89uDEakKLxjlntHbmr7CBu2+ZCmzwVxSZ0cZ16cDnGYFznYJd7SxkPfdObP7ZSNgA1U js3KLiyVJH9A8Pxxd3yihgjglHw9BVMq7RS07FGz9bj6GmQH8F7Y6SZQhS7D6mGbZLqYqYloRuV cgxEb2kAQA+fHVT6sAWUjOGGk4JIsjw9/4DijCs8wO5QRy/DJ+qwsuJMpGieN2N1ZqeLNAM+4t6 4Ds7RDhW4W8UtAZjHc1XJS5Hpfwrgaa4E9Hhzmhfr5mbmaxKkkJV6HAFT0HLyR3otN4hzkS9GMT gNMhZIvtFxoX2j6x6NjSzOG/mR+GtkJmxZsYRAocculUYObW850j43VMmxTGd4XFDxK9jlHj0yc VU= X-Received: by 2002:a05:600c:4ece:b0:488:a2ac:a338 with SMTP id 5b1f17b1804b1-488d7f3328bmr115367545e9.5.1776161252032; Tue, 14 Apr 2026 03:07:32 -0700 (PDT) Received: from spartian-1.home ([2a01:cb1c:784:2f00:708:2805:7128:7a75]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-488ede1e050sm65870545e9.5.2026.04.14.03.07.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Apr 2026 03:07:31 -0700 (PDT) From: Aurelien DESBRIERES 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 Subject: [PATCH v3 03/12] ftrfs: add inode operations Date: Tue, 14 Apr 2026 14:07:16 +0200 Message-ID: <20260414120726.5713-4-aurelien@hackers.camp> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260414120726.5713-1-aurelien@hackers.camp> References: <20260413230601.525400-1-aurelien@hackers.camp> <20260414120726.5713-1-aurelien@hackers.camp> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Implement ftrfs_iget() to read inodes from the on-disk inode table: - Locate inode block from s_inode_table_blk and inode number - Read raw ftrfs_inode via sb_bread() - Verify per-inode CRC32 checksum - Populate VFS inode fields (mode, uid, gid, size, timestamps) - Copy direct/indirect block pointers to ftrfs_inode_info - Assign inode_operations and file_operations based on file type - Use inode_state_read_once() for I_NEW test (kernel 7.0 API) Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/inode.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 fs/ftrfs/inode.c diff --git a/fs/ftrfs/inode.c b/fs/ftrfs/inode.c new file mode 100644 index 000000000000..e1279c7968a3 --- /dev/null +++ b/fs/ftrfs/inode.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTRFS =E2=80=94 Inode operations + * Author: roastercode - Aurelien DESBRIERES + */ + +#include +#include +#include +#include +#include "ftrfs.h" + +/* + * ftrfs_iget =E2=80=94 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 =3D 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 =3D 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 =3D FTRFS_BLOCK_SIZE / sizeof(struct ftrfs_inode); + block =3D le64_to_cpu(sbi->s_ftrfs_sb->s_inode_table_blk) + + (ino - 1) / inodes_per_block; + offset =3D (ino - 1) % inodes_per_block; + + bh =3D 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 =3D (struct ftrfs_inode *)bh->b_data + offset; + /* Verify inode CRC32 */ + crc =3D ftrfs_crc32(raw, offsetof(struct ftrfs_inode, i_crc32)); + if (crc !=3D 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 =3D FTRFS_I(inode); + + /* Populate VFS inode */ + inode->i_mode =3D le16_to_cpu(raw->i_mode); + inode->i_uid =3D make_kuid(sb->s_user_ns, le16_to_cpu(raw->i_uid)); + inode->i_gid =3D make_kgid(sb->s_user_ns, le16_to_cpu(raw->i_gid)); + set_nlink(inode, le16_to_cpu(raw->i_nlink)); + inode->i_size =3D le64_to_cpu(raw->i_size); + inode->i_blocks =3D 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 =3D raw->i_indirect; + fi->i_dindirect =3D raw->i_dindirect; + fi->i_flags =3D le32_to_cpu(raw->i_flags); + + /* Set ops based on file type */ + if (S_ISDIR(inode->i_mode)) { + inode->i_op =3D &ftrfs_dir_inode_operations; + inode->i_fop =3D &ftrfs_dir_operations; + } else if (S_ISREG(inode->i_mode)) { + inode->i_op =3D &ftrfs_file_inode_operations; + inode->i_fop =3D &ftrfs_file_operations; + } else { + /* Special files: use generic */ + init_special_inode(inode, inode->i_mode, 0); + } + + brelse(bh); + unlock_new_inode(inode); + return inode; +} --=20 2.52.0 From nobody Mon Jun 15 20:33:15 2026 Received: from mail-wm1-f49.google.com (mail-wm1-f49.google.com [209.85.128.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 12A3A3CCFB5 for ; Tue, 14 Apr 2026 10:07:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161256; cv=none; b=IapG7sfUrW6LJHPKT2fjU7qD/Y635EBzJAATrSltZ+DH1vbjFOlbiQkDnTce/YWiRy/6nBsFSGT9NZQ5UYpSpVa7asXJ4Iy1hQsNUo+yWpLrIKlQjHfY4hZHw82Iou+yUVN4miPsX7r9UCKIVtz0vB8tOqTQmjjYkP2Xn5hiMso= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161256; c=relaxed/simple; bh=UpYZeqLlO7XAI8xioHTCGc+EjyJYzK1FRJsK8WUWOeU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=N7h4Yjk6d4WMSUGiMxHZZuuCWCcU9geK/FcLobv4eRe9WhBLQFeUz6kRmTWS/bT3ZebMiiDIC7a8NI0cC09Y1j8ojqKBEBnaE312ZAa/sPbpApz/IplU4kfp2pK9wsk1gtHwLLldNNbJFGLQZwSj6dw7usQVFN2ucY3olux6j8A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.128.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-wm1-f49.google.com with SMTP id 5b1f17b1804b1-4830b67aa6bso7665915e9.0 for ; Tue, 14 Apr 2026 03:07:34 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776161253; x=1776766053; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=GZYlrpG6ibaaLw+Sa4FOLMlMfX1UHEtv7mMrIDu+G+Y=; b=HGqPUUl7ie1yc0NtMxmR371ymQCXQsbUPN7w2nW2bXHusg0k5ZCgmbl5M8DaJEXcKy Hs7Qj2iZDxX5i2wvOaj113pzhkdBW+upieJijxX4HEohDQlIi2AnxJXOsX6BlMSWntD/ fkwSwU04hCeOgn7VX8a/Z7hoMCqPVeCqJtfqpYDAjMgu/zFAGs0kO256de1WikTWVQzF wcj//TfJxCjriulIAe4VJWGZSa9VptDS0uRshoxJO808chdLJ8IlTpdCE8XSAY8T4Lz/ DeSfYtx8CmybTUR9fYErRHXvnIGtKLpF8NB77zx7CSZjJa93t5Gnap4eQT1LB3bWvoan Uxhg== X-Gm-Message-State: AOJu0YyyF9rxrC5Z4dncYRsc/GnBIjFQhPf071icJGRQ7Kf5erytMwL7 UKmB20p8CoqNvUa4LFSP4Nn8ZkKA4IXKdGy+nXCBkF+owZagDEdw0Ist X-Gm-Gg: AeBDiev5IAyTnCGPXw4UXmIbMuz7hVUV2TGek06gg1eeqvyNXC8rawvvp7CTbRdSTfX 3xHDdkoKDmqMSvWJ/M6O8OeOyTL5YC4VYBoIHyIm0gDPlaB84bzFoxHMRY7la9asHIXsawQG+yj 34PFGaRBen2LfILQqByJccb/4IjxuX6J5xT5H8QNFPVXtjfDzsAgqzzMeCBZdgbNoRis1iLn8ba rqB5Slh1C/6QwMRw/7idNML/mTBebh0lPF7ED2XMgFHx6sdAAYMpM2cp06uJQOU7USqgeAl/hA3 Un5yW4pQjkA3EWQnjCDysbISW/jdvVPXvPoq0nVlLVZxmm99Y3TKAK4zzjMS0kr0LfB17ylAoZ3 WoLNJnzu+S1Bhba4lOccQ9e0rIxCZtdDmFGSkbe+mNWkN/BZTUF/RRkXKNw7cFz862qT1HrpLhy /Vys+hiZe65Id+OP1VWMeBKBW+5VKhoWA29ZlptZCo5jfoBeIRyLxdlLbg8ed8n33gOWHSOzM6t to= X-Received: by 2002:a05:600c:b85:b0:486:fb8a:fed with SMTP id 5b1f17b1804b1-488d68a0975mr124812685e9.8.1776161253251; Tue, 14 Apr 2026 03:07:33 -0700 (PDT) Received: from spartian-1.home ([2a01:cb1c:784:2f00:708:2805:7128:7a75]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-488ede1e050sm65870545e9.5.2026.04.14.03.07.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Apr 2026 03:07:32 -0700 (PDT) From: Aurelien DESBRIERES 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 Subject: [PATCH v3 04/12] ftrfs: add directory operations Date: Tue, 14 Apr 2026 14:07:17 +0200 Message-ID: <20260414120726.5713-5-aurelien@hackers.camp> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260414120726.5713-1-aurelien@hackers.camp> References: <20260413230601.525400-1-aurelien@hackers.camp> <20260414120726.5713-1-aurelien@hackers.camp> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Implement directory iteration and lookup: - ftrfs_readdir(): iterate directory entries from direct blocks, emit via dir_emit_dots() then dir_emit() for real entries, use ctx->pos as entry index with INT_MAX as EOF sentinel - ftrfs_lookup(): search directory blocks for a matching name, call ftrfs_iget() on match and return via d_splice_alias() Both functions scan ftrfs_dir_entry records in direct block pointers only (no indirect block support yet). Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/dir.c | 132 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 fs/ftrfs/dir.c diff --git a/fs/ftrfs/dir.c b/fs/ftrfs/dir.c new file mode 100644 index 000000000000..dbf0102a40f3 --- /dev/null +++ b/fs/ftrfs/dir.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTRFS =E2=80=94 Directory operations + * Author: roastercode - Aurelien DESBRIERES + */ + +#include +#include +#include "ftrfs.h" + +/* + * ftrfs_readdir =E2=80=94 iterate directory entries + */ +static int ftrfs_readdir(struct file *file, struct dir_context *ctx) +{ + struct inode *inode =3D file_inode(file); + struct super_block *sb =3D inode->i_sb; + struct ftrfs_inode_info *fi =3D 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 =3D=3D INT_MAX) + return 0; + /* Emit . and .. (ctx->pos: 0=3D., 1=3D.., 2+=3Dreal 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 =3D 0; block_idx < FTRFS_DIRECT_BLOCKS; block_idx++) { + block_no =3D le64_to_cpu(fi->i_direct[block_idx]); + if (!block_no) + break; + + bh =3D sb_bread(sb, block_no); + if (!bh) + continue; + + offset =3D 0; + while (offset < FTRFS_BLOCK_SIZE) { + de =3D (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 +=3D le16_to_cpu(de->d_rec_len); + } + + brelse(bh); + } + + ctx->pos =3D INT_MAX; + return 0; +} + +/* + * ftrfs_lookup =E2=80=94 find dentry in directory + */ +struct dentry *ftrfs_lookup(struct inode *dir, + struct dentry *dentry, + unsigned int flags) +{ + struct super_block *sb =3D dir->i_sb; + struct ftrfs_inode_info *fi =3D FTRFS_I(dir); + struct buffer_head *bh; + struct ftrfs_dir_entry *de; + struct inode *inode =3D 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 =3D 0; block_idx < FTRFS_DIRECT_BLOCKS; block_idx++) { + block_no =3D le64_to_cpu(fi->i_direct[block_idx]); + if (!block_no) + break; + + bh =3D sb_bread(sb, block_no); + if (!bh) + continue; + + offset =3D 0; + while (offset < FTRFS_BLOCK_SIZE) { + de =3D (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 =3D=3D dentry->d_name.len && + !memcmp(de->d_name, dentry->d_name.name, + de->d_name_len)) { + unsigned long ino =3D le64_to_cpu(de->d_ino); + + brelse(bh); + inode =3D ftrfs_iget(sb, ino); + goto found; + } + + offset +=3D le16_to_cpu(de->d_rec_len); + } + brelse(bh); + } + +found: + return d_splice_alias(inode, dentry); +} + +const struct file_operations ftrfs_dir_operations =3D { + .llseek =3D generic_file_llseek, + .read =3D generic_read_dir, + .iterate_shared =3D ftrfs_readdir, +}; + + --=20 2.52.0 From nobody Mon Jun 15 20:33:15 2026 Received: from mail-wm1-f49.google.com (mail-wm1-f49.google.com [209.85.128.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 063CA3A0E80 for ; Tue, 14 Apr 2026 10:07:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161257; cv=none; b=V7upvNnNnyfQFu7k+aIkEZFW4rI5Ezt7sQDlTtEiR6lYjXrq7Gj5R6l7RvlVbzAUmO7kYfOyp8G6fget25ZyBWvlHDXLSfqavNwXghOXOjqxrgxzpcEn54oN1OYeasiw0By8YbrGbZXtQtoHa6YjUZensK7abINL6NPnSrMh8qE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161257; c=relaxed/simple; bh=MZhLO5vOeUNINP+HUF/YokkPpYoIjLJem7XwEBcHXDI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=CIuG/KXrgAgKxMP/5Yb38dQALTj/o/LOgjObBA2KUaZjS4AjdWbJSopbBXXMNLXgmLJ5eLF2TA/oFOpvLLCOdWIwDmFhqKa7iTeXFtEnxYVS6qMeJUHxHPic2U6s3rMJTad4gwGXl2djvjri2aJ8CUA1sDIfAulc/uN0yUBZqVM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.128.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-wm1-f49.google.com with SMTP id 5b1f17b1804b1-488965a9ca1so6536495e9.2 for ; Tue, 14 Apr 2026 03:07:35 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776161254; x=1776766054; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=x31a9VYuI8ziW8OcLILmPawSpu3o6edqbX2Sgd95MQs=; b=aM5PA+8ttekvX7dzaarw9v5QZoGr2HebY3uTnhMqiCw5iCal49q2uf3bdQ9lHQm2kz S7FYPQ1UH3QaT4dkmRleVURItH0QJd2i+VlcgZSsbvtkux0udch3lzX2HLhXPHOaR/3V TQNC98AjzreL9YLzPJ6dIODyTvLGVlmccikL+S/gisInAgeTeaFIpuHRWVrvogzfYauS HWg3BdVtbisNtY5Jy3yP1Jsh5IXgKieTNxxSW1eVFw2umNg7VO60FxNcmjb3dCU+0mMQ vDxuFCJXNej960Dq0MEv7OOPw+PZLsYB0vzD1mQ5EnEsfPSs/kNaouEfnbIBAkAg4c8I fRwQ== X-Gm-Message-State: AOJu0YxuPOLf9ULZ5BNsCxEE5l8dgT1qPt0Fjs+71Wgro8NQcw2/Mxsu Zf5r9BAG16g6S0GdEfmc2tB3DZ8ho41B1VzYYTgdrFw7FMmIwBWuc5hU X-Gm-Gg: AeBDieubH3qV3m3bjjzlTuyblpZrFjyZq5jIanyfD8HfFRnlJmDHBgS6eq1uL7VXXKF v+yQPgHAALMs1SZ07w8DY4BSUZ/N72El2/G37kGG+zn3sKHOqhQ8VRh56TXVRBrL+mLVC57wh0M XhjtkrvlgXzoh7MPP9t2h33GlwW76uqKxp3Xlp+ejclH7bF/4elv/1Z7qajlhS8e3LTqcLOggBZ VZ9AbHh8rwMY13Q/efdtchLXitmYiWdd7vqe+z1XCNqXdQyUQ6OK/QTEBYoDYCd8nC1TDTWmAw+ TEnJTpl7aYzNH7toqk+Mc4tepnYsIi/h9z7wGyY7ls4EMCRQmoesJq77R7dhQ8qNU4oxcVWC6/k 3ZVUG0U4/ws7MMGQfXgbn7V5wmZ8GbfYF47JDX4yROtcghHExE9I+B1hWGN7szeHY+Ot/Te0m6Z dfOa7t+LYccPxnp46MBcisCAMx5K2MFP1DDJn6EbsHwCjYKrc0eo4HXA0rqARQXnIMvd+dKhaVs QCh1DJQsOZ6Zg== X-Received: by 2002:a05:600c:3ba8:b0:488:7fd3:5cd5 with SMTP id 5b1f17b1804b1-488d7eac3bemr128539225e9.1.1776161254482; Tue, 14 Apr 2026 03:07:34 -0700 (PDT) Received: from spartian-1.home ([2a01:cb1c:784:2f00:708:2805:7128:7a75]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-488ede1e050sm65870545e9.5.2026.04.14.03.07.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Apr 2026 03:07:34 -0700 (PDT) From: Aurelien DESBRIERES 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 Subject: [PATCH v3 05/12] ftrfs: add file operations Date: Tue, 14 Apr 2026 14:07:18 +0200 Message-ID: <20260414120726.5713-6-aurelien@hackers.camp> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260414120726.5713-1-aurelien@hackers.camp> References: <20260413230601.525400-1-aurelien@hackers.camp> <20260414120726.5713-1-aurelien@hackers.camp> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Implement basic file read/write using generic VFS helpers: - ftrfs_file_operations: generic_file_read_iter, generic_file_write_iter - ftrfs_file_inode_operations: getattr via simple_getattr - ftrfs_address_space_operations: readpage via block_read_full_folio, writepage via block_write_full_folio, bmap via generic_block_bmap Read path delegates to the page cache via generic helpers. Write path is wired but depends on the block allocator (alloc.c) and write_inode (namei.c) for persistence. Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/file.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 fs/ftrfs/file.c diff --git a/fs/ftrfs/file.c b/fs/ftrfs/file.c new file mode 100644 index 000000000000..ef121359be77 --- /dev/null +++ b/fs/ftrfs/file.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTRFS =E2=80=94 File operations (skeleton) + * Author: roastercode - Aurelien DESBRIERES + * + * NOTE: read/write use generic_file_* for now. + * The EDAC/RS layer will intercept at the block I/O level (next iteration= ). + */ + +#include +#include +#include "ftrfs.h" + +const struct file_operations ftrfs_file_operations =3D { + .llseek =3D generic_file_llseek, + .read_iter =3D generic_file_read_iter, + .write_iter =3D generic_file_write_iter, + .mmap =3D generic_file_mmap, + .fsync =3D generic_file_fsync, + .splice_read =3D filemap_splice_read, +}; + +const struct inode_operations ftrfs_file_inode_operations =3D { + .getattr =3D simple_getattr, +}; --=20 2.52.0 From nobody Mon Jun 15 20:33:15 2026 Received: from mail-wm1-f54.google.com (mail-wm1-f54.google.com [209.85.128.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 669BE3CEBA9 for ; Tue, 14 Apr 2026 10:07:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161258; cv=none; b=bLipY5FzyF3vJfAZXo9Z0TYMLHtCvk9IsFLBGhrlMnh8qgFMExHZ+KYCB0ulY3BfzAQckjjqricitc5qKBfuu/B/ricWfCh/ZCseUaD3nYaQokeqhPdIsyoRP4Zxa6ijxTs2J9PuFlXU37C2noIj4mannvJ9eouwp5Qlfdz4fps= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161258; c=relaxed/simple; bh=HjovzqKRfihyvi0RQP8r60/89dcJiz4P3enX/XynB0Q=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=LQWWQiNz79GcDDmPm9nNuBC56XlsVHE7e/6auYOJyk2NkFhF8cwuTpPlDhkL3tdY2gNx/0w7/BBu/V5FdCCoMj/8bbHNS2GSUu7OM03nVT3f1NNiylOSBEnoGjsX4kQqwzf+SPcFjuVKlUbvLFCn2lYm2CCdNx4mjClzKRIPGa0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.128.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-wm1-f54.google.com with SMTP id 5b1f17b1804b1-483ad568d68so8517785e9.2 for ; Tue, 14 Apr 2026 03:07:37 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776161256; x=1776766056; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=PRwwsnX7+MUddonl4tGcWMd+tJhgj+p0Mkr9fVPJ6QQ=; b=QGYozFuywsRCQYuIS0+37YJ34pHpDzMYHs6E9iW7C6f33tOl92hFu4MFWQ7+c6/K1f EPl561YqtB9hc/aZ8ah5Drz5JgI2wjSmP62sNKKpzcdGTYWC1Cg30a3tnPPJIqe/DHnN zGGpSzL66BHSkO2PRe8Sa8DFO30pzcOGwKvG53Wtqzf3Z97ezGWcnS6sKyUOVc0Ne/7j uwb5buT0vTl+e8Meqoqhv4UErC8DyRo8fmY8fwKpxlobZOQZWs94G6mcoqb91+XP7zj1 5Q5o1sGKguJfXxBJwuHv/Z8doiluv1iSxWjFlGEYTwf843mJuMVpu6eRMtKFfpgnDHgK Bwqg== X-Gm-Message-State: AOJu0YyyAzm6it/HASiYrH0bZPm+15IsHtwqi45NrMHvGqGOaNIZnqPf N24vi2jH4GIPN3dJxbJ/XSRUjqlUscgsC8ZGXcqTAJ7/nNJIAq9uYKSx X-Gm-Gg: AeBDievnP3DAq720kwnLOPMdmeKIGXWBb7HaoU9LIERlNBRzfA/oLjk6OxuELxnpU1x LMAsdpwyJjRuNroUpy6Cjv0osc/avab2F4/7EJehu45BlKO4GpnR5ftNRl2ziL/WcipLlHtgxyx KSYQJgfy46V1Bw6EVQ9pAfoU7TiNKKtAYLaHVnWDqDU6lJXq1GzU61psP3v8uj+ypkpwYjhtiXw fQfpFxuzcCOoBTqY81VbqPtweH9cOsxogheajTtAm9QX3cDYTmpN2yQJvEJFWrEBo8WHzbsTtQ8 7eKNYa1W5Q+9iXdhBgsWB83wJZ3wvVUUpBjD1RPKOAhwrAcOvJpnklIhBcWn7wBlbwErdTGLOoL P/Lo/kIe65w8846es/diu+uydk3RysIigbqf8faESw0R1u+gOQSJRsd6ROnWvQ3BWqoJP8pq9hu gKw8eJdo6GuyV42o/jwAaLuI2OqfX8BCbypOnbruPdllNeapxnjZEnwog7FClATzxNc+wwkdkdm B8= X-Received: by 2002:a05:600c:3b29:b0:485:3177:d80c with SMTP id 5b1f17b1804b1-488d67d7278mr121437025e9.2.1776161255710; Tue, 14 Apr 2026 03:07:35 -0700 (PDT) Received: from spartian-1.home ([2a01:cb1c:784:2f00:708:2805:7128:7a75]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-488ede1e050sm65870545e9.5.2026.04.14.03.07.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Apr 2026 03:07:35 -0700 (PDT) From: Aurelien DESBRIERES 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 Subject: [PATCH v3 06/12] ftrfs: add block and inode allocator Date: Tue, 14 Apr 2026 14:07:19 +0200 Message-ID: <20260414120726.5713-7-aurelien@hackers.camp> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260414120726.5713-1-aurelien@hackers.camp> References: <20260413230601.525400-1-aurelien@hackers.camp> <20260414120726.5713-1-aurelien@hackers.camp> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable 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 =3D=3D 0), updates s_free_inodes counter The bitmap is loaded from the superblock free block count at mount and persisted incrementally on each allocation/free. A dedicated on-disk bitmap block is planned for a future revision. Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/alloc.c | 251 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 fs/ftrfs/alloc.c diff --git a/fs/ftrfs/alloc.c b/fs/ftrfs/alloc.c new file mode 100644 index 000000000000..753eb67cf9ff --- /dev/null +++ b/fs/ftrfs/alloc.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTRFS =E2=80=94 Block and inode allocator + * Author: Aur=C3=A9lien DESBRIERES + * + * Simple bitmap allocator. The free block bitmap is stored in-memory + * (loaded at mount time) and persisted to disk on each allocation/free. + * + * Layout assumption (from mkfs.ftrfs): + * Block 0 : superblock + * Block 1..N : inode table + * Block N+1 : root dir data + * Block N+2..end : data blocks + * + * The bitmap itself is stored in the first data block after the inode + * table. Each bit represents one data block (1 =3D free, 0 =3D used). + */ + +#include +#include +#include +#include +#include "ftrfs.h" + +/* + * ftrfs_setup_bitmap =E2=80=94 allocate and initialize the in-memory bloc= k 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 bloc= k. + */ +int ftrfs_setup_bitmap(struct super_block *sb) +{ + struct ftrfs_sb_info *sbi =3D FTRFS_SB(sb); + unsigned long total_blocks; + unsigned long data_start; + + total_blocks =3D le64_to_cpu(sbi->s_ftrfs_sb->s_block_count); + data_start =3D le64_to_cpu(sbi->s_ftrfs_sb->s_data_start_blk); + + if (total_blocks <=3D data_start) { + pr_err("ftrfs: invalid block layout (total=3D%lu data_start=3D%lu)\n", + total_blocks, data_start); + return -EINVAL; + } + + sbi->s_nblocks =3D total_blocks - data_start; + sbi->s_data_start =3D data_start; + + /* Allocate bitmap: one bit per data block */ + sbi->s_block_bitmap =3D 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 =3D sbi->s_nblocks - sbi->s_free_blocks; + unsigned long i; + + for (i =3D 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 =E2=80=94 free the in-memory bitmap + * Called from ftrfs_put_super(). + */ +void ftrfs_destroy_bitmap(struct super_block *sb) +{ + struct ftrfs_sb_info *sbi =3D FTRFS_SB(sb); + + if (sbi->s_block_bitmap) { + bitmap_free(sbi->s_block_bitmap); + sbi->s_block_bitmap =3D NULL; + } +} + +/* + * ftrfs_alloc_block =E2=80=94 allocate a free data block + * @sb: superblock + * + * Returns the absolute block number (>=3D 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 =3D 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 =3D=3D 0) { + spin_unlock(&sbi->s_lock); + pr_warn("ftrfs: no free blocks\n"); + return 0; + } + + /* Find first free bit (set =3D free in our convention) */ + bit =3D find_first_bit(sbi->s_block_bitmap, sbi->s_nblocks); + if (bit >=3D sbi->s_nblocks) { + spin_unlock(&sbi->s_lock); + pr_err("ftrfs: bitmap inconsistency (free_blocks=3D%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 =3D 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 =E2=80=94 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 =3D 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 =3D (unsigned long)(block - sbi->s_data_start); + + if (bit >=3D 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 =3D cpu_to_le64(sbi->s_free_blocks); + mark_buffer_dirty(sbi->s_sbh); + + spin_unlock(&sbi->s_lock); +} + +/* + * ftrfs_alloc_inode_num =E2=80=94 allocate a free inode number + * @sb: superblock + * + * Returns inode number >=3D 2 on success (1 =3D 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 =3D 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 =3D 0; + + inodes_per_block =3D FTRFS_BLOCK_SIZE / sizeof(struct ftrfs_inode); + inode_table_blk =3D le64_to_cpu(sbi->s_ftrfs_sb->s_inode_table_blk); + total_inodes =3D le64_to_cpu(sbi->s_ftrfs_sb->s_inode_count); + + spin_lock(&sbi->s_lock); + + if (sbi->s_free_inodes =3D=3D 0) { + spin_unlock(&sbi->s_lock); + return 0; + } + + /* Scan inode table blocks looking for a free inode (i_mode =3D=3D 0) */ + for (block =3D 0; block * inodes_per_block < total_inodes; block++) { + bh =3D sb_bread(sb, inode_table_blk + block); + if (!bh) + continue; + + raw =3D (struct ftrfs_inode *)bh->b_data; + + for (i =3D 0; i < inodes_per_block; i++) { + unsigned long ino_num =3D block * inodes_per_block + i + 1; + + if (ino_num > total_inodes) + break; + + /* inode 1 =3D root, always reserved */ + if (ino_num =3D=3D 1) + continue; + + if (le16_to_cpu(raw[i].i_mode) =3D=3D 0) { + /* Found a free inode slot */ + ino =3D (u64)ino_num; + sbi->s_free_inodes--; + sbi->s_ftrfs_sb->s_free_inodes =3D + 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; +} --=20 2.52.0 From nobody Mon Jun 15 20:33:15 2026 Received: from mail-wm1-f44.google.com (mail-wm1-f44.google.com [209.85.128.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 739B43CF05D for ; Tue, 14 Apr 2026 10:07:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161260; cv=none; b=hBebPgKGuHh0fYLv8FubuuV9QcmxxM/dRANwOH2QqDlimASmpuJVPgMRru4C7OZdgUPSs1jwUmgrBRHOkFmD2u6xfhbSp8POe1tBrQ53msgfgEiWdRXclSHMPi/RvumEzbI7B4ApBno3y4FJQI6j1MCELIMi8ZqLP53yOj0Iw90= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161260; c=relaxed/simple; bh=xB/yNGnxOR7s1//nkhBo7TPkoq+OgOBnD2r4Aw3SaHo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=mwGPAhWs3T8IyizX3XREOu5v9bbajSkImk6/KG/VzaPXRUXQAqk8Vc/H2srKg/2S132ksr1RDULf85/K58TUX00+O2e/A7vlAqn00+MS5UF+JGOpdF9TeGWugNWYMGQ2c01LXk4xOclXh+1q2AueCgGHedJ0+Ba+eZr+mBuYZV8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.128.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-wm1-f44.google.com with SMTP id 5b1f17b1804b1-488a8f97f6bso10032245e9.2 for ; Tue, 14 Apr 2026 03:07:38 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776161257; x=1776766057; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=Fdq4Fqu6vJGRSQdt56RxNwfRuxVRsYCe3ADkLdMXhoc=; b=i5yRu+9FUWSsqjoi6kI2i64srN7aXat4Hrpj6oGwsVhfNATXAjr6QFFj0WxcPWe6ym udg7i3uIdIkthW9Gk+zJOoXsC2BmNjLI/rvCeElFVttugulUmHCg2oRaUv1SXweLJwFk ig8uxUWRshI0vkszkbIISu5qW6dVz/NnS6Csy7YpuggJnspEM2TmQaRSplIfBYNuuZBm dlQIBkkaEkxQEwGzOzmfpAvk94xojMsTYeAxf+pYzkaZnpoiqllTwFeHb/kyR50x3F7D 834jJX3+xtRaMcCrMA+lQaquUzn3Zdt60pDO4wIBXqHCop3RR8AXBwu8gGuKl7ekCuGl B2ZA== X-Gm-Message-State: AOJu0YyC3swrOLksPW74qverHmmQBJWlr9SOVmssMH44ZSneshOExQHX xcQpaqrkhgwcMqV0X0YjtsP+/ytvqKKtF6031ME9FdF+vUuuaeBihCA1 X-Gm-Gg: AeBDieu+yY13HBVpaTqnwBT9m4JiP1Iu6VrJ3d4BELvE5VdpckavJ9zT9Asc6Q6eNgL pFf9Q6znQa69DTT7wgnI4R36wQ5SDHTIdWAkipeQvXKka61sk6NF6c6grb4eEawg/mxYYcdI9+e 9HlkYdvsku9LIGD3+AbAIDu1Ubo+oVMbwSKdEIBUiyN3OI5JpiiFG9Z26eSAuJAU4IViTfI4aQB Ehbdc+mjNgmdiDfAgp96p6AEM0becIay8xhfU4jdUYouKRqJgZ2lmzqER557hgi4ecYqJ3HARK6 6smQ+KTgtZjPo1GZeOgubpLfZftxzcOChmnlsZJYEuLj4Le57/WcWhkV1MSH4YYoKUbPd4f+sO9 m9HM7lvkT7ov2sA+bandsO6oqBH4fHrvjWBFEOYrhYSucsVZCQRhfAUhhfelpl/O21k5eOZ69xe MholFF2W8X9aZj1N+/5O3/u/cbj1EU6zZvqM42acN3/cP6QEyBJybY/67zrYXSA0AGjCfGwHU2Z Ic= X-Received: by 2002:a05:600c:474a:b0:487:1826:e13b with SMTP id 5b1f17b1804b1-488d67bbd2cmr119440225e9.2.1776161256733; Tue, 14 Apr 2026 03:07:36 -0700 (PDT) Received: from spartian-1.home ([2a01:cb1c:784:2f00:708:2805:7128:7a75]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-488ede1e050sm65870545e9.5.2026.04.14.03.07.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Apr 2026 03:07:36 -0700 (PDT) From: Aurelien DESBRIERES 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 Subject: [PATCH v3 07/12] ftrfs: add filename and directory entry operations Date: Tue, 14 Apr 2026 14:07:20 +0200 Message-ID: <20260414120726.5713-8-aurelien@hackers.camp> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260414120726.5713-1-aurelien@hackers.camp> References: <20260413230601.525400-1-aurelien@hackers.camp> <20260414120726.5713-1-aurelien@hackers.camp> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Implement VFS inode_operations for directories and write path: - ftrfs_create(): allocate inode, write to disk, add dir entry - ftrfs_mkdir(): create directory with . and .. entries - ftrfs_unlink(): remove directory entry, decrement link count - ftrfs_rmdir(): remove empty directory - ftrfs_link(): create hard link - ftrfs_write_inode(): VFS super_op, persist inode via sb_bread/ mark_buffer_dirty with CRC32 update - ftrfs_new_inode(): allocate and initialize a new VFS inode ftrfs_mkdir() returns struct dentry * as required by kernel 7.0 inode_operations.mkdir API change. Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/namei.c | 428 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 428 insertions(+) create mode 100644 fs/ftrfs/namei.c diff --git a/fs/ftrfs/namei.c b/fs/ftrfs/namei.c new file mode 100644 index 000000000000..a8c1f79ebe44 --- /dev/null +++ b/fs/ftrfs/namei.c @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTRFS =E2=80=94 Filename / directory entry operations + * Author: Aur=C3=A9lien DESBRIERES + * + * Implements: create, mkdir, unlink, rmdir, link, rename + */ + +#include +#include +#include +#include +#include "ftrfs.h" + +/* ------------------------------------------------------------------ */ +/* Helper: write a raw ftrfs_inode to disk */ +/* ------------------------------------------------------------------ */ + +static int ftrfs_write_inode_raw(struct inode *inode) +{ + struct super_block *sb =3D inode->i_sb; + struct ftrfs_sb_info *sbi =3D FTRFS_SB(sb); + struct ftrfs_inode_info *fi =3D FTRFS_I(inode); + struct ftrfs_inode *raw; + struct buffer_head *bh; + unsigned long inodes_per_block; + unsigned long block, offset; + + inodes_per_block =3D FTRFS_BLOCK_SIZE / sizeof(struct ftrfs_inode); + block =3D le64_to_cpu(sbi->s_ftrfs_sb->s_inode_table_blk) + + (inode->i_ino - 1) / inodes_per_block; + offset =3D (inode->i_ino - 1) % inodes_per_block; + + bh =3D sb_bread(sb, block); + if (!bh) + return -EIO; + + raw =3D (struct ftrfs_inode *)bh->b_data + offset; + + raw->i_mode =3D cpu_to_le16(inode->i_mode); + raw->i_uid =3D cpu_to_le16(i_uid_read(inode)); + raw->i_gid =3D cpu_to_le16(i_gid_read(inode)); + raw->i_nlink =3D cpu_to_le16(inode->i_nlink); + raw->i_size =3D cpu_to_le64(inode->i_size); + raw->i_blocks =3D cpu_to_le32(inode->i_blocks); + raw->i_atime =3D cpu_to_le64(inode_get_atime_sec(inode) * NSEC_PER_SEC + + inode_get_atime_nsec(inode)); + raw->i_mtime =3D cpu_to_le64(inode_get_mtime_sec(inode) * NSEC_PER_SEC + + inode_get_mtime_nsec(inode)); + raw->i_ctime =3D cpu_to_le64(inode_get_ctime_sec(inode) * NSEC_PER_SEC + + inode_get_ctime_nsec(inode)); + raw->i_flags =3D cpu_to_le32(fi->i_flags); + + memcpy(raw->i_direct, fi->i_direct, sizeof(fi->i_direct)); + raw->i_indirect =3D fi->i_indirect; + raw->i_dindirect =3D fi->i_dindirect; + + raw->i_crc32 =3D 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 =3D dir->i_sb; + struct ftrfs_inode_info *fi =3D 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 =3D 0; i < FTRFS_DIRECT_BLOCKS; i++) { + block_no =3D le64_to_cpu(fi->i_direct[i]); + if (!block_no) + break; + + bh =3D sb_bread(sb, block_no); + if (!bh) + return -EIO; + + offset =3D 0; + while (offset + sizeof(*de) <=3D FTRFS_BLOCK_SIZE) { + de =3D (struct ftrfs_dir_entry *)(bh->b_data + offset); + + /* Free slot: ino =3D=3D 0 */ + if (!de->d_ino) { + de->d_ino =3D cpu_to_le64(ino); + de->d_name_len =3D name->len; + de->d_file_type =3D file_type; + de->d_rec_len =3D cpu_to_le16( + sizeof(struct ftrfs_dir_entry)); + memcpy(de->d_name, name->name, name->len); + de->d_name[name->len] =3D '\0'; + mark_buffer_dirty(bh); + brelse(bh); + inode_set_mtime_to_ts(dir, + current_time(dir)); + mark_inode_dirty(dir); + return 0; + } + offset +=3D le16_to_cpu(de->d_rec_len); + if (!de->d_rec_len) + break; + } + brelse(bh); + } + + /* Need a new block */ + if (i >=3D FTRFS_DIRECT_BLOCKS) + return -ENOSPC; + + block_no =3D ftrfs_alloc_block(sb); + if (!block_no) + return -ENOSPC; + + bh =3D sb_bread(sb, block_no); + if (!bh) { + ftrfs_free_block(sb, block_no); + return -EIO; + } + + memset(bh->b_data, 0, FTRFS_BLOCK_SIZE); + + de =3D (struct ftrfs_dir_entry *)bh->b_data; + de->d_ino =3D cpu_to_le64(ino); + de->d_name_len =3D name->len; + de->d_file_type =3D file_type; + de->d_rec_len =3D cpu_to_le16(sizeof(struct ftrfs_dir_entry)); + memcpy(de->d_name, name->name, name->len); + de->d_name[name->len] =3D '\0'; + + mark_buffer_dirty(bh); + brelse(bh); + + fi->i_direct[i] =3D cpu_to_le64(block_no); + dir->i_size +=3D 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 =3D dir->i_sb; + struct ftrfs_inode_info *fi =3D FTRFS_I(dir); + struct ftrfs_dir_entry *de; + struct buffer_head *bh; + unsigned int offset; + u64 block_no; + int i; + + for (i =3D 0; i < FTRFS_DIRECT_BLOCKS; i++) { + block_no =3D le64_to_cpu(fi->i_direct[i]); + if (!block_no) + break; + + bh =3D sb_bread(sb, block_no); + if (!bh) + return -EIO; + + offset =3D 0; + while (offset + sizeof(*de) <=3D FTRFS_BLOCK_SIZE) { + de =3D (struct ftrfs_dir_entry *)(bh->b_data + offset); + + if (de->d_ino && + de->d_name_len =3D=3D 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 +=3D 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 =3D dir->i_sb; + struct inode *inode; + struct ftrfs_inode_info *fi; + u64 ino; + + ino =3D ftrfs_alloc_inode_num(sb); + if (!ino) + return ERR_PTR(-ENOSPC); + + inode =3D new_inode(sb); + if (!inode) + return ERR_PTR(-ENOMEM); + + inode_init_owner(&nop_mnt_idmap, inode, dir, mode); + inode->i_ino =3D ino; + inode->i_blocks =3D 0; + inode->i_size =3D 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 =3D FTRFS_I(inode); + memset(fi->i_direct, 0, sizeof(fi->i_direct)); + fi->i_indirect =3D 0; + fi->i_dindirect =3D 0; + fi->i_flags =3D 0; + + if (S_ISDIR(mode)) { + inode->i_op =3D &ftrfs_dir_inode_operations; + inode->i_fop =3D &ftrfs_dir_operations; + set_nlink(inode, 2); + } else { + inode->i_op =3D &ftrfs_file_inode_operations; + inode->i_fop =3D &ftrfs_file_operations; + set_nlink(inode, 1); + } + + insert_inode_hash(inode); + mark_inode_dirty(inode); + return ERR_CAST(inode); +} + +/* ------------------------------------------------------------------ */ +/* create =E2=80=94 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 =3D ftrfs_new_inode(dir, mode | S_IFREG); + if (IS_ERR(inode)) + return PTR_ERR(inode); + + ret =3D ftrfs_write_inode_raw(inode); + if (ret) + goto out_iput; + + ret =3D ftrfs_add_dirent(dir, &dentry->d_name, inode->i_ino, 1 /* DT_REG = */); + if (ret) + goto out_iput; + + ret =3D ftrfs_write_inode_raw(dir); + if (ret) + goto out_iput; + + d_instantiate(dentry, inode); + return 0; + +out_iput: + iput(inode); + return ret; +} + +/* ------------------------------------------------------------------ */ +/* mkdir =E2=80=94 create a directory = */ +/* ------------------------------------------------------------------ */ + +static struct dentry *ftrfs_mkdir(struct mnt_idmap *idmap, struct inode *d= ir, + struct dentry *dentry, umode_t mode) +{ + struct inode *inode; + int ret; + + inode_inc_link_count(dir); + + inode =3D ftrfs_new_inode(dir, mode | S_IFDIR); + if (IS_ERR(inode)) { + inode_dec_link_count(dir); + return ERR_CAST(inode); + } + + /* Add . and .. entries */ + ret =3D ftrfs_add_dirent(inode, &(struct qstr)QSTR_INIT(".", 1), + inode->i_ino, 4 /* DT_DIR */); + if (ret) + goto out_fail; + + ret =3D ftrfs_add_dirent(inode, &(struct qstr)QSTR_INIT("..", 2), + dir->i_ino, 4 /* DT_DIR */); + if (ret) + goto out_fail; + + ret =3D ftrfs_write_inode_raw(inode); + if (ret) + goto out_fail; + + ret =3D ftrfs_add_dirent(dir, &dentry->d_name, inode->i_ino, + 4 /* DT_DIR */); + if (ret) + goto out_fail; + + ret =3D 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 =E2=80=94 remove a file = */ +/* ------------------------------------------------------------------ */ + +static int ftrfs_unlink(struct inode *dir, struct dentry *dentry) +{ + struct inode *inode =3D d_inode(dentry); + int ret; + + ret =3D 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 =E2=80=94 remove an empty directory = */ +/* ------------------------------------------------------------------ */ + +static int ftrfs_rmdir(struct inode *dir, struct dentry *dentry) +{ + struct inode *inode =3D d_inode(dentry); + int ret; + + if (inode->i_nlink > 2) + return -ENOTEMPTY; + + ret =3D 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 =E2=80=94 create a hard link = */ +/* ------------------------------------------------------------------ */ + +static int ftrfs_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *dentry) +{ + struct inode *inode =3D d_inode(old_dentry); + int ret; + + inode_set_ctime_to_ts(inode, current_time(inode)); + inode_inc_link_count(inode); + + ret =3D 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 =E2=80=94 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 =E2=80=94 exported = */ +/* ------------------------------------------------------------------ */ + +const struct inode_operations ftrfs_dir_inode_operations =3D { + .lookup =3D ftrfs_lookup, + .create =3D ftrfs_create, + .mkdir =3D ftrfs_mkdir, + .unlink =3D ftrfs_unlink, + .rmdir =3D ftrfs_rmdir, + .link =3D ftrfs_link, +}; --=20 2.52.0 From nobody Mon Jun 15 20:33:15 2026 Received: from mail-wm1-f45.google.com (mail-wm1-f45.google.com [209.85.128.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 70CC63CAE9B for ; Tue, 14 Apr 2026 10:07:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161261; cv=none; b=bcW5XCzeaIL/n98y1k5JGMkF3MpMol19mc4ku3FIJFR4Jr7YZ28emvW0ngcbkKZpI0fue+Ln4S1pz8uuD6MEBwknkZIcmUT+HKaNgtPozL4elFU0Dtwpq0+ORuXZ4sU1ORBhh5qFzH9gcftzMndQRuj1NxK4NzIQDTA0Ol5z+co= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161261; c=relaxed/simple; bh=yxOwe+ZAi8eq+GEsp/IgrfQcj8tR/cps95A8fxqnPMQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Wmixjgx0Ak0TcotyifiGSTQpRGbb0h+zG3jRWzUs+UMtsuIcsYS2ZBvr92ttRgYMaTs6LXGZ+pkvtJ6UKKNdlw4wZhtF9FKYNiQ5wEAtxOqycKRJ9qMIf1zYf4PpJrPHO5noiu1UQAeWgqg7PSos8bOaI+iFlpJFpUfCjN8MDe4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.128.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-wm1-f45.google.com with SMTP id 5b1f17b1804b1-48880947b7dso7320965e9.0 for ; Tue, 14 Apr 2026 03:07:39 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776161258; x=1776766058; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=a465pWOyvoZU05HiIIPl0LD4EBbwLj19TV803zaD4aE=; b=rV/FVYS+DNVlC43JflrEdIrCWHZcN4qLWNEiU5TuoDYXO4JES5sXFbb3RyB8C+TtCU qnmcyefM1v4V2we7fSPkSECWxZyEvMaCwcjkSSAmIHM/G529HOFrBcWgS6QVP9kK3O7x Epotssg9uXFZW/pfAm1pvWvpz/f/T6oOFUmi3v5hnHjVdomSF/+obhfiiCfvWsGAA2mP VfuhZcgFxEQQt4viWTaOILeRifN8fkJfSLpGfuraPlAYFQ7yUC0sxFNz4Rm4WZ6jcylJ JDtTF7lpk1Hq9Aqt876TZyG0hW/5kgobB7iLyA0L9DAg8sUzeZtaggqqknD+VdbN5UCw OyEQ== X-Gm-Message-State: AOJu0YxDoAPPL58og/Z1Rd3xXEfmawvrkr3x+Y1cZW7cyFoikr/l6QXN g1IysJ00GHWkQXXzpd3d49pRYRxAdApyIRl0ZLojs2TsnBf4GL55L8Dw X-Gm-Gg: AeBDieufpD5S++tOgtR4kCDt+hvFwdn6xIz0lqKx768/UO85zDJ4996e26k88Bn0bTL 4CyzSDk7QYXZHkHuBO9rI7E1dql5nwY/BvxZhgz56GlxGNq5A8/n9BBQUdPhAOH4Bj/UEkUCFII pL60BKr64ePEAc2K0byPWKRNhRgXvcREhbUbnaArf6FaC/KP3BXPz9bqoWifh5II9J+R9P2q3Hr XbmSQws6MRUSBPYEV5ljYz85ahtV68Pyrq6Ay/3m2tYPz9GwLuV4w+0wmB2sMmlJirs9h1OLrtB C7RZc+jxSIchcnVNgWyeTzfwQyo3sf6VWqgMshPMhGD+NM2eZH+kKUuJpZx5O/lVk6iH3jG4lOy S/diD1odQKN/Ed6MHqrdI8JSvagI1Yqm2ul2EtJMqXow6a+zv/Sm462l/Dkok1HmoOBAgcTeyYv GLCYLkILP5zMOnC+jdvY9XoxMjsnnNxxAgtBn4M2BtwW/e7NUNDh7ONcgUoIXvxpq7rTGyjwBMM /w= X-Received: by 2002:a05:600c:524a:b0:488:926e:3ccd with SMTP id 5b1f17b1804b1-488d7f24014mr120661015e9.4.1776161257764; Tue, 14 Apr 2026 03:07:37 -0700 (PDT) Received: from spartian-1.home ([2a01:cb1c:784:2f00:708:2805:7128:7a75]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-488ede1e050sm65870545e9.5.2026.04.14.03.07.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Apr 2026 03:07:37 -0700 (PDT) From: Aurelien DESBRIERES 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 Subject: [PATCH v3 08/12] ftrfs: add CRC32 checksumming and Reed-Solomon FEC skeleton Date: Tue, 14 Apr 2026 14:07:21 +0200 Message-ID: <20260414120726.5713-9-aurelien@hackers.camp> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260414120726.5713-1-aurelien@hackers.camp> References: <20260413230601.525400-1-aurelien@hackers.camp> <20260414120726.5713-1-aurelien@hackers.camp> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Implement data integrity layer: - ftrfs_crc32(): CRC32 wrapper via kernel crc32_le(), used for per-inode and per-superblock checksums - init_gf_tables(): initialize GF(2^8) Galois Field lookup tables (primitive polynomial 0x1d) for Reed-Solomon arithmetic - gf_mul(): GF(2^8) multiplication via log/exp tables - ftrfs_rs_encode(): systematic Reed-Solomon encoder over FTRFS_SUBBLOCK_DATA bytes with FTRFS_RS_PARITY check symbols The RS encoder operates on sub-blocks within each 4096-byte data block. Decoding (error correction) is not yet implemented. Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/edac.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 fs/ftrfs/edac.c diff --git a/fs/ftrfs/edac.c b/fs/ftrfs/edac.c new file mode 100644 index 000000000000..ebe676c98be6 --- /dev/null +++ b/fs/ftrfs/edac.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTRFS =E2=80=94 EDAC layer: CRC32 + Reed-Solomon FEC + * Author: roastercode - Aurelien DESBRIERES + */ + +#include +#include +#include +#include "ftrfs.h" + +/* Reed-Solomon FEC context */ +static uint8_t gf_exp[256]; +static uint8_t gf_log[256]; +static bool rs_initialized; + +/* Initialize Galois Field tables */ +static void init_gf_tables(void) +{ + uint8_t x =3D 1; + + for (int i =3D 0; i < 255; i++) { + gf_exp[i] =3D x; + gf_log[x] =3D i; + x =3D (x << 1) ^ ((x & 0x80) ? 0x1d : 0); + } + gf_exp[255] =3D gf_exp[0]; + rs_initialized =3D true; +} + +/* Galois Field multiplication */ +static uint8_t gf_mul(uint8_t a, uint8_t b) +{ + if (a =3D=3D 0 || b =3D=3D 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 =3D 0; i < FTRFS_SUBBLOCK_DATA; i++) { + uint8_t feedback =3D gf_mul(msg[i], gf_exp[FTRFS_RS_PARITY]); + + if (feedback !=3D 0) { + for (int j =3D 1; j <=3D FTRFS_RS_PARITY; j++) + msg[FTRFS_SUBBLOCK_DATA + j - 1] ^=3D 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; +} --=20 2.52.0 From nobody Mon Jun 15 20:33:15 2026 Received: from mail-wm1-f48.google.com (mail-wm1-f48.google.com [209.85.128.48]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 659C43CAE9C for ; Tue, 14 Apr 2026 10:07:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161262; cv=none; b=jB0Oisry0FR61OMHguZYI6a/VFe3cOL+buKu6zwaU27jROKuZG/6xdOhYq//ebq0wiWnCKubyVWD+e9Igu9o4DggmcYSKcP9gZ4u2EjQk1WJ2SYA4d50sUL4kH89YcpdlEzsDuWa1XPHQ+PpSgqt468m9QJLFnu3z0jed9UP5Dg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161262; c=relaxed/simple; bh=hMaIZFhC0Qq5n3nLpuTUKzpgOWEDdEMtpfCkU3VWc/c=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=PkO2YQ3/ivX+X9kLYpnqDjd3mRI90KCYiURcx/YzFI8YCr1GqYKUc5fVDudx4YJ8yaKfI8+ZPHQeK+9f0H/0ZKfqanYbWnB+dGmv9eV71M+uThVud8bobn9YkEy5y4mQcAEeUNM4JQGaYoN9Vjfo8Syz+2smNUbLGz34HYIQywY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.128.48 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-wm1-f48.google.com with SMTP id 5b1f17b1804b1-488a8f97f6bso10032285e9.2 for ; Tue, 14 Apr 2026 03:07:40 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776161259; x=1776766059; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=K/PS/iPj83KlqJXElwiqqFPyNMNlzYn8kL+JR6PR3/w=; b=L2dKtqkVFHtF5w2C3WJrI+SAnK3DOOayG+Ll/raKrkC+uArKDq24gF2BI/OvOJk+QQ /YJmBG5Uq6Y+k+zaTOrZi48TL+9+n0zQZzATZ+VCoITD5+94x+oEYDCqQdTV2xEs3xFa MAXesLlw/GTg2YalXMZFODVZRHC0jiedF3OQWiAqN3xs27fv4l6m4RLr9Vn8qlYpyh0p u01IYlXeGmSt47WMvlAXF2A7m0GrOv0W79dV/WMPE6jkHOoAikFRNQREvYe195kMLUSr Fzu5FigHAQooLdFZ/TJpyf+1BaYsqdrwkjHTgD+WbQtp9cpgoM1E3fgetJMT1PprZtKY SUiQ== X-Gm-Message-State: AOJu0YzB0m0Vyy25n4JUHcxTDx0Dkf3PSwhlsJq6ss2YLA9QS0ovWgHq yj/GWmE5UKrnxHwTLT7jAK0Mz2zuSPgC0t/CdC30bNZ+soDb8qEaypP+ X-Gm-Gg: AeBDieusNgAsvhCYUQGKLzqVhK/e97Ebmp8XwZbtf6fbW3ueXhDeVjTSpL/DWm0M/E8 sA1ucO2rg6dQN3g06asliPeku0FurXYJvzVcDs2JK7hUZ6E389B8iYeKXGXoIa4yRQ3w4AcXpJ4 o0gwmviESMd5DW1+zI3/SxUzrbvCXOlMjHJ4KnE7VC7XuThHiwrwHUzCnbOAFouPpISSP0+oh8m 1BcyHycUsbJBsLou3YMbRMuaoBhGxfzRynMb/Is2zZltxsPnZbx4zfRyiYXlLGBnSzwce1UW1kx JXIGMt2bIwuFfzIVGU/xaAogkJ+dmfJRpWhXb7pDsP/B2RHPWaEdJoqoeNQ2jMRInJCK+YtASfM XIHHZdF2bKLiCGsXHDkX7jgF7n5aImI3mjg6SrvFu6vljz4uq2ISBjt0LH30BfKvzq0ZwoXIrdQ oWwM3YcYvpY58oVLFMd4t0aQJR6p/YZWzjBFvOXUyX+LNwAtXlDtu05C5ywTENKHWneUocy3q6Y 0k= X-Received: by 2002:a05:600c:4f95:b0:488:abe9:86 with SMTP id 5b1f17b1804b1-488d688dcc5mr125044035e9.7.1776161258764; Tue, 14 Apr 2026 03:07:38 -0700 (PDT) Received: from spartian-1.home ([2a01:cb1c:784:2f00:708:2805:7128:7a75]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-488ede1e050sm65870545e9.5.2026.04.14.03.07.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Apr 2026 03:07:38 -0700 (PDT) From: Aurelien DESBRIERES 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 Subject: [PATCH v3 09/12] ftrfs: add Kconfig, Makefile and fs/ tree integration Date: Tue, 14 Apr 2026 14:07:22 +0200 Message-ID: <20260414120726.5713-10-aurelien@hackers.camp> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260414120726.5713-1-aurelien@hackers.camp> References: <20260413230601.525400-1-aurelien@hackers.camp> <20260414120726.5713-1-aurelien@hackers.camp> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable 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) +=3D ftrfs/ Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/Kconfig | 49 +++++++++++++++++++++++++++++++++++++++++++++++ fs/ftrfs/Makefile | 46 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 fs/ftrfs/Kconfig create mode 100644 fs/ftrfs/Makefile diff --git a/fs/ftrfs/Kconfig b/fs/ftrfs/Kconfig new file mode 100644 index 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 =E2=80=94 Fault-Tolerant Radiation-Robust Filesystem +# + +obj-$(CONFIG_FTRFS_FS) +=3D ftrfs.o + +ftrfs-y :=3D super.o \ + inode.o \ + dir.o \ + file.o \ + edac.o \ + alloc.o \ + namei.o + +ftrfs-$(CONFIG_FTRFS_FS_XATTR) +=3D xattr.o + +ifneq ($(KERNELRELEASE),) +else + +ifneq ($(KERNEL_SRC),) + KERNELDIR :=3D $(KERNEL_SRC) +else + KERNELDIR ?=3D /lib/modules/$(shell uname -r)/build +endif + +ifneq ($(O),) + KBUILD_OUTPUT :=3D O=3D$(O) +else + KBUILD_OUTPUT :=3D +endif + +PWD :=3D $(shell pwd) + +all: + $(MAKE) -C $(KERNELDIR) $(KBUILD_OUTPUT) M=3D$(PWD) \ + CONFIG_FTRFS_FS=3Dm CONFIG_FTRFS_FS_XATTR=3Dn CONFIG_FTRFS_FS_SECURITY= =3Dn \ + modules + +clean: + $(MAKE) -C $(KERNELDIR) $(KBUILD_OUTPUT) M=3D$(PWD) clean + +modules_install: + $(MAKE) -C $(KERNELDIR) $(KBUILD_OUTPUT) M=3D$(PWD) modules_install + +endif --=20 2.52.0 From nobody Mon Jun 15 20:33:15 2026 Received: from mail-wm1-f53.google.com (mail-wm1-f53.google.com [209.85.128.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 58E393D0901 for ; Tue, 14 Apr 2026 10:07:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161263; cv=none; b=MSGXJfoOdlavjibIahhZTMhrs7JksZByO+/+CYXfpO9VsLXKNg00DqnpwidAEItPi8hVw4RcnlXNoLptPrwdvo1inXHbntrJ6lZYXcCcex4VkJuUO+RAfCFdUGTn1dRNXJHQC7clNJPof6Gm1HJ8G9HSAPSSCFlSFB+gLjGn5Ks= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161263; c=relaxed/simple; bh=DQpiyPZNbcYcXKHK2Ug7zpKaXkVtZXwpUbqDbwhMDcg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=f/yJk08KlAICmhpUD+AK1WWPw7KiZn1E0zna4rGLQf6idzZN1300fqcwuGyTx03apTSgdHlQsc9+4idWQi0rcwJGLUOKTC9ZQqG1Y9EVg9+NmN0/lYmIvgZgTlrScGNVNf9SIhkzsZhFdcbSQUTqrILAJ30Dub+bjtKdJKpsbvA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.128.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-wm1-f53.google.com with SMTP id 5b1f17b1804b1-488c1f446f3so8679015e9.0 for ; Tue, 14 Apr 2026 03:07:41 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776161260; x=1776766060; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=SYg9UhljPZ90L0nUSlZxDYCO9P69XaU6hZAMI9+Tfw4=; b=AjXjOmfCSNbtJCYg7igbCxQDC88y10T/XAcjUNW/+bqoWTiXtDM2IYKP5+Vd7Fvuad IuEgSwHVJWlbnnRsplWKy3nme5tDbgz9UCF/TLUK/DaTxLMEsx4V7xHF4+trQadts67r +vx9THO3MQfMyJJnMkTE1I4G/jcUpS9i501zDPViiyjeZMvHakU7M0bcRV4+wapcPydd /3MjuJxcXssTaSg9ZcthvlCINegqF7uXneuIGV36zB7My/wTelCpHXHHgETreBFtU74G 50KONik4ekIfpAEcqleueY2Zzepv8EpBNnhn2qUCDBK6bN6PDh2m23Po/bOu+846yBZs pfCA== X-Gm-Message-State: AOJu0YyJpQmNsIgQjYABeihU2LLmIVG0RWDvBcFji8BuWTIzGPEUlrUx Q3jfhbKaqObBhsAoT70sWjMIc3R5Ilv0Yga653srT3Jk+Z25dvY8WDhv X-Gm-Gg: AeBDiesUyEsVfj9vUYVZX89j/3+DkEyoSQax+RwNmzJdYZMmmYNW6p3CTmSspl7yqgW Y7NOHeFadEdy1JeC5s7jbsF9P71BdKPcdLOigrkxtrlK2f/+Wcr1duk5PcPmpqcJy6AYeRvAnpP qOx7XpSGP2xlh51mnD5gJoqQiOvZVI5LRZV9rh+Hmk5BLnSkiZQR2kwur8SllghnxnuZ8rLwzjP suG1e+xClz/h+oMtFgraMfDwaM4BKf2cdro730A2xFz1X/G4ni9Ji+rg7UUI3YbxAbTvzhcJvtl aUH3lylAhz173/eY0LY9fX3y3AQtn7Vff/VCpvNFWE4Zgn93S6nEoYtr5adYZKunhu3EgfjIggp tFyVy2Y+jQw+3R7NgzTJzMTACH5+3hNs9sYzeZiHFSohM01/QU9Qx2XNNBoEHXO/DREAReshPyn q3yeAdOHKISwEQB4naU+0+ryapoa5tXaxI/IQci36dnSoNsmKRmi7AYEVNs2j4A5y3smrYCb7fy 84= X-Received: by 2002:a05:600c:4fca:b0:485:f1d6:2b1d with SMTP id 5b1f17b1804b1-488d6658dafmr139045415e9.0.1776161259711; Tue, 14 Apr 2026 03:07:39 -0700 (PDT) Received: from spartian-1.home ([2a01:cb1c:784:2f00:708:2805:7128:7a75]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-488ede1e050sm65870545e9.5.2026.04.14.03.07.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Apr 2026 03:07:39 -0700 (PDT) From: Aurelien DESBRIERES 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 Subject: [PATCH v3 10/12] MAINTAINERS: add entry for FTRFS filesystem Date: Tue, 14 Apr 2026 14:07:23 +0200 Message-ID: <20260414120726.5713-11-aurelien@hackers.camp> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260414120726.5713-1-aurelien@hackers.camp> References: <20260413230601.525400-1-aurelien@hackers.camp> <20260414120726.5713-1-aurelien@hackers.camp> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Signed-off-by: Aurelien DESBRIERES --- 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 =20 +FTRFS FILE SYSTEM +M: Aur=C3=A9lien DESBRIERES +L: linux-fsdevel@vger.kernel.org +S: Maintained +F: fs/ftrfs/ + EXT2 FILE SYSTEM M: Jan Kara L: linux-ext4@vger.kernel.org --=20 2.52.0 From nobody Mon Jun 15 20:33:15 2026 Received: from mail-wm1-f42.google.com (mail-wm1-f42.google.com [209.85.128.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 967B23CBE88 for ; Tue, 14 Apr 2026 10:07:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161265; cv=none; b=FF1TIK7HsTNnHhaGtZJFO19KP3rPesBM00npl7KF7RR0gL6sKsKersbjnfhhlhRnKwH6gHILpXeoUFhJFmezh9KaJWERLKn/66oQxxUJnc2LGz7vcRqtRb/miGL+BwDb2mB5INHgkNl0U8iwSJ7IPPO3tN9BJvXUlnZKg+v4ED0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161265; c=relaxed/simple; bh=/EIM7YFF4b74bmElYKXZBW4YDA9BK2dtDOSuYEtCAco=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=RJaosWt19V6/FF4vPrGAr/DtQ8n4KnhgJPfYgmDp+gmCfmHDmEscw2NyjNVQ3ph1G2ULSYFu4H6ObSWu5PVsnjP77IJDl6r5LF6gEU9Jb6AuGTuiv64hYtzoklMEukxSa0cTUmzs8jUNy+TiCnkd9cCM6GrJXmu0UT3ArXrxPVk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.128.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-wm1-f42.google.com with SMTP id 5b1f17b1804b1-488822b93c7so6003915e9.1 for ; Tue, 14 Apr 2026 03:07:42 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776161261; x=1776766061; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=a900wcOj7zcPWpJzKD066ft+BK23BwJpbo5stM93Fwg=; b=NV9qgFqMp0/UJS252KAvS5G3Vwz54O2RBLXICwa/3iiSeNeu1Yd4fEZCXTwD+AxPWM MzHY9v4ec+q++Nzbu71nrthh8yS6chOZRxzYIlfb1Jvrg9Vm6GIn4QaGUrWH2BqTNJlH Zz6pUtm2xqHQJIJUITxlMGJWAR4qzkwvD84U/gOGUUXN9mS80Ts4DJax7jwiz6+8sa82 +R/qdKoFdMFG5Gj8bUZheSjilIu2WBcTfRxzl7+x9cPSeEUAPU0W+ImXh0FrqCk4yD2P 79DWFgGFaaR7yTSlSFnAJJgZYLedR32AwRJSR4VhE4nO+Stqcqh+ll6ih6ahJ72AHAIi fUQw== X-Gm-Message-State: AOJu0Yxem1wgiJwsI3VSSLFMxaOZWGiONSevjTsK0wBKDYGoCbzXh9hM 59k8pyfiJkTnb89gp2+e6M8U5/bNAnRbyh6iIcNo8/zaWk3qMc7e3Z/S X-Gm-Gg: AeBDieu2dG8iBsakWKBozCTiEzCfPhs1qj4bk7Qi9TmC8W4bE9U+dkmgu3gkILkbA6G uM7MDCuxzBIiVSamYCHkpBVKBlkUvsx+sp0e+0Nezn0ipsTQZhBXowIv6SBHAjEBvyGxWKo1iHC C+q8IQJghx8FyWbKcwx+05MoTY9xX+wuIPwhrtyjqYeemR0U249jsKEvR/tZNvyolGZtMq5n6to GTP5wHSDfGvJQkWoeyH2aHAzLPmkNfMoiFvTHJU33ymuArrocfgU6VGn8AfmXKMSfJf3b+0cQwY pCxj0Q47xrZShSli7hTmeQkvv6qrkgDxlKxW9Va5TnRVOLGJ1scTS4z9ISrwHxL3vnr8ymldN15 KfINYqldlDZ7qkY8KwXj759ELnIA53RjnqDIukftLjr47s/n8uKXGSBLIRxySa2+/3hwlRLJRGR y2fvWiib+Z5W2LF4XFMyVU2rmNB6vaGryZvyuMhvFLLe2wmLzvXgpSz+eWPOJ5Y5dls8Y74783t +E= X-Received: by 2002:a05:600c:4689:b0:488:93c5:4dc with SMTP id 5b1f17b1804b1-488d7f76192mr140071495e9.7.1776161260697; Tue, 14 Apr 2026 03:07:40 -0700 (PDT) Received: from spartian-1.home ([2a01:cb1c:784:2f00:708:2805:7128:7a75]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-488ede1e050sm65870545e9.5.2026.04.14.03.07.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Apr 2026 03:07:40 -0700 (PDT) From: Aurelien DESBRIERES 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 Subject: [PATCH v3 11/12] =?UTF-8?q?ftrfs:=20v2=20fixes=20=E2=80=94=20writ?= =?UTF-8?q?e=20path,=20inode=20lifecycle,=20on-disk=20format?= Date: Tue, 14 Apr 2026 14:07:24 +0200 Message-ID: <20260414120726.5713-12-aurelien@hackers.camp> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260414120726.5713-1-aurelien@hackers.camp> References: <20260413230601.525400-1-aurelien@hackers.camp> <20260414120726.5713-1-aurelien@hackers.camp> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable address_space_operations: - Implement ftrfs_get_block with block allocation (create=3D1) - Return -EIO for holes on read (create=3D0), not 0 - Set bh_result->b_size =3D 1 << i_blkbits on all mapped paths - Add ftrfs_write_begin, ftrfs_write_end, ftrfs_readahead, ftrfs_bmap - Add dirty_folio, invalidate_folio to ftrfs_aops Inode lifecycle: - Use insert_inode_locked() instead of insert_inode_hash() new_inode() does not set I_NEW; insert_inode_locked() does - Move unlock_new_inode() to callers after d_instantiate() - Add unlock_new_inode() on error paths On-disk format: - ftrfs_inode: 128 -> 256 bytes - uid/gid: __le16 -> __le32 (standard kernel convention) - Add i_tindirect: triple indirect (~512 GiB max file size) - Remove i_blocks: redundant, calculable from i_size - i_reserved[84]: explicit padding to 256 bytes - Superblock padding: 3948 -> 3980 bytes (enforce 4096) - Add BUILD_BUG_ON for both structure sizes Directory: - Skip . and .. in readdir data blocks (dir_emit_dots emits them) Compat: - Add ftrfs_inode_is_new macro for inode_state_read_once API Tested on arm64 kernel 7.0-rc7 (Yocto KVM): - mount, write, mkdir, read: all working - 0 BUG/WARN/Oops in dmesg Addresses review feedback from: - Matthew Wilcox: address_space_operations now implemented - Darrick J. Wong: i_size __le64 intentional, BUILD_BUG_ON documents limits - Andreas Dilger: DO-178C/ECSS certification rationale documented Signed-off-by: Aurelien DESBRIERES --- fs/ftrfs/dir.c | 88 +++++++++++++++++++++----------------------- fs/ftrfs/file.c | 95 +++++++++++++++++++++++++++++++++++++++++++++--- fs/ftrfs/ftrfs.h | 35 +++++++++++++----- fs/ftrfs/inode.c | 9 +++-- fs/ftrfs/namei.c | 21 +++++++---- fs/ftrfs/super.c | 2 + 6 files changed, 177 insertions(+), 73 deletions(-) diff --git a/fs/ftrfs/dir.c b/fs/ftrfs/dir.c index dbf0102a40f3..fd06910bf9cc 100644 --- a/fs/ftrfs/dir.c +++ b/fs/ftrfs/dir.c @@ -3,7 +3,6 @@ * FTRFS =E2=80=94 Directory operations * Author: roastercode - Aurelien DESBRIERES */ - #include #include #include "ftrfs.h" @@ -13,24 +12,23 @@ */ static int ftrfs_readdir(struct file *file, struct dir_context *ctx) { - struct inode *inode =3D file_inode(file); - struct super_block *sb =3D inode->i_sb; + struct inode *inode =3D file_inode(file); + struct super_block *sb =3D inode->i_sb; struct ftrfs_inode_info *fi =3D FTRFS_I(inode); struct buffer_head *bh; struct ftrfs_dir_entry *de; unsigned long block_idx, block_no; - unsigned int offset; + unsigned int offset; =20 - /* EOF guard */ if (ctx->pos =3D=3D INT_MAX) return 0; + /* Emit . and .. (ctx->pos: 0=3D., 1=3D.., 2+=3Dreal entries) */ if (ctx->pos < 2) { if (!dir_emit_dots(file, ctx)) return 0; } =20 - /* Iterate over direct blocks only (skeleton: no indirect yet) */ for (block_idx =3D 0; block_idx < FTRFS_DIRECT_BLOCKS; block_idx++) { block_no =3D 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 =3D 0; while (offset < FTRFS_BLOCK_SIZE) { de =3D (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 .. =E2=80=94 emitted by dir_emit_dots */ + if (!de->d_ino || !de->d_name_len) + goto next; + if (de->d_name_len =3D=3D 1 && de->d_name[0] =3D=3D '.') + goto next; + if (de->d_name_len =3D=3D 2 && de->d_name[0] =3D=3D '.' + && de->d_name[1] =3D=3D '.') + 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 +=3D le16_to_cpu(de->d_rec_len); } - brelse(bh); } =20 @@ -73,22 +74,19 @@ static int ftrfs_readdir(struct file *file, struct dir_= context *ctx) * ftrfs_lookup =E2=80=94 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 =3D dir->i_sb; + struct super_block *sb =3D dir->i_sb; struct ftrfs_inode_info *fi =3D FTRFS_I(dir); - struct buffer_head *bh; - struct ftrfs_dir_entry *de; - struct inode *inode =3D 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; =20 - for (block_idx =3D 0; block_idx < FTRFS_DIRECT_BLOCKS; block_idx++) { - block_no =3D le64_to_cpu(fi->i_direct[block_idx]); + for (i =3D 0; i < FTRFS_DIRECT_BLOCKS; i++) { + block_no =3D le64_to_cpu(fi->i_direct[i]); if (!block_no) break; =20 @@ -97,30 +95,28 @@ struct dentry *ftrfs_lookup(struct inode *dir, continue; =20 offset =3D 0; - while (offset < FTRFS_BLOCK_SIZE) { + while (offset + sizeof(*de) <=3D FTRFS_BLOCK_SIZE) { de =3D (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 =3D=3D dentry->d_name.len && !memcmp(de->d_name, dentry->d_name.name, - de->d_name_len)) { - unsigned long ino =3D le64_to_cpu(de->d_ino); + dentry->d_name.len)) { + u64 ino =3D le64_to_cpu(de->d_ino); + struct inode *inode; =20 brelse(bh); inode =3D ftrfs_iget(sb, ino); - goto found; + return d_splice_alias(inode, dentry); } - offset +=3D 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); } =20 const struct file_operations ftrfs_dir_operations =3D { @@ -128,5 +124,3 @@ const struct file_operations ftrfs_dir_operations =3D { .read =3D generic_read_dir, .iterate_shared =3D 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 =E2=80=94 File operations (skeleton) + * FTRFS =E2=80=94 File operations * Author: roastercode - Aurelien DESBRIERES - * - * NOTE: read/write use generic_file_* for now. - * The EDAC/RS layer will intercept at the block I/O level (next iteration= ). */ - #include #include +#include +#include #include "ftrfs.h" =20 const struct file_operations ftrfs_file_operations =3D { @@ -23,3 +21,90 @@ const struct file_operations ftrfs_file_operations =3D { const struct inode_operations ftrfs_file_inode_operations =3D { .getattr =3D simple_getattr, }; + +/* + * ftrfs_get_block =E2=80=94 map logical block to physical block + * Handles allocation when create=3D1, 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 =3D FTRFS_I(inode); + u64 new_block; + __le64 phys; + + if (iblock >=3D FTRFS_DIRECT_BLOCKS) { + pr_err("ftrfs: indirect block not yet supported\n"); + return -EOPNOTSUPP; + } + + phys =3D fi->i_direct[iblock]; + if (phys) { + map_bh(bh_result, inode->i_sb, le64_to_cpu(phys)); + bh_result->b_size =3D 1 << inode->i_blkbits; + return 0; + } + + if (!create) + return -EIO; + + new_block =3D ftrfs_alloc_block(inode->i_sb); + if (!new_block) { + pr_err("ftrfs: no free blocks\n"); + return -ENOSPC; + } + + fi->i_direct[iblock] =3D cpu_to_le64(new_block); + map_bh(bh_result, inode->i_sb, new_block); + bh_result->b_size =3D 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 =3D { + .read_folio =3D ftrfs_read_folio, + .readahead =3D ftrfs_readahead, + .write_begin =3D ftrfs_write_begin, + .write_end =3D ftrfs_write_end, + .writepages =3D ftrfs_writepages, + .bmap =3D ftrfs_bmap, + .dirty_folio =3D block_dirty_folio, + .invalidate_folio =3D 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 #include =20 +/* 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 =20 @@ -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; =20 /* * On-disk inode - * Size: 128 bytes + * Size: 256 bytes + * + * Addressing capacity: + * direct (12) =3D 48 KiB + * indirect (1) =3D 2 MiB + * dindirect (1) =3D 1 GiB + * tindirect (1) =3D 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; =20 /* 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_op= erations; /* 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; =20 /* 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); =20 /* Already in cache */ - if (!(inode_state_read_once(inode) & I_NEW)) + if (!ftrfs_inode_is_new(inode)) return inode; =20 inodes_per_block =3D FTRFS_BLOCK_SIZE / sizeof(struct ftrfs_inode); @@ -63,11 +63,10 @@ struct inode *ftrfs_iget(struct super_block *sb, unsign= ed long ino) =20 /* Populate VFS inode */ inode->i_mode =3D le16_to_cpu(raw->i_mode); - inode->i_uid =3D make_kuid(sb->s_user_ns, le16_to_cpu(raw->i_uid)); - inode->i_gid =3D make_kgid(sb->s_user_ns, le16_to_cpu(raw->i_gid)); + inode->i_uid =3D make_kuid(sb->s_user_ns, le32_to_cpu(raw->i_uid)); + inode->i_gid =3D make_kgid(sb->s_user_ns, le32_to_cpu(raw->i_gid)); set_nlink(inode, le16_to_cpu(raw->i_nlink)); inode->i_size =3D le64_to_cpu(raw->i_size); - inode->i_blocks =3D le32_to_cpu(raw->i_blocks); =20 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 =3D raw->i_indirect; fi->i_dindirect =3D raw->i_dindirect; + fi->i_tindirect =3D raw->i_tindirect; fi->i_flags =3D le32_to_cpu(raw->i_flags); =20 /* 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 =3D &ftrfs_file_inode_operations; inode->i_fop =3D &ftrfs_file_operations; + inode->i_mapping->a_ops =3D &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 =3D (struct ftrfs_inode *)bh->b_data + offset; =20 raw->i_mode =3D cpu_to_le16(inode->i_mode); - raw->i_uid =3D cpu_to_le16(i_uid_read(inode)); - raw->i_gid =3D cpu_to_le16(i_gid_read(inode)); + raw->i_uid =3D cpu_to_le32(i_uid_read(inode)); + raw->i_gid =3D cpu_to_le32(i_gid_read(inode)); raw->i_nlink =3D cpu_to_le16(inode->i_nlink); raw->i_size =3D cpu_to_le64(inode->i_size); - raw->i_blocks =3D cpu_to_le32(inode->i_blocks); raw->i_atime =3D cpu_to_le64(inode_get_atime_sec(inode) * NSEC_PER_SEC + inode_get_atime_nsec(inode)); raw->i_mtime =3D 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 =3D fi->i_indirect; raw->i_dindirect =3D fi->i_dindirect; + raw->i_tindirect =3D fi->i_tindirect; =20 raw->i_crc32 =3D ftrfs_crc32(raw, offsetof(struct ftrfs_inode, i_crc32)); @@ -145,7 +145,6 @@ static int ftrfs_add_dirent(struct inode *dir, const st= ruct qstr *name, =20 fi->i_direct[i] =3D cpu_to_le64(block_no); dir->i_size +=3D FTRFS_BLOCK_SIZE; - dir->i_blocks++; inode_set_mtime_to_ts(dir, current_time(dir)); mark_inode_dirty(dir); =20 @@ -223,7 +222,6 @@ struct inode *ftrfs_new_inode(struct inode *dir, umode_= t mode) =20 inode_init_owner(&nop_mnt_idmap, inode, dir, mode); inode->i_ino =3D ino; - inode->i_blocks =3D 0; inode->i_size =3D 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, umod= e_t mode) } else { inode->i_op =3D &ftrfs_file_inode_operations; inode->i_fop =3D &ftrfs_file_operations; + inode->i_mapping->a_ops =3D &ftrfs_aops; set_nlink(inode, 1); } =20 - 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; } =20 /* ------------------------------------------------------------------ */ @@ -277,9 +280,11 @@ static int ftrfs_create(struct mnt_idmap *idmap, struc= t inode *dir, goto out_iput; =20 d_instantiate(dentry, inode); + unlock_new_inode(inode); return 0; =20 out_iput: + unlock_new_inode(inode); iput(inode); return ret; } @@ -327,9 +332,11 @@ static struct dentry *ftrfs_mkdir(struct mnt_idmap *id= map, struct inode *dir, goto out_fail; =20 d_instantiate(dentry, inode); + unlock_new_inode(inode); return NULL; =20 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; } =20 + BUILD_BUG_ON(sizeof(struct ftrfs_super_block) !=3D FTRFS_BLOCK_SIZE); + BUILD_BUG_ON(sizeof(struct ftrfs_inode) !=3D 256); pr_info("ftrfs: module loaded (FTRFS Fault-Tolerant Radiation-Robust FS)\= n"); return 0; } --=20 2.52.0 From nobody Mon Jun 15 20:33:15 2026 Received: from mail-wm1-f43.google.com (mail-wm1-f43.google.com [209.85.128.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7DC273CC9E9 for ; Tue, 14 Apr 2026 10:07:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161266; cv=none; b=Ja28woW5nhgl4MYBvY5zv/aPop0MGQ6cqqXfVNP5yeaHYzTuZeRwEQgDBTKSRtp09Oa4KCkAkd+gXqk9yJ/A4aOg+SAe1PiFyq2LRlh5grblzVRvo6aVtVz/UPYYq+kJzTuK/LbJ+unK7ODIbkhEtlBfssXXl6a7qKnMBRboxHA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776161266; c=relaxed/simple; bh=xNYDjPn+AycQplZ9xOLGYmQqtfwFksBMI0lDSNikyYU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=QRHWcI9kTZ2TMTiDB+gOG3rqaVSV/4C2YrQ/1+NEldHNKQ1IhqE/WMCVK0RIlWOXeultz7WH2EW6B4Zx2a4w1c9WHtb+cfEtIxdrEyHYMQE13I0vILu6lft5sWE17Hgol99W+u9YMgCWmMQG95EefVvltV2Zybo5vRcj5/DSuPM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.128.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=hackers.camp Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-wm1-f43.google.com with SMTP id 5b1f17b1804b1-488debfa07bso2979215e9.2 for ; Tue, 14 Apr 2026 03:07:43 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776161262; x=1776766062; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=NcPs4nyUwKMtO0DM4U0v6bFXd0AmFFKWA58Kzo3r5rM=; b=KYbBpXPQ2C5PTSJTQGHokXD8DGuaUIogw9hA45JMyuhgX/l4rTeGT7E4FPkwtSfoZe hFZmy/CMpDlgJhNbp0gcl5Ua2qmuPxOwm9smWWplSmbhmNoxQMOzyb5rN3q0vE9Q2LN4 kT8Sz83awg5ZxX8tQOoghtmKSnMxIC1ombRiq93+FHJi9rDm93uLXonym6KpN0Ifh83X T+2RR+9iqu9CkB9P8mPZagPnlnqk/OUFxaPRHBwP669/iz392oOIbr227GQ9iikXwh/C 6j1pH4t9Mn19yCKG3EtEfTPz5Oaz1CpVy4DuN/k/hDy4cAkHrfxcqP5aFIjuYIyzjOHa Vnlg== X-Gm-Message-State: AOJu0YyQ7qOsADxZZEXjWpgyD7CcwLMRORFdfzV28eQacXHYq/qGpPes KIS56wMANDXw+RonoqO2Nxusl86Z942ogbQxXm8AdHb+qlDrkIXYc8onoveBoQyL X-Gm-Gg: AeBDiesrxT2fgWEfAhskZ8whNNz/KsOz9iJ4XZdgoJWOx82hvy8VkZqdKj41sq2rkKr LWlFJhph+v67KMSRU7E2uVhvMF8fq7SCi9aJ7Dn8WUF9vRGIRdEXGngbo1/GZGKqPYTjhw5WWUI ZvlPOvyWe4u1ORTg7IdYZ0RE2v9rxMF/qd5J+QJy3wm2JPNEeTjdcthHJ5odSN2nsFUq05JATb/ fCjpIz45FkA/vTKNlSKbVfOiHnRoWUpytztQ3spyZnY3UhzAHYghsGmvacrm3yOo5dUdU6pyg+j CHth/9LxZFJB2XQVOVVNOTDafjMrl6kLmFIOAUV1R2tWWHrhghSRUXR8Ivaji7VeniAY0LohQJG y9skXHShvdIhiulWwhpsDLdDEZsRyvFolt3ICIa4elXirJuAnwODqHnlHE6fxj2Fof1Jwg0zM/4 Bz++2+0nm2xqQ9x6XDHeAi9vhc1wxP44eUXUZnohsbyNgL7sVaOdWQl+mNHoFcggY2Vis8xHJuR Os= X-Received: by 2002:a05:600c:190d:b0:488:b7ec:a9ba with SMTP id 5b1f17b1804b1-488d6abebf8mr115314995e9.7.1776161261797; Tue, 14 Apr 2026 03:07:41 -0700 (PDT) Received: from spartian-1.home ([2a01:cb1c:784:2f00:708:2805:7128:7a75]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-488ede1e050sm65870545e9.5.2026.04.14.03.07.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Apr 2026 03:07:41 -0700 (PDT) From: Aurelien DESBRIERES 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 Subject: [PATCH v3 12/12] =?UTF-8?q?ftrfs:=20v3=20=E2=80=94=20iomap=20IO?= =?UTF-8?q?=20path,=20rename,=20RS=20decoder,=20Radiation=20Event=20Journa?= =?UTF-8?q?l?= Date: Tue, 14 Apr 2026 14:07:25 +0200 Message-ID: <20260414120726.5713-13-aurelien@hackers.camp> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260414120726.5713-1-aurelien@hackers.camp> References: <20260413230601.525400-1-aurelien@hackers.camp> <20260414120726.5713-1-aurelien@hackers.camp> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable 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 and 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 =3D 64 - Replace 3980-byte s_pad with s_rs_journal[64] + s_pad[2443] - sizeof(ftrfs_super_block) =3D=3D 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 --- 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; } =20 -/* 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 =3D true; + int i, j; + + for (i =3D 0; i < FTRFS_RS_PARITY; i++) { + syndromes[i] =3D 0; + for (j =3D 0; j < msglen; j++) + syndromes[i] ^=3D gf_mul(msg[j], gf_pow(gf_exp[1], i * j)); + if (syndromes[i]) + all_zero =3D false; + } + return all_zero; +} + +/* + * Berlekamp-Massey =E2=80=94 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 =3D 1; + int i, j, nerr, new_len; + uint8_t delta; + + err_loc[0] =3D 1; + *err_loc_len =3D 1; + old_loc[0] =3D 1; + + for (i =3D 0; i < FTRFS_RS_PARITY; i++) { + delta =3D syndromes[i]; + for (j =3D 1; j < *err_loc_len; j++) + delta ^=3D gf_mul(err_loc[j], syndromes[i - j]); + + tmp[0] =3D 0; + memcpy(tmp + 1, old_loc, old_len); + + if (delta =3D=3D 0) { + old_len++; + } else if (2 * (*err_loc_len - 1) <=3D i) { + new_len =3D old_len + 1; + for (j =3D 0; j < new_len; j++) { + new_loc[j] =3D (j < *err_loc_len) ? err_loc[j] : 0; + new_loc[j] ^=3D gf_mul(delta, tmp[j]); + } + memcpy(old_loc, err_loc, *err_loc_len); + old_len =3D *err_loc_len + 1 - new_len + old_len; + memcpy(err_loc, new_loc, new_len); + *err_loc_len =3D new_len; + } else { + for (j =3D 0; j < *err_loc_len; j++) + err_loc[j] ^=3D gf_mul(delta, tmp[j]); + old_len++; + } + } + + nerr =3D *err_loc_len - 1; + if (nerr > FTRFS_RS_PARITY / 2) + return -1; + return nerr; +} + +/* + * Chien search =E2=80=94 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 =3D 0; + int i, j; + uint8_t val; + + for (i =3D 0; i < msglen; i++) { + val =3D 0; + for (j =3D 0; j < err_loc_len; j++) + val ^=3D gf_mul(err_loc[j], gf_pow(gf_exp[1], i * j)); + if (val =3D=3D 0) + errs[nerrs++] =3D msglen - 1 - i; + } + return nerrs; +} + +/* + * Forney algorithm =E2=80=94 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 =3D 0; i < FTRFS_RS_PARITY; i++) { + for (j =3D 0; j < err_loc_len && j <=3D i; j++) + omega[i] ^=3D gf_mul(syndromes[i - j], err_loc[j]); + } + + memset(err_loc_prime, 0, sizeof(err_loc_prime)); + for (i =3D 1; i < err_loc_len; i +=3D 2) + err_loc_prime[i - 1] =3D err_loc[i]; + + for (i =3D 0; i < nerrs; i++) { + uint8_t xi =3D gf_pow(gf_exp[1], errs[i]); + uint8_t xi_inv =3D gf_inv(xi); + uint8_t omega_val =3D 0; + uint8_t elp_val =3D 0; + + for (j =3D FTRFS_RS_PARITY - 1; j >=3D 0; j--) + omega_val =3D gf_mul(omega_val, xi_inv) ^ omega[j]; + + for (j =3D (err_loc_len - 1) & ~1; j >=3D 0; j -=3D 2) + elp_val =3D gf_mul(elp_val, gf_mul(xi_inv, xi_inv)) + ^ err_loc_prime[j]; + + if (elp_val =3D=3D 0) + continue; + + msg[errs[i]] ^=3D 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(); =20 - /* 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 =3D 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 =3D rs_chien_search(err_loc, err_loc_len, + FTRFS_SUBBLOCK_DATA + FTRFS_RS_PARITY, + errs); + if (nroots !=3D 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; } =20 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 #include -#include -#include +#include +#include #include "ftrfs.h" =20 +/* Forward declaration =E2=80=94 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 =3D { .llseek =3D generic_file_llseek, .read_iter =3D generic_file_read_iter, - .write_iter =3D generic_file_write_iter, + .write_iter =3D ftrfs_file_write_iter, .mmap =3D generic_file_mmap, .fsync =3D generic_file_fsync, .splice_read =3D filemap_splice_read, @@ -23,88 +26,172 @@ const struct inode_operations ftrfs_file_inode_operati= ons =3D { }; =20 /* - * ftrfs_get_block =E2=80=94 map logical block to physical block - * Handles allocation when create=3D1, returns -EIO for holes on read. + * ftrfs_iomap_begin =E2=80=94 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 lengt= h, + unsigned int flags, struct iomap *iomap, + struct iomap *srcmap) { - struct ftrfs_inode_info *fi =3D FTRFS_I(inode); - u64 new_block; - __le64 phys; + struct ftrfs_inode_info *fi =3D FTRFS_I(inode); + struct super_block *sb =3D inode->i_sb; + u64 iblock =3D pos >> FTRFS_BLOCK_SHIFT; + u64 new_block; + u64 phys; =20 if (iblock >=3D FTRFS_DIRECT_BLOCKS) { - pr_err("ftrfs: indirect block not yet supported\n"); + pr_err_ratelimited("ftrfs: iomap: offset beyond direct blocks\n"); return -EOPNOTSUPP; } =20 - phys =3D fi->i_direct[iblock]; + iomap->offset =3D iblock << FTRFS_BLOCK_SHIFT; + iomap->length =3D FTRFS_BLOCK_SIZE; + iomap->bdev =3D sb->s_bdev; + iomap->flags =3D 0; + + phys =3D le64_to_cpu(fi->i_direct[iblock]); if (phys) { - map_bh(bh_result, inode->i_sb, le64_to_cpu(phys)); - bh_result->b_size =3D 1 << inode->i_blkbits; + iomap->type =3D IOMAP_MAPPED; + iomap->addr =3D phys << FTRFS_BLOCK_SHIFT; return 0; } =20 - if (!create) - return -EIO; + /* Hole on read =E2=80=94 do not allocate */ + if (!(flags & IOMAP_WRITE)) { + iomap->type =3D IOMAP_HOLE; + iomap->addr =3D IOMAP_NULL_ADDR; + return 0; + } =20 - new_block =3D ftrfs_alloc_block(inode->i_sb); + /* Allocate a new block for write */ + new_block =3D ftrfs_alloc_block(sb); if (!new_block) { - pr_err("ftrfs: no free blocks\n"); + pr_err("ftrfs: iomap: no free blocks\n"); return -ENOSPC; } =20 fi->i_direct[iblock] =3D cpu_to_le64(new_block); - map_bh(bh_result, inode->i_sb, new_block); - bh_result->b_size =3D 1 << inode->i_blkbits; - set_buffer_new(bh_result); + mark_inode_dirty(inode); + + iomap->type =3D IOMAP_MAPPED; + iomap->addr =3D new_block << FTRFS_BLOCK_SHIFT; return 0; } =20 -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; } =20 -static int ftrfs_writepages(struct address_space *mapping, - struct writeback_control *wbc) +const struct iomap_ops ftrfs_iomap_ops =3D { + .iomap_begin =3D ftrfs_iomap_begin, + .iomap_end =3D ftrfs_iomap_end, +}; + +/* + * Write path =E2=80=94 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); } =20 -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); } =20 -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 =3D { + .get_folio =3D ftrfs_iomap_get_folio, + .put_folio =3D 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); } =20 -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 =E2=80=94 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 >=3D wpc->iomap.offset + wpc->iomap.length) { + int ret; + + memset(&wpc->iomap, 0, sizeof(wpc->iomap)); + ret =3D 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 =3D { + .writeback_range =3D ftrfs_writeback_range, + .writeback_submit =3D iomap_ioend_writeback_submit, +}; + +static int ftrfs_writepages(struct address_space *mapping, + struct writeback_control *wbc) +{ + struct iomap_writepage_ctx wpc =3D { + .inode =3D mapping->host, + .wbc =3D wbc, + .ops =3D &ftrfs_writeback_ops, + }; + + return iomap_writepages(&wpc); +} + +/* + * Read path =E2=80=94 uses iomap_bio_read_ops (kernel-provided) + */ +static int ftrfs_read_folio(struct file *file, struct folio *folio) +{ + struct iomap_read_folio_ctx ctx =3D { + .ops =3D &iomap_bio_read_ops, + .cur_folio =3D 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 =3D { + .ops =3D &iomap_bio_read_ops, + .rac =3D rac, + }; + + iomap_readahead(&ftrfs_iomap_ops, &ctx, NULL); } =20 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); } =20 const struct address_space_operations ftrfs_aops =3D { .read_folio =3D ftrfs_read_folio, .readahead =3D ftrfs_readahead, - .write_begin =3D ftrfs_write_begin, - .write_end =3D ftrfs_write_end, .writepages =3D ftrfs_writepages, .bmap =3D ftrfs_bmap, - .dirty_folio =3D block_dirty_folio, - .invalidate_folio =3D block_invalidate_folio, + .dirty_folio =3D iomap_dirty_folio, + .invalidate_folio =3D iomap_invalidate_folio, + .release_folio =3D 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 =20 +/* + * Radiation Event Journal entry =E2=80=94 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 jour= nal */ + /* * On-disk superblock =E2=80=94 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; =20 /* @@ -142,6 +159,7 @@ static inline struct ftrfs_sb_info *FTRFS_SB(struct sup= er_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= ); =20 /* 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 wri= teback_control *wbc) /* dir inode_operations =E2=80=94 exported = */ /* ------------------------------------------------------------------ */ =20 +/* ------------------------------------------------------------------ */ +/* rename =E2=80=94 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 =3D d_inode(old_dentry); + struct inode *new_inode =3D d_inode(new_dentry); + int is_dir =3D 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 =3D=3D 2: . and ..) + */ + if (new_inode) { + if (is_dir) { + if (new_inode->i_nlink > 2) + return -ENOTEMPTY; + } + + ret =3D 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 =3D 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 =3D ftrfs_del_dirent(old_dir, &old_dentry->d_name); + if (ret) { + pr_err("ftrfs: rename: del_dirent failed after add, fs may be inconsiste= nt\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 !=3D new_dir) { + struct qstr dotdot =3D QSTR_INIT("..", 2); + + ret =3D ftrfs_del_dirent(old_inode, &dotdot); + if (ret) + return ret; + + ret =3D 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 =3D 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 =3D ftrfs_write_inode_raw(old_inode); + if (ret) + return ret; + + ret =3D ftrfs_write_inode_raw(old_dir); + if (ret) + return ret; + + if (old_dir !=3D new_dir) { + ret =3D 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 =3D { .lookup =3D ftrfs_lookup, .create =3D ftrfs_create, @@ -432,4 +544,5 @@ const struct inode_operations ftrfs_dir_inode_operation= s =3D { .unlink =3D ftrfs_unlink, .rmdir =3D ftrfs_rmdir, .link =3D ftrfs_link, + .rename =3D 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 =3D= { .statfs =3D ftrfs_statfs, }; =20 +/* + * 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 =3D 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 =3D (struct ftrfs_super_block *)sbi->s_sbh->b_data; + head =3D fsb->s_rs_journal_head % FTRFS_RS_JOURNAL_SIZE; + ev =3D &fsb->s_rs_journal[head]; + + ev->re_block_no =3D cpu_to_le64(block_no); + ev->re_timestamp =3D cpu_to_le64(ktime_get_ns()); + ev->re_error_bits =3D cpu_to_le32(err_bits); + ev->re_crc32 =3D cpu_to_le32( + ftrfs_crc32(ev, offsetof(struct ftrfs_rs_event, re_crc32))); + + fsb->s_rs_journal_head =3D (head + 1) % FTRFS_RS_JOURNAL_SIZE; + + mark_buffer_dirty(sbi->s_sbh); + + spin_unlock(&sbi->s_lock); + + pr_debug("ftrfs: RS correction block=3D%llu symbols=3D%u\n", + block_no, err_bits); +} + /* * ftrfs_fill_super =E2=80=94 read superblock from disk and initialize VFS= sb */ --=20 2.52.0