From f2b9297711d34a523f81288ac83e128a41141d58 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Mon, 1 Dec 2025 22:51:12 +0100 Subject: [PATCH 1/7] ext4: add initial ext4 support with 64-bit group descriptors and multi-extent reads This adds basic ext4 support to aboot so it can mount and read modern ext4 filesystems commonly found on Alpha systems. Key features: - 64-byte ext4 group descriptor support (using sb.s_desc_size) - Accept and whitelist common ext4 INCOMPAT/RO_COMPAT flags: 64bit, flex_bg, mmp, csum_seed, metadata_csum - Implement multi-extent reading for inline extents (eh_depth == 0) - Clamp reads to i_size to avoid out-of-range extent lookups With these changes aboot can successfully mount ext4 partitions and load kernels stored in files using multiple inline extents. Full extent-tree support (non-zero eh_depth) and full 64-bit block handling remain future work. Signed-off-by: Magnus Lindholm --- fs/ext2.c | 300 +++++++++++++++++++++++++++++++++++++++++-------- include/ext4.h | 62 ++++++++++ 2 files changed, 313 insertions(+), 49 deletions(-) diff --git a/fs/ext2.c b/fs/ext2.c index fc66fed..d125497 100644 --- a/fs/ext2.c +++ b/fs/ext2.c @@ -15,9 +15,37 @@ #include "ext4.h" #include "utils.h" #include - +#include #define MAX_OPEN_FILES 5 +/* + * What this build of aboot can safely cope with. + * + * Keep these masks conservative; you can relax them as you implement + * more features. + */ + +#ifndef EXT4_FEATURE_INCOMPAT_SUPP +#define EXT4_FEATURE_INCOMPAT_SUPP \ + (EXT4_FEATURE_INCOMPAT_FILETYPE | \ + EXT4_FEATURE_INCOMPAT_RECOVER | \ + EXT4_FEATURE_INCOMPAT_EXTENTS | \ + EXT4_FEATURE_INCOMPAT_64BIT | \ + EXT4_FEATURE_INCOMPAT_FLEX_BG | \ + EXT4_FEATURE_INCOMPAT_MMP | \ + EXT4_FEATURE_INCOMPAT_CSUM_SEED) +#endif + +#ifndef EXT4_FEATURE_RO_COMPAT_SUPP +#define EXT4_FEATURE_RO_COMPAT_SUPP \ + (EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER | \ + EXT4_FEATURE_RO_COMPAT_LARGE_FILE | \ + EXT4_FEATURE_RO_COMPAT_BTREE_DIR | \ + EXT4_FEATURE_RO_COMPAT_HUGE_FILE | \ + EXT4_FEATURE_RO_COMPAT_DIR_NLINK | \ + EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE | \ + EXT4_FEATURE_RO_COMPAT_METADATA_CSUM) +#endif extern struct bootfs ext2fs; static struct ext2_super_block sb; @@ -35,6 +63,7 @@ static int cached_diblkno = -1; static char *diblkbuf; static long dev = -1; static long partition_offset; +static unsigned int group_desc_size = 0; static struct inode_table_entry { struct ext2_inode inode; @@ -43,6 +72,77 @@ static struct inode_table_entry { unsigned short old_mode; } inode_table[MAX_OPEN_FILES]; +static int ext4_check_features(int quiet) +{ + /* sb is the global ext2_super_block already filled in by ext2_mount() */ +#ifdef DEBUG_EXT2 + uint32_t compat = sb.s_feature_compat; + printf("ext2/4: FS compat features: 0x%08x\n", compat); +#endif + uint32_t ro_compat = sb.s_feature_ro_compat; + uint32_t incompat = sb.s_feature_incompat; + uint32_t missing; + + /* First check incompatible features – these must all be understood */ + missing = incompat & ~EXT4_FEATURE_INCOMPAT_SUPP; + if (missing) { + if (!quiet) { + printf("ext2/4: unsupported INCOMPAT features: 0x%08x", missing); + + if (missing & EXT4_FEATURE_INCOMPAT_64BIT) + printf(" (64bit)"); + if (missing & EXT4_FEATURE_INCOMPAT_META_BG) + printf(" (meta_bg)"); + if (missing & EXT4_FEATURE_INCOMPAT_FLEX_BG) + printf(" (flex_bg)"); + if (missing & EXT4_FEATURE_INCOMPAT_MMP) + printf(" (mmp)"); + if (missing & EXT4_FEATURE_INCOMPAT_CSUM_SEED) + printf(" (csum_seed)"); + /* add more decodes here as you implement them */ + + printf("\n"); + printf(" Use an ext2/3/4 filesystem without these features\n"); + printf(" (for example: tune2fs -O ^64bit,^metadata_csum /dev/XXX\n"); + printf(" or create a small ext2/ext3 /boot partition).\n"); + } + return -1; + } + + /* + * Now check RO-compat features. + * In a read-only loader, unknown RO-compat features are theoretically + * safe, but some (e.g. metadata_csum) *do* affect on-disk layout, so + * we still reject unknown ones for now. + */ + missing = ro_compat & ~EXT4_FEATURE_RO_COMPAT_SUPP; + if (missing) { + if (!quiet) { + printf("ext2/4: unsupported RO_COMPAT features: 0x%08x", missing); + + if (missing & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM) + printf(" (metadata_csum)"); + if (missing & EXT4_FEATURE_RO_COMPAT_BIGALLOC) + printf(" (bigalloc)"); + if (missing & EXT4_FEATURE_RO_COMPAT_QUOTA) + printf(" (quota)"); + /* add more decodes here as needed */ + + printf("\n"); + printf(" This aboot build cannot read this ext4 layout safely.\n"); + } + return -1; + } + +#ifdef DEBUG_EXT2 + if (!quiet) { + printf("ext2/4: features OK: compat=0x%08x ro_compat=0x%08x " + "incompat=0x%08x\n", + compat, ro_compat, incompat); + } +#endif + return 0; +} /* * Initialize an ext2 partition starting at offset P_OFFSET; this is @@ -54,6 +154,7 @@ static int ext2_mount(long cons_dev, long p_offset, long quiet) { long sb_block = 1; long sb_offset; + long gdt_start; int i; dev = cons_dev; @@ -82,23 +183,66 @@ static int ext2_mount(long cons_dev, long p_offset, long quiet) } return -1; } + /* + * ext4: group descriptor size. For classic ext2 this is either + * zero or 32. For ext4 with 64bit it is typically 64. + * + * We only care about the first sizeof(struct ext2_group_desc) + * bytes, which are laid out compatibly with ext2. + */ + group_desc_size = sb.s_desc_size; + if (group_desc_size == 0) + group_desc_size = sizeof(struct ext2_group_desc); + + if (group_desc_size < sizeof(struct ext2_group_desc)) { + printf("ext2/4: group descriptor size %u too small\n", + group_desc_size); + return -1; + } + if (ext4_check_features(quiet) < 0) + return -1; ngroups = (sb.s_blocks_count - sb.s_first_data_block + EXT2_BLOCKS_PER_GROUP(&sb) - 1) / EXT2_BLOCKS_PER_GROUP(&sb); + ext2fs.blocksize = EXT2_BLOCK_SIZE(&sb); + if (group_desc_size > ext2fs.blocksize) { + printf("ext2/4: group descriptor size %u > blocksize %u\n", + group_desc_size, ext2fs.blocksize); + return -1; + } + gds = malloc((size_t)(ngroups * sizeof(struct ext2_group_desc))); + if (!gds) { + printf("ext2: no memory for group descriptors\n"); + return -1; + } - ext2fs.blocksize = EXT2_BLOCK_SIZE(&sb); blkbuf = malloc(ext2fs.blocksize); iblkbuf = malloc(ext2fs.blocksize); diblkbuf = malloc(ext2fs.blocksize); /* read in the group descriptors (immediately follows superblock) */ - cons_read(dev, gds, ngroups * sizeof(struct ext2_group_desc), - partition_offset + - ext2fs.blocksize * (EXT2_MIN_BLOCK_SIZE/ext2fs.blocksize + 1)); + + gdt_start = partition_offset + + ext2fs.blocksize * + (EXT2_MIN_BLOCK_SIZE / ext2fs.blocksize + 1); + + for (i = 0; i < ngroups; i++) { + long off = gdt_start + (long)i * group_desc_size; + size_t toread = sizeof(struct ext2_group_desc); + + if (toread > group_desc_size) + toread = group_desc_size; + + if (cons_read(dev, &gds[i], toread, off) != (long)toread) { + printf("ext2/4: group descriptor %d read failed\n", i); + return -1; + } + } + /* * Calculate direct/indirect block limits for this file system * (blocksize dependent): @@ -299,50 +443,108 @@ static int ext2_blkno(struct ext2_inode *ip, int blkoff) static int ext4_breadi(struct ext2_inode *ip, long blkno, long nblks, char *buffer) { - long tot_bytes = 0; - - struct ext4_extent_header *hdr; - hdr = (struct ext4_extent_header *)&ip->i_block[0]; - - if (hdr->eh_magic != EXT4_EXT_MAGIC) { - printf("ext4_breadi: Extent header magic wrong.\n"); - return -1; - } - - if (hdr->eh_entries != 1) { - printf("ext4_breadi: Extent entries must be 1.\n"); - return -1; - } - - struct ext4_extent *ext; - ext = (struct ext4_extent *)&ip->i_block[3]; - - if (ext->ee_block != 0) { - printf("ext4_breadi: First logical block must be 0.\n"); - return -1; - } - - if (nblks > ext->ee_len) { - printf("ext4_breadi: Request nblks greater than extent len.\n"); - return -1; - } - - long ee_start = ((long)ext->ee_start_hi << 32) + ext->ee_start_lo; - - if (blkno + nblks > ext->ee_len) { - nblks = ext->ee_len - blkno; - } - - long offset = partition_offset + (ee_start + blkno) * ext2fs.blocksize; - long nbytes = nblks * ext2fs.blocksize; - - tot_bytes = cons_read(dev, buffer, nbytes, offset); - if (tot_bytes != nbytes) { - printf("ext4_breadi: cons_read failed.\n"); - return -1; - } - - return tot_bytes; + struct ext4_extent_header *hdr; + struct ext4_extent *ext_base; + int entries; + long cur_blk = blkno; /* logical block we are at */ + long blocks_left = nblks; /* blocks still to read */ + char *bufp = buffer; + long tot_bytes = 0; + + hdr = (struct ext4_extent_header *)&ip->i_block[0]; + + if (hdr->eh_magic != EXT4_EXT_MAGIC) { + printf("ext4_breadi: Extent header magic wrong.\n"); + return -1; + } + + /* For now we only handle leaf extents stored inline in the inode. */ + if (hdr->eh_depth != 0) { + printf("ext4_breadi: Extent tree depth %d not supported.\n", + hdr->eh_depth); + return -1; + } + + entries = hdr->eh_entries; + if (entries <= 0) { + printf("ext4_breadi: No extents.\n"); + return -1; + } + + /* --- honour file size like ext2_breadi() does --- */ + if ((blkno + nblks) * ext2fs.blocksize > ip->i_size) { + long maxblk = (ip->i_size + ext2fs.blocksize) / ext2fs.blocksize; + nblks = maxblk - blkno; + if (nblks <= 0) + return 0; /* nothing to read */ + } + cur_blk = blkno; + blocks_left = nblks; + + + /* Extents start right after the header in i_block[] */ + ext_base = (struct ext4_extent *)((char *)hdr + sizeof(*hdr)); + + while (blocks_left > 0) { + struct ext4_extent *ext = NULL; + int i; + + /* Find the extent that covers cur_blk */ + for (i = 0; i < entries; i++) { + struct ext4_extent *e = &ext_base[i]; + uint32_t first = e->ee_block; + uint32_t len = e->ee_len; + + if (len == 0) + continue; + + if ((uint32_t)cur_blk >= first && + (uint32_t)cur_blk < first + len) { + ext = e; + break; + } + } + + if (!ext) { + printf("ext4_breadi: logical block %ld not in any extent.\n", + cur_blk); + return -1; + } + + { + uint32_t first = ext->ee_block; + uint32_t len = ext->ee_len; + long within = cur_blk - first; /* offset inside this extent */ + long can_read = len - within; /* blocks available here */ + + if (can_read > blocks_left) + can_read = blocks_left; + + /* Calculate physical start block of this chunk */ + long ee_start = + ((long)ext->ee_start_hi << 32) + ext->ee_start_lo; + long phys_blk = ee_start + within; + + long offset = partition_offset + + phys_blk * ext2fs.blocksize; + long nbytes = can_read * ext2fs.blocksize; + long got; + + got = cons_read(dev, bufp, nbytes, offset); + if (got != nbytes) { + printf("ext4_breadi: cons_read failed (wanted %ld, got %ld).\n", + nbytes, got); + return -1; + } + + bufp += nbytes; + tot_bytes += nbytes; + cur_blk += can_read; + blocks_left -= can_read; + } + } + + return tot_bytes; } static int ext2_breadi(struct ext2_inode *ip, long blkno, long nblks, char *buffer) diff --git a/include/ext4.h b/include/ext4.h index 440a14d..e6504be 100644 --- a/include/ext4.h +++ b/include/ext4.h @@ -19,6 +19,68 @@ #ifndef _EXT4_H_ #define _EXT4_H_ + +/* --- ext4 superblock feature flags we care about --- */ + +/* Incompatible features (s_feature_incompat) */ +#ifndef EXT4_FEATURE_INCOMPAT_FILETYPE +#define EXT4_FEATURE_INCOMPAT_FILETYPE 0x0002 +#endif +#ifndef EXT4_FEATURE_INCOMPAT_RECOVER +#define EXT4_FEATURE_INCOMPAT_RECOVER 0x0004 +#endif +#ifndef EXT4_FEATURE_INCOMPAT_META_BG +#define EXT4_FEATURE_INCOMPAT_META_BG 0x0010 +#endif +#ifndef EXT4_FEATURE_INCOMPAT_EXTENTS +#define EXT4_FEATURE_INCOMPAT_EXTENTS 0x0040 +#endif +#ifndef EXT4_FEATURE_INCOMPAT_64BIT +#define EXT4_FEATURE_INCOMPAT_64BIT 0x0080 +#endif +#ifndef EXT4_FEATURE_INCOMPAT_MMP +#define EXT4_FEATURE_INCOMPAT_MMP 0x0100 +#endif +#ifndef EXT4_FEATURE_INCOMPAT_FLEX_BG +#define EXT4_FEATURE_INCOMPAT_FLEX_BG 0x0200 +#endif + +/* RO-compat features (s_feature_ro_compat) */ +#ifndef EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER +#define EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001 +#endif +#ifndef EXT4_FEATURE_RO_COMPAT_LARGE_FILE +#define EXT4_FEATURE_RO_COMPAT_LARGE_FILE 0x0002 +#endif +#ifndef EXT4_FEATURE_RO_COMPAT_BTREE_DIR +#define EXT4_FEATURE_RO_COMPAT_BTREE_DIR 0x0004 +#endif +#ifndef EXT4_FEATURE_RO_COMPAT_HUGE_FILE +#define EXT4_FEATURE_RO_COMPAT_HUGE_FILE 0x0008 +#endif +#ifndef EXT4_FEATURE_RO_COMPAT_GDT_CSUM +#define EXT4_FEATURE_RO_COMPAT_GDT_CSUM 0x0010 +#endif +#ifndef EXT4_FEATURE_RO_COMPAT_DIR_NLINK +#define EXT4_FEATURE_RO_COMPAT_DIR_NLINK 0x0020 +#endif +#ifndef EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE +#define EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE 0x0040 +#endif +#ifndef EXT4_FEATURE_RO_COMPAT_BIGALLOC +#define EXT4_FEATURE_RO_COMPAT_BIGALLOC 0x0200 +#endif +#ifndef EXT4_FEATURE_RO_COMPAT_QUOTA +#define EXT4_FEATURE_RO_COMPAT_QUOTA 0x0100 +#endif +#ifndef EXT4_FEATURE_RO_COMPAT_METADATA_CSUM +#define EXT4_FEATURE_RO_COMPAT_METADATA_CSUM 0x0400 +#endif + +#ifndef EXT4_FEATURE_INCOMPAT_CSUM_SEED +#define EXT4_FEATURE_INCOMPAT_CSUM_SEED 0x2000 +#endif + /* * Inode flags */ From 2d0cf71610fa83cd78dcd040f9a9355bb68e9084 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Mon, 1 Dec 2025 23:15:45 +0100 Subject: [PATCH 2/7] Add support for XFS to aboot The following patch is a modified version of the patch written by j.j.vanderheijden@home.nl and allows aboot to handle XFS partitions. It has been tested against a Digital Personal Workstation and seems to work just fine. (patch modifications by Mike Hlavac) --- Makefile | 4 +- disk.c | 29 +- fs/xfs.c | 816 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/xfs.h | 536 ++++++++++++++++++++++++++++++++++++ 4 files changed, 1367 insertions(+), 18 deletions(-) create mode 100644 fs/xfs.c create mode 100644 fs/xfs.h diff --git a/Makefile b/Makefile index f24b5f2..3d0f357 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ VMLINUXGZ = $(KSRC)/arch/alpha/boot/vmlinux.gz #TESTING = yes # for boot testing -#CFGDEFS = -DDEBUG_ISO -DDEBUG_ROCK -DDEBUG_EXT2 -DDEBUG +#CFGDEFS = -DDEBUG_ISO -DDEBUG_ROCK -DDEBUG_EXT2 -DDEBUG_XFS -DDEBUG # root, aka prefix root = @@ -57,7 +57,7 @@ override ASFLAGS += $(CPPFLAGS) $(CC) $(ASFLAGS) -D__ASSEMBLY__ -c -o $*.o $< NET_OBJS = net.o -DISK_OBJS = disk.o fs/ext2.o fs/ufs.o fs/dummy.o fs/iso.o +DISK_OBJS = disk.o fs/ext2.o fs/ufs.o fs/dummy.o fs/iso.o fs/xfs.o ifeq ($(TESTING),) ABOOT_OBJS = \ head.o aboot.o cons.o utils.o \ diff --git a/disk.c b/disk.c index 3cc451a..f86eb28 100644 --- a/disk.c +++ b/disk.c @@ -40,6 +40,7 @@ extern struct bootfs ext2fs; extern struct bootfs iso; extern struct bootfs ufs; extern struct bootfs dummyfs; +extern struct bootfs xfsfs; struct disklabel * label; int boot_part = -1; @@ -47,7 +48,8 @@ int boot_part = -1; static const struct bootfs *bootfs[] = { &ext2fs, &iso, - &ufs + &ufs, + &xfsfs }; /* @@ -326,23 +328,18 @@ mount_fs (long dev, int partition) return 0; } part = &label->d_partitions[partition - 1]; - for (i = 0; bootfs[i]->fs_type != part->p_fstype; ++i) { - if (i + 1 - >= (int) (sizeof(bootfs)/sizeof(bootfs[0]))) - { - printf("aboot: don't know how to mount " - "partition %d (filesystem type %d)\n", - partition, part->p_fstype); - return 0; + for (i = 0; i < (int)(sizeof(bootfs)/sizeof(bootfs[0])); i++) { + if (bootfs[i]->fs_type == part->p_fstype) { + fs = bootfs[i]; + if (!((*fs->mount)(dev, (long)(part->p_offset) * (long)(label->d_secsize), 1) + < 0)) + return fs; } } - fs = bootfs[i]; - if ((*fs->mount)(dev, (long)(part->p_offset) * (long)(label->d_secsize), 0) - < 0) { - printf("aboot: mount of partition %d failed\n", - partition); - return 0; - } + printf("aboot: don't know how to mount " + "partition %d (filesystem type %d)\n", + partition, part->p_fstype); + return 0; } return fs; } diff --git a/fs/xfs.c b/fs/xfs.c new file mode 100644 index 0000000..d48254e --- /dev/null +++ b/fs/xfs.c @@ -0,0 +1,816 @@ +/* + * xfs.c - an implementation of the SGI XFS file system for aboot + * Jan-Jaap van der Heijden + * + * Based on fsys_xfs.c from GRUB -- GRand Unified Bootloader + * Copyright (C) 2001,2002,2004 Free Software Foundation, Inc. + */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include "config.h" +#include "aboot.h" +#include "bootfs.h" +#include "cons.h" +#include "disklabel.h" +#include "utils.h" +#include "xfs.h" + +static int xfs_mount(long cons_dev, long p_offset, long quiet); +static int xfs_bread(int fd, long blkno, long nblks, char *buffer); +static int xfs_open(const char *dirname); +static void xfs_close(int fd); +static const char *xfs_readdir(int fd, int rewind); +static int xfs_fstat(int fd, struct stat* buf); + +struct bootfs xfsfs = { + FS_EXT2, + 0, + xfs_mount, + xfs_open, + xfs_bread, + xfs_close, + xfs_readdir, + xfs_fstat +}; + +static long dev = -1; +static long partition_offset; +static long filepos; +static long filemax; /* filelen */ + +static long xfs_read (void *buf, long len); + +#define isspace(c) ((c) == 0x10) + +static int +devread(long sector, long start, long length, void *buf) +{ + long pos = sector * SECT_SIZE; + pos += partition_offset + start; +#ifdef DEBUG_XFS + printf("Reading %ld bytes, starting at sector %ld, disk offset %ld\n", + length, sector, pos); +#endif + return cons_read(dev, buf, length, pos); +} + +#define MAXNAMELEN 256 +#define SECTOR_BITS 9 +#define MAX_LINK_COUNT 8 + +typedef struct xad { + xfs_fileoff_t offset; + xfs_fsblock_t start; + xfs_filblks_t len; +} xad_t; + +struct xfs_info { + int bsize; + int dirbsize; + int isize; + unsigned int agblocks; + int bdlog; + int blklog; + int inopblog; + int agblklog; + int agnolog; + unsigned int nextents; + xfs_daddr_t next; + xfs_daddr_t daddr; + xfs_dablk_t forw; + xfs_dablk_t dablk; + xfs_bmbt_rec_32_t *xt; + xfs_bmbt_ptr_t ptr0; + int btnode_ptr0_off; + int i8param; + int dirpos; + int dirmax; + int blkoff; + int fpos; + xfs_ino_t rootino; + xfs_ino_t new_ino; +}; + +static struct xfs_info xfs; + +#ifdef __alpha__ /* take care of alignment*/ + static long FSYS_BUF[32768/sizeof(long)]; + #define dirbuf ((long *)FSYS_BUF) + #define filebuf ((long *)FSYS_BUF + 4096/sizeof(long)) + #define inode ((xfs_dinode_t *)((long *)FSYS_BUF + 8192/sizeof(long))) +#else + static char FSYS_BUF[32768]; + #define dirbuf ((char *)FSYS_BUF) + #define filebuf ((char *)FSYS_BUF + 4096) + #define inode ((xfs_dinode_t *)((char *)FSYS_BUF + 8192)) +#endif + +#define icore (inode->di_core) +#define mask32lo(n) (((uint32_t)1 << (n)) - 1) + +#define XFS_INO_MASK(k) ((uint32_t)((1ULL << (k)) - 1)) +#define XFS_INO_OFFSET_BITS xfs.inopblog +#define XFS_INO_AGBNO_BITS xfs.agblklog +#define XFS_INO_AGINO_BITS (xfs.agblklog + xfs.inopblog) +#define XFS_INO_AGNO_BITS xfs.agnolog + +static inline xfs_agblock_t +agino2agbno (xfs_agino_t agino) +{ + return agino >> XFS_INO_OFFSET_BITS; +} + +static inline xfs_agnumber_t +ino2agno (xfs_ino_t ino) +{ + return ino >> XFS_INO_AGINO_BITS; +} + +static inline xfs_agino_t +ino2agino (xfs_ino_t ino) +{ + return ino & XFS_INO_MASK(XFS_INO_AGINO_BITS); +} + +static inline int +ino2offset (xfs_ino_t ino) +{ + return ino & XFS_INO_MASK(XFS_INO_OFFSET_BITS); +} + +/* XFS is big endian, alpha is little endian */ +#define le16(x) __swab16(x) +#define le32(x) __swab32(x) +#define le64(x) __swab64(x) + +static xfs_fsblock_t +xt_start (xfs_bmbt_rec_32_t *r) +{ + return (((xfs_fsblock_t)(le32 (r->l1) & mask32lo(9))) << 43) | + (((xfs_fsblock_t)le32 (r->l2)) << 11) | + (((xfs_fsblock_t)le32 (r->l3)) >> 21); +} + +static xfs_fileoff_t +xt_offset (xfs_bmbt_rec_32_t *r) +{ + return (((xfs_fileoff_t)le32 (r->l0) & + mask32lo(31)) << 23) | + (((xfs_fileoff_t)le32 (r->l1)) >> 9); +} + +static xfs_filblks_t +xt_len (xfs_bmbt_rec_32_t *r) +{ + return le32(r->l3) & mask32lo(21); +} + +static const char xfs_highbit[256] = { + -1, 0, 1, 1, 2, 2, 2, 2, /* 00 .. 07 */ + 3, 3, 3, 3, 3, 3, 3, 3, /* 08 .. 0f */ + 4, 4, 4, 4, 4, 4, 4, 4, /* 10 .. 17 */ + 4, 4, 4, 4, 4, 4, 4, 4, /* 18 .. 1f */ + 5, 5, 5, 5, 5, 5, 5, 5, /* 20 .. 27 */ + 5, 5, 5, 5, 5, 5, 5, 5, /* 28 .. 2f */ + 5, 5, 5, 5, 5, 5, 5, 5, /* 30 .. 37 */ + 5, 5, 5, 5, 5, 5, 5, 5, /* 38 .. 3f */ + 6, 6, 6, 6, 6, 6, 6, 6, /* 40 .. 47 */ + 6, 6, 6, 6, 6, 6, 6, 6, /* 48 .. 4f */ + 6, 6, 6, 6, 6, 6, 6, 6, /* 50 .. 57 */ + 6, 6, 6, 6, 6, 6, 6, 6, /* 58 .. 5f */ + 6, 6, 6, 6, 6, 6, 6, 6, /* 60 .. 67 */ + 6, 6, 6, 6, 6, 6, 6, 6, /* 68 .. 6f */ + 6, 6, 6, 6, 6, 6, 6, 6, /* 70 .. 77 */ + 6, 6, 6, 6, 6, 6, 6, 6, /* 78 .. 7f */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 80 .. 87 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 88 .. 8f */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 90 .. 97 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 98 .. 9f */ + 7, 7, 7, 7, 7, 7, 7, 7, /* a0 .. a7 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* a8 .. af */ + 7, 7, 7, 7, 7, 7, 7, 7, /* b0 .. b7 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* b8 .. bf */ + 7, 7, 7, 7, 7, 7, 7, 7, /* c0 .. c7 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* c8 .. cf */ + 7, 7, 7, 7, 7, 7, 7, 7, /* d0 .. d7 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* d8 .. df */ + 7, 7, 7, 7, 7, 7, 7, 7, /* e0 .. e7 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* e8 .. ef */ + 7, 7, 7, 7, 7, 7, 7, 7, /* f0 .. f7 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* f8 .. ff */ +}; + +static int +xfs_highbit32(uint32_t v) +{ + int i; + + if (v & 0xffff0000) + if (v & 0xff000000) + i = 24; + else + i = 16; + else if (v & 0x0000ffff) + if (v & 0x0000ff00) + i = 8; + else + i = 0; + else + return -1; + return i + xfs_highbit[(v >> i) & 0xff]; +} + +static int +isinxt (xfs_fileoff_t key, xfs_fileoff_t offset, xfs_filblks_t len) +{ + return (key >= offset) ? (key < offset + len ? 1 : 0) : 0; +} + +static xfs_daddr_t +agb2daddr (xfs_agnumber_t agno, xfs_agblock_t agbno) +{ + return ((xfs_fsblock_t)agno*xfs.agblocks + agbno) << xfs.bdlog; +} + +static xfs_daddr_t +fsb2daddr (xfs_fsblock_t fsbno) +{ + return agb2daddr ((xfs_agnumber_t)(fsbno >> xfs.agblklog), + (xfs_agblock_t)(fsbno & mask32lo(xfs.agblklog))); +} + +#undef offsetof +#define offsetof(t,m) ((int)&(((t *)0)->m)) + +static inline int +btroot_maxrecs (void) +{ + int tmp = icore.di_forkoff ? (icore.di_forkoff << 3) : xfs.isize; + + return (tmp - sizeof(xfs_bmdr_block_t) - offsetof(xfs_dinode_t, di_u)) / + (sizeof (xfs_bmbt_key_t) + sizeof (xfs_bmbt_ptr_t)); +} + +static int +di_read (xfs_ino_t ino) +{ + xfs_agino_t agino; + xfs_agnumber_t agno; + xfs_agblock_t agbno; + xfs_daddr_t daddr; + int offset; + + agno = ino2agno (ino); + agino = ino2agino (ino); + agbno = agino2agbno (agino); + offset = ino2offset (ino); + daddr = agb2daddr (agno, agbno); + + devread (daddr, offset*xfs.isize, xfs.isize, (char *)inode); + + xfs.ptr0 = *(xfs_bmbt_ptr_t *) + (inode->di_u.di_c + sizeof(xfs_bmdr_block_t) + + btroot_maxrecs ()*sizeof(xfs_bmbt_key_t)); + + return 1; +} + +static void +init_extents (void) +{ + xfs_bmbt_ptr_t ptr0; + xfs_btree_lblock_t h; + + switch (icore.di_format) { + case XFS_DINODE_FMT_EXTENTS: + xfs.xt = inode->di_u.di_bmx; + xfs.nextents = le32 (icore.di_nextents); + break; + case XFS_DINODE_FMT_BTREE: + ptr0 = xfs.ptr0; + for (;;) { + xfs.daddr = fsb2daddr (le64(ptr0)); + devread (xfs.daddr, 0, + sizeof(xfs_btree_lblock_t), (char *)&h); + if (!h.bb_level) { + xfs.nextents = le16(h.bb_numrecs); + xfs.next = fsb2daddr (le64(h.bb_rightsib)); + xfs.fpos = sizeof(xfs_btree_block_t); + return; + } + devread (xfs.daddr, xfs.btnode_ptr0_off, + sizeof(xfs_bmbt_ptr_t), (char *)&ptr0); + } + } +} + +static xad_t * +next_extent (void) +{ + static xad_t xad; + + switch (icore.di_format) { + case XFS_DINODE_FMT_EXTENTS: + if (xfs.nextents == 0) + return NULL; + break; + case XFS_DINODE_FMT_BTREE: + if (xfs.nextents == 0) { + xfs_btree_lblock_t h; + if (xfs.next == 0) + return NULL; + xfs.daddr = xfs.next; + devread (xfs.daddr, 0, sizeof(xfs_btree_lblock_t), (char *)&h); + xfs.nextents = le16(h.bb_numrecs); + xfs.next = fsb2daddr (le64(h.bb_rightsib)); + xfs.fpos = sizeof(xfs_btree_block_t); + } + /* Yeah, I know that's slow, but I really don't care */ + devread (xfs.daddr, xfs.fpos, sizeof(xfs_bmbt_rec_t), filebuf); + xfs.xt = (xfs_bmbt_rec_32_t *)filebuf; + xfs.fpos += sizeof(xfs_bmbt_rec_32_t); + } + xad.offset = xt_offset (xfs.xt); + xad.start = xt_start (xfs.xt); + xad.len = xt_len (xfs.xt); + ++xfs.xt; + --xfs.nextents; + + return &xad; +} + +/* + * Name lies - the function reads only first 100 bytes + */ +static void +xfs_dabread (void) +{ + xad_t *xad; + xfs_fileoff_t offset;; + + init_extents (); + while ((xad = next_extent ())) { + offset = xad->offset; + if (isinxt (xfs.dablk, offset, xad->len)) { + devread (fsb2daddr (xad->start + xfs.dablk - offset), + 0, 100, dirbuf); + break; + } + } +} + +static inline xfs_ino_t +sf_ino (char *sfe, int namelen) +{ + void *p = sfe + namelen + 3; +#ifdef __alpha__ + xfs_ino_t ino = 0; + if (xfs.i8param == 0) { + memcpy(&ino, p, sizeof(xfs_dir2_ino8_t)); + return le64(ino); + } else { + memcpy(&ino, p, sizeof(xfs_dir2_ino4_t)); + return le32(ino); + } +#else + /* unaligned access */ + return (xfs.i8param == 0) + ? le64(*(xfs_ino_t *)p) : le32(*(uint32_t *)p); +#endif +} + +static inline xfs_ino_t +sf_parent_ino (void) +{ +#ifdef __alpha__ + void *p = &inode->di_u.di_dir2sf.hdr.parent; + xfs_ino_t ino = 0; + if (xfs.i8param == 0) { + memcpy(&ino, p, sizeof(xfs_dir2_ino8_t)); + return le64(ino); + } else { + memcpy(&ino, p, sizeof(xfs_dir2_ino4_t)); + return le32(ino); + } +#else + /* unaligned access */ + return (xfs.i8param == 0) + ? le64(*(xfs_ino_t *)(&inode->di_u.di_dir2sf.hdr.parent)) + : le32(*(uint32_t *)(&inode->di_u.di_dir2sf.hdr.parent)); +#endif +} + +static inline int +roundup8 (int n) +{ + return ((n+7)&~7); +} + +static char * +next_dentry (xfs_ino_t *ino) +{ + int namelen; + int toread; + static xfs_dir2_sf_entry_t *sfe; + char *name = NULL; + + if (xfs.dirpos >= xfs.dirmax) { + if (xfs.forw == 0) + return NULL; + xfs.dablk = xfs.forw; + xfs_dabread (); +#define h ((xfs_dir2_leaf_hdr_t *)dirbuf) + xfs.dirmax = le16 (h->count) - le16 (h->stale); + xfs.forw = le32 (h->info.forw); +#undef h + xfs.dirpos = 0; + } + + switch (icore.di_format) { + case XFS_DINODE_FMT_LOCAL: + switch (xfs.dirpos) { + case -2: + *ino = 0; + name = "."; + namelen = 1; + break; + case -1: /* ".." */ + *ino = sf_parent_ino (); + name = ".."; + namelen = 2; + sfe = (xfs_dir2_sf_entry_t *) + (inode->di_u.di_c + + sizeof(xfs_dir2_sf_hdr_t) + - xfs.i8param); + break; + default: + namelen = sfe->namelen; + *ino = sf_ino ((char *)sfe, namelen); + name = sfe->name; + name[namelen] = 0; + sfe = (xfs_dir2_sf_entry_t *) + ((char *)sfe + namelen + 11 - xfs.i8param); + } + break; + case XFS_DINODE_FMT_BTREE: + case XFS_DINODE_FMT_EXTENTS: +#define dau ((xfs_dir2_data_union_t *)dirbuf) + for (;;) { + if (xfs.blkoff >= xfs.dirbsize) { + xfs.blkoff = sizeof(xfs_dir2_data_hdr_t); + filepos &= ~(xfs.dirbsize - 1); + filepos |= xfs.blkoff; + } + xfs.blkoff += 4; + if (dau->unused.freetag == XFS_DIR2_DATA_FREE_TAG) { + toread = roundup8 (le16(dau->unused.length)) - 4; + xfs.blkoff += toread; + filepos += toread; + continue; + } + break; + } + xfs_read ((char *)dirbuf + 4, 5); + *ino = le64 (dau->entry.inumber); + namelen = dau->entry.namelen; +#undef dau + toread = roundup8 (namelen + 11) - 9; + xfs_read (dirbuf, toread); + name = (char *)dirbuf; + name[namelen] = 0; + xfs.blkoff += toread + 5; + } + ++xfs.dirpos; + + return name; +} + +static char * +first_dentry (xfs_ino_t *ino) +{ + xfs.forw = 0; + switch (icore.di_format) { + case XFS_DINODE_FMT_LOCAL: + xfs.dirmax = inode->di_u.di_dir2sf.hdr.count; + xfs.i8param = inode->di_u.di_dir2sf.hdr.i8count ? 0 : 4; + xfs.dirpos = -2; + break; + case XFS_DINODE_FMT_EXTENTS: + case XFS_DINODE_FMT_BTREE: + filepos = 0; + xfs_read (dirbuf, sizeof(xfs_dir2_data_hdr_t)); + if (((xfs_dir2_data_hdr_t *)dirbuf)->magic == le32(XFS_DIR2_BLOCK_MAGIC)) { +#define tail ((xfs_dir2_block_tail_t *)dirbuf) + filepos = xfs.dirbsize - sizeof(*tail); + xfs_read (dirbuf, sizeof(*tail)); + xfs.dirmax = le32 (tail->count) - le32 (tail->stale); +#undef tail + } else { + xfs.dablk = (1ULL << 35) >> xfs.blklog; +#define h ((xfs_dir2_leaf_hdr_t *)dirbuf) +#define n ((xfs_da_intnode_t *)dirbuf) + for (;;) { + xfs_dabread (); + if ((n->hdr.info.magic == le16(XFS_DIR2_LEAFN_MAGIC)) + || (n->hdr.info.magic == le16(XFS_DIR2_LEAF1_MAGIC))) { + xfs.dirmax = le16 (h->count) - le16 (h->stale); + xfs.forw = le32 (h->info.forw); + break; + } + xfs.dablk = le32 (n->btree[0].before); + } +#undef n +#undef h + } + xfs.blkoff = sizeof(xfs_dir2_data_hdr_t); + filepos = xfs.blkoff; + xfs.dirpos = 0; + } + return next_dentry (ino); +} + + + +/* + * Initialize an XFS partition starting at offset P_OFFSET; this is + * sort-of the same idea as "mounting" it. Read in the relevant + * control structures and make them available to the user. Returns 0 + * if successful, -1 on failure. + */ +static int +xfs_mount(long cons_dev, long p_offset, long quiet) +{ + xfs_sb_t super; + + partition_offset = p_offset; + + if (cons_read (cons_dev, &super, sizeof(super), partition_offset) != sizeof(super)) { + printf("xfs_mount: read_disk_block failed!\n"); + return -1; + } else if (le32(super.sb_magicnum) != XFS_SB_MAGIC) { + printf("xfs_mount: Bad magic: %x\n", super.sb_magicnum); + return -1; + } else if ((le16(super.sb_versionnum) & XFS_SB_VERSION_NUMBITS) != XFS_SB_VERSION_4) { + printf("xfs_mount: Bad version: %x\n", super.sb_versionnum); + return -1; + } + + xfs.bsize = le32 (super.sb_blocksize); + xfs.blklog = super.sb_blocklog; + xfs.bdlog = xfs.blklog - SECTOR_BITS; + xfs.rootino = le64 (super.sb_rootino); + xfs.isize = le16 (super.sb_inodesize); + xfs.agblocks = le32 (super.sb_agblocks); + xfs.dirbsize = xfs.bsize << super.sb_dirblklog; + + xfs.inopblog = super.sb_inopblog; + xfs.agblklog = super.sb_agblklog; + xfs.agnolog = xfs_highbit32 (le32(super.sb_agcount)); + + xfs.btnode_ptr0_off = + ((xfs.bsize - sizeof(xfs_btree_block_t)) / + (sizeof (xfs_bmbt_key_t) + sizeof (xfs_bmbt_ptr_t))) + * sizeof(xfs_bmbt_key_t) + sizeof(xfs_btree_block_t); + +#ifdef DEBUG + printf("XFS: version = %d\n",le16(super.sb_versionnum) & XFS_SB_VERSION_NUMBITS); + printf("XFS: blocksize = %d\n",xfs.bsize); +#endif + dev = cons_dev; + xfsfs.blocksize = xfs.bsize; + return 0; +} + +static long +xfs_read (void *buf, long len) +{ + xad_t *xad; + xfs_fileoff_t endofprev, endofcur, offset; + xfs_filblks_t xadlen; + int toread, startpos, endpos; + + if (icore.di_format == XFS_DINODE_FMT_LOCAL) { + memmove (buf, inode->di_u.di_c + filepos, len); + filepos += len; + return len; + } + + startpos = filepos; + endpos = filepos + len; + endofprev = (xfs_fileoff_t)-1; + init_extents (); + while (len > 0 && (xad = next_extent ())) { + offset = xad->offset; + xadlen = xad->len; + if (isinxt (filepos >> xfs.blklog, offset, xadlen)) { + endofcur = (offset + xadlen) << xfs.blklog; + toread = (endofcur >= endpos) + ? len : (endofcur - filepos); + devread (fsb2daddr (xad->start), + filepos - (offset << xfs.blklog), toread, buf); + buf += toread; + len -= toread; + filepos += toread; + } else if (offset > endofprev) { + toread = ((offset << xfs.blklog) >= endpos) + ? len : ((offset - endofprev) << xfs.blklog); + len -= toread; + filepos += toread; + for (; toread; toread--) { + *(char*)buf++ = 0; + } + continue; + } + endofprev = offset + xadlen; + } + + return filepos - startpos; +} + +/* + * Read block number "blkno". + */ + +static int +xfs_bread(int fd, long blkno, long nblks, char *buffer) +{ + if (fd == 1) { + /* + * Duh. XFS doesn't read past EOF + * aboot does just that by trying to read nblks*blksize, + * where nblks*blksize > filesize + */ + memset(buffer,0,nblks*xfs.bsize); + long nbytes = xfs_read(buffer, nblks*xfs.bsize); + if (nbytes == le64(icore.di_size)) + return nblks*xfs.bsize; + return (int)nbytes; + } + printf("XFS error: bad file descriptor!\n"); + return -1; +} + +/* + * Unix-like open routine. Returns a small integer + * (does not care what file, we say it's OK) + */ +static int xfs_open(const char *dirname) +{ + xfs_ino_t ino, parent_ino; + xfs_fsize_t di_size; + int di_mode; + int cmp, n, link_count; + char linkbuf[xfs.bsize]; + char *rest, *name, ch; + char namebuf[MAXNAMELEN]; + strncpy(namebuf,dirname,MAXNAMELEN); + char *filename = namebuf; + +#ifdef DEBUG_XFS + printf("xfs_open(): filename = %s\n", filename); +#endif + + parent_ino = ino = xfs.rootino; + link_count = 0; + for (;;) { + di_read (ino); + di_size = le64 (icore.di_size); + di_mode = le16 (icore.di_mode); + +#ifdef DEBUG_XFS + printf("xfs_open(): di_mode = %o\n", di_mode); +#endif + if ((di_mode & IFMT) == IFLNK) { + if (++link_count > MAX_LINK_COUNT) { + printf("XFS error: symlink loop!\n"); + return 0; + } + if (di_size < xfs.bsize - 1) { + filepos = 0; + filemax = di_size; + n = xfs_read (linkbuf, filemax); + } else { + printf("XFS error: bad file length!\n"); + return 0; + } + + ino = (linkbuf[0] == '/') ? xfs.rootino : parent_ino; + while (n < (xfs.bsize - 1) && (linkbuf[n++] = *filename++)); + linkbuf[n] = 0; + filename = linkbuf; + continue; + } + + if (!*filename || isspace (*filename)) { + if (((di_mode & IFMT) != IFREG) + && ((di_mode & IFMT) != IFDIR)) { + printf("XFS error: bad file type!\n"); + return 0; + } + filepos = 0; + filemax = di_size; + return 1; + } + + if ((di_mode & IFMT) != IFDIR) { + printf("XFS error: bad file type!\n"); + return 0; + } + + for (; *filename == '/'; filename++); + + if (!strcmp(filename,"")) { + filepos = 0; + filemax = 0; + return 1; + } + + for (rest = filename; (ch = *rest) && !isspace (ch) && ch != '/'; rest++); + *rest = 0; + + name = first_dentry (&xfs.new_ino); + for (;;) { +#ifdef DEBUG + printf("xfs_open(): found %s\n", name); +#endif + cmp = (!*filename) ? -1 : strcmp (filename, name); + if (cmp == 0) { + parent_ino = ino; + if (xfs.new_ino) + ino = xfs.new_ino; + *(filename = rest) = ch; + break; + } + name = next_dentry (&xfs.new_ino); + if (name == NULL) { + *rest = ch; + return -1; + } + } + } +} + +/* + * Only one file is opened at any time, so close is a nop. + */ +static void xfs_close(int fd) +{ +} + +/* + * Return the next directory entry. + * Must have opened the directory with xfs_open() + */ +static const char * +xfs_readdir(int fd, int rewind) +{ + if (fd != 1) + return NULL; + if ((le16 (icore.di_mode) & IFMT) != IFDIR) { + printf("Not a directory!\n"); + return NULL; + } + if (rewind) + return first_dentry (&xfs.new_ino); + return next_dentry (&xfs.new_ino); +} + +/* + * Get file status + */ +static int +xfs_fstat(int fd, struct stat* buf) +{ + if (fd != 1) + return -1; + + memset(buf, 0, sizeof(struct stat)); + buf->st_mode = le16(icore.di_mode); + buf->st_flags = le16(icore.di_flags); + buf->st_nlink = le16(icore.di_onlink); + buf->st_uid = le32(icore.di_uid); + buf->st_gid = le32(icore.di_gid); + buf->st_size = le64(icore.di_size); + buf->st_blocks = le64(icore.di_nblocks); + buf->st_atime = le32(icore.di_atime.t_sec); + buf->st_mtime = le32(icore.di_mtime.t_sec); + buf->st_ctime = le32(icore.di_ctime.t_sec); + return 0; +} + + diff --git a/fs/xfs.h b/fs/xfs.h new file mode 100644 index 0000000..e8ec0d6 --- /dev/null +++ b/fs/xfs.h @@ -0,0 +1,536 @@ +/* + * xfs.h - an extraction from xfsprogs-1.3.5/include/xfs* into one file + * + * Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston MA 02111-1307, USA. + * + * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, + * Mountain View, CA 94043, or: + * + * http://www.sgi.com + * + * For further information regarding this notice, see: + * + * http://oss.sgi.com/projects/GenInfo/SGIGPLNoticeExplan/ + */ + +typedef uint64_t xfs_ino_t; +typedef uint32_t xfs_agino_t; +typedef int64_t xfs_daddr_t; +typedef int64_t xfs_off_t; +typedef uint8_t uuid_t[16]; + + +/* those are from xfs_types.h */ + +typedef uint32_t xfs_agblock_t; /* blockno in alloc. group */ +typedef uint32_t xfs_extlen_t; /* extent length in blocks */ +typedef uint32_t xfs_agnumber_t; /* allocation group number */ +typedef int32_t xfs_extnum_t; /* # of extents in a file */ +typedef int16_t xfs_aextnum_t; /* # extents in an attribute fork */ +typedef int64_t xfs_fsize_t; /* bytes in a file */ + +typedef uint32_t xfs_dablk_t; /* dir/attr block number (in file) */ +typedef uint32_t xfs_dahash_t; /* dir/attr hash value */ + +/* + * Disk based types: + */ +typedef uint64_t xfs_dfsbno_t; /* blockno in filesystem (agno|agbno) */ +typedef uint64_t xfs_drfsbno_t; /* blockno in filesystem (raw) */ +typedef uint64_t xfs_drtbno_t; /* extent (block) in realtime area */ +typedef uint64_t xfs_dfiloff_t; /* block number in a file */ + +typedef uint64_t xfs_fsblock_t; /* blockno in filesystem (agno|agbno) */ +typedef uint64_t xfs_fileoff_t; /* block number in a file */ +typedef uint64_t xfs_filblks_t; /* number of blocks in a file */ + + +/* those are from xfs_sb.h */ + +#define XFS_SB_MAGIC 0x58465342 /* 'XFSB'*/ +#define XFS_SB_VERSION_4 4 /* 6.2+ - bitmask version */ +#define XFS_SB_VERSION_NUMBITS 0x000f + +typedef struct xfs_sb +{ + uint32_t sb_magicnum; /* magic number == XFS_SB_MAGIC */ + uint32_t sb_blocksize; /* logical block size, bytes */ + xfs_drfsbno_t sb_dblocks; /* number of data blocks */ + xfs_drfsbno_t sb_rblocks; /* number of realtime blocks */ + xfs_drtbno_t sb_rextents; /* number of realtime extents */ + uuid_t sb_uuid; /* file system unique id */ + xfs_dfsbno_t sb_logstart; /* starting block of log if internal */ + xfs_ino_t sb_rootino; /* root inode number */ + xfs_ino_t sb_rbmino; /* bitmap inode for realtime extents */ + xfs_ino_t sb_rsumino; /* summary inode for rt bitmap */ + xfs_agblock_t sb_rextsize; /* realtime extent size, blocks */ + xfs_agblock_t sb_agblocks; /* size of an allocation group */ + xfs_agnumber_t sb_agcount; /* number of allocation groups */ + xfs_extlen_t sb_rbmblocks; /* number of rt bitmap blocks */ + xfs_extlen_t sb_logblocks; /* number of log blocks */ + uint16_t sb_versionnum; /* header version == XFS_SB_VERSION */ + uint16_t sb_sectsize; /* volume sector size, bytes */ + uint16_t sb_inodesize; /* inode size, bytes */ + uint16_t sb_inopblock; /* inodes per block */ + char sb_fname[12]; /* file system name */ + uint8_t sb_blocklog; /* log2 of sb_blocksize */ + uint8_t sb_sectlog; /* log2 of sb_sectsize */ + uint8_t sb_inodelog; /* log2 of sb_inodesize */ + uint8_t sb_inopblog; /* log2 of sb_inopblock */ + uint8_t sb_agblklog; /* log2 of sb_agblocks (rounded up) */ + uint8_t sb_rextslog; /* log2 of sb_rextents */ + uint8_t sb_inprogress; /* mkfs is in progress, don't mount */ + uint8_t sb_imax_pct; /* max % of fs for inode space */ + /* statistics */ + /* + * These fields must remain contiguous. If you really + * want to change their layout, make sure you fix the + * code in xfs_trans_apply_sb_deltas(). + */ + uint64_t sb_icount; /* allocated inodes */ + uint64_t sb_ifree; /* free inodes */ + uint64_t sb_fdblocks; /* free data blocks */ + uint64_t sb_frextents; /* free realtime extents */ + /* + * End contiguous fields. + */ + xfs_ino_t sb_uquotino; /* user quota inode */ + xfs_ino_t sb_gquotino; /* group quota inode */ + uint16_t sb_qflags; /* quota flags */ + uint8_t sb_flags; /* misc. flags */ + uint8_t sb_shared_vn; /* shared version number */ + xfs_extlen_t sb_inoalignmt; /* inode chunk alignment, fsblocks */ + uint32_t sb_unit; /* stripe or raid unit */ + uint32_t sb_width; /* stripe or raid width */ + uint8_t sb_dirblklog; /* log2 of dir block size (fsbs) */ + uint8_t sb_dummy[7]; /* padding */ +} xfs_sb_t; + + +/* those are from xfs_btree.h */ + +/* + * Long form header: bmap btrees. + */ +typedef struct xfs_btree_lblock +{ + uint32_t bb_magic; /* magic number for block type */ + uint16_t bb_level; /* 0 is a leaf */ + uint16_t bb_numrecs; /* current # of data records */ + xfs_dfsbno_t bb_leftsib; /* left sibling block or NULLDFSBNO */ + xfs_dfsbno_t bb_rightsib; /* right sibling block or NULLDFSBNO */ +} xfs_btree_lblock_t; + +/* + * Combined header and structure, used by common code. + */ +typedef struct xfs_btree_hdr +{ + uint32_t bb_magic; /* magic number for block type */ + uint16_t bb_level; /* 0 is a leaf */ + uint16_t bb_numrecs; /* current # of data records */ +} xfs_btree_hdr_t; + +typedef struct xfs_btree_block +{ + xfs_btree_hdr_t bb_h; /* header */ + union { + struct { + xfs_agblock_t bb_leftsib; + xfs_agblock_t bb_rightsib; + } s; /* short form pointers */ + struct { + xfs_dfsbno_t bb_leftsib; + xfs_dfsbno_t bb_rightsib; + } l; /* long form pointers */ + } bb_u; /* rest */ +} xfs_btree_block_t; + +/* those are from xfs_bmap_btree.h */ + +/* + * Bmap root header, on-disk form only. + */ +typedef struct xfs_bmdr_block +{ + uint16_t bb_level; /* 0 is a leaf */ + uint16_t bb_numrecs; /* current # of data records */ +} xfs_bmdr_block_t; + +/* + * Bmap btree record and extent descriptor. + * For 32-bit kernels, + * l0:31 is an extent flag (value 1 indicates non-normal). + * l0:0-30 and l1:9-31 are startoff. + * l1:0-8, l2:0-31, and l3:21-31 are startblock. + * l3:0-20 are blockcount. + * For 64-bit kernels, + * l0:63 is an extent flag (value 1 indicates non-normal). + * l0:9-62 are startoff. + * l0:0-8 and l1:21-63 are startblock. + * l1:0-20 are blockcount. + */ + +#define BMBT_USE_64 1 + +typedef struct xfs_bmbt_rec_32 +{ + uint32_t l0, l1, l2, l3; +} xfs_bmbt_rec_32_t; +typedef struct xfs_bmbt_rec_64 +{ + uint64_t l0, l1; +} xfs_bmbt_rec_64_t; + +#if BMBT_USE_64 +typedef uint64_t xfs_bmbt_rec_base_t; /* use this for casts */ +typedef xfs_bmbt_rec_64_t xfs_bmbt_rec_t, xfs_bmdr_rec_t; +#else /* !BMBT_USE_64 */ +typedef uint32_t xfs_bmbt_rec_base_t; /* use this for casts */ +typedef xfs_bmbt_rec_32_t xfs_bmbt_rec_t, xfs_bmdr_rec_t; +#endif /* BMBT_USE_64 */ + +/* + * Key structure for non-leaf levels of the tree. + */ +typedef struct xfs_bmbt_key +{ + xfs_dfiloff_t br_startoff; /* starting file offset */ +} xfs_bmbt_key_t, xfs_bmdr_key_t; + +typedef xfs_dfsbno_t xfs_bmbt_ptr_t, xfs_bmdr_ptr_t; /* btree pointer type */ + /* btree block header type */ +typedef struct xfs_btree_lblock xfs_bmbt_block_t; + + + +/* those are from xfs_dir2.h */ +/* + * Directory version 2. + * There are 4 possible formats: + * shortform + * single block - data with embedded leaf at the end + * multiple data blocks, single leaf+freeindex block + * data blocks, node&leaf blocks (btree), freeindex blocks + * + * The shortform format is in xfs_dir2_sf.h. + * The single block format is in xfs_dir2_block.h. + * The data block format is in xfs_dir2_data.h. + * The leaf and freeindex block formats are in xfs_dir2_leaf.h. + * Node blocks are the same as the other version, in xfs_da_btree.h. + */ + +/* + * Byte offset in data block and shortform entry. + */ +typedef uint16_t xfs_dir2_data_off_t; + +/* + * Byte offset in a directory. + */ +typedef xfs_off_t xfs_dir2_off_t; + +/* those are from xfs_da_btree.h */ +/*======================================================================== + * Directory Structure when greater than XFS_LBSIZE(mp) bytes. + *========================================================================*/ + +/* + * This structure is common to both leaf nodes and non-leaf nodes in the Btree. + * + * Is is used to manage a doubly linked list of all blocks at the same + * level in the Btree, and to identify which type of block this is. + */ +#define XFS_DIR2_LEAF1_MAGIC 0xd2f1 /* magic number: v2 dirlf single blks */ +#define XFS_DIR2_LEAFN_MAGIC 0xd2ff /* magic number: v2 dirlf multi blks */ + +typedef struct xfs_da_blkinfo { + xfs_dablk_t forw; /* previous block in list */ + xfs_dablk_t back; /* following block in list */ + uint16_t magic; /* validity check on block */ + uint16_t pad; /* unused */ +} xfs_da_blkinfo_t; + +/* + * This is the structure of the root and intermediate nodes in the Btree. + * The leaf nodes are defined above. + * + * Entries are not packed. + * + * Since we have duplicate keys, use a binary search but always follow + * all match in the block, not just the first match found. + */ + +typedef struct xfs_da_intnode { + struct xfs_da_node_hdr { /* constant-structure header block */ + xfs_da_blkinfo_t info; /* block type, links, etc. */ + uint16_t count; /* count of active entries */ + uint16_t level; /* level above leaves (leaf == 0) */ + } hdr; + struct xfs_da_node_entry { + xfs_dahash_t hashval; /* hash value for this descendant */ + xfs_dablk_t before; /* Btree block before this key */ + } btree[1]; /* variable sized array of keys */ +} xfs_da_intnode_t; + + +/* those are from xfs_dir2_data.h */ +/* + * Directory format 2, data block structures. + */ + +/* + * Constants. + */ +#define XFS_DIR2_DATA_FREE_TAG 0xffff +#define XFS_DIR2_DATA_FD_COUNT 3 + +/* + * Structures. + */ + +/* + * Describe a free area in the data block. + * The freespace will be formatted as a xfs_dir2_data_unused_t. + */ +typedef struct xfs_dir2_data_free { + xfs_dir2_data_off_t offset; /* start of freespace */ + xfs_dir2_data_off_t length; /* length of freespace */ +} xfs_dir2_data_free_t; + +/* + * Header for the data blocks. + * Always at the beginning of a directory-sized block. + * The code knows that XFS_DIR2_DATA_FD_COUNT is 3. + */ +typedef struct xfs_dir2_data_hdr { + uint32_t magic; /* XFS_DIR2_DATA_MAGIC */ + /* or XFS_DIR2_BLOCK_MAGIC */ + xfs_dir2_data_free_t bestfree[XFS_DIR2_DATA_FD_COUNT]; +} xfs_dir2_data_hdr_t; + +/* + * Active entry in a data block. Aligned to 8 bytes. + * Tag appears as the last 2 bytes. + */ +typedef struct xfs_dir2_data_entry { + xfs_ino_t inumber; /* inode number */ + uint8_t namelen; /* name length */ + uint8_t name[1]; /* name bytes, no null */ + /* variable offset */ + xfs_dir2_data_off_t tag; /* starting offset of us */ +} xfs_dir2_data_entry_t; + +/* + * Unused entry in a data block. Aligned to 8 bytes. + * Tag appears as the last 2 bytes. + */ +typedef struct xfs_dir2_data_unused { + uint16_t freetag; /* XFS_DIR2_DATA_FREE_TAG */ + xfs_dir2_data_off_t length; /* total free length */ + /* variable offset */ + xfs_dir2_data_off_t tag; /* starting offset of us */ +} xfs_dir2_data_unused_t; + +typedef union { + xfs_dir2_data_entry_t entry; + xfs_dir2_data_unused_t unused; +} xfs_dir2_data_union_t; + + +/* those are from xfs_dir2_leaf.h */ +/* + * Directory version 2, leaf block structures. + */ + +/* + * Leaf block header. + */ +typedef struct xfs_dir2_leaf_hdr { + xfs_da_blkinfo_t info; /* header for da routines */ + uint16_t count; /* count of entries */ + uint16_t stale; /* count of stale entries */ +} xfs_dir2_leaf_hdr_t; + + +/* those are from xfs_dir2_block.h */ +/* + * xfs_dir2_block.h + * Directory version 2, single block format structures + */ + +/* + * The single block format is as follows: + * xfs_dir2_data_hdr_t structure + * xfs_dir2_data_entry_t and xfs_dir2_data_unused_t structures + * xfs_dir2_leaf_entry_t structures + * xfs_dir2_block_tail_t structure + */ + +#define XFS_DIR2_BLOCK_MAGIC 0x58443242 /* XD2B: for one block dirs */ + +typedef struct xfs_dir2_block_tail { + uint32_t count; /* count of leaf entries */ + uint32_t stale; /* count of stale lf entries */ +} xfs_dir2_block_tail_t; + + +/* those are from xfs_dir2_sf.h */ + +/* + * Directory layout when stored internal to an inode. + * + * Small directories are packed as tightly as possible so as to + * fit into the literal area of the inode. + */ + +/* + * Inode number stored as 8 8-bit values. + */ +typedef struct { uint8_t i[8]; } xfs_dir2_ino8_t; + +/* + * Inode number stored as 4 8-bit values. + * Works a lot of the time, when all the inode numbers in a directory + * fit in 32 bits. + */ +typedef struct { uint8_t i[4]; } xfs_dir2_ino4_t; + +typedef union { + xfs_dir2_ino8_t i8; + xfs_dir2_ino4_t i4; +} xfs_dir2_inou_t; + +/* + * Normalized offset (in a data block) of the entry, really xfs_dir2_data_off_t. + * Only need 16 bits, this is the byte offset into the single block form. + */ +typedef struct { uint8_t i[2]; } xfs_dir2_sf_off_t; + +/* + * The parent directory has a dedicated field, and the self-pointer must + * be calculated on the fly. + * + * Entries are packed toward the top as tightly as possible. The header + * and the elements must be bcopy()'d out into a work area to get correct + * alignment for the inode number fields. + */ +typedef struct xfs_dir2_sf_hdr { + uint8_t count; /* count of entries */ + uint8_t i8count; /* count of 8-byte inode #s */ + xfs_dir2_inou_t parent; /* parent dir inode number */ +} xfs_dir2_sf_hdr_t; + +typedef struct xfs_dir2_sf_entry { + uint8_t namelen; /* actual name length */ + xfs_dir2_sf_off_t offset; /* saved offset */ + uint8_t name[1]; /* name, variable size */ + xfs_dir2_inou_t inumber; /* inode number, var. offset */ +} xfs_dir2_sf_entry_t; + +typedef struct xfs_dir2_sf { + xfs_dir2_sf_hdr_t hdr; /* shortform header */ + xfs_dir2_sf_entry_t list[1]; /* shortform entries */ +} xfs_dir2_sf_t; + +/* those are from xfs_dinode.h */ + +#define XFS_DINODE_VERSION_1 1 +#define XFS_DINODE_VERSION_2 2 +#define XFS_DINODE_MAGIC 0x494e /* 'IN' */ + +/* + * Disk inode structure. + * This is just the header; the inode is expanded to fill a variable size + * with the last field expanding. It is split into the core and "other" + * because we only need the core part in the in-core inode. + */ +typedef struct xfs_timestamp { + int32_t t_sec; /* timestamp seconds */ + int32_t t_nsec; /* timestamp nanoseconds */ +} xfs_timestamp_t; + +/* + * Note: Coordinate changes to this structure with the XFS_DI_* #defines + * below and the offsets table in xfs_ialloc_log_di(). + */ +typedef struct xfs_dinode_core +{ + uint16_t di_magic; /* inode magic # = XFS_DINODE_MAGIC */ + uint16_t di_mode; /* mode and type of file */ + int8_t di_version; /* inode version */ + int8_t di_format; /* format of di_c data */ + uint16_t di_onlink; /* old number of links to file */ + uint32_t di_uid; /* owner's user id */ + uint32_t di_gid; /* owner's group id */ + uint32_t di_nlink; /* number of links to file */ + uint16_t di_projid; /* owner's project id */ + uint8_t di_pad[10]; /* unused, zeroed space */ + xfs_timestamp_t di_atime; /* time last accessed */ + xfs_timestamp_t di_mtime; /* time last modified */ + xfs_timestamp_t di_ctime; /* time created/inode modified */ + xfs_fsize_t di_size; /* number of bytes in file */ + xfs_drfsbno_t di_nblocks; /* # of direct & btree blocks used */ + xfs_extlen_t di_extsize; /* basic/minimum extent size for file */ + xfs_extnum_t di_nextents; /* number of extents in data fork */ + xfs_aextnum_t di_anextents; /* number of extents in attribute fork*/ + uint8_t di_forkoff; /* attr fork offs, <<3 for 64b align */ + int8_t di_aformat; /* format of attr fork's data */ + uint32_t di_dmevmask; /* DMIG event mask */ + uint16_t di_dmstate; /* DMIG state info */ + uint16_t di_flags; /* random flags, XFS_DIFLAG_... */ + uint32_t di_gen; /* generation number */ +} xfs_dinode_core_t; + +typedef struct xfs_dinode +{ + xfs_dinode_core_t di_core; + xfs_agino_t di_next_unlinked;/* agi unlinked list ptr */ + union { + xfs_bmdr_block_t di_bmbt; /* btree root block */ + xfs_bmbt_rec_32_t di_bmx[1]; /* extent list */ + xfs_dir2_sf_t di_dir2sf; /* shortform directory v2 */ + char di_c[1]; /* local contents */ + } di_u; +} xfs_dinode_t; + +/* + * Values for di_format + */ +typedef enum xfs_dinode_fmt +{ + XFS_DINODE_FMT_DEV, /* CHR, BLK: di_dev */ + XFS_DINODE_FMT_LOCAL, /* DIR, REG: di_c */ + /* LNK: di_symlink */ + XFS_DINODE_FMT_EXTENTS, /* DIR, REG, LNK: di_bmx */ + XFS_DINODE_FMT_BTREE, /* DIR, REG, LNK: di_bmbt */ + XFS_DINODE_FMT_UUID /* MNT: di_uuid */ +} xfs_dinode_fmt_t; + +/* + * File types (mode field) + */ +#define IFMT 0170000 /* type of file */ +#define IFDIR 0040000 /* directory */ +#define IFREG 0100000 /* regular */ +#define IFLNK 0120000 /* symbolic link */ + From 2b86b9fe09be654744cfdbc0c6147f52d24f61a5 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Tue, 2 Dec 2025 21:10:50 +0100 Subject: [PATCH 3/7] Some fixes to get the xfs code to build, plus adding some debug output. This code does not work at the moment. --- fs/xfs.c | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++----- fs/xfs.h | 24 +++++++++-- 2 files changed, 137 insertions(+), 15 deletions(-) diff --git a/fs/xfs.c b/fs/xfs.c index d48254e..0a0f5d1 100644 --- a/fs/xfs.c +++ b/fs/xfs.c @@ -21,7 +21,10 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include +//#include +#include +#include +#include #include "config.h" #include "aboot.h" #include "bootfs.h" @@ -57,12 +60,36 @@ static long xfs_read (void *buf, long len); #define isspace(c) ((c) == 0x10) +/* + * Replacement for old Linux __swabXX() helpers. + * XFS metadata is big-endian on disk, Alpha CPU is little-endian. + */ +static inline uint16_t __swab16(uint16_t x) +{ + return (uint16_t)((x << 8) | (x >> 8)); +} + +static inline uint32_t __swab32(uint32_t x) +{ + return ((x & 0x000000FFU) << 24) | + ((x & 0x0000FF00U) << 8) | + ((x & 0x00FF0000U) >> 8) | + ((x & 0xFF000000U) >> 24); +} + +static inline uint64_t __swab64(uint64_t x) +{ + return ((uint64_t)__swab32((uint32_t)x) << 32) | + __swab32((uint32_t)(x >> 32)); +} + + static int devread(long sector, long start, long length, void *buf) { long pos = sector * SECT_SIZE; pos += partition_offset + start; -#ifdef DEBUG_XFS +#ifdef DEBUG_XFS2 printf("Reading %ld bytes, starting at sector %ld, disk offset %ld\n", length, sector, pos); #endif @@ -428,9 +455,18 @@ next_dentry (xfs_ino_t *ino) int toread; static xfs_dir2_sf_entry_t *sfe; char *name = NULL; +#ifdef DEBUG_XFS_2 + printf("next_dentry(): dirpos=%d dirmax=%d di_format=%d\n", + xfs.dirpos, xfs.dirmax, icore.di_format); +#endif if (xfs.dirpos >= xfs.dirmax) { - if (xfs.forw == 0) +#ifdef DEBUG_XFS + printf("next_dentry(): at end of directory (dirpos >= dirmax)\n"); +#endif + + + if (xfs.forw == 0) return NULL; xfs.dablk = xfs.forw; xfs_dabread (); @@ -488,6 +524,19 @@ next_dentry (xfs_ino_t *ino) xfs_read ((char *)dirbuf + 4, 5); *ino = le64 (dau->entry.inumber); namelen = dau->entry.namelen; + + /* Some entries are free/invalid: namelen==0 or ino==0. + * Skip them and move to the next directory position. + */ + if (namelen == 0 || *ino == 0) { +#ifdef DEBUG_XFS_2 + printf("next_dentry(): skipping nameless/invalid entry (ino=%llu namelen=%d)\n", + (unsigned long long)*ino, namelen); +#endif + ++xfs.dirpos; + return next_dentry(ino); + } + #undef dau toread = roundup8 (namelen + 11) - 9; xfs_read (dirbuf, toread); @@ -496,6 +545,16 @@ next_dentry (xfs_ino_t *ino) xfs.blkoff += toread + 5; } ++xfs.dirpos; +#ifdef DEBUG_XFS + + if (name) + printf("next_dentry(): returning name='%s' ino=%llu (dirpos now %d)\n", + name, (unsigned long long)*ino, xfs.dirpos); + else + printf("next_dentry(): returning NULL name (dirpos now %d)\n", + xfs.dirpos); + +#endif return name; } @@ -504,11 +563,18 @@ static char * first_dentry (xfs_ino_t *ino) { xfs.forw = 0; +#ifdef DEBUG_XFS_2 + printf("first_dentry(): di_format=%d\n", icore.di_format); +#endif switch (icore.di_format) { case XFS_DINODE_FMT_LOCAL: xfs.dirmax = inode->di_u.di_dir2sf.hdr.count; xfs.i8param = inode->di_u.di_dir2sf.hdr.i8count ? 0 : 4; xfs.dirpos = -2; +#ifdef DEBUG_XFS_2 + printf("first_dentry(): LOCAL dir, dirmax=%d i8param=%d\n", + xfs.dirmax, xfs.i8param); +#endif break; case XFS_DINODE_FMT_EXTENTS: case XFS_DINODE_FMT_BTREE: @@ -540,6 +606,11 @@ first_dentry (xfs_ino_t *ino) xfs.blkoff = sizeof(xfs_dir2_data_hdr_t); filepos = xfs.blkoff; xfs.dirpos = 0; +#ifdef DEBUG_XFS_2 + printf("first_dentry(): DIR2 dir, dirmax=%d dirbsize=%d blkoff=%d\n", + xfs.dirmax, xfs.dirbsize, xfs.blkoff); +#endif + } return next_dentry (ino); } @@ -565,9 +636,21 @@ xfs_mount(long cons_dev, long p_offset, long quiet) } else if (le32(super.sb_magicnum) != XFS_SB_MAGIC) { printf("xfs_mount: Bad magic: %x\n", super.sb_magicnum); return -1; - } else if ((le16(super.sb_versionnum) & XFS_SB_VERSION_NUMBITS) != XFS_SB_VERSION_4) { - printf("xfs_mount: Bad version: %x\n", super.sb_versionnum); - return -1; + } else { + unsigned int ver = le16(super.sb_versionnum) & XFS_SB_VERSION_NUMBITS; + + /* Explicitly refuse XFS v5 (CRC-enabled) filesystems */ + if (ver == XFS_SB_VERSION_5) { + printf("xfs_mount: XFS v5 (CRC-enabled) filesystem is not supported by aboot\n"); + return -1; + } + + /* Only XFS v4 is supported */ + if (ver != XFS_SB_VERSION_4) { + printf("xfs_mount: unsupported XFS version %u\n", ver); + return -1; + } + } xfs.bsize = le32 (super.sb_blocksize); @@ -681,10 +764,12 @@ static int xfs_open(const char *dirname) strncpy(namebuf,dirname,MAXNAMELEN); char *filename = namebuf; + #ifdef DEBUG_XFS printf("xfs_open(): filename = %s\n", filename); #endif + parent_ino = ino = xfs.rootino; link_count = 0; for (;;) { @@ -742,11 +827,21 @@ static int xfs_open(const char *dirname) for (rest = filename; (ch = *rest) && !isspace (ch) && ch != '/'; rest++); *rest = 0; +#ifdef DEBUG_XFS + printf("xfs_open(): looking for '%s' in current directory\n", filename); +#endif name = first_dentry (&xfs.new_ino); for (;;) { -#ifdef DEBUG - printf("xfs_open(): found %s\n", name); +#ifdef DEBUG_XFS + if (name) + printf("xfs_open(): candidate='%s' looking_for='%s'\n", + name, filename); + else + printf("xfs_open(): candidate is NULL while looking_for='%s'\n", + filename); + + #endif cmp = (!*filename) ? -1 : strcmp (filename, name); if (cmp == 0) { @@ -779,14 +874,25 @@ static void xfs_close(int fd) static const char * xfs_readdir(int fd, int rewind) { +#ifdef DEBUG_XFS + printf("xfs_readdir(): fd=%d rewind=%d\n", fd, rewind); +#endif + if (fd != 1) return NULL; if ((le16 (icore.di_mode) & IFMT) != IFDIR) { printf("Not a directory!\n"); return NULL; } - if (rewind) - return first_dentry (&xfs.new_ino); + if (rewind) { +#ifdef DEBUG_XFS_2 + printf("xfs_readdir(): calling first_dentry()\n"); +#endif + return first_dentry (&xfs.new_ino); + } +#ifdef DEBUG_XFS_2 + printf("xfs_readdir(): calling next_dentry()\n"); +#endif return next_dentry (&xfs.new_ino); } @@ -801,7 +907,7 @@ xfs_fstat(int fd, struct stat* buf) memset(buf, 0, sizeof(struct stat)); buf->st_mode = le16(icore.di_mode); - buf->st_flags = le16(icore.di_flags); + //buf->st_flags = le16(icore.di_flags); buf->st_nlink = le16(icore.di_onlink); buf->st_uid = le32(icore.di_uid); buf->st_gid = le32(icore.di_gid); diff --git a/fs/xfs.h b/fs/xfs.h index e8ec0d6..e6f25b6 100644 --- a/fs/xfs.h +++ b/fs/xfs.h @@ -66,10 +66,26 @@ typedef uint64_t xfs_filblks_t; /* number of blocks in a file */ /* those are from xfs_sb.h */ -#define XFS_SB_MAGIC 0x58465342 /* 'XFSB'*/ -#define XFS_SB_VERSION_4 4 /* 6.2+ - bitmask version */ -#define XFS_SB_VERSION_NUMBITS 0x000f - +#define XFS_SB_MAGIC 0x58465342 /* 'XFSB'*/ +#define XFS_SB_VERSION_1 1 /* 5.3, 6.0.1, 6.1 */ +#define XFS_SB_VERSION_2 2 /* 6.2 - attributes */ +#define XFS_SB_VERSION_3 3 /* 6.2 - new inode version */ +#define XFS_SB_VERSION_4 4 /* 6.2+ - bitmask version */ +#define XFS_SB_VERSION_5 5 /* CRC enabled filesystem */ +#define XFS_SB_VERSION_NUMBITS 0x000f +#define XFS_SB_VERSION_ALLFBITS 0xfff0 +#define XFS_SB_VERSION_ATTRBIT 0x0010 +#define XFS_SB_VERSION_NLINKBIT 0x0020 +#define XFS_SB_VERSION_QUOTABIT 0x0040 +#define XFS_SB_VERSION_ALIGNBIT 0x0080 +#define XFS_SB_VERSION_DALIGNBIT 0x0100 +#define XFS_SB_VERSION_SHAREDBIT 0x0200 +#define XFS_SB_VERSION_LOGV2BIT 0x0400 +#define XFS_SB_VERSION_SECTORBIT 0x0800 +#define XFS_SB_VERSION_EXTFLGBIT 0x1000 +#define XFS_SB_VERSION_DIRV2BIT 0x2000 +#define XFS_SB_VERSION_BORGBIT 0x4000 /* ASCII only case-insens. */ +#define XFS_SB_VERSION_MOREBITSBIT 0x8000 typedef struct xfs_sb { uint32_t sb_magicnum; /* magic number == XFS_SB_MAGIC */ From 068b91299b752dc2fd189360773677b5d998662f Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Tue, 2 Dec 2025 23:21:11 +0100 Subject: [PATCH 4/7] aboot: fix XFS DIR2 parsing and restore XFS boot support Fix two bugs in the XFS directory reader that prevented aboot from finding files on XFS v4 (crc=0) filesystems: - Add missing read of the 4-byte DIR2 entry header. Without this, freetag/length values were uninitialized, causing corrupted filenames and incorrect directory offsets. - Skip nameless/invalid entries to avoid returning garbage names. With these fixes, aboot can reliably load files from classic XFS v4 (naming version 2, no CRC). Modern CRC-enabled (v5) XFS filesystems remain unsupported. Signed-off-by: Magnus Lindholm --- fs/xfs.c | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/fs/xfs.c b/fs/xfs.c index 0a0f5d1..2a4c875 100644 --- a/fs/xfs.c +++ b/fs/xfs.c @@ -505,26 +505,29 @@ next_dentry (xfs_ino_t *ino) break; case XFS_DINODE_FMT_BTREE: case XFS_DINODE_FMT_EXTENTS: -#define dau ((xfs_dir2_data_union_t *)dirbuf) - for (;;) { - if (xfs.blkoff >= xfs.dirbsize) { - xfs.blkoff = sizeof(xfs_dir2_data_hdr_t); - filepos &= ~(xfs.dirbsize - 1); - filepos |= xfs.blkoff; - } - xfs.blkoff += 4; - if (dau->unused.freetag == XFS_DIR2_DATA_FREE_TAG) { - toread = roundup8 (le16(dau->unused.length)) - 4; - xfs.blkoff += toread; - filepos += toread; - continue; - } - break; - } - xfs_read ((char *)dirbuf + 4, 5); - *ino = le64 (dau->entry.inumber); - namelen = dau->entry.namelen; - +#define dau ((xfs_dir2_data_union_t *)dirbuf) + for (;;) { + if (xfs.blkoff >= xfs.dirbsize) { + xfs.blkoff = sizeof(xfs_dir2_data_hdr_t); + filepos &= ~(xfs.dirbsize - 1); + filepos |= xfs.blkoff; + } + + xfs_read(dirbuf, 4); /* read freetag/length/tag */ + xfs.blkoff += 4; + + if (dau->unused.freetag == XFS_DIR2_DATA_FREE_TAG) { + toread = roundup8(le16(dau->unused.length)) - 4; + xfs.blkoff += toread; + filepos += toread; + continue; + } + break; + } + + xfs_read((char *)dirbuf + 4, 5); + *ino = le64(dau->entry.inumber); + namelen = dau->entry.namelen; /* Some entries are free/invalid: namelen==0 or ino==0. * Skip them and move to the next directory position. */ From e9ffb9179e3b7f922dc5c227b06df38725760579 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Sat, 6 Dec 2025 19:12:45 +0100 Subject: [PATCH 5/7] Initial XFSv5 suppport Implement some functions for initial support for XFSv5 file systems Signed-off-by: Magnus Lindholm --- fs/xfs.c | 1342 ++++++++++++++++++++++++++++++++++++++++++++++-------- fs/xfs.h | 16 +- 2 files changed, 1163 insertions(+), 195 deletions(-) diff --git a/fs/xfs.c b/fs/xfs.c index 2a4c875..d3b6631 100644 --- a/fs/xfs.c +++ b/fs/xfs.c @@ -39,6 +39,26 @@ static int xfs_open(const char *dirname); static void xfs_close(int fd); static const char *xfs_readdir(int fd, int rewind); static int xfs_fstat(int fd, struct stat* buf); +static char* first_dentry_dir3(xfs_ino_t *); +static char* first_dentry_dir2(xfs_ino_t *); +static char *next_dentry_dir3(xfs_ino_t *); +static char *next_dentry_dir2(xfs_ino_t *); +static char *next_dentry_local(xfs_ino_t *); +static xfs_ino_t xfs_dir2_sf_get_ino(xfs_dir2_sf_hdr_t *, xfs_dir2_inou_t *); + + +typedef struct { + uint32_t inumber_lo; + uint32_t inumber_hi; + uint8_t namelen; + uint8_t filetype; + uint8_t name[]; /* variable length */ +} xfs_dir3_data_entry_t; + +typedef union { + xfs_dir3_data_entry_t entry; + xfs_dir2_data_unused_t unused; +} xfs_dir3_data_union_t; struct bootfs xfsfs = { FS_EXT2, @@ -51,6 +71,12 @@ struct bootfs xfsfs = { xfs_fstat }; +typedef struct { + uint8_t namelen; + uint8_t offset; + char name[1]; /* name bytes follow */ +} xfs_dir3_sf_entry_t; + static long dev = -1; static long partition_offset; static long filepos; @@ -131,10 +157,81 @@ struct xfs_info { int fpos; xfs_ino_t rootino; xfs_ino_t new_ino; + int is_v5; + char* data_fork; }; + static struct xfs_info xfs; +/* On-disk v1/v2/v3 inode layout, used only to compute fork offsets. + * Matches current Linux struct xfs_dinode. Field order is important, + * but we only *use* di_version and the size/alignment. + */ +typedef struct xfs_dinode_disk { + uint16_t di_magic; /* XFS_DINODE_MAGIC */ + uint16_t di_mode; + uint8_t di_version; /* 1, 2 or 3 */ + uint8_t di_format; + uint16_t di_onlink; + uint32_t di_uid; + uint32_t di_gid; + uint32_t di_nlink; + uint16_t di_projid_lo; + uint16_t di_projid_hi; + uint8_t di_pad[6]; + uint16_t di_flushiter; + xfs_timestamp_t di_atime; + xfs_timestamp_t di_mtime; + xfs_timestamp_t di_ctime; + uint64_t di_size; + uint64_t di_nblocks; + uint32_t di_extsize; + uint32_t di_nextents; + uint16_t di_anextents; + uint8_t di_forkoff; + int8_t di_aformat; + uint32_t di_dmevmask; + uint16_t di_dmstate; + uint16_t di_flags; + uint32_t di_gen; + uint32_t di_next_unlinked; + + /* v3 (v5 filesystem) extra fields follow */ + uint32_t di_crc; + uint64_t di_changecount; + uint64_t di_lsn; + uint64_t di_flags2; + uint8_t di_pad2[16]; + xfs_timestamp_t di_crtime; + uint64_t di_ino; + uint8_t di_uuid[16]; +} xfs_dinode_disk_t; + + + +/* v5 (CRC-enabled) directory block header (48 bytes) */ +typedef struct xfs_dir3_blk_hdr { + uint32_t magic; /* XFS_DIR3_*_MAGIC */ + uint32_t crc; + uint64_t blkno; /* first block of the buffer */ + uint64_t lsn; /* sequence number of last write */ + uint8_t uuid[16]; /* filesystem we belong to */ + uint64_t owner; /* inode that owns the block */ +} xfs_dir3_blk_hdr_t; + +/* + * Full v5 directory data header (64 bytes). + * This is the v5 extension of xfs_dir2_data_hdr_t, so the + * bestfree[] array must be present and magic must still be + * at offset 0 (hdr.magic). + */ +typedef struct xfs_dir3_data_hdr { + xfs_dir3_blk_hdr_t hdr; /* 48 bytes */ + xfs_dir2_data_free_t bestfree[XFS_DIR2_DATA_FD_COUNT]; + uint32_t pad; /* 64-byte alignment */ +} xfs_dir3_data_hdr_t; + #ifdef __alpha__ /* take care of alignment*/ static long FSYS_BUF[32768/sizeof(long)]; #define dirbuf ((long *)FSYS_BUF) @@ -156,6 +253,45 @@ static struct xfs_info xfs; #define XFS_INO_AGINO_BITS (xfs.agblklog + xfs.inopblog) #define XFS_INO_AGNO_BITS xfs.agnolog + +/* Size of the core+next_unlinked for v1/v2 vs full inode for v3. */ +static inline int +xfs_dinode_size_disk(uint8_t version) +{ + if (version == 3) + return sizeof(xfs_dinode_disk_t); + /* v1/v2: data fork starts just before di_crc in the v3 layout */ + return offsetof(xfs_dinode_disk_t, di_crc); +} + +/* Pointer to the *data fork* (shortform dir / extents / etc). */ +static inline char * +xfs_dfork_dptr(void) +{ + xfs_dinode_disk_t *dip = (xfs_dinode_disk_t *)inode; + uint8_t version = dip->di_version; /* single byte, no swab needed */ + + return ((char *)dip) + xfs_dinode_size_disk(version); +} + +/* Pointer to shortform directory header. */ +static inline xfs_dir2_sf_t * +xfs_sf_dir(void) +{ + return (xfs_dir2_sf_t *)xfs_dfork_dptr(); +} + +/* Are we dealing with dir3-style shortform (v5 + ftype)? */ +static inline int +xfs_sf_is_dir3(void) +{ + /* Good enough approximation for aboot: v5 filesystem ⇒ dir3 shortform */ + return xfs.is_v5; /* or (xfs.is_v5 && icore.di_version >= 3) if you prefer */ +} + + + + static inline xfs_agblock_t agino2agbno (xfs_agino_t agino) { @@ -293,30 +429,158 @@ btroot_maxrecs (void) (sizeof (xfs_bmbt_key_t) + sizeof (xfs_bmbt_ptr_t)); } + +/* Offsets of the data fork (literal area) in bytes. + * From XFS on-disk format documentation: + * - v2 inode: 0x64 (100) + * - v3 inode: 0xB0 (176) + */ +#define XFS_DINODE_DFORK_V2_OFF 100 +#define XFS_DINODE_DFORK_V3_OFF 176 + +static inline char * +xfs_dinode_data_fork(void) +{ + /* icore.di_version is already byte-swapped in the inode core */ + if (icore.di_version >= 3) + return (char *)inode + XFS_DINODE_DFORK_V3_OFF; + else + return (char *)inode + XFS_DINODE_DFORK_V2_OFF; +} + +/* Read inode from disk (v4/v5 compatible) */ + static int di_read (xfs_ino_t ino) { - xfs_agino_t agino; - xfs_agnumber_t agno; - xfs_agblock_t agbno; - xfs_daddr_t daddr; - int offset; + xfs_agino_t agino; + xfs_agnumber_t agno; + xfs_agblock_t agbno; + xfs_daddr_t daddr; + int offset; + + /* ------------------------------- + * Locate the inode on disk + * ------------------------------- */ + agno = ino2agno (ino); + agino = ino2agino (ino); + agbno = agino2agbno (agino); + offset = ino2offset (ino); + daddr = agb2daddr (agno, agbno); + + /* Read the raw inode into FSYS_BUF-backed "inode" */ + devread (daddr, offset * xfs.isize, xfs.isize, (char *)inode); + +#ifdef DEBUG_XFS_2 + printf("di_read(): ino=%llu agno=%u agino=%u offset=%d isize=%d\n", + (unsigned long long) ino, + (unsigned int) agno, + (unsigned int) agino, + offset, + xfs.isize); + + printf("di_read(): magic=0x%x version=%d format=%d size=%llu flags=0x%x is_v5=%d\n", + le16(inode->di_core.di_magic), + inode->di_core.di_version, + inode->di_core.di_format, + (unsigned long long) le64(inode->di_core.di_size), + le16(inode->di_core.di_flags), + xfs.is_v5); +#endif + + /* ------------------------------- + * Basic sanity checks + * ------------------------------- */ + if (le16(inode->di_core.di_magic) != XFS_DINODE_MAGIC) { + printf("XFS: bad inode magic 0x%x for ino %llu\n", + le16(inode->di_core.di_magic), + (unsigned long long) ino); + return 0; + } + + /* v1 / v2 / v3 inodes are acceptable */ + if (inode->di_core.di_version != 1 && + inode->di_core.di_version != 2 && + inode->di_core.di_version != 3) { + printf("XFS: unsupported inode version %d for ino %llu\n", + inode->di_core.di_version, + (unsigned long long) ino); + return 0; + } + + /* + * NOTE: For v5 (di_version == 3), the fields we use: + * - di_format + * - di_forkoff + * - di_size + * - di_nextents + * - di_nblocks + * - timestamps, uid/gid, etc. + * are in the SAME places as for v1/v2 on disk. + * + * The extra v3 inode fields live after di_gen, so we do not + * need to special-case them here for our bootloader. + */ + + /* ------------------------------- + * Prime BTREE root pointer (ptr0) + * ------------------------------- + * init_extents() will: + * - for EXTENTS: use inode->di_u.di_bmx + * - for BTREE: follow xfs.ptr0, etc. + * + * xfs.ptr0 is only valid / needed if di_format == BTREE. + */ + if (inode->di_core.di_format == XFS_DINODE_FMT_BTREE) { + char *dfork = xfs_dfork_dptr(); /* start of data fork (v2/v3 aware) */ + + xfs.ptr0 = *(xfs_bmbt_ptr_t *)(dfork + + sizeof(xfs_bmdr_block_t) + + btroot_maxrecs() * sizeof(xfs_bmbt_key_t)); - agno = ino2agno (ino); - agino = ino2agino (ino); - agbno = agino2agbno (agino); - offset = ino2offset (ino); - daddr = agb2daddr (agno, agbno); - devread (daddr, offset*xfs.isize, xfs.isize, (char *)inode); + +#ifdef DEBUG_XFS_2 + printf("di_read(): BTREE dir, ptr0 = 0x%llx\n", + (unsigned long long) le64(xfs.ptr0)); +#endif + + printf("di_read(): ino=%llu di_version=%u di_format=%u di_size=%llu di_nextents=%u is_v5=%d\n", + (unsigned long long)ino, + inode->di_core.di_version, + inode->di_core.di_format, + (unsigned long long)le64(inode->di_core.di_size), + le32(inode->di_core.di_nextents), + xfs.is_v5); - xfs.ptr0 = *(xfs_bmbt_ptr_t *) - (inode->di_u.di_c + sizeof(xfs_bmdr_block_t) - + btroot_maxrecs ()*sizeof(xfs_bmbt_key_t)); + } - return 1; + return 1; } +/* On-disk header size (NOT sizeof the C struct) */ +static inline int +xfs_sf_hdr_size(uint8_t i8count) +{ + int size = 2; /* count + i8count */ + + if (i8count) + size += sizeof(xfs_dir2_ino8_t); + else + size += sizeof(xfs_dir2_ino4_t); + + return size; +} + +static inline xfs_dir2_sf_entry_t * +xfs_sf_firstentry(xfs_dir2_sf_t *sf) +{ + xfs_dir2_sf_hdr_t *hdr = &sf->hdr; + return (xfs_dir2_sf_entry_t *)((char *)hdr + xfs_sf_hdr_size(hdr->i8count)); +} + + + static void init_extents (void) { @@ -325,8 +589,45 @@ init_extents (void) switch (icore.di_format) { case XFS_DINODE_FMT_EXTENTS: - xfs.xt = inode->di_u.di_bmx; - xfs.nextents = le32 (icore.di_nextents); + /* Extent records start at the beginning of the data fork */ + //xfs.xt = (xfs_bmbt_rec_32_t *)xfs_dfork_dptr(); + //xfs.nextents = le32(icore.di_nextents); + xfs_dinode_disk_t *dip = (xfs_dinode_disk_t *)inode; + + /* This is what older XFS used for data extents (still correct + * on non-NREXT64 filesystems and v1/v2 inodes). + */ + uint32_t ne32 = le32(icore.di_nextents); + + /* On v5 filesystems with NREXT64, the 8 bytes where older + * layouts had (di_pad[6] + di_flushiter) are now a union that + * may contain di_big_nextents (64-bit data extent count). + * + * Our xfs_dinode_disk_t still has di_pad[6] + di_flushiter, + * but that's exactly the same 8-byte region, so we can just + * reinterpret it as a BE64 big extent counter. + */ + uint64_t be_big = 0; + memcpy(&be_big, dip->di_pad, sizeof(be_big)); /* di_pad[0..5] + di_flushiter */ + uint64_t big = le64(be_big); + + uint32_t ne; + if (big != 0) { + /* NREXT64 case: di_big_nextents is valid and non-zero. + * We only need 32 bits in the bootloader; directories on + * a boot partition won't have anywhere near 2^32 extents. + */ + ne = (uint32_t)big; + } else { + /* Legacy case: use 32-bit di_nextents */ + ne = ne32; + } + + xfs.xt = (xfs_bmbt_rec_32_t *)xfs_dfork_dptr(); + xfs.nextents = ne; + + //printf("init_extents(): EXTENTS, nextents=%u (ne32=%u, big=%lu)\n", + // xfs.nextents, ne32, (unsigned long long)big); break; case XFS_DINODE_FMT_BTREE: ptr0 = xfs.ptr0; @@ -421,201 +722,849 @@ sf_ino (char *sfe, int namelen) #endif } + + + +/* Get parent inode from shortform header (v4 & v5). */ + static inline xfs_ino_t -sf_parent_ino (void) +sf_parent_ino(void) { -#ifdef __alpha__ - void *p = &inode->di_u.di_dir2sf.hdr.parent; - xfs_ino_t ino = 0; - if (xfs.i8param == 0) { - memcpy(&ino, p, sizeof(xfs_dir2_ino8_t)); - return le64(ino); - } else { - memcpy(&ino, p, sizeof(xfs_dir2_ino4_t)); - return le32(ino); - } -#else - /* unaligned access */ - return (xfs.i8param == 0) - ? le64(*(xfs_ino_t *)(&inode->di_u.di_dir2sf.hdr.parent)) - : le32(*(uint32_t *)(&inode->di_u.di_dir2sf.hdr.parent)); -#endif + xfs_dir2_sf_t *sf = xfs_sf_dir(); /* your helper */ + xfs_dir2_sf_hdr_t *hdr = &sf->hdr; + + return xfs_dir2_sf_get_ino(hdr, &hdr->parent); } + +static void debug_dump_sf_bytes(void) +{ + unsigned char *p = (unsigned char *)xfs_sf_dir(); + int i; + + printf("SF RAW DUMP (first 128 bytes):\n"); + + for (i = 0; i < 128; i++) { + printf("%02x ", p[i]); + if ((i % 16) == 15) + printf("\n"); + } + printf("\n"); +} static inline int roundup8 (int n) { return ((n+7)&~7); } + + +static int +xfs_count_dir3_entries(void) +{ + uint32_t magic; + int count = 0; + int has_ftype = xfs.is_v5; /* v5 + ftype=1 on your fs */ + + /* Start at the beginning of the block. */ + filepos = 0; + xfs.blkoff = 0; + + /* Read block magic. */ + if (xfs_read(&magic, sizeof(magic)) != sizeof(magic)) { + printf("xfs: failed to read dir3 block magic\n"); + return 0; + } + magic = le32(magic); + + if (magic != XFS_DIR3_DATA_MAGIC && magic != XFS_DIR3_BLOCK_MAGIC) { + printf("xfs: unsupported DIR3 block magic: 0x%x\n", magic); + return 0; + } + + /* Skip full v5 data header. */ + filepos = sizeof(xfs_dir3_data_hdr_t); + xfs.blkoff = filepos; + + while (xfs.blkoff < xfs.dirbsize) { + xfs_dir2_data_union_t u; + + /* Read first 4 bytes of the next slot. */ + if (xfs_read(&u, 4) != 4) + break; + + xfs.blkoff += 4; + + /* Free slot? (first 16 bits are freetag) */ + if (le16(u.unused.freetag) == XFS_DIR2_DATA_FREE_TAG) { + uint16_t len = le16(u.unused.length); + int skip = roundup8(len) - 4; /* 4 bytes already consumed */ + + if (skip < 0) + skip = 0; + + filepos += skip; + xfs.blkoff += skip; + continue; + } + + /* Used entry: read the rest of the fixed header: + * remaining 4 bytes of inumber + namelen (5 bytes total). + */ + if (xfs_read(((char *)&u) + 4, 5) != 5) + break; + + xfs.blkoff += 5; + + /* Now inumber + namelen are valid. */ + if (u.entry.namelen != 0) + count++; + + /* + * Full on-disk entry size: + * 8 bytes inumber + * 1 byte namelen + * namelen bytes name + * optional 1 byte ftype + * 2 bytes tag + * padded to 8 bytes + */ + { + int base = 8 + 1 + u.entry.namelen + 2; + if (has_ftype) + base += 1; + + int entsize = roundup8(base); + + /* We already consumed 9 bytes (8 + 1) via xfs_read() calls. */ + int skip = entsize - 9; + if (skip < 0) + skip = 0; + + filepos += skip; + xfs.blkoff += skip; + } + } + + /* Leave caller positioned at the first real data entry. */ + filepos = sizeof(xfs_dir3_data_hdr_t); + xfs.blkoff = filepos; + + return count; +} + static char * -next_dentry (xfs_ino_t *ino) +next_dentry(xfs_ino_t *ino) { - int namelen; - int toread; - static xfs_dir2_sf_entry_t *sfe; - char *name = NULL; -#ifdef DEBUG_XFS_2 - printf("next_dentry(): dirpos=%d dirmax=%d di_format=%d\n", - xfs.dirpos, xfs.dirmax, icore.di_format); + +#ifdef DEBUG_XFS + printf("next_dentry(): di_format=%d dirpos=%d dirmax=%d is_v5=%d\n", + icore.di_format, xfs.dirpos, xfs.dirmax, xfs.is_v5); #endif + /* Generic end-of-directory handling */ + if (icore.di_format == XFS_DINODE_FMT_LOCAL) { + /* shortform: dirpos includes -2 (.), -1 (..), then 0..dirmax-1 */ + if (xfs.dirpos >= xfs.dirmax) + return NULL; + } else { + /* block/leaf/btree formats */ + if (xfs.dirpos >= xfs.dirmax) { + + /* v5: we don’t chain leaf blocks yet → stop here */ + if (xfs.is_v5) { + return NULL; + } + + /* v4 (DIR2) leaf form: follow forward pointer */ + if (xfs.forw == 0) + return NULL; + + xfs.dablk = xfs.forw; + xfs_dabread(); + +#define h ((xfs_dir2_leaf_hdr_t *)dirbuf) + xfs.dirmax = le16(h->count) - le16(h->stale); + xfs.forw = le32(h->info.forw); +#undef h + xfs.dirpos = 0; + } + } - if (xfs.dirpos >= xfs.dirmax) { -#ifdef DEBUG_XFS - printf("next_dentry(): at end of directory (dirpos >= dirmax)\n"); -#endif + switch (icore.di_format) { + case XFS_DINODE_FMT_LOCAL: + return next_dentry_local(ino); + case XFS_DINODE_FMT_EXTENTS: + case XFS_DINODE_FMT_BTREE: + /* v4 → DIR2, v5 → DIR3 */ + return xfs.is_v5 ? next_dentry_dir3(ino) : next_dentry_dir2(ino); - if (xfs.forw == 0) - return NULL; - xfs.dablk = xfs.forw; - xfs_dabread (); -#define h ((xfs_dir2_leaf_hdr_t *)dirbuf) - xfs.dirmax = le16 (h->count) - le16 (h->stale); - xfs.forw = le32 (h->info.forw); -#undef h - xfs.dirpos = 0; - } + default: + printf("xfs: unsupported inode format %d\n", icore.di_format); + return NULL; + } +} + + +/* ------------------------------------------------------------ + * SHORTFORM DIRECTORY (LOCAL FORMAT) + * ------------------------------------------------------------ */ +static char * +first_dentry_local(xfs_ino_t *ino) +{ + xfs_dir2_sf_t *sf = xfs_sf_dir(); + xfs_dir2_sf_hdr_t *hdr = &sf->hdr; + + xfs.forw = 0; + xfs.dirmax = hdr->count; /* number of real entries (no . / ..) */ + printf("sf->hdr.count=%d\n",xfs.dirmax); + //debug_dump_sf_bytes(); + /* Keep i8param for old helpers, though we no longer rely on it here. */ + xfs.i8param = hdr->i8count ? 0 : 4; + + /* + * dirpos: + * -2 = "." + * -1 = ".." + * 0 = first real entry in sf->list[] + */ + xfs.dirpos = -2; - switch (icore.di_format) { - case XFS_DINODE_FMT_LOCAL: - switch (xfs.dirpos) { - case -2: - *ino = 0; - name = "."; - namelen = 1; - break; - case -1: /* ".." */ - *ino = sf_parent_ino (); - name = ".."; - namelen = 2; - sfe = (xfs_dir2_sf_entry_t *) - (inode->di_u.di_c - + sizeof(xfs_dir2_sf_hdr_t) - - xfs.i8param); - break; - default: - namelen = sfe->namelen; - *ino = sf_ino ((char *)sfe, namelen); - name = sfe->name; - name[namelen] = 0; - sfe = (xfs_dir2_sf_entry_t *) - ((char *)sfe + namelen + 11 - xfs.i8param); - } - break; - case XFS_DINODE_FMT_BTREE: - case XFS_DINODE_FMT_EXTENTS: -#define dau ((xfs_dir2_data_union_t *)dirbuf) - for (;;) { - if (xfs.blkoff >= xfs.dirbsize) { - xfs.blkoff = sizeof(xfs_dir2_data_hdr_t); - filepos &= ~(xfs.dirbsize - 1); - filepos |= xfs.blkoff; - } - - xfs_read(dirbuf, 4); /* read freetag/length/tag */ - xfs.blkoff += 4; - - if (dau->unused.freetag == XFS_DIR2_DATA_FREE_TAG) { - toread = roundup8(le16(dau->unused.length)) - 4; - xfs.blkoff += toread; - filepos += toread; - continue; - } - break; - } - - xfs_read((char *)dirbuf + 4, 5); - *ino = le64(dau->entry.inumber); - namelen = dau->entry.namelen; - /* Some entries are free/invalid: namelen==0 or ino==0. - * Skip them and move to the next directory position. - */ - if (namelen == 0 || *ino == 0) { #ifdef DEBUG_XFS_2 - printf("next_dentry(): skipping nameless/invalid entry (ino=%llu namelen=%d)\n", - (unsigned long long)*ino, namelen); + printf("first_dentry(): ino=%llu version=%d format=%d size=%lld is_v5=%d\n", + (unsigned long long)*ino, + icore.di_version, + icore.di_format, + (long long)le64(icore.di_size), + xfs.is_v5); + printf("sf->hdr.count = %u\n", hdr->count); #endif - ++xfs.dirpos; - return next_dentry(ino); - } -#undef dau - toread = roundup8 (namelen + 11) - 9; - xfs_read (dirbuf, toread); - name = (char *)dirbuf; - name[namelen] = 0; - xfs.blkoff += toread + 5; - } - ++xfs.dirpos; -#ifdef DEBUG_XFS + return next_dentry_local(ino); +} + - if (name) - printf("next_dentry(): returning name='%s' ino=%llu (dirpos now %d)\n", - name, (unsigned long long)*ino, xfs.dirpos); - else - printf("next_dentry(): returning NULL name (dirpos now %d)\n", - xfs.dirpos); +static inline char * +xfs_sf_name(xfs_dir2_sf_entry_t *sfe) +{ + /* For both v4 and v5 shortform dirs, the name starts here. */ + return (char *)sfe->name; +} -#endif - return name; +/* Compute inumber for a shortform entry, from the same layout. */ + +static inline xfs_ino_t +xfs_sf_ino_from_entry(xfs_dir2_sf_entry_t *sfe) +{ + xfs_dir2_sf_t *sf = xfs_sf_dir(); + xfs_dir2_sf_hdr_t *hdr = &sf->hdr; + uint8_t *p; + xfs_ino_t ino = 0; + + /* Start at end of name */ + p = (uint8_t *)sfe->name + sfe->namelen; + + /* On v5/ftype=1 shortform dirs, name[namelen] is ftype, so skip it. */ + if (xfs.is_v5) + p++; + + if (hdr->i8count) { + /* 8-byte inode */ + memcpy(&ino, p, sizeof(xfs_dir2_ino8_t)); + ino = le64(ino) & 0x00ffffffffffffffULL; + } else { + uint32_t v32; + memcpy(&v32, p, sizeof(xfs_dir2_ino4_t)); + ino = le32(v32); + } + + return ino; +} + +/* Size of one shortform entry in a v4 (no ftype) shortform dir. + * + * On disk the layout is: + * [0] uint8_t namelen + * [1..] xfs_dir2_sf_off_t offset + * [..] name[namelen] + * [..] inode (4 or 8 bytes) + */ +/* v2 shortform entry size (no ftype stored) */ +static inline int +xfs_dir2_sf_entsize(xfs_dir2_sf_hdr_t *hdr, int namelen) +{ + int count = 0; + + count += 1; /* namelen */ + count += sizeof(xfs_dir2_sf_off_t); /* offset */ + count += namelen; /* name bytes */ + count += hdr->i8count + ? (int)sizeof(xfs_dir2_ino8_t) /* 8-byte inode (#s) */ + : (int)sizeof(xfs_dir2_ino4_t); /* 4-byte inode (#s) */ + + return count; +} +/* v5 shortform entry size: v2 + 1 byte for file type */ +static int +xfs_dir3_sf_entsize(xfs_dir2_sf_hdr_t *hdr, int namelen) +{ + return xfs_dir2_sf_entsize(hdr, namelen) + 1; +} + + + +/* Size of one shortform entry. + * + * On disk (v4): + * [0] u8 namelen + * [1..2] xfs_dir2_sf_off_t offset + * [3..] name[namelen] + * [..] inode (8 bytes for our purposes) + * + * On disk (v5 / ftype=1): + * [0] u8 namelen + * [1..2] xfs_dir2_sf_off_t offset + * [3..] name[namelen] + * [..] u8 ftype + * [..] inode (8 bytes) + */ + + +/* Version-agnostic helper */ +static inline int +xfs_sf_entsize(xfs_dir2_sf_hdr_t *hdr, int namelen) +{ + return xfs.is_v5 ? xfs_dir3_sf_entsize(hdr, namelen) + : xfs_dir2_sf_entsize(hdr, namelen); +} +/* Get parent inode from shortform header */ +static inline xfs_ino_t +sf_parent_ino_sf(void) +{ + xfs_dir2_sf_t *sf = xfs_sf_dir(); + xfs_dir2_sf_hdr_t *hdr = &sf->hdr; + uint8_t *p = (uint8_t *)&hdr->parent; + + if (hdr->i8count) + return (xfs_ino_t)le64(*(xfs_ino_t *)p); + else + return (xfs_ino_t)le32(*(uint32_t *)p); +} + + + +/* Decode an xfs_dir2_inou_t -> xfs_ino_t (v4/v5) */ +static inline xfs_ino_t +xfs_dir2_sf_get_ino(xfs_dir2_sf_hdr_t *hdr, xfs_dir2_inou_t *from) +{ + xfs_ino_t ino = 0; + + if (hdr->i8count) { + /* 64-bit inode stored big-endian in from->i8.i[8] */ + memcpy(&ino, &from->i8, sizeof(xfs_dir2_ino8_t)); + ino = le64(ino); /* swap from disk BE to CPU */ + ino &= 0x00ffffffffffffffULL; /* top byte reserved */ + } else { + uint32_t v = 0; + memcpy(&v, &from->i4, sizeof(xfs_dir2_ino4_t)); + ino = le32(v); + } + return ino; +} + +static char * +next_dentry_local(xfs_ino_t *ino) +{ + static xfs_dir2_sf_entry_t *sfe; + static char sf_name_buf[256]; /* XFS max namelen is 255 */ + + xfs_dir2_sf_t *sf = xfs_sf_dir(); + xfs_dir2_sf_hdr_t *hdr = &sf->hdr; + char *name = NULL; + int namelen; + + if (xfs.dirpos >= xfs.dirmax && xfs.dirpos >= 0) + return NULL; + + switch (xfs.dirpos) { + case -2: /* "." (synthetic) */ + *ino = 0; /* or current inode, depending on your design */ + name = "."; + namelen = 1; + break; + + case -1: /* ".." (from header parent) */ + *ino = sf_parent_ino(); /* using hdr + i8count */ + name = ".."; + namelen = 2; + + /* First on-disk entry starts here */ + sfe = xfs_sf_firstentry(sf); + break; + + default: + namelen = sfe->namelen; + if (namelen > 255) + namelen = 255; + + *ino = xfs_sf_ino_from_entry(sfe); + + memcpy(sf_name_buf, xfs_sf_name(sfe), namelen); + sf_name_buf[namelen] = '\0'; + name = sf_name_buf; + + /* Advance to the next shortform entry */ + sfe = (xfs_dir2_sf_entry_t *)((char *)sfe + + xfs_sf_entsize(hdr, sfe->namelen)); + break; + } + + xfs.dirpos++; + return name; +} + +static char * +first_dentry_dir3(xfs_ino_t *ino) +{ + xfs.forw = 0; + xfs.dirpos = 0; + + /* Count entries and position at first DIR3 data entry */ + xfs.dirmax = xfs_count_dir3_entries(); + if (xfs.dirmax <= 0) + return NULL; + + return next_dentry_dir3(ino); +} + + + +static char * +next_dentry_dir3(xfs_ino_t *ino) +{ + int has_ftype = xfs.is_v5; /* ftype present on your fs */ + +#define d2u ((xfs_dir2_data_union_t *)dirbuf) + + for (;;) { + /* At end of this DIR3 data block? Go to next block. */ + if (xfs.blkoff >= xfs.dirbsize) { + uint32_t magic; + + /* Align filepos down to the start of this block. */ + filepos &= ~(xfs.dirbsize - 1); + + /* Read block magic. */ + if (xfs_read(&magic, sizeof(magic)) != sizeof(magic)) { + printf("xfs: read error in DIR3 block header\n"); + return NULL; + } + magic = le32(magic); + + if (magic != XFS_DIR3_DATA_MAGIC && + magic != XFS_DIR3_BLOCK_MAGIC) { + printf("xfs: invalid DIR3 block magic: 0x%x\n", magic); + return NULL; + } + + /* Skip full v5 header. */ + xfs.blkoff = sizeof(xfs_dir3_data_hdr_t); + filepos |= xfs.blkoff; + } + /* Stop when we reach the tail */ +if (xfs.blkoff + sizeof(xfs_dir2_block_tail_t) >= xfs.dirbsize) + return NULL; + +/* Read first 4 bytes of the next slot */ +if (xfs_read(dirbuf, 4) != 4) { + /* This is end-of-dir, not an error */ + return NULL; } + xfs.blkoff += 4; + + /* Free entry? */ + if (le16(d2u->unused.freetag) == XFS_DIR2_DATA_FREE_TAG) { + uint16_t len = le16(d2u->unused.length); + int skip = roundup8(len) - 4; /* 4 already read */ + + if (skip < 0) + skip = 0; + + filepos += skip; + xfs.blkoff += skip; + continue; /* look at next slot */ + } + + /* Used entry found. */ + break; + } + + /* Read rest of fixed header: remaining 4 bytes of inumber + namelen. */ + if (xfs_read((char *)dirbuf + 4, 5) != 5) { + printf("xfs: short read in dir3 entry header\n"); + return NULL; + } + xfs.blkoff += 5; + + *ino = le64(d2u->entry.inumber); + int namelen = d2u->entry.namelen; + + /* Skip bogus entries. */ + if (*ino == 0 || namelen == 0) { + xfs.dirpos++; + return next_dentry_dir3(ino); + } + + /* Compute remaining size of this entry. */ + { + int base = 8 + 1 + namelen + 2; + if (has_ftype) + base += 1; + + int entsize = roundup8(base); + int toread = entsize - 9; /* 9 bytes already consumed */ + + if (toread < 0) + toread = 0; + + if (toread && xfs_read(dirbuf, toread) != toread) { + printf("xfs: short read in dir3 filename\n"); + return NULL; + } + + xfs.blkoff += toread; + } + /* dirbuf now starts with the filename bytes. */ + char *name = (char *)dirbuf; + name[namelen] = 0; /* overwrite filetype / padding / tag byte */ + + xfs.dirpos++; + +#undef d2u + return name; +} + + +/* + * Initialize reading entries from a classic XFS DIR2 directory + * (XFS v4, crc=0). Handles: + * - block format + * - leaf1 / leafn format + * + * Returns the first entry via next_dentry_dir2(). + */ static char * -first_dentry (xfs_ino_t *ino) +first_dentry_dir2(xfs_ino_t *ino) { - xfs.forw = 0; + xfs.forw = 0; + filepos = 0; + #ifdef DEBUG_XFS_2 - printf("first_dentry(): di_format=%d\n", icore.di_format); + printf("first_dentry_dir2(): starting (di_format=%d)\n", + icore.di_format); #endif - switch (icore.di_format) { - case XFS_DINODE_FMT_LOCAL: - xfs.dirmax = inode->di_u.di_dir2sf.hdr.count; - xfs.i8param = inode->di_u.di_dir2sf.hdr.i8count ? 0 : 4; - xfs.dirpos = -2; + + /* ---------------------------------------------------------- + * SHORTFORM (small) directory + * ---------------------------------------------------------- */ + if (icore.di_format == XFS_DINODE_FMT_LOCAL) + { + xfs.dirmax = inode->di_u.di_dir2sf.hdr.count; + xfs.i8param = inode->di_u.di_dir2sf.hdr.i8count ? 0 : 4; + xfs.dirpos = -2; /* ".", "..", then real entries */ + #ifdef DEBUG_XFS_2 - printf("first_dentry(): LOCAL dir, dirmax=%d i8param=%d\n", - xfs.dirmax, xfs.i8param); + printf("DIR2 shortform: dirmax=%d i8param=%d\n", + xfs.dirmax, xfs.i8param); #endif - break; - case XFS_DINODE_FMT_EXTENTS: - case XFS_DINODE_FMT_BTREE: - filepos = 0; - xfs_read (dirbuf, sizeof(xfs_dir2_data_hdr_t)); - if (((xfs_dir2_data_hdr_t *)dirbuf)->magic == le32(XFS_DIR2_BLOCK_MAGIC)) { -#define tail ((xfs_dir2_block_tail_t *)dirbuf) - filepos = xfs.dirbsize - sizeof(*tail); - xfs_read (dirbuf, sizeof(*tail)); - xfs.dirmax = le32 (tail->count) - le32 (tail->stale); -#undef tail - } else { - xfs.dablk = (1ULL << 35) >> xfs.blklog; -#define h ((xfs_dir2_leaf_hdr_t *)dirbuf) -#define n ((xfs_da_intnode_t *)dirbuf) - for (;;) { - xfs_dabread (); - if ((n->hdr.info.magic == le16(XFS_DIR2_LEAFN_MAGIC)) - || (n->hdr.info.magic == le16(XFS_DIR2_LEAF1_MAGIC))) { - xfs.dirmax = le16 (h->count) - le16 (h->stale); - xfs.forw = le32 (h->info.forw); - break; - } - xfs.dablk = le32 (n->btree[0].before); - } -#undef n -#undef h - } - xfs.blkoff = sizeof(xfs_dir2_data_hdr_t); - filepos = xfs.blkoff; - xfs.dirpos = 0; + return next_dentry_dir2(ino); + } + + /* ---------------------------------------------------------- + * EXTENTS or BTREE directory + * Read first data block header + * ---------------------------------------------------------- */ + if (xfs_read(dirbuf, sizeof(xfs_dir2_data_hdr_t)) != sizeof(xfs_dir2_data_hdr_t)) { + printf("xfs: failed to read dir2 data header\n"); + return NULL; + } + + uint32_t magic = le32(((xfs_dir2_data_hdr_t *)dirbuf)->magic); + #ifdef DEBUG_XFS_2 - printf("first_dentry(): DIR2 dir, dirmax=%d dirbsize=%d blkoff=%d\n", - xfs.dirmax, xfs.dirbsize, xfs.blkoff); + printf("DIR2 header magic = 0x%x\n", magic); #endif - } - return next_dentry (ino); + /* ---------------------------------------------------------- + * CASE 1: BLOCK-FORMAT DIRECTORY (single block) + * ---------------------------------------------------------- */ + if (magic == XFS_DIR2_BLOCK_MAGIC) + { + xfs_dir2_block_tail_t *tail; + + /* Tail is located at end of block */ + filepos = xfs.dirbsize - sizeof(*tail); + + if (xfs_read(dirbuf, sizeof(*tail)) != sizeof(*tail)) { + printf("xfs: failed to read dir2 block tail\n"); + return NULL; + } + + tail = (xfs_dir2_block_tail_t *)dirbuf; + xfs.dirmax = le32(tail->count) - le32(tail->stale); + +#ifdef DEBUG_XFS_2 + printf("DIR2 block-format: dirmax=%d\n", xfs.dirmax); +#endif + } + + /* ---------------------------------------------------------- + * CASE 2: LEAF1 / LEAFN directory + * ---------------------------------------------------------- */ + else + { + /* Starting dablk for leaf / node search, per XFS rules */ + xfs.dablk = (1ULL << 35) >> xfs.blklog; + + for (;;) + { + xfs_dabread(); + + xfs_dir2_leaf_hdr_t *lh = (xfs_dir2_leaf_hdr_t *)dirbuf; + xfs_da_intnode_t *n = (xfs_da_intnode_t *)dirbuf; + + uint16_t m = le16(n->hdr.info.magic); + +#ifdef DEBUG_XFS_2 + printf("DIR2 leaf/node scan: magic=0x%x\n", m); +#endif + + if (m == XFS_DIR2_LEAF1_MAGIC || m == XFS_DIR2_LEAFN_MAGIC) + { + xfs.dirmax = le16(lh->count) - le16(lh->stale); + xfs.forw = le32(lh->info.forw); + +#ifdef DEBUG_XFS_2 + printf("DIR2 leaf-format: dirmax=%d forw=%u\n", + xfs.dirmax, xfs.forw); +#endif + break; + } + + /* Follow B-tree "before" pointer */ + xfs.dablk = le32(n->btree[0].before); + } + } + + /* -------------------------------------- + * Initialize data-block scan position + * -------------------------------------- */ + xfs.blkoff = sizeof(xfs_dir2_data_hdr_t); + filepos = xfs.blkoff; + xfs.dirpos = 0; + +#ifdef DEBUG_XFS_2 + printf("DIR2 start: blkoff=%d dirpos=%d dirmax=%d\n", + xfs.blkoff, xfs.dirpos, xfs.dirmax); +#endif + + return next_dentry_dir2(ino); +} + +static char * +next_dentry_dir2(xfs_ino_t *ino) +{ + int namelen; + int toread; + char *name; + +#define dau ((xfs_dir2_data_union_t *)dirbuf) + + /* Loop until we find a valid (non-free) entry */ + for (;;) { + + /* -------------------------------------------------------- + * If we hit the end of a DIR2 block, move to the next one + * -------------------------------------------------------- */ + if (xfs.blkoff >= xfs.dirbsize) { + + uint32_t magic; + + /* Align filepos to start of new block */ + filepos &= ~(xfs.dirbsize - 1); + + /* Read block magic */ + if (xfs_read(&magic, sizeof(magic)) != sizeof(magic)) { + printf("xfs: read error in DIR2 block header\n"); + return NULL; + } + magic = le32(magic); + + /* Valid DIR2 magic values */ + if (magic != XFS_DIR2_DATA_MAGIC && + magic != XFS_DIR2_BLOCK_MAGIC) + { + printf("xfs: invalid DIR2 block magic: 0x%x\n", magic); + return NULL; + } + + /* Reset pointer just after header */ + xfs.blkoff = sizeof(xfs_dir2_data_hdr_t); + filepos |= xfs.blkoff; + } + + /* -------------------------------------------------------- + * Read first 4 bytes of entry + * ▪ free-entry: dau->unused.freetag == FREE_TAG + * ▪ used-entry: beginning of inode number (LSB) + * -------------------------------------------------------- */ + if (xfs_read(dirbuf, 4) != 4) { + printf("xfs: short read in dir2 entry\n"); + return NULL; + } + xfs.blkoff += 4; + + /* Free entry? */ + if (dau->unused.freetag == XFS_DIR2_DATA_FREE_TAG) { + + uint32_t len = le16(dau->unused.length); + + /* Length includes the 4 bytes we already consumed */ + toread = roundup8(len) - 4; + + xfs.blkoff += toread; + filepos += toread; + + continue; /* skip free entry */ + } + + /* Otherwise this is a DIR2 data entry */ + break; + } + + /* -------------------------------------------------------- + * Read rest of entry header (5 bytes): + * - namelen (1) + * - name[0] (1) + * - tag (2) + * - padding (1) + * -------------------------------------------------------- */ + if (xfs_read((char *)dirbuf + 4, 5) != 5) { + printf("xfs: short read in dir2 entry header\n"); + return NULL; + } + + *ino = le64(dau->entry.inumber); + namelen = dau->entry.namelen; + + /* Skip bogus entries */ + if (*ino == 0 || namelen == 0) { + xfs.dirpos++; + return next_dentry_dir2(ino); + } + + /* -------------------------------------------------------- + * Read filename + padding + * -------------------------------------------------------- */ + toread = roundup8(namelen + 11) - 9; + + if (xfs_read(dirbuf, toread) != toread) { + printf("xfs: short read in dir2 filename\n"); + return NULL; + } + + name = (char *)dirbuf; + name[namelen] = 0; + + /* Advance */ + xfs.blkoff += toread + 5; + xfs.dirpos++; + +#undef dau + + return name; +} + +/* Dispatcher for first directory entry. + * Chooses between: + * - local directory (shortform) + * - dir2 block/leaf formats (XFS v4) + * - dir3 block/leaf formats (XFS v5) + */ + + +static char * +first_dentry(xfs_ino_t *ino) +{ + xfs.forw = 0; + + printf("first_dentry(): ino=%lu version=%d format=%d size=%lu is_v5=%d\n", + (unsigned long long)*ino, + inode->di_core.di_version, + inode->di_core.di_format, + (unsigned long long) le64(inode->di_core.di_size), + xfs.is_v5); + switch (icore.di_format) + { + /* ---------------------------------------------------- + * SHORTFORM (LOCAL) DIRECTORIES + * ---------------------------------------------------- */ + case XFS_DINODE_FMT_LOCAL: + return first_dentry_local(ino); + + /* ---------------------------------------------------- + * BLOCK / LEAF / B-TREE DIRECTORIES + * ---------------------------------------------------- */ + case XFS_DINODE_FMT_EXTENTS: + case XFS_DINODE_FMT_BTREE: + { + uint32_t magic; + + filepos = 0; + + /* Read the directory block header magic */ + if (xfs_read(&magic, sizeof(magic)) != sizeof(magic)) { + printf("xfs: failed to read directory block header\n"); + return NULL; + } + magic = le32(magic); + +#ifdef DEBUG_XFS_2 + printf("first_dentry(): directory block magic = 0x%x\n", magic); +#endif + + /* ----------------------------- + * DIR3 (XFS v5 CRC-enabled) + * ----------------------------- */ + if (magic == XFS_DIR3_DATA_MAGIC || + magic == XFS_DIR3_BLOCK_MAGIC || + magic == XFS_DIR3_LEAFN_MAGIC || + magic == XFS_DIR3_LEAF1_MAGIC) + { + return first_dentry_dir3(ino); + } + + /* ----------------------------- + * DIR2 (Classic XFS v4) + * ----------------------------- */ + if (magic == XFS_DIR2_DATA_MAGIC || + magic == XFS_DIR2_BLOCK_MAGIC || + magic == XFS_DIR2_LEAFN_MAGIC || + magic == XFS_DIR2_LEAF1_MAGIC) + { + return first_dentry_dir2(ino); + } + + printf("xfs: unsupported directory magic 0x%x\n", magic); + return NULL; + } + + default: + printf("xfs: unsupported inode format %d\n", icore.di_format); + return NULL; + } } @@ -642,17 +1591,22 @@ xfs_mount(long cons_dev, long p_offset, long quiet) } else { unsigned int ver = le16(super.sb_versionnum) & XFS_SB_VERSION_NUMBITS; - /* Explicitly refuse XFS v5 (CRC-enabled) filesystems */ - if (ver == XFS_SB_VERSION_5) { - printf("xfs_mount: XFS v5 (CRC-enabled) filesystem is not supported by aboot\n"); - return -1; - } + /* Accept XFS v4 and v5 */ + if (ver == XFS_SB_VERSION_5) { +#ifdef DEBUG + printf("xfs_mount: Detected XFS v5 filesystem (CRC-enabled)\n"); +#endif + xfs.is_v5 = 1; /* new flag */ + printf("xfs_mount: XFS v5 (CRC-enabled) filesystem is not supported by aboot iteration=002\n"); + } + else if (ver == XFS_SB_VERSION_4) { + xfs.is_v5 = 0; + } + else { + printf("xfs_mount: unsupported XFS version %u\n", ver); + return -1; + } - /* Only XFS v4 is supported */ - if (ver != XFS_SB_VERSION_4) { - printf("xfs_mount: unsupported XFS version %u\n", ver); - return -1; - } } @@ -690,12 +1644,12 @@ xfs_read (void *buf, long len) xfs_filblks_t xadlen; int toread, startpos, endpos; - if (icore.di_format == XFS_DINODE_FMT_LOCAL) { - memmove (buf, inode->di_u.di_c + filepos, len); - filepos += len; - return len; - } - + if (icore.di_format == XFS_DINODE_FMT_LOCAL) { + char *dptr = xfs_dfork_dptr(); /* start of literal data/shortform dir */ + memmove(buf, dptr + filepos, len); + filepos += len; + return len; + } startpos = filepos; endpos = filepos + len; endofprev = (xfs_fileoff_t)-1; diff --git a/fs/xfs.h b/fs/xfs.h index e6f25b6..7f65e09 100644 --- a/fs/xfs.h +++ b/fs/xfs.h @@ -63,8 +63,20 @@ typedef uint64_t xfs_fsblock_t; /* blockno in filesystem (agno|ag typedef uint64_t xfs_fileoff_t; /* block number in a file */ typedef uint64_t xfs_filblks_t; /* number of blocks in a file */ +#define XFS_DIR3_DATA_MAGIC 0x58444433 /* 'XDD3' - dir3 data (v5) */ +#define XFS_DIR2_BLOCK_MAGIC 0x58444242 /* 'XDBB' - dir2 block dirs */ +#define XFS_DIR3_BLOCK_MAGIC 0x58444233 /* 'XDB3' - dir3 block dirs */ +#define XFS_DIR2_DATA_MAGIC 0x58443244 /* 'XD2D' - dir2 data (v4) */ -/* those are from xfs_sb.h */ +/* XFS directory block magic numbers */ + +/* ----- DIR2 (classic, v4) ----- */ +#define XFS_DIR2_LEAF1_MAGIC 0xd2f1 /* leaf block (version 2), type 1 */ +#define XFS_DIR2_LEAFN_MAGIC 0xd2ff /* leaf block (version 2), node type */ + +/* ----- DIR3 (CRC-enabled, v5) ----- */ +#define XFS_DIR3_LEAF1_MAGIC 0x3df1 /* leaf block (version 3), type 1 */ +#define XFS_DIR3_LEAFN_MAGIC 0x3dff /* leaf block (version 3), node type */ #define XFS_SB_MAGIC 0x58465342 /* 'XFSB'*/ #define XFS_SB_VERSION_1 1 /* 5.3, 6.0.1, 6.1 */ @@ -470,8 +482,10 @@ typedef struct xfs_dir2_sf { /* those are from xfs_dinode.h */ + #define XFS_DINODE_VERSION_1 1 #define XFS_DINODE_VERSION_2 2 +#define XFS_DINODE_VERSION_3 3 /* v5 filesystem, CRC inode */ #define XFS_DINODE_MAGIC 0x494e /* 'IN' */ /* From 3c9cccc0062a108b8383139b3ed96a0f4de56270 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Sat, 6 Dec 2025 19:44:42 +0100 Subject: [PATCH 6/7] xfs: fix v5 (CRC) directory parsing Fix shortform entry layout, ftype handling, and inode offsets, and stop DIR3 parsing at the data region. Prevents corrupted names, spurious entries, and lookup failures on XFS v5 filesystems. Signed-off-by: Magnus Lindholm --- fs/xfs.c | 100 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 62 insertions(+), 38 deletions(-) diff --git a/fs/xfs.c b/fs/xfs.c index d3b6631..fddea65 100644 --- a/fs/xfs.c +++ b/fs/xfs.c @@ -110,6 +110,18 @@ static inline uint64_t __swab64(uint64_t x) } +typedef struct xfs_dir2_leaf_entry { + uint32_t hashval; /* hash of name */ + uint32_t address; /* block/offset of data entry */ +} xfs_dir2_leaf_entry_t; + +typedef struct xfs_dir3_block_tail { + uint32_t bestcount; /* number of bestfree slots */ + uint32_t count; /* number of leaf entries */ + uint32_t stale; /* number of stale leaf entries */ +} xfs_dir3_block_tail_t; + + static int devread(long sector, long start, long length, void *buf) { @@ -758,19 +770,20 @@ roundup8 (int n) } - static int xfs_count_dir3_entries(void) { uint32_t magic; int count = 0; - int has_ftype = xfs.is_v5; /* v5 + ftype=1 on your fs */ + int has_ftype = xfs.is_v5; + xfs_dir3_block_tail_t tail; + int data_end; - /* Start at the beginning of the block. */ - filepos = 0; + /* Start at beginning of block */ + filepos = 0; xfs.blkoff = 0; - /* Read block magic. */ + /* Read block magic */ if (xfs_read(&magic, sizeof(magic)) != sizeof(magic)) { printf("xfs: failed to read dir3 block magic\n"); return 0; @@ -782,61 +795,73 @@ xfs_count_dir3_entries(void) return 0; } - /* Skip full v5 data header. */ + /* Skip v5 data header */ filepos = sizeof(xfs_dir3_data_hdr_t); xfs.blkoff = filepos; - while (xfs.blkoff < xfs.dirbsize) { + /* --- Find block tail to know where the leaf area starts --- */ + { + long saved_pos = filepos; + long saved_off = xfs.blkoff; + + /* Tail is at end of block */ + filepos = xfs.dirbsize - sizeof(xfs_dir3_block_tail_t); + if (xfs_read(&tail, sizeof(tail)) != sizeof(tail)) { + printf("xfs: failed to read dir3 block tail\n"); + return 0; + } + + /* Convert endianness */ + uint32_t leaf_count = le32(tail.count); + uint32_t leaf_bytes = leaf_count * sizeof(xfs_dir2_leaf_entry_t); + + /* Leaf array starts just before the tail */ + data_end = xfs.dirbsize - sizeof(xfs_dir3_block_tail_t) - leaf_bytes; + + /* Restore position to start scanning data entries */ + filepos = saved_pos; + xfs.blkoff = saved_off; + } + + /* Walk entries only within [header .. data_end) */ + while (xfs.blkoff < data_end) { xfs_dir2_data_union_t u; - /* Read first 4 bytes of the next slot. */ + /* Read first 4 bytes of slot */ if (xfs_read(&u, 4) != 4) break; xfs.blkoff += 4; - /* Free slot? (first 16 bits are freetag) */ + /* Free entry? */ if (le16(u.unused.freetag) == XFS_DIR2_DATA_FREE_TAG) { uint16_t len = le16(u.unused.length); - int skip = roundup8(len) - 4; /* 4 bytes already consumed */ - - if (skip < 0) - skip = 0; + int skip = roundup8(len) - 4; /* 4 already read */ + if (skip < 0) skip = 0; filepos += skip; xfs.blkoff += skip; continue; } - /* Used entry: read the rest of the fixed header: - * remaining 4 bytes of inumber + namelen (5 bytes total). - */ + /* Used entry: read remaining 4 bytes of inumber + namelen (5 bytes total) */ if (xfs_read(((char *)&u) + 4, 5) != 5) break; xfs.blkoff += 5; - /* Now inumber + namelen are valid. */ if (u.entry.namelen != 0) count++; - /* - * Full on-disk entry size: - * 8 bytes inumber - * 1 byte namelen - * namelen bytes name - * optional 1 byte ftype - * 2 bytes tag - * padded to 8 bytes - */ + /* Compute full entry size */ { int base = 8 + 1 + u.entry.namelen + 2; if (has_ftype) - base += 1; + base += 1; /* ftype byte */ int entsize = roundup8(base); - /* We already consumed 9 bytes (8 + 1) via xfs_read() calls. */ + /* We already consumed 9 bytes: 8 (ino) + 1 (namelen) */ int skip = entsize - 9; if (skip < 0) skip = 0; @@ -846,7 +871,7 @@ xfs_count_dir3_entries(void) } } - /* Leave caller positioned at the first real data entry. */ + /* Leave caller positioned at start of data region */ filepos = sizeof(xfs_dir3_data_hdr_t); xfs.blkoff = filepos; @@ -918,7 +943,6 @@ first_dentry_local(xfs_ino_t *ino) xfs.forw = 0; xfs.dirmax = hdr->count; /* number of real entries (no . / ..) */ printf("sf->hdr.count=%d\n",xfs.dirmax); - //debug_dump_sf_bytes(); /* Keep i8param for old helpers, though we no longer rely on it here. */ xfs.i8param = hdr->i8count ? 0 : 4; @@ -930,8 +954,8 @@ first_dentry_local(xfs_ino_t *ino) */ xfs.dirpos = -2; -#ifdef DEBUG_XFS_2 - printf("first_dentry(): ino=%llu version=%d format=%d size=%lld is_v5=%d\n", +#ifdef DEBUG_XFS + printf("first_dentry(): ino=%lu version=%d format=%d size=%ld is_v5=%d\n", (unsigned long long)*ino, icore.di_version, icore.di_format, @@ -1499,13 +1523,14 @@ static char * first_dentry(xfs_ino_t *ino) { xfs.forw = 0; - +#ifdef DEBUG_XFS printf("first_dentry(): ino=%lu version=%d format=%d size=%lu is_v5=%d\n", (unsigned long long)*ino, inode->di_core.di_version, inode->di_core.di_format, (unsigned long long) le64(inode->di_core.di_size), xfs.is_v5); +#endif switch (icore.di_format) { /* ---------------------------------------------------- @@ -1593,11 +1618,10 @@ xfs_mount(long cons_dev, long p_offset, long quiet) /* Accept XFS v4 and v5 */ if (ver == XFS_SB_VERSION_5) { -#ifdef DEBUG +#ifdef DEBUG_XFS printf("xfs_mount: Detected XFS v5 filesystem (CRC-enabled)\n"); #endif xfs.is_v5 = 1; /* new flag */ - printf("xfs_mount: XFS v5 (CRC-enabled) filesystem is not supported by aboot iteration=002\n"); } else if (ver == XFS_SB_VERSION_4) { xfs.is_v5 = 0; @@ -1627,7 +1651,7 @@ xfs_mount(long cons_dev, long p_offset, long quiet) (sizeof (xfs_bmbt_key_t) + sizeof (xfs_bmbt_ptr_t))) * sizeof(xfs_bmbt_key_t) + sizeof(xfs_btree_block_t); -#ifdef DEBUG +#ifdef DEBUG_XFS printf("XFS: version = %d\n",le16(super.sb_versionnum) & XFS_SB_VERSION_NUMBITS); printf("XFS: blocksize = %d\n",xfs.bsize); #endif @@ -1842,12 +1866,12 @@ xfs_readdir(int fd, int rewind) return NULL; } if (rewind) { -#ifdef DEBUG_XFS_2 +#ifdef DEBUG_XFS printf("xfs_readdir(): calling first_dentry()\n"); #endif return first_dentry (&xfs.new_ino); } -#ifdef DEBUG_XFS_2 +#ifdef DEBUG_XFS printf("xfs_readdir(): calling next_dentry()\n"); #endif return next_dentry (&xfs.new_ino); From 3075b9773bac9a4fd692229036eced70c66ac6b5 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Sun, 7 Dec 2025 18:03:17 +0100 Subject: [PATCH 7/7] XFS: code clean-up and removal of debug printouts Some code clean-up as well as putting debug printouts inside #ifdefs. Signed-off-by: Magnus Lindholm --- fs/xfs.c | 126 +++++++++++++++++++++---------------------------------- 1 file changed, 47 insertions(+), 79 deletions(-) diff --git a/fs/xfs.c b/fs/xfs.c index fddea65..87a817c 100644 --- a/fs/xfs.c +++ b/fs/xfs.c @@ -21,7 +21,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -//#include + #include #include #include @@ -302,8 +302,6 @@ xfs_sf_is_dir3(void) } - - static inline xfs_agblock_t agino2agbno (xfs_agino_t agino) { @@ -484,14 +482,14 @@ di_read (xfs_ino_t ino) devread (daddr, offset * xfs.isize, xfs.isize, (char *)inode); #ifdef DEBUG_XFS_2 - printf("di_read(): ino=%llu agno=%u agino=%u offset=%d isize=%d\n", + printf("di_read(): ino=%lu agno=%u agino=%u offset=%d isize=%d\n", (unsigned long long) ino, (unsigned int) agno, (unsigned int) agino, offset, xfs.isize); - printf("di_read(): magic=0x%x version=%d format=%d size=%llu flags=0x%x is_v5=%d\n", + printf("di_read(): magic=0x%x version=%d format=%d size=%lu flags=0x%x is_v5=%d\n", le16(inode->di_core.di_magic), inode->di_core.di_version, inode->di_core.di_format, @@ -504,7 +502,7 @@ di_read (xfs_ino_t ino) * Basic sanity checks * ------------------------------- */ if (le16(inode->di_core.di_magic) != XFS_DINODE_MAGIC) { - printf("XFS: bad inode magic 0x%x for ino %llu\n", + printf("XFS: bad inode magic 0x%x for ino %lu\n", le16(inode->di_core.di_magic), (unsigned long long) ino); return 0; @@ -514,7 +512,7 @@ di_read (xfs_ino_t ino) if (inode->di_core.di_version != 1 && inode->di_core.di_version != 2 && inode->di_core.di_version != 3) { - printf("XFS: unsupported inode version %d for ino %llu\n", + printf("XFS: unsupported inode version %d for ino %lu\n", inode->di_core.di_version, (unsigned long long) ino); return 0; @@ -549,22 +547,15 @@ di_read (xfs_ino_t ino) xfs.ptr0 = *(xfs_bmbt_ptr_t *)(dfork + sizeof(xfs_bmdr_block_t) + btroot_maxrecs() * sizeof(xfs_bmbt_key_t)); - - - -#ifdef DEBUG_XFS_2 - printf("di_read(): BTREE dir, ptr0 = 0x%llx\n", - (unsigned long long) le64(xfs.ptr0)); -#endif - - printf("di_read(): ino=%llu di_version=%u di_format=%u di_size=%llu di_nextents=%u is_v5=%d\n", - (unsigned long long)ino, +#ifdef DEBUG_XFS + printf("di_read(): ino=%lu di_version=%u di_format=%u di_size=%lu di_nextents=%u is_v5=%d\n", + (unsigned long long)ino, inode->di_core.di_version, inode->di_core.di_format, (unsigned long long)le64(inode->di_core.di_size), le32(inode->di_core.di_nextents), xfs.is_v5); - +#endif } return 1; @@ -604,42 +595,39 @@ init_extents (void) /* Extent records start at the beginning of the data fork */ //xfs.xt = (xfs_bmbt_rec_32_t *)xfs_dfork_dptr(); //xfs.nextents = le32(icore.di_nextents); - xfs_dinode_disk_t *dip = (xfs_dinode_disk_t *)inode; - - /* This is what older XFS used for data extents (still correct - * on non-NREXT64 filesystems and v1/v2 inodes). - */ - uint32_t ne32 = le32(icore.di_nextents); - - /* On v5 filesystems with NREXT64, the 8 bytes where older - * layouts had (di_pad[6] + di_flushiter) are now a union that - * may contain di_big_nextents (64-bit data extent count). - * - * Our xfs_dinode_disk_t still has di_pad[6] + di_flushiter, - * but that's exactly the same 8-byte region, so we can just - * reinterpret it as a BE64 big extent counter. - */ - uint64_t be_big = 0; - memcpy(&be_big, dip->di_pad, sizeof(be_big)); /* di_pad[0..5] + di_flushiter */ - uint64_t big = le64(be_big); - - uint32_t ne; - if (big != 0) { - /* NREXT64 case: di_big_nextents is valid and non-zero. - * We only need 32 bits in the bootloader; directories on - * a boot partition won't have anywhere near 2^32 extents. - */ - ne = (uint32_t)big; - } else { - /* Legacy case: use 32-bit di_nextents */ - ne = ne32; - } - - xfs.xt = (xfs_bmbt_rec_32_t *)xfs_dfork_dptr(); - xfs.nextents = ne; - - //printf("init_extents(): EXTENTS, nextents=%u (ne32=%u, big=%lu)\n", - // xfs.nextents, ne32, (unsigned long long)big); + xfs_dinode_disk_t *dip = (xfs_dinode_disk_t *)inode; + + /* This is what older XFS used for data extents (still correct + * on non-NREXT64 filesystems and v1/v2 inodes). + */ + uint32_t ne32 = le32(icore.di_nextents); + + /* On v5 filesystems with NREXT64, the 8 bytes where older + * layouts had (di_pad[6] + di_flushiter) are now a union that + * may contain di_big_nextents (64-bit data extent count). + * + * Our xfs_dinode_disk_t still has di_pad[6] + di_flushiter, + * but that's exactly the same 8-byte region, so we can just + * reinterpret it as a BE64 big extent counter. + */ + uint64_t be_big = 0; + memcpy(&be_big, dip->di_pad, sizeof(be_big)); /* di_pad[0..5] + di_flushiter */ + uint64_t big = le64(be_big); + + uint32_t ne; + if (big != 0) { + /* NREXT64 case: di_big_nextents is valid and non-zero. + * We only need 32 bits in the bootloader; directories on + * a boot partition won't have anywhere near 2^32 extents. + */ + ne = (uint32_t)big; + + } else { + /* Legacy case: use 32-bit di_nextents */ + ne = ne32; + } + xfs.xt = (xfs_bmbt_rec_32_t *)xfs_dfork_dptr(); + xfs.nextents = ne; break; case XFS_DINODE_FMT_BTREE: ptr0 = xfs.ptr0; @@ -714,35 +702,13 @@ xfs_dabread (void) } } -static inline xfs_ino_t -sf_ino (char *sfe, int namelen) -{ - void *p = sfe + namelen + 3; -#ifdef __alpha__ - xfs_ino_t ino = 0; - if (xfs.i8param == 0) { - memcpy(&ino, p, sizeof(xfs_dir2_ino8_t)); - return le64(ino); - } else { - memcpy(&ino, p, sizeof(xfs_dir2_ino4_t)); - return le32(ino); - } -#else - /* unaligned access */ - return (xfs.i8param == 0) - ? le64(*(xfs_ino_t *)p) : le32(*(uint32_t *)p); -#endif -} - - - /* Get parent inode from shortform header (v4 & v5). */ static inline xfs_ino_t sf_parent_ino(void) { - xfs_dir2_sf_t *sf = xfs_sf_dir(); /* your helper */ + xfs_dir2_sf_t *sf = xfs_sf_dir(); xfs_dir2_sf_hdr_t *hdr = &sf->hdr; return xfs_dir2_sf_get_ino(hdr, &hdr->parent); @@ -942,7 +908,9 @@ first_dentry_local(xfs_ino_t *ino) xfs.forw = 0; xfs.dirmax = hdr->count; /* number of real entries (no . / ..) */ +#ifdef DEBUG_XFS printf("sf->hdr.count=%d\n",xfs.dirmax); +#endif /* Keep i8param for old helpers, though we no longer rely on it here. */ xfs.i8param = hdr->i8count ? 0 : 4; @@ -1441,8 +1409,8 @@ next_dentry_dir2(xfs_ino_t *ino) /* -------------------------------------------------------- * Read first 4 bytes of entry - * ▪ free-entry: dau->unused.freetag == FREE_TAG - * ▪ used-entry: beginning of inode number (LSB) + * free-entry: dau->unused.freetag == FREE_TAG + * used-entry: beginning of inode number (LSB) * -------------------------------------------------------- */ if (xfs_read(dirbuf, 4) != 4) { printf("xfs: short read in dir2 entry\n");