From nobody Wed Feb 11 04:22:30 2026 Received: from mail-pl1-f176.google.com (mail-pl1-f176.google.com [209.85.214.176]) (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 E2B1516D4D4; Tue, 14 May 2024 13:18:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715692692; cv=none; b=Bfprar03YFel2j8RDFwOG1DcQdzXYGxTvvlWhqFh/0vBqMgsDG7iyQLZSEYvtWcJYkUZfHLV2lPPgWYgF870zLQxALaDqMZ4kGXK20BJNJ7u7Cpw3dct6UoXJ7rZEfRe968s2SyfcjIhCKOytkJ3oiTKu2hIK0LY0cwroV1K8zA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715692692; c=relaxed/simple; bh=p41Jp5EYVfGBDeVAOVl/zsa3dPK/Cb6MVZmuxA54sNc=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=eA/pR8tnEjctvuHaS2woMLWw0qSCWZ49a38YRvAKSnVZmkSBmoOVFHld6sVWU45DnCYHCEpbdwUJ9sNTpAMG2x34WdjNWsdcT+iZI9DPHQfa4Ry905/bpI3Sv7LEOYU/lrHbqKIy8PnLMa+p4twoUrF9Ohw7QvHqKwBLgbomeJ8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=PZDBNAZG; arc=none smtp.client-ip=209.85.214.176 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="PZDBNAZG" Received: by mail-pl1-f176.google.com with SMTP id d9443c01a7336-1ec69e3dbcfso43600605ad.0; Tue, 14 May 2024 06:18:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1715692689; x=1716297489; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Ac/6w8eF1/WlkpgOl3mXtqTlZQH+FEfp7jqPuIxAGeU=; b=PZDBNAZGGYe4fv1q4qayXccZK8OtiLu4EPRGjN7Ri7eCf6r/hIpUVBykMqz3dX8av0 Nvkp9B3HSaLqOo2Kzqt7CgSihHZHQDtpq1V56/+U9doiMw5DUW8Ij3uRsix+HYk3fMjd 8cOPEnWTOfi95HKrArNz+rHBaz6/gwD3v2l3WkSQyUpd7tnEMfN++QaVJNkNuvds5Rce zZ+kmaIQF8LiWE1L7kpqzlHpNaQWMvKivfcBLMzi0JZxjLXw2UZx3qUvhME6V2JMhzrU Ihku4vTMeXH9I7sD1qVMphHHYsVTgOCqtEVt17RB5voNXul3LRMpPkcwp4vUXiJxDdcT +DEw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1715692689; x=1716297489; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Ac/6w8eF1/WlkpgOl3mXtqTlZQH+FEfp7jqPuIxAGeU=; b=wYpghHm7pBNV7PUdjL3GnutAZvCzBWJ//os24e5ChTrW4HHRVsw2xXEDFQLKPyjTZ8 YodQtx9OKFMSX90lVAjUmkG11RIKXkxnxmIa7DnHoIAlNCmSy8r9I/sdNpia9VNGGXR1 AWc2ZTc12jPl8xLIlj0j0yxE65VIKWJfweBbr3DWGNGVbmJ56BcRDB8N0EyQK+7SQ6rZ VLcdIgqKMmprn5TY3p1iir1X1KSRw7+ENbXsTmWXNxvuT+ep7X+MBTQvvRSktR3GN/nY 2xz9HI/FNQanTbwgvBZOpuhoWPtMlgFpwT6AeC7GtfZnAjoQQZ1XBKDHNDfnAv4Cjb9C Vhyw== X-Forwarded-Encrypted: i=1; AJvYcCVVZgyC+QWAZT19OhI36ak2wyJJMosR262JhldzT4YsaeuhAMgoUCtF6eihMcSuga5iBj+pgXVy1XCspRrq5nu1x7Haz6TkRCl2XQaC2Wg0fmPaLw36Wa90KtC0YcDjuCSBqkMvMG0ti+V/03lrHvIaQ9Rs5esCmeG0/0XddLaFtaM4DNrc1fXubGJN X-Gm-Message-State: AOJu0YwDgud/rABgQ8AsBgPCkEkW0b/q6x4oQCdF/5wtKnKidmYvwkBN GrnfBILDkXEirceNjwSUyoWJVHo02sEYthoS637FB6o8kWZkm2I7 X-Google-Smtp-Source: AGHT+IFUEWOJAbO6+4ufK9Zg10BKKcqWXq5o8D/uKQjVC20XowtdZrymelxo6YOYB03vsfJ+FIXc0A== X-Received: by 2002:a17:903:234e:b0:1e3:cdd1:dd80 with SMTP id d9443c01a7336-1ef43c0cba2mr162831555ad.6.1715692688780; Tue, 14 May 2024 06:18:08 -0700 (PDT) Received: from wedsonaf-dev.. ([50.204.89.32]) by smtp.googlemail.com with ESMTPSA id d9443c01a7336-1ef0b9d18a4sm97277335ad.56.2024.05.14.06.18.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 May 2024 06:18:08 -0700 (PDT) From: Wedson Almeida Filho To: Alexander Viro , Christian Brauner , Matthew Wilcox , Dave Chinner Cc: Kent Overstreet , Greg Kroah-Hartman , linux-fsdevel@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, Wedson Almeida Filho Subject: [RFC PATCH v2 30/30] WIP: fs: ext2: add rust ro ext2 implementation Date: Tue, 14 May 2024 10:17:11 -0300 Message-Id: <20240514131711.379322-31-wedsonaf@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240514131711.379322-1-wedsonaf@gmail.com> References: <20240514131711.379322-1-wedsonaf@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Wedson Almeida Filho Signed-off-by: Wedson Almeida Filho --- fs/Kconfig | 1 + fs/Makefile | 1 + fs/rust-ext2/Kconfig | 13 + fs/rust-ext2/Makefile | 8 + fs/rust-ext2/defs.rs | 173 +++++++++++++ fs/rust-ext2/ext2.rs | 551 ++++++++++++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 3 + 7 files changed, 750 insertions(+) create mode 100644 fs/rust-ext2/Kconfig create mode 100644 fs/rust-ext2/Makefile create mode 100644 fs/rust-ext2/defs.rs create mode 100644 fs/rust-ext2/ext2.rs diff --git a/fs/Kconfig b/fs/Kconfig index 2cbd99d6784c..cf0cac5c5b1e 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -338,6 +338,7 @@ source "fs/ufs/Kconfig" source "fs/erofs/Kconfig" source "fs/vboxsf/Kconfig" source "fs/tarfs/Kconfig" +source "fs/rust-ext2/Kconfig" =20 endif # MISC_FILESYSTEMS =20 diff --git a/fs/Makefile b/fs/Makefile index d8bbda73e3a9..c1a3007efc7d 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -130,3 +130,4 @@ obj-$(CONFIG_EROFS_FS) +=3D erofs/ obj-$(CONFIG_VBOXSF_FS) +=3D vboxsf/ obj-$(CONFIG_ZONEFS_FS) +=3D zonefs/ obj-$(CONFIG_TARFS_FS) +=3D tarfs/ +obj-$(CONFIG_RUST_EXT2_FS) +=3D rust-ext2/ diff --git a/fs/rust-ext2/Kconfig b/fs/rust-ext2/Kconfig new file mode 100644 index 000000000000..976371655ca6 --- /dev/null +++ b/fs/rust-ext2/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only +# + +config RUST_EXT2_FS + tristate "Rust second extended fs support" + depends on RUST && BLOCK + help + Ext2 is a standard Linux file system for hard disks. + + To compile this file system support as a module, choose M here: the + module will be called rust_ext2. + + If unsure, say Y. diff --git a/fs/rust-ext2/Makefile b/fs/rust-ext2/Makefile new file mode 100644 index 000000000000..ac960b5f89d7 --- /dev/null +++ b/fs/rust-ext2/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the linux tarfs filesystem routines. +# + +obj-$(CONFIG_RUST_EXT2_FS) +=3D rust_ext2.o + +rust_ext2-y :=3D ext2.o diff --git a/fs/rust-ext2/defs.rs b/fs/rust-ext2/defs.rs new file mode 100644 index 000000000000..5f84852b4961 --- /dev/null +++ b/fs/rust-ext2/defs.rs @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Definitions of tarfs structures. + +use kernel::types::LE; + +pub(crate) const EXT2_SUPER_MAGIC: u16 =3D 0xEF53; + +pub(crate) const EXT2_MAX_BLOCK_LOG_SIZE: u32 =3D 16; + +pub(crate) const EXT2_GOOD_OLD_REV: u32 =3D 0; /* The good old (original) = format */ +pub(crate) const EXT2_DYNAMIC_REV: u32 =3D 1; /* V2 format w/ dynamic inod= e sizes */ + +pub(crate) const EXT2_GOOD_OLD_INODE_SIZE: u16 =3D 128; + +pub(crate) const EXT2_ROOT_INO: u32 =3D 2; /* Root inode */ + +/* First non-reserved inode for old ext2 filesystems. */ +pub(crate) const EXT2_GOOD_OLD_FIRST_INO: u32 =3D 11; + +pub(crate) const EXT2_FEATURE_INCOMPAT_FILETYPE: u32 =3D 0x0002; + +/* + * Constants relative to the data blocks + */ +pub(crate) const EXT2_NDIR_BLOCKS: usize =3D 12; +pub(crate) const EXT2_IND_BLOCK: usize =3D EXT2_NDIR_BLOCKS; +pub(crate) const EXT2_DIND_BLOCK: usize =3D EXT2_IND_BLOCK + 1; +pub(crate) const EXT2_TIND_BLOCK: usize =3D EXT2_DIND_BLOCK + 1; +pub(crate) const EXT2_N_BLOCKS: usize =3D EXT2_TIND_BLOCK + 1; + +kernel::derive_readable_from_bytes! { + #[repr(C)] + pub(crate) struct Super { + pub(crate) inodes_count: LE, + pub(crate) blocks_count: LE, + pub(crate) r_blocks_count: LE, + pub(crate) free_blocks_count: LE, /* Free blocks count */ + pub(crate) free_inodes_count: LE, /* Free inodes count */ + pub(crate) first_data_block: LE, /* First Data Block */ + pub(crate) log_block_size: LE, /* Block size */ + pub(crate) log_frag_size: LE, /* Fragment size */ + pub(crate) blocks_per_group: LE, /* # Blocks per group */ + pub(crate) frags_per_group: LE, /* # Fragments per group */ + pub(crate) inodes_per_group: LE, /* # Inodes per group */ + pub(crate) mtime: LE, /* Mount time */ + pub(crate) wtime: LE, /* Write time */ + pub(crate) mnt_count: LE, /* Mount count */ + pub(crate) max_mnt_count: LE, /* Maximal mount count */ + pub(crate) magic: LE, /* Magic signature */ + pub(crate) state: LE, /* File system state */ + pub(crate) errors: LE, /* Behaviour when detecting= errors */ + pub(crate) minor_rev_level: LE, /* minor revision level */ + pub(crate) lastcheck: LE, /* time of last check */ + pub(crate) checkinterval: LE, /* max. time between checks= */ + pub(crate) creator_os: LE, /* OS */ + pub(crate) rev_level: LE, /* Revision level */ + pub(crate) def_resuid: LE, /* Default uid for reserved= blocks */ + pub(crate) def_resgid: LE, /* Default gid for reserved= blocks */ + /* + * These fields are for EXT2_DYNAMIC_REV superblocks only. + * + * Note: the difference between the compatible feature set and + * the incompatible feature set is that if there is a bit set + * in the incompatible feature set that the kernel doesn't + * know about, it should refuse to mount the filesystem. + * + * e2fsck's requirements are more strict; if it doesn't know + * about a feature in either the compatible or incompatible + * feature set, it must abort and not try to meddle with + * things it doesn't understand... + */ + pub(crate) first_ino: LE, /* First non-reserved = inode */ + pub(crate) inode_size: LE, /* size of inode struc= ture */ + pub(crate) block_group_nr: LE, /* block group # of th= is superblock */ + pub(crate) feature_compat: LE, /* compatible feature = set */ + pub(crate) feature_incompat: LE, /* incompatible featur= e set */ + pub(crate) feature_ro_compat: LE, /* readonly-compatible= feature set */ + pub(crate) uuid: [u8; 16], /* 128-bit uuid for vo= lume */ + pub(crate) volume_name: [u8; 16], /* volume name */ + pub(crate) last_mounted: [u8; 64], /* directory where las= t mounted */ + pub(crate) algorithm_usage_bitmap: LE, /* For compression */ + /* + * Performance hints. Directory preallocation should only + * happen if the EXT2_COMPAT_PREALLOC flag is on. + */ + pub(crate) prealloc_blocks: u8, /* Nr of blocks to try to preal= locate*/ + pub(crate) prealloc_dir_blocks: u8, /* Nr to preallocate fo= r dirs */ + padding1: u16, + /* + * Journaling support valid if EXT3_FEATURE_COMPAT_HAS_JOURNAL set. + */ + pub(crate) journal_uuid: [u8; 16], /* uuid of journal superbl= ock */ + pub(crate) journal_inum: u32, /* inode number of journal= file */ + pub(crate) journal_dev: u32, /* device number of journa= l file */ + pub(crate) last_orphan: u32, /* start of list of inodes= to delete */ + pub(crate) hash_seed: [u32; 4], /* HTREE hash seed */ + pub(crate) def_hash_version: u8, /* Default hash version to= use */ + pub(crate) reserved_char_pad: u8, + pub(crate) reserved_word_pad: u16, + pub(crate) default_mount_opts: LE, + pub(crate) first_meta_bg: LE, /* First metablock block g= roup */ + reserved: [u32; 190], /* Padding to the end of t= he block */ + } + + #[repr(C)] + #[derive(Clone, Copy)] + pub(crate) struct Group { + /// Blocks bitmap block. + pub block_bitmap: LE, + + /// Inodes bitmap block. + pub inode_bitmap: LE, + + /// Inodes table block. + pub inode_table: LE, + + /// Number of free blocks. + pub free_blocks_count: LE, + + /// Number of free inodes. + pub free_inodes_count: LE, + + /// Number of directories. + pub used_dirs_count: LE, + + pad: LE, + reserved: [u32; 3], + } + + #[repr(C)] + pub(crate) struct INode { + pub mode: LE, /* File mode */ + pub uid: LE, /* Low 16 bits of Owner Uid */ + pub size: LE, /* Size in bytes */ + pub atime: LE, /* Access time */ + pub ctime: LE, /* Creation time */ + pub mtime: LE, /* Modification time */ + pub dtime: LE, /* Deletion Time */ + pub gid: LE, /* Low 16 bits of Group Id */ + pub links_count: LE, /* Links count */ + pub blocks: LE, /* Blocks count */ + pub flags: LE, /* File flags */ + pub reserved1: LE, + pub block: [LE; EXT2_N_BLOCKS],/* Pointers to blocks */ + pub generation: LE, /* File version (for NFS) */ + pub file_acl: LE, /* File ACL */ + pub dir_acl: LE, /* Directory ACL */ + pub faddr: LE, /* Fragment address */ + pub frag: u8, /* Fragment number */ + pub fsize: u8, /* Fragment size */ + pub pad1: LE, + pub uid_high: LE, + pub gid_high: LE, + pub reserved2: LE, + } + + #[repr(C)] + pub(crate) struct DirEntry { + pub(crate) inode: LE, /* Inode number */ + pub(crate) rec_len: LE, /* Directory entry length */ + pub(crate) name_len: u8, /* Name length */ + pub(crate) file_type: u8, /* Only if the "filetype" feature= flag is set. */ + } +} + +pub(crate) const FT_REG_FILE: u8 =3D 1; +pub(crate) const FT_DIR: u8 =3D 2; +pub(crate) const FT_CHRDEV: u8 =3D 3; +pub(crate) const FT_BLKDEV: u8 =3D 4; +pub(crate) const FT_FIFO: u8 =3D 5; +pub(crate) const FT_SOCK: u8 =3D 6; +pub(crate) const FT_SYMLINK: u8 =3D 7; diff --git a/fs/rust-ext2/ext2.rs b/fs/rust-ext2/ext2.rs new file mode 100644 index 000000000000..2d6b1e7ca156 --- /dev/null +++ b/fs/rust-ext2/ext2.rs @@ -0,0 +1,551 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Ext2 file system. + +use alloc::vec::Vec; +use core::mem::size_of; +use defs::*; +use kernel::fs::{ + self, address_space, dentry, dentry::DEntry, file, file::File, inode, = inode::INode, iomap, sb, + sb::SuperBlock, Offset, +}; +use kernel::types::{ARef, Either, FromBytes, Locked, LE}; +use kernel::{block, c_str, prelude::*, str::CString, time::Timespec, user,= PAGE_SIZE}; + +pub mod defs; + +kernel::module_fs! { + type: Ext2Fs, + name: "ext2", + author: "Wedson Almeida Filho ", + description: "ext2 file system", + license: "GPL", +} + +const SB_OFFSET: Offset =3D 1024; + +struct INodeData { + data_blocks: [u32; defs::EXT2_N_BLOCKS], +} + +struct Ext2Fs { + mapper: inode::Mapper, + block_size: u32, + has_file_type: bool, + _block_size_bits: u32, + inodes_per_block: u32, + inodes_per_group: u32, + inode_count: u32, + inode_size: u16, + first_ino: u32, + group: Vec, +} + +impl Ext2Fs { + fn iget(sb: &SuperBlock, ino: u32) -> Result>> { + let s =3D sb.data(); + if (ino !=3D EXT2_ROOT_INO && ino < s.first_ino) || ino > s.inode_= count { + return Err(ENOENT); + } + let group =3D ((ino - 1) / s.inodes_per_group) as usize; + let offset =3D (ino - 1) % s.inodes_per_group; + + if group >=3D s.group.len() { + return Err(ENOENT); + } + + // Create an inode or find an existing (cached) one. + let mut inode =3D match sb.get_or_create_inode(ino.into())? { + Either::Left(existing) =3D> return Ok(existing), + Either::Right(new) =3D> new, + }; + + let inodes_block =3D Offset::from(s.group[group].inode_table.value= ()); + let inode_block =3D inodes_block + Offset::from(offset / s.inodes_= per_block); + let offset =3D (offset % s.inodes_per_block) as usize; + let b =3D sb + .data() + .mapper + .mapped_folio(inode_block * Offset::from(s.block_size))?; + let idata =3D defs::INode::from_bytes(&b, offset * s.inode_size as= usize).ok_or(EIO)?; + let mode =3D idata.mode.value(); + + if idata.links_count.value() =3D=3D 0 && (mode =3D=3D 0 || idata.d= time.value() !=3D 0) { + return Err(ESTALE); + } + + const DIR_FOPS: file::Ops =3D file::Ops::new::(); + const DIR_IOPS: inode::Ops =3D inode::Ops::new::(); + const FILE_AOPS: address_space::Ops =3D iomap::ro_aops::(); + + let mut size =3D idata.size.value().into(); + let typ =3D match mode & fs::mode::S_IFMT { + fs::mode::S_IFREG =3D> { + size |=3D Offset::from(idata.dir_acl.value()) + .checked_shl(32) + .ok_or(EUCLEAN)?; + inode + .set_aops(FILE_AOPS) + .set_fops(file::Ops::generic_ro_file()); + inode::Type::Reg + } + fs::mode::S_IFDIR =3D> { + inode + .set_iops(DIR_IOPS) + .set_fops(DIR_FOPS) + .set_aops(FILE_AOPS); + inode::Type::Dir + } + fs::mode::S_IFLNK =3D> { + if idata.blocks.value() =3D=3D 0 { + const OFFSET: usize =3D core::mem::offset_of!(defs::IN= ode, block); + let name =3D &b[offset * usize::from(s.inode_size) + O= FFSET..]; + let name_len =3D size as usize; + if name_len > name.len() || name_len =3D=3D 0 { + return Err(EIO); + } + inode.set_iops(inode::Ops::simple_symlink_inode()); + inode::Type::Lnk(Some(CString::try_from(&name[..name_l= en])?)) + } else { + inode + .set_aops(FILE_AOPS) + .set_iops(inode::Ops::page_symlink_inode()); + inode::Type::Lnk(None) + } + } + fs::mode::S_IFSOCK =3D> inode::Type::Sock, + fs::mode::S_IFIFO =3D> inode::Type::Fifo, + fs::mode::S_IFCHR =3D> { + let (major, minor) =3D decode_dev(&idata.block); + inode::Type::Chr(major, minor) + } + fs::mode::S_IFBLK =3D> { + let (major, minor) =3D decode_dev(&idata.block); + inode::Type::Blk(major, minor) + } + _ =3D> return Err(ENOENT), + }; + inode.init(inode::Params { + typ, + mode: mode & 0o777, + size, + blocks: idata.blocks.value().into(), + nlink: idata.links_count.value().into(), + uid: u32::from(idata.uid.value()) | u32::from(idata.uid_high.v= alue()) << 16, + gid: u32::from(idata.gid.value()) | u32::from(idata.gid_high.v= alue()) << 16, + ctime: Timespec::new(idata.ctime.value().into(), 0)?, + mtime: Timespec::new(idata.mtime.value().into(), 0)?, + atime: Timespec::new(idata.atime.value().into(), 0)?, + value: INodeData { + data_blocks: core::array::from_fn(|i| idata.block[i].value= ()), + }, + }) + } + + fn offsets<'a>(&self, mut block: u64, out: &'a mut [u32]) -> Option<&'= a [u32]> { + let ptrs =3D u64::from(self.block_size / size_of::() as u32); + let ptr_mask =3D ptrs - 1; + let ptr_bits =3D ptrs.trailing_zeros(); + + if block < EXT2_NDIR_BLOCKS as u64 { + out[0] =3D block as u32; + return Some(&out[..1]); + } + + block -=3D EXT2_NDIR_BLOCKS as u64; + if block < ptrs { + out[0] =3D EXT2_IND_BLOCK as u32; + out[1] =3D block as u32; + return Some(&out[..2]); + } + + block -=3D ptrs; + if block < (1 << (2 * ptr_bits)) { + out[0] =3D EXT2_DIND_BLOCK as u32; + out[1] =3D (block >> ptr_bits) as u32; + out[2] =3D (block & ptr_mask) as u32; + return Some(&out[..3]); + } + + block -=3D ptrs * ptrs; + if block < ptrs * ptrs * ptrs { + out[0] =3D EXT2_TIND_BLOCK as u32; + out[1] =3D (block >> (2 * ptr_bits)) as u32; + out[2] =3D ((block >> ptr_bits) & ptr_mask) as u32; + out[3] =3D (block & ptr_mask) as u32; + return Some(&out[..4]); + } + + None + } + + fn offset_to_block(inode: &INode, block: Offset) -> Result { + let s =3D inode.super_block().data(); + let mut indices =3D [0u32; 4]; + let boffsets =3D s.offsets(block as u64, &mut indices).ok_or(EIO)?; + let mut boffset =3D inode.data().data_blocks[boffsets[0] as usize]; + let mapper =3D &s.mapper; + for i in &boffsets[1..] { + let b =3D mapper.mapped_folio(Offset::from(boffset) * Offset::= from(s.block_size))?; + let table =3D LE::::from_bytes_to_slice(&b).ok_or(EIO)?; + boffset =3D table[*i as usize].value(); + } + Ok(boffset.into()) + } + + fn check_descriptors(s: &Super, groups: &[Group]) -> Result { + for (i, g) in groups.iter().enumerate() { + let first =3D i as u32 * s.blocks_per_group.value() + s.first_= data_block.value(); + let last =3D if i =3D=3D groups.len() - 1 { + s.blocks_count.value() + } else { + first + s.blocks_per_group.value() - 1 + }; + + if g.block_bitmap.value() < first || g.block_bitmap.value() > = last { + pr_err!( + "Block bitmap for group {i} no in group (block {})\n", + g.block_bitmap.value() + ); + return Err(EINVAL); + } + + if g.inode_bitmap.value() < first || g.inode_bitmap.value() > = last { + pr_err!( + "Inode bitmap for group {i} no in group (block {})\n", + g.inode_bitmap.value() + ); + return Err(EINVAL); + } + + if g.inode_table.value() < first || g.inode_table.value() > la= st { + pr_err!( + "Inode table for group {i} no in group (block {})\n", + g.inode_table.value() + ); + return Err(EINVAL); + } + } + Ok(()) + } +} + +impl fs::FileSystem for Ext2Fs { + type Data =3D Box; + type INodeData =3D INodeData; + const NAME: &'static CStr =3D c_str!("rust-ext2"); + const SUPER_TYPE: sb::Type =3D sb::Type::BlockDev; + + fn fill_super( + sb: &mut SuperBlock, + mapper: Option, + ) -> Result { + let Some(mapper) =3D mapper else { + return Err(EINVAL); + }; + + if sb.min_blocksize(PAGE_SIZE as i32) =3D=3D 0 { + pr_err!("Unable to set block size\n"); + return Err(EINVAL); + } + + // Map the super block and check the magic number. + let mapped =3D mapper.mapped_folio(SB_OFFSET)?; + let s =3D Super::from_bytes(&mapped, 0).ok_or(EIO)?; + + if s.magic.value() !=3D EXT2_SUPER_MAGIC { + return Err(EINVAL); + } + + // Check for unsupported flags. + let mut has_file_type =3D false; + if s.rev_level.value() >=3D EXT2_DYNAMIC_REV { + let features =3D s.feature_incompat.value(); + if features & !EXT2_FEATURE_INCOMPAT_FILETYPE !=3D 0 { + pr_err!("Unsupported incompatible feature: {:x}\n", featur= es); + return Err(EINVAL); + } + + has_file_type =3D features & EXT2_FEATURE_INCOMPAT_FILETYPE != =3D 0; + + let features =3D s.feature_ro_compat.value(); + if !sb.rdonly() && features !=3D 0 { + pr_err!("Unsupported rw incompatible feature: {:x}\n", fea= tures); + return Err(EINVAL); + } + } + + // Set the block size. + let block_size_bits =3D s.log_block_size.value(); + if block_size_bits > EXT2_MAX_BLOCK_LOG_SIZE - 10 { + pr_err!("Invalid log block size: {}\n", block_size_bits); + return Err(EINVAL); + } + + let block_size =3D 1024u32 << block_size_bits; + if sb.min_blocksize(block_size as i32) !=3D block_size as i32 { + pr_err!("Bad block size: {}\n", block_size); + return Err(ENXIO); + } + + // Get the first inode and the inode size. + let (inode_size, first_ino) =3D if s.rev_level.value() =3D=3D EXT2= _GOOD_OLD_REV { + (EXT2_GOOD_OLD_INODE_SIZE, EXT2_GOOD_OLD_FIRST_INO) + } else { + let size =3D s.inode_size.value(); + if size < EXT2_GOOD_OLD_INODE_SIZE + || !size.is_power_of_two() + || u32::from(size) > block_size + { + pr_err!("Unsupported inode size: {}\n", size); + return Err(EINVAL); + } + (size, s.first_ino.value()) + }; + + // Get the number of inodes per group and per block. + let inode_count =3D s.inodes_count.value(); + let inodes_per_group =3D s.inodes_per_group.value(); + let inodes_per_block =3D block_size / u32::from(inode_size); + if inodes_per_group =3D=3D 0 || inodes_per_block =3D=3D 0 { + return Err(EINVAL); + } + + if inodes_per_group > block_size * 8 || inodes_per_group < inodes_= per_block { + pr_err!("Bad inodes per group: {}\n", inodes_per_group); + return Err(EINVAL); + } + + // Check the size of the groups. + let itb_per_group =3D inodes_per_group / inodes_per_block; + let blocks_per_group =3D s.blocks_per_group.value(); + if blocks_per_group > block_size * 8 || blocks_per_group <=3D itb_= per_group + 3 { + pr_err!("Bad blocks per group: {}\n", blocks_per_group); + return Err(EINVAL); + } + + let blocks_count =3D s.blocks_count.value(); + if block::Sector::from(blocks_count) > sb.sector_count() >> (1 + b= lock_size_bits) { + pr_err!( + "Block count ({blocks_count}) exceeds size of device ({})\= n", + sb.sector_count() >> (1 + block_size_bits) + ); + return Err(EINVAL); + } + + let group_count =3D (blocks_count - s.first_data_block.value() - 1= ) / blocks_per_group + 1; + if group_count * inodes_per_group !=3D inode_count { + pr_err!( + "Unexpected inode count: {inode_count} vs {}", + group_count * inodes_per_group + ); + return Err(EINVAL); + } + + let mut groups =3D Vec::new(); + groups.reserve(group_count as usize, GFP_NOFS)?; + + let mut remain =3D group_count; + let mut offset =3D (SB_OFFSET / Offset::from(block_size) + 1) * Of= fset::from(block_size); + while remain > 0 { + let b =3D mapper.mapped_folio(offset)?; + for g in Group::from_bytes_to_slice(&b).ok_or(EIO)? { + groups.push(*g, GFP_NOFS)?; + remain -=3D 1; + if remain =3D=3D 0 { + break; + } + } + offset +=3D Offset::try_from(b.len())?; + } + + Self::check_descriptors(s, &groups)?; + + sb.set_magic(s.magic.value().into()); + drop(mapped); + Ok(Box::new( + Ext2Fs { + mapper, + block_size, + _block_size_bits: block_size_bits, + has_file_type, + inodes_per_group, + inodes_per_block, + inode_count, + inode_size, + first_ino, + group: groups, + }, + GFP_KERNEL, + )?) + } + + fn init_root(sb: &SuperBlock) -> Result> { + let inode =3D Self::iget(sb, EXT2_ROOT_INO)?; + dentry::Root::try_new(inode) + } +} + +fn rec_len(d: &DirEntry) -> u32 { + let len =3D d.rec_len.value(); + + if PAGE_SIZE >=3D 65536 && len =3D=3D u16::MAX { + 1u32 << 16 + } else { + len.into() + } +} + +#[vtable] +impl file::Operations for Ext2Fs { + type FileSystem =3D Self; + + fn seek(file: &File, offset: Offset, whence: file::Whence) -> Re= sult { + file::generic_seek(file, offset, whence) + } + + fn read(_: &File, _: &mut user::Writer, _: &mut Offset) -> Resul= t { + Err(EISDIR) + } + + fn read_dir( + _file: &File, + inode: &Locked<&INode, inode::ReadSem>, + emitter: &mut file::DirEmitter, + ) -> Result { + let has_file_type =3D inode.super_block().data().has_file_type; + + inode.for_each_page(emitter.pos(), Offset::MAX, |data| { + let mut offset =3D 0usize; + let mut acc: Offset =3D 0; + let limit =3D data.len().saturating_sub(size_of::()); + while offset < limit { + let dirent =3D DirEntry::from_bytes(data, offset).ok_or(EI= O)?; + offset +=3D size_of::(); + + let name_len =3D usize::from(dirent.name_len); + if data.len() - offset < name_len { + return Err(EIO); + } + + let name =3D &data[offset..][..name_len]; + let rec_len =3D rec_len(dirent); + offset =3D offset - size_of::() + rec_len as usi= ze; + if rec_len =3D=3D 0 || offset > data.len() { + return Err(EIO); + } + + acc +=3D Offset::from(rec_len); + let ino =3D dirent.inode.value(); + if ino =3D=3D 0 { + continue; + } + + let t =3D if !has_file_type { + file::DirEntryType::Unknown + } else { + match dirent.file_type { + FT_REG_FILE =3D> file::DirEntryType::Reg, + FT_DIR =3D> file::DirEntryType::Dir, + FT_SYMLINK =3D> file::DirEntryType::Lnk, + FT_CHRDEV =3D> file::DirEntryType::Chr, + FT_BLKDEV =3D> file::DirEntryType::Blk, + FT_FIFO =3D> file::DirEntryType::Fifo, + FT_SOCK =3D> file::DirEntryType::Sock, + _ =3D> continue, + } + }; + + if !emitter.emit(acc, name, ino.into(), t) { + return Ok(Some(())); + } + acc =3D 0; + } + Ok(None) + })?; + Ok(()) + } +} + +#[vtable] +impl inode::Operations for Ext2Fs { + type FileSystem =3D Self; + + fn lookup( + parent: &Locked<&INode, inode::ReadSem>, + dentry: dentry::Unhashed<'_, Self>, + ) -> Result>>> { + let inode =3D parent.for_each_page(0, Offset::MAX, |data| { + let mut offset =3D 0usize; + while data.len() - offset > size_of::() { + let dirent =3D DirEntry::from_bytes(data, offset).ok_or(EI= O)?; + offset +=3D size_of::(); + + let name_len =3D usize::from(dirent.name_len); + if data.len() - offset < name_len { + return Err(EIO); + } + + let name =3D &data[offset..][..name_len]; + + offset =3D offset - size_of::() + usize::from(di= rent.rec_len.value()); + if offset > data.len() { + return Err(EIO); + } + + let ino =3D dirent.inode.value(); + if ino !=3D 0 && name =3D=3D dentry.name() { + return Ok(Some(Self::iget(parent.super_block(), ino)?)= ); + } + } + Ok(None) + })?; + + dentry.splice_alias(inode) + } +} + +impl iomap::Operations for Ext2Fs { + type FileSystem =3D Self; + + fn begin<'a>( + inode: &'a INode, + pos: Offset, + length: Offset, + _flags: u32, + map: &mut iomap::Map<'a>, + _srcmap: &mut iomap::Map<'a>, + ) -> Result { + let size =3D inode.size(); + if pos >=3D size { + map.set_offset(pos) + .set_length(length.try_into()?) + .set_flags(iomap::map_flags::MERGED) + .set_type(iomap::Type::Hole); + return Ok(()); + } + + let block_size =3D inode.super_block().data().block_size as Offset; + let block =3D pos / block_size; + + let boffset =3D Self::offset_to_block(inode, block)?; + map.set_offset(block * block_size) + .set_length(block_size as u64) + .set_flags(iomap::map_flags::MERGED) + .set_type(iomap::Type::Mapped) + .set_bdev(Some(inode.super_block().bdev())) + .set_addr(boffset * block_size as u64); + + Ok(()) + } +} + +fn decode_dev(block: &[LE]) -> (u32, u32) { + let v =3D block[0].value(); + if v !=3D 0 { + ((v >> 8) & 255, v & 255) + } else { + let v =3D block[1].value(); + ((v & 0xfff00) >> 8, (v & 0xff) | ((v >> 12) & 0xfff00)) + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 445599d4bff6..732bc9939f7f 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -165,3 +165,6 @@ macro_rules! container_of { ptr.wrapping_sub(offset) as *const $type }} } + +/// The size in bytes of a page of memory. +pub const PAGE_SIZE: usize =3D bindings::PAGE_SIZE; --=20 2.34.1