From e3906c72a9fe298b21a9147ebb765600ceecc593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel=20Arruga=20Vivas?= Date: Mon, 21 Dec 2020 12:06:21 +0100 Subject: [PATCH] source-date-epoch: New module. * ChangeLog: Update changes. * doc/gnulib.texi: Add Reproducible timestamps node. * doc/source-date-epoch.texi: New file. * lib/source-date-epoch.c: Likewise. * lib/source-date-epoch.h: Likewise. * m4/source-date-epoch.m4: Likewise. * modules/source-date-epoch: Likewise. --- ChangeLog | 10 +++ doc/gnulib.texi | 2 + doc/source-date-epoch.texi | 110 +++++++++++++++++++++++++++ lib/source-date-epoch.c | 148 +++++++++++++++++++++++++++++++++++++ lib/source-date-epoch.h | 52 +++++++++++++ m4/source-date-epoch.m4 | 94 +++++++++++++++++++++++ modules/source-date-epoch | 27 +++++++ 7 files changed, 443 insertions(+) create mode 100644 doc/source-date-epoch.texi create mode 100644 lib/source-date-epoch.c create mode 100644 lib/source-date-epoch.h create mode 100644 m4/source-date-epoch.m4 create mode 100644 modules/source-date-epoch diff --git a/ChangeLog b/ChangeLog index 8bf1adf93..453ad7011 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2020-12-22 Miguel Ángel Arruga Vivas + + source-date-epoch: New module. + * doc/gnulib.texi: Add Reproducible timestamps node. + * doc/source-date-epoch.texi: New file. + * lib/source-date-epoch.c: Likewise. + * lib/source-date-epoch.h: Likewise. + * m4/source-date-epoch.m4: Likewise. + * modules/source-date-epoch: Likewise. + 2020-12-20 Bruno Haible Remove support for broken in AIX 3. diff --git a/doc/gnulib.texi b/doc/gnulib.texi index d646d6de1..6452ccfe1 100644 --- a/doc/gnulib.texi +++ b/doc/gnulib.texi @@ -6769,6 +6769,7 @@ to POSIX that it can be treated like any other Unix-like platform. * Supporting Relocation:: * func:: * stat-size:: +* Reproducible timestamps:: @end menu @node alloca @@ -6819,6 +6820,7 @@ to POSIX that it can be treated like any other Unix-like platform. @include stat-size.texi +@include source-date-epoch.texi @node Regular expressions @chapter Regular expressions diff --git a/doc/source-date-epoch.texi b/doc/source-date-epoch.texi new file mode 100644 index 000000000..e4921ebef --- /dev/null +++ b/doc/source-date-epoch.texi @@ -0,0 +1,110 @@ +@c GNU SOURCE_DATE_EPOCH documentation + +@c Copyright (C) 2020 Free Software Foundation, Inc. + +@c Permission is granted to copy, distribute and/or modify this document +@c under the terms of the GNU Free Documentation License, Version 1.3 or +@c any later version published by the Free Software Foundation; with no +@c Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A +@c copy of the license is at . + +@node Reproducible timestamps +@section SOURCE_DATE_EPOCH handling + +The module @samp{source-date-epoch} provides an interface to the +enironment variable +@url{https://reproducible-builds.org/specs/source-date-epoch/,SOURCE_DATE_EPOCH} +which allows the user to control the timestamps introduced by the +application on the generated output, thus giving first-class status as +an input to these timestamps. Examples of such output are binary +objects compiled from source code, but also parsers generated from +grammar descriptions, or files of symbols and/or strings extracted from +source code. + +The header @file{source-date-epoch.h} provides the following +functionality: + +@menu +* source_date_epoch_time:: Interface for SOURCE_DATE_EPOCH. +* source_date_epoch_time_rpl:: Replacement for time function. +@end menu + +@node source_date_epoch_time +@subsection source_date_epoch_time +@deftypefun {int} source_date_epoch_time (time_t@tie{}*@var{tp}) + +This function parses @env{SOURCE_DATE_EPOCH} and uses its value to +generate a valid @code{time_t} object. It's undefined behavior to +provide an invalid pointer through @var{tp}. + +The function may signal these three options through its return code: +@itemize +@item @code{0}: +The environment variable @env{SOURCE_DATE_EPOCH} is defined and contain +a number of seconds since January 1st, 1970 GMT0 representable by the +domain of @code{time_t} type. This value is stored into *@var{tp}. +@item @code{1}: +The environment variable @env{SOURCE_DATE_EPOCH} is defined but its +value isn't valid. The errno value from @code{strtol}-like functions is +preserved through the return, but no value is written to *@var{tp}. +@item @code{-1}: +The environment variable @env{SOURCE_DATE_EPOCH} isn't defined. No +value is written to *@var{tp}. +@end itemize + +The following example shows the basic error handling proposed by the +standard: + +@example + time_t now; + int result = source_date_epoch_time (&now); + + /* A switch can be used too. */ + if (result == 0) + /* SOURCE_DATE_EPOCH contains a valid value. */ + else if (result == 1) + /* Oops, SOURCE_DATE_EPOCH is defined but not with a correct + value, we should notify the user. */ + exit (EXIT_FAILURE); + else if (result == 1) + /* SOURCE_DATE_EPOCH isn't defined, call time. */ + now = time (&now); + + /* The value of now can be used as usual. */ +@end example +@end deftypefun + +@node source_date_epoch_time_rpl +@subsection source_date_epoch_time_rpl +@deftypefun {time_t} source_date_epoch_time_rpl (time_t@tie{}*@var{tp}) + +This function is a replacement for the function @code{time}. It uses +the value provided by @code{source_date_epoch_time} +(@pxref{source_date_epoch_time}) or falls back to the actual @code{time} +function when the returned value isn't @code{0}, ignoring any error +related to the variable value. As mandated by the standard, the +behavior of this function is defined when a @code{NULL} pointer is +provided by @var{tp}. + +@cindex REPLACE_TIME_WITH_SOURCE_DATE_EPOCH +The following example uses the macro +@code{REPLACE_TIME_WITH_SOURCE_DATE_EPOCH} to replace @code{time} +function calls with calls to @code{source_date_epoch_time_rpl} through a +macro. For this reason, the inclusion of @file{source-date-epoch.h} +must be included after any @file{time.h} standard header replacement to +avoid conflicting declarations. + +@example +#define REPLACE_TIME_WITH_SOURCE_DATE_EPOCH +#include "source-date-epoch.h" + +/* No change needed on the calling site. */ +time_t now = time (NULL); +@end example + +Be aware that this affects every call on the same source file, so this +isn't compatible neither when that function is used on that source unit +for other means, such as logging or profiling, nor when the application +wants to notify the user of the different behavior taking place and/or +errors of the environment variable value. +@end deftypefun diff --git a/lib/source-date-epoch.c b/lib/source-date-epoch.c new file mode 100644 index 000000000..473195ea3 --- /dev/null +++ b/lib/source-date-epoch.c @@ -0,0 +1,148 @@ +/* Support for SOURCE_DATE_EPOCH environment variable. + + Copyright (C) 2020 Free Software Foundation, Inc. + + Written by Miguel Ángel Arruga Vivas . + + 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 3 of the License, 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, see . */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "source-date-epoch.h" + +#include +#include +#include +#include + +/* time_t has to be an arithmetic (real since C11) type, therefore it + must be assignable, with an optional conversion factor, from a 64 + bit value. */ +#ifdef SCALE_FACTOR_SECONDS_TO_TIME_T +#define SCALE_TO_TIME_T(x) (x * SCALE_FACTOR_SECONDS_TO_TIME_T) +#define SCALE_FROM_TIME_T(x) (x / SCALE_FACTOR_SECONDS_TO_TIME_T) +#else +#define SCALE_TO_TIME_T(x) x +#define SCALE_FROM_TIME_T(x) x +#endif + +#ifndef TIME_T_IS_INTEGRAL_TYPE +/* time_t can be a floating point type; assume +/- 2^64 seconds in + that case. Only scale the value, as the displacement may involve a + function call if the difference with UTC may change depending on + the timezone. */ +#define TIME_T_MAX ((time_t) SCALE_TO_TIME_T (((1ULL << 63) - 1) * 2 + 1)) +#define TIME_T_MIN (- TIME_T_MAX) +#else /* TIME_T_IS_INTEGRAL_TYPE */ +#ifdef TIME_T_IS_SIGNED +#include +/* From mktime.m4. */ +#define TIME_T_MAX \ + ((((time_t) 1 << (sizeof (time_t) * CHAR_BIT - 2)) - 1) * 2 + 1) +#define TIME_T_MIN \ + (((time_t) ~ (time_t) 0 < (time_t) -1) ? ~ (time_t) 0 : ~ TIME_T_MAX) +#else /* !defined (TIME_T_IS_SIGNED) */ +#define TIME_T_MAX ((time_t) -1) +#define TIME_T_MIN ((time_t) 0) +#endif /* !defined (TIME_T_IS_SIGNED) */ +#endif /* TIME_T_IS_INTEGRAL_TYPE */ + +#ifdef DISPLACEMENT_UNIX_EPOCH_TO_TIME_T +#ifdef TIME_T_DEPENDS_ON_TZ + +/* Calculate at runtime the displacement from UTC. */ +static int_least64_t +calculate_displacement_from_utc (void) +{ + struct tm zero_time; + time_t zero = 0; + + zero_time = gmtime (&zero); + /* TODO: Too much right now, steal difftm later. */ + if (zero_time.tm_year == 70 || zero_time.tm_year == 69) + { + return ((365 * 24 * 60 * 60 * (70 - zero_time.tm_year)) + + ((69 - zero_time.tm_year) + * (zero_time.tm_sec + + (60 * zero_time.tm_min + + (60 * zero_time.tm_hour + + (24 * zero_time.tm_yday)))))); + } + abort (); +} +#define DISPLACE_TO_UNIX_EPOCH(x) (x + calculate_displacement_from_utc ()) +#define DISPLACE_FROM_UNIX_EPOCH(x) (x - calculate_displacement_from_utc ()) +#else /* The displacement can be statically calculated. */ +#define DISPLACE_TO_UNIX_EPOCH(x) (x + DISPLACEMENT_UNIX_EPOCH_TO_TIME_T) +#define DISPLACE_FROM_UNIX_EPOCH(x) (x - DISPLACEMENT_UNIX_EPOCH_TO_TIME_T) +#endif +#else /* !DISPLACEMENT_UNIX_EPOCH_TO_TIME_T */ +#define DISPLACE_TO_UNIX_EPOCH(x) x +#define DISPLACE_FROM_UNIX_EPOCH(x) x +#endif + +#ifdef TIME_T_IS_SIGNED +typedef int_least64_t seconds_type; +#ifdef LONG_HAS_64_BITS +#define STRING_TO_NUMBER strtol +#else +#define STRING_TO_NUMBER strtoll +#endif +#else /* !TIME_T_IS_SIGNED */ +typedef uint_least64_t seconds_type; +#ifdef LONG_HAS_64_BITS +#define STRING_TO_NUMBER strtoul +#else +#define STRING_TO_NUMBER strtoull +#endif +#endif + +#define SECONDS_FROM_UNIX_EPOCH_TO_TIME_T(s) \ + ((time_t) DISPLACE_TO_UNIX_EPOCH (SCALE_TO_TIME_T (s))) +#define TIME_T_TO_SECONDS_FROM_UNIX_EPOCH(s) \ + ((seconds_type) (SCALE_FROM_TIME_T (DISPLACE_FROM_UNIX_EPOCH (s)))) + +int +source_date_epoch_time (time_t *tp) +{ + char *end; + seconds_type value; + const char *source_date_epoch = getenv ("SOURCE_DATE_EPOCH"); + + if (!source_date_epoch) + return -1; + + errno = 0; + value = STRING_TO_NUMBER (source_date_epoch, &end, 10); + + if (errno != 0 || end == source_date_epoch || *end != '\0' + || value > TIME_T_TO_SECONDS_FROM_UNIX_EPOCH (TIME_T_MAX) + || value < TIME_T_TO_SECONDS_FROM_UNIX_EPOCH (TIME_T_MIN)) + return 1; + + *tp = SECONDS_FROM_UNIX_EPOCH_TO_TIME_T (value); + return 0; +} + +time_t +source_date_epoch_time_rpl (time_t *tp) +{ + time_t stack; + time_t *ptr = tp ? tp : &stack; + if (source_date_epoch_time (ptr) == 0) + return *ptr; + return time (ptr); +} diff --git a/lib/source-date-epoch.h b/lib/source-date-epoch.h new file mode 100644 index 000000000..1f0056cd9 --- /dev/null +++ b/lib/source-date-epoch.h @@ -0,0 +1,52 @@ +/* Support for SOURCE_DATE_EPOCH environment variable. + + Copyright (C) 2020 Free Software Foundation, Inc. + + Written by Miguel Ángel Arruga Vivas . + + 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 3 of the License, 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, see . */ + +#ifndef SOURCE_DATE_EPOCH_H +#define SOURCE_DATE_EPOCH_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Read the environment variable SOURCE_DATE_EPOCH into the provided + time_t object. Return 0 when the value has been read successfully, + -1 when the variable isn't defined or 1 when the variable is + defined with an invalid value. + + See https://reproducible-builds.org/specs/source-date-epoch/ */ +int source_date_epoch_time (time_t *); + +#ifdef REPLACE_TIME_WITH_SOURCE_DATE_EPOCH +# ifdef time /* If it's already a macro, remove it. */ +# undef time +# endif +# define time source_date_epoch_time_rpl +#endif + +/* Fallback to calling time when SOURCE_DATE_EPOCH isn't defined or + its value isn't valid. */ +time_t source_date_epoch_time_rpl (time_t *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/m4/source-date-epoch.m4 b/m4/source-date-epoch.m4 new file mode 100644 index 000000000..131b6aec4 --- /dev/null +++ b/m4/source-date-epoch.m4 @@ -0,0 +1,94 @@ +# source-date-epoch.m4 serial 1 +dnl Copyright (C) 2020 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +AC_DEFUN([gl_LONG_HAS_64_BITS], +[ + AC_CACHE_CHECK([whether long has 64 bits], + [gl_cv_long_has_64_bits], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#include /* For CHAR_BIT. */ +char time_t_conv[((sizeof (long) * CHAR_BIT) >= 64) ? 1 : -1];]])], + [gl_cv_long_has_64_bits=yes], + [gl_cv_long_has_64_bits=no])]) + if test $gl_cv_long_has_64_bits = yes; then + AC_DEFINE([LONG_HAS_64_BITS], [1], + [Whether long has, at least, 64 bits of size]) + fi +]) + +AC_DEFUN([gl_TIME_T_IS_INTEGRAL_TYPE], +[ + AC_CACHE_CHECK([whether time_t is an integral type], + [gl_cv_time_t_is_integral_type], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#include /* For time_t. */ +char time_t_integral[((time_t) 1.0 == (time_t) 1.25) ? 1 : -1];]])], + [gl_cv_time_t_is_integral_type=yes], + [gl_cv_time_t_is_integral_type=no])]) + if test $gl_cv_time_t_is_integral_type = yes; then + AC_DEFINE([TIME_T_IS_INTEGRAL_TYPE], [1], + [Whether time_t is an integral type]) + fi +]) + +AC_DEFUN([gl_UNIX_EPOCH_COMPATIBILITY_WITH_TIME_T], +[ + AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles + AC_REQUIRE([gl_MULTIARCH]) + AC_CACHE_CHECK([whether time_t has unix epoch], + [gl_cv_time_t_has_unix_epoch], + [if test "x$gl_cv_func_working_mktime" != xyes; then + # XXX: Perhaps this check could be worked around. + gl_cv_time_t_has_unix_epoch=no + else + AC_RUN_IFELSE( + [AC_LANG_SOURCE( +[[/* Test program from Miguel Ángel Arruga Vivas. */ +#include /* putenv, EXIT_SUCCESS, EXIT_FAILURE */ +#include /* memset */ +#include /* time_t, mktime */ +]GL_MDA_DEFINES[ +int +main (int argc, char **argv) +{ + time_t utc_time; + struct tm utc_epoch; + + /* Ensure we are using the right timezone. */ + putenv ("TZ=GMT0"); + + /* Desired epoch: t0 = 1970-01-01 00:00:00+0000. */ + memset (&utc_epoch, 0, sizeof (utc_epoch)); + utc_epoch.tm_year = 70; /* Displacement is 1900. */ + utc_epoch.tm_mday = 1; /* First DOM is 1 unlike other values. */ + + utc_time = mktime (&utc_epoch); + return (utc_time == 0 ? EXIT_SUCCESS : EXIT_FAILURE); +}]])], + [gl_cv_time_t_has_unix_epoch=yes], + [gl_cv_time_t_has_unix_epoch=no] + [gl_cv_time_t_has_unix_epoch="$gl_cross_guess_normal"]) + fi + ]) + + if test "x$gl_cv_time_t_has_unix_epoch" != xyes; then + # TODO: Extract the actual conversion factors + AC_MSG_WARN([time_t doesn't have unix epoch!]) + AC_DEFINE([SCALE_FACTOR_SECONDS_TO_TIME_T], [1], + [TODO: Scale factor from seconds to time_t units.]) + AC_DEFINE([DISPLACEMENT_UNIX_EPOCH_TO_TIME_T], [0], + [TODO: Displacement from unix epoch to time_t.]) + AC_DEFINE([TIME_T_DEPENDS_ON_TZ], [1], + [TODO: Whether the displacement must be calculated at runtime.]) + fi +]) + +AC_DEFUN([gl_SOURCE_DATE_EPOCH], +[ + AC_REQUIRE([gl_LONG_HAS_64_BITS]) + AC_REQUIRE([gl_TIME_T_IS_INTEGRAL_TYPE]) + AC_REQUIRE([gl_UNIX_EPOCH_COMPATIBILITY_WITH_TIME_T]) +]) diff --git a/modules/source-date-epoch b/modules/source-date-epoch new file mode 100644 index 000000000..1e9e96fa4 --- /dev/null +++ b/modules/source-date-epoch @@ -0,0 +1,27 @@ +Description: +Support for SOURCE_DATE_EPOCH environment variable. + +Files: +lib/source-date-epoch.c +lib/source-date-epoch.h +m4/source-date-epoch.m4 + +Depends-on: +c99 +errno +mktime + +configure.ac: +gl_SOURCE_DATE_EPOCH + +Makefile.am: +lib_SOURCES += source-date-epoch.c + +Include: +"source-date-epoch.h" + +License: +GPL + +Maintainer: +Miguel Ángel Arruga Vivas, gettext base-commit: 87dc278345db394227f281c831a3fafb0b7854fb -- 2.29.2