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
+}