From 1272be04be0cb803eec87f602edb2e3e6f111aea Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 14 Apr 2024 21:33:34 +0200 Subject: Merging upstream version 2.40. Signed-off-by: Daniel Baumann --- lib/buffer.c | 7 +- lib/c_strtod.c | 4 +- lib/caputils.c | 38 ++++++++ lib/colors.c | 10 +- lib/cpuset.c | 10 +- lib/env.c | 4 +- lib/exec_shell.c | 4 +- lib/fileeq.c | 2 +- lib/jsonwrt.c | 37 ++++--- lib/loopdev.c | 165 ++++++++++++++++++++++--------- lib/mbsalign.c | 21 ++++ lib/mbsedit.c | 13 ++- lib/monotonic.c | 6 +- lib/pager.c | 38 +++++--- lib/path.c | 53 +++++----- lib/procfs.c | 5 + lib/pty-session.c | 7 +- lib/sha1.c | 11 +++ lib/sha256.c | 3 + lib/shells.c | 103 +++++++++++++------ lib/strutils.c | 60 ++++++++++-- lib/strv.c | 4 +- lib/terminal-colors.d.5 | 6 +- lib/timeutils.c | 255 ++++++++++++++++++++++++++++++++++++++++++------ lib/ttyutils.c | 78 ++++++++++++++- 25 files changed, 740 insertions(+), 204 deletions(-) (limited to 'lib') diff --git a/lib/buffer.c b/lib/buffer.c index fda2fc8..cc863fa 100644 --- a/lib/buffer.c +++ b/lib/buffer.c @@ -11,7 +11,7 @@ void ul_buffer_reset_data(struct ul_buffer *buf) { if (buf->begin) - buf->begin[0] = '\0'; + memset(buf->begin, 0, buf->sz); buf->end = buf->begin; if (buf->ptrs && buf->nptrs) @@ -49,7 +49,7 @@ int ul_buffer_is_empty(struct ul_buffer *buf) int ul_buffer_save_pointer(struct ul_buffer *buf, unsigned short ptr_idx) { if (ptr_idx >= buf->nptrs) { - char **tmp = realloc(buf->ptrs, (ptr_idx + 1) * sizeof(char *)); + char **tmp = reallocarray(buf->ptrs, ptr_idx + 1, sizeof(char *)); if (!tmp) return -EINVAL; @@ -134,12 +134,11 @@ int ul_buffer_append_data(struct ul_buffer *buf, const char *data, size_t sz) if (!buf) return -EINVAL; - if (!data || !*data) + if (!data) return 0; if (buf->begin && buf->end) maxsz = buf->sz - (buf->end - buf->begin); - if (maxsz <= sz + 1) { int rc = ul_buffer_alloc_data(buf, buf->sz + sz + 1); if (rc) diff --git a/lib/c_strtod.c b/lib/c_strtod.c index d25ee27..d7d8cbe 100644 --- a/lib/c_strtod.c +++ b/lib/c_strtod.c @@ -50,10 +50,10 @@ double c_strtod(char const *str, char **end) return strtod_l(str, end, cl); #elif defined(HAVE_USELOCALE) /* - * B) classic strtod(), but switch to "C" locale by uselocal() + * B) classic strtod(), but switch to "C" locale by uselocale() */ if (cl) { - locale_t org_cl = uselocale(locale); + locale_t org_cl = uselocale(cl); if (!org_cl) return 0; diff --git a/lib/caputils.c b/lib/caputils.c index 987533a..23866c0 100644 --- a/lib/caputils.c +++ b/lib/caputils.c @@ -24,6 +24,7 @@ #include "caputils.h" #include "pathnames.h" #include "procfs.h" +#include "nls.h" static int test_cap(unsigned int cap) { @@ -87,6 +88,43 @@ int cap_last_cap(void) return cap; } +void cap_permitted_to_ambient(void) +{ + /* We use capabilities system calls to propagate the permitted + * capabilities into the ambient set because we may have + * already forked so be in async-signal-safe context. */ + struct __user_cap_header_struct header = { + .version = _LINUX_CAPABILITY_VERSION_3, + .pid = 0, + }; + struct __user_cap_data_struct payload[_LINUX_CAPABILITY_U32S_3] = {{ 0 }}; + uint64_t effective, cap; + + if (capget(&header, payload) < 0) + err(EXIT_FAILURE, _("capget failed")); + + /* In order the make capabilities ambient, we first need to ensure + * that they are all inheritable. */ + payload[0].inheritable = payload[0].permitted; + payload[1].inheritable = payload[1].permitted; + + if (capset(&header, payload) < 0) + err(EXIT_FAILURE, _("capset failed")); + + effective = ((uint64_t)payload[1].effective << 32) | (uint64_t)payload[0].effective; + + for (cap = 0; cap < (sizeof(effective) * 8); cap++) { + /* This is the same check as cap_valid(), but using + * the runtime value for the last valid cap. */ + if (cap > (uint64_t) cap_last_cap()) + continue; + + if ((effective & (1ULL << cap)) + && prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0) < 0) + err(EXIT_FAILURE, _("prctl(PR_CAP_AMBIENT) failed")); + } +} + #ifdef TEST_PROGRAM_CAPUTILS int main(int argc, char *argv[]) { diff --git a/lib/colors.c b/lib/colors.c index 532e339..0928770 100644 --- a/lib/colors.c +++ b/lib/colors.c @@ -357,12 +357,10 @@ static char *colors_get_homedir(char *buf, size_t bufsz) /* * Adds one color sequence to array with color scheme. - * When returning success (0) this function takes ownership of - * @seq and @name, which have to be allocated strings. */ static int colors_add_scheme(struct ul_color_ctl *cc, - char *name, - char *seq0) + const char *name, + const char *seq0) { struct ul_color_scheme *cs = NULL; char *seq = NULL; @@ -380,8 +378,8 @@ static int colors_add_scheme(struct ul_color_ctl *cc, /* enlarge the array */ if (cc->nschemes == cc->schemes_sz) { - void *tmp = realloc(cc->schemes, (cc->nschemes + 10) - * sizeof(struct ul_color_scheme)); + void *tmp = reallocarray(cc->schemes, cc->nschemes + 10, + sizeof(struct ul_color_scheme)); if (!tmp) goto err; cc->schemes = tmp; diff --git a/lib/cpuset.c b/lib/cpuset.c index 098b8e5..533b8ab 100644 --- a/lib/cpuset.c +++ b/lib/cpuset.c @@ -287,7 +287,7 @@ static int nextnumber(const char *str, char **end, unsigned int *result) */ int cpulist_parse(const char *str, cpu_set_t *set, size_t setsize, int fail) { - size_t max = cpuset_nbits(setsize); + const size_t max = cpuset_nbits(setsize); const char *p, *q; char *end = NULL; @@ -326,8 +326,12 @@ int cpulist_parse(const char *str, cpu_set_t *set, size_t setsize, int fail) if (!(a <= b)) return 1; while (a <= b) { - if (fail && (a >= max)) - return 2; + if (a >= max) { + if (fail) + return 2; + else + break; + } CPU_SET_S(a, setsize, set); a += s; } diff --git a/lib/env.c b/lib/env.c index 2b3395c..2bdfe56 100644 --- a/lib/env.c +++ b/lib/env.c @@ -159,7 +159,7 @@ void __sanitize_env(struct ul_env_list **org) if (strncmp(*cur, *bad, strlen(*bad)) == 0) { if (org) *org = env_list_add(*org, *cur); - last = remote_entry(envp, cur - envp, last); + last = remove_entry(envp, cur - envp, last); cur--; break; } @@ -174,7 +174,7 @@ void __sanitize_env(struct ul_env_list **org) continue; /* OK */ if (org) *org = env_list_add(*org, *cur); - last = remote_entry(envp, cur - envp, last); + last = remove_entry(envp, cur - envp, last); cur--; break; } diff --git a/lib/exec_shell.c b/lib/exec_shell.c index 6fef6c7..96d3e95 100644 --- a/lib/exec_shell.c +++ b/lib/exec_shell.c @@ -42,9 +42,7 @@ void __attribute__((__noreturn__)) exec_shell(void) shellc = xstrdup(shell); shell_basename = basename(shellc); - arg0 = xmalloc(strlen(shell_basename) + 2); - arg0[0] = '-'; - strcpy(arg0 + 1, shell_basename); + xasprintf(&arg0, "-%s", shell_basename); execl(shell, arg0, (char *)NULL); errexec(shell); diff --git a/lib/fileeq.c b/lib/fileeq.c index af5b4e3..2a74af8 100644 --- a/lib/fileeq.c +++ b/lib/fileeq.c @@ -79,9 +79,9 @@ enum { }; struct ul_fileeq_method { - int id; const char *name; /* name used by applications */ const char *kname; /* name used by kernel crypto */ + int id; short digsiz; }; diff --git a/lib/jsonwrt.c b/lib/jsonwrt.c index 8ca1d4d..243ed82 100644 --- a/lib/jsonwrt.c +++ b/lib/jsonwrt.c @@ -26,12 +26,12 @@ * } * } */ -static void fputs_quoted_case_json(const char *data, FILE *out, int dir) +static void fputs_quoted_case_json(const char *data, FILE *out, int dir, size_t size) { const char *p; fputc('"', out); - for (p = data; p && *p; p++) { + for (p = data; p && *p && (!size || p < data + size); p++) { const unsigned int c = (unsigned int) *p; @@ -98,9 +98,9 @@ static void fputs_quoted_case_json(const char *data, FILE *out, int dir) fputc('"', out); } -#define fputs_quoted_json(_d, _o) fputs_quoted_case_json(_d, _o, 0) -#define fputs_quoted_json_upper(_d, _o) fputs_quoted_case_json(_d, _o, 1) -#define fputs_quoted_json_lower(_d, _o) fputs_quoted_case_json(_d, _o, -1) +#define fputs_quoted_json(_d, _o) fputs_quoted_case_json(_d, _o, 0, 0) +#define fputs_quoted_json_upper(_d, _o) fputs_quoted_case_json(_d, _o, 1, 0) +#define fputs_quoted_json_lower(_d, _o) fputs_quoted_case_json(_d, _o, -1, 0) void ul_jsonwrt_init(struct ul_jsonwrt *fmt, FILE *out, int indent) { @@ -154,12 +154,6 @@ void ul_jsonwrt_open(struct ul_jsonwrt *fmt, const char *name, int type) void ul_jsonwrt_close(struct ul_jsonwrt *fmt, int type) { - if (fmt->indent == 1) { - fputs("\n}\n", fmt->out); - fmt->indent--; - fmt->after_close = 1; - return; - } assert(fmt->indent > 0); switch (type) { @@ -168,6 +162,8 @@ void ul_jsonwrt_close(struct ul_jsonwrt *fmt, int type) fputc('\n', fmt->out); ul_jsonwrt_indent(fmt); fputs("}", fmt->out); + if (fmt->indent == 0) + fputs("\n", fmt->out); break; case UL_JSON_ARRAY: fmt->indent--; @@ -204,6 +200,17 @@ void ul_jsonwrt_value_s(struct ul_jsonwrt *fmt, ul_jsonwrt_value_close(fmt); } +void ul_jsonwrt_value_s_sized(struct ul_jsonwrt *fmt, + const char *name, const char *data, size_t size) +{ + ul_jsonwrt_value_open(fmt, name); + if (data && *data) + fputs_quoted_case_json(data, fmt->out, 0, size); + else + fputs("null", fmt->out); + ul_jsonwrt_value_close(fmt); +} + void ul_jsonwrt_value_u64(struct ul_jsonwrt *fmt, const char *name, uint64_t data) { @@ -212,6 +219,14 @@ void ul_jsonwrt_value_u64(struct ul_jsonwrt *fmt, ul_jsonwrt_value_close(fmt); } +void ul_jsonwrt_value_double(struct ul_jsonwrt *fmt, + const char *name, long double data) +{ + ul_jsonwrt_value_open(fmt, name); + fprintf(fmt->out, "%Lg", data); + ul_jsonwrt_value_close(fmt); +} + void ul_jsonwrt_value_boolean(struct ul_jsonwrt *fmt, const char *name, int data) { diff --git a/lib/loopdev.c b/lib/loopdev.c index dd9ead3..c72fb2c 100644 --- a/lib/loopdev.c +++ b/lib/loopdev.c @@ -116,7 +116,9 @@ int loopcxt_set_device(struct loopdev_cxt *lc, const char *device) DBG(CXT, ul_debugobj(lc, "closing old open fd")); } lc->fd = -1; - lc->mode = 0; + lc->is_lost = 0; + lc->devno = 0; + lc->mode = O_RDONLY; lc->blocksize = 0; lc->has_info = 0; lc->info_failed = 0; @@ -153,6 +155,28 @@ int loopcxt_has_device(struct loopdev_cxt *lc) return lc && *lc->device; } +dev_t loopcxt_get_devno(struct loopdev_cxt *lc) +{ + if (!lc || !loopcxt_has_device(lc)) + return 0; + if (!lc->devno) + lc->devno = sysfs_devname_to_devno(lc->device); + return lc->devno; +} + +int loopcxt_is_lost(struct loopdev_cxt *lc) +{ + if (!lc || !loopcxt_has_device(lc)) + return 0; + if (lc->is_lost) + return 1; + + lc->is_lost = access(lc->device, F_OK) != 0 + && loopcxt_get_devno(lc) != 0; + + return lc->is_lost; +} + /* * @lc: context * @flags: LOOPDEV_FL_* flags @@ -165,12 +189,6 @@ int loopcxt_has_device(struct loopdev_cxt *lc) * * * LO_FLAGS_* are kernel flags used for LOOP_{SET,GET}_STAT64 ioctls * - * Note about LOOPDEV_FL_{RDONLY,RDWR} flags. These flags are used for open(2) - * syscall to open loop device. By default is the device open read-only. - * - * The exception is loopcxt_setup_device(), where the device is open read-write - * if LO_FLAGS_READ_ONLY flags is not set (see loopcxt_set_flags()). - * * Returns: <0 on error, 0 on success. */ int loopcxt_init(struct loopdev_cxt *lc, int flags) @@ -271,7 +289,7 @@ static struct path_cxt *loopcxt_get_sysfs(struct loopdev_cxt *lc) return NULL; if (!lc->sysfs) { - dev_t devno = sysfs_devname_to_devno(lc->device); + dev_t devno = loopcxt_get_devno(lc); if (!devno) { DBG(CXT, ul_debugobj(lc, "sysfs: failed devname to devno")); return NULL; @@ -285,28 +303,50 @@ static struct path_cxt *loopcxt_get_sysfs(struct loopdev_cxt *lc) return lc->sysfs; } -/* - * @lc: context - * - * Returns: file descriptor to the open loop device or <0 on error. The mode - * depends on LOOPDEV_FL_{RDWR,RDONLY} context flags. Default is - * read-only. - */ -int loopcxt_get_fd(struct loopdev_cxt *lc) +static int __loopcxt_get_fd(struct loopdev_cxt *lc, mode_t mode) { + int old = -1; + if (!lc || !*lc->device) return -EINVAL; + /* It's okay to return a FD with read-write permissions if someone + * asked for read-only, but you shouldn't do the opposite. + * + * (O_RDONLY is a widely usable default.) + */ + if (lc->fd >= 0 && mode == O_RDWR && lc->mode == O_RDONLY) { + DBG(CXT, ul_debugobj(lc, "closing already open device (mode mismatch)")); + old = lc->fd; + lc->fd = -1; + } + if (lc->fd < 0) { - lc->mode = lc->flags & LOOPDEV_FL_RDWR ? O_RDWR : O_RDONLY; + lc->mode = mode; lc->fd = open(lc->device, lc->mode | O_CLOEXEC); DBG(CXT, ul_debugobj(lc, "open %s [%s]: %m", lc->device, - lc->flags & LOOPDEV_FL_RDWR ? "rw" : "ro")); + mode == O_RDONLY ? "ro" : + mode == O_RDWR ? "rw" : "??")); + + if (lc->fd < 0 && old >= 0) { + /* restore original on error */ + lc->fd = old; + old = -1; + } } + + if (old >= 0) + close(old); return lc->fd; } -int loopcxt_set_fd(struct loopdev_cxt *lc, int fd, int mode) +/* default is read-only file descriptor, it's enough for all ioctls */ +int loopcxt_get_fd(struct loopdev_cxt *lc) +{ + return __loopcxt_get_fd(lc, O_RDONLY); +} + +int loopcxt_set_fd(struct loopdev_cxt *lc, int fd, mode_t mode) { if (!lc) return -EINVAL; @@ -400,13 +440,6 @@ static int loopiter_set_device(struct loopdev_cxt *lc, const char *device) !(lc->iter.flags & LOOPITER_FL_FREE)) return 0; /* caller does not care about device status */ - if (!is_loopdev(lc->device)) { - DBG(ITER, ul_debugobj(&lc->iter, "%s does not exist", lc->device)); - return -errno; - } - - DBG(ITER, ul_debugobj(&lc->iter, "%s exist", lc->device)); - used = loopcxt_get_offset(lc, NULL) == 0; if ((lc->iter.flags & LOOPITER_FL_USED) && used) @@ -479,7 +512,7 @@ static int loop_scandir(const char *dirname, int **ary, int hasprefix) arylen += 1; - tmp = realloc(*ary, arylen * sizeof(int)); + tmp = reallocarray(*ary, arylen, sizeof(int)); if (!tmp) { free(*ary); *ary = NULL; @@ -747,6 +780,26 @@ char *loopcxt_get_backing_file(struct loopdev_cxt *lc) return res; } +/* + * @lc: context + * + * Returns (allocated) string with loop reference. The same as backing file by + * default. + */ +char *loopcxt_get_refname(struct loopdev_cxt *lc) +{ + char *res = NULL; + struct loop_info64 *lo = loopcxt_get_info(lc); + + if (lo) { + lo->lo_file_name[LO_NAME_SIZE - 1] = '\0'; + res = strdup((char *) lo->lo_file_name); + } + + DBG(CXT, ul_debugobj(lc, "get_refname [%s]", res)); + return res; +} + /* * @lc: context * @offset: returns offset number for the given device @@ -840,7 +893,7 @@ int loopcxt_get_sizelimit(struct loopdev_cxt *lc, uint64_t *size) /* * @lc: context - * @devno: returns encryption type + * @type: returns encryption type * * Cryptoloop is DEPRECATED! * @@ -865,7 +918,6 @@ int loopcxt_get_encrypt_type(struct loopdev_cxt *lc, uint32_t *type) /* * @lc: context - * @devno: returns crypt name * * Cryptoloop is DEPRECATED! * @@ -1180,6 +1232,28 @@ int loopcxt_set_flags(struct loopdev_cxt *lc, uint32_t flags) return 0; } +/* + * @lc: context + * @refname: reference name (used to overwrite lo_file_name where is backing + * file by default) + * + * The setting is removed by loopcxt_set_device() loopcxt_next()! + * + * Returns: 0 on success, <0 on error. + */ +int loopcxt_set_refname(struct loopdev_cxt *lc, const char *refname) +{ + if (!lc) + return -EINVAL; + + memset(lc->config.info.lo_file_name, 0, sizeof(lc->config.info.lo_file_name)); + if (refname) + xstrncpy((char *)lc->config.info.lo_file_name, refname, LO_NAME_SIZE); + + DBG(CXT, ul_debugobj(lc, "set refname=%s", (char *)lc->config.info.lo_file_name)); + return 0; +} + /* * @lc: context * @filename: backing file path (the path will be canonicalized) @@ -1197,9 +1271,10 @@ int loopcxt_set_backing_file(struct loopdev_cxt *lc, const char *filename) if (!lc->filename) return -errno; - xstrncpy((char *)lc->config.info.lo_file_name, lc->filename, LO_NAME_SIZE); + if (!lc->config.info.lo_file_name[0]) + loopcxt_set_refname(lc, lc->filename); - DBG(CXT, ul_debugobj(lc, "set backing file=%s", lc->config.info.lo_file_name)); + DBG(CXT, ul_debugobj(lc, "set backing file=%s", lc->filename)); return 0; } @@ -1314,7 +1389,8 @@ static int loopcxt_check_size(struct loopdev_cxt *lc, int file_fd) */ int loopcxt_setup_device(struct loopdev_cxt *lc) { - int file_fd, dev_fd, mode = O_RDWR, flags = O_CLOEXEC; + int file_fd, dev_fd; + mode_t flags = O_CLOEXEC, mode = O_RDWR; int rc = -1, cnt = 0; int errsv = 0; int fallback = 0; @@ -1344,25 +1420,22 @@ int loopcxt_setup_device(struct loopdev_cxt *lc) } DBG(SETUP, ul_debugobj(lc, "backing file open: OK")); - if (lc->fd != -1 && lc->mode != mode) { - DBG(SETUP, ul_debugobj(lc, "closing already open device (mode mismatch)")); - close(lc->fd); - lc->fd = -1; - lc->mode = 0; - } - - if (mode == O_RDONLY) { - lc->flags |= LOOPDEV_FL_RDONLY; /* open() mode */ + if (mode == O_RDONLY) lc->config.info.lo_flags |= LO_FLAGS_READ_ONLY; /* kernel loopdev mode */ - } else { - lc->flags |= LOOPDEV_FL_RDWR; /* open() mode */ + else lc->config.info.lo_flags &= ~LO_FLAGS_READ_ONLY; - lc->flags &= ~LOOPDEV_FL_RDONLY; - } do { errno = 0; - dev_fd = loopcxt_get_fd(lc); + + /* For the ioctls, it's enough to use O_RDONLY, but udevd + * monitor devices by inotify, and udevd needs IN_CLOSE_WRITE + * event to trigger probing of the new device. + * + * The mode used for the device does not have to match the mode + * used for the backing file. + */ + dev_fd = __loopcxt_get_fd(lc, O_RDWR); if (dev_fd >= 0 || lc->control_ok == 0) break; if (errno != EACCES && errno != ENOENT) diff --git a/lib/mbsalign.c b/lib/mbsalign.c index 7b8f5a6..b4ab7be 100644 --- a/lib/mbsalign.c +++ b/lib/mbsalign.c @@ -310,11 +310,32 @@ char *mbs_invalid_encode_to_buffer(const char *s, size_t *width, char *buf) return buf; } +/* + * Guess size + */ size_t mbs_safe_encode_size(size_t bytes) { return (bytes * 4) + 1; } +/* + * Count size of the original string in bytes (count \x?? as one byte) + */ +size_t mbs_safe_decode_size(const char *p) +{ + size_t bytes = 0; + + while (p && *p) { + if (*p == '\\' && *(p + 1) == 'x' && + isxdigit(*(p + 2)) && isxdigit(*(p + 3))) + p += 4; + else + p++; + bytes++; + } + return bytes; +} + /* * Returns allocated string where all control and non-printable chars are * replaced with \x?? hex sequence. diff --git a/lib/mbsedit.c b/lib/mbsedit.c index ecfa9f4..9cf4f0f 100644 --- a/lib/mbsedit.c +++ b/lib/mbsedit.c @@ -157,13 +157,14 @@ static size_t mbs_insert(char *str, wint_t c, size_t *ncells) #ifdef HAVE_WIDECHAR wchar_t wc = (wchar_t) c; - char in_buf[MB_CUR_MAX]; + in = malloc(MB_CUR_MAX); + if (!in) + return -1; - n = wctomb(in_buf, wc); + n = wctomb(in, wc); if (n == (size_t) -1) - return n; + goto out; *ncells = wcwidth(wc); - in = in_buf; #else *ncells = 1; in = (char *) &c; @@ -173,6 +174,10 @@ static size_t mbs_insert(char *str, wint_t c, size_t *ncells) memmove(str + n, str, bytes); memcpy(str, in, n); str[bytes + n] = '\0'; +out: +#ifdef HAVE_WIDECHAR + free(in); +#endif return n; } diff --git a/lib/monotonic.c b/lib/monotonic.c index cd554dd..63feec0 100644 --- a/lib/monotonic.c +++ b/lib/monotonic.c @@ -18,10 +18,10 @@ int get_boot_time(struct timeval *boot_time) { #ifdef CLOCK_BOOTTIME - struct timespec hires_uptime; - struct timeval lores_uptime; + struct timespec hires_uptime = { 0 }; + struct timeval lores_uptime = { 0 }; #endif - struct timeval now; + struct timeval now = { 0 }; #ifdef HAVE_SYSINFO struct sysinfo info; #endif diff --git a/lib/pager.c b/lib/pager.c index 9429032..98814b5 100644 --- a/lib/pager.c +++ b/lib/pager.c @@ -85,7 +85,9 @@ static int start_command(struct child_process *cmd) close(cmd->in); } - cmd->preexec_cb(); + if (cmd->preexec_cb) + cmd->preexec_cb(); + execvp(cmd->argv[0], (char *const*) cmd->argv); errexec(cmd->argv[0]); } @@ -140,7 +142,7 @@ static int finish_command(struct child_process *cmd) return wait_or_whine(cmd->pid); } -static void pager_preexec(void) +static void pager_preexec_less(void) { /* * Work around bug in "less" by not starting it until we @@ -178,35 +180,45 @@ static void wait_for_pager_signal(int signo) static int has_command(const char *cmd) { const char *path; - char *p, *s; + char *b, *c, *p, *s; int rc = 0; if (!cmd) goto done; - if (*cmd == '/') { - rc = access(cmd, X_OK) == 0; + + c = xstrdup(cmd); + if (!c) goto done; + b = strtok(c, " "); /* cmd may contain options */ + if (!b) + goto cleanup; + + if (*b == '/') { + rc = access(b, X_OK) == 0; + goto cleanup; } path = getenv("PATH"); if (!path) - goto done; + goto cleanup; p = xstrdup(path); if (!p) - goto done; + goto cleanup; - for(s = strtok(p, ":"); s; s = strtok(NULL, ":")) { + for (s = strtok(p, ":"); s; s = strtok(NULL, ":")) { int fd = open(s, O_RDONLY|O_CLOEXEC); if (fd < 0) continue; - rc = faccessat(fd, cmd, X_OK, 0) == 0; + rc = faccessat(fd, b, X_OK, 0) == 0; close(fd); if (rc) break; } free(p); +cleanup: + free(c); done: - /*fprintf(stderr, "has PAGER %s rc=%d\n", cmd, rc);*/ + /*fprintf(stderr, "has PAGER '%s': rc=%d\n", cmd, rc);*/ return rc; } @@ -230,7 +242,11 @@ static void __setup_pager(void) pager_argv[2] = pager; pager_process.argv = pager_argv; pager_process.in = -1; - pager_process.preexec_cb = pager_preexec; + + if (!strncmp(pager, "less", 4)) + pager_process.preexec_cb = pager_preexec_less; + else + pager_process.preexec_cb = NULL; if (start_command(&pager_process)) return; diff --git a/lib/path.c b/lib/path.c index 4aceda1..202f19a 100644 --- a/lib/path.c +++ b/lib/path.c @@ -996,38 +996,25 @@ int ul_path_next_dirent(struct path_cxt *pc, DIR **sub, const char *dirname, str return 1; } -/* - * Like fopen() but, @path is always prefixed by @prefix. This function is - * useful in case when ul_path_* API is overkill. - */ -FILE *ul_prefix_fopen(const char *prefix, const char *path, const char *mode) -{ - char buf[PATH_MAX]; - - if (!path) - return NULL; - if (!prefix) - return fopen(path, mode); - if (*path == '/') - path++; - - snprintf(buf, sizeof(buf), "%s/%s", prefix, path); - return fopen(buf, mode); -} - #ifdef HAVE_CPU_SET_T static int ul_path_cpuparse(struct path_cxt *pc, cpu_set_t **set, int maxcpus, int islist, const char *path, va_list ap) { FILE *f; size_t setsize, len = maxcpus * 7; - char buf[len]; + char *buf; int rc; *set = NULL; + buf = malloc(len); + if (!buf) + return -ENOMEM; + f = ul_path_vfopenf(pc, "r" UL_CLOEXECSTR, path, ap); - if (!f) - return -errno; + if (!f) { + rc = -errno; + goto out; + } if (fgets(buf, len, f) == NULL) { errno = EIO; @@ -1038,32 +1025,38 @@ static int ul_path_cpuparse(struct path_cxt *pc, cpu_set_t **set, int maxcpus, i fclose(f); if (rc) - return rc; + goto out; len = strlen(buf); if (len > 0 && buf[len - 1] == '\n') buf[len - 1] = '\0'; *set = cpuset_alloc(maxcpus, &setsize, NULL); - if (!*set) - return -ENOMEM; + if (!*set) { + rc = -EINVAL; + goto out; + } if (islist) { if (cpulist_parse(buf, *set, setsize, 0)) { - cpuset_free(*set); errno = EINVAL; rc = -errno; - return rc; + goto out; } } else { if (cpumask_parse(buf, *set, setsize)) { - cpuset_free(*set); errno = EINVAL; rc = -errno; - return rc; + goto out; } } - return 0; + rc = 0; + +out: + if (rc) + cpuset_free(*set); + free(buf); + return rc; } int ul_path_readf_cpuset(struct path_cxt *pc, cpu_set_t **set, int maxcpus, const char *path, ...) diff --git a/lib/procfs.c b/lib/procfs.c index aff20fb..59f40ff 100644 --- a/lib/procfs.c +++ b/lib/procfs.c @@ -169,6 +169,11 @@ ssize_t procfs_process_get_stat(struct path_cxt *pc, char *buf, size_t bufsz) return procfs_process_get_data_for(pc, buf, bufsz, "stat"); } +ssize_t procfs_process_get_syscall(struct path_cxt *pc, char *buf, size_t bufsz) +{ + return procfs_process_get_data_for(pc, buf, bufsz, "syscall"); +} + int procfs_process_get_stat_nth(struct path_cxt *pc, int n, uintmax_t *re) { ssize_t rc; diff --git a/lib/pty-session.c b/lib/pty-session.c index 3849065..815264d 100644 --- a/lib/pty-session.c +++ b/lib/pty-session.c @@ -241,6 +241,7 @@ int ul_pty_signals_setup(struct ul_pty *pty) sigaddset(&ourset, SIGCHLD); sigaddset(&ourset, SIGWINCH); sigaddset(&ourset, SIGALRM); + sigaddset(&ourset, SIGHUP); sigaddset(&ourset, SIGTERM); sigaddset(&ourset, SIGINT); sigaddset(&ourset, SIGQUIT); @@ -584,6 +585,8 @@ static int handle_signal(struct ul_pty *pty, int fd) &info, (void *) &pty->win); } break; + case SIGHUP: + /* fallthrough */ case SIGTERM: /* fallthrough */ case SIGINT: @@ -641,7 +644,7 @@ int ul_pty_proxy_master(struct ul_pty *pty) /* note, callback usually updates @next_callback_time */ if (timerisset(&pty->next_callback_time)) { - struct timeval now; + struct timeval now = { 0 };; DBG(IO, ul_debugobj(pty, " callback requested")); gettime_monotonic(&now); @@ -654,7 +657,7 @@ int ul_pty_proxy_master(struct ul_pty *pty) /* set timeout */ if (timerisset(&pty->next_callback_time)) { - struct timeval now, rest; + struct timeval now = { 0 }, rest = { 0 }; gettime_monotonic(&now); timersub(&pty->next_callback_time, &now, &rest); diff --git a/lib/sha1.c b/lib/sha1.c index eedeaa8..3edff12 100644 --- a/lib/sha1.c +++ b/lib/sha1.c @@ -1,4 +1,7 @@ /* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * * SHA-1 in C by Steve Reid * 100% Public Domain * @@ -153,7 +156,15 @@ void ul_SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) state[3] += d; state[4] += e; /* Wipe variables */ +#ifdef HAVE_EXPLICIT_BZERO + explicit_bzero(&a, sizeof(a)); + explicit_bzero(&b, sizeof(b)); + explicit_bzero(&c, sizeof(c)); + explicit_bzero(&d, sizeof(d)); + explicit_bzero(&e, sizeof(e)); +#else a = b = c = d = e = 0; +#endif #ifdef UL_SHA1HANDSOFF memset(block, '\0', sizeof(block)); #endif diff --git a/lib/sha256.c b/lib/sha256.c index 934303c..10877d2 100644 --- a/lib/sha256.c +++ b/lib/sha256.c @@ -1,4 +1,7 @@ /* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * * public domain sha256 crypt implementation * * original sha crypt design: http://people.redhat.com/drepper/SHA-crypt.txt diff --git a/lib/shells.c b/lib/shells.c index 6693ab0..18e4cca 100644 --- a/lib/shells.c +++ b/lib/shells.c @@ -9,20 +9,11 @@ #include "closestream.h" #include "shells.h" -/* - * is_known_shell() -- if the given shell appears in /etc/shells - * or vendor defined files. - * Return 1 if found and return 0 if not found. - */ -extern int is_known_shell(const char *shell_name) -{ - int ret = 0; - #if defined (HAVE_LIBECONF) && defined (USE_VENDORDIR) - size_t size = 0; +static econf_file *open_etc_shells(void) +{ econf_err error; - char **keys; - econf_file *key_file; + econf_file *key_file = NULL; error = econf_readDirs(&key_file, _PATH_VENDORDIR, @@ -35,31 +26,84 @@ extern int is_known_shell(const char *shell_name) syslog(LOG_ALERT, _("Cannot parse shells files: %s"), econf_errString(error)); - exit(EXIT_FAILURE); + return NULL; } - error = econf_getKeys(key_file, NULL, &size, &keys); - if (error) { - syslog(LOG_ALERT, - _("Cannot evaluate entries in shells files: %s"), - econf_errString(error)); - econf_free (key_file); - exit(EXIT_FAILURE); - } + return key_file; +} +#endif - for (size_t i = 0; i < size; i++) { - if (strcmp (keys[i], shell_name) == 0) { - ret = 1; - break; - } - } - econf_free (key_file); +/* + * print_shells () -- /etc/shells is outputted to stdout. + */ +extern void print_shells(FILE *out, const char *format) +{ +#if defined (HAVE_LIBECONF) && defined (USE_VENDORDIR) + size_t size = 0; + econf_err error; + char **keys = NULL; + econf_file *key_file = open_etc_shells(); + + if (!key_file) + return; + + error = econf_getKeys(key_file, NULL, &size, &keys); + if (error) { + econf_free(key_file); + errx(EXIT_FAILURE, + _("Cannot evaluate entries in shells files: %s"), + econf_errString(error)); + } + + for (size_t i = 0; i < size; i++) { + fprintf(out, format, keys[i]); + } + econf_free(keys); + econf_free(key_file); #else - char *s; + char *s; + + while ((s = getusershell())) + fprintf(out, format, s); + endusershell(); +#endif +} + + +/* + * is_known_shell() -- if the given shell appears in /etc/shells + * or vendor defined files. + * Return 1 if found and return 0 if not found. + */ +extern int is_known_shell(const char *shell_name) +{ + int ret = 0; if (!shell_name) return 0; +#if defined (HAVE_LIBECONF) && defined (USE_VENDORDIR) + char *val = NULL; + econf_err error; + econf_file *key_file = open_etc_shells(); + + if (!key_file) + return 0; + + error = econf_getStringValue (key_file, NULL, shell_name, &val); + if (error) { + if (error != ECONF_NOKEY) + syslog(LOG_ALERT, + _("Cannot evaluate entries in shells files: %s"), + econf_errString(error)); + } else + ret = 1; + + free(val); + econf_free(key_file); +#else + char *s; + setusershell(); while ((s = getusershell())) { if (*s != '#' && strcmp(shell_name, s) == 0) { @@ -71,4 +115,3 @@ extern int is_known_shell(const char *shell_name) #endif return ret; } - diff --git a/lib/strutils.c b/lib/strutils.c index 6229a6e..9ea5da7 100644 --- a/lib/strutils.c +++ b/lib/strutils.c @@ -456,21 +456,28 @@ err: errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); } -long double strtold_or_err(const char *str, const char *errmesg) +int ul_strtold(const char *str, long double *num) { - double num; char *end = NULL; errno = 0; if (str == NULL || *str == '\0') - goto err; - num = strtold(str, &end); + return -(errno = EINVAL); + *num = strtold(str, &end); - if (errno || str == end || (end && *end)) - goto err; + if (errno != 0) + return -errno; + if (str == end || (end && *end)) + return -(errno = EINVAL); + return 0; +} - return num; -err: +long double strtold_or_err(const char *str, const char *errmesg) +{ + long double num = 0; + + if (ul_strtold(str, &num) == 0) + return num; if (errno == ERANGE) err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); @@ -1010,6 +1017,34 @@ int strappend(char **a, const char *b) return 0; } +/* the hybrid version of strfconcat and strappend. */ +int strfappend(char **a, const char *format, ...) +{ + va_list ap; + int res; + + va_start(ap, format); + res = strvfappend(a, format, ap); + va_end(ap); + + return res; +} + +extern int strvfappend(char **a, const char *format, va_list ap) +{ + char *val; + int sz; + int res; + + sz = vasprintf(&val, format, ap); + if (sz < 0) + return -errno; + + res = strappend(a, val); + free(val); + return res; +} + static size_t strcspn_escaped(const char *s, const char *reject) { int escaped = 0; @@ -1386,6 +1421,15 @@ int main(int argc, char *argv[]) printf("\"%s\" --> \"%s\"\n", argv[2], ul_strchr_escaped(argv[2], *argv[3])); return EXIT_SUCCESS; + } else if (argc == 2 && strcmp(argv[1], "--next-string") == 0) { + char *buf = "abc\0Y\0\0xyz\0X"; + char *end = buf + 12; + char *p = buf; + + do { + printf("str: '%s'\n", p); + } while ((p = ul_next_string(p, end))); + } else { fprintf(stderr, "usage: %1$s --size [suffix]\n" " %1$s --cmp-paths \n" diff --git a/lib/strv.c b/lib/strv.c index c306e38..fd84fe3 100644 --- a/lib/strv.c +++ b/lib/strv.c @@ -74,7 +74,7 @@ unsigned strv_length(char * const *l) { return n; } -char **strv_new_ap(const char *x, va_list ap) { +static char **strv_new_ap(const char *x, va_list ap) { const char *s; char **a; unsigned n = 0, i = 0; @@ -265,7 +265,7 @@ int strv_push(char ***l, char *value) { if (m < n) return -ENOMEM; - c = realloc(*l, sizeof(char *) * m); + c = reallocarray(*l, m, sizeof(char *)); if (!c) return -ENOMEM; diff --git a/lib/terminal-colors.d.5 b/lib/terminal-colors.d.5 index 4b98389..1de42f4 100644 --- a/lib/terminal-colors.d.5 +++ b/lib/terminal-colors.d.5 @@ -2,12 +2,12 @@ .\" Title: terminal-colors.d .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-10-23 +.\" Date: 2024-01-31 .\" Manual: File formats -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "TERMINAL\-COLORS.D" "5" "2023-10-23" "util\-linux 2.39.3" "File formats" +.TH "TERMINAL\-COLORS.D" "5" "2024-01-31" "util\-linux 2.40" "File formats" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 diff --git a/lib/timeutils.c b/lib/timeutils.c index 34c7c8d..f53ec8f 100644 --- a/lib/timeutils.c +++ b/lib/timeutils.c @@ -161,6 +161,23 @@ static int parse_subseconds(const char *t, usec_t *usec) return 0; } +static const char *parse_epoch_seconds(const char *t, struct tm *tm) +{ + int64_t s; + time_t st; + int f, c; + + f = sscanf(t, "%"SCNd64"%n", &s, &c); + if (f < 1) + return NULL; + st = s; + if ((int64_t) st < s) + return NULL; + if (!localtime_r(&st, tm)) + return NULL; + return t + c; +} + static int parse_timestamp_reference(time_t x, const char *t, usec_t *usec) { static const struct { @@ -254,7 +271,7 @@ static int parse_timestamp_reference(time_t x, const char *t, usec_t *usec) goto finish; } else if (t[0] == '@') { - k = strptime(t + 1, "%s", &tm); + k = parse_epoch_seconds(t + 1, &tm); if (k && *k == 0) goto finish; else if (k && parse_subseconds(k, &ret) == 0) @@ -373,11 +390,13 @@ static int parse_timestamp_reference(time_t x, const char *t, usec_t *usec) ret += (usec_t) x * USEC_PER_SEC; + if (minus > ret) + return -ERANGE; + if ((ret + plus) < ret) + return -ERANGE; + ret += plus; - if (ret > minus) - ret -= minus; - else - ret = 0; + ret -= minus; *usec = ret; @@ -448,8 +467,9 @@ int get_gmtoff(const struct tm *tp) #endif } -static int format_iso_time(struct tm *tm, suseconds_t usec, int flags, char *buf, size_t bufsz) +static int format_iso_time(const struct tm *tm, uint32_t nsec, int flags, char *buf, size_t bufsz) { + uint32_t usec = nsec / NSEC_PER_USEC; char *p = buf; int len; @@ -479,15 +499,28 @@ static int format_iso_time(struct tm *tm, suseconds_t usec, int flags, char *buf p += len; } - if (flags & ISO_DOTUSEC) { - len = snprintf(p, bufsz, ".%06"PRId64, (int64_t) usec); + if (flags & ISO_DOTNSEC) { + len = snprintf(p, bufsz, ".%09"PRIu32, nsec); + if (len < 0 || (size_t) len > bufsz) + goto err; + bufsz -= len; + p += len; + + } else if (flags & ISO_COMMANSEC) { + len = snprintf(p, bufsz, ",%09"PRIu32, nsec); + if (len < 0 || (size_t) len > bufsz) + goto err; + bufsz -= len; + p += len; + } else if (flags & ISO_DOTUSEC) { + len = snprintf(p, bufsz, ".%06"PRIu32, usec); if (len < 0 || (size_t) len > bufsz) goto err; bufsz -= len; p += len; } else if (flags & ISO_COMMAUSEC) { - len = snprintf(p, bufsz, ",%06"PRId64, (int64_t) usec); + len = snprintf(p, bufsz, ",%06"PRIu32, usec); if (len < 0 || (size_t) len > bufsz) goto err; bufsz -= len; @@ -508,26 +541,37 @@ static int format_iso_time(struct tm *tm, suseconds_t usec, int flags, char *buf return -1; } -/* timeval to ISO 8601 */ -int strtimeval_iso(struct timeval *tv, int flags, char *buf, size_t bufsz) +/* timespec to ISO 8601 */ +int strtimespec_iso(const struct timespec *ts, int flags, char *buf, size_t bufsz) { struct tm tm; struct tm *rc; if (flags & ISO_GMTIME) - rc = gmtime_r(&tv->tv_sec, &tm); + rc = gmtime_r(&ts->tv_sec, &tm); else - rc = localtime_r(&tv->tv_sec, &tm); + rc = localtime_r(&ts->tv_sec, &tm); if (rc) - return format_iso_time(&tm, tv->tv_usec, flags, buf, bufsz); + return format_iso_time(&tm, ts->tv_nsec, flags, buf, bufsz); - warnx(_("time %"PRId64" is out of range."), (int64_t)(tv->tv_sec)); + warnx(_("time %"PRId64" is out of range."), (int64_t)(ts->tv_sec)); return -1; } +/* timeval to ISO 8601 */ +int strtimeval_iso(const struct timeval *tv, int flags, char *buf, size_t bufsz) +{ + struct timespec ts = { + .tv_sec = tv->tv_sec, + .tv_nsec = tv->tv_usec * NSEC_PER_USEC, + }; + + return strtimespec_iso(&ts, flags, buf, bufsz); +} + /* struct tm to ISO 8601 */ -int strtm_iso(struct tm *tm, int flags, char *buf, size_t bufsz) +int strtm_iso(const struct tm *tm, int flags, char *buf, size_t bufsz) { return format_iso_time(tm, 0, flags, buf, bufsz); } @@ -592,6 +636,63 @@ int strtime_short(const time_t *t, struct timeval *now, int flags, char *buf, si return rc <= 0 ? -1 : 0; } +int strtimespec_relative(const struct timespec *ts, char *buf, size_t bufsz) +{ + time_t secs = ts->tv_sec; + size_t i, parts = 0; + int rc; + + if (bufsz) + buf[0] = '\0'; + + static const struct { + const char * const suffix; + int width; + int64_t secs; + } table[] = { + { "y", 4, NSEC_PER_YEAR / NSEC_PER_SEC }, + { "d", 3, NSEC_PER_DAY / NSEC_PER_SEC }, + { "h", 2, NSEC_PER_HOUR / NSEC_PER_SEC }, + { "m", 2, NSEC_PER_MINUTE / NSEC_PER_SEC }, + { "s", 2, NSEC_PER_SEC / NSEC_PER_SEC }, + }; + + for (i = 0; i < ARRAY_SIZE(table); i++) { + if (secs >= table[i].secs) { + rc = snprintf(buf, bufsz, + "%*"PRId64"%s%s", + parts ? table[i].width : 0, + secs / table[i].secs, table[i].suffix, + secs % table[i].secs ? " " : ""); + if (rc < 0 || (size_t) rc > bufsz) + goto err; + parts++; + buf += rc; + bufsz -= rc; + secs %= table[i].secs; + } + } + + if (ts->tv_nsec) { + if (ts->tv_nsec % NSEC_PER_MSEC) { + rc = snprintf(buf, bufsz, "%*luns", + parts ? 10 : 0, ts->tv_nsec); + if (rc < 0 || (size_t) rc > bufsz) + goto err; + } else { + rc = snprintf(buf, bufsz, "%*llums", + parts ? 4 : 0, ts->tv_nsec / NSEC_PER_MSEC); + if (rc < 0 || (size_t) rc > bufsz) + goto err; + } + } + + return 0; + err: + warnx(_("format_reltime: buffer overflow.")); + return -1; +} + #ifndef HAVE_TIMEGM time_t timegm(struct tm *tm) { @@ -625,6 +726,7 @@ static int run_unittest_timestamp(void) { "2012-09-22 16:34:22.012", 1348331662012000 }, { "@1348331662" , 1348331662000000 }, { "@1348331662.234567" , 1348331662234567 }, + { "@0" , 0 }, { "2012-09-22 16:34" , 1348331640000000 }, { "2012-09-22" , 1348272000000000 }, { "16:34:22" , 1674232462000000 }, @@ -662,45 +764,142 @@ static int run_unittest_timestamp(void) return rc; } +static int run_unittest_format(void) +{ + int rc = EXIT_SUCCESS; + const struct timespec ts = { + .tv_sec = 1674180427, + .tv_nsec = 12345, + }; + char buf[FORMAT_TIMESTAMP_MAX]; + static const struct testcase { + int flags; + const char * const expected; + } testcases[] = { + { ISO_DATE, "2023-01-20" }, + { ISO_TIME, "02:07:07" }, + { ISO_TIMEZONE, "+00:00" }, + { ISO_TIMESTAMP_T, "2023-01-20T02:07:07+00:00" }, + { ISO_TIMESTAMP_COMMA_G, "2023-01-20 02:07:07,000012+00:00" }, + { ISO_TIME | ISO_DOTNSEC, "02:07:07.000012345" }, + }; + + setenv("TZ", "GMT", 1); + tzset(); + + for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) { + struct testcase t = testcases[i]; + int r = strtimespec_iso(&ts, t.flags, buf, sizeof(buf)); + if (r) { + fprintf(stderr, "Could not format '%s'\n", t.expected); + rc = EXIT_FAILURE; + } + + if (strcmp(buf, t.expected)) { + fprintf(stderr, "#%02zu %-20s != %-20s\n", i, buf, t.expected); + rc = EXIT_FAILURE; + } + } + + return rc; +} + +static int run_unittest_format_relative(void) +{ + int rc = EXIT_SUCCESS; + char buf[FORMAT_TIMESTAMP_MAX]; + static const struct testcase { + struct timespec ts; + const char * const expected; + } testcases[] = { + {{}, "" }, + {{ 1 }, "1s" }, + {{ 10 }, "10s" }, + {{ 100 }, "1m 40s" }, + {{ 1000 }, "16m 40s" }, + {{ 10000 }, "2h 46m 40s" }, + {{ 100000 }, "1d 3h 46m 40s" }, + {{ 1000000 }, "11d 13h 46m 40s" }, + {{ 10000000 }, "115d 17h 46m 40s" }, + {{ 100000000 }, "3y 61d 15h 46m 40s" }, + {{ 60 }, "1m" }, + {{ 3600 }, "1h" }, + + {{ 1, 1 }, "1s 1ns" }, + {{ 0, 1 }, "1ns" }, + {{ 0, 1000000 }, "1ms" }, + {{ 0, 1000001 }, "1000001ns" }, + }; + + setenv("TZ", "GMT", 1); + tzset(); + + for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) { + struct testcase t = testcases[i]; + int r = strtimespec_relative(&t.ts, buf, sizeof(buf)); + if (r) { + fprintf(stderr, "Could not format '%s'\n", t.expected); + rc = EXIT_FAILURE; + } + + if (strcmp(buf, t.expected)) { + fprintf(stderr, "#%02zu '%-20s' != '%-20s'\n", i, buf, t.expected); + rc = EXIT_FAILURE; + } + } + + return rc; +} + int main(int argc, char *argv[]) { - struct timeval tv = { 0 }; + struct timespec ts = { 0 }; char buf[ISO_BUFSIZ]; + int r; if (argc < 2) { fprintf(stderr, "usage: %s [