commit: 3dad7ea700332cd309f901ae90b6a8d1a095753a
parent 2aa161fb35fc542ee070f1a2b4d7cbf5e7f2762c
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Sat, 10 Jul 2021 17:52:05 +0200
bin/tee: New program
Diffstat:
4 files changed, 132 insertions(+), 1 deletion(-)
diff --git a/bin/Makefile.config b/bin/Makefile.config
@@ -1,2 +1,2 @@
-EXE = args basename cat date del dirname echo false humanize lolcat mdate pwd range sizeof sname true tty xcd
+EXE = args basename cat date del dirname echo false humanize lolcat mdate pwd range sizeof sname tee true tty xcd
 MAN1 = basename.1 date.1 del.1 dirname.1 humanize.1 lolcat.1 sname.1
diff --git a/bin/tee.c b/bin/tee.c
@@ -0,0 +1,98 @@
+// Collection of Unix tools, comparable to coreutils
+// Copyright 2017-2021 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
+
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h> /* assert() */
+#include <errno.h>  /* errno */
+#include <stdio.h>  /* fprintf(), fgetc(), fputc(), fclose(), fopen() */
+#include <stdlib.h> /* malloc(), free(), abort() */
+#include <string.h> /* strerror() */
+#include <unistd.h> /* getopt(), opt… */
+
+void
+cleanup(FILE **fds)
+{
+	if(fds != NULL)
+	{
+		free(fds);
+	}
+}
+
+int
+main(int argc, char *argv[])
+{
+	const char *mode = "w";
+	FILE **fds = { NULL }; // Shut up GCC
+	int c;
+
+	while((c = getopt(argc, argv, ":ai")) != -1)
+	{
+		switch(c)
+		{
+		case 'a': mode = "a"; break;
+		case 'i': /* ignore SIGINT */; break;
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+
+	if(argc > 0)
+	{
+		fds = malloc(sizeof(FILE *) * argc);
+
+		if(!fds)
+		{
+			fprintf(stderr, "Cannot allocate fd array: %s\n", strerror(errno));
+			return 1;
+		}
+	}
+
+	for(int argi = 0; argi < argc; argi++)
+	{
+		assert(argv[argi]);
+
+		// POSIX: implementations shouldn't treat '-' as stdin
+		fds[argi] = fopen(argv[argi], mode);
+
+		if(fds[argi] == NULL)
+		{
+			fprintf(stderr, "Error opening ‘%s’: %s\n", argv[argi], strerror(errno));
+			cleanup(fds);
+			return 1;
+		}
+	}
+
+	// main loop, note that failed writes shouldn't make tee exit
+	while((c = fgetc(stdin)) != EOF)
+	{
+		if(fputc(c, stdout) == EOF)
+		{
+			fprintf(stderr, "Error writing ‘<stdout>’: %s\n", strerror(errno));
+			errno = 0;
+		}
+
+		for(int argi = 0; argi < argc; argi++)
+		{
+			if(fputc(c, fds[argi]) == EOF)
+			{
+				fprintf(stderr, "Error writing to argument %d: %s\n", argi, strerror(errno));
+				errno = 0;
+			}
+		}
+	}
+
+	// cleanup
+	for(int argi = 0; argi < argc; argi++)
+	{
+		if(fclose(fds[argi]) != 0)
+		{
+			abort();
+		}
+	}
+
+	cleanup(fds);
+
+	return 0;
+}
diff --git a/test-bin/Kyuafile b/test-bin/Kyuafile
@@ -5,6 +5,7 @@ test_suite("utils")
 -- /BEGIN/,$ | LC_ALL=C.UTF-8 sort
 atf_test_program{name="args"}
 atf_test_program{name="cat"}
+atf_test_program{name="tee"}
 atf_test_program{name="basename"}
 atf_test_program{name="dirname"}
 atf_test_program{name="sname"}
diff --git a/test-bin/tee b/test-bin/tee
@@ -0,0 +1,32 @@
+#!/usr/bin/env atf-sh
+atf_test_case noargs
+noargs_body() {
+	atf_check -o file:all_bytes ../bin/tee <all_bytes
+}
+
+atf_test_case writefile
+writefile_body() {
+	echo 'hello' > tmp_tee.log
+	atf_check -o file:all_bytes ../bin/tee tmp_tee.log <all_bytes
+	atf_check -o empty -s exit:1 grep hello tmp_tee.log
+}
+writefile_cleanup() {
+	rm tmp_tee.log
+}
+
+atf_test_case appendfile
+appendfile_body() {
+	echo 'hello' > tmp_tee.log
+	atf_check -o file:all_bytes ../bin/tee tmp_tee.log <all_bytes
+	atf_check -o file:all_bytes cat tmp_tee.log
+}
+appendfile_cleanup() {
+	rm tmp_tee.log
+}
+
+atf_init_test_cases() {
+	cd "$(atf_get_srcdir)"
+	atf_add_test_case noargs
+	atf_add_test_case writefile
+	atf_add_test_case appendfile
+}