Blob Blame History Raw
From 2554f389cd167ee28033b8885da3f92b798f7ed3 Mon Sep 17 00:00:00 2001
From: Peter Jones <pjones@redhat.com>
Date: Mon, 3 Feb 2020 13:26:08 -0500
Subject: [PATCH 80/86] Add efi_time_t and time conversion and formatting
 utilities.

Signed-off-by: Peter Jones <pjones@redhat.com>
---
 src/Makefile                      |   2 +-
 src/include/efivar/efivar-time.h  |  27 +++
 src/include/efivar/efivar-types.h |  33 ++++
 src/include/efivar/efivar.h       |   2 +
 src/libefivar.map.in              |  12 ++
 src/time.c                        | 272 ++++++++++++++++++++++++++++++
 6 files changed, 347 insertions(+), 1 deletion(-)
 create mode 100644 src/include/efivar/efivar-time.h
 create mode 100644 src/time.c

diff --git a/src/Makefile b/src/Makefile
index 0783cb3b55f..b0ef8ec29a5 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -18,7 +18,7 @@ LIBEFIBOOT_SOURCES = crc32.c creator.c disk.c gpt.c loadopt.c path-helpers.c \
 LIBEFIBOOT_OBJECTS = $(patsubst %.c,%.o,$(LIBEFIBOOT_SOURCES))
 LIBEFIVAR_SOURCES = crc32.c dp.c dp-acpi.c dp-hw.c dp-media.c dp-message.c \
 	efivarfs.c error.c export.c guid.c guids.S guid-symbols.c \
-	lib.c vars.c
+	lib.c vars.c time.c
 LIBEFIVAR_OBJECTS = $(patsubst %.S,%.o,$(patsubst %.c,%.o,$(LIBEFIVAR_SOURCES)))
 EFIVAR_SOURCES = efivar.c
 GENERATED_SOURCES = include/efivar/efivar-guids.h guid-symbols.c
diff --git a/src/include/efivar/efivar-time.h b/src/include/efivar/efivar-time.h
new file mode 100644
index 00000000000..04c243601f5
--- /dev/null
+++ b/src/include/efivar/efivar-time.h
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: LGPL-2.1
+/*
+ * efivar-time.h
+ * Copyright 2020 Peter Jones <pjones@redhat.com>
+ */
+
+#ifndef EFIVAR_TIME_H_
+#define EFIVAR_TIME_H_
+
+#include <stdbool.h>
+
+extern int tm_to_efi_time(const struct tm * const s, efi_time_t *d, bool tzadj);
+extern int efi_time_to_tm(const efi_time_t * const s, struct tm *d);
+
+extern char *efi_asctime(const efi_time_t * const time);
+extern char *efi_asctime_r(const efi_time_t * const time, char *buf);
+extern efi_time_t *efi_gmtime(const time_t *time);
+extern efi_time_t *efi_gmtime_r(const time_t *time, efi_time_t *result);
+extern efi_time_t *efi_localtime(const time_t *time);
+extern efi_time_t *efi_localtime_r(const time_t *time, efi_time_t *result);
+extern time_t efi_mktime(const efi_time_t * const time);
+
+extern char *efi_strptime(const char *s, const char *format, efi_time_t *time);
+extern size_t efi_strftime(char *s, size_t max, const char *format, const efi_time_t *time);
+
+#endif /* !EFIVAR_TIME_H_ */
+// vim:fenc=utf-8:tw=75:noet
diff --git a/src/include/efivar/efivar-types.h b/src/include/efivar/efivar-types.h
index 6fca8a495f4..ce22b6c12b3 100644
--- a/src/include/efivar/efivar-types.h
+++ b/src/include/efivar/efivar-types.h
@@ -51,6 +51,39 @@ typedef uint16_t efi_char16_t;
 typedef unsigned long uintn_t;
 typedef long intn_t;
 
