~nabijaczleweli/voreutils

c1515638c092d067d9acaffe12c0e2c0ca60f39a — наб 23 days ago d489ee5
Add nice, with the worst-documented syscall in history
7 files changed, 269 insertions(+), 3 deletions(-)

M README.md
A cmd/nice.cpp
M include/vore-numeric
M include/vore-size
A man/nice.1
A tests/nice
M tests/paste/test
M README.md => README.md +1 -1
@@ 62,7 62,7 @@ GNU coreutils provide the following 105 binaries, according to `dpkg -L coreutil
  * ☑ /usr/bin/logname
  * ☑ /usr/bin/md5sum
  * ☐ /usr/bin/mkfifo
  * ☐ /usr/bin/nice
  * ☑ /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!)
  * ☑ /usr/bin/nproc

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


#include <cstdio>
#include <cstring>
#include <errno.h>
#include <optional>
#include <unistd.h>
#include <vore-getopt>
#include <vore-numeric>
#include <vore-print>


#define USAGE(self)                               \
	"usage: %s\n"                                   \
	"       %s [-n delta] program [argument]...\n", \
	    self, self


int main(int argc, char * const * argv) {
	std::optional<int> diff;
	for(auto && [arg, val] : vore::opt::get{argc, argv, "+n:", {{"adjustment", required_argument, nullptr, 'n'}}})
		switch(arg) {
			case 'n':
				if(!vore::parse_sint(val, diff.emplace(0))) {
					std::fprintf(stderr, "%s: -n %s: %s\n", argv[0], val, std::strerror(errno));
					return 125;
				}
				break;
			default:
				std::fprintf(stderr, USAGE(argv[0]));
				return 125;
		}
	if(diff && !*(argv + optind)) {
		std::fprintf(stderr, USAGE(argv[0]));
		return 125;
	}


	if(!*(argv + optind)) {
		std::fprintf(stdout, "%d\n", nice(0));
		return vore::flush_stdout(argv[0]) * 125;
	} else {
		errno = 0;
		if(nice(diff.value_or(10)) == -1 && errno)
			std::fprintf(stderr, "%s: %s\n", argv[0], std::strerror(errno));

		execvp(argv[optind], argv + optind);
		int exec_err = errno;
		std::fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], std::strerror(exec_err));
		return exec_err == ENOENT ? 127 : 126;
	}
}

M include/vore-numeric => include/vore-numeric +24 -0
@@ 37,5 37,29 @@ namespace vore {

			return true;
		}

		template <class T>
		bool parse_sint(const char * val, T & out) {
			if(val[0] == '\0') {
				errno = EINVAL;
				return false;
			}

			char * end{};
			errno = 0;
			out   = static_cast<T>(std::strtoll(val, &end, 0));
			if(errno)
				return false;
			if(out > std::numeric_limits<T>::max() || out < std::numeric_limits<T>::min()) {
				errno = ERANGE;
				return false;
			}
			if(*end != '\0') {
				errno = EINVAL;
				return false;
			}

			return true;
		}
	}
}

M include/vore-size => include/vore-size +1 -0
@@ 9,6 9,7 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <errno.h>
#include <optional>
#include <string>
#include <string_view>

A man/nice.1 => man/nice.1 +147 -0
@@ 0,0 1,147 @@
.\" SPDX-License-Identifier: 0BSD
.\"
.Dd
.Dt NICE 1
.Os
.
.Sh NAME
.Nm nice
.Nd adjust scheduling priority
.Sh SYNOPSIS
.Nm
.Nm
.Op Fl n Ar diff
.Ar program
.Oo Ar argument Oc Ns …
.
.Sh DESCRIPTION
With
.Ar program ,
adds
.Ar diff Pq default Sy 10
to the niceness, then executes
.Ar program argument Ns s ;
otherwise prints current niceness.
.Pp
Niceness, ranging
.Li -20..19 ,
reflects the (inverse) scheduling priority.
.Ar diff
may be any integer, but it will be clamped to that range by the system;
lowering niceness (increasing priority) is a privileged operation: if it fails, a diagnostic is issued, but
.Ar program
is still executed.
.
.Sh OPTIONS
.Bl -tag -compact -width "-n, --adjustment=diff"
.It Fl n , -adjustment Ns = Ns Ar diff
Alter niceness by this much.
Default:
.Sy 10 .
.El
.
.Sh ENVIRONMENT
.Bl -tag -compact -width "HOME"
.It Ev PATH
In which
.Ar program
is searched, confer
.Xr execvp 3 .
.El
.
.Sh EXIT STATUS
.Bl -tag -compact -width "all others"
.It Sy 127
.Ar program
wasn't found.
.It Sy 126
.Ar program
exists, but couldn't be executed for a different reason.
.It Sy 125
internal error.
.It All others
returned by
.Ar program .
.El
.
.Sh SEE ALSO
.Xr nice 3
.
.Sh STANDARDS
Conforms to
.St -p1003.1-2008 .
.Pp
The
.No no- Ns Ar program
behaviour is an extension, also present on the GNU system.
.Pp
The default
.Fl n
value is
.Sy 10
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
.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".
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
renamed it to
.Xr nice II ,
noting "Once done, there is no way to restore a process to normal priority.".
.Pp
.At v4
sees
.Xr nice II
amended with a priority argument and a noted range of "20 to -220"
(incorrect, of course \(em it's a maximum of
.Sy 19
and a minimum of
.Sy 0
for regular users and
.Dv CHAR_MIN
for root).
.Sy 16
is recommended for "users who wish to execute long-running programs without flak from the administration.".
Indeed, this is what the new
.Xr nice I
command does.
.Pp
.At v6
.Xr nice I
defaults to
.Sy 4
and accepts a
.Fl Ar niceness
argument to override it.
.Pp
.At v7
defaults to
.Sy 10
.Pq and uses Ev PATH No to find Ar program .
.Xr nice 2
now applies an increment, exactly like today, and notes a
.Li -20..20
range (wrong, of course; the actual range is
.Li -20..19 ,
like today).
.Pp
.St -xpg4 \" SUSv1, but groff doesn't have it
standardises it, except it invents
.Fl n Ar diff
to supersede
.Fl Ar diff ,
noting the latter as obsolete, as it violates the Utility Syntax Guidelines;
.St -p1003.1-2001 \" SUSv3, but we already didn't use SUS in the first one
removes it.
.Fl n
was likely chosen because the
.At v6
and later usage strings are
.Dl "usage: nice [ -n ] command"

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

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

out="$("$nice" 2>&3)" || echo "nice: failed?" >&3
[ "$out" = "0" ]      || echo "nice: niceness wrong" >&3

out="$("$nice" -n0 "$nice" 2>&3)" || echo "nice: -n0 nice failed?" >&3
[ "$out" = "0" ]                  || echo "nice: -n0 nice niceness wrong" >&3

out="$("$nice" -n999 "$nice" 2>&3)" || echo "nice: -n999 nice failed?" >&3
[ "$out" = "19" ]                   || echo "nice: -n999 nice niceness wrong" >&3

out="$("$nice" "$nice" 2>&3)" || echo "nice: nice failed?" >&3
[ "$out" = "10" ]             || echo "nice: nice niceness wrong" >&3

out="$("$nice" "$nice" "$nice" 2>&3)" || echo "nice: nice nice failed?" >&3
[ "$out" = "19" ]                     || echo "nice: nice nice niceness wrong" >&3

out="$("$nice" "$nice" "$nice" "$nice" 2>&3)" || echo "nice: nice nice nice failed?" >&3
[ "$out" = "19" ]                             || echo "nice: nice nice nice niceness wrong" >&3

out="$("$nice" -n1 "$nice" -n1 "$nice" -n1 "$nice" 2>&3)" || echo "nice: -n1 nice -n1 nice -n1 nice failed?" >&3
[ "$out" = "3" ]                                          || echo "nice: -n1 nice -n1 nice -n1 nice niceness wrong" >&3

out="$("$nice" -n-1 "$nice" 2>"${tmpdir}err")" || echo "nice: -n-1 nice failed?" >&3
[ "$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

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

errstr="$("$nice" 2>&1 > /dev/full)"; err=$?
[ "$err" -eq 125 ] || echo "nice: /dev/full okay?" >&3
[ -n "$errstr" ]   || echo "nice: stderr empty for /dev/full" >&3

M tests/paste/test => tests/paste/test +2 -2
@@ 18,8 18,8 @@ for s in '' '-s'; do
done
wait

errstr="$("$paste" /ENOENT 2>&1 > /dev/full)" && echo "paste: /dev/full okay?" >&3
[ -n "$errstr" ]                              || echo "paste: stderr empty for /dev/full" >&3
errstr="$("$paste" /ENOENT 2>&1 >&3)" && echo "paste: /ENOENT okay?" >&3
[ -n "$errstr" ]                      || echo "paste: stderr empty for /ENOENT" >&3

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