From 64426f1a89450a0f79b723a4d966f7f9c7492c60 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 12 Mar 2022 22:43:10 -0500 Subject: Don't shadow standard headers @italic on the AUR stated that bfs from the AUR fails to build on Manjaro. From the build log, it seems like doesn't get included properly. I assume it's picking up ./time.h instead. I couldn't reproduce the build issue in the default configuration, but it does fail with EXTRA_CFLAGS="-I." which isn't good. So rename everything with an x prefix to stop clashing. Link: https://aur.archlinux.org/packages/bfs#comment-856102 Link: https://paste.rs/eqR --- Makefile | 10 +- eval.c | 4 +- exec.c | 2 +- expr.h | 2 +- main.c | 6 +- parse.c | 6 +- printf.c | 2 +- regex.c | 301 ---------------------------------------------------- regex.h | 97 ----------------- spawn.c | 318 ------------------------------------------------------- spawn.h | 123 --------------------- tests/xtimegm.c | 2 +- time.c | 323 -------------------------------------------------------- time.h | 86 --------------- util.c | 2 +- xregex.c | 301 ++++++++++++++++++++++++++++++++++++++++++++++++++++ xregex.h | 97 +++++++++++++++++ xspawn.c | 318 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ xspawn.h | 123 +++++++++++++++++++++ xtime.c | 323 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ xtime.h | 86 +++++++++++++++ 21 files changed, 1266 insertions(+), 1266 deletions(-) delete mode 100644 regex.c delete mode 100644 regex.h delete mode 100644 spawn.c delete mode 100644 spawn.h delete mode 100644 time.c delete mode 100644 time.h create mode 100644 xregex.c create mode 100644 xregex.h create mode 100644 xspawn.c create mode 100644 xspawn.h create mode 100644 xtime.c create mode 100644 xtime.h diff --git a/Makefile b/Makefile index 9ae4bd1..01b2c8d 100644 --- a/Makefile +++ b/Makefile @@ -217,17 +217,17 @@ bfs: \ parse.o \ printf.o \ pwcache.o \ - regex.o \ - spawn.o \ stat.o \ - time.o \ trie.o \ typo.o \ - util.o + util.o \ + xregex.o \ + xspawn.o \ + xtime.o tests/mksock: tests/mksock.o tests/trie: trie.o tests/trie.o -tests/xtimegm: time.o tests/xtimegm.o +tests/xtimegm: xtime.o tests/xtimegm.o $(BIN_GOALS): +$(CC) $(ALL_LDFLAGS) $^ $(ALL_LDLIBS) -o $@ diff --git a/eval.c b/eval.c index 321a8d9..41892d0 100644 --- a/eval.c +++ b/eval.c @@ -34,10 +34,10 @@ #include "printf.h" #include "pwcache.h" #include "stat.h" -#include "time.h" #include "trie.h" #include "util.h" -#include "regex.h" +#include "xregex.h" +#include "xtime.h" #include #include #include diff --git a/exec.c b/exec.c index 4cfb2e0..505e51c 100644 --- a/exec.c +++ b/exec.c @@ -20,8 +20,8 @@ #include "color.h" #include "diag.h" #include "dstring.h" -#include "spawn.h" #include "util.h" +#include "xspawn.h" #include #include #include diff --git a/expr.h b/expr.h index a660a6d..23a6f41 100644 --- a/expr.h +++ b/expr.h @@ -25,8 +25,8 @@ #include "eval.h" #include "exec.h" #include "printf.h" -#include "regex.h" #include "stat.h" +#include "xregex.h" #include #include #include diff --git a/main.c b/main.c index 2ad24a4..b40d245 100644 --- a/main.c +++ b/main.c @@ -43,13 +43,13 @@ * - fsade.[ch] (a facade over non-standard filesystem features) * - mtab.[ch] (parses the system's mount table) * - pwcache.[ch] (a cache for the user/group tables) - * - regex.[ch] (regular expression support) - * - spawn.[ch] (spawns processes) * - stat.[ch] (wraps stat(), or statx() on Linux) - * - time.[ch] (date/time handling utilities) * - trie.[ch] (a trie set/map implementation) * - typo.[ch] (fuzzy matching for typos) * - util.[ch] (everything else) + * - xregex.[ch] (regular expression support) + * - xspawn.[ch] (spawns processes) + * - xtime.[ch] (date/time handling utilities) */ #include "ctx.h" diff --git a/parse.c b/parse.c index 3439f23..04789da 100644 --- a/parse.c +++ b/parse.c @@ -36,12 +36,12 @@ #include "opt.h" #include "printf.h" #include "pwcache.h" -#include "spawn.h" #include "stat.h" -#include "time.h" #include "typo.h" #include "util.h" -#include "regex.h" +#include "xregex.h" +#include "xspawn.h" +#include "xtime.h" #include #include #include diff --git a/printf.c b/printf.c index 3e7df7d..b259104 100644 --- a/printf.c +++ b/printf.c @@ -24,8 +24,8 @@ #include "mtab.h" #include "pwcache.h" #include "stat.h" -#include "time.h" #include "util.h" +#include "xtime.h" #include #include #include diff --git a/regex.c b/regex.c deleted file mode 100644 index 9785bf8..0000000 --- a/regex.c +++ /dev/null @@ -1,301 +0,0 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ - -#include "regex.h" -#include "util.h" -#include -#include -#include -#include - -#if BFS_WITH_ONIGURUMA -# include -# include -#else -# include -#endif - -struct bfs_regex { -#if BFS_WITH_ONIGURUMA - unsigned char *pattern; - OnigRegex impl; - int err; - OnigErrorInfo einfo; -#else - regex_t impl; - int err; -#endif -}; - -#if BFS_WITH_ONIGURUMA -/** Get (and initialize) the appropriate encoding for the current locale. */ -static int bfs_onig_encoding(OnigEncoding *penc) { - static OnigEncoding enc = NULL; - if (enc) { - *penc = enc; - return ONIG_NORMAL; - } - - // Fall back to ASCII by default - enc = ONIG_ENCODING_ASCII; - - // Oniguruma has no locale support, so try to guess the right encoding - // from the current locale. - const char *charmap = nl_langinfo(CODESET); - if (charmap) { -#define BFS_MAP_ENCODING(name, value) \ - do { \ - if (strcmp(charmap, name) == 0) { \ - enc = value; \ - } \ - } while (0) -#define BFS_MAP_ENCODING2(name1, name2, value) \ - do { \ - BFS_MAP_ENCODING(name1, value); \ - BFS_MAP_ENCODING(name2, value); \ - } while (0) - - // These names were found with locale -m on Linux and FreeBSD -#define BFS_MAP_ISO_8859(n) \ - BFS_MAP_ENCODING2("ISO-8859-" #n, "ISO8859-" #n, ONIG_ENCODING_ISO_8859_ ## n) - - BFS_MAP_ISO_8859(1); - BFS_MAP_ISO_8859(2); - BFS_MAP_ISO_8859(3); - BFS_MAP_ISO_8859(4); - BFS_MAP_ISO_8859(5); - BFS_MAP_ISO_8859(6); - BFS_MAP_ISO_8859(7); - BFS_MAP_ISO_8859(8); - BFS_MAP_ISO_8859(9); - BFS_MAP_ISO_8859(10); - BFS_MAP_ISO_8859(11); - // BFS_MAP_ISO_8859(12); - BFS_MAP_ISO_8859(13); - BFS_MAP_ISO_8859(14); - BFS_MAP_ISO_8859(15); - BFS_MAP_ISO_8859(16); - - BFS_MAP_ENCODING("UTF-8", ONIG_ENCODING_UTF8); - -#define BFS_MAP_EUC(name) \ - BFS_MAP_ENCODING2("EUC-" #name, "euc" #name, ONIG_ENCODING_EUC_ ## name) - - BFS_MAP_EUC(JP); - BFS_MAP_EUC(TW); - BFS_MAP_EUC(KR); - BFS_MAP_EUC(CN); - - BFS_MAP_ENCODING2("SHIFT_JIS", "SJIS", ONIG_ENCODING_SJIS); - - // BFS_MAP_ENCODING("KOI-8", ONIG_ENCODING_KOI8); - BFS_MAP_ENCODING("KOI8-R", ONIG_ENCODING_KOI8_R); - - BFS_MAP_ENCODING("CP1251", ONIG_ENCODING_CP1251); - - BFS_MAP_ENCODING("GB18030", ONIG_ENCODING_BIG5); - } - - int ret = onig_initialize(&enc, 1); - if (ret != ONIG_NORMAL) { - enc = NULL; - } - *penc = enc; - return ret; -} -#endif - -int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_type type, enum bfs_regcomp_flags flags) { - struct bfs_regex *regex = *preg = malloc(sizeof(*regex)); - if (!regex) { - return -1; - } - -#if BFS_WITH_ONIGURUMA - // onig_error_code_to_str() says - // - // don't call this after the pattern argument of onig_new() is freed - // - // so make a defensive copy. - regex->pattern = (unsigned char *)strdup(pattern); - if (!regex->pattern) { - goto fail; - } - - regex->impl = NULL; - regex->err = ONIG_NORMAL; - - OnigSyntaxType *syntax = NULL; - switch (type) { - case BFS_REGEX_POSIX_BASIC: - syntax = ONIG_SYNTAX_POSIX_BASIC; - break; - case BFS_REGEX_POSIX_EXTENDED: - syntax = ONIG_SYNTAX_POSIX_EXTENDED; - break; - case BFS_REGEX_EMACS: - syntax = ONIG_SYNTAX_EMACS; - break; - case BFS_REGEX_GREP: - syntax = ONIG_SYNTAX_GREP; - break; - } - assert(syntax); - - OnigOptionType options = syntax->options; - if (flags & BFS_REGEX_ICASE) { - options |= ONIG_OPTION_IGNORECASE; - } - - OnigEncoding enc; - regex->err = bfs_onig_encoding(&enc); - if (regex->err != ONIG_NORMAL) { - return -1; - } - - const unsigned char *end = regex->pattern + strlen(pattern); - regex->err = onig_new(®ex->impl, regex->pattern, end, options, enc, syntax, ®ex->einfo); - if (regex->err != ONIG_NORMAL) { - return -1; - } -#else - int cflags = 0; - switch (type) { - case BFS_REGEX_POSIX_BASIC: - break; - case BFS_REGEX_POSIX_EXTENDED: - cflags |= REG_EXTENDED; - break; - default: - errno = EINVAL; - goto fail; - } - - if (flags & BFS_REGEX_ICASE) { - cflags |= REG_ICASE; - } - -#if BFS_HAS_FEATURE(memory_sanitizer, false) - // https://github.com/google/sanitizers/issues/1496 - memset(®ex->impl, 0, sizeof(regex->impl)); -#endif - - regex->err = regcomp(®ex->impl, pattern, cflags); - if (regex->err != 0) { - return -1; - } -#endif - - return 0; - -fail: - free(regex); - *preg = NULL; - return -1; -} - -int bfs_regexec(struct bfs_regex *regex, const char *str, enum bfs_regexec_flags flags) { - size_t len = strlen(str); - -#if BFS_WITH_ONIGURUMA - const unsigned char *ustr = (const unsigned char *)str; - const unsigned char *end = ustr + len; - - // The docs for onig_{match,search}() say - // - // Do not pass invalid byte string in the regex character encoding. - if (!onigenc_is_valid_mbc_string(onig_get_encoding(regex->impl), ustr, end)) { - return 0; - } - - int ret; - if (flags & BFS_REGEX_ANCHOR) { - ret = onig_match(regex->impl, ustr, end, ustr, NULL, ONIG_OPTION_DEFAULT); - } else { - ret = onig_search(regex->impl, ustr, end, ustr, end, NULL, ONIG_OPTION_DEFAULT); - } - - if (ret >= 0) { - if (flags & BFS_REGEX_ANCHOR) { - return (size_t)ret == len; - } else { - return 1; - } - } else if (ret == ONIG_MISMATCH) { - return 0; - } else { - regex->err = ret; - return -1; - } -#else - regmatch_t match = { - .rm_so = 0, - .rm_eo = len, - }; - - int eflags = 0; -#ifdef REG_STARTEND - eflags |= REG_STARTEND; -#endif - - int ret = regexec(®ex->impl, str, 1, &match, eflags); - if (ret == 0) { - if (flags & BFS_REGEX_ANCHOR) { - return match.rm_so == 0 && (size_t)match.rm_eo == len; - } else { - return 1; - } - } else if (ret == REG_NOMATCH) { - return 0; - } else { - regex->err = ret; - return -1; - } -#endif -} - -void bfs_regfree(struct bfs_regex *regex) { - if (regex) { -#if BFS_WITH_ONIGURUMA - onig_free(regex->impl); - free(regex->pattern); -#else - regfree(®ex->impl); -#endif - free(regex); - } -} - -char *bfs_regerror(const struct bfs_regex *regex) { - if (!regex) { - return strdup(strerror(ENOMEM)); - } - -#if BFS_WITH_ONIGURUMA - unsigned char *str = malloc(ONIG_MAX_ERROR_MESSAGE_LEN); - if (str) { - onig_error_code_to_str(str, regex->err, ®ex->einfo); - } - return (char *)str; -#else - size_t len = regerror(regex->err, ®ex->impl, NULL, 0); - char *str = malloc(len); - if (str) { - regerror(regex->err, ®ex->impl, str, len); - } - return str; -#endif -} diff --git a/regex.h b/regex.h deleted file mode 100644 index 63cd120..0000000 --- a/regex.h +++ /dev/null @@ -1,97 +0,0 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2022 Tavian Barnes and bfs * - * contributors * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ - -#ifndef BFS_REGEX_H -#define BFS_REGEX_H - -/** - * A compiled regular expression. - */ -struct bfs_regex; - -/** - * Regex syntax flavors. - */ -enum bfs_regex_type { - BFS_REGEX_POSIX_BASIC, - BFS_REGEX_POSIX_EXTENDED, - BFS_REGEX_EMACS, - BFS_REGEX_GREP, -}; - -/** - * Regex compilation flags. - */ -enum bfs_regcomp_flags { - /** Treat the regex case-insensitively. */ - BFS_REGEX_ICASE = 1 << 0, -}; - -/** - * Regex execution flags. - */ -enum bfs_regexec_flags { - /** Only treat matches of the entire string as successful. */ - BFS_REGEX_ANCHOR = 1 << 0, -}; - -/** - * Wrapper for regcomp() that supports additional regex types. - * - * @param[out] preg - * Will hold the compiled regex. - * @param pattern - * The regular expression to compile. - * @param type - * The regular expression syntax to use. - * @param flags - * Regex compilation flags. - * @return - * 0 on success, -1 on failure. - */ -int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_type type, enum bfs_regcomp_flags flags); - -/** - * Wrapper for regexec(). - * - * @param regex - * The regular expression to execute. - * @param str - * The string to match against. - * @param flags - * Regex execution flags. - * @return - * 1 for a match, 0 for no match, -1 on failure. - */ -int bfs_regexec(struct bfs_regex *regex, const char *str, enum bfs_regexec_flags flags); - -/** - * Free a compiled regex. - */ -void bfs_regfree(struct bfs_regex *regex); - -/** - * Get a human-readable regex error message. - * - * @param regex - * The compiled regex. - * @return - * A human-readable description of the error, which should be free()'d. - */ -char *bfs_regerror(const struct bfs_regex *regex); - -#endif // BFS_REGEX_H diff --git a/spawn.c b/spawn.c deleted file mode 100644 index 31f9897..0000000 --- a/spawn.c +++ /dev/null @@ -1,318 +0,0 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2018-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ - -#include "spawn.h" -#include "util.h" -#include -#include -#include -#include -#include -#include -#include - -/** - * Types of spawn actions. - */ -enum bfs_spawn_op { - BFS_SPAWN_CLOSE, - BFS_SPAWN_DUP2, - BFS_SPAWN_FCHDIR, - BFS_SPAWN_SETRLIMIT, -}; - -/** - * A spawn action. - */ -struct bfs_spawn_action { - struct bfs_spawn_action *next; - - enum bfs_spawn_op op; - int in_fd; - int out_fd; - int resource; - struct rlimit rlimit; -}; - -int bfs_spawn_init(struct bfs_spawn *ctx) { - ctx->flags = 0; - ctx->actions = NULL; - ctx->tail = &ctx->actions; - return 0; -} - -int bfs_spawn_destroy(struct bfs_spawn *ctx) { - struct bfs_spawn_action *action = ctx->actions; - while (action) { - struct bfs_spawn_action *next = action->next; - free(action); - action = next; - } - return 0; -} - -int bfs_spawn_setflags(struct bfs_spawn *ctx, enum bfs_spawn_flags flags) { - ctx->flags = flags; - return 0; -} - -/** Add a spawn action to the chain. */ -static struct bfs_spawn_action *bfs_spawn_add(struct bfs_spawn *ctx, enum bfs_spawn_op op) { - struct bfs_spawn_action *action = malloc(sizeof(*action)); - if (action) { - action->next = NULL; - action->op = op; - action->in_fd = -1; - action->out_fd = -1; - - *ctx->tail = action; - ctx->tail = &action->next; - } - return action; -} - -int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd) { - if (fd < 0) { - errno = EBADF; - return -1; - } - - struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_CLOSE); - if (action) { - action->out_fd = fd; - return 0; - } else { - return -1; - } -} - -int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) { - if (oldfd < 0 || newfd < 0) { - errno = EBADF; - return -1; - } - - struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_DUP2); - if (action) { - action->in_fd = oldfd; - action->out_fd = newfd; - return 0; - } else { - return -1; - } -} - -int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { - if (fd < 0) { - errno = EBADF; - return -1; - } - - struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_FCHDIR); - if (action) { - action->in_fd = fd; - return 0; - } else { - return -1; - } -} - -int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rlimit *rl) { - struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_SETRLIMIT); - if (action) { - action->resource = resource; - action->rlimit = *rl; - return 0; - } else { - return -1; - } -} - -/** Actually exec() the new process. */ -static void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { - int error; - const struct bfs_spawn_action *actions = ctx ? ctx->actions : NULL; - - xclose(pipefd[0]); - - for (const struct bfs_spawn_action *action = actions; action; action = action->next) { - // Move the error-reporting pipe out of the way if necessary... - if (action->out_fd == pipefd[1]) { - int fd = dup_cloexec(pipefd[1]); - if (fd < 0) { - goto fail; - } - xclose(pipefd[1]); - pipefd[1] = fd; - } - - // ... and pretend the pipe doesn't exist - if (action->in_fd == pipefd[1]) { - errno = EBADF; - goto fail; - } - - switch (action->op) { - case BFS_SPAWN_CLOSE: - if (close(action->out_fd) != 0) { - goto fail; - } - break; - case BFS_SPAWN_DUP2: - if (dup2(action->in_fd, action->out_fd) < 0) { - goto fail; - } - break; - case BFS_SPAWN_FCHDIR: - if (fchdir(action->in_fd) != 0) { - goto fail; - } - break; - case BFS_SPAWN_SETRLIMIT: - if (setrlimit(action->resource, &action->rlimit) != 0) { - goto fail; - } - break; - } - } - - execve(exe, argv, envp); - -fail: - error = errno; - - // In case of a write error, the parent will still see that we exited - // unsuccessfully, but won't know why - (void) xwrite(pipefd[1], &error, sizeof(error)); - - xclose(pipefd[1]); - _Exit(127); -} - -pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { - extern char **environ; - if (!envp) { - envp = environ; - } - - enum bfs_spawn_flags flags = ctx ? ctx->flags : 0; - char *resolved = NULL; - if (flags & BFS_SPAWN_USEPATH) { - exe = resolved = bfs_spawn_resolve(exe); - if (!resolved) { - return -1; - } - } - - // Use a pipe to report errors from the child - int pipefd[2]; - if (pipe_cloexec(pipefd) != 0) { - free(resolved); - return -1; - } - - pid_t pid = fork(); - if (pid < 0) { - close_quietly(pipefd[1]); - close_quietly(pipefd[0]); - free(resolved); - return -1; - } else if (pid == 0) { - // Child - bfs_spawn_exec(exe, ctx, argv, envp, pipefd); - } - - // Parent - xclose(pipefd[1]); - free(resolved); - - int error; - ssize_t nbytes = xread(pipefd[0], &error, sizeof(error)); - xclose(pipefd[0]); - if (nbytes == sizeof(error)) { - int wstatus; - waitpid(pid, &wstatus, 0); - errno = error; - return -1; - } - - return pid; -} - -char *bfs_spawn_resolve(const char *exe) { - if (strchr(exe, '/')) { - return strdup(exe); - } - - const char *path = getenv("PATH"); - - char *confpath = NULL; - if (!path) { - path = confpath = xconfstr(_CS_PATH); - if (!path) { - return NULL; - } - } - - size_t cap = 0; - char *ret = NULL; - while (true) { - const char *end = strchr(path, ':'); - size_t len = end ? (size_t)(end - path) : strlen(path); - - // POSIX 8.3: "A zero-length prefix is a legacy feature that - // indicates the current working directory." - if (len == 0) { - path = "."; - len = 1; - } - - size_t total = len + 1 + strlen(exe) + 1; - if (cap < total) { - char *grown = realloc(ret, total); - if (!grown) { - goto fail; - } - ret = grown; - cap = total; - } - - memcpy(ret, path, len); - if (ret[len - 1] != '/') { - ret[len++] = '/'; - } - strcpy(ret + len, exe); - - if (xfaccessat(AT_FDCWD, ret, X_OK) == 0) { - break; - } - - if (!end) { - errno = ENOENT; - goto fail; - } - - path = end + 1; - } - - free(confpath); - return ret; - -fail: - free(confpath); - free(ret); - return NULL; -} diff --git a/spawn.h b/spawn.h deleted file mode 100644 index 0dd9d61..0000000 --- a/spawn.h +++ /dev/null @@ -1,123 +0,0 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2018-2019 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ - -/** - * A process-spawning library inspired by posix_spawn(). - */ - -#ifndef BFS_SPAWN_H -#define BFS_SPAWN_H - -#include -#include - -/** - * bfs_spawn() flags. - */ -enum bfs_spawn_flags { - /** Use the PATH variable to resolve the executable (like execvp()). */ - BFS_SPAWN_USEPATH = 1 << 0, -}; - -/** - * bfs_spawn() attributes, controlling the context of the new process. - */ -struct bfs_spawn { - enum bfs_spawn_flags flags; - struct bfs_spawn_action *actions; - struct bfs_spawn_action **tail; -}; - -/** - * Create a new bfs_spawn() context. - * - * @return 0 on success, -1 on failure. - */ -int bfs_spawn_init(struct bfs_spawn *ctx); - -/** - * Destroy a bfs_spawn() context. - * - * @return 0 on success, -1 on failure. - */ -int bfs_spawn_destroy(struct bfs_spawn *ctx); - -/** - * Set the flags for a bfs_spawn() context. - * - * @return 0 on success, -1 on failure. - */ -int bfs_spawn_setflags(struct bfs_spawn *ctx, enum bfs_spawn_flags flags); - -/** - * Add a close() action to a bfs_spawn() context. - * - * @return 0 on success, -1 on failure. - */ -int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd); - -/** - * Add a dup2() action to a bfs_spawn() context. - * - * @return 0 on success, -1 on failure. - */ -int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd); - -/** - * Add an fchdir() action to a bfs_spawn() context. - * - * @return 0 on success, -1 on failure. - */ -int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd); - -/** - * Add a setrlimit() action to a bfs_spawn() context. - * - * @return 0 on success, -1 on failure. - */ -int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rlimit *rl); - -/** - * Spawn a new process. - * - * @param exe - * The executable to run. - * @param ctx - * The context for the new process. - * @param argv - * The arguments for the new process. - * @param envp - * The environment variables for the new process (NULL for the current - * environment). - * @return - * The PID of the new process, or -1 on error. - */ -pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp); - -/** - * Look up an executable in the current PATH, as BFS_SPAWN_USEPATH or execvp() - * would do. - * - * @param exe - * The name of the binary to execute. Bare names without a '/' will be - * searched on the provided PATH. - * @return - * The full path to the executable, which should be free()'d, or NULL on - * failure. - */ -char *bfs_spawn_resolve(const char *exe); - -#endif // BFS_SPAWN_H diff --git a/tests/xtimegm.c b/tests/xtimegm.c index 2788dee..f4e28ee 100644 --- a/tests/xtimegm.c +++ b/tests/xtimegm.c @@ -14,7 +14,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ -#include "../time.h" +#include "../xtime.h" #include #include #include diff --git a/time.c b/time.c deleted file mode 100644 index c7331b5..0000000 --- a/time.c +++ /dev/null @@ -1,323 +0,0 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ - -#include "time.h" -#include -#include -#include -#include -#include - -/** Whether tzset() has been called. */ -static bool tz_is_set = false; - -int xlocaltime(const time_t *timep, struct tm *result) { - // Should be called before localtime_r() according to POSIX.1-2004 - if (!tz_is_set) { - tzset(); - tz_is_set = true; - } - - if (localtime_r(timep, result)) { - return 0; - } else { - return -1; - } -} - -int xgmtime(const time_t *timep, struct tm *result) { - // Should be called before gmtime_r() according to POSIX.1-2004 - if (!tz_is_set) { - tzset(); - tz_is_set = true; - } - - if (gmtime_r(timep, result)) { - return 0; - } else { - return -1; - } -} - -int xmktime(struct tm *tm, time_t *timep) { - *timep = mktime(tm); - - if (*timep == -1) { - int error = errno; - - struct tm tmp; - if (xlocaltime(timep, &tmp) != 0) { - return -1; - } - - if (tm->tm_year != tmp.tm_year || tm->tm_yday != tmp.tm_yday - || tm->tm_hour != tmp.tm_hour || tm->tm_min != tmp.tm_min || tm->tm_sec != tmp.tm_sec) { - errno = error; - return -1; - } - } - - return 0; -} - -static int safe_add(int *value, int delta) { - if (*value >= 0) { - if (delta > INT_MAX - *value) { - return -1; - } - } else { - if (delta < INT_MIN - *value) { - return -1; - } - } - - *value += delta; - return 0; -} - -static int floor_div(int n, int d) { - int a = n < 0; - return (n + a)/d - a; -} - -static int wrap(int *value, int max, int *next) { - int carry = floor_div(*value, max); - *value -= carry * max; - return safe_add(next, carry); -} - -static int month_length(int year, int month) { - static const int month_lengths[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - int ret = month_lengths[month]; - if (month == 1 && year%4 == 0 && (year%100 != 0 || (year + 300)%400 == 0)) { - ++ret; - } - return ret; -} - -int xtimegm(struct tm *tm, time_t *timep) { - tm->tm_isdst = 0; - - if (wrap(&tm->tm_sec, 60, &tm->tm_min) != 0) { - goto overflow; - } - if (wrap(&tm->tm_min, 60, &tm->tm_hour) != 0) { - goto overflow; - } - if (wrap(&tm->tm_hour, 24, &tm->tm_mday) != 0) { - goto overflow; - } - - // In order to wrap the days of the month, we first need to know what - // month it is - if (wrap(&tm->tm_mon, 12, &tm->tm_year) != 0) { - goto overflow; - } - - if (tm->tm_mday < 1) { - do { - --tm->tm_mon; - if (wrap(&tm->tm_mon, 12, &tm->tm_year) != 0) { - goto overflow; - } - - tm->tm_mday += month_length(tm->tm_year, tm->tm_mon); - } while (tm->tm_mday < 1); - } else { - while (true) { - int days = month_length(tm->tm_year, tm->tm_mon); - if (tm->tm_mday <= days) { - break; - } - - tm->tm_mday -= days; - ++tm->tm_mon; - if (wrap(&tm->tm_mon, 12, &tm->tm_year) != 0) { - goto overflow; - } - } - } - - tm->tm_yday = 0; - for (int i = 0; i < tm->tm_mon; ++i) { - tm->tm_yday += month_length(tm->tm_year, i); - } - tm->tm_yday += tm->tm_mday - 1; - - int leap_days; - // Compute floor((year - 69)/4) - floor((year - 1)/100) + floor((year + 299)/400) without overflows - if (tm->tm_year >= 0) { - leap_days = floor_div(tm->tm_year - 69, 4) - floor_div(tm->tm_year - 1, 100) + floor_div(tm->tm_year - 101, 400) + 1; - } else { - leap_days = floor_div(tm->tm_year + 3, 4) - floor_div(tm->tm_year + 99, 100) + floor_div(tm->tm_year + 299, 400) - 17; - } - - long long epoch_days = 365LL*(tm->tm_year - 70) + leap_days + tm->tm_yday; - tm->tm_wday = (epoch_days + 4)%7; - if (tm->tm_wday < 0) { - tm->tm_wday += 7; - } - - long long epoch_time = tm->tm_sec + 60*(tm->tm_min + 60*(tm->tm_hour + 24*epoch_days)); - *timep = (time_t)epoch_time; - if ((long long)*timep != epoch_time) { - goto overflow; - } - return 0; - -overflow: - errno = EOVERFLOW; - return -1; -} - -/** Parse some digits from a timestamp. */ -static int parse_timestamp_part(const char **str, size_t n, int *result) { - char buf[n + 1]; - for (size_t i = 0; i < n; ++i, ++*str) { - char c = **str; - if (c < '0' || c > '9') { - return -1; - } - buf[i] = c; - } - buf[n] = '\0'; - - *result = atoi(buf); - return 0; -} - -int parse_timestamp(const char *str, struct timespec *result) { - struct tm tm = { - .tm_isdst = -1, - }; - - int tz_hour = 0; - int tz_min = 0; - bool tz_negative = false; - bool local = true; - - // YYYY - if (parse_timestamp_part(&str, 4, &tm.tm_year) != 0) { - goto invalid; - } - tm.tm_year -= 1900; - - // MM - if (*str == '-') { - ++str; - } - if (parse_timestamp_part(&str, 2, &tm.tm_mon) != 0) { - goto invalid; - } - tm.tm_mon -= 1; - - // DD - if (*str == '-') { - ++str; - } - if (parse_timestamp_part(&str, 2, &tm.tm_mday) != 0) { - goto invalid; - } - - if (!*str) { - goto end; - } else if (*str == 'T') { - ++str; - } - - // hh - if (parse_timestamp_part(&str, 2, &tm.tm_hour) != 0) { - goto invalid; - } - - // mm - if (!*str) { - goto end; - } else if (*str == ':') { - ++str; - } - if (parse_timestamp_part(&str, 2, &tm.tm_min) != 0) { - goto invalid; - } - - // ss - if (!*str) { - goto end; - } else if (*str == ':') { - ++str; - } - if (parse_timestamp_part(&str, 2, &tm.tm_sec) != 0) { - goto invalid; - } - - if (!*str) { - goto end; - } else if (*str == 'Z') { - local = false; - ++str; - } else if (*str == '+' || *str == '-') { - local = false; - tz_negative = *str == '-'; - ++str; - - // hh - if (parse_timestamp_part(&str, 2, &tz_hour) != 0) { - goto invalid; - } - - // mm - if (!*str) { - goto end; - } else if (*str == ':') { - ++str; - } - if (parse_timestamp_part(&str, 2, &tz_min) != 0) { - goto invalid; - } - } else { - goto invalid; - } - - if (*str) { - goto invalid; - } - -end: - if (local) { - if (xmktime(&tm, &result->tv_sec) != 0) { - goto error; - } - } else { - if (xtimegm(&tm, &result->tv_sec) != 0) { - goto error; - } - - int offset = 60*tz_hour + tz_min; - if (tz_negative) { - result->tv_sec -= offset; - } else { - result->tv_sec += offset; - } - } - - result->tv_nsec = 0; - return 0; - -invalid: - errno = EINVAL; -error: - return -1; -} diff --git a/time.h b/time.h deleted file mode 100644 index 0f9adb4..0000000 --- a/time.h +++ /dev/null @@ -1,86 +0,0 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ - -/** - * Date/time handling. - */ - -#ifndef BFS_TIME_H -#define BFS_TIME_H - -#include - -/** - * localtime_r() wrapper that calls tzset() first. - * - * @param[in] timep - * The time_t to convert. - * @param[out] result - * Buffer to hold the result. - * @return - * 0 on success, -1 on failure. - */ -int xlocaltime(const time_t *timep, struct tm *result); - -/** - * gmtime_r() wrapper that calls tzset() first. - * - * @param[in] timep - * The time_t to convert. - * @param[out] result - * Buffer to hold the result. - * @return - * 0 on success, -1 on failure. - */ -int xgmtime(const time_t *timep, struct tm *result); - -/** - * mktime() wrapper that reports errors more reliably. - * - * @param[in,out] tm - * The struct tm to convert. - * @param[out] timep - * A pointer to the result. - * @return - * 0 on success, -1 on failure. - */ -int xmktime(struct tm *tm, time_t *timep); - -/** - * A portable timegm(), the inverse of gmtime(). - * - * @param[in,out] tm - * The struct tm to convert. - * @param[out] timep - * A pointer to the result. - * @return - * 0 on success, -1 on failure. - */ -int xtimegm(struct tm *tm, time_t *timep); - -/** - * Parse an ISO 8601-style timestamp. - * - * @param[in] str - * The string to parse. - * @param[out] result - * A pointer to the result. - * @return - * 0 on success, -1 on failure. - */ -int parse_timestamp(const char *str, struct timespec *result); - -#endif // BFS_TIME_H diff --git a/util.c b/util.c index 21921e8..f67406c 100644 --- a/util.c +++ b/util.c @@ -16,7 +16,7 @@ #include "util.h" #include "dstring.h" -#include "regex.h" +#include "xregex.h" #include #include #include diff --git a/xregex.c b/xregex.c new file mode 100644 index 0000000..3c3cf35 --- /dev/null +++ b/xregex.c @@ -0,0 +1,301 @@ +/**************************************************************************** + * bfs * + * Copyright (C) 2022 Tavian Barnes * + * * + * Permission to use, copy, modify, and/or distribute this software for any * + * purpose with or without fee is hereby granted. * + * * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * + ****************************************************************************/ + +#include "xregex.h" +#include "util.h" +#include +#include +#include +#include + +#if BFS_WITH_ONIGURUMA +# include +# include +#else +# include +#endif + +struct bfs_regex { +#if BFS_WITH_ONIGURUMA + unsigned char *pattern; + OnigRegex impl; + int err; + OnigErrorInfo einfo; +#else + regex_t impl; + int err; +#endif +}; + +#if BFS_WITH_ONIGURUMA +/** Get (and initialize) the appropriate encoding for the current locale. */ +static int bfs_onig_encoding(OnigEncoding *penc) { + static OnigEncoding enc = NULL; + if (enc) { + *penc = enc; + return ONIG_NORMAL; + } + + // Fall back to ASCII by default + enc = ONIG_ENCODING_ASCII; + + // Oniguruma has no locale support, so try to guess the right encoding + // from the current locale. + const char *charmap = nl_langinfo(CODESET); + if (charmap) { +#define BFS_MAP_ENCODING(name, value) \ + do { \ + if (strcmp(charmap, name) == 0) { \ + enc = value; \ + } \ + } while (0) +#define BFS_MAP_ENCODING2(name1, name2, value) \ + do { \ + BFS_MAP_ENCODING(name1, value); \ + BFS_MAP_ENCODING(name2, value); \ + } while (0) + + // These names were found with locale -m on Linux and FreeBSD +#define BFS_MAP_ISO_8859(n) \ + BFS_MAP_ENCODING2("ISO-8859-" #n, "ISO8859-" #n, ONIG_ENCODING_ISO_8859_ ## n) + + BFS_MAP_ISO_8859(1); + BFS_MAP_ISO_8859(2); + BFS_MAP_ISO_8859(3); + BFS_MAP_ISO_8859(4); + BFS_MAP_ISO_8859(5); + BFS_MAP_ISO_8859(6); + BFS_MAP_ISO_8859(7); + BFS_MAP_ISO_8859(8); + BFS_MAP_ISO_8859(9); + BFS_MAP_ISO_8859(10); + BFS_MAP_ISO_8859(11); + // BFS_MAP_ISO_8859(12); + BFS_MAP_ISO_8859(13); + BFS_MAP_ISO_8859(14); + BFS_MAP_ISO_8859(15); + BFS_MAP_ISO_8859(16); + + BFS_MAP_ENCODING("UTF-8", ONIG_ENCODING_UTF8); + +#define BFS_MAP_EUC(name) \ + BFS_MAP_ENCODING2("EUC-" #name, "euc" #name, ONIG_ENCODING_EUC_ ## name) + + BFS_MAP_EUC(JP); + BFS_MAP_EUC(TW); + BFS_MAP_EUC(KR); + BFS_MAP_EUC(CN); + + BFS_MAP_ENCODING2("SHIFT_JIS", "SJIS", ONIG_ENCODING_SJIS); + + // BFS_MAP_ENCODING("KOI-8", ONIG_ENCODING_KOI8); + BFS_MAP_ENCODING("KOI8-R", ONIG_ENCODING_KOI8_R); + + BFS_MAP_ENCODING("CP1251", ONIG_ENCODING_CP1251); + + BFS_MAP_ENCODING("GB18030", ONIG_ENCODING_BIG5); + } + + int ret = onig_initialize(&enc, 1); + if (ret != ONIG_NORMAL) { + enc = NULL; + } + *penc = enc; + return ret; +} +#endif + +int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_type type, enum bfs_regcomp_flags flags) { + struct bfs_regex *regex = *preg = malloc(sizeof(*regex)); + if (!regex) { + return -1; + } + +#if BFS_WITH_ONIGURUMA + // onig_error_code_to_str() says + // + // don't call this after the pattern argument of onig_new() is freed + // + // so make a defensive copy. + regex->pattern = (unsigned char *)strdup(pattern); + if (!regex->pattern) { + goto fail; + } + + regex->impl = NULL; + regex->err = ONIG_NORMAL; + + OnigSyntaxType *syntax = NULL; + switch (type) { + case BFS_REGEX_POSIX_BASIC: + syntax = ONIG_SYNTAX_POSIX_BASIC; + break; + case BFS_REGEX_POSIX_EXTENDED: + syntax = ONIG_SYNTAX_POSIX_EXTENDED; + break; + case BFS_REGEX_EMACS: + syntax = ONIG_SYNTAX_EMACS; + break; + case BFS_REGEX_GREP: + syntax = ONIG_SYNTAX_GREP; + break; + } + assert(syntax); + + OnigOptionType options = syntax->options; + if (flags & BFS_REGEX_ICASE) { + options |= ONIG_OPTION_IGNORECASE; + } + + OnigEncoding enc; + regex->err = bfs_onig_encoding(&enc); + if (regex->err != ONIG_NORMAL) { + return -1; + } + + const unsigned char *end = regex->pattern + strlen(pattern); + regex->err = onig_new(®ex->impl, regex->pattern, end, options, enc, syntax, ®ex->einfo); + if (regex->err != ONIG_NORMAL) { + return -1; + } +#else + int cflags = 0; + switch (type) { + case BFS_REGEX_POSIX_BASIC: + break; + case BFS_REGEX_POSIX_EXTENDED: + cflags |= REG_EXTENDED; + break; + default: + errno = EINVAL; + goto fail; + } + + if (flags & BFS_REGEX_ICASE) { + cflags |= REG_ICASE; + } + +#if BFS_HAS_FEATURE(memory_sanitizer, false) + // https://github.com/google/sanitizers/issues/1496 + memset(®ex->impl, 0, sizeof(regex->impl)); +#endif + + regex->err = regcomp(®ex->impl, pattern, cflags); + if (regex->err != 0) { + return -1; + } +#endif + + return 0; + +fail: + free(regex); + *preg = NULL; + return -1; +} + +int bfs_regexec(struct bfs_regex *regex, const char *str, enum bfs_regexec_flags flags) { + size_t len = strlen(str); + +#if BFS_WITH_ONIGURUMA + const unsigned char *ustr = (const unsigned char *)str; + const unsigned char *end = ustr + len; + + // The docs for onig_{match,search}() say + // + // Do not pass invalid byte string in the regex character encoding. + if (!onigenc_is_valid_mbc_string(onig_get_encoding(regex->impl), ustr, end)) { + return 0; + } + + int ret; + if (flags & BFS_REGEX_ANCHOR) { + ret = onig_match(regex->impl, ustr, end, ustr, NULL, ONIG_OPTION_DEFAULT); + } else { + ret = onig_search(regex->impl, ustr, end, ustr, end, NULL, ONIG_OPTION_DEFAULT); + } + + if (ret >= 0) { + if (flags & BFS_REGEX_ANCHOR) { + return (size_t)ret == len; + } else { + return 1; + } + } else if (ret == ONIG_MISMATCH) { + return 0; + } else { + regex->err = ret; + return -1; + } +#else + regmatch_t match = { + .rm_so = 0, + .rm_eo = len, + }; + + int eflags = 0; +#ifdef REG_STARTEND + eflags |= REG_STARTEND; +#endif + + int ret = regexec(®ex->impl, str, 1, &match, eflags); + if (ret == 0) { + if (flags & BFS_REGEX_ANCHOR) { + return match.rm_so == 0 && (size_t)match.rm_eo == len; + } else { + return 1; + } + } else if (ret == REG_NOMATCH) { + return 0; + } else { + regex->err = ret; + return -1; + } +#endif +} + +void bfs_regfree(struct bfs_regex *regex) { + if (regex) { +#if BFS_WITH_ONIGURUMA + onig_free(regex->impl); + free(regex->pattern); +#else + regfree(®ex->impl); +#endif + free(regex); + } +} + +char *bfs_regerror(const struct bfs_regex *regex) { + if (!regex) { + return strdup(strerror(ENOMEM)); + } + +#if BFS_WITH_ONIGURUMA + unsigned char *str = malloc(ONIG_MAX_ERROR_MESSAGE_LEN); + if (str) { + onig_error_code_to_str(str, regex->err, ®ex->einfo); + } + return (char *)str; +#else + size_t len = regerror(regex->err, ®ex->impl, NULL, 0); + char *str = malloc(len); + if (str) { + regerror(regex->err, ®ex->impl, str, len); + } + return str; +#endif +} diff --git a/xregex.h b/xregex.h new file mode 100644 index 0000000..63cd120 --- /dev/null +++ b/xregex.h @@ -0,0 +1,97 @@ +/**************************************************************************** + * bfs * + * Copyright (C) 2022 Tavian Barnes and bfs * + * contributors * + * * + * Permission to use, copy, modify, and/or distribute this software for any * + * purpose with or without fee is hereby granted. * + * * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * + ****************************************************************************/ + +#ifndef BFS_REGEX_H +#define BFS_REGEX_H + +/** + * A compiled regular expression. + */ +struct bfs_regex; + +/** + * Regex syntax flavors. + */ +enum bfs_regex_type { + BFS_REGEX_POSIX_BASIC, + BFS_REGEX_POSIX_EXTENDED, + BFS_REGEX_EMACS, + BFS_REGEX_GREP, +}; + +/** + * Regex compilation flags. + */ +enum bfs_regcomp_flags { + /** Treat the regex case-insensitively. */ + BFS_REGEX_ICASE = 1 << 0, +}; + +/** + * Regex execution flags. + */ +enum bfs_regexec_flags { + /** Only treat matches of the entire string as successful. */ + BFS_REGEX_ANCHOR = 1 << 0, +}; + +/** + * Wrapper for regcomp() that supports additional regex types. + * + * @param[out] preg + * Will hold the compiled regex. + * @param pattern + * The regular expression to compile. + * @param type + * The regular expression syntax to use. + * @param flags + * Regex compilation flags. + * @return + * 0 on success, -1 on failure. + */ +int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_type type, enum bfs_regcomp_flags flags); + +/** + * Wrapper for regexec(). + * + * @param regex + * The regular expression to execute. + * @param str + * The string to match against. + * @param flags + * Regex execution flags. + * @return + * 1 for a match, 0 for no match, -1 on failure. + */ +int bfs_regexec(struct bfs_regex *regex, const char *str, enum bfs_regexec_flags flags); + +/** + * Free a compiled regex. + */ +void bfs_regfree(struct bfs_regex *regex); + +/** + * Get a human-readable regex error message. + * + * @param regex + * The compiled regex. + * @return + * A human-readable description of the error, which should be free()'d. + */ +char *bfs_regerror(const struct bfs_regex *regex); + +#endif // BFS_REGEX_H diff --git a/xspawn.c b/xspawn.c new file mode 100644 index 0000000..93c270a --- /dev/null +++ b/xspawn.c @@ -0,0 +1,318 @@ +/**************************************************************************** + * bfs * + * Copyright (C) 2018-2022 Tavian Barnes * + * * + * Permission to use, copy, modify, and/or distribute this software for any * + * purpose with or without fee is hereby granted. * + * * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * + ****************************************************************************/ + +#include "xspawn.h" +#include "util.h" +#include +#include +#include +#include +#include +#include +#include + +/** + * Types of spawn actions. + */ +enum bfs_spawn_op { + BFS_SPAWN_CLOSE, + BFS_SPAWN_DUP2, + BFS_SPAWN_FCHDIR, + BFS_SPAWN_SETRLIMIT, +}; + +/** + * A spawn action. + */ +struct bfs_spawn_action { + struct bfs_spawn_action *next; + + enum bfs_spawn_op op; + int in_fd; + int out_fd; + int resource; + struct rlimit rlimit; +}; + +int bfs_spawn_init(struct bfs_spawn *ctx) { + ctx->flags = 0; + ctx->actions = NULL; + ctx->tail = &ctx->actions; + return 0; +} + +int bfs_spawn_destroy(struct bfs_spawn *ctx) { + struct bfs_spawn_action *action = ctx->actions; + while (action) { + struct bfs_spawn_action *next = action->next; + free(action); + action = next; + } + return 0; +} + +int bfs_spawn_setflags(struct bfs_spawn *ctx, enum bfs_spawn_flags flags) { + ctx->flags = flags; + return 0; +} + +/** Add a spawn action to the chain. */ +static struct bfs_spawn_action *bfs_spawn_add(struct bfs_spawn *ctx, enum bfs_spawn_op op) { + struct bfs_spawn_action *action = malloc(sizeof(*action)); + if (action) { + action->next = NULL; + action->op = op; + action->in_fd = -1; + action->out_fd = -1; + + *ctx->tail = action; + ctx->tail = &action->next; + } + return action; +} + +int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd) { + if (fd < 0) { + errno = EBADF; + return -1; + } + + struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_CLOSE); + if (action) { + action->out_fd = fd; + return 0; + } else { + return -1; + } +} + +int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) { + if (oldfd < 0 || newfd < 0) { + errno = EBADF; + return -1; + } + + struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_DUP2); + if (action) { + action->in_fd = oldfd; + action->out_fd = newfd; + return 0; + } else { + return -1; + } +} + +int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { + if (fd < 0) { + errno = EBADF; + return -1; + } + + struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_FCHDIR); + if (action) { + action->in_fd = fd; + return 0; + } else { + return -1; + } +} + +int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rlimit *rl) { + struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_SETRLIMIT); + if (action) { + action->resource = resource; + action->rlimit = *rl; + return 0; + } else { + return -1; + } +} + +/** Actually exec() the new process. */ +static void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { + int error; + const struct bfs_spawn_action *actions = ctx ? ctx->actions : NULL; + + xclose(pipefd[0]); + + for (const struct bfs_spawn_action *action = actions; action; action = action->next) { + // Move the error-reporting pipe out of the way if necessary... + if (action->out_fd == pipefd[1]) { + int fd = dup_cloexec(pipefd[1]); + if (fd < 0) { + goto fail; + } + xclose(pipefd[1]); + pipefd[1] = fd; + } + + // ... and pretend the pipe doesn't exist + if (action->in_fd == pipefd[1]) { + errno = EBADF; + goto fail; + } + + switch (action->op) { + case BFS_SPAWN_CLOSE: + if (close(action->out_fd) != 0) { + goto fail; + } + break; + case BFS_SPAWN_DUP2: + if (dup2(action->in_fd, action->out_fd) < 0) { + goto fail; + } + break; + case BFS_SPAWN_FCHDIR: + if (fchdir(action->in_fd) != 0) { + goto fail; + } + break; + case BFS_SPAWN_SETRLIMIT: + if (setrlimit(action->resource, &action->rlimit) != 0) { + goto fail; + } + break; + } + } + + execve(exe, argv, envp); + +fail: + error = errno; + + // In case of a write error, the parent will still see that we exited + // unsuccessfully, but won't know why + (void) xwrite(pipefd[1], &error, sizeof(error)); + + xclose(pipefd[1]); + _Exit(127); +} + +pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { + extern char **environ; + if (!envp) { + envp = environ; + } + + enum bfs_spawn_flags flags = ctx ? ctx->flags : 0; + char *resolved = NULL; + if (flags & BFS_SPAWN_USEPATH) { + exe = resolved = bfs_spawn_resolve(exe); + if (!resolved) { + return -1; + } + } + + // Use a pipe to report errors from the child + int pipefd[2]; + if (pipe_cloexec(pipefd) != 0) { + free(resolved); + return -1; + } + + pid_t pid = fork(); + if (pid < 0) { + close_quietly(pipefd[1]); + close_quietly(pipefd[0]); + free(resolved); + return -1; + } else if (pid == 0) { + // Child + bfs_spawn_exec(exe, ctx, argv, envp, pipefd); + } + + // Parent + xclose(pipefd[1]); + free(resolved); + + int error; + ssize_t nbytes = xread(pipefd[0], &error, sizeof(error)); + xclose(pipefd[0]); + if (nbytes == sizeof(error)) { + int wstatus; + waitpid(pid, &wstatus, 0); + errno = error; + return -1; + } + + return pid; +} + +char *bfs_spawn_resolve(const char *exe) { + if (strchr(exe, '/')) { + return strdup(exe); + } + + const char *path = getenv("PATH"); + + char *confpath = NULL; + if (!path) { + path = confpath = xconfstr(_CS_PATH); + if (!path) { + return NULL; + } + } + + size_t cap = 0; + char *ret = NULL; + while (true) { + const char *end = strchr(path, ':'); + size_t len = end ? (size_t)(end - path) : strlen(path); + + // POSIX 8.3: "A zero-length prefix is a legacy feature that + // indicates the current working directory." + if (len == 0) { + path = "."; + len = 1; + } + + size_t total = len + 1 + strlen(exe) + 1; + if (cap < total) { + char *grown = realloc(ret, total); + if (!grown) { + goto fail; + } + ret = grown; + cap = total; + } + + memcpy(ret, path, len); + if (ret[len - 1] != '/') { + ret[len++] = '/'; + } + strcpy(ret + len, exe); + + if (xfaccessat(AT_FDCWD, ret, X_OK) == 0) { + break; + } + + if (!end) { + errno = ENOENT; + goto fail; + } + + path = end + 1; + } + + free(confpath); + return ret; + +fail: + free(confpath); + free(ret); + return NULL; +} diff --git a/xspawn.h b/xspawn.h new file mode 100644 index 0000000..0dd9d61 --- /dev/null +++ b/xspawn.h @@ -0,0 +1,123 @@ +/**************************************************************************** + * bfs * + * Copyright (C) 2018-2019 Tavian Barnes * + * * + * Permission to use, copy, modify, and/or distribute this software for any * + * purpose with or without fee is hereby granted. * + * * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * + ****************************************************************************/ + +/** + * A process-spawning library inspired by posix_spawn(). + */ + +#ifndef BFS_SPAWN_H +#define BFS_SPAWN_H + +#include +#include + +/** + * bfs_spawn() flags. + */ +enum bfs_spawn_flags { + /** Use the PATH variable to resolve the executable (like execvp()). */ + BFS_SPAWN_USEPATH = 1 << 0, +}; + +/** + * bfs_spawn() attributes, controlling the context of the new process. + */ +struct bfs_spawn { + enum bfs_spawn_flags flags; + struct bfs_spawn_action *actions; + struct bfs_spawn_action **tail; +}; + +/** + * Create a new bfs_spawn() context. + * + * @return 0 on success, -1 on failure. + */ +int bfs_spawn_init(struct bfs_spawn *ctx); + +/** + * Destroy a bfs_spawn() context. + * + * @return 0 on success, -1 on failure. + */ +int bfs_spawn_destroy(struct bfs_spawn *ctx); + +/** + * Set the flags for a bfs_spawn() context. + * + * @return 0 on success, -1 on failure. + */ +int bfs_spawn_setflags(struct bfs_spawn *ctx, enum bfs_spawn_flags flags); + +/** + * Add a close() action to a bfs_spawn() context. + * + * @return 0 on success, -1 on failure. + */ +int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd); + +/** + * Add a dup2() action to a bfs_spawn() context. + * + * @return 0 on success, -1 on failure. + */ +int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd); + +/** + * Add an fchdir() action to a bfs_spawn() context. + * + * @return 0 on success, -1 on failure. + */ +int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd); + +/** + * Add a setrlimit() action to a bfs_spawn() context. + * + * @return 0 on success, -1 on failure. + */ +int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rlimit *rl); + +/** + * Spawn a new process. + * + * @param exe + * The executable to run. + * @param ctx + * The context for the new process. + * @param argv + * The arguments for the new process. + * @param envp + * The environment variables for the new process (NULL for the current + * environment). + * @return + * The PID of the new process, or -1 on error. + */ +pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp); + +/** + * Look up an executable in the current PATH, as BFS_SPAWN_USEPATH or execvp() + * would do. + * + * @param exe + * The name of the binary to execute. Bare names without a '/' will be + * searched on the provided PATH. + * @return + * The full path to the executable, which should be free()'d, or NULL on + * failure. + */ +char *bfs_spawn_resolve(const char *exe); + +#endif // BFS_SPAWN_H diff --git a/xtime.c b/xtime.c new file mode 100644 index 0000000..b83bf20 --- /dev/null +++ b/xtime.c @@ -0,0 +1,323 @@ +/**************************************************************************** + * bfs * + * Copyright (C) 2020 Tavian Barnes * + * * + * Permission to use, copy, modify, and/or distribute this software for any * + * purpose with or without fee is hereby granted. * + * * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * + ****************************************************************************/ + +#include "xtime.h" +#include +#include +#include +#include +#include + +/** Whether tzset() has been called. */ +static bool tz_is_set = false; + +int xlocaltime(const time_t *timep, struct tm *result) { + // Should be called before localtime_r() according to POSIX.1-2004 + if (!tz_is_set) { + tzset(); + tz_is_set = true; + } + + if (localtime_r(timep, result)) { + return 0; + } else { + return -1; + } +} + +int xgmtime(const time_t *timep, struct tm *result) { + // Should be called before gmtime_r() according to POSIX.1-2004 + if (!tz_is_set) { + tzset(); + tz_is_set = true; + } + + if (gmtime_r(timep, result)) { + return 0; + } else { + return -1; + } +} + +int xmktime(struct tm *tm, time_t *timep) { + *timep = mktime(tm); + + if (*timep == -1) { + int error = errno; + + struct tm tmp; + if (xlocaltime(timep, &tmp) != 0) { + return -1; + } + + if (tm->tm_year != tmp.tm_year || tm->tm_yday != tmp.tm_yday + || tm->tm_hour != tmp.tm_hour || tm->tm_min != tmp.tm_min || tm->tm_sec != tmp.tm_sec) { + errno = error; + return -1; + } + } + + return 0; +} + +static int safe_add(int *value, int delta) { + if (*value >= 0) { + if (delta > INT_MAX - *value) { + return -1; + } + } else { + if (delta < INT_MIN - *value) { + return -1; + } + } + + *value += delta; + return 0; +} + +static int floor_div(int n, int d) { + int a = n < 0; + return (n + a)/d - a; +} + +static int wrap(int *value, int max, int *next) { + int carry = floor_div(*value, max); + *value -= carry * max; + return safe_add(next, carry); +} + +static int month_length(int year, int month) { + static const int month_lengths[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + int ret = month_lengths[month]; + if (month == 1 && year%4 == 0 && (year%100 != 0 || (year + 300)%400 == 0)) { + ++ret; + } + return ret; +} + +int xtimegm(struct tm *tm, time_t *timep) { + tm->tm_isdst = 0; + + if (wrap(&tm->tm_sec, 60, &tm->tm_min) != 0) { + goto overflow; + } + if (wrap(&tm->tm_min, 60, &tm->tm_hour) != 0) { + goto overflow; + } + if (wrap(&tm->tm_hour, 24, &tm->tm_mday) != 0) { + goto overflow; + } + + // In order to wrap the days of the month, we first need to know what + // month it is + if (wrap(&tm->tm_mon, 12, &tm->tm_year) != 0) { + goto overflow; + } + + if (tm->tm_mday < 1) { + do { + --tm->tm_mon; + if (wrap(&tm->tm_mon, 12, &tm->tm_year) != 0) { + goto overflow; + } + + tm->tm_mday += month_length(tm->tm_year, tm->tm_mon); + } while (tm->tm_mday < 1); + } else { + while (true) { + int days = month_length(tm->tm_year, tm->tm_mon); + if (tm->tm_mday <= days) { + break; + } + + tm->tm_mday -= days; + ++tm->tm_mon; + if (wrap(&tm->tm_mon, 12, &tm->tm_year) != 0) { + goto overflow; + } + } + } + + tm->tm_yday = 0; + for (int i = 0; i < tm->tm_mon; ++i) { + tm->tm_yday += month_length(tm->tm_year, i); + } + tm->tm_yday += tm->tm_mday - 1; + + int leap_days; + // Compute floor((year - 69)/4) - floor((year - 1)/100) + floor((year + 299)/400) without overflows + if (tm->tm_year >= 0) { + leap_days = floor_div(tm->tm_year - 69, 4) - floor_div(tm->tm_year - 1, 100) + floor_div(tm->tm_year - 101, 400) + 1; + } else { + leap_days = floor_div(tm->tm_year + 3, 4) - floor_div(tm->tm_year + 99, 100) + floor_div(tm->tm_year + 299, 400) - 17; + } + + long long epoch_days = 365LL*(tm->tm_year - 70) + leap_days + tm->tm_yday; + tm->tm_wday = (epoch_days + 4)%7; + if (tm->tm_wday < 0) { + tm->tm_wday += 7; + } + + long long epoch_time = tm->tm_sec + 60*(tm->tm_min + 60*(tm->tm_hour + 24*epoch_days)); + *timep = (time_t)epoch_time; + if ((long long)*timep != epoch_time) { + goto overflow; + } + return 0; + +overflow: + errno = EOVERFLOW; + return -1; +} + +/** Parse some digits from a timestamp. */ +static int parse_timestamp_part(const char **str, size_t n, int *result) { + char buf[n + 1]; + for (size_t i = 0; i < n; ++i, ++*str) { + char c = **str; + if (c < '0' || c > '9') { + return -1; + } + buf[i] = c; + } + buf[n] = '\0'; + + *result = atoi(buf); + return 0; +} + +int parse_timestamp(const char *str, struct timespec *result) { + struct tm tm = { + .tm_isdst = -1, + }; + + int tz_hour = 0; + int tz_min = 0; + bool tz_negative = false; + bool local = true; + + // YYYY + if (parse_timestamp_part(&str, 4, &tm.tm_year) != 0) { + goto invalid; + } + tm.tm_year -= 1900; + + // MM + if (*str == '-') { + ++str; + } + if (parse_timestamp_part(&str, 2, &tm.tm_mon) != 0) { + goto invalid; + } + tm.tm_mon -= 1; + + // DD + if (*str == '-') { + ++str; + } + if (parse_timestamp_part(&str, 2, &tm.tm_mday) != 0) { + goto invalid; + } + + if (!*str) { + goto end; + } else if (*str == 'T') { + ++str; + } + + // hh + if (parse_timestamp_part(&str, 2, &tm.tm_hour) != 0) { + goto invalid; + } + + // mm + if (!*str) { + goto end; + } else if (*str == ':') { + ++str; + } + if (parse_timestamp_part(&str, 2, &tm.tm_min) != 0) { + goto invalid; + } + + // ss + if (!*str) { + goto end; + } else if (*str == ':') { + ++str; + } + if (parse_timestamp_part(&str, 2, &tm.tm_sec) != 0) { + goto invalid; + } + + if (!*str) { + goto end; + } else if (*str == 'Z') { + local = false; + ++str; + } else if (*str == '+' || *str == '-') { + local = false; + tz_negative = *str == '-'; + ++str; + + // hh + if (parse_timestamp_part(&str, 2, &tz_hour) != 0) { + goto invalid; + } + + // mm + if (!*str) { + goto end; + } else if (*str == ':') { + ++str; + } + if (parse_timestamp_part(&str, 2, &tz_min) != 0) { + goto invalid; + } + } else { + goto invalid; + } + + if (*str) { + goto invalid; + } + +end: + if (local) { + if (xmktime(&tm, &result->tv_sec) != 0) { + goto error; + } + } else { + if (xtimegm(&tm, &result->tv_sec) != 0) { + goto error; + } + + int offset = 60*tz_hour + tz_min; + if (tz_negative) { + result->tv_sec -= offset; + } else { + result->tv_sec += offset; + } + } + + result->tv_nsec = 0; + return 0; + +invalid: + errno = EINVAL; +error: + return -1; +} diff --git a/xtime.h b/xtime.h new file mode 100644 index 0000000..0f9adb4 --- /dev/null +++ b/xtime.h @@ -0,0 +1,86 @@ +/**************************************************************************** + * bfs * + * Copyright (C) 2020 Tavian Barnes * + * * + * Permission to use, copy, modify, and/or distribute this software for any * + * purpose with or without fee is hereby granted. * + * * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * + ****************************************************************************/ + +/** + * Date/time handling. + */ + +#ifndef BFS_TIME_H +#define BFS_TIME_H + +#include + +/** + * localtime_r() wrapper that calls tzset() first. + * + * @param[in] timep + * The time_t to convert. + * @param[out] result + * Buffer to hold the result. + * @return + * 0 on success, -1 on failure. + */ +int xlocaltime(const time_t *timep, struct tm *result); + +/** + * gmtime_r() wrapper that calls tzset() first. + * + * @param[in] timep + * The time_t to convert. + * @param[out] result + * Buffer to hold the result. + * @return + * 0 on success, -1 on failure. + */ +int xgmtime(const time_t *timep, struct tm *result); + +/** + * mktime() wrapper that reports errors more reliably. + * + * @param[in,out] tm + * The struct tm to convert. + * @param[out] timep + * A pointer to the result. + * @return + * 0 on success, -1 on failure. + */ +int xmktime(struct tm *tm, time_t *timep); + +/** + * A portable timegm(), the inverse of gmtime(). + * + * @param[in,out] tm + * The struct tm to convert. + * @param[out] timep + * A pointer to the result. + * @return + * 0 on success, -1 on failure. + */ +int xtimegm(struct tm *tm, time_t *timep); + +/** + * Parse an ISO 8601-style timestamp. + * + * @param[in] str + * The string to parse. + * @param[out] result + * A pointer to the result. + * @return + * 0 on success, -1 on failure. + */ +int parse_timestamp(const char *str, struct timespec *result); + +#endif // BFS_TIME_H -- cgit v1.2.3