Ensure cache checksums are deterministic
Whilst working on the Reproducible Builds[0] effort, we noticed that fontconfig generates unreproducible cache files. This is due to fc-cache uses the modification timestamps of each directory in the "checksum" and "checksum_nano" members of the _FcCache struct. This is so that it can identify which cache files are valid and/or require regeneration. This patch changes the behaviour of the checksum calculations to prefer the value of the SOURCE_DATE_EPOCH[1] environment variable over the directory's own mtime. This variable can then be exported by build systems to ensure reproducible output. If SOURCE_DATE_EPOCH is not set or is newer than the mtime of the directory, the existing behaviour is unchanged. This work was sponsored by Tails[2]. [0] https://reproducible-builds.org/ [1] https://reproducible-builds.org/specs/source-date-epoch/ [2] https://tails.boum.org/
This commit is contained in:
parent
0b85e77ede
commit
f098adac54
|
@ -802,10 +802,14 @@ is used to specify the default language as the weak binding in the query. if thi
|
||||||
<emphasis>FONTCONFIG_USE_MMAP</emphasis>
|
<emphasis>FONTCONFIG_USE_MMAP</emphasis>
|
||||||
is used to control the use of mmap(2) for the cache files if available. this take a boolean value. fontconfig will checks if the cache files are stored on the filesystem that is safe to use mmap(2). explicitly setting this environment variable will causes skipping this check and enforce to use or not use mmap(2) anyway.
|
is used to control the use of mmap(2) for the cache files if available. this take a boolean value. fontconfig will checks if the cache files are stored on the filesystem that is safe to use mmap(2). explicitly setting this environment variable will causes skipping this check and enforce to use or not use mmap(2) anyway.
|
||||||
</para>
|
</para>
|
||||||
|
<para>
|
||||||
|
<emphasis>SOURCE_DATE_EPOCH</emphasis>
|
||||||
|
is used to ensure <literal>fc-cache(1)</literal> generates files in a deterministic manner in order to support reproducible builds. When set to a numeric representation of UNIX timestamp, fontconfig will prefer this value over using the modification timestamps of the input files in order to identify which cache files require regeneration. If <literal>SOURCE_DATE_EPOCH</literal> is not set (or is newer than the mtime of the directory), the existing behaviour is unchanged.
|
||||||
|
</para>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
<refsect1><title>See Also</title>
|
<refsect1><title>See Also</title>
|
||||||
<para>
|
<para>
|
||||||
fc-cat(1), fc-cache(1), fc-list(1), fc-match(1), fc-query(1)
|
fc-cat(1), fc-cache(1), fc-list(1), fc-match(1), fc-query(1), <ulink url="https://reproducible-builds.org/specs/source-date-epoch/">SOURCE_DATE_EPOCH</ulink>.
|
||||||
</para>
|
</para>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
<refsect1><title>Version</title>
|
<refsect1><title>Version</title>
|
||||||
|
|
|
@ -1015,6 +1015,55 @@ FcDirCacheLoadFile (const FcChar8 *cache_file, struct stat *file_stat)
|
||||||
return cache;
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
FcDirChecksum (struct stat *statb)
|
||||||
|
{
|
||||||
|
int ret = (int) statb->st_mtime;
|
||||||
|
char *endptr;
|
||||||
|
char *source_date_epoch;
|
||||||
|
unsigned long long epoch;
|
||||||
|
|
||||||
|
source_date_epoch = getenv("SOURCE_DATE_EPOCH");
|
||||||
|
if (source_date_epoch)
|
||||||
|
{
|
||||||
|
epoch = strtoull(source_date_epoch, &endptr, 10);
|
||||||
|
|
||||||
|
if (endptr == source_date_epoch)
|
||||||
|
fprintf (stderr,
|
||||||
|
"Fontconfig: SOURCE_DATE_EPOCH invalid\n");
|
||||||
|
else if ((errno == ERANGE && (epoch == ULLONG_MAX || epoch == 0))
|
||||||
|
|| (errno != 0 && epoch == 0))
|
||||||
|
fprintf (stderr,
|
||||||
|
"Fontconfig: SOURCE_DATE_EPOCH: strtoull: %s: %llu\n",
|
||||||
|
strerror(errno), epoch);
|
||||||
|
else if (*endptr != '\0')
|
||||||
|
fprintf (stderr,
|
||||||
|
"Fontconfig: SOURCE_DATE_EPOCH has trailing garbage\n");
|
||||||
|
else if (epoch > ULONG_MAX)
|
||||||
|
fprintf (stderr,
|
||||||
|
"Fontconfig: SOURCE_DATE_EPOCH must be <= %lu but saw: %llu\n",
|
||||||
|
ULONG_MAX, epoch);
|
||||||
|
else if (epoch < ret)
|
||||||
|
/* Only override if directory is newer */
|
||||||
|
ret = (int) epoch;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t
|
||||||
|
FcDirChecksumNano (struct stat *statb)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_STRUCT_STAT_ST_MTIM
|
||||||
|
/* No nanosecond component to parse */
|
||||||
|
if (getenv("SOURCE_DATE_EPOCH"))
|
||||||
|
return 0;
|
||||||
|
return statb->st_mtim.tv_nsec;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Validate a cache file by reading the header and checking
|
* Validate a cache file by reading the header and checking
|
||||||
* the magic number and the size field
|
* the magic number and the size field
|
||||||
|
@ -1033,10 +1082,10 @@ FcDirCacheValidateHelper (FcConfig *config, int fd, struct stat *fd_stat, struct
|
||||||
ret = FcFalse;
|
ret = FcFalse;
|
||||||
else if (fd_stat->st_size != c.size)
|
else if (fd_stat->st_size != c.size)
|
||||||
ret = FcFalse;
|
ret = FcFalse;
|
||||||
else if (c.checksum != (int) dir_stat->st_mtime)
|
else if (c.checksum != FcDirChecksum (dir_stat))
|
||||||
ret = FcFalse;
|
ret = FcFalse;
|
||||||
#ifdef HAVE_STRUCT_STAT_ST_MTIM
|
#ifdef HAVE_STRUCT_STAT_ST_MTIM
|
||||||
else if (c.checksum_nano != dir_stat->st_mtim.tv_nsec)
|
else if (c.checksum_nano != FcDirChecksumNano (dir_stat))
|
||||||
ret = FcFalse;
|
ret = FcFalse;
|
||||||
#endif
|
#endif
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -1112,10 +1161,8 @@ FcDirCacheBuild (FcFontSet *set, const FcChar8 *dir, struct stat *dir_stat, FcSt
|
||||||
cache->magic = FC_CACHE_MAGIC_ALLOC;
|
cache->magic = FC_CACHE_MAGIC_ALLOC;
|
||||||
cache->version = FC_CACHE_VERSION_NUMBER;
|
cache->version = FC_CACHE_VERSION_NUMBER;
|
||||||
cache->size = serialize->size;
|
cache->size = serialize->size;
|
||||||
cache->checksum = (int) dir_stat->st_mtime;
|
cache->checksum = FcDirChecksum (dir_stat);
|
||||||
#ifdef HAVE_STRUCT_STAT_ST_MTIM
|
cache->checksum_nano = FcDirChecksumNano (dir_stat);
|
||||||
cache->checksum_nano = dir_stat->st_mtim.tv_nsec;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Serialize directory name
|
* Serialize directory name
|
||||||
|
|
Loading…
Reference in New Issue