~nabijaczleweli/voreutils

40cf5ed0d0b8eaf3d1774ec4b275d60ac9b8e5f0 — наб a month ago 72087d4
Add rmdir
M .builds/openbsd-latest.yml => .builds/openbsd-latest.yml +3 -2
@@ 2,8 2,9 @@ image: openbsd/latest
packages:
  - gmake
  - libb2
# TODO: libiconv should get autodetected
tasks:
  - build: |
      cd voreutils
      gmake CXX=c++
      gmake CXX=c++ test
      gmake CXX=c++ LDFLAGS='-L/usr/local/lib -liconv'
      gmake CXX=c++ LDFLAGS='-L/usr/local/lib -liconv' test

M README.md => README.md +2 -2
@@ 24,7 24,7 @@ GNU coreutils provide the following 105 binaries, according to `dpkg -L coreutil
  * ☑ /bin/pwd
  * ☐ /bin/readlink
  * ☐ /bin/rm
  * ☐ /bin/rmdir
  * ☑ /bin/rmdir
  * ☑ /bin/sleep
  * ☐ /bin/stty
  * ☑ /bin/sync


@@ 103,7 103,7 @@ GNU coreutils provide the following 105 binaries, according to `dpkg -L coreutil
  * ☐ /usr/bin/uniq
  * ☑ /usr/bin/unlink
  * ☐ /usr/bin/users
  * ☐ /usr/bin/wc
  * ☑ /usr/bin/wc
  * ☐ /usr/bin/who
  * ☑ /usr/bin/whoami
  * ☑ /usr/bin/yes

