Hello,

Milos Nikic, le mer. 21 janv. 2026 14:58:01 -0800, a ecrit:
> Over the past week, I have been working on creating a prototype for a journal
> inside ext2fs which is fully Linux compatible (binary JBD2 compatible). This
> enables standard Linux tools (e2fsck, tune2fs, debugfs, etc.) to work
> seamlessly with Hurd partitions.

Great :D

It'll take me some time to get a look at it, people are welcome to
provide feedback of course!

Samuel

> This means one can mount a Hurd image from Linux and fix any issues with the
> drive using standard journaling tools if the need arises. While this is
> currently a prototype with polish still required, it is functional.
> 
> Key Features:
>     Log Replay: The driver writes JBD2-compliant transactions. I have verified
> that after a hard crash of the Hurd guest, a Linux host running e2fsck
> correctly replays the journal and restores filesystem metadata consistency.
>     Continuous Operation: The driver handles ring-buffer wrap-around and
> checkpoints correctly. I have tested it with sustained loads (50,000+
> transaction loops) without deadlocks or corruption.
>     Crash Safety: I have verified via "sabotage tests" (modifying the disk
> offline after a crash) that the journal accurately restores the correct
> metadata state.
>     Lightweight: The implementation consists of only a few new files and ~800
> lines of code.
> 
> Implementation Details: I had to add one weak symbol in diskfs.h. For this
> prototype, I am using a "safe sync" strategy (flushing the pager buckets)
> because there is no explicit write barrier RPC in Mach to ensure strict
> ordering between transaction commits and superblock updates.
> 
> If there is interest from the Hurd community in integrating this, I am willing
> to work on adding a proper write barrier RPC into Mach and productionizing the
> code adding whatever else is needed. There is a path to a safer, more atomic
> Hurd and also retiring fsck fully if we integrate a version of this.
> 
> How to Test It: To test this on your own images, you simply need to create the
> journal using tune2fs. You will need the byte offset of your ext2 partition.
> (WARNING: Please apply the patch first, otherwise your ext2 will refuse to 
> work
> with your image!!!)
> 
> The easiest way (for me) to get the offset is this:
> )> parted debian-hurd-amd64-20251105.img unit B print
> Output example:
> Number  Start        End          Size         Type      File system     Flags
>  1      1048576B     1000341503B  999292928B   primary   linux-swap(v1)  swap
>  2      1001389056B  9564061695B  8562672640B  extended                  lba
>  5      1001390080B  9564061695B  8562671616B  logical   ext2
> (the 1001390080B is the ext2 partition for me.)
> 
> now armed with that knowledge i do:
> > sudo modprobe nbd max_part=8
> > sudo qemu-nbd --connect=/dev/nbd0 --offset=1001390080 --format=raw
> debian-hurd-amd64-20251105.img
> > sudo tune2fs -j /dev/nbd0
> > sudo qemu-nbd --disconnect /dev/nbd0
> 
> That's it! tune2fs has now allocated an empty journal inside your Hurd
> filesystem.
> 
> The next time you boot into Hurd, you might notice a warning: ext2fs:
> part:5:device:wd0: warning: mounting ext3 filesystem as ext2. This is normal.
> 
> All tools will now recognize the partition as ext3. Even mounting the image
> inside Linux will automatically replay the journal and fix any issues if
> needed. Besides that everything should work seamlessly.
> 
> Let me know if this is something the community would like to see developed
> further.
> 
> Thanks,
> Milos
> 

