~nabijaczleweli/voreutils

d3c3318e35aabf915c6c73d0c21cb3c228c5942e — наб 22 days ago c151563
Add mkfifo (mknod)
M README.md => README.md +4 -2
@@ 18,7 18,7 @@ GNU coreutils provide the following 105 binaries, according to `dpkg -L coreutil
  * ☐ /bin/ln
  * ☐ /bin/ls
  * ☐ /bin/mkdir
  * ☐ /bin/mknod
  * ☑ /bin/mknod
  * ☐ /bin/mktemp
  * ☐ /bin/mv
  * ☐ /bin/pwd


@@ 61,7 61,7 @@ GNU coreutils provide the following 105 binaries, according to `dpkg -L coreutil
  * ☑ /usr/bin/link
  * ☑ /usr/bin/logname
  * ☑ /usr/bin/md5sum
  * ☐ /usr/bin/mkfifo
  * ☑ /usr/bin/mkfifo – https://bugs.debian.org/990962 lole
  * ☑ /usr/bin/nice
  * ☐ /usr/bin/nl
  * ☑ /usr/bin/nohup – GNU nohup resets umask (for no discernible reason?), returns 125 instead of 127 for processing/no-nohup.out (which is wrong!)


@@ 116,6 116,8 @@ TODO: should posix_fadvise(sequential) where appropriate maybe?

TODO: should fread_unlocked(sequential) where appropriate maybe?

TODO: support SMACK in addition to SELinux

## Building
You'll need a non-ancient C[++] toolchain, a POSIX AWK, GNU make, and mandoc (optional, which may change, confer `nomandoc` target).


M cmd/aliases => cmd/aliases +1 -0
@@ 5,3 5,4 @@ id      groups     whoami
base64  base64url  base32    base32hex  base16
sha1sum sha224sum  sha256sum sha384sum  sha512sum  b2sum  md5sum  md5sum.textutils
cksum   sum
mkfifo  mknod

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


#include <algorithm>
#include <cstdio>
#include <cstring>
#include <errno.h>
#include <fcntl.h>
#include <initializer_list>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <unistd.h>
#include <utility>
#include <vore-getopt>
#include <vore-mode>
#include <vore-numeric>
#include <vore-optarg>
#include <vore-path>
#include <vore-selinux>


#define USAGE_MKFIFO(self) "usage: %s [-m mode] [-Z] [--context=MAC] [fifo]...\n", self
#define USAGE_MKNOD(self)                                           \
	"usage: %s [-m mode] [-Z] [--context=MAC] dev  c|b major minor\n" \
	"usage: %s [-m mode] [-Z] [--context=MAC] fifo p\n",              \
	    self, self


static_assert(std::is_same_v<mode_t, unsigned int>, "for the %o format");


enum class op_t : bool { mkfifo, mknod };
enum class type_t : std::uint8_t { chardev, blockdev, pipe };
static const constexpr mode_t type_modes[] = {S_IFCHR, S_IFBLK, S_IFIFO};


