logo

utils

~/.local/bin tools and git-hooks git clone https://hacktivis.me/git/utils.git
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:

Mbin/Makefile.config2+-
Abin/tee.c98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest-bin/Kyuafile1+
Atest-bin/tee32++++++++++++++++++++++++++++++++
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 +}