Linux XFS filesystem development
 help / color / mirror / Atom feed
From: "Darrick J. Wong" <djwong@kernel.org>
To: Luca Di Maio <luca.dimaio1@gmail.com>
Cc: linux-xfs@vger.kernel.org, dimitri.ledkov@chainguard.dev,
	smoser@chainguard.dev
Subject: Re: [PATCH v2] libxfs: support reproducible filesystems using deterministic time/seed
Date: Mon, 10 Nov 2025 10:01:49 -0800	[thread overview]
Message-ID: <20251110180149.GO196370@frogsfrogsfrogs> (raw)
In-Reply-To: <20251108143953.4189618-1-luca.dimaio1@gmail.com>

On Sat, Nov 08, 2025 at 03:39:53PM +0100, Luca Di Maio wrote:
> Add support for reproducible filesystem creation through two environment
> variables that enable deterministic behavior when building XFS filesystems.
> 
> SOURCE_DATE_EPOCH support:
> When SOURCE_DATE_EPOCH is set, use its value for all filesystem timestamps
> instead of the current time. This follows the reproducible builds
> specification (https://reproducible-builds.org/specs/source-date-epoch/)
> and ensures consistent inode timestamps across builds.
> 
> DETERMINISTIC_SEED support:
> When DETERMINISTIC_SEED=1 is set, return a fixed seed value (0x53454544 =
> "SEED") from get_random_u32() instead of reading from /dev/urandom.
> 
> get_random_u32() seems to be used mostly to set inode generation number, being
> fixed should not be create collision issues at mkfs time.
> 
> The implementation introduces two helper functions to minimize changes
> to existing code:
> 
> - current_fixed_time(): Parses and caches SOURCE_DATE_EPOCH on first
>   call. Returns fixed timestamp when set, falls back to gettimeofday() on
>   parse errors or when unset.
> - get_deterministic_seed(): Checks for DETERMINISTIC_SEED=1 environment
>   variable on first call, and returns a fixed seed value (0x53454544).
>   Falls back to getrandom() when unset.
> - Both helpers use one-time initialization to avoid repeated getenv() calls.
> - Both quickly exit and noop if environment is not set or has invalid
>   variables, falling back to original behaviour.
> 
> Example usage:
>   SOURCE_DATE_EPOCH=1234567890 \
>   DETERMINISTIC_SEED=1 \
>   mkfs.xfs \
> 	-m uuid=$EXAMPLE_UUID \
> 	-p file=./rootfs \
> 	disk1.img
> 
> This enables distributions and build systems to create bit-for-bit
> identical XFS filesystems when needed for verification and debugging.
> 
> v1 -> v2:
> - simplify deterministic seed by returning a fixed value instead
>   of using Middle Square Weyl Sequence PRNG
> - fix timestamp type time_t -> time64_t
> - fix timestamp initialization flag to allow negative epochs
> - fix timestamp conversion type using strtoll
> - fix timestamp conversion check to be sure the whole string was parsed
> - print warning message when SOURCE_DATE_EPOCH is invalid
> 
> Signed-off-by: Luca Di Maio <luca.dimaio1@gmail.com>

Looks good to me,
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

--D

> ---
>  libxfs/util.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 114 insertions(+)
> 
> diff --git a/libxfs/util.c b/libxfs/util.c
> index 3597850d..f6af4531 100644
> --- a/libxfs/util.c
> +++ b/libxfs/util.c
> @@ -137,12 +137,76 @@ xfs_log_calc_unit_res(
>  	return unit_bytes;
>  }
>  
> +/*
> + * current_fixed_time() tries to detect if SOURCE_DATE_EPOCH is in our
> + * environment, and set input timespec's timestamp to that value.
> + *
> + * Returns true on success, fail otherwise.
> + */
> +bool
> +current_fixed_time(
> +	struct			timespec64 *tv)
> +{
> +	/*
> +	 * To avoid many getenv() we'll use an initialization static flag, so
> +	 * we only read once.
> +	 */
> +	static bool		enabled = false;
> +	static bool		read_env = false;
> +	static time64_t		epoch;
> +	char			*endp;
> +	char			*source_date_epoch;
> +
> +	if (!read_env) {
> +		read_env = true;
> +		source_date_epoch = getenv("SOURCE_DATE_EPOCH");
> +		if (source_date_epoch && source_date_epoch[0] != '\0') {
> +			errno = 0;
> +			epoch = strtoll(source_date_epoch, &endp, 10);
> +			if (errno != 0 || *endp != '\0') {
> +				fprintf(stderr,
> +			"%s: SOURCE_DATE_EPOCH '%s' invalid timestamp, ignoring.\n",
> +				progname, source_date_epoch);
> +
> +				return false;
> +			}
> +
> +			enabled = true;
> +		}
> +	}
> +
> +	/*
> +	 * This will happen only if we successfully read a valid
> +	 * SOURCE_DATE_EPOCH and properly initiated the epoch value.
> +	 */
> +	if (read_env && enabled) {
> +		tv->tv_sec = epoch;
> +		tv->tv_nsec = 0;
> +		return true;
> +	}
> +
> +	/*
> +	 * We initialized but had no valid SOURCE_DATE_EPOCH so we fall back
> +	 * to regular behaviour.
> +	 */
> +	return false;
> +}
> +
>  struct timespec64
>  current_time(struct inode *inode)
>  {
>  	struct timespec64	tv;
>  	struct timeval		stv;
>  
> +	/*
> +	 * Check if we're creating a reproducible filesystem.
> +	 * In this case we try to parse our SOURCE_DATE_EPOCH from environment.
> +	 * If it fails, fall back to returning gettimeofday()
> +	 * like we used to do.
> +	 */
> +	if (current_fixed_time(&tv))
> +		return tv;
> +
>  	gettimeofday(&stv, (struct timezone *)0);
>  	tv.tv_sec = stv.tv_sec;
>  	tv.tv_nsec = stv.tv_usec * 1000;
> @@ -515,6 +579,49 @@ void xfs_dirattr_mark_sick(struct xfs_inode *ip, int whichfork) { }
>  void xfs_da_mark_sick(struct xfs_da_args *args) { }
>  void xfs_inode_mark_sick(struct xfs_inode *ip, unsigned int mask) { }
>  
> +/*
> + * get_deterministic_seed() tries to detect if DETERMINISTIC_SEED=1 is in our
> + * environment, and set our result to 0x53454544 (SEED) instead of
> + * extracting from getrandom().
> + *
> + * Returns true on success, fail otherwise.
> + */
> +bool
> +get_deterministic_seed(
> +	uint32_t	*result)
> +{
> +	/*
> +	 * To avoid many getenv() we'll use an initialization static flag, so
> +	 * we only read once.
> +	 */
> +	static bool	enabled = false;
> +	static bool	read_env = false;
> +	static uint32_t	deterministic_seed = 0x53454544; /* SEED */
> +	char		*seed_env;
> +
> +	if (!read_env) {
> +		read_env = true;
> +		seed_env = getenv("DETERMINISTIC_SEED");
> +		if (seed_env && strcmp(seed_env, "1") == 0)
> +			enabled = true;
> +	}
> +
> +	/*
> +	 * This will happen only if we successfully read DETERMINISTIC_SEED=1.
> +	 */
> +	if (read_env && enabled) {
> +		*result = deterministic_seed;
> +
> +		return true;
> +	}
> +
> +	/*
> +	 * We initialized but had no DETERMINISTIC_SEED=1 in env so we fall
> +	 * back to regular behaviour.
> +	 */
> +	return false;
> +}
> +
>  #ifdef HAVE_GETRANDOM_NONBLOCK
>  uint32_t
>  get_random_u32(void)
> @@ -522,6 +629,13 @@ get_random_u32(void)
>  	uint32_t	ret;
>  	ssize_t		sz;
>  
> +	/*
> +	 * Check for DETERMINISTIC_SEED in environment, it means we're
> +	 * creating a reproducible filesystem.
> +	 * If it fails, fall back to returning getrandom() like we used to do.
> +	 */
> +	if (get_deterministic_seed(&ret))
> +		return ret;
>  	/*
>  	 * Try to extract a u32 of randomness from /dev/urandom.  If that
>  	 * fails, fall back to returning zero like we used to do.
> -- 
> 2.51.2
> 
> 

  reply	other threads:[~2025-11-10 18:01 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-11-08 14:39 [PATCH v2] libxfs: support reproducible filesystems using deterministic time/seed Luca Di Maio
2025-11-10 18:01 ` Darrick J. Wong [this message]
2025-11-24  6:13 ` Christoph Hellwig
2025-11-24  9:38   ` Luca Di Maio

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20251110180149.GO196370@frogsfrogsfrogs \
    --to=djwong@kernel.org \
    --cc=dimitri.ledkov@chainguard.dev \
    --cc=linux-xfs@vger.kernel.org \
    --cc=luca.dimaio1@gmail.com \
    --cc=smoser@chainguard.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox