~nabijaczleweli/voreutils

6a2c23ac2c86017a3156ca781f1a7017316c1f54 — наб 2 months ago 08e8964
Splice, if possible, in cat

This would've waited for after I did the big manual review but I need to
prove a point
2 files changed, 37 insertions(+), 2 deletions(-)

M cmd/cat.cpp
M tests/cat/test
M cmd/cat.cpp => cmd/cat.cpp +32 -0
@@ 3,11 3,13 @@

#include <cstring>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <string_view>
#include <vector>
#include <vore-file>
#include <vore-getopt>
#include <vore-numeric>
#include <vore-optarg>
#include <vore-print>
#include <vore-span>


@@ 128,6 130,22 @@ int main(int argc, char * const * argv) {


	bool was_empty{}, err{}, need_lineno = true;
#if __linux__
	bool should_splice = !squeeze && !numbering && !tx;
	if(struct stat sb; !fstat(1, &sb) && S_ISFIFO(sb.st_mode)) {
		// stdout is "our" pipe – bump the size as high as it can go to promote big splices
		// (even if we aren't splicing, this will, potentially drastically, lower the scheduler overhead)
		// TODO: rethink if we want to maybe bump input buffers too
		unsigned max = 1048576;  // fs/pipe.c default
		if(vore::file::fd<false> mps{"/proc/sys/fs/pipe-max-size", O_RDONLY | O_CLOEXEC}; mps != -1) {
			char buf[10 + 1]{};
			if(read(mps, buf, sizeof(buf) - 1) != -1)
				vore::parse_uint(buf, max);  // untouched if fails (but it won't)
		}
		if(fcntl(1, F_GETPIPE_SZ) < max)  // root can bump this past the limit: don't downgrade
			(void)fcntl(1, F_SETPIPE_SZ, max);
	}
#endif
	std::uint64_t lineno{};
	char buf[64 * 1024];
	for(auto file : vore::opt::args{*(argv + optind) ? (argv + optind) : default_files}) {


@@ 139,6 157,20 @@ int main(int argc, char * const * argv) {
		}


#if __linux__
		// Only splice if no tx (obv), but also if we've never used stdio, so as not to interfere with buffers/not lose the flush errors/&c.
		// Realistically, I think this should cover 98% of spliceable cases and degrades gracefully.
		if(should_splice) {
			ssize_t rd;
			while((rd = splice(fd, NULL, 1, NULL, 128 * 1024 * 1024, SPLICE_F_MOVE | SPLICE_F_MORE)) > 0)
				;
			if(rd == -1)
				should_splice = false;  // All errors are better-served when detected by the read()/fwrite() loop below
			else
				continue;
		}
#endif

		for(ssize_t rd;;) {
			while((rd = read(fd, buf, sizeof(buf))) == -1 && errno == EINTR)
				;

M tests/cat/test => tests/cat/test +5 -2
@@ 17,8 17,6 @@ echo g > "${tmpdir}xac"
[ "$(echo -n : | "$cat" "${tmpdir}xaa" - "${tmpdir}xab" - "${tmpdir}xac")" = "abc:defg" ] || echo "cat: cat.1-2.1 wrong"
# cat.1-2.2 untestable

rm -rf "$tmpdir"


cd data 2>&3 || exit
for f in $files; do


@@ 30,6 28,9 @@ for f in $files; do
            for t in '' -t; do
              for e in '' -e; do
                for A in '' -A; do
                  "$cat" $s $b $T $E $v $t $e $A "$f" > "${tmpdir}$s $b $T $E $v $t $e $A $f"  # inhibit splice
                  cmp "${tmpdir}$s $b $T $E $v $t $e $A $f" "$f.d/$f-$s$b$T$E$v$t$e$A" >&3 || echo "cat: $s $b $T $E $v $t $e $A $f to file wrong" >&3

                  "$cat" $s $b $T $E $v $t $e $A "$f"               | cmp - "$f.d/$f-$s$b$T$E$v$t$e$A" >&3 || echo "cat: $s $b $T $E $v $t $e $A $f wrong" >&3
                  echo -n | "$cat" $s $b $T $E $v $t $e $A - "$f"   | cmp - "$f.d/$f-$s$b$T$E$v$t$e$A" >&3 || echo "cat: $s $b $T $E $v $t $e $A - $f wrong" >&3
                  echo -n | "$cat" $s $b $T $E $v $t $e $A - "$f" - | cmp - "$f.d/$f-$s$b$T$E$v$t$e$A" >&3 || echo "cat: $s $b $T $E $v $t $e $A - $f - wrong" >&3


@@ 49,6 50,8 @@ done
wait


rm -rf "$tmpdir"

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