git: 09e0a909fb07 - stable/14 - tee: try opening as a UNIX socket if open(2) fails

From: Kyle Evans <kevans_at_FreeBSD.org>
Date: Sun, 27 Apr 2025 18:59:50 UTC
The branch stable/14 has been updated by kevans:

URL: https://cgit.FreeBSD.org/src/commit/?id=09e0a909fb07615ed97c8648a24b541840bc1fa7

commit 09e0a909fb07615ed97c8648a24b541840bc1fa7
Author:     Kyle Evans <kevans@FreeBSD.org>
AuthorDate: 2025-04-20 16:34:52 +0000
Commit:     Kyle Evans <kevans@FreeBSD.org>
CommitDate: 2025-04-27 18:59:34 +0000

    tee: try opening as a UNIX socket if open(2) fails
    
    If we get EOPNOTSUPP from open() failing, then it may just be that we're
    looking at a unix(4) socket instead of a normal file/device or whatnot.
    Fallback to trying to open it as a unix(4) socket, which is a useful
    feature that doesn't add much complexity.
    
    Reviewed by:    des, emaste, markj
    
    (cherry picked from commit 1b3748977f28c70e0b161fb476bf4e075bcc5940)
---
 usr.bin/tee/tee.1             |  8 +++++++-
 usr.bin/tee/tee.c             | 46 +++++++++++++++++++++++++++++++++++++++++--
 usr.bin/tee/tests/tee_test.sh | 32 ++++++++++++++++++++++++++++++
 3 files changed, 83 insertions(+), 3 deletions(-)

diff --git a/usr.bin/tee/tee.1 b/usr.bin/tee/tee.1
index b7673ea0ce61..b4d38fbb5b24 100644
--- a/usr.bin/tee/tee.1
+++ b/usr.bin/tee/tee.1
@@ -30,7 +30,7 @@
 .\"
 .\"     @(#)tee.1	8.1 (Berkeley) 6/6/93
 .\"
-.Dd October 30, 2022
+.Dd December 25, 2024
 .Dt TEE 1
 .Os
 .Sh NAME
@@ -71,6 +71,12 @@ 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
diff --git a/usr.bin/tee/tee.c b/usr.bin/tee/tee.c
index aeda9c34ac45..92c8913210aa 100644
--- a/usr.bin/tee/tee.c
+++ b/usr.bin/tee/tee.c
@@ -41,10 +41,12 @@ static char sccsid[] = "@(#)tee.c	8.1 (Berkeley) 6/6/93";
 #endif
 #endif /* not lint */
 
+#include <sys/types.h>
 #include <sys/capsicum.h>
 #include <sys/queue.h>
+#include <sys/socket.h>
 #include <sys/stat.h>
-#include <sys/types.h>
+#include <sys/un.h>
 
 #include <capsicum_helpers.h>
 #include <err.h>
@@ -64,6 +66,7 @@ struct entry {
 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
@@ -105,7 +108,7 @@ main(int argc, char *argv[])
 		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 {
@@ -161,3 +164,42 @@ add(int fd, const char *name)
 	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
@@ -64,6 +64,37 @@ sigint_ignored_body()
 	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
@@ -71,4 +102,5 @@ atf_init_test_cases()
 	atf_add_test_case multiple_file
 	atf_add_test_case append
 	atf_add_test_case sigint_ignored
+	atf_add_test_case unixsock
 }