From 94bbd989dfe075d712c4234dea9e3f52f2f6bf82 Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Sat, 17 Aug 2024 15:58:36 +0200 Subject: [PATCH] lib: support reproducible install date When building an Arch Linux image the install date would vary when trying to reproduce it. Similar to building packages, respect `SOURCE_DATE_EPOCH` when installing packages. --- lib/libalpm/add.c | 39 ++++++++++++++++++- test/pacman/README | 1 + test/pacman/meson.build | 1 + test/pacman/pmrule.py | 3 ++ test/pacman/pmtest.py | 3 +- .../pacman/tests/source_date_epoch_install.py | 12 ++++++ 6 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 test/pacman/tests/source_date_epoch_install.py diff --git a/lib/libalpm/add.c b/lib/libalpm/add.c index 6d245ba9..1fe476da 100644 --- a/lib/libalpm/add.c +++ b/lib/libalpm/add.c @@ -412,6 +412,43 @@ static int extract_single_file(alpm_handle_t *handle, struct archive *archive, return errors; } +static time_t get_install_time(void) +{ + + time_t now; + char *source_date_epoch; + unsigned long long epoch; + char *endptr; + + source_date_epoch = getenv("SOURCE_DATE_EPOCH"); + if (source_date_epoch) { + errno = 0; + epoch = strtoull(source_date_epoch, &endptr, 10); + if ((errno == ERANGE && (epoch == ULLONG_MAX || epoch == 0)) + || (errno != 0 && epoch == 0)) { + fprintf(stderr, "Environment variable $SOURCE_DATE_EPOCH: strtoull: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + if (endptr == source_date_epoch) { + fprintf(stderr, "Environment variable $SOURCE_DATE_EPOCH: No digits were found: %s\n", endptr); + exit(EXIT_FAILURE); + } + if (*endptr != '\0') { + fprintf(stderr, "Environment variable $SOURCE_DATE_EPOCH: Trailing garbage: %s\n", endptr); + exit(EXIT_FAILURE); + } + if (epoch > ULONG_MAX) { + fprintf(stderr, "Environment variable $SOURCE_DATE_EPOCH: value must be smaller than or equal to %lu but was found to be: %llu \n", ULONG_MAX, epoch); + exit(EXIT_FAILURE); + } + now = epoch; + } else { + now = time(NULL); + } + + return now; +} + static int commit_single_pkg(alpm_handle_t *handle, alpm_pkg_t *newpkg, size_t pkg_current, size_t pkg_count) { @@ -591,7 +628,7 @@ static int commit_single_pkg(alpm_handle_t *handle, alpm_pkg_t *newpkg, } /* make an install date (in UTC) */ - newpkg->installdate = time(NULL); + newpkg->installdate = get_install_time(); _alpm_log(handle, ALPM_LOG_DEBUG, "updating database\n"); _alpm_log(handle, ALPM_LOG_DEBUG, "adding database entry '%s'\n", newpkg->name); diff --git a/test/pacman/README b/test/pacman/README index e6fc210b..0c660f8d 100644 --- a/test/pacman/README +++ b/test/pacman/README @@ -294,6 +294,7 @@ Possible rules are: PKG_REASON=name|intvalue PKG_FILES=name|filename PKG_BACKUP=name|backupname + PKG_INSTALLDATE=name|epoch Example: PKG_DEPENDS=ncurses|glibc diff --git a/test/pacman/meson.build b/test/pacman/meson.build index f223f1f4..b78b0f7b 100644 --- a/test/pacman/meson.build +++ b/test/pacman/meson.build @@ -278,6 +278,7 @@ pacman_tests = [ 'tests/sync992.py', 'tests/sync993.py', 'tests/sync999.py', + 'tests/source_date_epoch_install.py', 'tests/trans001.py', 'tests/type001.py', 'tests/unresolvable001.py', diff --git a/test/pacman/pmrule.py b/test/pacman/pmrule.py index ca342138..86d1965b 100644 --- a/test/pacman/pmrule.py +++ b/test/pacman/pmrule.py @@ -108,6 +108,9 @@ class pmrule(object): if f.startswith(value + "\t"): success = 1 break; + elif case == "INSTALLDATE": + if newpkg.installdate != value: + success = 0 else: tap.diag("PKG rule '%s' not found" % case) success = -1 diff --git a/test/pacman/pmtest.py b/test/pacman/pmtest.py index f5f033fa..485bdd9a 100644 --- a/test/pacman/pmtest.py +++ b/test/pacman/pmtest.py @@ -92,6 +92,7 @@ class pmtest(object): "fail": 0 } self.args = "" + self.env = {} self.retcode = 0 self.db = { "local": pmdb.pmdb("local", self.root) @@ -298,7 +299,7 @@ class pmtest(object): # archives are made available more easily. time_start = time.time() self.retcode = subprocess.call(cmd, stdout=output, stderr=output, - cwd=os.path.join(self.root, util.TMPDIR), env={'LC_ALL': 'C'}) + cwd=os.path.join(self.root, util.TMPDIR), env={'LC_ALL': 'C', **self.env}) time_end = time.time() vprint("\ttime elapsed: %.2fs" % (time_end - time_start)) diff --git a/test/pacman/tests/source_date_epoch_install.py b/test/pacman/tests/source_date_epoch_install.py new file mode 100644 index 00000000..2645f9c8 --- /dev/null +++ b/test/pacman/tests/source_date_epoch_install.py @@ -0,0 +1,12 @@ +self.description = "Check that installing a package retains INSTALL DATE with SOURCE_DATE_EPOCH" + +p = pmpkg("pkg1") +p.files = ["foo/file1", + "foo/file2"] +self.addpkg(p) + +self.args = "-U %s" % p.filename() +self.env = {"SOURCE_DATE_EPOCH": "1662046009"} + +self.addrule("PACMAN_RETCODE=0") +self.addrule("PKG_INSTALLDATE=pkg1|1662046009")