summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml15
-rw-r--r--.github/workflows/codecov.yml1
-rw-r--r--Makefile5
-rw-r--r--bench/ioq.c16
-rw-r--r--build/has/tcsetwinsize.c9
-rw-r--r--build/header.mk1
-rwxr-xr-xconfigure6
-rw-r--r--src/bar.c16
-rw-r--r--src/bfstd.c132
-rw-r--r--src/bfstd.h63
-rw-r--r--tests/bfstd.c176
-rw-r--r--tests/main.c8
-rw-r--r--tests/ptyx.c252
-rw-r--r--tests/run.sh10
-rw-r--r--tests/util.sh1
-rw-r--r--tests/xtouch.c4
16 files changed, 618 insertions, 97 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8e34313..e9c3f8e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -16,7 +16,6 @@ jobs:
sudo dpkg --add-architecture i386
sudo apt-get update -y
sudo apt-get install -y \
- expect \
mandoc \
gcc-multilib \
libgcc-s1:i386 \
@@ -57,7 +56,6 @@ jobs:
run: |
sudo apt-get update -y
sudo apt-get install -y \
- expect \
mandoc \
acl \
libacl1-dev \
@@ -110,10 +108,8 @@ jobs:
run: |
sudo pkg install -y \
bash \
- expect \
oniguruma \
- pkgconf \
- tcl-wrapper
+ pkgconf
sudo mount -t fdescfs none /dev/fd
.github/diag.sh make -j$(nproc) distcheck
@@ -139,7 +135,6 @@ jobs:
run: |
sudo pkg_add \
bash \
- expect \
gmake \
oniguruma
jobs=$(sysctl -n hw.ncpu)
@@ -170,8 +165,7 @@ jobs:
sudo pkgin -y install \
bash \
oniguruma \
- pkgconf \
- tcl-expect
+ pkgconf
jobs=$(sysctl -n hw.ncpu)
./configure
.github/diag.sh make -j$jobs check TEST_FLAGS="--sudo --verbose=skipped"
@@ -198,11 +192,9 @@ jobs:
prepare: |
pkg install -y \
bash \
- expect \
oniguruma \
pkgconf \
- sudo \
- tcl-wrapper
+ sudo
pw useradd -n action -m -G wheel -s /usr/local/bin/bash
echo "%wheel ALL=(ALL) NOPASSWD: ALL" >>/usr/local/etc/sudoers
@@ -235,7 +227,6 @@ jobs:
pkg install \
bash \
build-essential \
- expect \
gnu-make \
onig \
sudo
diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml
index 4cce8ed..e4e8f71 100644
--- a/.github/workflows/codecov.yml
+++ b/.github/workflows/codecov.yml
@@ -13,7 +13,6 @@ jobs:
run: |
sudo apt-get update -y
sudo apt-get install -y \
- expect \
gcc \
acl \
libacl1-dev \
diff --git a/Makefile b/Makefile
index 8a7b270..977974a 100644
--- a/Makefile
+++ b/Makefile
@@ -43,6 +43,7 @@ bfs: bin/bfs
BINS := \
bin/bfs \
bin/tests/mksock \
+ bin/tests/ptyx \
bin/tests/units \
bin/tests/xspawnee \
bin/tests/xtouch \
@@ -119,6 +120,7 @@ UTEST_BINS := \
# Integration test binaries
ITEST_BINS := \
bin/tests/mksock \
+ bin/tests/ptyx \
bin/tests/xtouch
# Build (but don't run) test binaries
@@ -179,6 +181,9 @@ integration-tests: ${INTEGRATION_TESTS}
bin/tests/mksock: obj/tests/mksock.o ${LIBBFS}
OBJS += obj/tests/mksock.o
+bin/tests/ptyx: obj/tests/ptyx.o ${LIBBFS}
+OBJS += obj/tests/ptyx.o
+
bin/tests/xtouch: obj/tests/xtouch.o ${LIBBFS}
OBJS += obj/tests/xtouch.o
diff --git a/bench/ioq.c b/bench/ioq.c
index 5db585a..61c9714 100644
--- a/bench/ioq.c
+++ b/bench/ioq.c
@@ -177,9 +177,9 @@ int main(int argc, char *argv[]) {
setlocale(LC_ALL, "");
// -d: queue depth
- long depth = 4096;
+ unsigned int depth = 4096;
// -j: threads
- long threads = 0;
+ unsigned int threads = 0;
// -t: timeout
double timeout = 5.0;
// -L|-H: ioq_nop() type
@@ -190,13 +190,13 @@ int main(int argc, char *argv[]) {
while (c = getopt(argc, argv, ":d:j:t:LH"), c != -1) {
switch (c) {
case 'd':
- if (xstrtol(optarg, NULL, 10, &depth) != 0) {
+ if (xstrtoui(optarg, NULL, 10, &depth) != 0) {
fprintf(stderr, "%s: Bad depth '%s': %s\n", cmd, optarg, errstr());
return EXIT_FAILURE;
}
break;
case 'j':
- if (xstrtol(optarg, NULL, 10, &threads) != 0) {
+ if (xstrtoui(optarg, NULL, 10, &threads) != 0) {
fprintf(stderr, "%s: Bad thread count '%s': %s\n", cmd, optarg, errstr());
return EXIT_FAILURE;
}
@@ -222,7 +222,7 @@ int main(int argc, char *argv[]) {
}
}
- if (threads <= 0) {
+ if (!threads) {
threads = nproc();
if (threads > 8) {
threads = 8;
@@ -238,8 +238,8 @@ int main(int argc, char *argv[]) {
printf("I/O queue benchmark (%s)\n\n", bfs_version);
- printf("[-d] depth: %ld\n", depth);
- printf("[-j] threads: %ld (including main)\n", threads + 1);
+ printf("[-d] depth: %u\n", depth);
+ printf("[-j] threads: %u (including main)\n", threads + 1);
if (type == IOQ_NOP_HEAVY) {
printf("[-H] type: heavy (with syscalls)\n");
} else {
@@ -252,7 +252,7 @@ int main(int argc, char *argv[]) {
fflush(stdout);
struct ioq *ioq = ioq_create(depth, threads);
- bfs_everify(ioq, "ioq_create(%ld, %ld)", depth, threads);
+ bfs_everify(ioq, "ioq_create(%u, %u)", depth, threads);
// Pre-allocate all the requests
while (ioq_capacity(ioq) > 0) {
diff --git a/build/has/tcsetwinsize.c b/build/has/tcsetwinsize.c
new file mode 100644
index 0000000..6717415
--- /dev/null
+++ b/build/has/tcsetwinsize.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <termios.h>
+
+int main(void) {
+ const struct winsize ws = {0};
+ return tcsetwinsize(0, &ws);
+}
diff --git a/build/header.mk b/build/header.mk
index 0e3af14..13672ba 100644
--- a/build/header.mk
+++ b/build/header.mk
@@ -56,6 +56,7 @@ HEADERS := \
gen/has/string-to-flags.h \
gen/has/strtofflags.h \
gen/has/tcgetwinsize.h \
+ gen/has/tcsetwinsize.h \
gen/has/timegm.h \
gen/has/timer-create.h \
gen/has/tm-gmtoff.h \
diff --git a/configure b/configure
index f6818c3..ab62aa6 100755
--- a/configure
+++ b/configure
@@ -87,7 +87,9 @@ invalid() {
# Get the number of cores to use
nproc() {
{
- command nproc \
+ # Run command nproc in a subshell to work around a bash 3 bug
+ # https://stackoverflow.com/q/68143965
+ (command nproc) \
|| sysctl -n hw.ncpu \
|| getconf _NPROCESSORS_ONLN \
|| echo 1
@@ -227,7 +229,7 @@ for arg; do
done
# Set up symbolic links for out-of-tree builds
-for f in Makefile build completions docs src tests; do
+for f in Makefile bench build completions docs src tests; do
test -e "$f" || ln -s "$DIR/$f" "$f"
done
diff --git a/src/bar.c b/src/bar.c
index 3258df0..1b0691a 100644
--- a/src/bar.c
+++ b/src/bar.c
@@ -18,7 +18,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
@@ -33,25 +32,14 @@ struct bfs_bar {
/** Get the terminal size, if possible. */
static int bfs_bar_getsize(struct bfs_bar *bar) {
-#if BFS_HAS_TCGETWINSIZE || defined(TIOCGWINSZ)
struct winsize ws;
-
-# if BFS_HAS_TCGETWINSIZE
- int ret = tcgetwinsize(bar->fd, &ws);
-# else
- int ret = ioctl(bar->fd, TIOCGWINSZ, &ws);
-# endif
- if (ret != 0) {
- return ret;
+ if (xtcgetwinsize(bar->fd, &ws) != 0) {
+ return -1;
}
store(&bar->width, ws.ws_col, relaxed);
store(&bar->height, ws.ws_row, relaxed);
return 0;
-#else
- errno = ENOTSUP;
- return -1;
-#endif
}
/** Write a string to the status bar (async-signal-safe). */
diff --git a/src/bfstd.c b/src/bfstd.c
index 82663eb..b78af7a 100644
--- a/src/bfstd.c
+++ b/src/bfstd.c
@@ -23,10 +23,12 @@
#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>
@@ -187,16 +189,6 @@ char *xgetdelim(FILE *file, char delim) {
}
}
-int open_cterm(int flags) {
- char path[L_ctermid];
- if (ctermid(path) == NULL || strlen(path) == 0) {
- errno = ENOTTY;
- return -1;
- }
-
- return open(path, flags);
-}
-
const char *xgetprogname(void) {
const char *cmd = NULL;
#if BFS_HAS_GETPROGNAME
@@ -243,6 +235,36 @@ static int xstrtox_epilogue(const char *str, char **end, char *endp) {
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;
@@ -283,6 +305,70 @@ int xstrtod(const char *str, char **end, double *value) {
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);
@@ -558,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);
diff --git a/src/bfstd.h b/src/bfstd.h
index 28f473e..15dd949 100644
--- a/src/bfstd.h
+++ b/src/bfstd.h
@@ -158,16 +158,6 @@ FILE *xfopen(const char *path, int flags);
*/
char *xgetdelim(FILE *file, char delim);
-/**
- * Open the controlling terminal.
- *
- * @flags
- * The open() flags.
- * @return
- * An open file descriptor, or -1 on failure.
- */
-int open_cterm(int flags);
-
// #include <stdlib.h>
/**
@@ -179,6 +169,16 @@ int open_cterm(int flags);
const char *xgetprogname(void);
/**
+ * Like xstrtol(), but for short.
+ */
+int xstrtos(const char *str, char **end, int base, short *value);
+
+/**
+ * Like xstrtol(), but for int.
+ */
+int xstrtoi(const char *str, char **end, int base, int *value);
+
+/**
* Wrapper for strtol() that forbids leading spaces.
*/
int xstrtol(const char *str, char **end, int base, long *value);
@@ -189,6 +189,26 @@ int xstrtol(const char *str, char **end, int base, long *value);
int xstrtoll(const char *str, char **end, int base, long long *value);
/**
+ * Like xstrtoul(), but for unsigned short.
+ */
+int xstrtous(const char *str, char **end, int base, unsigned short *value);
+
+/**
+ * Like xstrtoul(), but for unsigned int.
+ */
+int xstrtoui(const char *str, char **end, int base, unsigned int *value);
+
+/**
+ * Wrapper for strtoul() that forbids leading spaces, negatives.
+ */
+int xstrtoul(const char *str, char **end, int base, unsigned long *value);
+
+/**
+ * Wrapper for strtoull() that forbids leading spaces, negatives.
+ */
+int xstrtoull(const char *str, char **end, int base, unsigned long long *value);
+
+/**
* Wrapper for strtof() that forbids leading spaces.
*/
int xstrtof(const char *str, char **end, float *value);
@@ -342,6 +362,29 @@ int xminor(dev_t dev);
*/
pid_t xwaitpid(pid_t pid, int *status, int flags);
+#include <sys/ioctl.h> // May be necessary for struct winsize
+#include <termios.h>
+
+/**
+ * Open the controlling terminal.
+ *
+ * @flags
+ * The open() flags.
+ * @return
+ * An open file descriptor, or -1 on failure.
+ */
+int open_cterm(int flags);
+
+/**
+ * tcgetwinsize()/ioctl(TIOCGWINSZ) wrapper.
+ */
+int xtcgetwinsize(int fd, struct winsize *ws);
+
+/**
+ * tcsetwinsize()/ioctl(TIOCSWINSZ) wrapper.
+ */
+int xtcsetwinsize(int fd, const struct winsize *ws);
+
// #include <unistd.h>
/**
diff --git a/tests/bfstd.c b/tests/bfstd.c
index a43783a..6e15e2b 100644
--- a/tests/bfstd.c
+++ b/tests/bfstd.c
@@ -6,35 +6,15 @@
#include "bfstd.h"
#include "diag.h"
+#include <errno.h>
#include <langinfo.h>
+#include <limits.h>
+#include <stdint.h>
#include <stdlib.h>
#include <string.h>
-/** Check the result of xdirname()/xbasename(). */
-static void check_base_dir(const char *path, const char *dir, const char *base) {
- char *xdir = xdirname(path);
- bfs_everify(xdir, "xdirname()");
- bfs_check(strcmp(xdir, dir) == 0, "xdirname('%s') == '%s' (!= '%s')", path, xdir, dir);
- free(xdir);
-
- char *xbase = xbasename(path);
- bfs_everify(xbase, "xbasename()");
- bfs_check(strcmp(xbase, base) == 0, "xbasename('%s') == '%s' (!= '%s')", path, xbase, base);
- free(xbase);
-}
-
-/** Check the result of wordesc(). */
-static void check_wordesc(const char *str, const char *exp, enum wesc_flags flags) {
- char buf[256];
- char *end = buf + sizeof(buf);
- char *esc = wordesc(buf, end, str, flags);
-
- if (bfs_check(esc != end)) {
- bfs_check(strcmp(buf, exp) == 0, "wordesc('%s') == '%s' (!= '%s')", str, buf, exp);
- }
-}
-
-void check_bfstd(void) {
+/** asciilen() test cases. */
+static void check_asciilen(void) {
bfs_check(asciilen("") == 0);
bfs_check(asciilen("@") == 1);
bfs_check(asciilen("@@") == 2);
@@ -49,7 +29,23 @@ void check_bfstd(void) {
bfs_check(asciilen("@@@@@@@\xFF@@@@@@a\xFF@@@@@@@") == 7);
bfs_check(asciilen("@@@@@@@@\xFF@@@@@a\xFF@@@@@@@") == 8);
bfs_check(asciilen("@@@@@@@@@\xFF@@@@a\xFF@@@@@@@") == 9);
+}
+
+/** Check the result of xdirname()/xbasename(). */
+static void check_base_dir(const char *path, const char *dir, const char *base) {
+ char *xdir = xdirname(path);
+ bfs_everify(xdir, "xdirname()");
+ bfs_check(strcmp(xdir, dir) == 0, "xdirname('%s') == '%s' (!= '%s')", path, xdir, dir);
+ free(xdir);
+ char *xbase = xbasename(path);
+ bfs_everify(xbase, "xbasename()");
+ bfs_check(strcmp(xbase, base) == 0, "xbasename('%s') == '%s' (!= '%s')", path, xbase, base);
+ free(xbase);
+}
+
+/** xdirname()/xbasename() test cases. */
+static void check_basedirs(void) {
// From man 3p basename
check_base_dir("usr", ".", "usr");
check_base_dir("usr/", ".", "usr");
@@ -61,7 +57,21 @@ void check_bfstd(void) {
check_base_dir("/usr/lib", "/usr", "lib");
check_base_dir("//usr//lib//", "//usr", "lib");
check_base_dir("/home//dwc//test", "/home//dwc", "test");
+}
+
+/** Check the result of wordesc(). */
+static void check_wordesc(const char *str, const char *exp, enum wesc_flags flags) {
+ char buf[256];
+ char *end = buf + sizeof(buf);
+ char *esc = wordesc(buf, end, str, flags);
+ if (bfs_check(esc != end)) {
+ bfs_check(strcmp(buf, exp) == 0, "wordesc('%s') == '%s' (!= '%s')", str, buf, exp);
+ }
+}
+
+/** wordesc() test cases. */
+static void check_wordescs(void) {
check_wordesc("", "\"\"", WESC_SHELL);
check_wordesc("word", "word", WESC_SHELL);
check_wordesc("two words", "\"two words\"", WESC_SHELL);
@@ -80,7 +90,123 @@ void check_bfstd(void) {
check_wordesc("\xF0\x9F\x98\x80", "\xF0\x9F\x98\x80", WESC_SHELL | WESC_TTY);
check_wordesc("\xCB\x9Cuser", "\xCB\x9Cuser", WESC_SHELL);
}
+}
+
+/** xstrto*() test cases. */
+static void check_strtox(void) {
+ short s;
+ unsigned short us;
+ int i;
+ unsigned int ui;
+ long l;
+ unsigned long ul;
+ long long ll;
+ unsigned long long ull;
+ char *end;
+
+#define check_strtouerr(err, str, end, base) \
+ do { \
+ bfs_echeck(xstrtous(str, end, base, &us) != 0 && errno == err); \
+ bfs_echeck(xstrtoui(str, end, base, &ui) != 0 && errno == err); \
+ bfs_echeck(xstrtoul(str, end, base, &ul) != 0 && errno == err); \
+ bfs_echeck(xstrtoull(str, end, base, &ull) != 0 && errno == err); \
+ } while (0)
+
+ check_strtouerr(ERANGE, "-1", NULL, 0);
+ check_strtouerr(ERANGE, "-0x1", NULL, 0);
+ check_strtouerr(EINVAL, "-", NULL, 0);
+ check_strtouerr(EINVAL, "-q", NULL, 0);
+ check_strtouerr(EINVAL, "-1q", NULL, 0);
+ check_strtouerr(EINVAL, "-0x", NULL, 0);
+
+#define check_strtoerr(err, str, end, base) \
+ do { \
+ bfs_echeck(xstrtos(str, end, base, &s) != 0 && errno == err); \
+ bfs_echeck(xstrtoi(str, end, base, &i) != 0 && errno == err); \
+ bfs_echeck(xstrtol(str, end, base, &l) != 0 && errno == err); \
+ bfs_echeck(xstrtoll(str, end, base, &ll) != 0 && errno == err); \
+ check_strtouerr(err, str, end, base); \
+ } while (0)
+
+ check_strtoerr(EINVAL, "", NULL, 0);
+ check_strtoerr(EINVAL, "", &end, 0);
+ check_strtoerr(EINVAL, " 1 ", &end, 0);
+ check_strtoerr(EINVAL, " -1", NULL, 0);
+ check_strtoerr(EINVAL, " 123", NULL, 0);
+ check_strtoerr(EINVAL, "123 ", NULL, 0);
+ check_strtoerr(EINVAL, "0789", NULL, 0);
+ check_strtoerr(EINVAL, "789A", NULL, 0);
+ check_strtoerr(EINVAL, "0x", NULL, 0);
+ check_strtoerr(EINVAL, "0x789A", NULL, 10);
+ check_strtoerr(EINVAL, "0x-1", NULL, 0);
+
+#define check_strtotype(type, min, max, fmt, fn, str, base, v, n) \
+ do { \
+ if ((n) >= min && (n) <= max) { \
+ bfs_echeck(fn(str, NULL, base, &v) == 0); \
+ bfs_check(v == (type)(n), "%s('%s') == " fmt " (!= " fmt ")", #fn, str, v, (type)(n)); \
+ } else { \
+ bfs_echeck(fn(str, NULL, base, &v) != 0 && errno == ERANGE); \
+ } \
+ } while (0)
+
+#define check_strtoint(str, base, n) \
+ do { \
+ check_strtotype( signed short, SHRT_MIN, SHRT_MAX, "%d", xstrtos, str, base, s, n); \
+ check_strtotype( signed int, INT_MIN, INT_MAX, "%d", xstrtoi, str, base, i, n); \
+ check_strtotype( signed long, LONG_MIN, LONG_MAX, "%ld", xstrtol, str, base, l, n); \
+ check_strtotype( signed long long, LLONG_MIN, LLONG_MAX, "%lld", xstrtoll, str, base, ll, n); \
+ check_strtotype(unsigned short, 0, USHRT_MAX, "%u", xstrtous, str, base, us, n); \
+ check_strtotype(unsigned int, 0, UINT_MAX, "%u", xstrtoui, str, base, ui, n); \
+ check_strtotype(unsigned long, 0, ULONG_MAX, "%lu", xstrtoul, str, base, ul, n); \
+ check_strtotype(unsigned long long, 0, ULLONG_MAX, "%llu", xstrtoull, str, base, ull, n); \
+ } while (0)
+
+ check_strtoint("123", 0, 123);
+ check_strtoint("+123", 0, 123);
+ check_strtoint("-123", 0, -123);
+
+ check_strtoint("0123", 0, 0123);
+ check_strtoint("0x789A", 0, 0x789A);
+
+ check_strtoint("0123", 10, 123);
+ check_strtoint("0789", 10, 789);
+
+ check_strtoint("123", 16, 0x123);
+
+ check_strtoint("0x7FFF", 0, 0x7FFF);
+ check_strtoint("-0x8000", 0, -0x8000);
+
+ check_strtoint("0x7FFFFFFF", 0, 0x7FFFFFFFL);
+ check_strtoint("-0x80000000", 0, -0x7FFFFFFFL - 1);
+
+ check_strtoint("0x7FFFFFFFFFFFFFFF", 0, 0x7FFFFFFFFFFFFFFFLL);
+ check_strtoint("-0x8000000000000000", 0, -0x7FFFFFFFFFFFFFFFLL - 1);
+
+#define check_strtoend(str, estr, base, n) \
+ do { \
+ bfs_echeck(xstrtoll(str, &end, base, &ll) == 0); \
+ bfs_check(ll == (n), "xstrtoll('%s') == %lld (!= %lld)", str, ll, (long long)(n)); \
+ bfs_check(strcmp(end, estr) == 0, "xstrtoll('%s'): end == '%s' (!= '%s')", str, end, estr); \
+ } while (0)
+
+ check_strtoend("123 ", " ", 0, 123);
+ check_strtoend("0789", "89", 0, 07);
+ check_strtoend("789A", "A", 0, 789);
+ check_strtoend("0xDEFG", "G", 0, 0xDEF);
+}
+
+/** xstrwidth() test cases. */
+static void check_strwidth(void) {
bfs_check(xstrwidth("Hello world") == 11);
bfs_check(xstrwidth("Hello\1world") == 10);
}
+
+void check_bfstd(void) {
+ check_asciilen();
+ check_basedirs();
+ check_wordescs();
+ check_strtox();
+ check_strwidth();
+}
diff --git a/tests/main.c b/tests/main.c
index 4c770bd..9240e1c 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -222,15 +222,15 @@ int main(int argc, char *argv[]) {
}
tzset();
- long jobs = 0;
+ unsigned int jobs = 0;
const char *cmd = argc > 0 ? argv[0] : "units";
int c;
while (c = getopt(argc, argv, ":j:"), c != -1) {
switch (c) {
case 'j':
- if (xstrtol(optarg, NULL, 10, &jobs) != 0 || jobs <= 0) {
- fprintf(stderr, "%s: Bad job count '%s'\n", cmd, optarg);
+ if (xstrtoui(optarg, NULL, 10, &jobs) != 0) {
+ fprintf(stderr, "%s: Bad job count '%s': %s\n", cmd, optarg, errstr());
return EXIT_FAILURE;
}
break;
@@ -243,7 +243,7 @@ int main(int argc, char *argv[]) {
}
}
- if (jobs == 0) {
+ if (!jobs) {
jobs = nproc();
}
diff --git a/tests/ptyx.c b/tests/ptyx.c
new file mode 100644
index 0000000..59292df
--- /dev/null
+++ b/tests/ptyx.c
@@ -0,0 +1,252 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+/**
+ * Execute a command in a pseudo-terminal.
+ *
+ * $ ptyx [-w WIDTH] [-h HEIGHT] [--] COMMAND [ARGS...]
+ */
+
+#include "bfs.h"
+#include "bfstd.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+
+#if __has_include(<stropts.h>)
+# include <stropts.h>
+#endif
+
+#if __sun
+/**
+ * Push a STREAMS module, if it's not already there.
+ *
+ * See https://www.illumos.org/issues/9042.
+ */
+static int i_push(int fd, const char *name) {
+ int ret = ioctl(fd, I_FIND, name);
+ if (ret < 0) {
+ return ret;
+ } else if (ret == 0) {
+ return ioctl(fd, I_PUSH, name);
+ } else {
+ return 0;
+ }
+}
+#endif
+
+int main(int argc, char *argv[]) {
+ const char *cmd = argc > 0 ? argv[0] : "ptyx";
+
+/** Report an error message and exit. */
+#define die(...) die_(__VA_ARGS__, )
+
+#define die_(format, ...) \
+ do { \
+ fprintf(stderr, "%s: " format "%s", cmd, __VA_ARGS__ "\n"); \
+ exit(EXIT_FAILURE); \
+ } while (0)
+
+/** Report an error code and exit. */
+#define edie(...) edie_(__VA_ARGS__, )
+
+#define edie_(format, ...) \
+ do { \
+ fprintf(stderr, "%s: " format ": %s\n", cmd, __VA_ARGS__ errstr()); \
+ exit(EXIT_FAILURE); \
+ } while (0)
+
+ unsigned short width = 0;
+ unsigned short height = 0;
+
+ // Parse the command line
+ int c;
+ while (c = getopt(argc, argv, "+:w:h:"), c != -1) {
+ switch (c) {
+ case 'w':
+ if (xstrtous(optarg, NULL, 10, &width) != 0) {
+ edie("Bad width '%s'", optarg);
+ }
+ break;
+ case 'h':
+ if (xstrtous(optarg, NULL, 10, &height) != 0) {
+ edie("Bad height '%s'", optarg);
+ }
+ break;
+ case ':':
+ die("Missing argument to -%c", optopt);
+ case '?':
+ die("Unrecognized option -%c", optopt);
+ }
+ }
+
+ if (optind >= argc) {
+ die("Missing command");
+ }
+ char **args = argv + optind;
+
+ // Create a new pty, and set it up
+ int ptm = posix_openpt(O_RDWR | O_NOCTTY);
+ if (ptm < 0) {
+ edie("posix_openpt()");
+ }
+ if (grantpt(ptm) != 0) {
+ edie("grantpt()");
+ }
+ if (unlockpt(ptm) != 0) {
+ edie("unlockpt()");
+ }
+
+ // Get the subsidiary device path
+ char *name = ptsname(ptm);
+ if (!name) {
+ edie("ptsname()");
+ }
+
+ // Open the subsidiary device
+ int pts = open(name, O_RDWR | O_NOCTTY);
+ if (pts < 0) {
+ edie("%s", name);
+ }
+
+#if __sun
+ // On Solaris/illumos, a pty doesn't behave like a terminal until we
+ // push some STREAMS modules (see ptm(4D), ptem(4M), ldterm(4M)).
+ if (i_push(pts, "ptem") != 0) {
+ die("ioctl(I_PUSH, ptem)");
+ }
+ if (i_push(pts, "ldterm") != 0) {
+ die("ioctl(I_PUSH, ldterm)");
+ }
+#endif
+
+ // A new pty starts at 0x0, which is not very useful. Instead, grab the
+ // default size from the current controlling terminal, if possible.
+ if (!width || !height) {
+ int tty = open_cterm(O_RDONLY | O_CLOEXEC);
+ if (tty >= 0) {
+ struct winsize ws;
+ if (xtcgetwinsize(tty, &ws) != 0) {
+ edie("tcgetwinsize()");
+ }
+ if (!width) {
+ width = ws.ws_col;
+ }
+ if (!height) {
+ height = ws.ws_row;
+ }
+ xclose(tty);
+ }
+ }
+ if (!width) {
+ width = 80;
+ }
+ if (!height) {
+ height = 24;
+ }
+
+ // Update the pty size
+ struct winsize ws;
+ if (xtcgetwinsize(pts, &ws) != 0) {
+ edie("tcgetwinsize()");
+ }
+ ws.ws_col = width;
+ ws.ws_row = height;
+ if (xtcsetwinsize(pts, &ws) != 0) {
+ edie("tcsetwinsize()");
+ }
+
+ // Set custom terminal attributes
+ struct termios attrs;
+ if (tcgetattr(pts, &attrs) != 0) {
+ edie("tcgetattr()");
+ }
+ attrs.c_oflag &= ~OPOST; // Don't convert \n to \r\n
+ if (tcsetattr(pts, TCSANOW, &attrs) != 0) {
+ edie("tcsetattr()");
+ }
+
+ pid_t pid = fork();
+ if (pid < 0) {
+ edie("fork()");
+ } else if (pid == 0) {
+ // Child
+ close(ptm);
+
+ // Make ourselves a session leader so we can have our own
+ // controlling terminal
+ if (setsid() < 0) {
+ edie("setsid()");
+ }
+
+#ifdef TIOCSCTTY
+ // Set the pty as the controlling terminal
+ if (ioctl(pts, TIOCSCTTY, 0) != 0) {
+ edie("ioctl(TIOCSCTTY)");
+ }
+#endif
+
+ // Redirect std{in,out,err} to the pty
+ if (dup2(pts, STDIN_FILENO) < 0
+ || dup2(pts, STDOUT_FILENO) < 0
+ || dup2(pts, STDERR_FILENO) < 0) {
+ edie("dup2()");
+ }
+ if (pts > STDERR_FILENO) {
+ xclose(pts);
+ }
+
+ // Run the requested command
+ execvp(args[0], args);
+ edie("execvp(): %s", args[0]);
+ }
+
+ // Parent
+ xclose(pts);
+
+ // Read output from the pty and copy it to stdout
+ char buf[1024];
+ while (true) {
+ ssize_t len = read(ptm, buf, sizeof(buf));
+ if (len > 0) {
+ if (xwrite(STDOUT_FILENO, buf, len) < 0) {
+ edie("write()");
+ }
+ } else if (len == 0) {
+ break;
+ } else if (errno == EINTR) {
+ continue;
+ } else if (errno == EIO) {
+ // Linux reports EIO rather than EOF when pts is closed
+ break;
+ } else {
+ die("read()");
+ }
+ }
+
+ xclose(ptm);
+
+ int wstatus;
+ if (xwaitpid(pid, &wstatus, 0) < 0) {
+ edie("waitpid()");
+ }
+
+ if (WIFEXITED(wstatus)) {
+ return WEXITSTATUS(wstatus);
+ } else if (WIFSIGNALED(wstatus)) {
+ int sig = WTERMSIG(wstatus);
+ fprintf(stderr, "%s: %s: %s\n", cmd, args[0], strsignal(sig));
+ return 128 + sig;
+ } else {
+ return 128;
+ }
+}
diff --git a/tests/run.sh b/tests/run.sh
index e3a4e3f..8d3a5d2 100644
--- a/tests/run.sh
+++ b/tests/run.sh
@@ -362,20 +362,12 @@ invoke_bfs() {
fi
}
-if command -v unbuffer &>/dev/null; then
- UNBUFFER=unbuffer
-elif command -v expect_unbuffer &>/dev/null; then
- UNBUFFER=expect_unbuffer
-fi
-
# Run bfs with a pseudo-terminal attached
bfs_pty() {
- test -n "${UNBUFFER:-}" || skip
-
bfs_verbose "$@"
local ret=0
- "$UNBUFFER" bash -c 'stty cols 80 rows 24 && "$@" </dev/null' bash "${BFS[@]}" "$@" || ret=$?
+ "$PTYX" -w80 -h24 -- "${BFS[@]}" "$@" || ret=$?
if ((ret > 125)); then
exit $ret
diff --git a/tests/util.sh b/tests/util.sh
index d8b7036..b846d45 100644
--- a/tests/util.sh
+++ b/tests/util.sh
@@ -16,6 +16,7 @@ ROOT=$(_realpath "$(dirname -- "$TESTS")")
TESTS="$ROOT/tests"
BIN="$ROOT/bin"
MKSOCK="$BIN/tests/mksock"
+PTYX="$BIN/tests/ptyx"
XTOUCH="$BIN/tests/xtouch"
UNAME=$(uname)
diff --git a/tests/xtouch.c b/tests/xtouch.c
index 5d65a4c..f33c573 100644
--- a/tests/xtouch.c
+++ b/tests/xtouch.c
@@ -217,8 +217,8 @@ int main(int argc, char *argv[]) {
}
if (marg) {
- long mode;
- if (xstrtol(marg, NULL, 8, &mode) == 0 && mode >= 0 && mode < 01000) {
+ unsigned int mode;
+ if (xstrtoui(marg, NULL, 8, &mode) == 0 && mode < 01000) {
args.fmode = args.dmode = mode;
} else {
fprintf(stderr, "%s: Invalid mode '%s'\n", cmd, marg);