int main(int argc, char * const * argv) {
	auto type = type_t::pipe;

	op_t op;
	auto self = vore::basename(argv[0] ?: "(?)");
	if(self == "mkfifo")
		op = op_t::mkfifo;
	else if(self == "mknod")
		op = op_t::mknod;
	else {
		std::fprintf(stderr, USAGE_MKFIFO("mkfifo"));
		std::fprintf(stderr, USAGE_MKNOD("mknod"));
		return 2;
	}

	std::optional<mode_t> mode;
	std::optional<const char *> mac_ctx;
	for(auto && [arg, val] : vore::opt::get{argc,
	                                        argv,
	                                        op == op_t::mkfifo ? "m:Z" : "+m:Z",
	                                        {{"mode", required_argument, nullptr, 'm'},  //
	                                         {"context", optional_argument, nullptr, 'Z'}}})
		switch(arg) {
			case 'm':
				if(!(mode = vore::parse_mode(val, 0666, false)))
					return std::fprintf(stderr, "%s: -m %s: %s\n", argv[0], val, std::strerror(errno)), 1;
				if(*mode & ~static_cast<mode_t>(0777))
					return std::fprintf(stderr, "%s: -m %04o: non-permission bits set\n", argv[0], *mode), 1;
				break;
			case 'Z':
				mac_ctx = val;
				break;
			default:
				switch(op) {
					case op_t::mkfifo:
						return std::fprintf(stderr, USAGE_MKFIFO(argv[0])), 1;
					case op_t::mknod:
						return std::fprintf(stderr, USAGE_MKNOD(argv[0])), 1;
				}
		}

	dev_t majmin{};
	const char * filebuf[2] = {};
	vore::opt::args files{filebuf};
	switch(op) {
		case op_t::mkfifo:
			if(!*(argv + optind))
				return std::fprintf(stderr, USAGE_MKFIFO(argv[0])), 1;
			files = {argv + optind};
			break;
		case op_t::mknod: {
			*filebuf            = *(argv + optind);
			std::string_view tp = *filebuf ? *(argv + optind + 1) : "";
			if(!*filebuf || !(tp == "c" || tp == "u" || tp == "b" || tp == "p"))
				return std::fprintf(stderr, USAGE_MKNOD(argv[0])), 1;
			files = {filebuf};

			switch(tp[0]) {
				case 'c':
				case 'u':
					type = type_t::chardev;
					break;
				case 'b':
					type = type_t::blockdev;
					break;
				case 'p':
					type = type_t::pipe;
					break;
				default:
					__builtin_unreachable();
			}

			switch(type) {
				case type_t::chardev:
				case type_t::blockdev: {
					auto maj_s = *(argv + optind + 2);
					auto min_s = maj_s ? *(argv + optind + 3) : nullptr;
					if(!maj_s || !min_s || *(argv + optind + 4))
						return std::fprintf(stderr, USAGE_MKNOD(argv[0])), 1;

					unsigned int maj, min;
					for(auto [s, v] : std::initializer_list<std::pair<const char *, unsigned int *>>{{maj_s, &maj}, {min_s, &min}})
						if(!vore::parse_uint(s, *v))
							return std::fprintf(stderr, "%s: %s: %s\n", argv[0], s, std::strerror(errno)), 1;

					majmin = makedev(maj, min);
					break;
				}
				case type_t::pipe:
					if(*(argv + optind + 2))
						return std::fprintf(stderr, USAGE_MKNOD(argv[0])), 1;
					break;
			}
		}
	}


	if(mode)
		umask(0);

	if(mac_ctx && !is_selinux_enabled()) {
		if(*mac_ctx)
			std::fprintf(stderr, "%s: --context=%s: SELinux not available, ignoring\n", argv[0], *mac_ctx);
		mac_ctx = {};
	}

	selabel_handle * mac_lbl{};
	std::string_view cwd;
	if(mac_ctx) {
		errno = 0;
		if(*mac_ctx) {
			if(setfscreatecon(*mac_ctx) == -1)
				return std::fprintf(stderr, "%s: --context=%s: %s\n", argv[0], *mac_ctx, std::strerror(errno ?: ENOSYS)), 1;
		} else {
			if((mac_lbl = selabel_open(SELABEL_CTX_FILE, nullptr, 0))) {
				if(std::any_of(std::begin(files), std::end(files), [](auto f) { return f[0] == '/'; })) {
					if(auto c = getcwd(nullptr, 0); c)
						cwd = c;
					else {
						selabel_close(mac_lbl);
						mac_lbl = nullptr;
					}
				}
			}

			if(!mac_lbl)
				std::fprintf(stderr, "%s: -Z: %s, ignoring\n", argv[0], std::strerror(errno ?: ENOSYS));
		}
	}

	bool err{};
	for(auto file : files) {
		if(mac_lbl) {
			std::unique_ptr<char, vore::selinux::freecon_deleter> ctx;
			char * c;
			errno = 0;
			if(selabel_lookup(mac_lbl, &c, file[0] == '/' ? file : ((std::string{cwd} += "/") += file).c_str(), S_IFIFO) == -1)
				c = nullptr;
			else
				ctx.reset(c);
			if(setfscreatecon(c) == -1) {
				std::fprintf(stderr, "%s: %s: -Z %s: %s\n", argv[0], file, c, std::strerror(errno ?: ENOSYS));
				err = true;
				continue;
			}
		}

		if(mknod(file, mode.value_or(0666) | type_modes[static_cast<std::uint8_t>(type)], majmin) == -1) {
			std::fprintf(stderr, "%s: %s: %s\n", argv[0], file, std::strerror(errno));
			err = true;
		}
	}
	return err;
}

