~nabijaczleweli/voreutils

a4b85bc9c982fac33161a72c1fd4177e22a20ad9 — наб 21 days ago d3c3318
Add mkdir
M cmd/expand.cpp => cmd/expand.cpp +1 -1
@@ 72,7 72,7 @@ int main(int argc, char * const * argv) {
				std::uint64_t s;
				if(std::strchr(val, ',')) {
					std::vector<std::uint64_t> ss;
					for(char * st : vore::tokenise{val, ", \t"}) {
					for(char * st : vore::tokenise<false>{std::string{val}, ", \t"}) {
						if(!parse_stop(argv[0], st, s))
							return 1;
						if(!ss.empty() && s <= ss.back()) {

M cmd/factor.cpp => cmd/factor.cpp +2 -1
@@ 6,6 6,7 @@
#include <cstdlib>
#include <vore-numeric>
#include <vore-optarg>
#include <vore-token>
#include <vore-print>




@@ 56,7 57,7 @@ int main(int, const char * const * argv) {
		char * line{};
		std::size_t linecap{};
		for(ssize_t len; (len = getline(&line, &linecap, stdin)) != -1;)
			for(char *saveptr, *tok = strtok_r(line, " \t\n", &saveptr); tok; tok = strtok_r(nullptr, " \t\n", &saveptr))
			for(auto tok : vore::tokenise<true>{line, " \t\n"})
				err |= factorise(self, tok);
	}
	return vore::flush_stdout(self) | err;

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


#include <cstdio>
#include <cstring>
#include <errno.h>
#include <optional>
#include <string>
#include <string_view>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <vector>
#include <vore-getopt>
#include <vore-mac>
#include <vore-mode>
#include <vore-numeric>
#include <vore-optarg>
#include <vore-path>
#include <vore-print>
#include <vore-token>


#define USAGE(self) "usage: %s [-vpZ] [-m mode] [--context=MAC] directory...\n", self


int main(int argc, char * const * argv) {
	bool parents{}, verbose{};
	std::optional<mode_t> mode;
	std::optional<const char *> mac_ctx;
	for(auto && [arg, val] : vore::opt::get{argc,
	                                        argv,
	                                        "pvm:Z",
	                                        {{"parents", no_argument, nullptr, 'p'},
	                                         {"verbose", no_argument, nullptr, 'v'},
	                                         {"mode", required_argument, nullptr, 'm'},
	                                         {"context", optional_argument, nullptr, 'Z'}}})
		switch(arg) {
			case 'p':
				parents = true;
				break;
			case 'v':
				verbose = true;
				break;
			case 'm':
				if(!(mode = vore::parse_mode(val, 0777, true)))
					return std::fprintf(stderr, "%s: -m %s: %s\n", argv[0], val, std::strerror(errno)), 1;
				break;
			case 'Z':
				mac_ctx = val;
				break;
			default:
				return std::fprintf(stderr, USAGE(argv[0])), 1;
		}
	if(!*(argv + optind))
		return std::fprintf(stderr, USAGE(argv[0])), 1;
	vore::opt::args files{argv + optind};


	// Always reset umask to ensure we can make children, cf. POSIX.1-2017 mkdir APPLICATION USAGE
	auto mask        = umask(0);
	auto actual_mode = mode.value_or(0777 & ~mask);
	auto parent_mode = (0300 | ~mask) & 0777;

	vore::mac::decay_unavail(argv[0], mac_ctx);

	selabel_handle * mac_lbl{};
	std::string_view cwd;
	if(auto glob = vore::mac::prep_global(argv[0], mac_ctx, files))
		std::tie(mac_lbl, cwd) = std::move(*glob);
	else
		return 1;


	auto isdir = [](auto f) {
		struct stat sb;
		return stat(f, &sb) == 0 && S_ISDIR(sb.st_mode);
	};

	bool err{};
	auto make = [&](auto file, auto mode) {
		if(parents && isdir(file))
			// we could do this in the mkdir() == -1 branch instead, but this would mean we're setting the context for everything from / downward, which, yeah
			return true;

		if(!vore::mac::prep_single(argv[0], mac_lbl, cwd, file, S_IFDIR)) {
			err = true;
			return false;
		}

		if(mkdir(file, mode) == -1) {
			std::fprintf(stderr, "%s: %s: %s\n", argv[0], file, std::strerror(errno));
			err = true;
			return false;
		}

		if(mode & ~0777 && chmod(file, mode) == -1)
			std::fprintf(stderr, "%s: %s: %s\n", argv[0], file, std::strerror(errno));

		if(verbose)
			std::fprintf(stdout, "%s: %s: created\n", argv[0], file);
		return true;
	};

	std::string acc;
	std::vector<std::string_view> segs;
	for(auto file : files) {
		if(parents && *file) {
			if(isdir(file))
				continue;

			acc.clear();
			if(file[0] == '/')
				acc.push_back('/');

			segs.clear();
			vore::soft_tokenise<true> toks{file, "/"};
			segs.insert(std::end(segs), std::begin(toks), std::end(toks));
			auto final = segs.back();
			segs.pop_back();
			for(auto && tok : segs) {
				acc.append(tok);
				if(!make(acc.c_str(), parent_mode))
					goto continue2;
				acc.push_back('/');
			}
			acc.append(final);
			make(acc.c_str(), actual_mode);
		} else
			make(file, actual_mode);
	continue2:;
	}

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

M cmd/mkfifo.cpp => cmd/mkfifo.cpp +13 -46
@@ 1,13 1,11 @@
// 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>


@@ 17,17 15,17 @@
#include <unistd.h>
#include <utility>
#include <vore-getopt>
#include <vore-mac>
#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",              \
#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] device c|b major minor\n" \
	"usage: %s [-m mode] [-Z] [--context=MAC] fifo   p\n",              \
	    self, self




@@ 139,51 137,20 @@ int main(int argc, char * const * argv) {
	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 = {};
	}
	vore::mac::decay_unavail(argv[0], 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));
		}
	}
	if(auto glob = vore::mac::prep_global(argv[0], mac_ctx, files))
		std::tie(mac_lbl, cwd) = std::move(*glob);
	else
		return 1;

	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(!vore::mac::prep_single(argv[0], mac_lbl, cwd, file, type_modes[static_cast<std::uint8_t>(type)])) {
			err = true;
			continue;
		}

		if(mknod(file, mode.value_or(0666) | type_modes[static_cast<std::uint8_t>(type)], majmin) == -1) {

M cmd/tsort.cpp => cmd/tsort.cpp +2 -1
@@ 7,6 7,7 @@
#include <string>
#include <vector>
#include <vore-file>
#include <vore-token>
#include <vore-print>




@@ 46,7 47,7 @@ int main(int, const char * const * argv) {
	char * line{};
	std::size_t linecap{};
	for(ssize_t len; (len = getline(&line, &linecap, input)) != -1;)
		for(char *saveptr, *tok = strtok_r(line, " \t\n", &saveptr); tok; tok = strtok_r(nullptr, " \t\n", &saveptr))
		for(auto tok : vore::tokenise<true>{line, " \t\n"})
			if(savtok.empty())
				savtok = tok;
			else {

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


#pragma once

#include <optional>
#include <vore-selinux>
#include <memory>
#include <cstring>
#include <cstdio>
#include <string_view>
#include <algorithm>


namespace vore::mac {
	namespace {
		void decay_unavail(const char * self, std::optional<const char *> & mac_ctx) {
			if(mac_ctx && !is_selinux_enabled()) {
				if(*mac_ctx)
					std::fprintf(stderr, "%s: --context=%s: SELinux not available, ignoring\n", self, *mac_ctx);
				mac_ctx = {};
			}
		}


		template <class I>
		std::optional<std::pair<selabel_handle *, std::string_view>> prep_global(const char * self, const std::optional<const char *> & mac_ctx, I begin, I end) {
			selabel_handle * mac_lbl{};
			std::string_view cwd;

			if(mac_ctx) {
				errno = 0;
				if(*mac_ctx) {
					if(setfscreatecon(*mac_ctx) == -1) {
						std::fprintf(stderr, "%s: --context=%s: %s\n", self, *mac_ctx, std::strerror(errno ?: ENOSYS));
						return {};
					}
				} else {
					if((mac_lbl = selabel_open(SELABEL_CTX_FILE, nullptr, 0))) {
						if(std::any_of(begin, end, [](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", self, std::strerror(errno ?: ENOSYS));
				}
			}

			return std::pair{mac_lbl, cwd};
		}

		template <class C>
		std::optional<std::pair<selabel_handle *, std::string_view>> prep_global(const char * self, const std::optional<const char *> & mac_ctx, const C & files) {
			return prep_global(self, mac_ctx, std::begin(files), std::end(files));
		}


		bool prep_single(const char * self, selabel_handle * mac_lbl, std::string_view cwd, const char * file, mode_t type) {
			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(), type) == -1)
					c = nullptr;
				else
					ctx.reset(c);
				if(setfscreatecon(c) == -1) {
					std::fprintf(stderr, "%s: %s: -Z %s: %s\n", self, file, c, std::strerror(errno ?: ENOSYS));
					return false;
				}
			}
			return true;
		}
	}
}

M include/vore-token => include/vore-token +76 -4
@@ 5,19 5,34 @@

#include <cstdint>
#include <cstring>
#include <iterator>
#include <string>
#include <variant>
#include <vore-visit>


namespace vore {
	namespace {
		template <bool merge_seps>
		struct tokenise_iter {
			using iterator_category = std::input_iterator_tag;
			using difference_type   = void;
			using value_type        = char *;
			using pointer           = char **;
			using reference         = char *&;

			const char * delim;
			char * saveptr = nullptr;
			char * token   = nullptr;
			bool first     = true;


			tokenise_iter & operator++() noexcept {
				this->token = strsep(&saveptr, delim);
				if constexpr(merge_seps) {
					this->token = strtok_r(first ? saveptr : nullptr, delim, &saveptr);
					first       = false;
				} else
					this->token = strsep(&saveptr, delim);
				return *this;
			}



@@ 34,15 49,72 @@ namespace vore {
		};


		template <bool merge_seps>
		struct tokenise {
			using iterator = tokenise_iter;
			using iterator = tokenise_iter<merge_seps>;


			std::variant<std::string, char *> str;
			const char * delim;


			iterator begin() noexcept {
				return ++iterator{this->delim, std::visit(vore::overload{[&](std::string & s) { return s.data(); }, [&](char * s) { return s; }}, this->str)};
			}
			constexpr iterator end() const noexcept { return {}; }
		};


		template <bool merge_seps>
		struct soft_tokenise_iter {
			using iterator_category = std::input_iterator_tag;
			using difference_type   = void;
			using value_type        = std::string_view;
			using pointer           = std::string_view *;
			using reference         = std::string_view &;

			const char * delim;
			const char * remaining;
			std::string_view token = {};


			soft_tokenise_iter & operator++() noexcept {
				if constexpr(merge_seps) {
					remaining += strspn(remaining, delim);
					auto len = strcspn(remaining, delim);
					if(len) {
						token = {remaining, len};
						remaining += len;
					} else
						token = {};
				} else
					abort();
				return *this;
			}

			soft_tokenise_iter operator++(int) noexcept {
				const auto ret = *this;
				++(*this);
				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 !(*this == rhs); }

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


		template <bool merge_seps>
		struct soft_tokenise {
			using iterator = soft_tokenise_iter<merge_seps>;


			std::string str;
			const char * str;
			const char * delim;


			iterator begin() noexcept { return ++iterator{this->delim, this->str.data()}; }
			iterator begin() noexcept { return ++iterator{this->delim, this->str}; }
			constexpr iterator end() const noexcept { return {}; }
		};
	}

A man/mkdir.1 => man/mkdir.1 +109 -0
@@ 0,0 1,109 @@
.\" SPDX-License-Identifier: 0BSD
.\"
.Dd
.Dt MKDIR 1
.Os
.
.Sh NAME
.Nm mkdir
.Nd create directory
.Sh SYNOPSIS
.Nm
.Op Fl pvZ
.Op Fl m Ar mode
.Op Fl -context Ns = Ns Ar MAC
.Ar directory Ns …
.
.Sh DESCRIPTION
Creates the specified directories.
.Pp
With
.Fl p ,
creates all leading directories, too, with the permission bits of
.Li u=wx
+
.Li ~ Ns Va umask .
This ensures the final directory can be created, regardless of ill-advised
.Va umask Ns s .
.
.Sh OPTIONS
.Bl -tag -compact -width "-m, --mode=mode"
.It Fl p , -parents
Create all parents of the specified directories as well, and ignore directories that already exist.
.It Fl v , -verbose
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 .
.
.It Fl Z , -context
Create directories with the default SELinux contexts for their paths.
Ignored without SELinux.
.It Fl -context Ns = Ns Ar MAC
Create directories with SELinux context set to
.Ar MAC .
Ignored (diagnostic issued) without SELinux.
.El
.
.Sh EXIT STATUS
.Sy 1
if a directory couldn't be created, except for when it's because of
.Er EEXIST ,
the path is a directory, and
.Fl p
was specified.
.
.Sh SEE ALSO
.Xr rmdir 1 ,
.Xr mkdir 2
.
.Sh STANDARDS
.Nm
conforms to
.St -p1003.1-2008 .
.Fl -context
and
.Fl v
are extensions, also present on the GNU system.
.
.Sh HISTORY
.\" Carefully copied from the Unix Programmer's Manual
Appears in the first edition of the UNIX Programmer's Manual as
.Xr mkdir I :
.Bl -tag -compact -offset Ds -width "DESCRIPTION"
.It Li NAME
.Li "mkdir  --  make a directory"
.It Li SYNOPSIS
.Li \z\(ulm\z\(ulk\z\(uld\z\(uli\z\(ulr dirname
.El
Which bypassed permissions and made the "system's user ID" the owner of the directory.
.Pp
Second and later editions fix the bugs, note the mode being
.Li 17
(read+write owner+non-owner), and expand the
.Sx SYNOPSIS
to
.Dl \z\(ulm\z\(ulk\z\(uld\z\(uli\z\(ulr dirname ...
.Pp
.At V.3
adds
.Fl m Ar octal
and
.Fl p
(with parents in the same mode as the destination).
.Bx 4.3 Reno
adds
.Fl p
(with parents as
.Li 777
\-
.Va umask ) .
.Pp
.St -p1003.2-92
specifies
.Fl mp
in their current form, and such they appear in
.Bx 4.4 Lite2 .

M man/mkfifo.1 => man/mkfifo.1 +9 -7
@@ 17,7 17,7 @@
.Op Fl m Ar mode
.Op Fl Z
.Op Fl -context Ns = Ns Ar MAC
.Ar dev
.Ar device
.Sy c Ns | Ns Sy b
.Ar major
.Ar minor


@@ 29,7 29,7 @@
.Sy p
.
.Sh DESCRIPTION
Creates named named pipes (FIFOs), or
Creates the specified named pipes (FIFOs), or
.Ar major Ns Li \&: Ns Ar minor
device nodes \(em character with
.Sy c ,


@@ 41,19 41,19 @@ block with
.It Fl m , -mode Ns = Ns Ar mode
.Xr chmod 1 Ns -style
file permissions to create
.Ar fifo Ns s Pq Ar dev
.Ar fifo Ns s Pq Ar device
as, rather than the default
.Li a=rw
\-
.Va umask .
.It Fl Z , -context
Create
.Ar fifo Ns s Pq Ar dev
.Ar fifo Ns s Pq Ar device
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
.Ar fifo Ns s Pq Ar device
with SELinux context set to
.Ar MAC .
Ignored (diagnostic issued) without SELinux.


@@ 106,7 106,9 @@ alongside the inclusion of named pipes.
.Pp
.Nm
was invented by
.St -p1003.2-92 ,
.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 ,
including
.Fl m
for symmetry with


@@ 114,4 116,4 @@ for symmetry with
it appears in
.Bx 4.3 Reno
and
.At V.3 . \" TODO: verify this! im waiting on vetus cooldown
.At V.4 .

M man/tsort.1 => man/tsort.1 +1 -1
@@ 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 | 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

M tests/base64/test => tests/base64/test +1 -1
@@ 175,4 175,4 @@ else
  echo "base64: skipping error testing, /dev/full unavailable" >&2
fi

rm -rf "$tmpdir" >&3
rm -rf "$tmpdir" 2>&3

M tests/factor => tests/factor +1 -1
@@ 28,7 28,7 @@ out="$("$factor" 1 2 3 ' ' 'a' 4 2>"${tmpdir}err")" && echo "factor: nonnumbers 
[ "$out" = "$(printf "1:\n2: 2\n3: 3\n4: 2 2\n")" ] || echo "factor: 1 2 3 a 4 wrong"
[ -s "${tmpdir}err" ] || echo "factor: 1 2 3 a 4: empty stderr"

rm -rf "$tmpdir" >&3
rm -rf "$tmpdir" 2>&3

[ "$("$factor" 1410 1981 2>&3)" = "$(printf '1410: 2 3 5 47\n1981: 7 283\n')" ] || echo "factor: factor.1-1 wrong"
[ "$({ echo 605705216825306; echo 20511708682; echo 126239178158; echo 939961713586089507; } | "$factor" 2>&3)" = \

M tests/id => tests/id +1 -1
@@ 66,4 66,4 @@ else
  exit
fi

rm -rf "$tmpdir" >&3
rm -rf "$tmpdir" 2>&3

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

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

echo > "${tmpdir}da"
"$mkdir" "${tmpdir}da" "${tmpdir}db" >&3 2>"${tmpdir}err"         && echo "mkdir: da db ok?" >&3
[ -s "${tmpdir}err" ]                                             || echo "mkdir: da db empty stderr?" >&3
[ "$(ls -ld "${tmpdir}da" 2>&3 | cut -f1 -d' ')" = "-rw-r--r--" ] || echo "mkdir: da: wrong mode" >&3
[ "$(ls -ld "${tmpdir}db" 2>&3 | cut -f1 -d' ')" = "drwxr-xr-x" ] || echo "mkdir: db: wrong mode" >&3


"$mkdir" "${tmpdir}dda" >&3 2>&1                              || echo "mkdir: dda failed" >&3
[ "$(ls -ld "${tmpdir}dda" | cut -f1 -d' ')" = "drwxr-xr-x" ] || echo "mkdir: dda: wrong mode" >&3

"$mkdir" -mu-w,a=u,+s "${tmpdir}ddc" >&3 2>&1                 || echo "mkdir: -mu-w,a=u,+s failed" >&3
[ "$(ls -ld "${tmpdir}ddc" | cut -f1 -d' ')" = "dr-sr-sr-x" ] || echo "mkdir: -mu-w,a=u,+s: wrong mode" >&3

"$mkdir" -mu-w,o=u "${tmpdir}dde" >&3 2>&1                    || echo "mkdir: -mu-w,o=u failed" >&3
[ "$(ls -ld "${tmpdir}dde" | cut -f1 -d' ')" = "dr-xrwxr-x" ] || echo "mkdir: -mu-w,o=u: wrong mode" >&3

"$mkdir" -m123 "${tmpdir}ddd" >&3 2>&1                        || echo "mkdir: -m123 failed" >&3
[ "$(ls -ld "${tmpdir}ddd" | cut -f1 -d' ')" = "d--x-w--wx" ] || echo "mkdir: -m123: wrong mode" >&3

"$mkdir" -vpm123 "${tmpdir}ddd/e/f/g/h" >&3 2>"${tmpdir}err" && echo "mkdir: -vpm123 ddd* ok?" >&3
[ -s "${tmpdir}err" ]                                        || echo "mkdir: -vpm123 ddd* empty stderr" >&3
[ -d "${tmpdir}ddd/e" ]                                      && echo "mkdir: -vpm123 ddd/e      : made ddd/e" >&3
[ -d "${tmpdir}ddd/e/f" ]                                    && echo "mkdir: -vpm123 ddd/e/f    : made ddd/e/f" >&3
[ -d "${tmpdir}ddd/e/f/g" ]                                  && echo "mkdir: -vpm123 ddd/e/f/g  : made ddd/e/f/g" >&3
[ -d "${tmpdir}ddd/e/f/g/h" ]                                && echo "mkdir: -vpm123 ddd/e/f/g/h: made ddd/e/f/g/h" >&3

"$mkdir" -vpm123 "${tmpdir}dda/e/f/g/h" > "${tmpdir}lines" 2>&3       || echo "mkdir: -vpm123 dda* failed" >&3
[ "$(wc -l < "${tmpdir}lines" | tr -cd '[:digit:]')" -eq 4 ]            || echo "mkdir: -vpm123 dda* wrong line count" >&3
[ "$(ls -ld "${tmpdir}dda"         | cut -f1 -d' ')" = "drwxr-xr-x" ] || echo "mkdir: -vpm123 dda        : wrong mode" >&3
[ "$(ls -ld "${tmpdir}dda/e"       | cut -f1 -d' ')" = "drwxr-xr-x" ] || echo "mkdir: -vpm123 dda/e      : wrong mode" >&3
[ "$(ls -ld "${tmpdir}dda/e/f"     | cut -f1 -d' ')" = "drwxr-xr-x" ] || echo "mkdir: -vpm123 dda/e/f    : wrong mode" >&3
[ "$(ls -ld "${tmpdir}dda/e/f/g"   | cut -f1 -d' ')" = "drwxr-xr-x" ] || echo "mkdir: -vpm123 dda/e/f/g  : wrong mode" >&3
[ "$(ls -ld "${tmpdir}dda/e/f/g/h" | cut -f1 -d' ')" = "d--x-w--wx" ] || echo "mkdir: -vpm123 dda/e/f/g/h: wrong mode" >&3


cd "$tmpdir" 2>&3 || exit

echo > "rda"
"$mkdir" "rda" "rdb" >&3 2>"err"                          && echo "mkdir: rda rdb ok?" >&3
[ -s "err" ]                                              || echo "mkdir: rda rdb empty stderr?" >&3
[ "$(ls -ld "rda" 2>&3 | cut -f1 -d' ')" = "-rw-r--r--" ] || echo "mkdir: rda: wrong mode" >&3
[ "$(ls -ld "rdb" 2>&3 | cut -f1 -d' ')" = "drwxr-xr-x" ] || echo "mkdir: rdb: wrong mode" >&3


"$mkdir" "rdda" >&3 2>&1                              || echo "mkdir: rdda failed" >&3
[ "$(ls -ld "rdda" | cut -f1 -d' ')" = "drwxr-xr-x" ] || echo "mkdir: rdda: wrong mode" >&3

"$mkdir" -mu-w,a=u,+s "rddc" >&3 2>&1                 || echo "mkdir: -mu-w,a=u,+s failed" >&3
[ "$(ls -ld "rddc" | cut -f1 -d' ')" = "dr-sr-sr-x" ] || echo "mkdir: -mu-w,a=u,+s: wrong mode" >&3

"$mkdir" -mu-w,o=u "rdde" >&3 2>&1                    || echo "mkdir: -mu-w,o=u failed" >&3
[ "$(ls -ld "rdde" | cut -f1 -d' ')" = "dr-xrwxr-x" ] || echo "mkdir: -mu-w,o=u: wrong mode" >&3

"$mkdir" -m123 "rddd" >&3 2>&1                        || echo "mkdir: -m123 failed" >&3
[ "$(ls -ld "rddd" | cut -f1 -d' ')" = "d--x-w--wx" ] || echo "mkdir: -m123: wrong mode" >&3

"$mkdir" -vpm123 "rddd/e/f/g/h" >&3 2>"err" && echo "mkdir: -vpm123 rddd* ok?" >&3
[ -s "err" ]                                || echo "mkdir: -vpm123 rddd* empty stderr" >&3
[ -d "rddd/e" ]                             && echo "mkdir: -vpm123 rddd/e      : made rddd/e" >&3
[ -d "rddd/e/f" ]                           && echo "mkdir: -vpm123 rddd/e/f    : made rddd/e/f" >&3
[ -d "rddd/e/f/g" ]                         && echo "mkdir: -vpm123 rddd/e/f/g  : made rddd/e/f/g" >&3
[ -d "rddd/e/f/g/h" ]                       && echo "mkdir: -vpm123 rddd/e/f/g/h: made rddd/e/f/g/h" >&3

"$mkdir" -vpm123 "rdda/e/f/g/h" > "lines" 2>&3                         || echo "mkdir: -vpm123 rdda* failed" >&3
[ "$(wc -l < "${tmpdir}lines" | tr -cd '[:digit:]')" -eq 4 ]           || echo "mkdir: -vpm123 rdda* wrong line count" >&3
[ "$(ls -ld "${tmpdir}rdda"         | cut -f1 -d' ')" = "drwxr-xr-x" ] || echo "mkdir: -vpm123 rdda        : wrong mode" >&3
[ "$(ls -ld "${tmpdir}rdda/e"       | cut -f1 -d' ')" = "drwxr-xr-x" ] || echo "mkdir: -vpm123 rdda/e      : wrong mode" >&3
[ "$(ls -ld "${tmpdir}rdda/e/f"     | cut -f1 -d' ')" = "drwxr-xr-x" ] || echo "mkdir: -vpm123 rdda/e/f    : wrong mode" >&3
[ "$(ls -ld "${tmpdir}rdda/e/f/g"   | cut -f1 -d' ')" = "drwxr-xr-x" ] || echo "mkdir: -vpm123 rdda/e/f/g  : wrong mode" >&3
[ "$(ls -ld "${tmpdir}rdda/e/f/g/h" | cut -f1 -d' ')" = "d--x-w--wx" ] || echo "mkdir: -vpm123 rdda/e/f/g/h: wrong mode" >&3


if [ -w '/dev/full' ]; then
	"$mkdir" "f" > /dev/full 2>&3                      || echo "mkdir: /dev/full failed" >&3
	[ "$(ls -ld "f" | cut -f1 -d' ')" = "drwxr-xr-x" ] || echo "mkdir: /dev/full: wrong mode" >&3

	errmsg="$("$mkdir" -v "ff" 2>&1 > /dev/full)"       && echo "mkdir: /dev/full -v ok" >&3
	[ -n "$errmsg" ]                                    || echo "mkdir: no message after /dev/full?" >&3
	[ "$(ls -ld "ff" | cut -f1 -d' ')" = "drwxr-xr-x" ] || echo "mkdir: /dev/full: wrong mode" >&3
else
	echo "mkdir: skipping error testing, /dev/full unavailable" >&2
fi


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

M tests/mkfifo => tests/mkfifo +5 -2
@@ 31,7 31,7 @@ mknodp() {
}

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

	"$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


@@ 42,7 42,10 @@ for mkf in "${tmpdir}mkfifo" mknodp; do

	"$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

	"$mkf" -m123 "${tmpdir}pd" >&3 2>&1                         || echo "mkfifo: $mkf: -m123 failed" >&3
	[ "$(ls -l "${tmpdir}pd" | cut -f1 -d' ')" = "p--x-w--wx" ] || echo "mkfifo: $mkf: -m123: wrong mode" >&3
done


rm -rf "$tmpdir" >&3
rm -rf "$tmpdir" 2>&3

M tests/nice => tests/nice +1 -1
@@ 29,7 29,7 @@ out="$("$nice" -n-1 "$nice" 2>"${tmpdir}err")" || echo "nice: -n-1 nice failed?"
[ "$out" = "0" ]                               || echo "nice: -n-1 nice niceness wrong" >&3
[ -s "${tmpdir}err" ]                          || echo "nice: -n-1 nice empty stderr" >&3

rm -rf "$tmpdir" >&3
rm -rf "$tmpdir" 2>&3

[ -w '/dev/full' ] || {
  echo "nice: skipping error testing, /dev/full unavailable" >&2

M tests/nohup => tests/nohup +1 -1
@@ 71,4 71,4 @@ nohup /enoent > /dev/null 2>&1; err=$?


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

M tests/true => tests/true +1 -1
@@ 21,4 21,4 @@ ln -fs "$true" "${tmpdir}$altname"
"${tmpdir}$altname"; err=$?
[ $err -eq 2 ] || echo "true: got $err instead of 2 for $altname" >&3

rm -rf "$tmpdir" >&3
rm -rf "$tmpdir" 2>&3

M tests/truncate => tests/truncate +1 -1
@@ 60,4 60,4 @@ err="$("$truncate" -s123 "${tmpdir}pipe" 2>&1 >&3)"; ret=$?
[ -n "$err"    ] || echo "truncate: empty stderr for pipe"


rm -rf "$tmpdir" >&3
rm -rf "$tmpdir" 2>&3

M tests/uname => tests/uname +1 -1
@@ 54,4 54,4 @@ for s in '' '-s'; do for n in '' '-n'; do for r in '' '-r'; do for v in '' $V; d
	"${tmpdir}uname" $s $n $r $v $m $p $i | cmp - "${tmpdir}uname $s $n $r $v $m $p $i" || echo "uname: $s $n $r $v $m $p $i doesn't match system" >&3
done; done; done; done; done; done; done

rm -rf "$tmpdir" >&3
rm -rf "$tmpdir" 2>&3

M tests/unlink => tests/unlink +1 -1
@@ 28,4 28,4 @@ for f in 'file' './file' '--'; do
done

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