~nabijaczleweli/voreutils

0aed7b704e5c9070cffc866d1092d130c66f9622 — наб 16 days ago a4b85bc
Add df. Tentatively builds on Illumos (Tribblix)
M Makefile => Makefile +8 -4
@@ 37,6 37,10 @@ else ifeq "$(SYSTEM)" "Darwin"
	SO := .dylib
else ifeq "$(SYSTEM)" "Linux"
	CXXSPECIFICCC += -D_FILE_OFFSET_BITS=64
else ifeq "$(SYSTEM)" "SunOS"
	AS_NEEDED :=
	CXXSPECIFICCC += $(shell getconf LFS_CFLAGS)
	CXXSPECIFICLD += $(shell getconf LFS_LDFLAGS) $(shell getconf LFS_LIBS)
else
	#
endif


@@ 51,16 55,16 @@ ifneq "$(findstring Apple,$(CXXVER))" ""
endif
	CXXSPECIFICCC += -Wno-gnu-conditional-omitted-operand
	ifeq "$(LTO)" "y"
		CXXSPECIFICLD := -flto=full  # Clang produces .o files with LLVM bitcode, which cannot be linked to if put in .as
		CXXSPECIFICLD += -flto=full  # Clang produces .o files with LLVM bitcode, which cannot be linked to if put in .as
	else
		CXXSPECIFICLD :=
		CXXSPECIFICLD +=
	endif
else
	CXXSPECIFICCC := -Wno-missing-field-initializers -fno-common
	CXXSPECIFICCC += -Wno-missing-field-initializers -fno-common
	ifeq "$(LTO)" "y"
		CXXSPECIFICCC += -flto
	endif
	CXXSPECIFICLD :=
	CXXSPECIFICLD +=
endif

ifeq "$(SYMLINK)" "y"