M cmd/basename.cpp => cmd/basename.cpp +1 -0
@@ 89,6 89,7 @@ int main(int argc, char * const * argv) {


	for(std::string_view path : vore::opt::args{paths}) {
		// dirname part also found in rmdir(1), as per spec
		bool sslash = !path.empty() && path[0] == '/';
		while(path.size() > 1 && path.back() == '/')
			path = path.substr(0, path.size() - 1);

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


#include <cstring>
#include <errno.h>
#include <string>
#include <unistd.h>
#include <vore-getopt>
#include <vore-optarg>
#include <vore-print>


using namespace std::literals;

#define USAGE(self) "usage: %s [-pve] directory...\n", self


int main(int argc, char * const * argv) {
	bool verbose{}, recursive{}, allow_non_empty{};
	for(auto && [arg, _] : vore::opt::get{argc,
	                                      argv,
	                                      "pve",
	                                      {{"verbose", no_argument, nullptr, 'v'},  //
	                                       {"parents", no_argument, nullptr, 'p'},
	                                       {"ignore-fail-on-non-empty", no_argument, nullptr, 'e'}}})
		switch(arg) {
			case 'p':
				recursive = true;
				break;
			case 'v':
				verbose = true;
				break;
			case 'e':
				allow_non_empty = true;
				break;
			default:
				return std::fprintf(stderr, USAGE(argv[0])), 1;
		}
	if(!*(argv + optind))
		return std::fprintf(stderr, USAGE(argv[0])), 1;


	bool err{};
	std::string buf;
	auto remove = [&](auto file) {
		if(rmdir(file) == -1) {
			auto ok = allow_non_empty && (errno == ENOTEMPTY || errno == EEXIST);
			err     = err || !ok;
			if(!ok || verbose)
				std::fprintf(ok ? stdout : stderr, "%s: %s: %s\n", argv[0], file, std::strerror(errno));
			return false;
		} else {
			if(verbose)
				std::fprintf(stdout, "%s: %s: removed\n", argv[0], file);  // matches mkdir -v
			return true;
		}
	};

	for(auto file : vore::opt::args{argv + optind})
		if(!recursive)
			remove(file);
		else
			for(buf = file; remove(buf.c_str());) {
				// dirname(1), as per spec
				while(buf.size() > 1 && buf.back() == '/')
					buf.pop_back();

				if(auto idx = buf.rfind('/'); idx != std::string_view::npos)
					buf.resize(idx);
				else
					break;

				while(buf.size() > 1 && buf.back() == '/')
					buf.pop_back();

				if(buf.empty())
					buf.push_back('/');
			}

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

M cmd/wc.cpp => cmd/wc.cpp +1 -1
@@ 143,7 143,7 @@ int main(int argc, char * const * argv) {
				off_t sz;
#endif
				ret = ioctl(fd, DIOCGMEDIASIZE, &sz);
#elif __OpenBSD__
#elif defined(DIOCGDINFO)  // OpenBSD
				struct disklabel dl;
				auto & sz = dl.d_secsize;
				ret       = ioctl(fd, DIOCGDINFO, &dl);

M man/base64.1 => man/base64.1 +1 -1
@@ 154,7 154,7 @@ only.
A compatible
.Nm
also appears in
.Nx 9 .
.Nx 9.0 .
.Pp
.Bx 4.0
introduced

M man/cut.1 => man/cut.1 +2 -2
@@ 139,11 139,11 @@ couldn't be opened.
00000007

.\" TODO: should this have the continued > prompts or just rely on indent?
.Li $ Nm for Ar i Nm in Li $( Ns Nm seq Ar 10 Ns Li ); Nm do
.Li $ Ic for Ar i Ic in Li $( Ns Nm seq Ar 10 Ns Li ); Ic do
.Li > " " Nm echo Li \&"-$i;$((i+1))-" \&| Nm paste Ar - Li \e
.Li > "   " <( Ns Nm printf Li 'яйцо\enЯЙЦО' \&| Nm cut Fl nb Ar -$i Ns Li )\& \e
.Li > "   " <( Ns Nm printf Li 'яйцо\enЯЙЦО' \&| Nm cut Fl nb Ar $((i+1))- Ns Li )\&
.Li > Nm done
.Li > Ic done
-1;2-           яйцо
                ЯЙЦО
-2;3-   я       йцо

M man/mkdir.1 => man/mkdir.1 +5 -2
@@ 34,7 34,7 @@ This ensures the final directory can be created, regardless of ill-advised
.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.
Write which directories were created to the standard output stream.
.It Fl m , -mode Ns = Ns Ar mode
.Xr chmod 1 Ns -style
mode to create final directories as.


@@ 50,7 50,10 @@ Ignored (diagnostic issued) without SELinux.
.
.Sh EXIT STATUS
.Sy 1
if a directory couldn't be created, except for when it's because of
if a
.Ar directory
.Pq or its parent with Fl p
couldn't be created, except for when it's because of
.Er EEXIST ,
the path is a directory, and
.Fl p

A man/rmdir.1 => man/rmdir.1 +130 -0
@@ 0,0 1,130 @@
.\" SPDX-License-Identifier: 0BSD
.\"
.Dd
.Dt RMDIR 1
.Os
.
.Sh NAME
.Nm rmdir
.Nd remove directory
.Sh SYNOPSIS
.Nm
.Op Fl pve
.Ar directory Ns …
.
.Sh DESCRIPTION
Removes
.Ar directory Ns ies ,
and, with
.Fl p ,
their parents.
.
.Sh OPTIONS
.Bl -tag -compact -width "-e, --ignore-fail-on-non-empty"
.It Fl p , -parents
Remove parents of each
.Ar directory ,
up to the first failure.
.It Fl v , -verbose
Write which directories were removed
.Pq or, if Fl i , No existed
to the standard output stream.
.It Fl e , -ignore-fail-on-non-empty
Don't error if removal failed with
.Er ENOTEMPTY .
.El
.
.Sh EXIT STATUS
.Sy 1
if a
.Ar directory
.Pq or its parent with Fl p
couldn't be removed, except if
.Fl e
and because
.Er ENOTEMPTY .
.
.Sh SEE ALSO
.Xr mkdir 1 ,
.Xr rm 1 ,
.Xr rmdir 2
.
.Sh STANDARDS
Conforms to
.St -p1003.1-2008 .
.Fl v
and
.Fl -ignore-fail-on-non-empty
are extensions, also present on the GNU system.
.Pp
Short
.Fl e
is an extension, compatible with the KornShell.
.Pp
.Fl v
is also available in
.Fx 6.0 .
.
.Sh HISTORY
.\" Carefully copied from the Unix Programmer's Manual
Appears in the first edition of the UNIX Programmer's Manual as
.Xr rmdir I :
.Bl -tag -compact -offset Ds -width "DESCRIPTION"
.It Li NAME
.Li "rmdir  --  remove directory"
.It Li SYNOPSIS
.Li \z\(ulr\z\(ulm\z\(uld\z\(uli\z\(ulr dir\d1\u ...
.El
.Pp
.At v7
merges that page into
.Xr rm 1
with an updated
.Sx SYNOPSIS
of
.D1 Sy rmdir No dir ...
As
.Nm rm
now forks out to
.Nm
to remove directories in
.Fl r
mode.
This is standardised in
.Tn X/Open No Portability Guide Issue\~2 Pq Dq Tn XPG Ns \^2 .  \" .St -xpg2
.Pp
.Bx 4.2
and
.At V.3
add
.Xr rmdir 2 ,
reducing both implementations considerably.
.At V.3
alone adds
.Fl ps ,
with
.Fl p
additionally printing any of
.Bl -bullet -compact -offset Ds -width "@"
.It
.Qq Li "rmdir:\&" Ar directory Ns Li ": Whole path removed."
.It
.Qq Li "rmdir: Directory not empty:\&" Ar directory Li not removed
for
.Er EEXIST ,
otherwise
.Qq Li "rmdir:\&" Sy strerror Ns Po Va errno Pc Ns Li :\& Ar directory Li not removed  \" actually sys_errlist[errno] because its sysv, but who cares
.It
.Qq Li "rmdir: Can not remove . or ..:\&" Ar directory Li not removed
.It
.Qq Li "rmdir: Can not remove current directory:\&" Ar directory Li not removed
.El
to the standard output stream for each argument, depending on the situation, and
.Fl s
silencing this.
.Pp
.St -p1003.2-92
codifies
.Fl p
in this behavioural shape, but strips the messages
.Pq and, hence, Fl s .

M man/test.1 => man/test.1 +12 -12
@@ 263,23 263,23 @@ A short, edited, extract from

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

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


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

.Ed
.

M man/timeout.1 => man/timeout.1 +2 -2
@@ 257,9 257,9 @@ for a list of available signals.
.Sh HISTORY
Originates from the GNU system in coreutils 7.0;
also present in
.Nx 7
.Nx 7.0
and
.Fx 11 ,
.Fx 11.0 ,
although those versions miss
.Fl v
and

M tests/mkdir => tests/mkdir +2 -1
@@ 3,6 3,7 @@

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

echo > "${tmpdir}da"
"$mkdir" "${tmpdir}da" "${tmpdir}db" >&3 2>"${tmpdir}err"         && echo "mkdir: da db ok?" >&3


@@ 31,7 32,7 @@ echo > "${tmpdir}da"
[ -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
[ "$("$wc" -l < "${tmpdir}lines")" -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

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

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

mkdir -p "${tmpdir}a/b/c/d/e/f" "${tmpdir}a/b/c/cd/ce/cf"

"$rmdir" -v "${tmpdir}a/b/c/cd/ce/cf" > "${tmpdir}log" 2>&3 || echo "rmdir: -v a/b/c/cd/ce/cf failed?" >&3
[ -d "${tmpdir}a/b/c/cd/ce/cf"         ]                    && echo "rmdir: a/b/c/cd/ce/cf exists?" >&3
[ "$("$wc" -l < "${tmpdir}log")" -eq 1 ]                    || echo "rmdir: -v a/b/c/cd/ce/cf wrong line count" >&3

"$rmdir" -pv "${tmpdir}a/b/c/cd/ce"   > "${tmpdir}log" 2>"${tmpdir}err" && echo "rmdir: -pv a/b/c/cd/ce okay?" >&3
[ -d "${tmpdir}a/b/c/cd"               ]                                && echo "rmdir:     a/b/c/cd exists?" >&3
[ "$("$wc" -l < "${tmpdir}log")" -eq 2 ]                                || echo "rmdir: -pv a/b/c/cd wrong line count" >&3
[ "$("$wc" -l < "${tmpdir}err")" -eq 1 ]                                || echo "rmdir: -pv a/b/c/cd wrong error count" >&3

"$rmdir" -pve "${tmpdir}a/b/c/d/e/f"  > "${tmpdir}log" 2>&3 || echo "rmdir: -pve a/b/c/d/e/f failed?" >&3
[ -d "${tmpdir}a"                      ]                    && echo "rmdir:      a exists?" >&3
[ "$("$wc" -l < "${tmpdir}log")" -eq 7 ]                    || echo "rmdir: -pve a wrong line count" >&3


mkdir -p "${tmpdir}a/b/c"
chmod -w "${tmpdir}a"

"$rmdir" -pve "${tmpdir}a/b/c"         > "${tmpdir}log" 2>"${tmpdir}err" && echo "rmdir: -v a/b/c/cd/ce/cf failed?" >&3
[ -d "${tmpdir}a/b/c"                  ]                                 && echo "rmdir:      a/b/c exists?" >&3
[ -d "${tmpdir}a/b"                    ]                                 || echo "rmdir:      a/b removed?" >&3
[ "$("$wc" -l < "${tmpdir}log")" -eq 1 ]                                 || echo "rmdir: -pve a/b/c wrong line count" >&3
[ "$("$wc" -l < "${tmpdir}err")" -eq 1 ]                                 || echo "rmdir: -pve a/b/c wrong error count" >&3


if [ -w '/dev/full' ]; then
	mkdir "${tmpdir}Q"
	errmsg="$("$rmdir" -v "${tmpdir}Q" 2>&1 > /dev/full)" && echo "rmdir: /dev/full -v ok" >&3
	[ -n "$errmsg" ]                                      || echo "rmdir: no message after /dev/full?" >&3
else
	echo "rmdir: skipping error testing, /dev/full unavailable" >&2
fi


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