summaryrefslogtreecommitdiffstats
path: root/src/bfstd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bfstd.c')
-rw-r--r--src/bfstd.c483
1 files changed, 381 insertions, 102 deletions
diff --git a/src/bfstd.c b/src/bfstd.c
index 2499f00..b78af7a 100644
--- a/src/bfstd.c
+++ b/src/bfstd.c
@@ -2,12 +2,14 @@
// SPDX-License-Identifier: 0BSD
#include "bfstd.h"
+
+#include "bfs.h"
#include "bit.h"
-#include "config.h"
#include "diag.h"
#include "sanity.h"
#include "thread.h"
#include "xregex.h"
+
#include <errno.h>
#include <fcntl.h>
#include <langinfo.h>
@@ -15,25 +17,28 @@
#include <locale.h>
#include <nl_types.h>
#include <pthread.h>
+#include <sched.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/ioctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
+#include <termios.h>
#include <unistd.h>
#include <wchar.h>
-#if BFS_USE_SYS_SYSMACROS_H
+#if __has_include(<sys/sysmacros.h>)
# include <sys/sysmacros.h>
-#elif BFS_USE_SYS_MKDEV_H
+#elif __has_include(<sys/mkdev.h>)
# include <sys/mkdev.h>
#endif
-#if BFS_USE_UTIL_H
+#if __has_include(<util.h>)
# include <util.h>
#endif
@@ -186,10 +191,10 @@ char *xgetdelim(FILE *file, char delim) {
const char *xgetprogname(void) {
const char *cmd = NULL;
-#if __GLIBC__
- cmd = program_invocation_short_name;
-#elif BSD
+#if BFS_HAS_GETPROGNAME
cmd = getprogname();
+#elif BFS_HAS_GETPROGNAME_GNU
+ cmd = program_invocation_short_name;
#endif
if (!cmd) {
@@ -199,6 +204,171 @@ const char *xgetprogname(void) {
return cmd;
}
+/** Common prologue for xstrto*() wrappers. */
+static int xstrtox_prologue(const char *str) {
+ // strto*() skips leading spaces, but we want to reject them
+ if (xisspace(str[0])) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ errno = 0;
+ return 0;
+}
+
+/** Common epilogue for xstrto*() wrappers. */
+static int xstrtox_epilogue(const char *str, char **end, char *endp) {
+ if (errno != 0) {
+ return -1;
+ }
+
+ if (end) {
+ *end = endp;
+ }
+
+ // If end is NULL, make sure the entire string is valid
+ if (endp == str || (!end && *endp != '\0')) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+int xstrtos(const char *str, char **end, int base, short *value) {
+ long n;
+ if (xstrtol(str, end, base, &n) != 0) {
+ return -1;
+ }
+
+ if (n < SHRT_MIN || n > SHRT_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ *value = n;
+ return 0;
+}
+
+int xstrtoi(const char *str, char **end, int base, int *value) {
+ long n;
+ if (xstrtol(str, end, base, &n) != 0) {
+ return -1;
+ }
+
+ if (n < INT_MIN || n > INT_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ *value = n;
+ return 0;
+}
+
+int xstrtol(const char *str, char **end, int base, long *value) {
+ if (xstrtox_prologue(str) != 0) {
+ return -1;
+ }
+
+ char *endp;
+ *value = strtol(str, &endp, base);
+ return xstrtox_epilogue(str, end, endp);
+}
+
+int xstrtoll(const char *str, char **end, int base, long long *value) {
+ if (xstrtox_prologue(str) != 0) {
+ return -1;
+ }
+
+ char *endp;
+ *value = strtoll(str, &endp, base);
+ return xstrtox_epilogue(str, end, endp);
+}
+
+int xstrtof(const char *str, char **end, float *value) {
+ if (xstrtox_prologue(str) != 0) {
+ return -1;
+ }
+
+ char *endp;
+ *value = strtof(str, &endp);
+ return xstrtox_epilogue(str, end, endp);
+}
+
+int xstrtod(const char *str, char **end, double *value) {
+ if (xstrtox_prologue(str) != 0) {
+ return -1;
+ }
+
+ char *endp;
+ *value = strtod(str, &endp);
+ return xstrtox_epilogue(str, end, endp);
+}
+
+int xstrtous(const char *str, char **end, int base, unsigned short *value) {
+ unsigned long n;
+ if (xstrtoul(str, end, base, &n) != 0) {
+ return -1;
+ }
+
+ if (n > USHRT_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ *value = n;
+ return 0;
+}
+
+int xstrtoui(const char *str, char **end, int base, unsigned int *value) {
+ unsigned long n;
+ if (xstrtoul(str, end, base, &n) != 0) {
+ return -1;
+ }
+
+ if (n > UINT_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ *value = n;
+ return 0;
+}
+
+/** Common epilogue for xstrtou*() wrappers. */
+static int xstrtoux_epilogue(const char *str, char **end, char *endp) {
+ if (xstrtox_epilogue(str, end, endp) != 0) {
+ return -1;
+ }
+
+ if (str[0] == '-') {
+ errno = ERANGE;
+ return -1;
+ }
+
+ return 0;
+}
+
+int xstrtoul(const char *str, char **end, int base, unsigned long *value) {
+ if (xstrtox_prologue(str) != 0) {
+ return -1;
+ }
+
+ char *endp;
+ *value = strtoul(str, &endp, base);
+ return xstrtoux_epilogue(str, end, endp);
+}
+
+int xstrtoull(const char *str, char **end, int base, unsigned long long *value) {
+ if (xstrtox_prologue(str) != 0) {
+ return -1;
+ }
+
+ char *endp;
+ *value = strtoull(str, &endp, base);
+ return xstrtoux_epilogue(str, end, endp);
+}
+
/** Compile and execute a regular expression for xrpmatch(). */
static int xrpregex(nl_item item, const char *response) {
const char *pattern = nl_langinfo(item);
@@ -252,40 +422,6 @@ int ynprompt(void) {
return ret;
}
-/** Get the single character describing the given file type. */
-static char type_char(mode_t mode) {
- switch (mode & S_IFMT) {
- case S_IFREG:
- return '-';
- case S_IFBLK:
- return 'b';
- case S_IFCHR:
- return 'c';
- case S_IFDIR:
- return 'd';
- case S_IFLNK:
- return 'l';
- case S_IFIFO:
- return 'p';
- case S_IFSOCK:
- return 's';
-#ifdef S_IFDOOR
- case S_IFDOOR:
- return 'D';
-#endif
-#ifdef S_IFPORT
- case S_IFPORT:
- return 'P';
-#endif
-#ifdef S_IFWHT
- case S_IFWHT:
- return 'w';
-#endif
- }
-
- return '?';
-}
-
void *xmemdup(const void *src, size_t size) {
void *ret = malloc(size);
if (ret) {
@@ -317,35 +453,33 @@ const char *xstrerror(int errnum) {
const char *ret = NULL;
static thread_local char buf[256];
- // - __APPLE__
- // - __COSMOPOLITAN__
- // - No strerror_l()
- // - __FreeBSD__ && SANITIZE_MEMORY
- // - duplocale() triggers https://github.com/llvm/llvm-project/issues/65532
-#if __APPLE__ || __COSMOPOLITAN__ || (__FreeBSD__ && SANITIZE_MEMORY)
- if (strerror_r(errnum, buf, sizeof(buf)) == 0) {
- ret = buf;
- }
-#else
-# if __NetBSD__
- // NetBSD has no thread-specific locales
- locale_t loc = LC_GLOBAL_LOCALE;
-# else
+ // On FreeBSD with MemorySanitizer, duplocale() triggers
+ // https://github.com/llvm/llvm-project/issues/65532
+#if BFS_HAS_STRERROR_L && !(__FreeBSD__ && __SANITIZE_MEMORY__)
+# if BFS_HAS_USELOCALE
locale_t loc = uselocale((locale_t)0);
+# else
+ locale_t loc = LC_GLOBAL_LOCALE;
# endif
- locale_t copy = loc;
- if (copy == LC_GLOBAL_LOCALE) {
- copy = duplocale(copy);
+ bool free_loc = false;
+ if (loc == LC_GLOBAL_LOCALE) {
+ loc = duplocale(loc);
+ free_loc = true;
}
- if (copy != (locale_t)0) {
- ret = strerror_l(errnum, copy);
-
- if (loc == LC_GLOBAL_LOCALE) {
- freelocale(copy);
+ if (loc != (locale_t)0) {
+ ret = strerror_l(errnum, loc);
+ if (free_loc) {
+ freelocale(loc);
}
}
+#elif BFS_HAS_STRERROR_R_POSIX
+ if (strerror_r(errnum, buf, sizeof(buf)) == 0) {
+ ret = buf;
+ }
+#elif BFS_HAS_STRERROR_R_GNU
+ ret = strerror_r(errnum, buf, sizeof(buf));
#endif
if (!ret) {
@@ -358,6 +492,44 @@ const char *xstrerror(int errnum) {
return ret;
}
+const char *errstr(void) {
+ return xstrerror(errno);
+}
+
+/** Get the single character describing the given file type. */
+static char type_char(mode_t mode) {
+ switch (mode & S_IFMT) {
+ case S_IFREG:
+ return '-';
+ case S_IFBLK:
+ return 'b';
+ case S_IFCHR:
+ return 'c';
+ case S_IFDIR:
+ return 'd';
+ case S_IFLNK:
+ return 'l';
+ case S_IFIFO:
+ return 'p';
+ case S_IFSOCK:
+ return 's';
+#ifdef S_IFDOOR
+ case S_IFDOOR:
+ return 'D';
+#endif
+#ifdef S_IFPORT
+ case S_IFPORT:
+ return 'P';
+#endif
+#ifdef S_IFWHT
+ case S_IFWHT:
+ return 'w';
+#endif
+ }
+
+ return '?';
+}
+
void xstrmode(mode_t mode, char str[11]) {
strcpy(str, "----------");
@@ -439,7 +611,9 @@ int rlim_cmp(rlim_t a, rlim_t b) {
}
dev_t xmakedev(int ma, int mi) {
-#ifdef makedev
+#if __QNX__
+ return makedev(0, ma, mi);
+#elif defined(makedev)
return makedev(ma, mi);
#else
return (ma << 8) | mi;
@@ -470,6 +644,32 @@ pid_t xwaitpid(pid_t pid, int *status, int flags) {
return ret;
}
+int open_cterm(int flags) {
+ char path[L_ctermid];
+ if (ctermid(path) == NULL || strlen(path) == 0) {
+ errno = ENOTTY;
+ return -1;
+ }
+
+ return open(path, flags);
+}
+
+int xtcgetwinsize(int fd, struct winsize *ws) {
+#if BFS_HAS_TCGETWINSIZE
+ return tcgetwinsize(fd, ws);
+#else
+ return ioctl(fd, TIOCGWINSZ, ws);
+#endif
+}
+
+int xtcsetwinsize(int fd, const struct winsize *ws) {
+#if BFS_HAS_TCSETWINSIZE
+ return tcsetwinsize(fd, ws);
+#else
+ return ioctl(fd, TIOCSWINSZ, ws);
+#endif
+}
+
int dup_cloexec(int fd) {
#ifdef F_DUPFD_CLOEXEC
return fcntl(fd, F_DUPFD_CLOEXEC, 0);
@@ -489,7 +689,7 @@ int dup_cloexec(int fd) {
}
int pipe_cloexec(int pipefd[2]) {
-#if __linux__ || (BSD && !__APPLE__)
+#if BFS_HAS_PIPE2
return pipe2(pipefd, O_CLOEXEC);
#else
if (pipe(pipefd) != 0) {
@@ -581,10 +781,7 @@ int xfaccessat(int fd, const char *path, int amode) {
}
char *xconfstr(int name) {
-#if __ANDROID__
- errno = ENOTSUP;
- return NULL;
-#else
+#if BFS_HAS_CONFSTR
size_t len = confstr(name, NULL, 0);
if (len == 0) {
return NULL;
@@ -601,7 +798,10 @@ char *xconfstr(int name) {
}
return str;
-#endif // !__ANDROID__
+#else
+ errno = ENOTSUP;
+ return NULL;
+#endif
}
char *xreadlinkat(int fd, const char *path, size_t size) {
@@ -639,8 +839,14 @@ error:
return NULL;
}
+#if BFS_HAS_STRTOFFLAGS
+# define BFS_STRTOFFLAGS strtofflags
+#elif BFS_HAS_STRING_TO_FLAGS
+# define BFS_STRTOFFLAGS string_to_flags
+#endif
+
int xstrtofflags(const char **str, unsigned long long *set, unsigned long long *clear) {
-#if BSD && !__GNU__
+#ifdef BFS_STRTOFFLAGS
char *str_arg = (char *)*str;
#if __OpenBSD__
@@ -651,11 +857,7 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long *
bfs_fflags_t set_arg = 0;
bfs_fflags_t clear_arg = 0;
-#if __NetBSD__
- int ret = string_to_flags(&str_arg, &set_arg, &clear_arg);
-#else
- int ret = strtofflags(&str_arg, &set_arg, &clear_arg);
-#endif
+ int ret = BFS_STRTOFFLAGS(&str_arg, &set_arg, &clear_arg);
*str = str_arg;
*set = set_arg;
@@ -665,47 +867,124 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long *
errno = EINVAL;
}
return ret;
-#else // !BSD
+#else // !BFS_STRTOFFLAGS
errno = ENOTSUP;
return -1;
#endif
}
-size_t asciilen(const char *str) {
- return asciinlen(str, strlen(str));
-}
+long xsysconf(int name) {
+#if __FreeBSD__ && __SANITIZE_MEMORY__
+ // Work around https://github.com/llvm/llvm-project/issues/88163
+ __msan_scoped_disable_interceptor_checks();
+#endif
-size_t asciinlen(const char *str, size_t n) {
- size_t i = 0;
+ long ret = sysconf(name);
-#if SIZE_WIDTH % 8 == 0
- // Word-at-a-time isascii()
- for (size_t word; i + sizeof(word) <= n; i += sizeof(word)) {
- memcpy(&word, str + i, sizeof(word));
+#if __FreeBSD__ && __SANITIZE_MEMORY__
+ __msan_scoped_enable_interceptor_checks();
+#endif
- const size_t mask = (SIZE_MAX / 0xFF) << 7; // 0x808080...
- word &= mask;
- if (!word) {
- continue;
+ return ret;
+}
+
+#if BFS_HAS_SCHED_GETAFFINITY
+/** Get the CPU count in an affinity mask of the given size. */
+static long bfs_sched_getaffinity(size_t size) {
+ cpu_set_t set, *pset = &set;
+
+ if (size > sizeof(set)) {
+ pset = malloc(size);
+ if (!pset) {
+ return -1;
}
+ }
+
+ long ret = -1;
+ if (sched_getaffinity(0, size, pset) == 0) {
+# ifdef CPU_COUNT_S
+ ret = CPU_COUNT_S(size, pset);
+# else
+ bfs_assert(size <= sizeof(set));
+ ret = CPU_COUNT(pset);
+# endif
+ }
+
+ if (pset != &set) {
+ free(pset);
+ }
+ return ret;
+}
+#endif
+
+long nproc(void) {
+ long ret = 0;
-#if ENDIAN_NATIVE == ENDIAN_BIG
- word = bswap(word);
-#elif ENDIAN_NATIVE != ENDIAN_LITTLE
+#if BFS_HAS_SCHED_GETAFFINITY
+ size_t size = sizeof(cpu_set_t);
+ do {
+ ret = bfs_sched_getaffinity(size);
+
+# ifdef CPU_COUNT_S
+ // On Linux, sched_getaffinity(2) says:
+ //
+ // When working on systems with large kernel CPU affinity masks, one must
+ // dynamically allocate the mask argument (see CPU_ALLOC(3)). Currently,
+ // the only way to do this is by probing for the size of the required mask
+ // using sched_getaffinity() calls with increasing mask sizes (until the
+ // call does not fail with the error EINVAL).
+ size *= 2;
+# else
+ // No support for dynamically-sized CPU masks
break;
+# endif
+ } while (ret < 0 && errno == EINVAL);
#endif
- size_t first = trailing_zeros(word) / 8;
- return i + first;
+ if (ret < 1) {
+ ret = xsysconf(_SC_NPROCESSORS_ONLN);
}
-#endif
- for (; i < n; ++i) {
- if (!xisascii(str[i])) {
- break;
- }
+ if (ret < 1) {
+ ret = 1;
}
+ return ret;
+}
+
+size_t asciilen(const char *str) {
+ return asciinlen(str, strlen(str));
+}
+
+size_t asciinlen(const char *str, size_t n) {
+ const unsigned char *ustr = (const unsigned char *)str;
+ size_t i = 0;
+
+ // Word-at-a-time isascii()
+#define CHUNK(n) CHUNK_(uint##n##_t, load8_leu##n)
+#define CHUNK_(type, load8) \
+ (n - i >= sizeof(type)) { \
+ type word = load8(ustr + i); \
+ type mask = (((type)-1) / 0xFF) << 7; /* 0x808080.. */ \
+ word &= mask; \
+ i += trailing_zeros(word) / 8; \
+ if (word) { \
+ return i; \
+ } \
+ }
+
+#if SIZE_WIDTH >= 64
+ while CHUNK(64);
+ if CHUNK(32);
+#else
+ while CHUNK(32);
+#endif
+ if CHUNK(16);
+ if CHUNK(8);
+
+#undef CHUNK_
+#undef CHUNK
+
return i;
}
@@ -907,14 +1186,14 @@ static char *dollar_quote(char *dest, char *end, const char *str, size_t len, en
/** How much of this string is safe as a bare word? */
static size_t bare_len(const char *str, size_t len) {
- // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02
+ // https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_02
size_t ret = strcspn(str, "|&;<>()$`\\\"' *?[#~=%!{}");
return ret < len ? ret : len;
}
/** How much of this string is safe to double-quote? */
static size_t quotable_len(const char *str, size_t len) {
- // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_03
+ // https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_02_03
size_t ret = strcspn(str, "`$\\\"!");
return ret < len ? ret : len;
}