efivar/0080-Add-efi_time_t-and-time-conversion-and-formatting-ut.patch

431 lines
9.7 KiB
Diff
Raw Normal View History

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