M cmd/stdbuf.cpp => cmd/stdbuf.cpp +1 -1
@@ 62,7 62,7 @@ int main(int argc, char * const * argv) {
				return 125;
		}
	}
	if(!argv[optind]) {
	if(!*(argv + optind)) {
		std::fprintf(stderr, USAGE, argv[0]);
		return 125;
	}

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


#pragma once


#include <cctype>
#include <optional>
#include <vore-numeric>


// these are bitmasks /and/ properly off-set multiply-masks
#define WHO_U 0100
#define WHO_G 0010
#define WHO_O 0001
#define WHO_A 0111


namespace vore {
	namespace {
		/// non-thread-safe: gets umask by temporarily resetting when needed
		std::optional<mode_t> parse_mode(const char * num, mode_t original, bool is_dir) {
			if(std::isdigit(num[0]) || ((num[0] == '+' || num[0] == '-' || num[0] == '=') && std::isdigit(num[1]))) {  // latter is GNU extension
				auto m = std::isdigit(num[0]) ? '=' : num[0];
				if(!std::isdigit(num[0]))
					++num;

				mode_t ret;
				if(parse_uint<mode_t, 8>(num, ret)) {
					switch(m) {
						case '+':
							return original | ret;
						case '-':
							return original & ~ret;
						case '=':
							return ret;
						default:
							__builtin_unreachable();
					}
				} else
					return {};
			}


			// symbolic_mode, as documented in POSIX.1-2017 utilities/chmod
			auto orig_umask = [um = std::optional<mode_t>{}]() mutable {
				if(!um) {
					um = umask(0);
					umask(*um);
				}
				return *um;
			};

			auto cur     = num;
			auto current = original;

			for(bool any = false;;) {
				std::optional<std::uint8_t> who_t;
				while(*cur == 'u' || *cur == 'g' || *cur == 'o' || *cur == 'a') {
					who_t = who_t.value_or(0) | (*cur == 'u'   ? WHO_U  //
					                             : *cur == 'g' ? WHO_G
					                             : *cur == 'o' ? WHO_O
					                             : *cur == 'a' ? WHO_A
					                                           : (__builtin_unreachable(), -1));
					++cur;
				}
				auto cur_who = who_t.value_or(WHO_A);

				for(;;) {  // actionlist
					auto op = *cur;
					if(!(op == '+' || op == '-' || op == '='))
						break;
					++cur;

					mode_t bits{}, specbits{};

					auto perm = *cur;
					if(perm == 'u' || perm == 'g' || perm == 'o') {  // copy
						++cur;
						bits = perm == 'u'   ? (current & 0700) >> 6
						       : perm == 'g' ? (current & 0070) >> 3
						       : perm == 'o' ? (current & 0007) >> 0
						                     : (__builtin_unreachable(), -1);
					} else
						while(perm == 'r' || perm == 'w' || perm == 'x' || perm == 'X' || perm == 's' || perm == 't') {  // list
							++cur;

							bits |= perm == 'r'   ? 04  //
							        : perm == 'w' ? 02
							        : perm == 'x' ? 01
							        : perm == 'X' ? ((is_dir || original & 0111) ? 01 : 0)
							                      : 0;
							if(perm == 's' && cur_who & WHO_U)
								specbits |= S_ISUID;
							if(perm == 's' && cur_who & WHO_G)
								specbits |= S_ISGID;
							if(perm == 't' && cur_who & WHO_O)
								specbits |= S_ISVTX;

							perm = *cur;
						}

					auto umask_mask = who_t ? static_cast<mode_t>(-1) : ~orig_umask();
					switch(op) {
						case '=':
							current &= ~(cur_who * 07);
							if(cur_who & WHO_U)
								current &= ~S_ISUID;
							if(cur_who & WHO_G)
								current &= ~S_ISGID;
							if(cur_who & WHO_O)  // GNU chmod bug??
								current &= ~S_ISVTX;
							[[fallthrough]];
						case '+':
							current |= (cur_who * bits) & umask_mask;
							current |= specbits;
							break;
						case '-':
							current &= ~((cur_who * bits) & umask_mask);
							current &= ~(specbits);
							break;
						default:
							__builtin_unreachable();
					}
					any = true;
				}

				if(!any) {
					errno = EINVAL;
					return {};
				}

				if(!*cur)
					break;
				else if(*cur == ',') {
					any = false;
					++cur;
				} else {
					errno = EINVAL;
					return {};
				}
			}

			return current;
		}
	}
}

/*
symbolic_mode : clause
              | symbolic_mode ',' clause

clause        : actionlist
              | wholist actionlist

wholist       : who
              | wholist who

who           : 'u' | 'g' | 'o' | 'a'

actionlist    : action
              | actionlist action

action        : op
              | op permlist
              | op permcopy

permcopy      : 'u' | 'g' | 'o'

op            : '+' | '-' | '='

permlist      : perm
              | perm permlist

perm          : 'r' | 'w' | 'x' | 'X' | 's' | 't'
*/

M include/vore-numeric => include/vore-numeric +2 -2
@@ 10,7 10,7 @@

namespace vore {
	namespace {
		template <class T>
		template <class T, int base = 0>
		bool parse_uint(const char * val, T & out) {
			if(val[0] == '\0') {
				errno = EINVAL;


@@ 23,7 23,7 @@ namespace vore {

			char * end{};
			errno = 0;
			out   = static_cast<T>(std::strtoull(val, &end, 0));
			out   = static_cast<T>(std::strtoull(val, &end, base));
			if(errno)
				return false;
			if(out > std::numeric_limits<T>::max()) {

M include/vore-optarg => include/vore-optarg +3 -0
@@ 37,6 37,9 @@ namespace vore::opt {
			constexpr args(const args &) noexcept = default;
			constexpr args(args &&) noexcept      = default;

			constexpr args & operator=(const args &) noexcept = default;
			constexpr args & operator=(args &&) noexcept      = default;

			constexpr iterator begin() const noexcept { return b; }
			constexpr iterator end() const noexcept { return e; }


M include/vore-selinux => include/vore-selinux +36 -0
@@ 4,7 4,15 @@
#pragma once

#if VOREUTILS_LIBSELINUX
#include <selinux/label.h>
#include <selinux/selinux.h>
#else
#define SELABEL_CTX_FILE 0
struct selabel_handle;
struct selinux_opt {
	int type;
	const char * value;
};
#endif




@@ 25,3 33,31 @@ template <class T = void>
static void freecon(char * context) {
	free(context);
}

template <class T = void>
static int setfscreatecon(const char *) {
	return -1;
}


template <class T = void>
static selabel_handle * selabel_open(unsigned int, const selinux_opt *, unsigned) {
	return nullptr;
}

template <class T = void>
static void selabel_close(struct selabel_handle *) {}

template <class T = void>
static int selabel_lookup(selabel_handle *, char **, const char *, int) {
	return -1;
}


namespace vore::selinux {
	namespace {
		struct freecon_deleter {
			void operator()(char * str) { freecon(str); }
		};
	}
}

M man/aliases => man/aliases +1 -0
@@ 5,3 5,4 @@ factor.1  factor.6
id.1      groups.1     whoami.1
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

M man/cksum.1 => man/cksum.1 +1 -1
@@ 56,5 56,5 @@ Created in
.St -p1003.2-92 ,
as
.Nm sum
implementations were irreconcilable, cf.
implementations were irreconcilable, cf.\&
.Xr sum 1 .

M man/expand.1 => man/expand.1 +22 -14
@@ 36,8 36,9 @@ except for the backspace, which has a width of -1 column.
When a tab is encountered, it's replaced by blanks equal in number to the distance to the next stop;
if it's beyond the final defined stop, it's replaced by a single blank.
.Pp
.Nm unexpand No performs the equivalent processing, but inverse operation ,
.No replacing runs of at least two blanks before each stop with a tab .
.Nm unexpand
performs the equivalent processing, but inverse operation,
replacing runs of at least two blanks before each stop with a tab.
If an explicit set of stops is specified, process input lines up to the final stop,
don't output tabs that would exceed that stop, and munge all extraneous spaces in the run thereupto.
.Pq Probably. The semantics here are very subtle and unintuitive, but compatible with Bx 4.4 Ns \&.


@@ 50,8 51,11 @@ Lay out stops at columns equal to integer multiples of
.
.It Fl t , -tabs Ns = Ns Ar stop , Ns Ar stop Ns Oo , Ns Ar stop Oc Ns …
Set stops at the specified columns.
.No Each Ar stop No must be further than the last .
.Ar stop Ns s can be separated by the comma, space, and tab characters.
Each
.Ar stop
must be further than the last .
.Ar stop Ns s
may be separated by the comma, space, and tab characters.
.
.It Fl i , -initial
Only expand tabs if they were preceded by just blanks and other tabs.


@@ 63,11 67,11 @@ Also coalesce blanks if they were preceded by characters other than tabs and oth
.Sh EXAMPLES
Align tab-separated data:
.Bd -literal -compact -offset 4n
.No $ Nm printf No '147.312$\et12$\et12\et2.3%%\en11 520$\et320$\et30\et20%%\en' | Nm expand
.Li $ Nm printf Li '147.312$\et12$\et12\et2.3%%\en11 520$\et320$\et30\et20%%\en' \&| Nm expand
147.312$        12$     12      2.3%
11 520$ 320$    30      20%

.No $ Nm printf No '147.312$\et12$\et12\et2.3%%\en11 520$\et320$\et30\et20%%\en' | Nm expand Fl t Ar 12
.Li $ Nm printf Li '147.312$\et12$\et12\et2.3%%\en11 520$\et320$\et30\et20%%\en' \&| Nm expand Fl t Ar 12
147.312$    12$         12          2.3%
11 520$     320$        30          20%
.Ed


@@ 75,12 79,12 @@ Align tab-separated data:
.Pp
Tabulate a simple form:
.Bd -literal -compact -offset 4n
.No $ {
.No > "  " Nm echo No Groceries for February :
.No > "  " Nm printf No '\etBananas\et3.5kg\et$4.51\en'
.No > "  " Nm printf No '\etKiwis\et2kg\et$3.19\etCall Siegfried to explain short!\en'
.No > "  " Nm printf No '\etBread\et\et$20.21\en'
.No > } | Nm Fl t Ar 4 , Ns Ar 15 , Ns Ar 25 , Ns Ar 35
.Li $ {
.Li > "  " Nm echo Li Groceries for February :
.Li > "  " Nm printf Li '\etBananas\et3.5kg\et$4.51\en'
.Li > "  " Nm printf Li '\etKiwis\et2kg\et$3.19\etCall Siegfried to explain short!\en'
.Li > "  " Nm printf Li '\etBread\et\et$20.21\en'
.Li > } \&| Nm Fl t Ar 4 , Ns Ar 15 , Ns Ar 25 , Ns Ar 35
Groceries for February:
    Bananas    3.5kg     $4.51
    Kiwis      2kg       $3.19     Call Siegfried to explain short!


@@ 88,6 92,10 @@ Groceries for February:
.Ed
.
.Sh STANDARDS
.No Appeared in Bx 1 . This version is compatible with Bx 4.4 .
Appeared in
.Bx 1 .
This version is compatible with
.Bx 4.4 .
.Pp
.Fl i No is an extension, also present on the GNU system .
.Fl i
is an extension, also present on the GNU system .

M man/factor.1 => man/factor.1 +3 -3
@@ 10,7 10,7 @@
.Sh SYNOPSIS
.Nm
.Ar number Ns …
.Nm cat Ar numbers | Nm
.Nm cat Ar numbers \&| Nm
.
.Sh DESCRIPTION
Prints factors of all


@@ 36,7 36,7 @@ wasn't actually, or was out of range.
1410: 2 3 5 47
1981: 7 283

.Li $ Nm tr Fl cd Li '0-9\en \et' < /dev/urandom | Nm dd Li bs=60 count=1 | Nm
.Li $ Nm tr Fl cd Li '0-9\en \et' < /dev/urandom \&| Nm dd Li bs=60 count=1 \&| Nm
11: 11
41615: 5 7 29 41
0:


@@ 56,7 56,7 @@ first appeared in
.At v7
as
.Xr factor 1 :
.Dl Sy factor No [\ number\ ]
.Dl Sy factor Li "[ number ]"
.Pp
A direct C port appeared in
.Bx 4.1

A man/mkfifo.1 => man/mkfifo.1 +117 -0
@@ 0,0 1,117 @@
.\" SPDX-License-Identifier: 0BSD
.\"
.Dd
.Dt MKFIFO 1
.Os
.
.Sh NAME
.Nm mkfifo
.Nd create named pipe
.Sh SYNOPSIS
.Nm
.Op Fl m Ar mode
.Op Fl Z
.Op Fl -context Ns = Ns Ar MAC
.Ar fifo Ns …
.Nm mknod
.Op Fl m Ar mode
.Op Fl Z
.Op Fl -context Ns = Ns Ar MAC
.Ar dev
.Sy c Ns | Ns Sy b
.Ar major
.Ar minor
.Nm mknod
.Op Fl m Ar mode
.Op Fl Z
.Op Fl -context Ns = Ns Ar MAC
.Ar fifo
.Sy p
.
.Sh DESCRIPTION
Creates named named pipes (FIFOs), or
.Ar major Ns Li \&: Ns Ar minor
device nodes \(em character with
.Sy c ,
block with
.Sy b .
.
.Sh OPTIONS
.Bl -tag -compact -width "-m, --mode=mode"
.It Fl m , -mode Ns = Ns Ar mode
.Xr chmod 1 Ns -style
file permissions to create
.Ar fifo Ns s Pq Ar dev
as, rather than the default
.Li a=rw
\-
.Va umask .
.It Fl Z , -context
Create
.Ar fifo Ns s Pq Ar dev
with the default SELinux contexts for their paths.
Ignored without SELinux.
.It Fl -context Ns = Ns Ar MAC
Create
.Ar fifo Ns s Pq Ar dev
with SELinux context set to
.Ar MAC .
Ignored (diagnostic issued) without SELinux.
.El
.
.Sh EXIT STATUS
.Sy 1
if a file couldn't be created,
.
.Sh SEE ALSO
.Xr mkfifo 2 ,
.Xr mknod 2 ,
.Xr fifo 7
.
.Sh STANDARDS
.Nm
conforms to
.St -p1003.1-2008 .
.Fl -context
is an extension, also present on the GNU system.
.Pp
.Nm mknod Sy c Ns | Ns Sy b
is compatible with
.At v4 ;
.Nm mknod Sy p
with
.At V .
.Sy u
is accepted as an alias for
.Sy c ,
for compatibility with the GNU system; avoid it.
.
.Sh HISTORY
.\" Carefully copied from V4 manpage
.Nm mknod
appeared, fully formed, in
.At v4
as
.Xr mknod VIII :
.D1 Sy /etc/mknod No name \&[ Sy c No ]\& \&[ Sy b No ]\& major minor
Alongside the corresponding
.Xr mknod II
syscall.
.Pp
It gained the
.Sy p
usage in
.At V ,
alongside the inclusion of named pipes.
.Pp
.Nm
was invented by
.St -p1003.2-92 ,
including
.Fl m
for symmetry with
.Xr mkdir 1 ;
it appears in
.Bx 4.3 Reno
and
.At V.3 . \" TODO: verify this! im waiting on vetus cooldown

M man/paste.1 => man/paste.1 +2 -2
@@ 66,7 66,7 @@ couldn't be opened.
.Sh EXAMPLES
List directory in four columns:
.Bd -literal -compact -offset Ds
.Li $ Nm ls | Nm Li - - - -
.Li $ Nm ls \&| Nm Li - - - -
a       a.c     a.cpp   a.out
fips151-1.pdf   paste   paste.1 paste.cpp
paste.o paste.ps


@@ 74,7 74,7 @@ paste.o paste.ps
.Pp
Combine pairs of lines:
.Bd -literal -compact -offset Ds
.Li $ Nm seq Ar 10 | Nm Fl sd Ar '\et\en' Li -
.Li $ Nm seq Ar 10 \&| Nm Fl sd Ar '\et\en' Li -
1       2
3       4
5       6

M man/sha1sum.1 => man/sha1sum.1 +11 -11
@@ 134,7 134,7 @@ With
.Fl b ,
an asterisk replaces the second space:
.Bd -literal -compact -offset Ds
.Li $ Nm echo Qo POSIX.2 Qc Sy \&| Nm sha1sum Fl b
.Li $ Nm echo Qo POSIX.2 Qc | Nm sha1sum Fl b
.Li 33210013f52127d6ada425601f16bbb62e85f3be Sy "*" Ns Ar -
.Ed
.Pp


@@ 142,13 142,13 @@ With
.Fl -tag ,
the output is in the form
.Bd -literal -compact -offset Ds
.Li $ Nm echo Qo POSIX.2 Qc Sy \&| Nm sha1sum Fl -tag Li LICENCE -
.Li $ Nm echo Qo POSIX.2 Qc | Nm sha1sum Fl -tag Li LICENCE -
.Sy SHA1 Ns Li \&( Ns Ar LICENCE Ns Li )\& Sy = Li bd25664d6e803060dcb31bfdd9642ba9d8a3f1b9
.Sy SHA1 Ns Li \&( Ns Ar - Ns Li )\& Sy = Li 33210013f52127d6ada425601f16bbb62e85f3be
.Ed
A dash and width in bits is appended for non-default digest lengths:
.Bd -literal -compact -offset Ds
.Li $ Nm echo Qo POSIX.2 Qc Sy \&| Nm b2sum Fl -tag l Ar 96
.Li $ Nm echo Qo POSIX.2 Qc | Nm b2sum Fl -tag l Ar 96
.Sy BLAKE2b- Ns Ar 96 Ns Li \&( Ns Ar - Ns Li )\& Sy = Li 386b90bea2a1e566249cdb96
.Ed
.Pp


@@ 224,12 224,12 @@ all output, except for diagnostics, is suppressed.
.Pp
Consider the following shell transcript:
.Bd -literal -compact -offset Ds
.Li $ Nm echo Qo POSIX.2 Qc Sy \&| Nm sha1sum Li LICENCE - \&"$(printf 'UNIX\enregistered')" Sy \&| Nm tee Ar sum
.Li $ Nm echo Qo POSIX.2 Qc | Nm sha1sum Li LICENCE - \&"$(printf 'UNIX\enregistered')" | Nm tee Ar sum
.Li 22eb73bd30d47540a4e05781f0f6e07640857cae Sy " " Ns Ar LICENCE
.Li 33210013f52127d6ada425601f16bbb62e85f3be Sy " " Ns Ar -
.Sy \e Ns Li 7390a4a0bfb7c6da55d6f5f3db4e42c534271363 Sy " " Ns Ar UNIX\enregistered

.Li $ Nm echo Qo POSIX.2 Qc Sy \&| Nm sha1sum Fl c Ar sum Ns Sy \&; Nm echo Li $?
.Li $ Nm echo Qo POSIX.2 Qc | Nm sha1sum Fl c Ar sum Ns Sy \&; Nm echo Li $?
.Li LICENCE : Sy OK
.Li - : Sy OK
.Li UNIX\enregistered : Sy OK


@@ 237,7 237,7 @@ Consider the following shell transcript:

.Li $ Nm rm Ar UNIX* LICENCE
.Li $ Nm ln Fl s Ar LICENCE LICENCE
.Li $ Nm echo Qo POSIX.2 Qc Sy \&| Nm sha1sum Fl c Ar sum Ns Sy \&; Nm echo Li $?
.Li $ Nm echo Qo POSIX.2 Qc | Nm sha1sum Fl c Ar sum Ns Sy \&; Nm echo Li $?
.Nm sha1sum : Ar sum : Li LICENCE: Too many levels of symbolic links
.Li LICENCE : Sy FAILED (I/O)
.Li - : Sy OK


@@ 246,7 246,7 @@ Consider the following shell transcript:
.Nm sha1sum : Ar sum : Li 1/3 files OK (2 missing, 0 bad lines)
1

.Li $ Nm echo Qo POSIX.2 Qc Sy \&| Nm sha1sum Fl c -ignore-missing Ar sum Ns Sy \&; Nm echo Li $?
.Li $ Nm echo Qo POSIX.2 Qc | Nm sha1sum Fl c -ignore-missing Ar sum Ns Sy \&; Nm echo Li $?
.Nm sha1sum : Ar sum : Li LICENCE: Too many levels of symbolic links
.Li LICENCE : Sy FAILED (I/O)
.Li - : Sy OK


@@ 254,21 254,21 @@ Consider the following shell transcript:
1

.Li $ Nm sed Fl i Ns Ar B Li 's/22eb73bd/22eb73bd /' Ar sum
.Li $ Nm echo Qo POSIX.2 Qc Sy \&| Nm sha1sum Fl c -ignore-missing Ar sum Ns Sy \&; Nm echo Li $?
.Li $ Nm echo Qo POSIX.2 Qc | Nm sha1sum Fl c -ignore-missing Ar sum Ns Sy \&; Nm echo Li $?
.Li - : Sy OK
.Nm sha1sum : Ar sum : Li 2/2 files OK (1 missing, 1 bad lines)
0

.Li $ Nm echo Qo POSIX.2 Qc Sy \&| Nm sha1sum Fl c -ignore-missing -quiet Ar sum Ns Sy \&; Nm echo Li $?
.Li $ Nm echo Qo POSIX.2 Qc | Nm sha1sum Fl c -ignore-missing -quiet Ar sum Ns Sy \&; Nm echo Li $?
.Nm sha1sum : Ar sum : Li 2/2 files OK (1 missing, 1 bad lines)
0

.Li $ Nm echo Qo POSIX.2 Qc Sy \&| Nm sha1sum Fl cw -ignore-missing -quiet Ar sum Ns Sy \&; Nm echo Li $?
.Li $ Nm echo Qo POSIX.2 Qc | Nm sha1sum Fl cw -ignore-missing -quiet Ar sum Ns Sy \&; Nm echo Li $?
.Nm sha1sum : Ar sum : Li 1: invalid line
.Nm sha1sum : Ar sum : Li 2/2 files OK (1 missing, 1 bad lines)
0

.Li $ Nm echo Qo POSIX.2 Qc Sy \&| Nm sha1sum Fl cw -ignore-missing -quiet -strict Ar sum Ns Sy \&; Nm echo Li $?
.Li $ Nm echo Qo POSIX.2 Qc | Nm sha1sum Fl cw -ignore-missing -quiet -strict Ar sum Ns Sy \&; Nm echo Li $?
.Nm sha1sum : Ar sum : Li 1: invalid line
.Nm sha1sum : Ar sum : Li 2/2 files OK (1 missing, 1 bad lines)
1

M man/stdbuf.1 => man/stdbuf.1 +2 -2
@@ 81,7 81,7 @@ would buffer incoming CIFS connections because the output leads into a pipe.
By employing
.Nm ,
each connection is printed as soon as it lands in the log:
.Dl Nm tail Fl f Pa /var/log/messages | Nm Fl o Ns Ar L Nm grep Li 'DPT=445' | Nm cat Fl n
.Dl Nm tail Fl f Pa /var/log/messages \&| Nm Fl o Ns Ar L Nm grep Li 'DPT=445' \&| Nm cat Fl n
.
.Sh SEE ALSO
.Xr libstdbuf 3


@@ 112,5 112,5 @@ includes a similar facility natively in its
as
.Ev STDBUF Ns Oo Ar n Oc Ns = Ns Sy U Ns | Ns Sy L Ns | Ns Sy F Ns Op Ar size ;
the example above could be re-written as
.Dl Nm tail Fl f Pa /var/log/messages | Ev STDBUF Ns Ar 1 Ns = Ns Ar L Nm grep Li 'DPT=445' | Nm cat Fl n
.Dl Nm tail Fl f Pa /var/log/messages \&| Ev STDBUF Ns Ar 1 Ns = Ns Ar L Nm grep Li 'DPT=445' \&| Nm cat Fl n
.\" TODO: actually do this on NetBSD?

M man/timeout.1 => man/timeout.1 +1 -1
@@ 214,7 214,7 @@ Limit a command to a second of run-time:
.Pp
Emulate pipe shutter after a half-minute:
.Bd -literal -compact -offset Ds
.No $ Nm Fl vfs Ns Dv PIPE Ar 0.5m Nm yes | Nm wc
.No $ Nm Fl vfs Ns Dv PIPE Ar 0.5m Nm yes \&| Nm wc
timeout: yes (14539): sending Broken pipe
568057856 568057856 1136115712
.Ed

M man/tsort.1 => man/tsort.1 +2 -2
@@ 83,7 83,7 @@ relationship, an
.Li A \[->] B \[->] D \[->] A
loop is created:
.Bd -literal -compact -offset Ds
.Li $ Nm echo Li A B A F B C B D D E D A Sy \&| Nm
.Li $ Nm echo Li A B A F B C B D D E D A | Nm
.Nm Ns Li : -: breaking D -> A link
A
F


@@ 155,7 155,7 @@ which noted:
.Pp
This brash one-liner intends to build a new library
from existing `.o' files.
.D1 ar cr library \`\|lorder *.o | tsort\`
.D1 "ar cr library \`\|lorder *.o | tsort\`"
.Pp
It was was imported into
.At v7 ,

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

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

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

ln -fs "$mkfifo" "${tmpdir}mkfifo"
ln -fs "$mkfifo" "${tmpdir}mknod"
ln -fs "$mkfifo" "${tmpdir}something-else"
ln -fs "$mkfifo" "${tmpdir}$altname"

"${tmpdir}something-else" 2>"${tmpdir}err"; err=$?
[ $err -eq 2 ]        || echo "mkfifo: got $err instead of 2 for something-else" >&3
[ -s "${tmpdir}err" ] || echo "mkfifo: stderr empty for something-else" >&3
"${tmpdir}$altname" 2>"${tmpdir}err"; err=$?
[ $err -eq 2 ]        || echo "mkfifo: got $err instead of 2 for $altname" >&3
[ -s "${tmpdir}err" ] || echo "mkfifo: stderr empty for $altname" >&3

echo > "${tmpdir}pa"
"${tmpdir}mkfifo" "${tmpdir}pa" "${tmpdir}pb" >&3 2>"${tmpdir}err" && echo "mkfifo: pa pb ok?" >&3
[ -s "${tmpdir}err" ]                                              || echo "mkfifo: pa pb empty stderr?" >&3
[ "$(ls -l "${tmpdir}pa" | cut -f1 -d' ')" = "-rw-r--r--" ]        || echo "mkfifo: pa: wrong mode" >&3
[ "$(ls -l "${tmpdir}pb" | cut -f1 -d' ')" = "prw-r--r--" ]        || echo "mkfifo: pb: wrong mode" >&3


mknodp() {
	"${tmpdir}mknod" "$@" p
}

for mkf in "${tmpdir}mkfifo" mknodp; do
	rm -f "${tmpdir}pa" "${tmpdir}pb" "${tmpdir}pc"

	"$mkf" "${tmpdir}pa" >&3 2>&1                               || echo "mkfifo: $mkf: pa failed" >&3
	[ "$(ls -l "${tmpdir}pa" | cut -f1 -d' ')" = "prw-r--r--" ] || echo "mkfifo: $mkf: pa: wrong mode" >&3

	"$mkf" -mu-w,a=u,+s "${tmpdir}pc" >&3 2>"${tmpdir}err" && echo "mkfifo: $mkf: -mu-w,a=u,+s ok?" >&3
	[ -s "${tmpdir}err" ]                                  || echo "mkfifo: $mkf: -mu-w,a=u,+s empty stderr?" >&3
	ls -l "${tmpdir}pc" > /dev/null 2>&1                   && echo "mkfifo: $mkf: -mu-w,a=u,+s made something?" >&3

	"$mkf" -mu-w,o=u "${tmpdir}pc" >&3 2>&1                     || echo "mkfifo: $mkf: -mu-w,o=u failed" >&3
	[ "$(ls -l "${tmpdir}pc" | cut -f1 -d' ')" = "pr--rw-r--" ] || echo "mkfifo: $mkf: -mu-w,o=u: wrong mode" >&3
done


rm -rf "$tmpdir" >&3