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:
A | cmd/pat.1 | 29 | +++++++++++++++++++++++++++++ |
A | cmd/pat.c | 89 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | test-cmd/Kyuafile | 1 | + |
A | test-cmd/pat | 78 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
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
+}