>From 4206a278ee88e5ca8e33a68c2a248c586fe26af9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel=20Arruga=20Vivas?= Date: Mon, 14 Dec 2020 22:21:16 +0100 Subject: [PATCH] source-date-epoch: New module. * 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 | 9 +++ doc/source-date-epoch.texi | 69 ++++++++++++++++++ lib/source-date-epoch.c | 143 +++++++++++++++++++++++++++++++++++++ lib/source-date-epoch.h | 52 ++++++++++++++ m4/source-date-epoch.m4 | 94 ++++++++++++++++++++++++ modules/source-date-epoch | 27 +++++++ 6 files changed, 394 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 9f9a02160..140ccb463 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2020-12-14 Miguel Ángel Arruga Vivas + + source-date-epoch: New module. + * 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-13 Paul Eggert string: port memchr macro to AIX 7.2 XLC diff --git a/doc/source-date-epoch.texi b/doc/source-date-epoch.texi new file mode 100644 index 000000000..ceea3ac16 --- /dev/null +++ b/doc/source-date-epoch.texi @@ -0,0 +1,69 @@ +@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 + +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 the source code, but also parsers generated from a +grammar description, or files of symbols or strings extracted from the +source code. + +To enable it, just include the header and substitute the appropiate time +calls with the function source_date_epoch_time handling the error return +at the application level. +@smallexample +#include "source-date-epoch.h" +... +void +foo (void) +{ + time_t now; + switch (source_date_epoch_time (&now)) + { + case 0: + return now; + case 1: + /* Oops, SOURCE_DATE_EPOCH is defined but not with a correct + value, we should notify the user. */ + case -1: + /* SOURCE_DATE_EPOCH isn't defined, call time. */ + now = time (&now); + break; + } + return time (now); +} +@end smallexample + +This functionality shouldn't be used for log or interactive messages +shown to the user, therefore the wrapper for the time function isn't +provided by default. Instead, calls to time can be replaced with +source_date_epoch_time_rpl and any error related to SOURCE_DATE_EPOCH +will be ignored. + +The macro @code{REPLACE_TIME_WITH_SOURCE_DATE_EPOCH} enables the +replacement of time. The header must be included after or +substitute it completely to avoid any issue. +@smallexample +#define REPLACE_TIME_WITH_SOURCE_DATE_EPOCH 1 +#include "source-date-epoch.h" +... + time_t now = time (NULL); +@end smallexample + +Please, note that this may affect other calls not used for the output, +so this isn't compatible neither with source units where the time +function is used for other means as logging or profiling, nor when the +application wants to notify the user of the different behavior in place. diff --git a/lib/source-date-epoch.c b/lib/source-date-epoch.c new file mode 100644 index 000000000..06e73d06f --- /dev/null +++ b/lib/source-date-epoch.c @@ -0,0 +1,143 @@ +/* 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) + { + int hack = ; + 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 +#define DISPLACE_TO_UNIX_EPOCH(x) x +#define DISPLACE_FROM_UNIX_EPOCH(x) x +#endif + +/* XXX: Perhaps strtoll is always ok? */ +#ifdef LONG_HAS_64_BITS +#define STRING_TO_INT_LEAST64_T strtol +#else +#define STRING_TO_INT_LEAST64_T strtoll +#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) \ + ((int_least64_t) (SCALE_FROM_TIME_T (DISPLACE_FROM_UNIX_EPOCH (s)))) + +int +source_date_epoch_time (time_t *tp) +{ + char *end; + int_least64_t value; + const char *source_date_epoch = getenv ("SOURCE_DATE_EPOCH"); + + if (!source_date_epoch) + return -1; + + errno = 0; + value = STRING_TO_INT_LEAST64_T (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; +} + +#ifdef time +#undef time +#endif + +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: 67b5d51f81869345b8d02f196eb260c75a7e7c55 -- 2.29.2