Hello Samuel, Thanks for the feedback its greatly appreciated.
Here is the file, let me know if i miss to address anything. Kind regards, Milos On Mon, Jan 26, 2026 at 4:22 PM Samuel Thibault <[email protected]> wrote: > Hello, > > Thanks for working on this! > > Milos Nikic, le ven. 23 janv. 2026 22:55:23 -0800, a ecrit: > > diff --git a/ext2fs/ext2fs.h b/ext2fs/ext2fs.h > > index 62ee9f77..eea85d1d 100644 > > --- a/ext2fs/ext2fs.h > > +++ b/ext2fs/ext2fs.h > > @@ -427,9 +427,14 @@ dino_ref (ino_t inum) > > unsigned long bg_num = (inum - 1) / inodes_per_group; > > unsigned long group_inum = (inum - 1) % inodes_per_group; > > struct ext2_group_desc *bg = group_desc (bg_num); > > + uint16_t inode_size = le16toh (sblock->s_inode_size); > > + if (inode_size == 0) > > + inode_size = EXT2_GOOD_OLD_INODE_SIZE; /* Fallback for old volumes > */ > > + unsigned long inodes_per_blk = block_size / inode_size; > > - block_t block = le32toh (bg->bg_inode_table) + (group_inum / > inodes_per_block); > > + block_t block = le32toh (bg->bg_inode_table) + (group_inum / > inodes_per_blk); > > Mmm, is EXT2_INODE_SIZE not already putting the right value in > inodes_per_block? > > > - struct ext2_inode *inode = disk_cache_block_ref (block); > > + void *block_ptr = disk_cache_block_ref (block); > > - inode += group_inum % inodes_per_block; > > + size_t offset = (group_inum % inodes_per_blk) * inode_size; > > We'd probably want to make inode_size a global variable so we don't have > to recompute it all the time while it is constant for the opened FS. > > > + struct ext2_inode *inode = (struct ext2_inode *)((char *)block_ptr + > offset); > > ext2_debug ("(%llu) = %p", inum, inode); > > return inode; > > } > > > diff --git a/ext2fs/inode.c b/ext2fs/inode.c > > index dc309ac8..0fe07748 100644 > > --- a/ext2fs/inode.c > > +++ b/ext2fs/inode.c > > @@ -106,6 +106,36 @@ diskfs_new_hardrefs (struct node *np) > > { > > allow_pager_softrefs (np); > > } > > + > > +static inline void > > +ext2_decode_extra_time (uint32_t legacy_sec, uint32_t extra, > > + time_t *sec, long *nsec) > > +{ > > + /* Epoch extension (bits 32 and 33) */ > > + *sec = (time_t)legacy_sec + (((time_t)extra & 0x3) << 32); > > Urgl, so they just multiplied by 4 the lifetime of the ext2 format... :/ > > > + /* Nanoseconds (bits 2 through 31) */ > > + *nsec = (long)(extra >> 2); > > +} > > + > > +static inline uint32_t > > +ext2_encode_extra_time (time_t sec, long nsec) > > +{ > > + uint32_t extra; > > + /* Pack nanoseconds into the upper 30 bits */ > > + extra = (uint32_t)(nsec << 2); > > + /* Pack bits 32 and 33 of seconds into the lower 2 bits */ > > + extra |= (uint32_t)((sec >> 32) & 0x3); > > + return extra; > > +} > > + > > +/* Helper to check if the current filesystem supports extended inodes */ > > +static inline int > > +ext2_has_extra_inodes (struct ext2_super_block *sb) > > +{ > > + return (le32toh (sb->s_rev_level) > EXT2_GOOD_OLD_REV > > + && le16toh (sb->s_inode_size) > EXT2_GOOD_OLD_INODE_SIZE); > > +} > > + > > > > /* The user must define this function if she wants to use the node > > cache. Read stat information out of the on-disk node. */ > > @@ -136,23 +166,31 @@ diskfs_user_read_node (struct node *np, struct > lookup_context *ctx) > > st->st_gen = le32toh (di->i_generation); > > > > st->st_atim.tv_sec = le32toh (di->i_atime); > > -#ifdef not_yet > > - /* ``struct ext2_inode'' doesn't do better than sec. precision yet. > */ > > -#else > > - st->st_atim.tv_nsec = 0; > > -#endif > > st->st_mtim.tv_sec = le32toh (di->i_mtime); > > -#ifdef not_yet > > - /* ``struct ext2_inode'' doesn't do better than sec. precision yet. > */ > > -#else > > - st->st_mtim.tv_nsec = 0; > > -#endif > > st->st_ctim.tv_sec = le32toh (di->i_ctime); > > -#ifdef not_yet > > - /* ``struct ext2_inode'' doesn't do better than sec. precision yet. > */ > > -#else > > - st->st_ctim.tv_nsec = 0; > > -#endif > > + st->st_atim.tv_nsec = st->st_mtim.tv_nsec = st->st_ctim.tv_nsec = 0; > > + if (ext2_has_extra_inodes (sblock)) > > + { > > + struct ext2_inode_extra *di_extra = > > + (struct ext2_inode_extra *) ((char *) di + > EXT2_GOOD_OLD_INODE_SIZE); > > + > > + /* Only decode if the inode actually uses the extra space > (i_extra_isize) > > + The i_extra_isize tells us how many extra bytes are used in THIS > inode. */ > > + if (le16toh (di_extra->i_extra_isize) >= 32) /* Enough room for > all 3 extra timestamps */ > > Better use offsetof() + sizeof() to make it really obvious that we are > checking for the last required field, rather than a magic number 32. > > > + { > > + ext2_decode_extra_time (le32toh (di->i_atime), le32toh > (di_extra->i_atime_extra), > > + &st->st_atim.tv_sec, > &st->st_atim.tv_nsec); > > + ext2_decode_extra_time (le32toh (di->i_ctime), le32toh > (di_extra->i_ctime_extra), > > + &st->st_ctim.tv_sec, > &st->st_ctim.tv_nsec); > > + ext2_decode_extra_time (le32toh (di->i_mtime), le32toh > (di_extra->i_mtime_extra), > > + &st->st_mtim.tv_sec, > &st->st_mtim.tv_nsec); > > + info->i_atime_extra = le32toh (di_extra->i_atime_extra); > > + info->i_ctime_extra = le32toh (di_extra->i_ctime_extra); > > + info->i_mtime_extra = le32toh (di_extra->i_mtime_extra); > > Do we really need to remember these three? > > > + } > > + else > > + info->i_atime_extra = info->i_ctime_extra = info->i_mtime_extra > = 0; > > + } > > > > st->st_blocks = le32toh (di->i_blocks); > > > > @@ -416,19 +454,25 @@ write_node (struct node *np) > > di->i_links_count = htole16 (st->st_nlink); > > > > di->i_atime = htole32(st->st_atim.tv_sec); > > -#ifdef not_yet > > - /* ``struct ext2_inode'' doesn't do better than sec. precision > yet. */ > > - di->i_atime.tv_nsec = htole32 (st->st_atim.tv_nsec); > > -#endif > > di->i_mtime = htole32 (st->st_mtim.tv_sec); > > -#ifdef not_yet > > - di->i_mtime.tv_nsec = htole32 (st->st_mtim.tv_nsec); > > -#endif > > di->i_ctime = htole32 (st->st_ctim.tv_sec); > > -#ifdef not_yet > > - di->i_ctime.tv_nsec = htole32 (st->st_ctim.tv_nsec); > > -#endif > > - > > + if (ext2_has_extra_inodes (sblock)) > > You also need to check that the superblock-recorded inode size is large > enough for the required time fields. > > > + { > > + struct ext2_inode_extra *di_extra = > > + (struct ext2_inode_extra *) ((char *) di + > EXT2_GOOD_OLD_INODE_SIZE); > > + if (le16toh (di_extra->i_extra_isize) < 32) > > + di_extra->i_extra_isize = htole16 (32); > > Again, use offsetof+sizeof. You can define a macro to use it in the > various places. > > > + > > + di_extra->i_atime_extra = htole32 (ext2_encode_extra_time > (st->st_atim.tv_sec, > > + > st->st_atim.tv_nsec)); > > + di_extra->i_mtime_extra = htole32 (ext2_encode_extra_time > (st->st_mtim.tv_sec, > > + > st->st_mtim.tv_nsec)); > > + di_extra->i_ctime_extra = htole32 (ext2_encode_extra_time > (st->st_ctim.tv_sec, > > + > st->st_ctim.tv_nsec)); > > + info->i_atime_extra = le32toh (di_extra->i_atime_extra); > > + info->i_mtime_extra = le32toh (di_extra->i_mtime_extra); > > + info->i_ctime_extra = le32toh (di_extra->i_ctime_extra); > > Again, these three don't seem to be used? > > And i_extra_isize in info is unused too. > > Samuel >
From fca97d56f6090e71423a4805d3a8bf3092a278a1 Mon Sep 17 00:00:00 2001 From: Milos Nikic <[email protected]> Date: Mon, 26 Jan 2026 22:32:02 -0800 Subject: [PATCH] ext2: support 64 bit time Implemented at ext4 spec we now support timestamps after 2038 and to a nano precision. Backward compatible (works as before on systems that are formatted to 128) if node size is larger it supports extra time precision To test (inside Hurd): dd if=/dev/zero of=test.img bs=1M count=100 sudo mke2fs -I 256 -v test.img sudo settrans -ca /mnt /hurd/ext2fs.static test.img sudo touch -d '2045-01-01 12:34:56.789123456' /mnt/precision_test stat /mnt/precision_test File: /mnt/precision_test Size: 0 Blocks: 0 IO Block: 8192 regular empty file Device: 3,1 Inode: 13 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2045-01-01 12:34:56.789123456 +0000 Modify: 2045-01-01 12:34:56.789123456 +0000 Change: 2026-01-24 06:23:58.318082000 +0000 --- ext2fs/ext2_fs.h | 17 ++++++++- ext2fs/ext2fs.h | 9 ++--- ext2fs/hyper.c | 11 ++++-- ext2fs/ialloc.c | 2 +- ext2fs/inode.c | 92 ++++++++++++++++++++++++++++++++++-------------- 5 files changed, 97 insertions(+), 34 deletions(-) diff --git a/ext2fs/ext2_fs.h b/ext2fs/ext2_fs.h index daa49543..6460db35 100644 --- a/ext2fs/ext2_fs.h +++ b/ext2fs/ext2_fs.h @@ -278,6 +278,18 @@ struct ext2_inode { } osd2; /* OS dependent 2 */ }; +struct ext2_inode_extra { + __u16 i_extra_isize; /* Size of this extra record */ + __u16 i_checksum_hi; /* Upper 16-bits of inode checksum */ + __u32 i_ctime_extra; /* Extra ctime bits (nanos + epoch) */ + __u32 i_mtime_extra; /* Extra mtime bits (nanos + epoch) */ + __u32 i_atime_extra; /* Extra atime bits (nanos + epoch) */ + __u32 i_crtime; /* File creation time (Birth time) */ + __u32 i_crtime_extra; /* Extra crtime bits */ + __u32 i_version_hi; /* High 32 bits of 64-bit version */ + __u32 i_projid; /* Project ID */ +}; + #define i_size_high i_dir_acl #define i_translator osd1.hurd1.h_i_translator @@ -425,10 +437,13 @@ struct ext2_super_block { #define EXT2_GOOD_OLD_REV 0 /* The good old (original) format */ #define EXT2_DYNAMIC_REV 1 /* V2 format w/ dynamic inode sizes */ -#define EXT2_CURRENT_REV EXT2_GOOD_OLD_REV +#define EXT2_CURRENT_REV EXT2_DYNAMIC_REV #define EXT2_MAX_SUPP_REV EXT2_DYNAMIC_REV #define EXT2_GOOD_OLD_INODE_SIZE 128 +#define EXT2_INODE_EXTENT_SIZE \ + (offsetof (struct ext2_inode_extra, i_mtime_extra) + \ + sizeof (((struct ext2_inode_extra *)0)->i_mtime_extra)) /* * Feature set definitions diff --git a/ext2fs/ext2fs.h b/ext2fs/ext2fs.h index 62ee9f77..78a88a6a 100644 --- a/ext2fs/ext2fs.h +++ b/ext2fs/ext2fs.h @@ -281,6 +281,8 @@ int disk_cache_block_is_ref (block_t block); extern struct ext2_super_block *sblock; /* True if sblock has been modified. */ extern int sblock_dirty; +/* Size of one inode. */ +extern __u16 global_inode_size; /* Where the super-block is located on disk (at min-block 1). */ #define SBLOCK_BLOCK 1 /* Default location, second 1k block. */ @@ -428,10 +430,9 @@ dino_ref (ino_t inum) unsigned long group_inum = (inum - 1) % inodes_per_group; struct ext2_group_desc *bg = group_desc (bg_num); block_t block = le32toh (bg->bg_inode_table) + (group_inum / inodes_per_block); - struct ext2_inode *inode = disk_cache_block_ref (block); - inode += group_inum % inodes_per_block; - ext2_debug ("(%llu) = %p", inum, inode); - return inode; + void *block_ptr = disk_cache_block_ref (block); + size_t offset = (group_inum % inodes_per_block) * global_inode_size; + return (struct ext2_inode *)((char *)block_ptr + offset); } EXT2FS_EI void diff --git a/ext2fs/hyper.c b/ext2fs/hyper.c index 2af7e870..9e135015 100644 --- a/ext2fs/hyper.c +++ b/ext2fs/hyper.c @@ -133,8 +133,9 @@ get_hypermetadata (void) features); diskfs_readonly = 1; } - if (le16toh (sblock->s_inode_size) != EXT2_GOOD_OLD_INODE_SIZE) - ext2_panic ("inode size %d isn't supported, only %d is supported", le16toh (sblock->s_inode_size), EXT2_GOOD_OLD_INODE_SIZE); + uint16_t inode_size = le16toh (sblock->s_inode_size); + if (inode_size < EXT2_GOOD_OLD_INODE_SIZE || (inode_size & (inode_size - 1)) != 0) + ext2_panic ("inode size %d isn't supported", inode_size); if (EXT2_HAS_COMPAT_FEATURE (sblock, EXT3_FEATURE_COMPAT_HAS_JOURNAL)) ext2_warning ("mounting ext3 filesystem as ext2"); } @@ -171,6 +172,7 @@ get_hypermetadata (void) } static struct ext2_super_block *mapped_sblock; +__u16 global_inode_size; void map_hypermetadata (void) @@ -181,6 +183,11 @@ map_hypermetadata (void) These are stored in the filesystem blocks following the superblock. */ group_desc_image = (struct ext2_group_desc *) bptr (group_desc_block); + + global_inode_size = le16toh (sblock->s_inode_size); + + if (global_inode_size == 0) + global_inode_size = EXT2_GOOD_OLD_INODE_SIZE; } error_t diff --git a/ext2fs/ialloc.c b/ext2fs/ialloc.c index c2588fc4..46c86eaa 100644 --- a/ext2fs/ialloc.c +++ b/ext2fs/ialloc.c @@ -278,7 +278,7 @@ repeat: fields. */ { struct ext2_inode *di = dino_ref (inum); - memset (di, 0, sizeof *di); + memset (di, 0, EXT2_INODE_SIZE (sblock)); dino_deref (di); } diff --git a/ext2fs/inode.c b/ext2fs/inode.c index dc309ac8..221a854a 100644 --- a/ext2fs/inode.c +++ b/ext2fs/inode.c @@ -106,6 +106,36 @@ diskfs_new_hardrefs (struct node *np) { allow_pager_softrefs (np); } + +static inline void +ext2_decode_extra_time (uint32_t legacy_sec, uint32_t extra, + time_t *sec, long *nsec) +{ + /* Epoch extension (bits 32 and 33) */ + *sec = (time_t)legacy_sec + (((time_t)extra & 0x3) << 32); + /* Nanoseconds (bits 2 through 31) */ + *nsec = (long)(extra >> 2); +} + +static inline uint32_t +ext2_encode_extra_time (time_t sec, long nsec) +{ + uint32_t extra; + /* Pack nanoseconds into the upper 30 bits */ + extra = (uint32_t)(nsec << 2); + /* Pack bits 32 and 33 of seconds into the lower 2 bits */ + extra |= (uint32_t)((sec >> 32) & 0x3); + return extra; +} + +/* Helper to check if the current filesystem supports extended inodes */ +static inline int +ext2_has_extra_inodes (struct ext2_super_block *sb) +{ + return (le32toh (sb->s_rev_level) > EXT2_GOOD_OLD_REV + && le16toh (sb->s_inode_size) > EXT2_GOOD_OLD_INODE_SIZE); +} + /* The user must define this function if she wants to use the node cache. Read stat information out of the on-disk node. */ @@ -136,23 +166,29 @@ diskfs_user_read_node (struct node *np, struct lookup_context *ctx) st->st_gen = le32toh (di->i_generation); st->st_atim.tv_sec = le32toh (di->i_atime); -#ifdef not_yet - /* ``struct ext2_inode'' doesn't do better than sec. precision yet. */ -#else - st->st_atim.tv_nsec = 0; -#endif st->st_mtim.tv_sec = le32toh (di->i_mtime); -#ifdef not_yet - /* ``struct ext2_inode'' doesn't do better than sec. precision yet. */ -#else - st->st_mtim.tv_nsec = 0; -#endif st->st_ctim.tv_sec = le32toh (di->i_ctime); -#ifdef not_yet - /* ``struct ext2_inode'' doesn't do better than sec. precision yet. */ -#else - st->st_ctim.tv_nsec = 0; -#endif + st->st_atim.tv_nsec = st->st_mtim.tv_nsec = st->st_ctim.tv_nsec = 0; + if (ext2_has_extra_inodes (sblock)) + { + struct ext2_inode_extra *di_extra = + (struct ext2_inode_extra *) ((char *) di + EXT2_GOOD_OLD_INODE_SIZE); + + /* Only decode if the inode actually uses the extra space (i_extra_isize) + The i_extra_isize tells us how many extra bytes are used in THIS inode. */ + if (le16toh (di_extra->i_extra_isize) >= EXT2_INODE_EXTENT_SIZE) + { + ext2_decode_extra_time (le32toh (di->i_atime), + le32toh (di_extra->i_atime_extra), + &st->st_atim.tv_sec, &st->st_atim.tv_nsec); + ext2_decode_extra_time (le32toh (di->i_ctime), + le32toh (di_extra->i_ctime_extra), + &st->st_ctim.tv_sec, &st->st_ctim.tv_nsec); + ext2_decode_extra_time (le32toh (di->i_mtime), + le32toh (di_extra->i_mtime_extra), + &st->st_mtim.tv_sec, &st->st_mtim.tv_nsec); + } + } st->st_blocks = le32toh (di->i_blocks); @@ -416,19 +452,23 @@ write_node (struct node *np) di->i_links_count = htole16 (st->st_nlink); di->i_atime = htole32(st->st_atim.tv_sec); -#ifdef not_yet - /* ``struct ext2_inode'' doesn't do better than sec. precision yet. */ - di->i_atime.tv_nsec = htole32 (st->st_atim.tv_nsec); -#endif di->i_mtime = htole32 (st->st_mtim.tv_sec); -#ifdef not_yet - di->i_mtime.tv_nsec = htole32 (st->st_mtim.tv_nsec); -#endif di->i_ctime = htole32 (st->st_ctim.tv_sec); -#ifdef not_yet - di->i_ctime.tv_nsec = htole32 (st->st_ctim.tv_nsec); -#endif - + if (ext2_has_extra_inodes (sblock)) + { + struct ext2_inode_extra *di_extra = + (struct ext2_inode_extra *) ((char *) di + EXT2_GOOD_OLD_INODE_SIZE); + + if (le16toh (di_extra->i_extra_isize) < EXT2_INODE_EXTENT_SIZE) + di_extra->i_extra_isize = htole16 (EXT2_INODE_EXTENT_SIZE); + + di_extra->i_atime_extra = htole32 (ext2_encode_extra_time (st->st_atim.tv_sec, + st->st_atim.tv_nsec)); + di_extra->i_mtime_extra = htole32 (ext2_encode_extra_time (st->st_mtim.tv_sec, + st->st_mtim.tv_nsec)); + di_extra->i_ctime_extra = htole32 (ext2_encode_extra_time (st->st_ctim.tv_sec, + st->st_ctim.tv_nsec)); + } /* Convert generic flags in ST->st_flags to ext2-specific flags in DI (but don't mess with ext2 flags we don't know about). The original set was copied from DI into INFO by read_node, but might have been -- 2.52.0