M README.md => README.md +4 -2
@@ 11,13 11,13 @@ GNU coreutils provide the following 105 binaries, according to `dpkg -L coreutil
  * ☐ /bin/cp
  * ☐ /bin/date
  * ☐ /bin/dd
  * ☐ /bin/df
  * ☑ /bin/df
  * ☐ /bin/dir
  * ☑ /bin/echo – `-n` only
  * ☑ /bin/false
  * ☐ /bin/ln
  * ☐ /bin/ls
  * ☐ /bin/mkdir
  * ☑ /bin/mkdir
  * ☑ /bin/mknod
  * ☐ /bin/mktemp
  * ☐ /bin/mv


@@ 118,6 118,8 @@ TODO: should fread_unlocked(sequential) where appropriate maybe?

TODO: support SMACK in addition to SELinux

TODO: filesize.js-style (`include/vore-human`) sizing might be a bit suboptimal for this (`df`) sort of display?

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


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


#include <algorithm>
#include <cstdio>
#include <cstring>
#include <errno.h>
#include <fcntl.h>
#include <initializer_list>
#include <map>
#if __linux__
#include <mntent.h>
#else
#include <sys/mntent.h>
#endif
#include <optional>
#include <string>
#include <string_view>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/types.h>
#include <unistd.h>
#include <utility>
#include <vector>
#include <vore-file>
#include <vore-getopt>
#include <vore-human>
#include <vore-numeric>
#include <vore-optarg>
#include <vore-path>
#include <vore-print>
#include <vore-size>
#include <vore-token>


#define USAGE(self) \
	"usage: %s [-PklaiThH] [-t only-type]... [-x not-type]... [-B blocksize] [--output[=col[,col]...]] [--[no-]sync] [--total] [path|device]...\n", self


#if __sun
#define MTAB "/etc/mnttab"
#else
#define MTAB "/etc/mtab"
#endif


enum class align_t : bool { left, right };
enum class field_t : std::uint8_t { source, fstype, itotal, iused, iavail, ipcent, size, used, avail, pcent, file, target };
static const constexpr std::string_view field_s[]{"source", "fstype", "itotal", "iused", "iavail", "ipcent",
                                                  "size",   "used",   "avail",  "pcent", "file",   "target"};
static const constexpr align_t field_a[]{align_t::left,  align_t::left,  align_t::right, align_t::right, align_t::right, align_t::right,
                                         align_t::right, align_t::right, align_t::right, align_t::right, align_t::left,  align_t::left};

enum class human_size_t : std::uint8_t { human_1024, si_1000 };


int main(int argc, char * const * argv) {
	std::optional<std::uint64_t> block_size_o;
	if(auto dbs = std::getenv("DF_BLOCK_SIZE"); dbs && (block_size_o = vore::parse_size<std::uint64_t>(argv[0], dbs, false)) && *block_size_o)
		;
	else if(auto bs = std::getenv("BLOCK_SIZE"); bs && (block_size_o = vore::parse_size<std::uint64_t>(argv[0], bs, false)) && *block_size_o)
		;
	else if(auto bs = std::getenv("BLOCKSIZE"); bs && (block_size_o = vore::parse_size<std::uint64_t>(argv[0], bs, false)) && *block_size_o)
		;
	auto block_size = block_size_o.value_or(1024);

	block_size_o = {};
	bool strict_format{}, do_sync{}, local_only{}, inject_type{}, default_filters = true, do_total{};
	std::vector<field_t> fields{field_t::source, field_t::size, field_t::used, field_t::avail, field_t::pcent, field_t::target};
	std::optional<std::size_t> type_injection_pos = 1;
	std::vector<std::string_view> exclusive_filters, negative_filters;
	std::optional<vore::human_size_t> human_size;
	for(auto && [arg, val] : vore::opt::get{argc,
	                                        argv,
	                                        "PiTlB:kat:x:vHh",
	                                        {{"portability", no_argument, nullptr, 'P'},
	                                         {"sync", no_argument, nullptr, 's'},
	                                         {"no-sync", no_argument, nullptr, 'S'},
	                                         {"output", optional_argument, nullptr, 'o'},
	                                         {"inodes", no_argument, nullptr, 'i'},
	                                         {"print-type", no_argument, nullptr, 'T'},
	                                         {"local", no_argument, nullptr, 'l'},
	                                         {"block-size", required_argument, nullptr, 'B'},
	                                         {"all", no_argument, nullptr, 'a'},
	                                         {"type", required_argument, nullptr, 't'},
	                                         {"exclude-type", required_argument, nullptr, 'x'},
	                                         {"si", no_argument, nullptr, 'H'},
	                                         {"human-readable", no_argument, nullptr, 'h'},
	                                         {"total", no_argument, nullptr, 'Z'}}})
		switch(arg) {
			case 'P':
				strict_format = true;
				break;
			case 's':  // sync
				do_sync = true;
				break;
			case 'S':  // no-sync
				do_sync = false;
				break;
			case 'o':  // output
				if(val) {
					if(type_injection_pos)
						fields.clear();
					std::optional<std::string_view> err;
					vore::soft_tokenise<false> val_toks{val, ","};
					std::transform(std::begin(val_toks), std::end(val_toks), std::back_inserter(fields), [&](std::string_view tok) {
						if(auto fs = std::find(std::begin(field_s), std::end(field_s), tok); fs != std::end(field_s))
							return static_cast<field_t>(fs - std::begin(field_s));
						else {
							if(!err)
								err = tok;
							return field_t::source;
						}
					});
					if(err)
						return std::fprintf(stderr, "%s: -o %.*s: unknown\n", argv[0], (int)err->size(), err->data()), 1;
				} else
					fields = {field_t::source, field_t::fstype, field_t::itotal, field_t::iused, field_t::iavail, field_t::ipcent,
					          field_t::size,   field_t::used,   field_t::avail,  field_t::pcent, field_t::file,   field_t::target};
				type_injection_pos = {};
				break;
			case 'i':
				fields             = {field_t::source, field_t::itotal, field_t::iused, field_t::iavail, field_t::ipcent, field_t::target};
				type_injection_pos = 1;
				break;
			case 'T':
				inject_type = true;
				break;
			case 'l':
				local_only = true;
				break;
			case 'B': {
				auto s = vore::parse_size<std::uint64_t>(argv[0], val);
				if(!s)
					return 1;
				if(!*s)
					return std::fprintf(stderr, "%s: %s: %s\n", argv[0], val, std::strerror(ERANGE)), 1;
				block_size_o = *s;
			} break;
			case 'k':
				block_size_o = 1024;
				break;
			case 'a':
				default_filters = false;
				break;
			case 't':
				if(auto itr = std::lower_bound(exclusive_filters.begin(), exclusive_filters.end(), val); itr == exclusive_filters.end() || val < *itr)
					exclusive_filters.insert(itr, val);
				break;
			case 'x':
				if(auto itr = std::lower_bound(negative_filters.begin(), negative_filters.end(), val); itr == negative_filters.end() || val < *itr)
					negative_filters.insert(itr, val);
				break;
			case 'H':
				human_size = vore::human_size_t::si_1000;
				break;
			case 'h':
				human_size = vore::human_size_t::human_1024;
				break;
			case 'Z':  //--total
				do_total = true;
				break;
			case 'v':
				break;
			default:
				return std::fprintf(stderr, USAGE(argv[0])), 1;
		}
	block_size = block_size_o.value_or(strict_format ? 512 : block_size);

	if(inject_type) {
		if(!type_injection_pos)
			return std::fprintf(stderr, "%s: -T excludes --output\n", argv[0]), 1;
		fields.insert(std::begin(fields) + *type_injection_pos, field_t::fstype);
	}

	if(do_sync)
		sync();

	struct entry {
		std::string source, target, type;
	};
	std::vector<entry> mtab_entries;

#if __linux__ || __sun
	{
		vore::file::FILE<false> mtab{MTAB, "re"};
		if(!mtab)
			return std::fprintf(stderr, "%s: %s: %s", argv[0], MTAB, std::strerror(errno));
		while(auto me = getmntent(mtab))
			mtab_entries.emplace_back(entry {
#if __sun
				me->mnt_special, me->mnt_mountp, me->mnt_fstype
#else
			    me->mnt_fsname, me->mnt_dir, me->mnt_type
#endif
			});
	}
#else
	{
		struct statvfs * entries{};
		auto len = getmntinfo(&entries, ST_WAIT);
		if(!len)
			return std::fprintf(stderr, "%s: %s", argv[0], std::strerror(errno));

		for(auto vfs : vore::span{entries, entries + len})
			mtab_entries.emplace_back(entry{vfs->f_mntfromname, vfs->f_mntonname, vfs->f_fstypename});
	}
#endif


	std::vector<std::vector<std::variant<std::string, std::string_view>>> output_lines;
	{
		std::vector<std::variant<std::string, std::string_view>> f_s;
		char buf[128];
		for(auto f : fields)
			switch(f) {
				case field_t::source:
					f_s.emplace_back(std::string_view{"Filesystem"});
					break;
				case field_t::fstype:
					f_s.emplace_back(std::string_view{"Type"});
					break;
				case field_t::itotal:
					f_s.emplace_back(std::string_view{"i-nodes"});
					break;
				case field_t::iused:
					f_s.emplace_back(std::string_view{"iUsed"});
					break;
				case field_t::iavail:
					f_s.emplace_back(std::string_view{"iFree"});
					break;
				case field_t::ipcent:
					f_s.emplace_back(std::string_view{"iUse%"});
					break;
				case field_t::size:
					if(human_size)
						f_s.emplace_back(std::string_view{"Size"});
					else {
						if(strict_format) {
							std::snprintf(buf, sizeof(buf), "%" PRIu64 "-blocks", block_size);
							f_s.emplace_back(std::string{buf});
						} else {
							vore::human_size(buf, sizeof(buf), block_size, vore::human_size_t::human_1024);
							f_s.emplace_back(std::move(std::string{buf} += "-blocks"));
						}
					}
					break;
				case field_t::used:
					f_s.emplace_back(std::string_view{"Used"});
					break;
				case field_t::avail:
					f_s.emplace_back(std::string_view{strict_format ? "Available" : "Avail"});
					break;
				case field_t::pcent:
					f_s.emplace_back(std::string_view{strict_format ? "Capacity" : "Use%"});
					break;
				case field_t::file:
					f_s.emplace_back(std::string_view{"File"});
					break;
				case field_t::target:
					f_s.emplace_back(std::string_view{"Mounted on"});
					break;
			}
		output_lines.emplace_back(std::move(f_s));
	}


	auto apply_filters = [&](auto && ent) {
		if(std::binary_search(std::begin(negative_filters), std::end(negative_filters), ent.type))
			return false;
		if(!exclusive_filters.empty() && !std::binary_search(std::begin(exclusive_filters), std::end(exclusive_filters), ent.type))
			return false;
		return true;
	};


	struct statvfs totals_vfs {
		.f_frsize = 1
	};
	auto add_fs = [&](auto && ent, auto && vfs, auto ent_err, auto fname, auto total) {
		if(local_only &&
		   (ent.source.find(':') != std::string::npos || ent.type == "cifs" || ent.type == "afs"))  // TODO?: NetBSD has a local flag (and ignore) in its statvfs
			return;

		if(total) {
			totals_vfs.f_blocks += vfs.f_blocks * vfs.f_frsize;
			totals_vfs.f_bfree += vfs.f_bfree * vfs.f_frsize;
			totals_vfs.f_bavail += vfs.f_bavail * vfs.f_frsize;
			totals_vfs.f_files += vfs.f_files;
			totals_vfs.f_favail += vfs.f_favail;
			totals_vfs.f_ffree += vfs.f_ffree;
		}

		char buf[128];
		std::vector<std::variant<std::string, std::string_view>> f_s;
		f_s.reserve(fields.size());
		for(auto f : fields) {
			switch(f) {
				case field_t::source:
					f_s.emplace_back(std::string_view{ent.source});
					continue;
				case field_t::fstype:
					f_s.emplace_back(std::string_view{ent.type});
					continue;
				case field_t::itotal:
					if(human_size)
						vore::human_size(buf, sizeof(buf), vfs.f_files, *human_size);
					else
						std::snprintf(buf, sizeof(buf), "%" PRIu64 "", static_cast<std::uint64_t>(vfs.f_files));
					break;
				case field_t::iused:
					if(human_size)
						vore::human_size(buf, sizeof(buf), vfs.f_files - vfs.f_ffree, *human_size);
					else
						std::snprintf(buf, sizeof(buf), "%" PRId64 "", static_cast<std::int64_t>(vfs.f_files) - static_cast<std::int64_t>(vfs.f_ffree));
					break;
				case field_t::iavail:
					if(human_size)
						vore::human_size(buf, sizeof(buf), vfs.f_favail, *human_size);
					else
						std::snprintf(buf, sizeof(buf), "%" PRIu64 "", static_cast<std::uint64_t>(vfs.f_favail));
					break;
				case field_t::ipcent:
					if(vfs.f_files == vfs.f_ffree && vfs.f_favail == 0) {
						f_s.emplace_back(std::string_view{"-"});
						continue;
					} else {
						auto used = static_cast<double>(vfs.f_files) - static_cast<double>(vfs.f_ffree);
						std::snprintf(buf, sizeof(buf), "%g%%", std::ceil((used / (used + static_cast<double>(vfs.f_favail))) * 100.));
						break;
					}
				case field_t::size: {
					auto size = static_cast<std::uint64_t>(vfs.f_blocks) * static_cast<std::uint64_t>(vfs.f_frsize);
					if(human_size)
						vore::human_size(buf, sizeof(buf), size, *human_size);
					else
						std::snprintf(buf, sizeof(buf), "%" PRIu64 "", (size + (block_size - 1)) / block_size);
				} break;
				case field_t::used: {
					auto used = (static_cast<std::int64_t>(vfs.f_blocks) - static_cast<std::int64_t>(vfs.f_bfree)) * static_cast<std::uint64_t>(vfs.f_frsize);
					if(human_size)
						vore::human_size(buf, sizeof(buf), used, *human_size);
					else
						std::snprintf(buf, sizeof(buf), "%" PRId64 "", (used + (block_size - 1)) / block_size);
				} break;
				case field_t::avail: {
					auto avail = static_cast<std::uint64_t>(vfs.f_bavail) * static_cast<std::uint64_t>(vfs.f_frsize);
					if(human_size)
						vore::human_size(buf, sizeof(buf), avail, *human_size);
					else
						std::snprintf(buf, sizeof(buf), "%" PRIu64 "", (avail + (block_size - 1)) / block_size);
				} break;
				case field_t::pcent:
					if(vfs.f_blocks == vfs.f_bfree && vfs.f_bavail == 0) {
						f_s.emplace_back(std::string_view{"-"});
						continue;
					} else {
						auto used = static_cast<double>(vfs.f_blocks) - static_cast<double>(vfs.f_bfree);
						std::snprintf(buf, sizeof(buf), "%g%%", std::ceil((used / (used + static_cast<double>(vfs.f_bavail))) * 100.));
						break;
					}
				case field_t::file:
					f_s.emplace_back(std::string_view{fname});
					continue;
				case field_t::target:
					f_s.emplace_back(std::string_view{ent.target});
					continue;
			}
			f_s.emplace_back(std::string{ent_err ? "-" : buf});
		}
		output_lines.emplace_back(std::move(f_s));
	};


	bool arg_err{};
	if(*(argv + optind)) {
		// AFAICT there's no way to get the device class from statvfs, but I don't think there are systems that allow both char- and blockdevs
		// TODO: this could probably be rewritten to optimise latency in wake of single file lookups instead of bulk ones, but
		std::map<dev_t, const entry *> target_statmap, source_statmap;
		for(auto && ent : mtab_entries) {
			struct stat sb;
			if(lstat(ent.target.c_str(), &sb) == 0)
				target_statmap[sb.st_dev] = &ent;
			if(ent.source[0] == '/' && stat(ent.source.c_str(), &sb) == 0 && (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)))
				source_statmap[sb.st_rdev] = &ent;
		}

		std::vector<dev_t> devices;
		for(auto file : vore::opt::args{argv + optind}) {
			struct statvfs vfs;
			int err;
			while((err = statvfs(file, &vfs)) == -1 && errno == EINTR)
				;
			if(err == -1)
				goto err;

			struct stat file_sb;
			if(stat(file, &file_sb) == -1)
				goto err;

			if(S_ISBLK(file_sb.st_mode) || S_ISCHR(file_sb.st_mode))
				if(auto itr = source_statmap.find(file_sb.st_rdev); itr != std::end(source_statmap)) {
					if(apply_filters(*itr->second))
						add_fs(*itr->second, vfs, false, file, true);
					continue;
				}

			if(auto itr = target_statmap.find(file_sb.st_dev); itr != std::end(target_statmap)) {
				if(apply_filters(*itr->second))
					add_fs(*itr->second, vfs, false, file, true);
				continue;
			}

			errno = ENOENT;
		err:
			std::fprintf(stderr, "%s: %s: %s\n", argv[0], file, std::strerror(errno));
			arg_err = true;
		}
	} else
		for(auto itr = std::begin(mtab_entries); itr != std::end(mtab_entries); ++itr) {
			auto && ent = *itr;

			if(!apply_filters(ent))
				continue;

			bool ent_err = std::any_of(itr + 1, std::end(mtab_entries), [&](auto && me) { return me.target == ent.target; });
			if(ent_err && default_filters)
				continue;

			struct statvfs vfs;
			int err;
			while((err = statvfs(ent.target.c_str(), &vfs)) == -1 && errno == EINTR)
				;
			ent_err = ent_err || err == -1;
			if(ent_err && default_filters)
				continue;

			if(!vfs.f_blocks && default_filters)
				continue;

			add_fs(ent, vfs, ent_err, "-", true);
		}

	if(output_lines.size() == 1)
		return std::fprintf(stderr, "%s: no filesystems\n", argv[0]), 1;

	if(do_total)
		add_fs(entry{"total", "-", "-"}, totals_vfs, false, "-", false);

	std::vector<int> widths;
	widths.resize(fields.size());
	for(auto && l : output_lines)
		for(std::size_t i = 0; i < fields.size(); ++i)
			widths[i] = std::max(widths[i], static_cast<int>(std::visit(vore::overload{[&](auto && s) { return s.size(); }}, l[i])));

	for(auto && l : output_lines) {
		for(std::size_t i = 0; i < fields.size(); ++i) {
			auto sp = widths[i] - static_cast<int>(std::visit(vore::overload{[&](auto && s) { return s.size(); }}, l[i]));
			std::fprintf(stdout, "%s%*.s%s%*.s",                                                        //
			             i ? " " : "",                                                                  //
			             field_a[static_cast<std::uint8_t>(fields[i])] == align_t::right ? sp : 0, "",  //
			             std::visit(vore::overload{[&](auto && s) { return static_cast<const char *>(s.data()); }}, l[i]),
			             (field_a[static_cast<std::uint8_t>(fields[i])] == align_t::left && i != fields.size() - 1) ? sp : 0, "");
		}
		std::fputc('\n', stdout);
	}

	return vore::flush_stdout(argv[0]) || arg_err;
}

M cmd/mkfifo.cpp => cmd/mkfifo.cpp +5 -5
@@ 6,12 6,15 @@
#include <errno.h>
#include <fcntl.h>
#include <initializer_list>
#include <inttypes.h>
#include <optional>
#include <string>
#include <string_view>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#if __has_include(<sys/sysmacros.h>)
#include <sys/sysmacros.h>
#endif
#include <unistd.h>
#include <utility>
#include <vore-getopt>


@@ 29,9 32,6 @@
	    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};


@@ 64,7 64,7 @@ int main(int argc, char * const * argv) {
				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;
					return std::fprintf(stderr, "%s: -m %04" PRIu64 "o: non-permission bits set\n", argv[0], static_cast<std::uint64_t>(*mode)), 1;
				break;
			case 'Z':
				mac_ctx = val;

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


#pragma once

#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>


namespace vore {
	namespace {
		enum class human_size_t : bool { human_1024, si_1000 };

		// Originally borrowed from http
		// TODO: this kinda sucks for this purpose! just try df
		void human_size(char * out, std::size_t outs, double s, human_size_t base) {
			static constexpr const char * suff[] = {"", "k", "M", "G", "T", "P", "E", "Z", "Y"};

			if(s == 0)
				std::strncpy(out, "0", outs);
			else {
				auto exp =
				    std::min(std::max(static_cast<std::uint64_t>(std::log(s) / std::log(base == human_size_t::human_1024 ? 1024 : 1000)), static_cast<std::size_t>(0)),
				             static_cast<std::size_t>(8));

				auto val = s / (base == human_size_t::human_1024 ? [=]() mutable {auto ret = 1.;while(exp--)ret *=1000;return ret;}() : static_cast<double>(1ull << exp * 10));

				std::snprintf(out, outs, "%g%s", exp > 0 ? std::round(val * 10.) / 10. : std::round(val), suff[static_cast<std::size_t>(exp)]);
			}
		}
	}
}

M include/vore-signal => include/vore-signal +7 -5
@@ 15,7 15,7 @@

namespace vore::signal {
	namespace {
#if __linux__
#if __linux__ || __sun
		std::optional<int> from_name(const char * name) {
			for(auto && [k, v] : std::initializer_list<std::pair<const char *, int>> {
				    {"Signal 0", 0},            // matches NetBSD (4.4BSD)


@@ 51,10 51,12 @@ namespace vore::signal {
#if SIGEMT
				        {"EMT", SIGEMT},
#endif
				        {"STKFLT", SIGSTKFLT},  //
				        {"IO", SIGIO},          //
				        {"CLD", SIGCLD},        //
				        {"PWR", SIGPWR},        //
#if SIGSTKFLT
				        {"STKFLT", SIGSTKFLT},
#endif
				        {"IO", SIGIO},    //
				        {"CLD", SIGCLD},  //
				        {"PWR", SIGPWR},  //
#if SIGINFO
				        {"INFO", SIGINFO},
#endif

M include/vore-size => include/vore-size +9 -5
@@ 18,7 18,7 @@
namespace vore {
	namespace {
		template <class T>
		std::optional<T> parse_size(const char * self, const char * val) {
		std::optional<T> parse_size(const char * self, const char * val, bool verbose_err = true) {
			double unit = 1024;
			std::uint8_t power{};
			std::string_view v{val};


@@ 62,7 62,8 @@ namespace vore {
						v = v.substr(0, v.size() - 1);
						break;
					default:
						std::fprintf(stderr, "%s: %s: unrecognised suffix %c; must be one of: KMGTPEZY\n", self, val, v.back());
						if(verbose_err)
							std::fprintf(stderr, "%s: %s: unrecognised suffix %c; must be one of: KMGTPEZY\n", self, val, v.back());
						return {};
				}



@@ 71,18 72,21 @@ namespace vore {
			char * v_se{};
			double base = std::strtod(v_s.c_str(), &v_se);
			if(base == 0 && v_se == v_s.c_str()) {
				std::fprintf(stderr, "%s: %s: invalid size\n", self, v_s.c_str());
				if(verbose_err)
					std::fprintf(stderr, "%s: %s: invalid size\n", self, v_s.c_str());
				return {};
			}
			if(std::isnan(base)) {
				std::fprintf(stderr, "%s: %s: invalid size: not a number\n", self, v_s.c_str());
				if(verbose_err)
					std::fprintf(stderr, "%s: %s: invalid size: not a number\n", self, v_s.c_str());
				return {};
			}

			for(auto p = power; p--;)
				base *= unit;
			if(base > static_cast<double>(std::numeric_limits<T>::max())) {
				std::fprintf(stderr, "%s: %s*(%g^%" PRIu8 "): %s\n", self, v_s.c_str(), unit, power, std::strerror(ERANGE));
				if(verbose_err)
					std::fprintf(stderr, "%s: %s*(%g^%" PRIu8 "): %s\n", self, v_s.c_str(), unit, power, std::strerror(ERANGE));
				return {};
			}


M include/vore-token => include/vore-token +16 -3
@@ 76,6 76,7 @@ namespace vore {
			const char * delim;
			const char * remaining;
			std::string_view token = {};
			bool first             = true;


			soft_tokenise_iter & operator++() noexcept {


@@ 87,8 88,20 @@ namespace vore {
						remaining += len;
					} else
						token = {};
				} else
					abort();
				} else {  // TODO: actually slightly wrong for long delim-only runs, but, well
					if(!first && !*remaining) {
						delim = nullptr;
						token = {};
					} else {
						first = false;

						auto len = strcspn(remaining, delim);
						token    = {remaining, len};
						remaining += len;
						if(*remaining)
							++remaining;
					}
				}
				return *this;
			}



@@ 98,7 111,7 @@ namespace vore {
				return ret;
			}

			constexpr bool operator==(const soft_tokenise_iter & rhs) const noexcept { return this->token == rhs.token; }
			constexpr bool operator==(const soft_tokenise_iter & rhs) const noexcept { return merge_seps ? this->token == rhs.token : this->delim == rhs.remaining; }
			constexpr bool operator!=(const soft_tokenise_iter & rhs) const noexcept { return !(*this == rhs); }

			constexpr std::string_view operator*() const noexcept { return this->token; }

M man/base64.1 => man/base64.1 +1 -1
@@ 49,7 49,7 @@
Without
.Fl d ,
encode
.Ar file Pq or standard input if Qo Sy - Qc or not specified ,
.Ar file Pq or standard input if Qo Fl Qc or not specified ,
mapping consecutive bits to the indices into one of the following alphabets:
.Bl -tag -compact -offset Ds -width "base32hex"
.It Nm

M man/cksum.1 => man/cksum.1 +1 -1
@@ 21,7 21,7 @@ use the standard input stream and omit the name.
If a
.Ar file
is
.Qq Sy - ,
.Qq Fl ,
use the standard input stream.
.Pp
The

A man/df.1 => man/df.1 +566 -0
@@ 0,0 1,566 @@
.\" SPDX-License-Identifier: 0BSD
.\"
.Dd
.Dt DF 1
.Os
.
.Sh NAME
.Nm df
.Nd get free filesystem space
.Sh SYNOPSIS
.Nm
.Op Fl PklaiThH
.Oo Fl t Ar only-type Oc Ns …
.Oo Fl x Ar not-type Oc Ns …
.Op Fl B Ar blocksize
.Op Fl -output Ns Oo = Ns Ar col Ns Oo , Ns Ar col Oc Ns … Oc
.Op Fl -sync
.Op Fl -no-sync
.Op Fl -total
.Oo Ar path Ns | Ns Ar device Oc Ns …
.
.Sh DESCRIPTION
Lists the free and used space accessible to unprivileged users on mounted filesystems.
With no
.Ar path Ns s ,
all filesystems are listed; otherwise, those on which the
.Ar path Ns s
lie are, or if any correspond to a source device node of a mounted filesystem \(em that filesystem.
.Pp
With neither
.Ar path Ns s
nor
.Fl a ,
zero-size, inaccessible, and over-mounted filesystems are omitted.
Additional filtering via
.Fl txl
is applied in all cases.
.Pp
With
.Fl hH ,
output is in a human-readable
.Li 3.2T Ns -style .
Otherwise, without
.Fl P ,
output is in blocks of
.Fl \&Bk ,
the first valid of the
.Ev DF_BLOCK_SIZE ,
.Ev BLOCK_SIZE ,
.Ev BLOCKSIZE
environment variables, or
.Sy 1024
bytes.
Otherwise, the output is in blocks of
.Fl k
or
.Sy 512
bytes.
.Pp
.Fl B
and the block size environment variables are in the case-insensitive format:
.Dl Oo Sy +-<>/% Oc Ns Ar base Ns Oo Sy KMGTPEZY Oc Ns Op Sy B
Where
.Em base
is a floating-point amount of bytes, which is then optionally multiplied by the relevant unit.
.Sy B
sets the unit multiplier to
.Em 1000 Pq from Em 1024 .
.Ar size
is equal to
.Em base Ns Sy \(pc Ns Em unit Ns Sy ^ Ns Em mult ,
if any, or
.Em base .
.\" TODO: this is clumsy, copied from truncate.1
.
.Ss Columns
.Nm
produces a columnated listing; numeric columns are right-aligned, others are left-aligned.
.Pp
.Bl -item -compact
.It
The default columns are
.Sy source , Ns Sy size , Ns Sy used , Ns Sy avail , Ns Sy pcent , Ns Sy target .
.It
With
.Fl i ,
it's the same, but with i-nodes:
.Sy source , Ns Sy itotal , Ns Sy iused , Ns Sy iavail , Ns Sy ipcent , Ns Sy target .
.It
.Fl T
inserts
.Sy fstype
as the second column.
.Pp
Available columns are:
.TS
lw3 lb lf(CR) l .
	source	Filesystem	Mount source, like \f(CI/dev/nvme0n1p2\fP or \f(CItarta:/pub\fP.
	fstype	Type	Filesystem type, like \f(CBvfat\fP or \f(CBnfs4\fP.
	itotal	i-nodes	Total amount of i-nodes.
	iused	iUsed	Amount of inodes in use.
	iavail	iFree	Amount of inodes available to unprivileged users.
	ipcent	iUse%	\f(CBceil\fP(100 * \f(CBiused\fP / (\f(CBiused\fP \- \f(CBiavail\fP))\f(CR%\fP
	size	\f(CI1k\fP-blocks	Total capacity, in blocks, rounded up.
	used	Used	Capacity used, in blocks, rounded up.
	avail	Avail	Capacity available to unprivileged users, in blocks, rounded up.
	pcent	Use%	\f(CBceil\fP(100 * \f(CBused\fP / (\f(CBused\fP \- \f(CBavail\fP))\f(CR%\fP
	file	File	\f(CIpath\fP this corresponds to, or "\f(CB-\fP".
	target	Mounted on	Mount point, like \f(CI/boot\fP or \f(CI/\fP.
.TE
.Pp
.Fl hH
change the
.Sy size
heading to
.Qq Li Size .
.Fl P
changes the headings for
.Sy size
to
.Qq Ar 1024 Ns Li -blocks
(plain number),
.Sy avail
to
.Qq Li Available ,
and
.Sy pcent
to
.Qq Li Capacity .
.El
.
.Sh OPTIONS
.Bl -tag -compact -width "-x, --exclude-type=not-type"
.It Fl P , -portability
Default to
.Sy 512 Ns -byte
blocks
.St -p1003.1-2008 Ns -compatible
headings.
.It Fl k
Equivalent to
.Fl B Ar 1024 .
.It Fl B , -block-size Ns = Ns Ar blocksize
Set block size for capacity output.
.It Fl l , -local
Filter out non-local filesystems:
.Nm cifs , afs ,
and those with a colon
.Pq Qq Sy \&:
in their mount source.
.It Fl a , -all
With no
.Ar path Ns s ,
remove default filter (see above).
No effect otherwise.
.It Fl i , -inodes
Set output format to
.Sy source , Ns Sy itotal , Ns Sy iused , Ns Sy iavail , Ns Sy ipcent , Ns Sy target .
.It Fl T , -print-type
With default or
.Fl i
format, use
.Sy fstype
as the second column.
.It Fl h , -human-readable
Fold all sizes into a human readable
.Em 1024 Ns -based
.Li 3.2T
style .
.It Fl H , -si
Like
.Fl h
but
.Em 1000 .
.It Fl t , -print-type Ns = Ns Ar only-type
Filter out all filesystems with types different than
.Ar only-type Ns s .
.It Fl x , -exclude-type Ns = Ns Ar not-type
Filter out all filesystems with types equal to
.Ar not-type Ns s .
.It Fl -output Ns Oo = Ns Ar col Ns Oo , Ns Ar col Oc Ns … Oc
Print
.Ar col Ns umns
.Pq or all if no argument .
See the
.Sx Columns
section.
Excludes
.Fl T .
.It Fl -sync
Run
.Xr sync 2
before collecting any data.
This may provide more accurate statistics on some systems.
.It Fl -no-sync
Don't.
This is the default.
.It Fl -total
Write a final line with summary information of all filesystems printed before.
.It Fl v
Ignored for compatibility with
.At V.4
on i386.
.El
.
.Sh ENVIRONMENT
.Bl -tag -compact -width "DF_BLOCK_SIZE, BLOCK_SIZE, BLOCKSIZE"
.It Ev DF_BLOCK_SIZE , BLOCK_SIZE , BLOCKSIZE
Without
.Fl P ,
the first valid of these variables sets the default block size, instead of
.Sy 1024 .
.El
.
.Sh FILES
.Bl -tag -compact -width "/etc/mtab"
.It Pa /etc/mtab  \" None on 4.4BSD, /etc/mnttab on Illumos, but who cares
List of mounted filesystems.
.El
.
.Sh EXIT STATUS
.Sy 1
if a
.Ar path
couldn't be accessed, or if no filesystems were listed (the heading is also suppressed in that case).
.
.Sh SEE ALSO
.Xr fstab 5 ,
.Xr mount 8
.
.Sh STANDARDS
Conforms to
.St -p1003.1-2008 ;
as historical practice is irreconcilable, portable output is achieved only with
.Fl P
and an optional
.Fl k ;
even then, columnation is acceptable, but not required.
Some implementations blunder even this, and default to
.Sy 1024 Ns -byte
blocks.
The only truly portable invocation of
.Nm
is
.Dl $ Ev POSIXLY_CORRECT Ns Li = Nm Fl P Oo Fl k Oc Oo Ar path Ns | Ns Ar device Oc Ns …
.Pp
It also defines a
.Fl t
XSI extension as "Include total allocated-space figures in the output", but leaves it explicitly unspecified; this is good, as
.Fl t Ar only-type
is universally supported.
.Pp
This implementation is compatible with the GNU system, which is broken as noted above, more strict about mixing output format flags, disallows block sizes with
.Sy B
but without a unit, as well as lowercase
.Sy B ,
and only supports integer
.Ar base Ns s .
.
.Sh HISTORY
.Ss Research \s-2UNIX\s0
.\" Copied from Unix Programmer's Manual, compared against impl in svntree-20081216.tar.gz, 4BSD tape, 4.* tarballs, and the CSRG ISO
Appears in the first edition of the UNIX Programmer's Manual as
.Xr df I :
.Bl -tag -compact -offset Ds -width "DESCRIPTION"
.It Li NAME
.Li "df  --  disk free"
.It Li SYNOPSIS
.Li "\z\(uld\z\(ulf [ filesystem ]"
.El
Writing out plus-separated free block counts for
.Pa /dev/rf0 , /dev/rk1 , /dev/rk2 No and Pa /dev/rk3 ,
.\" list from svntree-20081216, not the manpage, which says rf0 and dk0
or
.Ar filesystem
.Pq a file with at least a filesystem superblock .
The second edition provides a different list and notes that these are the "normally mounted file systems".
.Pp
.At v5 ,
alongside a new filesystem format, sees a rewrite in C and
.Qo Pa device Ar blocks Qc Ns -style
output.
.Pp
.At v7
installs
.Nm
set-user-ID, since it read the superblocks directly.
This practice continues intermittently across all
.Nm Ns s
that do so.
.
.Ss The BSD
.Bx 4.0
sees the first version that reads
.Pa /etc/mtab :
.D1 Sy df No \&[ Sy \-i No ]\& \&[ Sy \-l No ]\& \&[ filesystem ... ]\& \&[ file ... ]\&
It also introduces a faintly familiar format:
.Dl "Filesystem  Mounted on  blocks    used    free  % used"
.\" Original has tabs, this is expanded for your viewing pleasure
With
.Fl i
appending the inode fields:
.Dl "iused   ifree   %iused"
And
.Fl l
adding
.Li hardway ,
which reads the underlying block device's free list, after
.Li free .
The
.Li blocks
are in real filesystem block size \(em
.Sy 1024 ;
the page notes this as being twice the block size of
.Xr du 1
and
.Xr ls 1 ;
.St -xpg4
notes this as one of the reasons for the irreconcilability of existing practice
.Pq and, hence, Fl P .
.Pp
.Bx 4.2
removes
.Fl l
and changes the format to a less confusing (and more familiar) one:
.Dl "Filesystem    kbytes    used   avail capacity  Mounted on"
With
.Fl i
Adding
.Dl " iused   ifree  %iused"
before
.Li "Mounted on" .
.St -p1003.1-2008
.\" TODO: is this wording from POSIX.2a actually?
erroneously notes this as the
.Fl P
format.
.Pp
.Bx 4.3 Reno
sees
.D1 Nm df Oo Fl ikn Oc Op Ar file Li \&| Ar filesystem \&...
With an automatically-scaling
.Li Filesystem
column and
.Sy 512 Ns -byte
blocks with an appropriate
.Li "512-blks"
heading by default, with
.Fl k
to revert.
.Fl n
doesn't block for mount information.
.Pp
.Bx 4.4
sees a
.Sx SYNOPSIS
of
.D1 Nm df Oo Fl in Oc Oo Fl t Ar type Oc Op Ar file | Ar filesystem ...
.Fl k
was replaced with
.Ev BLOCKSIZE
in the
.Dl base Ns Op Sy KMG
format.
Multiple instances of
.Fl t
can be used to filter from a list of supported filesystems
.Pq like Sy ufs , nfs , No or Sy kernfs
and groups
.Pq Sy all , local , misc ,
or to filter them out by prepending
.Sy no .
.Pp
.Bx 4.4 Lite2
allows
.Em one
.Fl t ,
but sees it as a
.Pq Sy no Ns -prefixed
comma-separated list of filesystem types.
.
.Ss System V
.No PWB/ Ns Ux
has
.\" PWB/UNIX (spencer) abuses \c in a way contradictory with a modern interpretation; adapted for your viewing pleasure
.D1 Sy df No \&[ Sy \-uqs No ]\& \&[ Sy \-t No number ]\& [arg ...]
and uses
.Pa /etc/mnttab
by default, accepting
.Ar arg Ns s
of either the source device or the mount point.
The default output is relatively similar to
.At v5 Ns 's
as
.Qq Li /dev/ Ns Ar mountpoint Li \&( Ns Pa device Ns Li )\& Ar free-blocks
.Fl t
compares the free block count on processed filesystems with
.Ar number ,
writing
.Qq Ar maj min Sy Y
if it's more and
.Sy N ,
alongside exiting with
.Sy 1
otherwise.
.Fl u
writes a verbose usage listing for each filesystem:
.Bd -literal -compact
.Li /dev/ Ns Ar mountpoint Ns Li \&( Ns Pa device Ns Li ")\&	" Ns Ar size Li total blocks
.Li "		" Ns Ar internal-inodes Li system use
.Li "		" Ns Ar free-blocks Li free
.Li "		" Ns Ar blocks-used Li used
.Li "		" Ns Ar free-inodes Li free inodes
.Ed
.Fl q
reads the free space directly out of the superblock.
.Fl s
suppresses all output.
This is a big fucking mess!
No wonder that
.Pp
.At III
is completely different.
By default, the output format is
.Dl Ar mountpoint Ns Li \&( Ns Pa device Ns Li )\&: Ar free-blocks Li blocks Ar free-inodes Li i-nodes
With
.Fl t ,
it grows an additional line:
.Dl "                     (" Ns Ar size Li total blocks,\& Ar inode-blocks Li for i-nodes)\&
With
.Fl f ,
the output is as default, but without the i-node count, and the free block count is validated against the superblock.
.Fl q
is ignored.
.Pp
.At V
fixes
.Fl t
with
.Fl f
and uses the filesystem name from the superblock for unmounted filesystems.
.No Non- Ns Sy 512 Ns -byte
filesystem blocks are corrected to
.Sy 512 Ns -byte
output blocks.
.Pp
.At V.3
replaces
.Fl q
with
.Fl l ,
skipping network mounts, and gains the ability to match a file to its filesystem.
The default output gains an asterisk
.Pq Qq Sy *
after
.Li blocks ,
for subsequent iterations over the same source.
.Fl t
now adds
.Dl "                   total:" Ar size Li blocks Ar total-inodes Li i-nodes
.Pp
Cool, but what if I told you it could be worse?
.At V.4
ships
.Dl "df [-F FSType] [-begklntVv] [current_options] [-o specific_options] [directory | special ...]"
.Pq with Fl v No on i386 only .
The default output is similar to
.At III Ns 's ,
save for
.Li files
instead of
.Li i-nodes .
.\" TODO: do we want to spend three paragraphs discussing literally every fucked-up tidbit of SVr4 df? we could!
The interesting part (rather, the one that isn't eye-piercingly insane like the filesystem-specific helper programs,
the intricate precedence rules, or transitive handling of a remote mount on the remote host)
are the output formats
.Pq with Ar free No in kilobytes and Sy 512 Ns -byte blocks :
.Bl -tag -compact -width "-be"
.It Fl n
.Li mountpoint Ns Li ": " Ns Ar type
.It Fl e
.Bl -item -compact
.It
.Li "Filesystem             ifree"
.It
.Li "         " Ns Pa device Ns Li "       " Ns Ar free-ino
.El
.It Fl b
.Bl -item -compact
.It
.Li "Filesystem             avail"
.It
.Li "         " Ns Pa device Ns Li "           " Ns Ar free
.El
.It Fl be
.Bl -item -compact
.It
.Li "         " Ns Ar mountpoint Ns Li "(          " Ns Pa device Ns Li "):      " Ns Ar free Li kilobytes
.It
.Li "         " Ns Ar mountpoint Ns Li "(          " Ns Pa device Ns Li "):  " Ns Ar free-ino Li files
.It
.El
.It Fl t
The same as
.At III
with
.Fl t ,
save for
.Li files
instead of
.Li i-nodes :
.Bl -item -compact
.It
.Li "         " Ns Ar mountpoint Ns Li "(          " Ns Pa device Ns Li "):  " Ns Ar free-blo Li blocks Ns Ar free-ino Li files
.It
.Li "                                total:    " Ns Ar blocks Li "blocks  " Ns Ar inodes Li files
.El
.It Fl k
.Li "filesystem         kbytes   used     avail    capacity  mounted on"
.Pq with a two-digit precision for Li capacity
.It Fl g
.Ar UUID
is a 32-bit integer,
.Ar filesystem-name
is a 32-character string;
.Ar flags
are the mount flags, numerically:
.Bl -item -compact
.It
.Li "        " Ns Ar mountpoint Ns Li "(         " Ns Pa device Ns Li "):     " Ns Ar bsz Li "block size      " Ns Ar fsz Li frag size
.It
.Li " " Ns Ar blocks Li total blocks Ns Ar free-bl Li free blocks Ns Ar availbl Li "available    " Ns Ar inodes Li total files
.It
.Ar free-in Li "free files     " Ns Ar UUID Li "filesys id                  " Ns Ar filesystem-name Ns Li " "
.It
.Li "   " Ns Ar type Li "fstype   0x000" Ns Ar flags Li "flag        " Ns Ar maxlen Li filename length
.It
.El
.It Fl v
The heading is printed whenever the option is specified, but the usage is an integer:
.Bl -item -compact
.It
.Li "Mount Dir  Filesystem           blocks      used      free  %used"
.El
.El
.
.Ss Standards
\*[doc-Tn-font-size]X/Open\*[doc-str-St] Portability Guide Issue\~2 (\*[Lq]\*[doc-Tn-font-size]XPG\*[doc-str-St]\^2\*[Rq])
specifies
.Nm Op Fl t
with an undefined format.
.\" This is according to http://archive.opengroup.org/publications/archive/CDROM/g501.pdf (page 59)! I can't find a pre-4 XPG anywhere
.St -p1003.2-92
excludes
.Nm ,
as it doesn't address the concept of filesystems;
it's included in the
.St -p1003.2a-92 Pq User Portability Extension
supplement, creating
.Fl Pk
with their well-defined formats and block sizes of today;
.St -xpg4
aligns its definition therewith, retaining
.Fl t
as an extension.
.\" TODO: is the timeline strictly right?

M man/mkdir.1 => man/mkdir.1 +5 -5
@@ 15,7 15,10 @@
.Ar directory Ns …
.
.Sh DESCRIPTION
Creates the specified directories.
Creates the specified directories with mode
.Li a=rwx
\-
.Va umask .
.Pp
With
.Fl p ,


@@ 34,10 37,7 @@ Create all parents of the specified directories as well, and ignore directories 
Print which directories have been created to the standard output stream.
.It Fl m , -mode Ns = Ns Ar mode
.Xr chmod 1 Ns -style
file permissions to create directories as, rather than the default
.Li a=rwx
\-
.Va umask .
mode to create final directories as.
.
.It Fl Z , -context
Create directories with the default SELinux contexts for their paths.

M man/mkfifo.1 => man/mkfifo.1 +1 -1
@@ 105,7 105,7 @@ usage in
alongside the inclusion of named pipes.
.Pp
.Nm
was invented by
was invented in
.St -p1003.2-92
to provide a way to create named pipes from interactive sessions
.Pq Nm mknod No is not part of the standard ,

M man/nice.1 => man/nice.1 +3 -3
@@ 83,12 83,12 @@ on all known systems.
.
.Sh HISTORY
.\" Copied from Unix Programmer's Manual, Version 2 and V3, V4 manpages
A way to set scheduling priority first appeared in
A way to alter scheduling priority first appeared in
.At v2
as
.Xr hog II :
.Dl "NAME            hog -- set program in low priority"
which moved the caller to the low-priority queue, which "background jobs that execute for a long time should do".
.Dl "NAME            hog -\&- set program in low priority"
Moving the caller to the low-priority queue, which "background jobs that execute for a long time should do".
The job was moved back to the regular queue "as soon as the process is dismissed for any reason other than quantum overflow" (when a syscall is made).
.Pp
.At v3

M man/paste.1 => man/paste.1 +1 -1
@@ 22,7 22,7 @@
Without
.Fl s ,
concatenates consecutive lines of
.Ar file Ns s Pq standard input if Qo Sy - Qc or missing ,
.Ar file Ns s Pq standard input if Qo Fl Qc or missing ,
separating them with tabs, until all are exhausted;
otherwise concatenates each
.Ar file

M man/sha1sum.1 => man/sha1sum.1 +2 -2
@@ 183,10 183,10 @@ With
.Fl -strict ,
invalid lines yield a non-zero exit code.
Be wary of using
.Qq Sy -
.Qq Fl
.Ar file Ns s
and
.Qq Sy -
.Qq Fl
hashed files.
.Pp
For each valid line, the file is hashed, compared to the listed hash, and a verdict is issued to the standard output stream:

M man/shred.1 => man/shred.1 +2 -2
@@ 17,8 17,8 @@
.Ar file Ns …
.
.Sh DESCRIPTION
Writes NUL bytes to end of
.Ar file Ns s Pq standard output if Qq Sy - ,
Writes NUL bytes until end of
.Ar file Ns s Pq standard output if Qq Fl ,
then removes them if
.Fl u
is specified.

M man/sum.1 => man/sum.1 +1 -1
@@ 14,7 14,7 @@
.
.Sh DESCRIPTION
Prints a legacy checksum of
.Ar file Ns s Pq standard input if none or Qq Sy - .
.Ar file Ns s Pq standard input if none or Qq Fl .
.
.Sh OPTIONS
.Bl -tag -compact -width "-s, --sysv"

M man/truncate.1 => man/truncate.1 +1 -1
@@ 46,7 46,7 @@ is equal to
.Em base Ns Sy \(pc Ns Em unit Ns Sy ^ Ns Em mult ,
if any, or
.Em base .
.\" TODO: this is clumsy, copied into stdbuf.1, libstdbuf.3
.\" TODO: this is clumsy, copied into stdbuf.1, libstdbuf.3, df.1
.Pp
If
.Fl o

M man/tsort.1 => man/tsort.1 +1 -1
@@ 13,7 13,7 @@
.
.Sh DESCRIPTION
Constructs a directed graph from the pairs of whitespace-separated node relationships provided in
.Ar file Pq standard input if Qo Sy - Qc or none ,
.Ar file Pq standard input if Qo Fl Qc or none ,
then prints all unique nodes, one per line, sorted according to the total ordering described by that graph,
consistently with the partial ordering of the final occurrence of nodes in the input.
.Pp

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

df="${CMDDIR}df"

POSIXLY_CORRECT=
export POSIXLY_CORRECT


[ "$(df -P  | tr -s ' ')" = "$("${df}" -P  | tr -s ' ')" ] || echo "df: -P  doesn't match" >&3
[ "$(df -Pk | tr -s ' ')" = "$("${df}" -Pk | tr -s ' ')" ] || echo "df: -Pk doesn't match" >&3


if ! [ -w '/dev/full' ]; then
  echo "df: skipping error testing, /dev/full unavailable" >&2
  exit
fi

err="$("${df}" 2>&1 > /dev/full)" && echo "df: /dev/full ok?" >&3
[ -n "$err" ]                     || echo "df: stderr empty for /dev/full" >&3

M tests/id => tests/id +0 -1
@@ 63,7 63,6 @@ if [ -w '/dev/full' ]; then
	done
else
  echo "id: skipping error testing, /dev/full unavailable" >&2
  exit
fi

rm -rf "$tmpdir" 2>&3

M tests/mkdir => tests/mkdir +1 -0
@@ 89,4 89,5 @@ fi


cd /
chmod -R 777 "$tmpdir"
rm -rf "$tmpdir" 2>&3