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

Reply via email to