~nabijaczleweli/voreutils

60550bd6e1076b41b17ea6609a6d57b42bcc06fb — наб 19 days ago d3e7cdb
Add test
M Makefile => Makefile +2 -2
@@ 132,10 132,10 @@ $(OBJDIR)man/% : man/%
# https://bugs.debian.org/901636#52
$(MANDIR)man1/% $(MANDIR)man3/% $(MANDIR)man8/% : $(OBJDIR)man/%
	@mkdir -p $(dir $@)
	$(AWK) 'BEGIN { ints=0; tsc=0 }  /^\.TS/,/^\.TE/ { if(!ints) { ints=1; ++tsc; print "_VO_TS" tsc } print > ("$<-TS" tsc); next }  { ints=0; print }' $< | \
	$(AWK) 'BEGIN { ints=0; tsc=0 }  /^\.(TS|EQ)/,/^\.(TE|EN)/ { if(!ints) { ints=1; ++tsc; print "_VO_TS" tsc } print > ("$<-TS" tsc); next }  { ints=0; print }' $< | \
		$(AWK) 'BEGIN { inds=0; dsc=0 }  /^\.[da]s/,!/^\.[da]s/ { if(!inds) { inds=1; ++dsc; print "_VO_DS" dsc } if($$0 ~ /^\.[da]s/) { print > ("$<-DS" dsc); next } }  { inds=0; print }' | \
		$(MANDOC) -I os="voreutils $(VOREUTILS_VERSION)" -Tman | \
		$(AWK) '/Automatically generated from an mdoc input file\./ { next }  /^_VO_[TD]S/ {ff = substr($$0, 5); while(getline < ("$<-" ff)) print; next}  { print }' > $@
		$(AWK) '/Automatically generated from an mdoc input file\./ { next }  NR == 2 && $$2 == "e" { print "\x27\\\" e"; next }  /^_VO_[TD]S/ {ff = substr($$0, 5); while(getline < ("$<-" ff)) print; next}  { print }' > $@

$(HTMLMANDIR)man1/%.html $(HTMLMANDIR)man3/%.html $(HTMLMANDIR)man8/%.html : $(OBJDIR)man/%
	@mkdir -p $(dir $@)

M README.md => README.md +17 -7
@@ 32,7 32,7 @@ GNU coreutils provide the following 105 binaries, according to `dpkg -L coreutil
  * ☑ /bin/true
  * ☑ /bin/uname
  * ☐ /bin/vdir
  * ☐ /usr/bin/[
  * ☑ /usr/bin/[ — no V7-style `-l string -eq ...`
  * ☑ /usr/bin/arch
  * ☑ /usr/bin/b2sum
  * ☑ /usr/bin/base32


@@ 42,7 42,7 @@ GNU coreutils provide the following 105 binaries, according to `dpkg -L coreutil
  * ☑ /usr/bin/cksum
  * ☐ /usr/bin/comm
  * ☐ /usr/bin/csplit
  * ☑ /usr/bin/cut – [#993258: -d only accepts a single byte, must accept character](https://bugs.debian.org/993258), [#992667: -c doesn't seem to actually take characters?](https://bugs.debian.org/992667), [#992666: -n ignored => mangles output](https://bugs.debian.org/992666)
  * ☑ /usr/bin/cut – [#993258: -d only accepts a single byte, must accept character](//bugs.debian.org/993258), [#992667: -c doesn't seem to actually take characters?](//bugs.debian.org/992667), [#992666: -n ignored => mangles output](//bugs.debian.org/992666)
  * ☐ /usr/bin/dircolors
  * ☑ /usr/bin/dirname
  * ☐ /usr/bin/du


@@ 92,14 92,14 @@ GNU coreutils provide the following 105 binaries, according to `dpkg -L coreutil
  * ☑ /usr/bin/sum
  * ☑ /usr/bin/tac
  * ☐ /usr/bin/tail
  * ☑ /usr/bin/tee – `--output-error` is [multiple levels of wrong](https://bugs.debian.org/991887)
  * ☐ /usr/bin/test
  * ☑ /usr/bin/tee – `--output-error` is [multiple levels of wrong](//bugs.debian.org/991887)
  * ☑ /usr/bin/test — see `[`
  * ☑ /usr/bin/timeout
  * ☑ /usr/bin/tr – implements -C as -c and `[=e=]` as `e`: this matches 4.4BSD and GNU tr, but is nevertheless a missing POSIX feature; could also stand to do buffering lower than fgetc/fputc, as ~~I imagine the overhead of calling those for each byte is noninsignificant~~ locked I/O 33MB/s, unlocked I/O 46.6, GNU tr 180-200
  * ☑ /usr/bin/truncate
  * ☑ /usr/bin/tsort – GNU tsort is [turbofucked](https://bugs.debian.org/990854), and returns 1 for loops
  * ☑ /usr/bin/tsort – GNU tsort is [turbofucked](//bugs.debian.org/990854), and returns 1 for loops
  * ☑ /usr/bin/tty
  * ☑ /usr/bin/unexpand – flag handling is mildly (very?) different, and matches NetBSD; the tests are (should be) representative and all pass, but i've had [the worst time of my life](https://twitter.com/nabijaczleweli/status/1403145708543295493) writing it, so it's entirely possible (if unlikely) it's not 100% bug-compatible
  * ☑ /usr/bin/unexpand – flag handling is mildly (very?) different, and matches NetBSD; the tests are (should be) representative and all pass, but i've had [the worst time of my life](//twitter.com/nabijaczleweli/status/1403145708543295493) writing it, so it's entirely possible (if unlikely) it's not 100% bug-compatible
  * ☐ /usr/bin/uniq
  * ☑ /usr/bin/unlink
  * ☐ /usr/bin/users


@@ 146,9 146,19 @@ A `.note.version` section is included in every output file, and includes the ful
Unlocked stdio used by default, toggle comment in `include/vore-stdio` to disable (for testing or otherwise).
TODO: temporarily permanently disabled for testing; enable later.

Enable in-line eqn(1) with
```roff
.EQ
delim %%
.EN
```
(or whichever delimiter is best) after `.Sh DESCRIPTION` and disable it at the end.
If typesetting something that doesn't work in nroff mode (like the big equations in `base64.1`) provide an `.if n`/`.el` alternative in `.Fn`-like syntax;
otherwise (like the polynomial in `cksum.1`) enable eqn(1) preprocessing in man(1) by starting with `'\" e`.

## Compatibility
Free UNIXes, hopefully.
Debian, OpenBSD, and FreeBSD are on CI, as normal, bare, and [fucked](https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=256486) baselines, respectively.
Debian, OpenBSD, and FreeBSD are on CI, as normal, bare, and [fucked](//bugs.freebsd.org/bugzilla/show_bug.cgi?id=256486) baselines, respectively.
I also test on NetBSD (and TODO: some Illumos distro) before release.

## Contributing

M cmd/aliases => cmd/aliases +1 -0
@@ 7,3 7,4 @@ sha1sum  sha224sum sha256sum sha384sum  sha512sum  b2sum  md5sum  md5sum.textuti
cksum    sum
mkfifo   mknod
basename dirname
test     [

M cmd/basename.cpp => cmd/basename.cpp +1 -2
@@ 59,8 59,7 @@ int main(int argc, char * const * argv) {
				return std::fprintf(stderr, USAGE_BASENAME("basename")), 1;

			*single_path = *(argv + optind);
			if(*(argv + optind + 1))
				suffix = *(argv + optind + 1);
			suffix       = *(argv + optind + 1) ?: ""sv;
		}
	} else if(self == "dirname") {
		op = op_t::dirname;

M cmd/expr.cpp => cmd/expr.cpp +1 -1
@@ 3,6 3,7 @@

#include <algorithm>
#include <array>
#include <cctype>
#include <cinttypes>
#include <clocale>
#include <cstring>


@@ 14,7 15,6 @@
#include <variant>
#include <vore-dupa>
#include <vore-numeric>
#include <vore-optarg>
#include <vore-print>
#include <vore-span>
#include <vore-stdio>

M cmd/id.cpp => cmd/id.cpp +7 -14
@@ 70,10 70,8 @@ int main(int argc, char * const * argv) {
					[[fallthrough]];
				case 'Z': {
					auto variant = static_cast<single_output_t>(static_cast<std::uint8_t>(single_output_t::context) - var);
					if(single_output && *single_output != variant) {
						std::fprintf(stderr, "%s: -%c excludes -%c\n", argv[0], "ugGZ"[static_cast<std::uint8_t>(*single_output)], arg);
						return 1;
					}
					if(single_output && *single_output != variant)
						return std::fprintf(stderr, "%s: -%c excludes -%c\n", argv[0], "ugGZ"[static_cast<std::uint8_t>(*single_output)], arg), 1;
					single_output = variant;
					var           = 0;
				} break;


@@ 89,19 87,14 @@ int main(int argc, char * const * argv) {
				case 'a':
					break;
				default:
					std::fprintf(stderr, ID_USAGE(argv[0]));
					return 1;
					return std::fprintf(stderr, ID_USAGE(argv[0])), 1;
			}

		if((full_name || real_ids || nul_sep) && !single_output) {
			std::fprintf(stderr, ID_USAGE(argv[0]));
			return 1;
		}
		if((full_name || real_ids || nul_sep) && !single_output)
			return std::fprintf(stderr, ID_USAGE(argv[0])), 1;

		if(*(argv + optind) && single_output == single_output_t::context) {
			std::fprintf(stderr, ID_USAGE(argv[0]));
			return 1;
		}
		if(*(argv + optind) && single_output == single_output_t::context)
			return std::fprintf(stderr, ID_USAGE(argv[0])), 1;

		if(!*(argv + optind))
			real_ids = true;

M cmd/sleep.cpp => cmd/sleep.cpp +2 -2
@@ 27,14 27,14 @@ int main(int, char * const * argv) {

		double waited = 0;
		while(tosleep > waited + static_cast<double>(std::numeric_limits<std::time_t>::max())) {
			const struct timespec del { std::numeric_limits<std::time_t>::max(), 0 };
			const timespec del{std::numeric_limits<std::time_t>::max(), 0};
			nanosleep(&del, nullptr);
			waited += static_cast<double>(std::numeric_limits<std::time_t>::max());
		}

		double seconds{};
		double subsec = std::modf(tosleep - waited, &seconds);
		const struct timespec del { static_cast<time_t>(seconds), static_cast<long>(subsec * 1000 * 1000 * 1000) };
		const timespec del{static_cast<time_t>(seconds), static_cast<long>(subsec * 1000 * 1000 * 1000)};
		nanosleep(&del, nullptr);
	}
}

A cmd/test.cpp => cmd/test.cpp +289 -0
@@ 0,0 1,289 @@
// SPDX-License-Identifier: 0BSD


#include <algorithm>
#include <cstdio>
#include <cstring>
#include <optional>
#include <string_view>
#include <sys/stat.h>
#include <unistd.h>
#include <variant>
#include <vore-algorithm>
#include <vore-numeric>
#include <vore-optarg>
#include <vore-path>
#include <vore-span>
#include <vore-visit>


using namespace std::literals;

static const char * self{};


// Quoth POSIX.1-2017:
// In evaluating these more complex combined expressions, the following precedence rules are used:
//   * The unary primaries have higher precedence than the algebraic binary primaries.
//   * The unary primaries have lower precedence than the string binary primaries.
//   * The unary and binary primaries have higher precedence than the unary string primary.
//   * The ! operator has higher precedence than the -a operator, and the -a operator has higher precedence than the -o operator.
//   * The -a and -o operators are left associative.
//   * The parentheses can be used to alter the normal precedence and associativity.

// These must be sorted for the std::binary_search()!
static const constexpr std::string_view binary_ops_0[] = {"-eq"sv, "-ge"sv, "-gt"sv, "-le"sv, "-lt"sv, "-ne"sv};  // algebraic binary primaries
static const constexpr std::string_view binary_ops_2[] = {"!="sv, "-ef"sv, "-nt"sv, "-ot"sv, "="sv};              // string binary primaries
static const constexpr std::string_view binary_ops_3   = "-o"sv;
static const constexpr std::string_view binary_ops_4   = "-a"sv;

static const constexpr vore::span<const std::string_view *> binary_ops[] = {{std::begin(binary_ops_0), std::end(binary_ops_0)},
                                                                            {nullptr, nullptr},
                                                                            {std::begin(binary_ops_2), std::end(binary_ops_2)},
                                                                            {&binary_ops_3, &binary_ops_3 + 1},
                                                                            {&binary_ops_4, &binary_ops_4 + 1}};


static const constexpr std::string_view unary_ops_1[] = {"-G"sv, "-L"sv, "-O"sv, "-S"sv, "-b"sv, "-c"sv, "-d"sv, "-e"sv, "-f"sv, "-g"sv, "-h"sv,
                                                         "-k"sv, "-n"sv, "-p"sv, "-r"sv, "-s"sv, "-t"sv, "-u"sv, "-w"sv, "-x"sv, "-z"sv};  // unary primaries
static const constexpr std::string_view unary_ops_5   = "!"sv;

static const constexpr vore::span<const std::string_view *> unary_ops[] = {{nullptr, nullptr},                                //
                                                                           {std::begin(unary_ops_1), std::end(unary_ops_1)},  //
                                                                           {nullptr, nullptr},                                //
                                                                           {nullptr, nullptr},                                //
                                                                           {nullptr, nullptr},                                //
                                                                           {&unary_ops_5, &unary_ops_5 + 1}};


#define ALGEBRAIC_OP(op) [](auto l, auto r) { return l op r; }
using algebraic_op_t                                  = bool (*)(std::int64_t, std::int64_t);
static const constexpr algebraic_op_t algebraic_ops[] = {ALGEBRAIC_OP(==), ALGEBRAIC_OP(>=), ALGEBRAIC_OP(>),
                                                         ALGEBRAIC_OP(<=), ALGEBRAIC_OP(<),  ALGEBRAIC_OP(!=)};


static bool operator<(const struct timespec & lhs, const struct timespec & rhs) {
	return lhs.tv_sec < rhs.tv_sec || (lhs.tv_sec == rhs.tv_sec && lhs.tv_nsec < rhs.tv_nsec);
}
static bool operator>(const struct timespec & lhs, const struct timespec & rhs) {
	return lhs.tv_sec > rhs.tv_sec || (lhs.tv_sec == rhs.tv_sec && lhs.tv_nsec > rhs.tv_nsec);
}

#define STRINGBIN_OP(op) [](const auto & l, const auto & r) { return l op r; }
#define STRINGBIN_FILE_OP(op, ...)                 \
	[](const auto & l, const auto & r) {             \
		struct stat sl, sr;                            \
		if(stat(l.data(), &sl) || stat(r.data(), &sr)) \
			return false;                                \
		return __VA_ARGS__;                            \
	}
using stringbin_op_t                                  = bool (*)(const std::string_view &, const std::string_view &);
static const constexpr stringbin_op_t stringbin_ops[] = {STRINGBIN_OP(!=),                                                          //
                                                         STRINGBIN_FILE_OP(-ef, sl.st_dev == sr.st_dev && sl.st_ino == sr.st_ino),  //
                                                         STRINGBIN_FILE_OP(-nt, sl.st_mtim > sr.st_mtim),                           //
                                                         STRINGBIN_FILE_OP(-ot, sl.st_mtim < sr.st_mtim),                           //
                                                         STRINGBIN_OP(==)};


using expr_t = std::variant<bool, std::string_view>;

static bool eval_default(const expr_t & val) {
	return std::visit(vore::overload{[](const std::string_view & val) { return !!val.size(); }, [](bool b) { return b; }}, val);
}

static std::string_view force_string(const expr_t & val, const std::string_view & from) {
	return std::visit(vore::overload{[](const std::string_view & val) { return val; },
	                                 [&](bool) -> std::string_view { std::fprintf(stderr, "%s: %s: extraneous token\n", self, from.data()), std::exit(2); }},
	                  val);
}

static bool eval_unary(const std::string_view & tok, expr_t && val_e) {
	auto val = force_string(val_e, tok);
	struct stat sb;
#define STAT()              \
	if(stat(val.data(), &sb)) \
		return false;

	switch(tok[1]) {
		case 't': {
			int fd;
			if(!vore::parse_sint(val.data(), fd))
				std::fprintf(stderr, "%s: %s %s: %s\n", self, tok.data(), val.data(), std::strerror(errno)), std::exit(2);
			return isatty(fd);
		} break;

		case 'n':
			return val.size();
		case 'z':
			return !val.size();

		case 'e':
			STAT();
			return true;
		case 's':
			STAT();
			return sb.st_size;

		case 'f':
			STAT();
			return S_ISREG(sb.st_mode);
		case 'd':
			STAT();
			return S_ISDIR(sb.st_mode);
		case 'c':
			STAT();
			return S_ISCHR(sb.st_mode);
		case 'b':
			STAT();
			return S_ISBLK(sb.st_mode);
		case 'p':
			STAT();
			return S_ISFIFO(sb.st_mode);
		case 'S':
			STAT();
			return S_ISSOCK(sb.st_mode);

		case 'h':
		case 'L':
			if(lstat(val.data(), &sb))
				return false;
			return S_ISLNK(sb.st_mode);

		case 'O':
			STAT();
			return sb.st_uid == geteuid();
		case 'G':
			STAT();
			return sb.st_gid == getegid();

		case 'r':
			return !access(val.data(), R_OK);
		case 'w':
			return !access(val.data(), W_OK);
		case 'x':
			return !access(val.data(), X_OK);

		case 'u':
			STAT();
			return sb.st_mode & S_ISUID;
		case 'g':
			STAT();
			return sb.st_mode & S_ISGID;
		case 'k':
			STAT();
			return sb.st_mode & S_ISVTX;

		default:
			__builtin_unreachable();
	}
}

static bool eval_binary(const std::string_view & from, expr_t && lhe, expr_t && rhe) {
	if(auto itr = vore::binary_find(std::begin(binary_ops_0), std::end(binary_ops_0), from); itr != std::end(binary_ops_0)) {
		auto lhs = force_string(lhe, from).data();
		auto rhs = force_string(rhe, from).data();
		std::int64_t l, r;
		if(!vore::parse_sint(lhs, l))
			std::fprintf(stderr, "%s: %s %s %s: %s: %s\n", self, lhs, itr->data(), rhs, lhs, std::strerror(errno)), std::exit(2);
		if(!vore::parse_sint(rhs, r))
			std::fprintf(stderr, "%s: %s %s %s: %s: %s\n", self, lhs, itr->data(), rhs, rhs, std::strerror(errno)), std::exit(2);
		return algebraic_ops[itr - std::begin(binary_ops_0)](l, r);
	} else if(auto itr = vore::binary_find(std::begin(binary_ops_2), std::end(binary_ops_2), from); itr != std::end(binary_ops_2))
		return stringbin_ops[itr - std::begin(binary_ops_2)](force_string(lhe, from), force_string(rhe, from));
	else if(from == "-o"sv)
		return eval_default(lhe) || eval_default(rhe);
	else if(from == "-a"sv)
		return eval_default(lhe) && eval_default(rhe);
	else
		__builtin_unreachable();
}


static std::optional<std::uint8_t> prefix_precedence(const std::string_view & tok) {
	if(auto itr = std::find_if(std::begin(unary_ops), std::end(unary_ops), [&](auto && ops) { return std::binary_search(std::begin(ops), std::end(ops), tok); });
	   itr != std::end(unary_ops))
		return (itr - std::begin(unary_ops)) * 2;
	else
		return {};
}

static std::optional<std::pair<std::uint8_t, std::uint8_t>> infix_precedence(const std::string_view & of) {
	if(auto itr = std::find_if(std::begin(binary_ops), std::end(binary_ops), [&](auto && sp) { return std::binary_search(std::begin(sp), std::end(sp), of); });
	   itr != std::end(binary_ops))
		return std::pair<std::uint8_t, std::uint8_t>{(itr - std::begin(binary_ops)) * 2, (itr - std::begin(binary_ops)) * 2 + 1};
	else
		return {};
}

static expr_t ingest(const char * const *& begin, const char * const * end, std::uint8_t min_prec) {
	if(begin == end)
		std::fprintf(stderr, "%s: not enough tokens\n", self), std::exit(2);
	std::string_view tok{*begin++};

	expr_t lhs;
	if(tok == "("sv) {
		lhs = eval_default(ingest(begin, end, 0));
		if(begin == end || *begin++ != ")"sv)
			std::fprintf(stderr, "%s: (: unmatched parentheses\n", self), std::exit(2);
	} else if(tok == ")"sv)
		std::fprintf(stderr, "%s: ): unmatched parentheses\n", self), std::exit(2);
	else {
		if(auto prec = prefix_precedence(tok)) {
			if(tok == "!"sv)
				lhs = !eval_default(ingest(begin, end, *prec));
			else if(std::binary_search(std::begin(unary_ops_1), std::end(unary_ops_1), tok)) {
				// lhs = eval_unary(tok, ingest(begin, end, *prec));
				if(begin == end)
					std::fprintf(stderr, "%s: not enough tokens\n", self), std::exit(2);
				lhs = eval_unary(tok, std::string_view{*begin++});
			} else
				__builtin_unreachable();
		} else
			lhs = std::move(tok);
	}

	while(begin != end) {
		tok = *begin;  // guaranteed op

		if(auto prec = infix_precedence(tok)) {
			if(prec->first < min_prec)
				break;
			++begin;

			lhs = eval_binary(tok, std::move(lhs), ingest(begin, end, prec->second));
			continue;
		}

		break;
	}

	return lhs;
}


int main(int, const char * const * argv) {
	vore::opt::args args{argv + static_cast<bool>(argv[0])};
	vore::span expression{std::begin(args), std::end(args)};
	if(vore::basename(argv[0] ?: "(?)") == "["sv) {
		if(*(std::end(expression) - 1) != "]"sv)
			return std::fprintf(stderr, "%s: no ]\n", argv[0]), 2;
		--expression.e;
	}

	self = argv[0];

	switch(std::distance(std::begin(expression), std::end(expression))) {
		case 0:
			return !false;
		case 1:
			return !**std::begin(expression);
		default: {
			auto itr = std::begin(expression);
			auto val = ingest(itr, std::end(expression), 0);
			if(itr != std::end(expression))
				return std::fprintf(stderr, "%s: %s: extraneous token\n", self, *itr), 2;

			return !eval_default(val);
		}
	}
}

A include/vore-algorithm => include/vore-algorithm +15 -0
@@ 0,0 1,15 @@
// SPDX-License-Identifier: 0BSD


#include <algorithm>


namespace vore {
	namespace {
		template <class I, class T>
		I binary_find(I begin, I end, const T & val) {  // std::binary_search() but returns the iterator instead
			begin = std::lower_bound(begin, end, val);
			return (!(begin == end) && !(val < *begin)) ? begin : end;
		}
	}
}

M man/aliases => man/aliases +1 -0
@@ 7,3 7,4 @@ base64.1   base64url.1 base32.1    base32hex.1  base16.1
sha1sum.1  sha224sum.1 sha256sum.1 sha384sum.1  sha512sum.1  b2sum.1  md5sum.1  md5sum.textutils.1
mkfifo.1   mknod.8
basename.1 dirname.1
test.1     [.1

M man/base64.1 => man/base64.1 +18 -2
@@ 1,3 1,4 @@
'\" e
.\" SPDX-License-Identifier: 0BSD
.\"
.Dd


@@ 45,6 46,10 @@
.Op Fl i
.Op Ar file
.
.EQ
delim %%
.EN
.
.Sh DESCRIPTION
Without
.Fl d ,


@@ 64,7 69,12 @@ mapping consecutive bits to the indices into one of the following alphabets:
.Li 0123456789ABCDEF
.El
If the input is not long enough
.Pq Sy len Ns Po Ar input Pc Ns * Ns Sy 8 No / Sy log2 Ns Po Sy len Ns Po alphabet Pc Pc is not an integer ,
.Po
.ie n \
.Sy len Ns Po Ar input Pc Ns * Ns Sy 8 No / Sy log2 Ns Po Sy len Ns Po alphabet Pc Pc
.el   % { 8 len ( input ) } over { log sub 2 ( len ( alphabet ) ) } %
is not an integer
.Pc ,
it's padded with null bytes, expressed as
.Qo Sy = Qc Ns s .
.Pp


@@ 87,7 97,9 @@ By definition, sequentially applying either two inverse transformations yields t
encoding into any of these alphabets, carefully composed of characters which universally have no special meaning,
allows lossless transmission of binary data as plain text at only a minor increase in size,
equal to the amount of alphabet text required to express one byte of input:
.ie n \
.Sy 8 Ns / Ns Sy log2 Ns Pq Sy len Ns Pq alphabet
.el   % 8 over { log sub 2 ( len ( alphabet ) ) } %
\(rA
.Sy 4 Ns / Ns Sy 3 Ns = Ns Sy 1.(3) ,
.Sy 1.6 ,


@@ 149,7 161,7 @@ also appears in
.Bx 4.0
introduced
.Xr uuencode 1C
.Pq and Xr uudecode 1C ,
.Pq and Xr uudecode 1C :
a structured, whole-file approach including the name and permissions in the encoded output,
also using a 6-bits-per-byte encoding (but a fundamentally different one), an enhancement to the
.Xr uucp 1 Ns / Ns Xr uusend 1


@@ 160,3 172,7 @@ added
using the
.Nm
encoding instead.
.
.EQ
delim off
.EN

M man/cksum.1 => man/cksum.1 +11 -2
@@ 1,3 1,4 @@
'\" e
.\" SPDX-License-Identifier: 0BSD
.\"
.Dd


@@ 11,6 12,10 @@
.Nm
.Oo Ar file Oc Ns …
.
.EQ
delim %%
.EN
.
.Sh DESCRIPTION
Prints an Ethernet CRC of each
.Ar file ,


@@ 32,7 37,7 @@ size is also included in the sum, so files beginning with runs of NUL bytes prod
.Bd -literal -compact
.Li $ Nm echo Li POSIX.2 \&| Nm
3842620415 8
.Li $ Nm echo Li POSIX.2 \&| Nm Ar 'IEEE P1003.2 Draft 11.2\(emSeptember 1991.pdf' -
.Li $ Nm echo Li POSIX.2 \&| Nm Li 'IEEE P1003.2 Draft 11.2\(emSeptember 1991.pdf' -
2938529873 3226947 IEEE P1003.2 Draft 11.2\(emSeptember 1991.pdf
3842620415 8 -
.Ed


@@ 45,7 50,7 @@ size is also included in the sum, so files beginning with runs of NUL bytes prod
Conforms to
.St -p1003.1-2008 .
The CRC polynomial is
\fIx\fP\u\s-432\s0\d+\fIx\fP\u\s-426\s0\d+\fIx\fP\u\s-423\s0\d+\fIx\fP\u\s-422\s0\d+\fIx\fP\u\s-416\s0\d+\fIx\fP\u\s-412\s0\d+\fIx\fP\u\s-411\s0\d+\fIx\fP\u\s-410\s0\d+\fIx\fP\u\s-48\s0\d+\fIx\fP\u\s-47\s0\d+\fIx\fP\u\s-45\s0\d+\fIx\fP\u\s-44\s0\d+\fIx\fP\u\s-42\s0\d+\fIx\fP+1,
% x sup 32 + x sup 26 + x sup 23 + x sup 22 + x sup 16 + x sup 12 + x sup 11 + x sup 10 + x sup 8 + x sup 7 + x sup 5 + x sup 4 + x sup 2 + x + 1 %,
the same one used by
.St -iso8802-3 Pq Ethernet .
.Pp


@@ 58,3 63,7 @@ as
.Nm sum
implementations were irreconcilable, cf.\&
.Xr sum 1 .
.
.EQ
delim off
.EN

M man/env.1 => man/env.1 +3 -2
@@ 269,5 269,6 @@ removes
.Sy - ,
but
.St -p1003.1-2008
and later define it as unspecified to allow compatibility with the \s-2CB-UNIX\s0 behaviour \(em
all known implementations support it, and the vast majority mark it as deprecated.
and later define it as unspecified to allow compatibility with the
.Tn CB-UNIX
behaviour \(em all known implementations support it, and the vast majority mark it as deprecated.

M man/expr.1 => man/expr.1 +0 -26
@@ 324,32 324,6 @@ supposedly making another one in the pattern plain text, despite not doing so an
Of interest is also that the
.Sx ARCHAIC FORMS
are such because they "have been made obsolete by the : operator" \(em the suggested replacements are:
.Bl -tag -compact -offset Ds -width "substr expra exprb exprc"
.It Cm substr Ar expra exprb exprc
Given
.Cm substr Ar abcd 2 2 :
.Ar abcd Cm \&: Ar '..\e(..\e)'
\(em this is mostly reasonable, but more accurate as
.Ar '..\e(..\e?\e)' ,
and more generic as
.Ar '.\e{2\e}\e(.\e{1,2\e}\e)' .
.It Cm length Ar expr
.Ar expr Cm \&: Ar '.*'
.It Cm index Ar expra exprb
Given
.Cm index Ar abcd d :
.Ar abcd Cm \&: Ar d .
Not even close!
This is approximately seven centimeters down from explaining how
.Cm \&:
is anchored and what that entails.
Recreating
.Cm index
is very likely impossible with
.Cm \&: ,
even for a simple single-letter case.
.El
.Pp
.Bl -tag -compact -offset Ds -width "substr abcd 2 2 "
.It Cm substr Ar abcd 2 2
.Ar abcd Cm \&: Ar '..\e(..\e)'

A man/test.1 => man/test.1 +367 -0
@@ 0,0 1,367 @@
.\" SPDX-License-Identifier: 0BSD
.\"
.Dd
.Dt TEST 1
.Os
.
.Sh NAME
.Nm test , \&[
.Nd validate predicate
.Sh SYNOPSIS
.Nm
.Op Cm \&!
.Cm \&( Ar expr Cm \&)
.Oo Bro Fl a , o Brc Ar expr Oc Ns …
.Nm
.Op Cm \&!
.Ar string Bro Cm = , != Brc Ar string , Ar file Bro Fl ef , nt , ot Brc Ar file
.Oo Bro Fl a , o Brc Ar expr Oc Ns …
.Nm
.Op Cm \&!
.Fl t Ar fd , Bro Fl n , z Brc Ar string , Bro Fl e , s , f , d , c , b , p , S , O , G , r , w , x , u , g , k Brc Ar file , Bro Fl h , L Brc Ar path
.Oo Bro Fl a , o Brc Ar expr Oc Ns …
.Nm
.Op Cm \&!
.Ar integer Bro Fl lt , le , eq , ne , ge , gt Brc Ar integer
.Oo Bro Fl a , o Brc Ar expr Oc Ns …
.Nm
.Op Cm \&!
.Ar string
.Oo Bro Fl a , o Brc Ar expr Oc Ns …
.Nm
.Pp
.Nm \&[
.Op Cm \&!
.Cm \&( Ar expr Cm \&)
.Oo Bro Fl a , o Brc Ar expr Oc Ns …
.Cm ]\&
.Nm \&[
.Op Cm \&!
.Ar string Bro Cm = , != Brc Ar string , Ar file Bro Fl ef , nt , ot Brc Ar file
.Oo Bro Fl a , o Brc Ar expr Oc Ns …
.Cm ]\&
.Nm \&[
.Op Cm \&!
.Fl t Ar fd , Bro Fl n , z Brc Ar string , Bro Fl e , s , f , d , c , b , p , S , O , G , r , w , x , u , g , k Brc Ar file , Bro Fl h , L Brc Ar path
.Oo Bro Fl a , o Brc Ar expr Oc Ns …
.Cm ]\&
.Nm \&[
.Op Cm \&!
.Ar integer Bro Fl lt , le , eq , ne , ge , gt Brc Ar integer
.Oo Bro Fl a , o Brc Ar expr Oc Ns …
.Cm ]\&
.Nm \&[
.Op Cm \&!
.Ar string
.Oo Bro Fl a , o Brc Ar expr Oc Ns …
.Cm ]\&
.Nm \&[
.Cm ]\&
.
.Sh DESCRIPTION
Exits with the result of the specified boolean expression.
With no expression, exits false.
.
.Ss Operators
In chunked descending precedence, except all unary operators are equiprecedent;
.Fl ao
left-associative.
.Pp
True if:
.Bl -tag -compact -width "file-l -ot file-r "
.It Cm \&( Ar expr Cm \&)
.Ar expr
.Pp
.
.It Cm \&! Ar expr
.Ar expr
is not true.
.Pp
.
.\" Strictly, this should be expr-l not expr\-l (expr-dash-l not expr-minus-l), but it italicises better in -Tps, so
.It Ar expr Fl a Ar expr
Both expressions are.
.Pp
.
.It Ar expr Fl o Ar expr
Either expression is.
.Pp
.
.It Ar string Ar " =" Ar string
The strings are identical.
.
.It Ar string Ar != Ar string
The strings are
.Em not
identical.
.
.It Ar "file  " Fl ef Ar "file  "
The files correspond to the same file \(em lie on the same device and point at the same i-node.
.
.It Ar file\-l Fl nt Ar file\-r
The modification time of
.Ar file\-l
is earler than that of
.Ar file\-r .
.
.It Ar file\-l Fl ot Ar file\-r
The modification time of
.Ar file\-l
is later than that of
.Ar file\-r .
.Pp
.
.It Fl t Ar fd
File descriptor
.Ar fd
corresponds to a teletype.
.Pp
.
.It Fl n Ar string
.Ar string
is
.Em not
empty.
.
.It Fl z Ar string
.Ar string
is empty.
.Pp
.
.It Fl e Ar file
.Ar file
exists.
.
.It Fl s Ar file
.Ar file Ns 's
size is non-zero.
.Pp
.
.It Fl f Ar file
.Ar file
is a regular file.
.
.It Fl d Ar file
.Ar file
is a directory.
.
.It Fl c Ar file
.Ar file
is a character device.
.
.It Fl b Ar file
.Ar file
is a block device.
.
.It Fl p Ar file
.Ar file
is a named pipe (FIFO).
.
.It Fl S Ar file
.Ar file
corresponds to a
.Tn UNIX Ns -domain
socket.
.Pp
.
.It Fl h Ar path ,  Fl L Ar path
.Ar path
is a symbolic link.
.Pp
.
.It Fl O Ar file
.Ar file
is owned by the process' effective user ID.
.
.It Fl G Ar file
.Ar file
is owned by the process' effective group ID.
.Pp
.
.It Fl r Ar file
.Ar file
could be read by the process.
.
.It Fl w Ar file
.Ar file
could be written by the process.
.
.It Fl x Ar file
.Ar file
could be executed (searched) by the process.
.Pp
.
.It Fl u Ar file
.Ar file
is set-user-ID.
.
.It Fl g Ar file
.Ar file
is set-group-ID.
.
.It Fl k Ar file
.Ar file
is sticky.
.Pp
.
.It Ar int\-l Fl lt Ar int\-r
.Ar int\-l
<
.Ar int\-r
.
.It Ar int\-l Fl le Ar int\-r
.Ar int\-l
\[<=]
.Ar int\-r
.
.It Ar int\-l Fl eq Ar int\-r
.Ar int\-l
=
.Ar int\-r
.
.It Ar int\-l Fl ne Ar int\-r
.Ar int\-l
\[!=]
.Ar int\-r
.
.It Ar int\-l Fl ge Ar int\-r
.Ar int\-l
\[>=]
.Ar int\-r
.
.It Ar int\-l Fl gt Ar int\-r
.Ar int\-l
>
.Ar int\-r
.Pp
.
.It Ar string
.Fl n Ar string
.El
.
.Sh EXIT STATUS
.Bl -tag -compact -width "I"
.It Sy 0
The expression evaluated true.
.It Sy 1
The expression evaluated false.
.It Sy 2
Syntax error in expression or non-integer passed to
.Fl t
or an arithmetic operator.
.El
.
.Sh EXAMPLES
.\" https://github.com/systemd/systemd/pull/19006/files
A short, edited, extract from
.Xr kernel-install 8 :
.Bd -literal -compact
#!/bin/sh

.Nm \&[ Fl z Li \&" Ns Ev $MACHINE_ID Ns \&" Cm ]\& Li && Nm \&[ Fl f Pa /etc/machine-id Cm ]\& Li && Nm read Fl r Ev MACHINE_ID Li < Pa /etc/machine-id
.Nm \&[ Fl z Li \&" Ns Ev $MACHINE_ID Ns \&" Cm ]\& Li && Ev MACHINE_ID Ns =Default

.Nm \&[ Li \&" Ns Ev $VERBOSE Ns \&" Fl ge Ar 3 Cm ]\& Li && Nm echo Li \&"Machine ID : Ev $MACHINE_ID Ns \&"

.Nm for Ev suff Cm in Li \&" Ns Ev $MACHINE_ID Ns \&" \&"Default" \&"loader/entries" ; Cm do
.Nm "   " for Ev pref Cm in Li \&"/efi" \&"/boot/efi" \&"/boot" ; Cm do
.Nm "   " "   " if \&[ Fl d Li \&" Ns Ev $pref/$suff Ns \&" Cm ]\& ; then
.Ev "   " "   " "   " BOOT_ROOT Ns = Ns \&" Ns Ev $pref Ns \&"
.Nm "   " "   " "   " break Ar 2
.Nm "   " "   " fi
.Cm "   " done
.Cm done


.Nm if \&[ Fl z Li \&" Ns Ev $layout Ns \&" Cm ]\& ; then
.Nm "   " if \&[ Fl d Li \&" Ns Ev $BOOT_ROOT/$MACHINE_ID Ns \&" Cm ]\& ; then
.Li "   " "   " Ev layout Ns ="bls-efi"
.Cm "   " else
.Li "   " "   " Ev layout Ns ="legacy"
.Cm "   " fi
.Cm fi

.Ed
.
.Sh SEE ALSO
.Xr expr 1 ,
.Xr access 2 ,
.Xr lstat 2 ,
.Xr stat 2 ,
.Xr isatty 3
.
.Sh STANDARDS
Conforms to
.St -p1003.1-2008 ;
.Fl O , G , k , ef , nt , No and Fl ot
are extensions;
.Fl k
originates from
.Tn CB-UNIX ,
the rest from the KornShell,
.Pp
The standard marks
.Cm ()
and
.Fl ao
as obsolete, and for good reason \(em the expression grammar is very loose and easy to throw off with malicious input.
Chain multiple
.Nm
invocations with
.Li &&
and
.Li ||
instead, though be wary of precedence (rather, the comparative lack thereof).
.
.Sh HISTORY
Appeared in
.At v7 ,
supporting
.Cm () , \&! , Fl ao , Cm = , != , Fl tnzsfdrw , lt , le , eq , ne , ge , gt , No and the plain string .
.Fl t ,
when not followed by an argument, defaults to
.Fl t Ar 1 .
.Fl lt , le , eq , ne , ge , gt ,
instead of integers, can be provided with
.Fl l Ar string ,
resolving to the length of
.Ar string .
.Pp
.Tn CB-UNIX
at-or-before version 2.3 adds
.Fl xcbugk
and drops
.Fl l .
.Pp
.Tn CB-UNIX
was, among others, the basis for
.At III ,
which sees the same implementation, but, curiously, only as a
.Xr sh 1
built-in.
.Pp
.At V.1
adds
.Fl p .
.At V.4
adds
.Fl h ,
alters
.Fl f
to match any non-directories if
.Pa /usr/ucb
is in the
.Ev PATH .
.Pp
.Bx 4.4
adds all
.Tn CB-UNIX
operators,
.Fl eph ,
and
.Cm &|
as aliases for
.Fl ao
to a
.At v7
base.

A tests/test/make-socket.c => tests/test/make-socket.c +15 -0
@@ 0,0 1,15 @@
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>


int main() {
	int fd = socket(AF_UNIX, SOCK_STREAM, 0);
	if(fd == -1)
		abort();

	struct sockaddr_un addr = {.sun_family = AF_UNIX, .sun_path = "socket"};
	if(bind(fd, (const struct sockaddr *)&addr, sizeof(addr)) == -1)
		abort();
}

A tests/test/test => tests/test/test +162 -0
@@ 0,0 1,162 @@
#!/bin/sh
# SPDX-License-Identifier: 0BSD

tmpdir="$(mktemp -dt "test.XXXXXXXXXX")/"
test="${CMDDIR}test"

echo > "${tmpdir}a"
ln "${tmpdir}a" "${tmpdir}a2"
${CC:-cc} -o "${tmpdir}make-socket" make-socket.c &

altname="${tmpdir%/}"
altname="${altname##*/}"

ln -fs "$test" "${tmpdir}test"
ln -fs "$test" "${tmpdir}["
ln -fs "$test" "${tmpdir}something-else"
ln -fs "$test" "${tmpdir}$altname"

"${tmpdir}test" a 2>&3; err=$?
[ $err -eq 0 ] || echo "test: got $err instead of 0 for test a" >&3
"${tmpdir}something-else" а 2>&3; err=$?
[ $err -eq 0 ] || echo "test: got $err instead of 0 for something-else a" >&3
"${tmpdir}$altname" а 2>&3; err=$?
[ $err -eq 0 ] || echo "test: got $err instead of 0 for $altname a" >&3

"${tmpdir}[" a ] 2>&3; err=$?
[ $err -eq 0 ] || echo "test: got $err instead of 0 for [ a ]" >&3
"${tmpdir}[" a 2>"${tmpdir}err"; err=$?
[ $err -eq 2 ]        || echo "test: got $err instead of 2 for [ a" >&3
[ -s "${tmpdir}err" ] || echo "test: [ a: stderr empty" >&3


for t in [ test; do
  tst="${tmpdir}$t"
  bkt="$("${tmpdir}[" "$t" = '[' ] && echo ']')"

  "$tst" a  = a  $bkt || echo "test: $t a  = a $bkt failed?" >&3
  "$tst" a != a  $bkt && echo "test: $t a != a $bkt okay?" >&3
  "$tst" a != ab $bkt || echo "test: $t a != ab $bkt failed?" >&3
  "$tst" a  = ab $bkt && echo "test: $t a  = ab $bkt okay?" >&3

  echo > "${tmpdir}b"
  "$tst" "${tmpdir}a" -ef "${tmpdir}a"  $bkt || echo "test: $t a -ef a  $bkt failed?" >&3
  "$tst" "${tmpdir}a" -ef "${tmpdir}a2" $bkt || echo "test: $t a -ef a2 $bkt failed?" >&3
  "$tst" "${tmpdir}a" -ef "${tmpdir}b"  $bkt && echo "test: $t a -ef b  $bkt okay?" >&3

  "$tst" "${tmpdir}a"  -nt "${tmpdir}a"  $bkt && echo "test: $t a  -nt a  $bkt okay?" >&3
  "$tst" "${tmpdir}a"  -nt "${tmpdir}a2" $bkt && echo "test: $t a  -nt a2 $bkt okay?" >&3
  "$tst" "${tmpdir}a2" -nt "${tmpdir}a"  $bkt && echo "test: $t a2 -nt a  $bkt okay?" >&3
  "$tst" "${tmpdir}a"  -nt "${tmpdir}b"  $bkt && echo "test: $t a  -nt b  $bkt okay?" >&3
  "$tst" "${tmpdir}b"  -nt "${tmpdir}a"  $bkt || echo "test: $t b  -nt a  $bkt failed?" >&3

  "$tst" "${tmpdir}a"  -ot "${tmpdir}a"  $bkt && echo "test: $t a  -ot a  $bkt okay?" >&3
  "$tst" "${tmpdir}a"  -ot "${tmpdir}a2" $bkt && echo "test: $t a  -ot a2 $bkt okay?" >&3
  "$tst" "${tmpdir}a2" -ot "${tmpdir}a"  $bkt && echo "test: $t a2 -ot a  $bkt okay?" >&3
  "$tst" "${tmpdir}a"  -ot "${tmpdir}b"  $bkt || echo "test: $t a  -ot b  $bkt failed?" >&3
  "$tst" "${tmpdir}b"  -ot "${tmpdir}a"  $bkt && echo "test: $t b  -ot a  $bkt okay?" >&3


  "$tst" -t 0 $bkt < /dev/tty  || echo "test: $t -t /dev/tty  $bkt failed?" >&3
  "$tst" -t 0 $bkt < /dev/null && echo "test: $t -t /dev/null $bkt okay?" >&3


  "$tst" -n ''   $bkt && echo "test: $t -n ''   $bkt okay?" >&3
  "$tst" -n 'a'  $bkt || echo "test: $t -n 'a'  $bkt failed?" >&3
  "$tst" -n '-n' $bkt || echo "test: $t -n '-n' $bkt failed?" >&3

  "$tst" -z ''   $bkt || echo "test: $t -z ''   $bkt failed?" >&3
  "$tst" -z 'a'  $bkt && echo "test: $t -z 'a'  $bkt okay?" >&3
  "$tst" -z '-n' $bkt && echo "test: $t -z '-n' $bkt okay?" >&3


  "$tst" -e /            $bkt || echo "test: $t -e /         $bkt failed?" >&3
  "$tst" -e /dev/null    $bkt || echo "test: $t -e /dev/null $bkt failed?" >&3
  "$tst" -e "${tmpdir}a" $bkt || echo "test: $t -e a         $bkt failed?" >&3
  "$tst" -e "${tmpdir}[" $bkt || echo "test: $t -e [         $bkt failed?" >&3
  "$tst" -e "/ENOENT"    $bkt && echo "test: $t -e /ENOENT   $bkt okay?" >&3

  printf ''     > "${tmpdir}empty"
  echo nonempty > "${tmpdir}nonempty"
  "$tst" -s /                   $bkt || echo "test: $t -s /         $bkt failed?" >&3
  "$tst" -s /dev/null           $bkt && echo "test: $t -s /dev/null $bkt okay?" >&3
  "$tst" -s "${tmpdir}empty"    $bkt && echo "test: $t -s empty     $bkt okay?" >&3
  "$tst" -s "${tmpdir}nonempty" $bkt || echo "test: $t -s nonempty  $bkt failed?" >&3


  blockdev="$(find /dev -type b -print -quit)"
  "$tst" -e "${tmpdir}fifo"   $bkt || mkfifo "${tmpdir}fifo"
  "$tst" -e "${tmpdir}root"   $bkt || ln -s / "${tmpdir}root"
  wait; "$tst" -e "${tmpdir}socket" $bkt || ( cd "$tmpdir"; ./make-socket 2>&3 )
  typify() {
    for fl in f d c b p S h L; do
      "$tst" "-$fl" "$1" $bkt && printf "$fl"
    done
  }
  "$tst" "$(typify "/"               )" = 'd'   $bkt || echo "test: -fdcbpShL /         wrong" >&3
  "$tst" "$(typify "$tmpdir"         )" = 'd'   $bkt || echo "test: -fdcbpShL $tmpdir   wrong" >&3
  "$tst" "$(typify "${tmpdir}empty"  )" = 'f'   $bkt || echo "test: -fdcbpShL empty     wrong" >&3
  "$tst" "$(typify "/dev/tty"        )" = 'c'   $bkt || echo "test: -fdcbpShL /dev/tty  wrong" >&3
  "$tst" "$(typify "/dev/null"       )" = 'c'   $bkt || echo "test: -fdcbpShL /dev/null wrong" >&3
  "$tst" "$(typify "$blockdev"       )" = 'b'   $bkt || echo "test: -fdcbpShL $blockdev wrong" >&3
  "$tst" "$(typify "${tmpdir}fifo"   )" = 'p'   $bkt || echo "test: -fdcbpShL fifo      wrong" >&3
  "$tst" "$(typify "${tmpdir}socket" )" = 'S'   $bkt || echo "test: -fdcbpShL socket    wrong" >&3
  "$tst" "$(typify "${tmpdir}["      )" = 'fhL' $bkt || echo "test: -fdcbpShL [         wrong" >&3
  "$tst" "$(typify "${tmpdir}root"   )" = 'dhL' $bkt || echo "test: -fdcbpShL root      wrong" >&3


  "$tst" -O "${tmpdir}a" $bkt || echo "test: $t -O a $bkt failed?" >&3
  "$tst" -O /            $bkt && echo "test: $t -O / $bkt okay?" >&3

  "$tst" -G "${tmpdir}a" $bkt || echo "test: $t -G a $bkt failed?" >&3
  "$tst" -G /            $bkt && echo "test: $t -G / $bkt okay?" >&3

  "$tst" -e "${tmpdir}000" $bkt || echo > "${tmpdir}000"
  echo > "${tmpdir}700"
  chmod 000 "${tmpdir}000"
  chmod 700 "${tmpdir}700"
  "$tst" -r "${tmpdir}000" $bkt && echo "test: $t -r 000 $bkt okay?" >&3
  "$tst" -r "${tmpdir}700" $bkt || echo "test: $t -r 700 $bkt failed?" >&3
  "$tst" -w "${tmpdir}000" $bkt && echo "test: $t -w 000 $bkt okay?" >&3
  "$tst" -w "${tmpdir}700" $bkt || echo "test: $t -w 700 $bkt failed?" >&3
  "$tst" -x "${tmpdir}000" $bkt && echo "test: $t -x 000 $bkt okay?" >&3
  "$tst" -x "${tmpdir}700" $bkt || echo "test: $t -x 700 $bkt failed?" >&3

  "$tst" -e "${tmpdir}sst" $bkt || echo > "${tmpdir}sst"
  chmod a=st "${tmpdir}sst"
  "$tst" -u "${tmpdir}700" $bkt && echo "test: $t -u 700 $bkt okay?" >&3
  "$tst" -u "${tmpdir}sst" $bkt || echo "test: $t -u sst $bkt failed?" >&3
  "$tst" -g "${tmpdir}700" $bkt && echo "test: $t -g 700 $bkt okay?" >&3
  "$tst" -g "${tmpdir}sst" $bkt || echo "test: $t -g sst $bkt failed?" >&3
  "$tst" -k "${tmpdir}700" $bkt && echo "test: $t -k 700 $bkt okay?" >&3
  "$tst" -k "${tmpdir}sst" $bkt || echo "test: $t -k sst $bkt failed?" >&3


  "$tst" 0 -lt 9223372036854775808 $bkt 2>"${tmpdir}err"; err=$?
  [ $err -eq 2 ]        || echo "test: got $err instead of 2 for $t 0 -lt 9223372036854775808 $bkt" >&3
  [ -s "${tmpdir}err" ] || echo "test: $t 0 -lt 9223372036854775808 $bkt: stderr empty" >&3

  "$tst" -9223372036854775808 -lt 9223372036854775807 $bkt || echo "test: $t -9223372036854775808 -lt 9223372036854775807 $bkt failed?" >&3
  "$tst" -9223372036854775808 -le 9223372036854775807 $bkt || echo "test: $t -9223372036854775808 -le 9223372036854775807 $bkt failed?" >&3
  "$tst" -9223372036854775808 -eq 9223372036854775807 $bkt && echo "test: $t -9223372036854775808 -eq 9223372036854775807 $bkt okay?" >&3
  "$tst" -9223372036854775808 -ne 9223372036854775807 $bkt || echo "test: $t -9223372036854775808 -ne 9223372036854775807 $bkt failed?" >&3
  "$tst" -9223372036854775808 -ge 9223372036854775807 $bkt && echo "test: $t -9223372036854775808 -ge 9223372036854775807 $bkt okay?" >&3
  "$tst" -9223372036854775808 -gt 9223372036854775807 $bkt && echo "test: $t -9223372036854775808 -gt 9223372036854775807 $bkt okay?" >&3

  "$tst" -9223372036854775808 -lt ' -0x8000000000000000' $bkt && echo "test: $t -9223372036854775808 -lt  -0x8000000000000000 $bkt okay?" >&3
  "$tst" -9223372036854775808 -le ' -0x8000000000000000' $bkt || echo "test: $t -9223372036854775808 -le  -0x8000000000000000 $bkt failed?" >&3
  "$tst" -9223372036854775808 -eq ' -0x8000000000000000' $bkt || echo "test: $t -9223372036854775808 -eq  -0x8000000000000000 $bkt failed?" >&3
  "$tst" -9223372036854775808 -ne ' -0x8000000000000000' $bkt && echo "test: $t -9223372036854775808 -ne  -0x8000000000000000 $bkt okay?" >&3
  "$tst" -9223372036854775808 -ge ' -0x8000000000000000' $bkt || echo "test: $t -9223372036854775808 -ge  -0x8000000000000000 $bkt failed?" >&3
  "$tst" -9223372036854775808 -gt ' -0x8000000000000000' $bkt && echo "test: $t -9223372036854775808 -gt  -0x8000000000000000 $bkt okay?" >&3

  "$tst" ''       $bkt && echo "test: $t          $bkt okay?" >&3
  "$tst" ]        $bkt || echo "test: $t ]        $bkt failed?" >&3
  "$tst" -n       $bkt || echo "test: $t -n       $bkt failed?" >&3
  "$tst" anything $bkt || echo "test: $t anything $bkt failed?" >&3

  "$tst" $bkt && echo "test: $t $bkt okay?" >&3
done


rm -rf "$tmpdir" 2>&3