diff --git a/usr.bin/tee/tee.1 b/usr.bin/tee/tee.1 index 9497a2aa921e..9884dcf37919 100644 --- a/usr.bin/tee/tee.1 +++ b/usr.bin/tee/tee.1 @@ -1,92 +1,98 @@ .\" Copyright (c) 1991, 1993 .\" The Regents of the University of California. All rights reserved. .\" .\" This code is derived from software contributed to Berkeley by .\" the Institute of Electrical and Electronics Engineers, Inc. .\" .\" 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. .\" 3. Neither the name of the University nor the names of its contributors .\" may be used to endorse or promote products derived from this software .\" without specific prior written permission. .\" .\" 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. .\" -.Dd October 30, 2022 +.Dd December 25, 2024 .Dt TEE 1 .Os .Sh NAME .Nm tee .Nd duplicate standard input .Sh SYNOPSIS .Nm .Op Fl ai .Op Ar .Sh DESCRIPTION The .Nm utility copies standard input to standard output, making a copy in zero or more files. The output is unbuffered. .Pp The following options are available: .Bl -tag -width indent .It Fl a Append the output to the files rather than overwriting them. .It Fl i Ignore the .Dv SIGINT signal. .El .Pp The following operands are available: .Bl -tag -width indent .It Ar file A pathname of an output .Ar file . .El .Pp The .Nm utility takes the default action for all signals, except in the event of the .Fl i option. +.Pp +This implementation of the +.Nm +utility may also write to +.Xr unix 4 +sockets. .Sh EXIT STATUS .Ex -std .Sh EXAMPLES Send the echoed message both to stdout and to the .Pa greetings.txt file: .Bd -literal -offset indent $ echo "Hello" | tee greetings.txt Hello .Ed .Sh STANDARDS The .Nm utility is expected to be .St -p1003.2 compatible. .Sh HISTORY The .Nm command first appeared in .At v7 . diff --git a/usr.bin/tee/tee.c b/usr.bin/tee/tee.c index f1d192ff315f..fb73b311a31b 100644 --- a/usr.bin/tee/tee.c +++ b/usr.bin/tee/tee.c @@ -1,151 +1,193 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1988, 1993 * The Regents of the University of California. 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. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * 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 #include #include #include #include #include #include #include struct entry { int fd; const char *name; STAILQ_ENTRY(entry) entries; }; static STAILQ_HEAD(, entry) head = STAILQ_HEAD_INITIALIZER(head); static void add(int, const char *); +static int tee_open(const char *, int); static void usage(void) __dead2; int main(int argc, char *argv[]) { char *bp, *buf; struct entry *p; int append, ch, exitval, fd, n, oflags, rval, wval; #define BSIZE (8 * 1024) append = 0; while ((ch = getopt(argc, argv, "ai")) != -1) switch((char)ch) { case 'a': append = 1; break; case 'i': (void)signal(SIGINT, SIG_IGN); break; case '?': default: usage(); } argv += optind; argc -= optind; if ((buf = malloc(BSIZE)) == NULL) err(1, "malloc"); if (caph_limit_stdin() == -1 || caph_limit_stderr() == -1) err(EXIT_FAILURE, "unable to limit stdio"); add(STDOUT_FILENO, "stdout"); oflags = O_WRONLY | O_CREAT; if (append) oflags |= O_APPEND; else oflags |= O_TRUNC; for (exitval = 0; *argv; ++argv) { - if ((fd = open(*argv, oflags, DEFFILEMODE)) < 0) { + if ((fd = tee_open(*argv, oflags)) < 0) { warn("%s", *argv); exitval = 1; } else { add(fd, *argv); } } if (caph_enter() < 0) err(EXIT_FAILURE, "unable to enter capability mode"); while ((rval = read(STDIN_FILENO, buf, BSIZE)) > 0) STAILQ_FOREACH(p, &head, entries) { n = rval; bp = buf; do { if ((wval = write(p->fd, bp, n)) == -1) { warn("%s", p->name); exitval = 1; break; } bp += wval; } while (n -= wval); } if (rval < 0) err(1, "read"); exit(exitval); } static void usage(void) { (void)fprintf(stderr, "usage: tee [-ai] [file ...]\n"); exit(1); } static void add(int fd, const char *name) { struct entry *p; cap_rights_t rights; if (fd == STDOUT_FILENO) { if (caph_limit_stdout() == -1) err(EXIT_FAILURE, "unable to limit stdout"); } else { cap_rights_init(&rights, CAP_WRITE, CAP_FSTAT); if (caph_rights_limit(fd, &rights) < 0) err(EXIT_FAILURE, "unable to limit rights"); } if ((p = malloc(sizeof(struct entry))) == NULL) err(1, "malloc"); p->fd = fd; p->name = name; STAILQ_INSERT_HEAD(&head, p, entries); } + +static int +tee_open(const char *path, int oflags) +{ + struct sockaddr_un sun = { .sun_family = AF_UNIX }; + size_t pathlen; + int fd; + + if ((fd = open(path, oflags, DEFFILEMODE)) >= 0) + return (fd); + + if (errno != EOPNOTSUPP) + return (-1); + + pathlen = strnlen(path, sizeof(sun.sun_path)); + if (pathlen >= sizeof(sun.sun_path)) + goto failed; + + /* + * For EOPNOTSUPP, we'll try again as a unix(4) socket. Any errors here + * we'll just surface as the original EOPNOTSUPP since they may not have + * intended for this. + */ + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + goto failed; + + (void)strlcpy(&sun.sun_path[0], path, sizeof(sun.sun_path)); + sun.sun_len = SUN_LEN(&sun); + + if (connect(fd, (const struct sockaddr *)&sun, sun.sun_len) == 0) + return (fd); + +failed: + if (fd >= 0) + close(fd); + errno = EOPNOTSUPP; + return (-1); +} diff --git a/usr.bin/tee/tests/tee_test.sh b/usr.bin/tee/tests/tee_test.sh index 6ac733f2e58f..cf8e74dd47e7 100644 --- a/usr.bin/tee/tests/tee_test.sh +++ b/usr.bin/tee/tests/tee_test.sh @@ -1,74 +1,106 @@ # # Copyright (c) 2024 Kyle Evans # # SPDX-License-Identifier: BSD-2-Clause # atf_test_case single_file single_file_body() { atf_check -o inline:"text\n" -x "echo text | tee file" atf_check -o inline:"text\n" cat file } atf_test_case device device_body() { atf_check -e inline:"text\n" -o inline:"text\n" -x \ "echo text | tee /dev/stderr" } atf_test_case multiple_file multiple_file_body() { atf_check -o inline:"text\n" -x "echo text | tee file1 file2" atf_check -o inline:"text\n" cat file1 atf_check -o inline:"text\n" cat file2 } atf_test_case append append_body() { atf_check -o ignore -x "echo text | tee file" atf_check -o inline:"text\n" cat file # Should overwrite if done again atf_check -o ignore -x "echo text | tee file" atf_check -o inline:"text\n" cat file # Should duplicate if we use -a atf_check -o ignore -x "echo text | tee -a file" atf_check -o inline:"text\ntext\n" cat file } atf_test_case sigint_ignored sigint_ignored_head() { # This is most cleanly tested with interactive input, to avoid adding # a lot of complexity in trying to manage an input and signal delivery # dance purely in shell. atf_set "require.progs" "porch" } sigint_ignored_body() { # sigint.orch will write "text" to the file twice if we're properly # ignoring SIGINT, so we'll do one test to confirm that SIGINT is not # being ignored by porch(1), then another to confirm that tee(1) will # ignore SIGINT when instructed to. atf_check -s exit:1 -e ignore \ porch -f $(atf_get_srcdir)/sigint.orch tee file atf_check -o inline:"text\n" cat file atf_check porch -f $(atf_get_srcdir)/sigint.orch tee -i file atf_check -o inline:"text\ntext\n" cat file } +atf_test_case unixsock "cleanup" +unixsock_pidfile="nc.pid" + +unixsock_body() +{ + outfile=out.log + + nc -lU logger.sock > "$outfile" & + npid=$! + + atf_check -o save:"$unixsock_pidfile" echo "$npid" + + # Wait for the socket to come online, just in case. + while [ ! -S logger.sock ]; do + sleep 0.1 + done + + atf_check -o inline:"text over socket\n" -x \ + 'echo "text over socket" | tee logger.sock' + + atf_check rm "$unixsock_pidfile" + atf_check -o inline:"text over socket\n" cat "$outfile" +} +unixsock_cleanup() +{ + if [ -s "$unixsock_pidfile" ]; then + read npid < "$unixsock_pidfile" + kill "$npid" + fi +} + atf_init_test_cases() { atf_add_test_case single_file atf_add_test_case device atf_add_test_case multiple_file atf_add_test_case append atf_add_test_case sigint_ignored + atf_add_test_case unixsock }