From 2554f389cd167ee28033b8885da3f92b798f7ed3 Mon Sep 17 00:00:00 2001 From: Peter Jones 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 --- 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 + */ + +#ifndef EFIVAR_TIME_H_ +#define EFIVAR_TIME_H_ + +#include + +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 #include #include +#include #include #include @@ -200,6 +201,7 @@ extern uint32_t efi_get_libefivar_version(void) __attribute__((__visibility__("default"))); #include +#include #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 + */ + +#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