+#define EFIVAR_HAVE_EFI_TIME_T 1
+
+/*
+ * This can never be correct in, as defined, in the face of leap seconds.
+ * Because seconds here are defined with a range of [0,59], we can't
+ * express leap seconds correctly there.  Because TimeZone is specified in
+ * minutes West of UTC, rather than seconds (like struct tm), it can't be
+ * used to correct when we cross a leap second boundary condition.  As a
+ * result, EFI_TIME can only express UT1, rather than UTC, and there's no
+ * way when converting to know wether the error has been taken into
+ * account, nor if it should be.
+ *
+ * As I write this, there is a 37 second error.
+ */
+typedef struct {
+	uint16_t	year;		// 1900 - 9999
+	uint8_t		month;		// 1 - 12
+	uint8_t		day;		// 1 - 31
+	uint8_t		hour;		// 0 - 23
+	uint8_t		minute;		// 0 - 59
+	uint8_t		second;		// 0 - 59 // ha ha only serious
+	uint8_t		pad1;		// 0
+	uint32_t	nanosecond;	// 0 - 999,999,999
+	int16_t		timezone;	// minutes from UTC or EFI_UNSPECIFIED_TIMEZONE
+	uint8_t		daylight;	// bitfield
+	uint8_t		pad2;		// 0
+} efi_time_t __attribute__((__aligned__(1)));
+
+#define EFI_TIME_ADJUST_DAYLIGHT        ((uint8_t)0x01)
+#define EFI_TIME_IN_DAYLIGHT            ((uint8_t)0x02)
+
+#define EFI_UNSPECIFIED_TIMEZONE        ((uint16_t)0x07ff)
+
 #define EFI_VARIABLE_NON_VOLATILE				((uint64_t)0x0000000000000001)
 #define EFI_VARIABLE_BOOTSERVICE_ACCESS				((uint64_t)0x0000000000000002)
 #define EFI_VARIABLE_RUNTIME_ACCESS				((uint64_t)0x0000000000000004)
diff --git a/src/include/efivar/efivar.h b/src/include/efivar/efivar.h
index 6b38ce8faf4..7518a3238c7 100644
--- a/src/include/efivar/efivar.h
+++ b/src/include/efivar/efivar.h
@@ -14,6 +14,7 @@
 #include <stdio.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <time.h>
 #include <unistd.h>
 #include <byteswap.h>
 
@@ -200,6 +201,7 @@ extern uint32_t efi_get_libefivar_version(void)
 	__attribute__((__visibility__("default")));
 
 #include <efivar/efivar-dp.h>
+#include <efivar/efivar-time.h>
 
 #endif /* EFIVAR_H */
 
diff --git a/src/libefivar.map.in b/src/libefivar.map.in
index f2505134c63..47d45456372 100644
--- a/src/libefivar.map.in
+++ b/src/libefivar.map.in
@@ -139,4 +139,16 @@ LIBEFIVAR_1.38 {
 		efi_guid_external_management;
 		efi_variable_alloc;
 		efi_variable_export_dmpstore;
+
+		tm_to_efi_time;
+		efi_time_to_tm;
+		efi_asctime;
+		efi_asctime_r;
+		efi_gmtime;
+		efi_gmtime_r;
+		efi_localtime;
+		efi_localtime_r;
+		efi_mktime;
+		efi_strptime;
+		efi_strftime;
 } LIBEFIVAR_1.37;