> From e77b197789ee72be42304e705254abd4f35c0aa4 Mon Sep 17 00:00:00 2001
> From: Milos Nikic <[email protected]>
> Date: Sat, 17 Jan 2026 22:49:00 -0800
> Subject: [PATCH] ext2: Prototype: Add JDB2 binary compliant journal
> 
> This prototype adds journal driver that captures some node
> metadata inside a binary compliant place with binary
> compliant content of the JBD2.
> 
> This way standard linux tools (e2fsck, debugfs, tune2fs etc)
> all work and can understand and replay ext2 journal.
> In fact all of them now recognize ext2 as ext3.
> ---
>  ext2fs/Makefile           |   2 +-
>  ext2fs/ext2_fs.h          |   3 +-
>  ext2fs/ext2fs.c           |  24 ++
>  ext2fs/ext2fs.h           |   4 +
>  ext2fs/hyper.c            |   9 +
>  ext2fs/inode.c            |  41 +-
>  ext2fs/jbd2_format.h      | 101 +++++
>  ext2fs/journal.c          | 843 ++++++++++++++++++++++++++++++++++++++
>  ext2fs/journal.h          |  62 +++
>  ext2fs/pager.c            |  31 ++
>  libdiskfs/diskfs.h        |   5 +
>  libdiskfs/node-modified.c |  28 ++
>  libdiskfs/priv.h          |   6 +
>  13 files changed, 1153 insertions(+), 6 deletions(-)
>  create mode 100644 ext2fs/jbd2_format.h
>  create mode 100644 ext2fs/journal.c
>  create mode 100644 ext2fs/journal.h
>  create mode 100644 libdiskfs/node-modified.c
> 
> diff --git a/ext2fs/Makefile b/ext2fs/Makefile
> index 0c2f4a24..a2b0f1ee 100644
> --- a/ext2fs/Makefile
> +++ b/ext2fs/Makefile
> @@ -22,7 +22,7 @@ makemode := server
>  target = ext2fs
>  SRCS = balloc.c dir.c ext2fs.c getblk.c hyper.c ialloc.c \
>         inode.c pager.c pokel.c truncate.c storeinfo.c msg.c xinl.c \
> -       xattr.c
> +       xattr.c journal.c
>  OBJS = $(SRCS:.c=.o)
>  HURDLIBS = diskfs pager iohelp fshelp store ports ihash shouldbeinlibc
>  LDLIBS = -lpthread $(and $(HAVE_LIBBZ2),-lbz2) $(and $(HAVE_LIBZ),-lz)
> diff --git a/ext2fs/ext2_fs.h b/ext2fs/ext2_fs.h
> index daa49543..a3a00c95 100644
> --- a/ext2fs/ext2_fs.h
> +++ b/ext2fs/ext2_fs.h
> @@ -474,7 +474,8 @@ struct ext2_super_block {
>  #define EXT2_FEATURE_INCOMPAT_ANY            0xffffffff
>  
>  #define EXT2_FEATURE_COMPAT_SUPP     EXT2_FEATURE_COMPAT_EXT_ATTR
> -#define EXT2_FEATURE_INCOMPAT_SUPP   EXT2_FEATURE_INCOMPAT_FILETYPE
> +#define EXT2_FEATURE_INCOMPAT_SUPP (EXT2_FEATURE_INCOMPAT_FILETYPE | \
> +                                    EXT3_FEATURE_INCOMPAT_RECOVER)
>  #define EXT2_FEATURE_RO_COMPAT_SUPP  (EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER| \
>                                        EXT2_FEATURE_RO_COMPAT_LARGE_FILE| \
>                                        EXT2_FEATURE_RO_COMPAT_BTREE_DIR)
> diff --git a/ext2fs/ext2fs.c b/ext2fs/ext2fs.c
> index 3836bdf6..37a6cc2a 100644
> --- a/ext2fs/ext2fs.c
> +++ b/ext2fs/ext2fs.c
> @@ -32,6 +32,7 @@
>  #include <hurd/store.h>
>  #include <version.h>
>  #include "ext2fs.h"
> +#include "journal.h"
>  
>  /* ---------------------------------------------------------------- */
>  
> @@ -80,6 +81,7 @@ unsigned long desc_per_block;
>  unsigned long addr_per_block;
>  
>  unsigned long groups_count;
> +struct journal *ext2_journal = NULL;
>  
>  /* ---------------------------------------------------------------- */
>  
> @@ -251,6 +253,28 @@ main (int argc, char **argv)
>      ext2_panic ("no root node!");
>    pthread_mutex_unlock (&diskfs_root_node->lock);
>  
> +  if (sblock->s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL)
> +    {
> +      JRNL_LOG_DEBUG ("\n[JOURNAL CHECK] >>> Inode 8 DETECTED! <<<");
> +      JRNL_LOG_DEBUG ("[JOURNAL CHECK] s_journal_inum: %u (Expected: 8)",
> +                   sblock->s_journal_inum);
> +      JRNL_LOG_DEBUG ("[JOURNAL CHECK] s_journal_dev:  %u",
> +                   sblock->s_journal_dev);
> +      struct node *jnode = NULL;
> +      error_t err = diskfs_cached_lookup (8, &jnode);
> +
> +      if (!err && jnode) 
> +      {
> +       ext2_journal = journal_create (jnode);
> +       JRNL_LOG_DEBUG ("Global Journal Initialized at %p", ext2_journal);
> +       diskfs_nput(jnode); 
> +      }
> +    }
> +  else
> +    {
> +      JRNL_LOG_DEBUG ("\n[JOURNAL CHECK] No Journal flag found.");
> +    }
> +
>    /* Now that we are all set up to handle requests, and diskfs_root_node is
>       set properly, it is safe to export our fsys control port to the
>       outside world.  */
> diff --git a/ext2fs/ext2fs.h b/ext2fs/ext2fs.h
> index 62ee9f77..5d9f4ee4 100644
> --- a/ext2fs/ext2fs.h
> +++ b/ext2fs/ext2fs.h
> @@ -282,6 +282,10 @@ extern struct ext2_super_block *sblock;
>  /* True if sblock has been modified.  */
>  extern int sblock_dirty;
>  
> +/* Forward declaration prevents circular dependency with journal.h */
> +struct journal; 
> +extern struct journal *ext2_journal;
> +
>  /* Where the super-block is located on disk (at min-block 1).  */
>  #define SBLOCK_BLOCK 1       /* Default location, second 1k block.  */
>  #define SBLOCK_SIZE  (sizeof (struct ext2_super_block))
> diff --git a/ext2fs/hyper.c b/ext2fs/hyper.c
> index 2af7e870..821a4781 100644
> --- a/ext2fs/hyper.c
> +++ b/ext2fs/hyper.c
> @@ -190,11 +190,20 @@ diskfs_set_hypermetadata (int wait, int clean)
>      /* The filesystem is clean, so we need to set the clean flag.  */
>      {
>        sblock->s_state |= htole16 (EXT2_VALID_FS);
> +      if (ext2_journal)
> +       {
> +       sblock->s_feature_incompat &= htole32(~EXT3_FEATURE_INCOMPAT_RECOVER);
> +       }
>        sblock_dirty = 1;
>      }
>    else if (!clean && (sblock->s_state & htole16 (EXT2_VALID_FS)))
>      /* The filesystem just became dirty, so clear the clean flag.  */
>      {
> +      if (ext2_journal && 
> +          !(sblock->s_feature_incompat & 
> htole32(EXT3_FEATURE_INCOMPAT_RECOVER)))
> +     {
> +           sblock->s_feature_incompat |= 
> htole32(EXT3_FEATURE_INCOMPAT_RECOVER);
> +        }
>        sblock->s_state &= htole16 (~EXT2_VALID_FS);
>        sblock_dirty = 1;
>        wait = 1;
> diff --git a/ext2fs/inode.c b/ext2fs/inode.c
> index dc309ac8..84c1d56b 100644
> --- a/ext2fs/inode.c
> +++ b/ext2fs/inode.c
> @@ -20,6 +20,7 @@
>     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
>  
>  #include "ext2fs.h"
> +#include "journal.h"
>  #include <string.h>
>  #include <unistd.h>
>  #include <stdio.h>
> @@ -524,14 +525,38 @@ write_all_disknodes (void)
>  void
>  diskfs_write_disknode (struct node *np, int wait)
>  {
> +  if (ext2_journal)
> +  {
> +    journal_start_transaction(ext2_journal);
> +  }
> +
>    struct ext2_inode *di = write_node (np);
> +
>    if (di)
> +  {
> +    if (ext2_journal)
>      {
> -      if (wait)
> -     sync_global_ptr (di, 1);
> -      else
> -     record_global_poke (di);
> +      unsigned long ino = np->dn_stat.st_ino;
> +      unsigned long group = inode_group_num(ino);
> +      block_t table_start = le32toh (group_desc(group)->bg_inode_table);
> +      unsigned long inodes_per_group = le32toh (sblock->s_inodes_per_group);
> +      unsigned long inode_index = (ino - 1) % inodes_per_group;
> +      unsigned long byte_offset = inode_index * le16toh 
> (sblock->s_inode_size);
> +      block_t block_num = table_start + (byte_offset / block_size);
> +      void *block_ptr = bptr (block_num);
> +      journal_dirty_block(ext2_journal, block_num, block_ptr);
>      }
> +
> +    if (wait)
> +      sync_global_ptr (di, 1);
> +    else
> +      record_global_poke (di);
> +  }
> +
> +  if (ext2_journal)
> +  {
> +    journal_stop_transaction(ext2_journal);
> +  }
>  }
>  
>  /* Set *ST with appropriate values to reflect the current state of the
> @@ -862,3 +887,11 @@ diskfs_shutdown_soft_ports (void)
>    /* Should initiate termination of internally held pager ports
>       (the only things that should be soft) XXX */
>  }
> +
> +void
> +diskfs_notify_change (struct node *np)
> +{
> +    /* If journaling is active, capture this metadata change immediately */
> +    if (ext2_journal)
> +        diskfs_node_update (np, 0);
> +}
> diff --git a/ext2fs/jbd2_format.h b/ext2fs/jbd2_format.h
> new file mode 100644
> index 00000000..a74a19d2
> --- /dev/null
> +++ b/ext2fs/jbd2_format.h
> @@ -0,0 +1,101 @@
> +/* JBD2 Standard On-Disk Layout 
> +
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   Written by Milos Nikic.
> +
> +   Converted for ext2fs by Miles Bader <[email protected]>
> +
> +   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, 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. */
> +
> +#ifndef _JBD2_FORMAT_H
> +#define _JBD2_FORMAT_H
> +
> +#include <stdint.h>
> +
> +/** JBD2 Magic Numbers 
> + * If a block starts with this, it's a metadata block.
> + */
> +#define JBD2_MAGIC_NUMBER 0xC03B3998U
> +
> +/* Block Types */
> +#define JBD2_DESCRIPTOR_BLOCK 1
> +#define JBD2_COMMIT_BLOCK     2
> +#define JBD2_SUPERBLOCK_V1    3
> +#define JBD2_SUPERBLOCK_V2    4
> +#define JBD2_REVOKE_BLOCK     5
> +
> +#define JBD2_PACKED __attribute__((packed))
> +
> +/* * 
> + * The Journal Superblock 
> + * Lives at the very start of the journal partition (typically Inode 8).
> + */
> +typedef struct JBD2_PACKED journal_superblock_s
> +{
> +  uint32_t s_header[3];              /* Standard header (magic, type, etc) */
> +  uint32_t s_blocksize;              /* Journal device blocksize */
> +  uint32_t s_maxlen;         /* Total blocks in journal file */
> +  uint32_t s_first;          /* First block of log information */
> +
> +  uint32_t s_sequence;               /* First commit ID expected in log */
> +  uint32_t s_start;          /* Block number of start of log */
> +
> +  uint32_t s_errno;          /* Error value, if any */
> +
> +  uint32_t s_feature_compat;
> +  uint32_t s_feature_incompat;
> +  uint32_t s_feature_ro_compat;
> +
> +  uint8_t s_uuid[16];                /* 128-bit uuid for journal */
> +  uint32_t s_nr_users;               /* Nr of filesystems sharing log */
> +  uint32_t s_dynsuper;               /* Blocknr of dynamic superblock copy */
> +  uint32_t s_max_transaction;        /* Limit of handle size */
> +  uint32_t s_max_trans_data; /* Limit of data blocks per trans */
> +  uint32_t s_checksum_type;  /* checksum type */
> +  uint8_t s_padding2[42 * 4];
> +  uint32_t s_checksum;               /* crc32c(superblock) */
> +  uint8_t s_users[16 * 48];  /* ids of all filesystems sharing log */
> +} journal_superblock_t;
> +
> +_Static_assert (sizeof (journal_superblock_t) == 1024,
> +             "JBD2 Superblock size mismatch! Check padding.");
> +
> +/* * The Standard Header
> + * Every metadata block (Descriptor, Commit) starts with this.
> + */
> +typedef struct JBD2_PACKED journal_header_s
> +{
> +  uint32_t h_magic;          /* 0xC03B3998 */
> +  uint32_t h_blocktype;              /* Descriptor, Commit, etc. */
> +  uint32_t h_sequence;               /* The Transaction ID */
> +} journal_header_t;
> +
> +/* * The Block Tag
> + * Describes a data block that follows.
> + * Structure: [Descriptor Block] [Tag 1] [Tag 2] ... [Data 1] [Data 2] ...
> + */
> +typedef struct JBD2_PACKED journal_block_tag_s
> +{
> +  uint32_t t_blocknr;                /* The 32-bit physical block number */
> +  uint32_t t_flags;          /* See flags below */
> +} journal_block_tag_t;
> +
> +/* Flags for the Block Tag */
> +#define JBD2_FLAG_ESCAPE    1        /* The data block starts with magic 
> number (escaped) */
> +#define JBD2_FLAG_SAME_UUID 2        /* (Not needed for us usually) */
> +#define JBD2_FLAG_DELETED   4        /* Block was deleted */
> +#define JBD2_FLAG_LAST_TAG  8        /* This is the last tag in this 
> descriptor block */
> +
> +#endif
> diff --git a/ext2fs/journal.c b/ext2fs/journal.c
> new file mode 100644
> index 00000000..06f09e2f
> --- /dev/null
> +++ b/ext2fs/journal.c
> @@ -0,0 +1,843 @@
> +/* JBD2 binary compliant journal driver. 
> +
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   Written by Milos Nikic.
> +
> +   Converted for ext2fs by Miles Bader <[email protected]>
> +
> +   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, 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 <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +
> +#include "ext2fs.h"
> +#include "jbd2_format.h"
> +#include "journal.h"
> +
> +static pthread_t kjournald_tid;
> +
> +extern void journal_sync_everything (void);
> +
> +/**
> + * Represents one modified block (4KB) that needs to be written to the 
> journal.
> + */
> +typedef struct journal_buffer
> +{
> +  block_t jb_blocknr;                        /* The physical block number on 
> the filesystem */
> +  void *jb_shadow_data;                      /* 4KB Copy of the data to be 
> logged */
> +  struct journal_buffer *jb_next;    /* Linked list next pointer */
> +  uint32_t jb_log_spot;
> +} journal_buffer_t;
> +
> +/* The state of a transaction in memory */
> +typedef enum
> +{
> +  T_RUNNING,                         /* Accepting new handles/buffers */
> +  T_LOCKED,                          /* Locked, no new handles, waiting for 
> updates to finish */
> +  T_FLUSHING,                                /* Writing to the journal ring 
> buffer */
> +  T_COMMIT,                          /* Writing the commit block */
> +  T_FINISHED                         /* Done, waiting to be checkpointed */
> +} transaction_state_t;
> +
> +/* The Transaction Object */
> +struct journal_transaction
> +{
> +  uint32_t t_tid;                    /* Transaction ID (Sequence Number) */
> +  transaction_state_t t_state;
> +
> +  /* The Log Position */
> +  uint32_t t_log_start;                      /* Where this transaction 
> starts in the ring */
> +  uint32_t t_nr_blocks;                      /* How many blocks it consumes 
> */
> +
> +  uint32_t t_updates;                        /* Refcount: How many threads 
> are in this transaction? */
> +  
> +  /* The Payload (The Shadow Buffers) */
> +  journal_buffer_t *t_buffers;               /* Linked List of dirty blocks 
> */
> +  int t_buffer_count;
> +
> +  /* Timing/Debug */
> +  long t_start_time;
> +};
> +
> +/* The Simple Mapper (Virtual -> Physical) */
> +typedef struct journal_map
> +{
> +  block_t *phys_blocks;                      /* The 64KB array we malloc'd */
> +  uint32_t total_blocks;             /* 16384 */
> +  struct node *inode;                        /* Inode 8 (for keeping ref) */
> +} journal_map_t;
> +
> +/* The Grand Abstraction */
> +typedef struct journal
> +{
> +  /* The Physics of it (The Map) */
> +  journal_map_t map;
> +
> +  /* The Ring Buffer State (The Logic) */
> +  uint32_t j_head;                   /* Where we are writing next */
> +  uint32_t j_tail;                   /* The oldest live transaction 
> (checkpoint) */
> +  uint32_t j_first;                  /* First block of data (usually 1, 
> after SB) */
> +  uint32_t j_last;                   /* Last block of data */
> +  uint32_t j_free;                   /* How many blocks left? */
> +
> +  /* The Sequence Counter */
> +  uint32_t j_transaction_sequence;   /* Monotonic ID (e.g. 500, 501...) */
> +
> +  pthread_mutex_t j_state_lock;      /* Protects the pointers below */
> +  /* The Transactions */
> +  struct journal_transaction *j_running_transaction; /* Currently filling */
> +  struct journal_transaction *j_committing_transaction;      /* Flushing to 
> journal */
> +
> +} journal_t;
> +
> +static void
> +init_map (journal_t *journal, struct node *jnode)
> +{
> +  journal->map.total_blocks = jnode->allocsize / block_size;
> +  journal->map.phys_blocks =
> +    malloc (journal->map.total_blocks * sizeof (block_t));
> +  if (!journal->map.phys_blocks)
> +    ext2_panic ("No RAM for journal map");
> +
> +  for (uint32_t i = 0; i < journal->map.total_blocks; i++)
> +    {
> +      block_t phys = 0;
> +
> +      /* ext2_getblk handles the indirect blocks/fragmentation. */
> +      error_t err = ext2_getblk (jnode, i, 0, &phys);
> +
> +      if (err || phys == 0)
> +     {
> +       ext2_panic ("[JOURNAL] Gap in journal file at logical %u!", i);
> +     }
> +
> +      journal->map.phys_blocks[i] = phys;
> +    }
> +
> +  journal->map.inode = jnode;
> +}
> +
> +static void
> +destroy_map (journal_t *journal)
> +{
> +  free (journal->map.phys_blocks);
> +  journal->map.total_blocks = 0;
> +  if (journal->map.inode)
> +    diskfs_nput (journal->map.inode);
> +}
> +
> +static void *
> +kjournald_thread (void *arg)
> +{
> +  journal_t *journal = (journal_t *) arg;
> +  while (1)
> +    {
> +      sleep (5);
> +
> +      if (journal->j_running_transaction)
> +     {
> +       JRNL_LOG_DEBUG ("Woke the journal up:\n"
> +                       " - Sequence: %u\n"
> +                       " - Start (Head): %u\n"
> +                       " - First Data Block: %u\n"
> +                       " - Total Blocks: %u",
> +                       journal->j_transaction_sequence, journal->j_head,
> +                       journal->j_first, journal->j_last);
> +
> +       // "Lightweight" commit - only writes the log
> +       journal_commit_transaction (journal);
> +     }
> +    }
> +  return NULL;
> +}
> +
> +static block_t
> +get_journal_phys_block (journal_t *journal, uint32_t idx)
> +{
> +  assert_backtrace (idx < journal->map.total_blocks);
> +  return journal->map.phys_blocks[idx];
> +}
> +
> +/* Centralized logic to map FS Block -> Store Offset */
> +static store_offset_t
> +journal_map_offset (journal_t *journal, uint32_t logical_idx)
> +{
> +  block_t phys_block = get_journal_phys_block (journal, logical_idx);
> +  return phys_block << (log2_block_size - store->log2_block_size);
> +}
> +
> +/**
> + * Reads the JBD2 superblock (Block 0 of the journal file)
> + * and initializes the journal_t state.
> + */
> +error_t
> +journal_load_superblock (journal_t *journal)
> +{
> +  error_t err;
> +  journal_superblock_t *jsb;
> +  void *buf;
> +
> +  buf = malloc (block_size);
> +  if (!buf)
> +    return ENOMEM;
> +
> +  /* journal_read_block handles all the store_read/vm_deallocate logic 
> internally */
> +  err = journal_read_block (journal, 0, buf);
> +
> +  if (err)
> +    {
> +      JRNL_LOG_DEBUG ("[JOURNAL] Failed to read SB. Err: %s", strerror 
> (err));
> +      free (buf);
> +      return err;
> +    }
> +
> +  /* Interpret as JBD2 Superblock and verify */
> +  jsb = (journal_superblock_t *) buf;
> +  uint32_t magic = be32toh (jsb->s_header[0]);
> +  uint32_t type = be32toh (jsb->s_header[1]);
> +  if (magic != JBD2_MAGIC_NUMBER)
> +    {
> +      ext2_warning ("[JOURNAL] Invalid Magic: %x (Expected %x)", magic,
> +                 JBD2_MAGIC_NUMBER);
> +      free (buf);
> +      return EINVAL;
> +    }
> +  if (type != JBD2_SUPERBLOCK_V2 && type != JBD2_SUPERBLOCK_V1)
> +    {
> +      ext2_warning ("[JOURNAL] Invalid SB Type: %d", type);
> +      free (buf);
> +      return EINVAL;
> +    }
> +
> +  /* Populate Journal Struct */
> +  journal->j_first = be32toh (jsb->s_first);
> +  journal->j_last = be32toh (jsb->s_maxlen);
> +  journal->j_head = be32toh (jsb->s_start);
> +  journal->j_tail = journal->j_head;
> +  journal->j_transaction_sequence = be32toh (jsb->s_sequence);
> +
> +  /* Validate blocksize */
> +  uint32_t j_bsize = be32toh (jsb->s_blocksize);
> +  if (j_bsize != block_size)
> +    {
> +      ext2_warning ("[JOURNAL] Blocksize mismatch! Journal: %u, FS: %u",
> +                 j_bsize, block_size);
> +      free (buf);
> +      return EINVAL;
> +    }
> +
> +  JRNL_LOG_DEBUG ("Loaded JBD2 Superblock:\n"
> +               " - Sequence: %u\n"
> +               " - Start (Head): %u\n"
> +               " - First Data Block: %u\n"
> +               " - Total Blocks: %u",
> +               journal->j_transaction_sequence, journal->j_head,
> +               journal->j_first, journal->j_last);
> +
> +  free (buf);
> +  return 0;
> +}
> +
> +error_t
> +journal_update_superblock (journal_t *journal, uint32_t sequence,
> +                        uint32_t start)
> +{
> +  void *buf;
> +  journal_superblock_t *jsb;
> +  error_t err;
> +
> +  buf = malloc (block_size);
> +  if (!buf)
> +    return ENOMEM;
> +
> +  /* Read existing SB to preserve UUID/Features */
> +  err = journal_read_block (journal, 0, buf);
> +  if (err)
> +    {
> +      JRNL_LOG_DEBUG ("[SB] Critical: Failed to read SB. Aborting update.");
> +      free (buf);
> +      return err;
> +    }
> +
> +  jsb = (journal_superblock_t *) buf;
> +
> +  /* Sanity Check Magic (Don't overwrite garbage with garbage) */
> +  if (jsb->s_header[0] != htobe32 (JBD2_MAGIC_NUMBER))
> +    {
> +      JRNL_LOG_DEBUG ("[SB] Critical: On-disk magic invalid. Aborting.");
> +      free (buf);
> +      return EIO;
> +    }
> +
> +  /* Update Dynamic Fields */
> +  jsb->s_sequence = htobe32 (sequence);
> +  jsb->s_start = htobe32 (start);
> +
> +  /* Ensure maxlen matches the map (self-correction) */
> +  jsb->s_maxlen = htobe32 (journal->map.total_blocks);
> +
> +  JRNL_LOG_DEBUG ("[SB] Updating: Seq %u, Head %u", sequence, start);
> +
> +  /* Write Back */
> +  err = journal_write_block (journal, 0, buf);
> +
> +  free (buf);
> +  return err;
> +}
> +
> +journal_t *
> +journal_create (struct node *journal_inode)
> +{
> +  journal_t *j = calloc (1, sizeof (struct journal));
> +  if (!j)
> +    ext2_panic ("[JOURNAL] Cannot create journal struct.");
> +
> +  init_map (j, journal_inode);
> +
> +  /* Take ownership of the inode ref */
> +  diskfs_nref (journal_inode);
> +
> +  /* Set generic defaults (Will be overwritten by Superblock read later) */
> +  j->j_first = 1;            /* Skip SB block by default */
> +  j->j_last = j->map.total_blocks - 1;
> +  j->j_free = j->j_last - j->j_first;
> +
> +  if (journal_load_superblock (j) != 0)
> +    {
> +      ext2_panic ("[JOURNAL] Failed to load superblock!");
> +    }
> +  pthread_mutex_init (&j->j_state_lock, NULL);
> +
> +  if (pthread_create (&kjournald_tid, NULL, kjournald_thread, j) != 0)
> +    {
> +      JRNL_LOG_DEBUG ("Failed to create a flusher thread.");
> +    }
> +  else
> +    {
> +      JRNL_LOG_DEBUG ("Created flusher thread.");
> +    }
> +  return j;
> +}
> +
> +/**
> + * Writes a full filesystem block (4096 bytes) to the journal.
> + * Handles the Logical -> Physical -> Store Offset conversion.
> + */
> +error_t
> +journal_write_block (journal_t *journal, uint32_t logical_idx, void *data)
> +{
> +  store_offset_t offset;
> +  size_t written_amount = 0;
> +  error_t err;
> +
> +  /* Safety Check */
> +  if (logical_idx >= journal->map.total_blocks)
> +    {
> +      ext2_warning ("[JOURNAL] Write out of bounds! Index: %u, Max: %u",
> +                 logical_idx, journal->map.total_blocks);
> +      return EINVAL;
> +    }
> +
> +  offset = journal_map_offset (journal, logical_idx);
> +  err = store_write (store, offset, data, block_size, &written_amount);
> +
> +  if (err)
> +    {
> +      JRNL_LOG_DEBUG
> +     ("[JOURNAL] Write failed at logical %u. Err: %s",
> +      logical_idx, strerror (err));
> +      return err;
> +    }
> +
> +  if (written_amount != block_size)
> +    {
> +      JRNL_LOG_DEBUG ("[JOURNAL] Short write! Wanted %u, wrote %lu",
> +                   block_size, written_amount);
> +      return EIO;
> +    }
> +
> +  return 0;
> +}
> +
> +/**
> + * Reads a full filesystem block (4096 bytes) from the journal into 
> 'out_buf'.
> + * out_buf must be at least block_size bytes.
> + */
> +error_t
> +journal_read_block (journal_t *journal, uint32_t logical_idx, void *out_buf)
> +{
> +  store_offset_t offset;
> +  size_t read_amount = 0;
> +  void *temp_buf = NULL;
> +  error_t err;
> +
> +  if (!out_buf)
> +    return EINVAL;
> +
> +  if (logical_idx >= journal->map.total_blocks)
> +    {
> +      ext2_warning ("[JOURNAL] Read out of bounds! Index: %u, Max: %u",
> +                 logical_idx, journal->map.total_blocks);
> +      return EINVAL;
> +    }
> +
> +  offset = journal_map_offset (journal, logical_idx);
> +
> +  err = store_read (store, offset, block_size, &temp_buf, &read_amount);
> +
> +  if (err)
> +    {
> +      if (temp_buf)
> +     vm_deallocate (mach_task_self (), (vm_address_t) temp_buf,
> +                    read_amount);
> +      return err;
> +    }
> +
> +  if (read_amount != block_size)
> +    {
> +      JRNL_LOG_DEBUG ("[JOURNAL] Short read! Wanted %u, got %lu", block_size,
> +                   read_amount);
> +      if (temp_buf)
> +     vm_deallocate (mach_task_self (), (vm_address_t) temp_buf,
> +                    read_amount);
> +      return EIO;
> +    }
> +
> +  memcpy (out_buf, temp_buf, block_size);
> +  vm_deallocate (mach_task_self (), (vm_address_t) temp_buf, read_amount);
> +  return 0;
> +}
> +
> +void
> +journal_destroy (journal_t *journal)
> +{
> +  destroy_map (journal);
> +  pthread_mutex_destroy (&journal->j_state_lock);
> +
> +  free (journal);
> +}
> +
> +/**
> + * Called when we are running out of space.
> + * Since we do a version of sync() on every commit, we can safely declare 
> all 
> + * previous transactions "checkpointed" and reset the log.
> + */
> +static void
> +journal_force_checkpoint (journal_t *journal, uint32_t current_tid)
> +{
> +  JRNL_LOG_DEBUG
> +    ("[CHECKPOINT] Journal Full! Forcing Global Sync & Reset...");
> +
> +  journal_sync_everything ();
> +
> +  journal->j_tail = journal->j_head;
> +  journal->j_free = journal->j_last - journal->j_first;
> +
> +  /* Update Superblock */
> +  journal_update_superblock (journal, current_tid,
> +                          journal->j_head);
> +  JRNL_LOG_DEBUG ("[CHECKPOINT] after superblock...");
> +
> +  JRNL_LOG_DEBUG
> +    ("[CHECKPOINT] Reset complete. Tail moved to %u. Free space restored.",
> +     journal->j_tail);
> +  sync_global (1);           // Make sure the superblock hit the disk
> +}
> +
> +static uint32_t
> +journal_next_log_block (journal_t *journal)
> +{
> +  journal->j_head++;
> +  if (journal->j_head > journal->j_last)
> +    {
> +      journal->j_head = journal->j_first;
> +    }
> +  journal->j_free--;
> +  return journal->j_head;
> +}
> +
> +/* Helper to calculate where the next block is, handling the ring buffer 
> wrap.
> +   Must match journal_next_log_block logic exactly! */
> +static uint32_t
> +journal_next_after (journal_t *journal, uint32_t current_block)
> +{
> +  uint32_t next = current_block + 1;
> +  /* Wrap around to the first usable block */
> +  if (next > journal->j_last)
> +    next = journal->j_first;
> +  return next;
> +}
> +
> +error_t
> +journal_commit_transaction (journal_t *journal)
> +{
> +  struct journal_transaction *txn;
> +  void *descriptor_buf = NULL, *commit_buf = NULL;
> +  journal_header_t *hdr;
> +  error_t err;
> +  uint32_t descriptor_loc, commit_loc;
> +  uint32_t tag_offset;
> +  journal_buffer_t *jb;
> +
> +  pthread_mutex_lock (&journal->j_state_lock);
> +  txn = journal->j_running_transaction;
> +
> +  if (!txn || txn->t_state != T_RUNNING)
> +    {
> +      pthread_mutex_unlock (&journal->j_state_lock);
> +      return EINVAL;
> +    }
> +
> +  /* Detach from global state (Stop NEW writers) */
> +  journal->j_running_transaction = NULL;
> +  txn->t_state = T_LOCKED;
> +
> +  journal->j_transaction_sequence = txn->t_tid + 1;
> +
> +  /* DRAIN WRITERS */
> +  while (txn->t_updates > 0)
> +    {
> +      JRNL_LOG_DEBUG ("[COMMIT] Waiting for %u active handles...",
> +                   txn->t_updates);
> +      pthread_mutex_unlock (&journal->j_state_lock);
> +      sched_yield ();
> +      pthread_mutex_lock (&journal->j_state_lock);
> +    }
> +
> +  txn->t_state = T_FLUSHING;
> +
> +  /* RESERVATION */
> +  if (journal->j_free < txn->t_nr_blocks + 50)
> +    {
> +      if (txn->t_nr_blocks > (journal->j_last - journal->j_first))
> +     {
> +       ext2_panic ("[COMMIT] Transaction too huge (%u) for journal!",
> +                   txn->t_nr_blocks);
> +     }
> +
> +      journal_force_checkpoint (journal, txn->t_tid);
> +    }
> +  /* Reserve Descriptor */
> +  descriptor_loc = journal_next_log_block (journal);
> +
> +  /* Reserve Data */
> +  jb = txn->t_buffers;
> +  uint32_t expected = journal_next_after (journal, descriptor_loc);
> +
> +  while (jb)
> +    {
> +      jb->jb_log_spot = journal_next_log_block (journal);
> +      if (jb->jb_log_spot != expected)
> +     {
> +       ext2_panic ("[COMMIT] Layout Logic Error! Expected %u got %u",
> +                   expected, jb->jb_log_spot);
> +     }
> +      expected = journal_next_after (journal, expected);
> +
> +      jb = jb->jb_next;
> +    }
> +
> +  /* Reserve Commit */
> +  commit_loc = journal_next_log_block (journal);
> +  if (commit_loc != expected)
> +    {
> +      ext2_panic ("[COMMIT] Layout Logic Error! Commit expected %u got %u",
> +               expected, commit_loc);
> +    }
> +
> +  pthread_mutex_unlock (&journal->j_state_lock);
> +
> +
> +  /* WRITE DESCRIPTOR FIRST */
> +  descriptor_buf = calloc (1, block_size);
> +  if (!descriptor_buf)
> +    {
> +      err = ENOMEM;
> +      goto out;
> +    }
> +
> +  hdr = (journal_header_t *) descriptor_buf;
> +  hdr->h_magic = htobe32 (JBD2_MAGIC_NUMBER);
> +  hdr->h_blocktype = htobe32 (JBD2_DESCRIPTOR_BLOCK);
> +  hdr->h_sequence = htobe32 (txn->t_tid);
> +
> +  tag_offset = sizeof (journal_header_t);
> +  jb = txn->t_buffers;
> +
> +  while (jb)
> +    {
> +      if (tag_offset + sizeof (journal_block_tag_t) > block_size)
> +     {
> +       err = E2BIG;
> +       goto out;
> +     }
> +
> +      journal_block_tag_t *tag =
> +     (journal_block_tag_t *) ((char *) descriptor_buf + tag_offset);
> +      tag->t_blocknr = htobe32 (jb->jb_blocknr);
> +
> +      uint32_t flags = JBD2_FLAG_SAME_UUID;
> +      if (jb->jb_next == NULL)
> +     flags |= JBD2_FLAG_LAST_TAG;
> +      tag->t_flags = htobe32 (flags);
> +
> +      jb = jb->jb_next;
> +      tag_offset += sizeof (journal_block_tag_t);
> +    }
> +
> +  JRNL_LOG_DEBUG ("[COMMIT] Writing Descriptor to %u", descriptor_loc);
> +  err = journal_write_block (journal, descriptor_loc, descriptor_buf);
> +  free (descriptor_buf);
> +  descriptor_buf = NULL;
> +  if (err)
> +    goto out;
> +
> +
> +  /* WRITE DATA BLOCKS NEXT */
> +  jb = txn->t_buffers;
> +  while (jb)
> +    {
> +      err =
> +     journal_write_block (journal, jb->jb_log_spot, jb->jb_shadow_data);
> +      if (err)
> +     goto out;
> +      jb = jb->jb_next;
> +    }
> +
> +
> +  /* ONLY THEN WRITE A COMMIT BLOCK */
> +  commit_buf = calloc (1, block_size);
> +  if (!commit_buf)
> +    {
> +      err = ENOMEM;
> +      goto out;
> +    }
> +
> +  hdr = (journal_header_t *) commit_buf;
> +  hdr->h_magic = htobe32 (JBD2_MAGIC_NUMBER);
> +  hdr->h_blocktype = htobe32 (JBD2_COMMIT_BLOCK);
> +  hdr->h_sequence = htobe32 (txn->t_tid);
> +
> +  JRNL_LOG_DEBUG ("[COMMIT] Writing Commit Block at %u", commit_loc);
> +  err = journal_write_block (journal, commit_loc, commit_buf);
> +  JRNL_LOG_DEBUG ("[COMMIT] Done with Commit Block at %u", commit_loc);
> +  free (commit_buf);
> +  commit_buf = NULL;
> +
> +  /* BARRIER & UPDATE at the end */
> +  if (!err)
> +    {
> +      journal_sync_everything ();
> +      if (journal->j_tail == 0)
> +     {
> +       JRNL_LOG_DEBUG ("[COMMIT] First Time: Anchoring Tail at Block %u",
> +                       journal->j_first);
> +
> +       journal_update_superblock (journal, txn->t_tid, journal->j_first);    
> /* Block 1 */
> +       sync_global (1);
> +
> +       journal->j_tail = journal->j_first;
> +     }
> +    }
> +
> +out:
> +  if (descriptor_buf)
> +    free (descriptor_buf);
> +  if (commit_buf)
> +    free (commit_buf);
> +
> +  journal_buffer_t *curr = txn->t_buffers;
> +  while (curr)
> +    {
> +      journal_buffer_t *next = curr->jb_next;
> +      free (curr->jb_shadow_data);
> +      free (curr);
> +      curr = next;
> +    }
> +  free (txn);
> +
> +  return err;
> +}
> +
> +/**
> + * Ensures there is a VALID running transaction to attach to.
> + * Returns 0 on success, or error code.
> + */
> +error_t
> +journal_start_transaction (journal_t *journal)
> +{
> +  struct journal_transaction *txn;
> +
> +  if (!journal)
> +    return EINVAL;
> +  pthread_mutex_lock (&journal->j_state_lock);
> +  txn = journal->j_running_transaction;
> +  if (txn && txn->t_state == T_RUNNING)
> +    {
> +      txn->t_updates++;
> +    }
> +  else
> +    {
> +      /* TODO: Is the *previous* transaction still committing?
> +         If so, we might need to wait if we run out of space.
> +         For now, assume we have infinite space.) */
> +      txn = calloc (1, sizeof (struct journal_transaction));
> +      if (!txn)
> +     {
> +       pthread_mutex_unlock (&journal->j_state_lock);
> +       return ENOMEM;
> +     }
> +
> +      txn->t_tid = journal->j_transaction_sequence;
> +      txn->t_state = T_RUNNING;
> +      txn->t_updates = 1;
> +
> +      journal->j_running_transaction = txn;
> +      JRNL_LOG_DEBUG ("[TRX] Created NEW TID %u", txn->t_tid);
> +    }
> +
> +  pthread_mutex_unlock (&journal->j_state_lock);
> +  return 0;
> +}
> +
> +void
> +journal_stop_transaction (journal_t *journal)
> +{
> +  struct journal_transaction *txn;
> +
> +  if (!journal)
> +    return;
> +
> +  pthread_mutex_lock (&journal->j_state_lock);
> +
> +  txn = journal->j_running_transaction;
> +  if (!txn)
> +    {
> +      ext2_warning
> +     ("[TRX] stop_transaction called but no transaction running!");
> +      pthread_mutex_unlock (&journal->j_state_lock);
> +      return;
> +    }
> +
> +  txn->t_updates--;
> +  pthread_mutex_unlock (&journal->j_state_lock);
> +}
> +
> +/**
> + * Adds a modified filesystem block to the current running transaction.
> + * Performs a "Shadow Copy" of the data immediately.
> + */
> +error_t
> +journal_dirty_block (journal_t *journal, block_t fs_blocknr, const void 
> *data)
> +{
> +  struct journal_transaction *txn;
> +  journal_buffer_t *jb;
> +  journal_buffer_t *new_jb;
> +
> +  if (!journal || !data)
> +    return EINVAL;
> +
> +  pthread_mutex_lock (&journal->j_state_lock);
> +
> +  txn = journal->j_running_transaction;
> +
> +  if (!txn || txn->t_state != T_RUNNING)
> +    {
> +      JRNL_LOG_DEBUG
> +     ("[ERROR] journal_dirty_block called outside of transaction!");
> +      pthread_mutex_unlock (&journal->j_state_lock);
> +      return EPERM;
> +    }
> +
> +  /* TODO: Optimization: linear scan is fine for now. In prod use hash 
> tables. */
> +  jb = txn->t_buffers;
> +  while (jb)
> +    {
> +      if (jb->jb_blocknr == fs_blocknr)
> +     {
> +       memcpy (jb->jb_shadow_data, data, block_size);
> +       pthread_mutex_unlock (&journal->j_state_lock);
> +       return 0;
> +     }
> +      jb = jb->jb_next;
> +    }
> +
> +  new_jb = malloc (sizeof (journal_buffer_t));
> +  if (!new_jb)
> +    {
> +      pthread_mutex_unlock (&journal->j_state_lock);
> +      return ENOMEM;
> +    }
> +
> +  new_jb->jb_shadow_data = malloc (block_size);
> +  if (!new_jb->jb_shadow_data)
> +    {
> +      free (new_jb);
> +      pthread_mutex_unlock (&journal->j_state_lock);
> +      return ENOMEM;
> +    }
> +
> +  new_jb->jb_blocknr = fs_blocknr;
> +  memcpy (new_jb->jb_shadow_data, data, block_size);
> +
> +  new_jb->jb_next = txn->t_buffers;
> +  txn->t_buffers = new_jb;
> +  txn->t_buffer_count++;
> +  txn->t_nr_blocks++;
> +
> +  pthread_mutex_unlock (&journal->j_state_lock);
> +  return 0;
> +}
> +
> +/** 
> + * Check if a specific filesystem block is currently part of the
> + * Running Transaction.
> + * * Returns: 1 if the block is "pinned" (must not be written to disk yet),
> + * 0 if it is safe to write.
> + */
> +int
> +journal_block_is_active (journal_t *journal, block_t blocknr)
> +{
> +  struct journal_transaction *txn;
> +  journal_buffer_t *jb;
> +  int is_active = 0;
> +
> +  if (!journal)
> +    return 0;
> +
> +  pthread_mutex_lock (&journal->j_state_lock);
> +  txn = journal->j_running_transaction;
> +
> +  if (txn && txn->t_state == T_RUNNING)
> +    {
> +      /** TODO: hash map please! */
> +      jb = txn->t_buffers;
> +      while (jb)
> +     {
> +       if (jb->jb_blocknr == blocknr)
> +         {
> +           is_active = 1;
> +           break;
> +         }
> +       jb = jb->jb_next;
> +     }
> +    }
> +
> +  pthread_mutex_unlock (&journal->j_state_lock);
> +  return is_active;
> +}
> diff --git a/ext2fs/journal.h b/ext2fs/journal.h
> new file mode 100644
> index 00000000..b02e5791
> --- /dev/null
> +++ b/ext2fs/journal.h
> @@ -0,0 +1,62 @@
> +/* JBD2 binary compliant journal driver. 
> +
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   Written by Milos Nikic.
> +
> +   Converted for ext2fs by Miles Bader <[email protected]>
> +
> +   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, 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. */
> +
> +#ifndef  _JOURNAL_H
> +#define _JOURNAL_H
> +
> +#include <stdio.h>
> +
> +#include "ext2fs.h"
> +
> +#ifndef JOURNAL_DEBUG
> +#define JOURNAL_DEBUG 0              /* Set to enable (very chatty) debug 
> messages. */
> +#endif
> +
> +#if JOURNAL_DEBUG
> +#define JRNL_LOG_DEBUG(fmt, ...)                                          \
> +     do {                                                                    
>    \
> +             fprintf(stderr, "[JRNL][DEBUG] " fmt "\n", ##__VA_ARGS__);      
>      \
> +             fflush(stderr);                                                 
>         \
> +     } while (0)
> +#else
> +#define JRNL_LOG_DEBUG(fmt, ...) do { } while (0)
> +#endif
> +
> +/* Forward declaration only. The struct contents are hidden. */
> +typedef struct journal journal_t;
> +
> +journal_t *journal_create (struct node *journal_inode);
> +error_t
> +journal_write_block (journal_t * journal, uint32_t logical_idx, void *data);
> +error_t
> +journal_read_block (journal_t * journal, uint32_t logical_idx, void 
> *out_buf);
> +void journal_destroy (journal_t * journal);
> +
> +error_t journal_start_transaction (journal_t * journal);
> +error_t journal_commit_transaction (journal_t * journal);
> +void journal_stop_transaction (journal_t * journal);
> +
> +error_t
> +journal_dirty_block (journal_t * journal, block_t fs_blocknr,
> +                  const void *data);
> +int journal_block_is_active (journal_t * journal, block_t blocknr);
> +
> +#endif //_JOURNAL_H
> diff --git a/ext2fs/pager.c b/ext2fs/pager.c
> index a7801bea..f76b6531 100644
> --- a/ext2fs/pager.c
> +++ b/ext2fs/pager.c
> @@ -25,6 +25,7 @@
>  #include <inttypes.h>
>  #include <hurd/store.h>
>  #include "ext2fs.h"
> +#include "journal.h"
>  
>  /* XXX */
>  #include "../libpager/priv.h"
> @@ -648,6 +649,11 @@ disk_pager_write_page (vm_offset_t page, void *buf)
>        while (length > 0 && !err)
>       {
>         block_t block = boffs_block (offset);
> +       if (ext2_journal && journal_block_is_active(ext2_journal, block))
> +         {
> +            JRNL_LOG_DEBUG ("Pageout conflict on Block %u -> Forcing 
> Commit", block);
> +            journal_commit_transaction(ext2_journal);
> +         }
>  
>         /* We don't clear the block modified bit here because this paging
>            write request may not be the same one that actually set the bit,
> @@ -1580,6 +1586,23 @@ diskfs_shutdown_pager (void)
>       pager, just make sure it's synced. */
>  }
>  
> +static error_t 
> +journal_sync_one (void *v_p)
> +{
> +  struct pager *p = v_p;
> +  pager_sync (p, 1);
> +  return 0;
> +}
> +
> +/* Sync all the pagers synchronously. */
> +void
> +journal_sync_everything (void)
> +{
> +  write_all_disknodes ();
> +  ports_bucket_iterate (file_pager_bucket, journal_sync_one);
> +  sync_global (1);
> +}
> +
>  /* Sync all the pagers. */
>  void
>  diskfs_sync_everything (int wait)
> @@ -1591,6 +1614,14 @@ diskfs_sync_everything (int wait)
>        return 0;
>      }
>  
> +  if (ext2_journal)
> +    {
> +      /* We only commit if we have a running transaction */
> +      journal_commit_transaction (ext2_journal);
> +      
> +      /* Checkpoint: In a real system, we would flush the journal to the FS 
> here.
> +         For now, we rely on the standard paging below to do it lazily. */
> +    }
>    write_all_disknodes ();
>    ports_bucket_iterate (file_pager_bucket, sync_one);
>  
> diff --git a/libdiskfs/diskfs.h b/libdiskfs/diskfs.h
> index 5f832dd7..7f420725 100644
> --- a/libdiskfs/diskfs.h
> +++ b/libdiskfs/diskfs.h
> @@ -507,6 +507,11 @@ error_t diskfs_validate_flags_change (struct node *np, 
> int flags);
>     changed to RDEV; otherwise return an error code. */
>  error_t diskfs_validate_rdev_change (struct node *np, dev_t rdev);
>  
> +/* The user may define this function.  It is called immediately when
> +   a node's metadata (stat info) is modified in memory, even if
> +   diskfs_synchronous is false.  The default definition does nothing. */
> +void diskfs_notify_change (struct node *np);
> +
>  /* The user must define this function.  Sync the info in NP->dn_stat
>     and any associated format-specific information to disk.  If WAIT is true,
>     then return only after the physicial media has been completely updated. */
> diff --git a/libdiskfs/node-modified.c b/libdiskfs/node-modified.c
> new file mode 100644
> index 00000000..a29dc39f
> --- /dev/null
> +++ b/libdiskfs/node-modified.c
> @@ -0,0 +1,28 @@
> +/* Default version of diskfs_notify_change
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   Written by Milos Nikic.
> +
> +   This file is part of the GNU Hurd.
> +
> +   The GNU Hurd 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, or (at
> +   your option) any later version.
> +
> +   The GNU Hurd 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA. */
> +
> +#include "priv.h"
> +#include "diskfs.h"
> +
> +void __attribute__ ((weak))
> +diskfs_notify_change (struct node *np)
> +{
> +  // default function does nothing.
> +}
> diff --git a/libdiskfs/priv.h b/libdiskfs/priv.h
> index ca3c23ca..e8186d49 100644
> --- a/libdiskfs/priv.h
> +++ b/libdiskfs/priv.h
> @@ -140,7 +140,13 @@ extern fshelp_fetch_root_callback2_t 
> _diskfs_translator_callback2;
>    pthread_mutex_lock (&np->lock);                                        \
>    (OPERATION);                                                               
>     \
>    if (diskfs_synchronous)                                                \
> +   {                                                                         
>     \
>      diskfs_node_update (np, 1);                                              
>     \
> +   }                                                                         
>     \
> +  else                                                                   \
> +   {                                                                         
>     \
> +    diskfs_notify_change (np);                                           \
> +   }                                                                         
>     \
>    pthread_mutex_unlock (&np->lock);                                      \
>    return err;                                                                
>     \
>  })
> -- 
> 2.52.0
> 


-- 
Samuel
<T> csp.tar.gz:     ascii text
 -+- #ens-mim - vive les browsers qui prennent des initiatives à la con -+-


Reply via email to