Index: usr.bin/sponge/Makefile =================================================================== --- /dev/null +++ usr.bin/sponge/Makefile @@ -0,0 +1,8 @@ +# $FreeBSD$ + +PROG= sponge + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +.include Index: usr.bin/sponge/sponge.1 =================================================================== --- /dev/null +++ usr.bin/sponge/sponge.1 @@ -0,0 +1,75 @@ +.\" Eitan Adler. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd November 1, 2017 +.Dt SPONGE 1 +.Os +.Sh NAME +.Nm sponge +.Nd buffer stdin and write to stdout +.Sh SYNOPSIS +.Nm +.Op Fl a +.Ar filename +.Sh DESCRIPTION +The +.Nm +utility reads standard in until complete, then opens +the output file and writes to it. +This makes it useful in pipelines that read a file and then write to it. +These options are available: +.Bl -tag -width indent +.It Fl a +Open +.Ar filename +in append mode. +.El +.Pp +If an attempt to allocate memory fails, +.Nm +fails without output. +The file is written even if earlier components +of the pipeline failed. +.Sh SEE ALSO +.Xr builtin 1 , +.Xr csh 1 , +.Xr getrusage 2 , +.Xr tee 1 , +.Xr wait 2 +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +A +.Pa file +can be be sorted "in place" by executing +.Cm sort file | sponge file +.Sh HISTORY +The +.Nm +utility was written by +.An Eitan Adler Aq Mt eadler@FreeBSD.org +and first appeared +in +.Fx 12.0 . Index: usr.bin/sponge/sponge.c =================================================================== --- /dev/null +++ usr.bin/sponge/sponge.c @@ -0,0 +1,187 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2017 Eitan Adler + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_BUF_SIZE 16384 +#define DEFAULT_BUF_CNT 12 + +static int flag_append = 0; + +static void usage(void); +static void *safe_malloc(size_t size); +static void *safe_calloc(size_t count, size_t size); +static void *safe_reallocf(void *ptr, size_t size); + +static void * +safe_malloc(size_t size) +{ + void *ret; + + ret = malloc(size); + if (ret == NULL) { + err(1, "malloc failed"); + } + return (ret); +} + +static void * +safe_calloc(size_t count, size_t size) +{ + void *ret; + + ret = calloc(count, size); + if (ret == NULL) { + err(1, "calloc failed"); + } + return (ret); +} + +static void * +safe_reallocf(void *ptr, size_t size) +{ + void *ret; + + ret = reallocf(ptr, size); + if (ret == NULL) { + err(1, "reallocf failed"); + } + return (ret); +} + +static void +usage(void) +{ + fprintf(stderr, "usage: sponge [-a] filename\n"); +} + +int +main(int argc, char* argv[]) +{ + struct iovec *iov; + char *buf; + char *outfile; + ssize_t i; + size_t bufcnt; + size_t whichbuf; + size_t bufremain; + long maxiovec; + int fd; + int openflags = O_WRONLY; + int opt; + + while ((opt = getopt(argc, argv, "ah")) != -1) { + switch (opt) { + case 'a': + flag_append = 1; + break; + case 'h': + usage(); + exit(0); + case '?': + default: + usage(); + exit(1); + } + } + + if (optind < argc) { + outfile = argv[optind]; + } + + + bufcnt = DEFAULT_BUF_CNT; + whichbuf = 0; + iov = safe_calloc(bufcnt, sizeof(*iov)); + + for (;;) { + buf = safe_malloc(DEFAULT_BUF_SIZE); + i = read(STDIN_FILENO, buf, DEFAULT_BUF_SIZE); + if (whichbuf == bufcnt) { + bufcnt *= 2; + iov = safe_reallocf(iov, bufcnt * sizeof(*iov)); + } + if (i < 0) { + err(1, "read failed"); + } + if (i == 0) { + free(buf); + break; + } + iov[whichbuf].iov_base = buf; + iov[whichbuf].iov_len = i; + whichbuf++; + } + + if (outfile) { + if (flag_append) { + openflags |= O_APPEND; + } else { + openflags |= O_TRUNC; + } + fd = open(outfile, openflags); + } + else { + fd = STDOUT_FILENO; + } + + if (fd < 0) { + err(1, "failed to open"); + } + + maxiovec = sysconf(_SC_IOV_MAX); + if (maxiovec == -1) { + maxiovec = _XOPEN_IOV_MAX; + } + bufcnt = whichbuf; + bufremain = bufcnt; + + while (bufremain > 0) { + whichbuf = (bufremain < maxiovec) ? bufremain : maxiovec; + bufremain -= whichbuf; + + i = writev(fd, iov, whichbuf); + if (i < 0) { + err(1, "failed to write"); + } + } + + if (outfile) { + i = close(fd); + if (i < 0) { + err(1, "failed to close"); + } + } +} Index: usr.bin/sponge/tests/Makefile =================================================================== --- /dev/null +++ usr.bin/sponge/tests/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +PACKAGE= ${TESTBASE}/usr.bin/sponge + +TESTSDIR= ${TESTSBASE}/usr.bin/sponge/tests + +ATF_TESTS_SH= cp_test + +.include Index: usr.bin/sponge/tests/Makefile.depend =================================================================== --- /dev/null +++ usr.bin/sponge/tests/Makefile.depend @@ -0,0 +1,18 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/atf/libatf-c \ + lib/libc \ + lib/libcompiler_rt \ + + +.include + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif Index: usr.bin/sponge/tests/sponge_test.sh =================================================================== --- /dev/null +++ usr.bin/sponge/tests/sponge_test.sh @@ -0,0 +1,48 @@ +# $FreeBSD$ +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright 2017 Eitan Adler +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +atf_test_case simple +simple_body() { + printf 'd\ne\na\n' >| x + printf 'a\nd\ne\n' >| y + sort x | sponge x + + atf_check -s exit:0 -o empty -e empty 'sort x | sponge x' + + if ! cmp -s x y; then + echo "x and y differ, but they should be equal" + diff -u x y + atf_fail "Original and copy do not match" + fi + +} + +atf_init_test_cases() { + atf_add_test_case simple +} Index: usr.bin/tee/tee.1 =================================================================== --- usr.bin/tee/tee.1 +++ usr.bin/tee/tee.1 @@ -72,6 +72,8 @@ except in the event of the .Fl i option. +.Sh SEE ALSO +.Xr sponge 1 .Sh EXIT STATUS .Ex -std .Sh STANDARDS Index: usr.bin/tests/Makefile =================================================================== --- usr.bin/tests/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -# $FreeBSD$ - -PACKAGE= tests - -.PATH: ${SRCTOP}/tests -KYUAFILE= yes - -${PACKAGE}FILES+= regress.m4 - -.include Index: usr.bin/tests/Makefile.depend =================================================================== --- usr.bin/tests/Makefile.depend +++ /dev/null @@ -1,11 +0,0 @@ -# $FreeBSD$ -# Autogenerated - do NOT edit! - -DIRDEPS = \ - - -.include - -.if ${DEP_RELDIR} == ${_DEP_RELDIR} -# local dependencies - needed for -jN in clean tree -.endif Index: usr.bin/tests/regress.m4 =================================================================== --- usr.bin/tests/regress.m4 +++ /dev/null @@ -1,59 +0,0 @@ -# $FreeBSD$ - -dnl A library of routines for doing regression tests for userland utilities. - -dnl Start up. We initialise the exit status to 0 (no failure) and change -dnl into the directory specified by our first argument, which is the -dnl directory to run the tests inside. -define(`REGRESSION_START', -TESTDIR=$1 -if [ -z "$TESTDIR" ]; then - TESTDIR=. -fi -cd $TESTDIR - -STATUS=0) - -dnl Check $? to see if we passed or failed. The first parameter is the test -dnl which passed or failed. It may be nil. -define(`REGRESSION_PASSFAIL', -if [ $? -eq 0 ]; then - echo "ok - $1 # Test detected no regression. (in $TESTDIR)" -else - STATUS=$? - echo "not ok - $1 # Test failed: regression detected. See above. (in $TESTDIR)" -fi) - -dnl An actual test. The first parameter is the test name. The second is the -dnl command/commands to execute for the actual test. Their exit status is -dnl checked. It is assumed that the test will output to stdout, and that the -dnl output to be used to check for regression will be in regress.TESTNAME.out. -define(`REGRESSION_TEST', -$2 | diff -u ${SRCDIR:-.}/regress.$1.out - -REGRESSION_PASSFAIL($1)) - -dnl A freeform regression test. Only exit status is checked. -define(`REGRESSION_TEST_FREEFORM', -$2 -REGRESSION_PASSFAIL($1)) - -dnl A regression test like REGRESSION_TEST, except only regress.out is used -dnl for checking output differences. The first argument is the command, the -dnl second argument (which may be empty) is the test name. -define(`REGRESSION_TEST_ONE', -$1 | diff -u ${SRCDIR:-.}/regress.out - -REGRESSION_PASSFAIL($2)) - -dnl A fatal error. This will exit with the given status (first argument) and -dnl print the message (second argument) prefixed with the string "FATAL :" to -dnl the error stream. -define(`REGRESSION_FATAL', -echo "Bail out! $2 (in $TESTDIR)" > /dev/stderr -exit $1) - -dnl Cleanup. Exit with the status code of the last failure. Should probably -dnl be the number of failed tests, but hey presto, this is what it does. This -dnl could also clean up potential droppings, if some forms of regression tests -dnl end up using mktemp(1) or such. -define(`REGRESSION_END', -exit $STATUS)