diff --git a/src/time.c b/src/time.c
new file mode 100644
index 00000000000..f267fd193e6
--- /dev/null
+++ b/src/time.c
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: LGPL-2.1
+/*
+ * time.c - efi_time_t helper functions
+ * Copyright 2020 Peter Jones <pjones@redhat.com>
+ */
+
+#include "efivar.h"
+
+int
+efi_time_to_tm(const efi_time_t * const s, struct tm *d)
+{
+
+	if (!s || !d) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	d->tm_year = s->year - 1900;
+	d->tm_mon = s->month - 1;
+	d->tm_mday = s->day;
+	d->tm_hour = s->hour;
+	d->tm_min = s->minute;
+	/*
+	 * Just ignore EFI's range problem here and pretend we're in UTC
+	 * not UT1.
+	 */
+	d->tm_sec = s->second;
+	d->tm_isdst = (s->daylight & EFI_TIME_IN_DAYLIGHT) ? 1 : 0;
+
+	return 0;
+}
+
+int
+tm_to_efi_time(const struct tm * const s, efi_time_t *d, bool tzadj)
+{
+	if (!s || !d) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	d->pad2 = 0;
+	d->daylight = s->tm_isdst ? EFI_TIME_IN_DAYLIGHT : 0;
+	d->timezone = 0;
+	d->nanosecond = 0;
+	d->pad1 = 0;
+	/*
+	 * Just ignore EFI's range problem here and pretend we're in UTC
+	 * not UT1.
+	 */
+	d->second = s->tm_sec;
+	d->minute = s->tm_min;
+	d->hour = s->tm_hour;
+	d->day = s->tm_mday;
+	d->month = s->tm_mon + 1;
+	d->year = s->tm_year + 1900;
+
+	if (tzadj) {
+		tzset();
+		d->timezone = timezone / 60;
+	}
+
+	return 0;
+}
+
+static char *otz_;
+static char *ntz_;
+
+static const char *
+newtz(int16_t timezone_)
+{
+	if (!otz_)
+		otz_ = strdup(secure_getenv("TZ"));
+
+	if (ntz_) {
+		free(ntz_);
+		ntz_ = NULL;
+	}
+
+	if (timezone_ == EFI_UNSPECIFIED_TIMEZONE) {
+		unsetenv("TZ");
+	} else {
+		char tzsign = timezone_ >= 0 ? '+' : '-';
+		int tzabs = tzsign == '+' ? timezone_ : -timezone_;
+		int16_t tzhours = tzabs / 60;
+		int16_t tzminutes = tzabs % 60;
+
+		/*
+		 * I have no idea what the right thing to do with DST is
+		 * here, so I'm going to ignore it.
+		 */
+		asprintf(&ntz_, "UTC%c%"PRId16":%"PRId16":00",
+			  tzsign, tzhours, tzminutes);
+		setenv("TZ", ntz_, 1);
+	}
+	tzset();
+
+	return ntz_;
+}
+
+static const char *
+oldtz(void) {
+	if (ntz_) {
+		free(ntz_);
+		ntz_ = NULL;
+
+		if (otz_)
+			setenv("TZ", otz_, 1);
+		else
+			unsetenv("TZ");
+	}
+
+	tzset();
+
+	return otz_;
+}
+
+efi_time_t *
+efi_gmtime_r(const time_t *time, efi_time_t *result)
+{
+	struct tm tm = { 0 };
+
+	if (!time || !result) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	gmtime_r(time, &tm);
+	tm_to_efi_time(&tm, result, false);
+
+	return result;
+}
+
+efi_time_t *
+efi_gmtime(const time_t *time)
+{
+	static efi_time_t ret;
+
+	if (!time) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	efi_gmtime_r(time, &ret);
+
+	return &ret;
+}
+
+efi_time_t *
+efi_localtime_r(const time_t *time, efi_time_t *result)
+{
+	struct tm tm = { 0 };
+
+	if (!time || !result) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	localtime_r(time, &tm);
+	tm_to_efi_time(&tm, result, true);
+
+	return result;
+}
+
+efi_time_t *
+efi_localtime(const time_t *time)
+{
+	static efi_time_t ret;
+
+	if (!time) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	efi_localtime_r(time, &ret);
+
+	return &ret;
+}
+
+time_t
+efi_mktime(const efi_time_t * const time)
+{
+	struct tm tm = { 0 };
+	time_t ret;
+
+	if (!time) {
+		errno = EINVAL;
+		return (time_t)-1;
+	}
+
+	newtz(time->timezone);
+
+	efi_time_to_tm(time, &tm);
+	ret = mktime(&tm);
+
+	oldtz();
+
+	return ret;
+}
+
+char *
+efi_strptime(const char *s, const char *format, efi_time_t *time)
+{
+	struct tm tm;
+	char *end;
+
+	if (!s || !format || !time) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	memset(&tm, 0, sizeof(tm));
+	end = strptime(s, format, &tm);
+	if (end != NULL && tm_to_efi_time(&tm, time, true) < 0)
+		return NULL;
+
+	return end;
+}
+
+char *
+efi_asctime_r(const efi_time_t * const time, char *buf)
+{
+	struct tm tm;
+	char *ret;
+
+	newtz(time->timezone);
+
+	efi_time_to_tm(time, &tm);
+	ret = asctime_r(&tm, buf);
+
+	oldtz();
+
+	return ret;
+}
+
+char *
+efi_asctime(const efi_time_t * const time)
+{
+	struct tm tm;
+	char *ret;
+
+	newtz(time->timezone);
+
+	efi_time_to_tm(time, &tm);
+	ret = asctime(&tm);
+
+	oldtz();
+
+	return ret;
+}
+
+size_t
+efi_strftime(char *s, size_t max, const char *format, const efi_time_t *time)
+{
+	size_t ret = 0;
+	struct tm tm = { 0 };
+
+	if (!s || !format || !time) {
+		errno = EINVAL;
+		return ret;
+	}
+
+	newtz(time->timezone);
+
+	efi_time_to_tm(time, &tm);
+	ret = strftime(s, max, format, &tm);
+
+	oldtz();
+
+	return ret;
+}
+
+// vim:fenc=utf-8:tw=75:noet
-- 
2.24.1