logo

utils

~/.local/bin tools and git-hooks git clone https://hacktivis.me/git/utils.git
commit: dbee8b2f41dd7889f5cb46248f3c91ee9d7f9661
parent bf5af2869009cbd16b8e1a513bb92ff4abfe9866
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Wed, 22 Feb 2023 16:47:01 +0100

cmd/pat: New command

Diffstat:

Acmd/pat.129+++++++++++++++++++++++++++++
Acmd/pat.c89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest-cmd/Kyuafile1+
Atest-cmd/pat78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 197 insertions(+), 0 deletions(-)

diff --git a/cmd/pat.1 b/cmd/pat.1 @@ -0,0 +1,29 @@ +.\" Collection of Unix tools, comparable to coreutils +.\" Copyright 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +.\" SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only +.Dd 2023-02-22 +.Dt PAT 1 +.Os +.Sh NAME +.Nm pat +.Nd print concatenated files +.Sh SYNOPSIS +.Nm +.Op Ar files ... +.Sh DESCRIPTION +.Nm +writes the number of files to be read, then in sequence: +prints their filename; then reads the +.Ar file +and writes it on the standard output. +If no +.Ar file +is given, +.Nm +uses standard input as one. +.Sh EXIT STATUS +.Ex -std +.Sh STANDARDS +None applicable. +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me diff --git a/cmd/pat.c b/cmd/pat.c @@ -0,0 +1,89 @@ +// Collection of Unix tools, comparable to coreutils +// SPDX-FileCopyrightText: 2017-2023 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 <errno.h> /* errno */ +#include <fcntl.h> /* open(), O_RDONLY */ +#include <stdio.h> /* fprintf(), fwrite(), BUFSIZ */ +#include <string.h> /* strerror(), strncmp() */ +#include <unistd.h> /* read(), close() */ + +int files = 1; + +int +concat(int fd, const char *fdname) +{ + ssize_t c; + char buf[BUFSIZ]; + + // File Number as an esccape/containment solution + printf("\n### File %d << %s >> ###\n", files++, fdname); + + while((c = read(fd, buf, sizeof(buf))) > 0) + { + if(fwrite(buf, (size_t)c, 1, stdout) < 0) + { + fprintf(stderr, "pat: Error writing: %s\n", strerror(errno)); + return 1; + } + } + + if(c < 0) + { + fprintf(stderr, "pat: Error reading ‘%s’: %s\n", fdname, strerror(errno)); + return 1; + } + + return 0; +} + +int +main(int argc, char *argv[]) +{ + if(argc <= 1) + { + printf("### 1 Files ###"); + return concat(0, "<stdin>"); + } + + // no \n, concat starts with one + printf("### %d Files ###", argc - 1); + + for(int argi = 1; argi < argc; argi++) + { + if(strncmp(argv[argi], "-", 2) == 0) + { + if(concat(0, "<stdin>") != 0) + { + return 1; + } + } + else if(strncmp(argv[argi], "--", 3) == 0) + { + continue; + } + else + { + int fd = open(argv[argi], O_RDONLY); + if(fd < 0) + { + fprintf(stderr, "pat: Error opening ‘%s’: %s\n", argv[argi], strerror(errno)); + return 1; + } + + if(concat(fd, argv[argi]) != 0) + { + return 1; + } + + if(close(fd) < 0) + { + fprintf(stderr, "pat: Error closing ‘%s’: %s\n", argv[argi], strerror(errno)); + return 1; + } + } + } + + return 0; +} diff --git a/test-cmd/Kyuafile b/test-cmd/Kyuafile @@ -23,6 +23,7 @@ atf_test_program{name="link", required_files=basedir.."/cmd/link", timeout=1} atf_test_program{name="lolcat", required_files=basedir.."/cmd/lolcat", timeout=1} atf_test_program{name="mdate", required_files=basedir.."/cmd/mdate", timeout=1} atf_test_program{name="memsys", required_files=basedir.."/cmd/memsys", timeout=1} +atf_test_program{name="pat", required_files=basedir.."/cmd/pat", timeout=1} atf_test_program{name="pwd", required_files=basedir.."/cmd/pwd", timeout=1} atf_test_program{name="seq", required_files=basedir.."/cmd/seq", timeout=1} atf_test_program{name="sizeof", required_files=basedir.."/cmd/sizeof", timeout=1} diff --git a/test-cmd/pat b/test-cmd/pat @@ -0,0 +1,78 @@ +#!/usr/bin/env atf-sh +# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only + +atf_test_case allfile +allfile_body() { + atf_check -o file:outputs/pat/all_bytes ../cmd/pat outputs/pat/all_bytes +} + +atf_test_case allinput +allinput_body() { + atf_check -o file:outputs/pat/all_bytes ../cmd/pat <outputs/pat/all_bytes +} + +atf_test_case alldashinput +alldashinput_body() { + atf_check -o file:outputs/pat/all_bytes ../cmd/pat - <outputs/pat/all_bytes +} + +atf_test_case devnull +devnull_body() { + atf_check ../cmd/pat /dev/null + atf_check ../cmd/pat </dev/null + atf_check ../cmd/pat - </dev/null +} + +atf_test_case noperm cleanup +noperm_body() { + touch inputs/chmod_000 || atf_fail "touching chmod_000" + chmod 0000 inputs/chmod_000 || atf_fail "chmod 0000 chmod_000" + # shellcheck disable=SC1112 + atf_check -s exit:1 -e 'inline:pat: Error opening ‘inputs/chmod_000’: Permission denied\n' ../cmd/pat inputs/chmod_000 +} +noperm_cleanup() { + chmod 0600 inputs/chmod_000 || atf_fail "chmod 0600 chmod_000" + rm inputs/chmod_000 || atf_fail "rm chmod_000" +} + +atf_test_case devfull +devfull_body() { + atf_check -s exit:1 -e 'inline:pat: Error writing: No space left on device\n' sh -c '../cmd/pat outputs/pat/all_bytes >/dev/full' + atf_check -s exit:1 -e 'inline:pat: Error writing: No space left on device\n' sh -c '../cmd/pat <outputs/pat/all_bytes >/dev/full' + atf_check -s exit:1 -e 'inline:pat: Error writing: No space left on device\n' sh -c '../cmd/pat - <outputs/pat/all_bytes >/dev/full' +} + +atf_test_case readslash +readslash_body() { + [ "$(uname -s)" = "NetBSD" ] && atf_skip "NetBSD allows to read directories" + + # shellcheck disable=SC1112 + atf_check -s exit:1 -e 'inline:pat: Error reading ‘/’: Is a directory\n' ../cmd/pat / +} + +atf_test_case enoent +enoent_body() { + # shellcheck disable=SC1112 + atf_check -s exit:1 -e 'inline:pat: Error opening ‘/var/empty/e/no/ent’: No such file or directory\n' ../cmd/pat /var/empty/e/no/ent +} + +atf_test_case doubledash +doubledash_body() { + atf_check -o file:outputs/pat/all_bytes -- ../cmd/pat -- outputs/pat/all_bytes + # shellcheck disable=SC1112 + atf_check -s exit:1 -e 'inline:pat: Error opening ‘---’: No such file or directory\n' -o empty -- ../cmd/pat --- outputs/pat/all_bytes +} + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + atf_add_test_case allfile + atf_add_test_case allinput + atf_add_test_case alldashinput + atf_add_test_case devnull + atf_add_test_case noperm + atf_add_test_case devfull + atf_add_test_case readslash + atf_add_test_case enoent + atf_add_test_case doubledash +}