@@ -5,3 +5,4 @@ src/**/*.o
src/false/false
src/true/true
src/rm/rm
+src/cat/cat
--- /dev/null
+.POSIX:
+
+.PHONY: all clean install uninstall
+
+include config.mk
+
+SRCS != echo *.c
+OBJS = ${SRCS:.c=.o}
+
+all: ${PROG}
+
+${PROG}: ${OBJS}
+ ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDLIBS}
+
+${PROG}.o: ${PROG}.c
+
+clean:
+ ${RM} -f ${OBJS} ${PROG}
+
+install: ${PROG} ${PROG}.1
+ mkdir -p ${DESTDIR}${PREFIX}/bin
+ install -m 755 ${PROG} ${DESTDIR}${PREFIX}/bin
+ mkdir -p ${DESTDIR}${MANPREFIX}/man1
+ gzip < ${PROG}.1 > ${DESTDIR}${MANPREFIX}/man1/${PROG}.1.gz
+
+uninstall:
+ ${RM} -f ${DESTDIR}${PREFIX}/bin/${PROG}
+ ${RM} -f ${DESTDIR}${MANPREFIX}/man1/${PROG}.1.gz
--- /dev/null
+.\" SPDX-License-Identifier: BSD-2-Clause
+
+.\" Copyright (c) 2022 Alessio Chiapperini
+
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+
+.Dd April 6, 2022
+.Dt CAT 1
+.Os
+.Sh NAME
+.Nm cat
+.Nd concatenate and print files in reverse.
+.Sh SYNOPSIS
+.Nm
+.Op Fl \&hu
+.Op Ar file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility reads files sequentially, writing them to standard output in reverse
+starting from the last character. The
+.Ar file
+operands are processed in command-line order. If
+.Ar file
+is a single dash
+.Pq Sq Fl
+or absent,
+.Nm
+reads from standard input.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl h
+Print usage information and exit.
+.It Fl u
+Write bytes from the input file to the standard output without delay as each is read.
+.El
+.Sh "EXIT STATUS"
+The
+.Nm
+utility exits 0 on success, and 1 if an error occurs.
+.Sh EXAMPLES
+Backup the file named
+.Ar foo
+and ask for confirmation:
+.Dl $ rm -i foo
+.Pp
+Any of these commands will backup the file
+.Ar -f :
+.Dl $ rm -- -f
+.Dl $ rm ./-f
+.Sh SEE ALSO
+.Xr rev 1
+.Sh AUTHOR
+.An Alessio Chiapperini Aq Mt alessio.chiapperini@\:nullbuffer.com
+
--- /dev/null
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Alessio Chiapperini
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _POSIX_C_SOURCE
+# define _POSIX_C_SOURCE 200112L
+#elif _POSIX_C_SOURCE < 200112L
+# error incompatible _POSIX_C_SOURCE level
+#endif
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define N 32768
+#define PREFETCH (32*N)
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: cat [-u] [file...]\n");
+ exit(1);
+}
+
+static void
+memrev(char *dst, const char *src, size_t len)
+{
+ for (size_t i = 0; i < len; i++) {
+ dst[i] = src[len-1-i];
+ }
+}
+
+static int
+print_rev(char *buf, off_t off)
+{
+ char dst[N];
+ int ret;
+ size_t blen = off % N ? off % N : N;
+
+ ret = 0;
+ while (off > 0) {
+ off -= blen;
+
+ if (off > PREFETCH) {
+ (void)posix_madvise(buf + off - PREFETCH, PREFETCH,
+ POSIX_MADV_WILLNEED);
+ }
+ memrev(dst, buf + off, blen);
+ if (write(STDOUT_FILENO, dst, blen) < (ssize_t)blen) {
+ ret = 1;
+ perror("cat");
+ goto err;
+ }
+
+ blen = N;
+ }
+
+err:
+ return (ret);
+}
+
+static int
+cat_buffered(const int fd)
+{
+ FILE *ifile;
+ ssize_t buflen, bufsize, nbufsize;
+ char *buf, *nbuf;
+ int ch;
+ int ret;
+
+ ifile = fdopen(fd, "r");
+ if (ifile == 0) {
+ ret = 1;
+ perror("cat");
+ goto out;
+ }
+
+ buflen = 0;
+ bufsize = BUFSIZ;
+ buf = calloc((size_t)bufsize, 1);
+ if (buf == 0) {
+ ret = 1;
+ perror("cat");
+ goto calloc_err;
+ }
+
+ while ((ch = fgetc(ifile)) != EOF) {
+ buf[buflen++] = (char)ch;
+
+ if (buflen == bufsize) {
+ nbufsize = bufsize << 1;
+ if ((nbuf = realloc(buf, (size_t)nbufsize)) == 0) {
+ ret = 1;
+ perror("cat");
+ goto realloc_err;
+ }
+ buf = nbuf;
+ bufsize = nbufsize;
+ }
+ }
+ buf[buflen] = 0;
+
+ ret = print_rev(buf, buflen);
+
+realloc_err:
+ free(buf);
+ buf = 0;
+calloc_err:
+ (void)fclose(ifile);
+out:
+ return (ret);
+}
+
+static int
+cat_mmap(const int fd)
+{
+ struct stat s;
+ int ret;
+ char *f;
+
+ if (fstat(fd, &s) < 0) {
+ ret = 1;
+ perror("cat");
+ goto err;
+ }
+
+ f = mmap(0, (size_t)s.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (f == MAP_FAILED) {
+ ret = 1;
+ perror("cat");
+ goto err;
+ }
+
+ ret = print_rev(f, s.st_size);
+
+ if (munmap(f, (size_t)s.st_size) < 0) {
+ ret = 1;
+ perror("cat");
+ goto err;
+ }
+
+err:
+ return (ret);
+}
+
+static int
+cat(const char *src)
+{
+ int fd;
+ int ret;
+
+ fd = STDIN_FILENO;
+ if (src[0] != '-' || src[1] != '\0') {
+ fd = open(src, O_RDONLY);
+ if (fd < 0) {
+ ret = 1;
+ perror("cat");
+ goto open_err;
+ }
+ }
+
+ if (fd == STDIN_FILENO || lseek(STDIN_FILENO, 0, SEEK_CUR) == -1) {
+ ret = cat_buffered(fd);
+
+ } else {
+ ret = cat_mmap(fd);
+ }
+
+ close(fd);
+open_err:
+ return (ret);
+}
+
+int
+main(int argc, char *argv[])
+{
+
+ char *def[] = { "-" };
+ int opt;
+
+ while ((opt = getopt(argc, argv, "u")) != -1) {
+ switch (opt) {
+ case 'u':
+ /*
+ * "Write bytes from the input file to the standard
+ * output without delay as each is read", which we were
+ * going to do anyway.
+ */
+ break;
+ case '?':
+ /* FALLTHROUGH */
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ argc = 1;
+ argv = def;
+ }
+
+ for (int i = 0; i < argc; i++) {
+ if (cat(argv[i]) > 0) {
+ return (1);
+ }
+ }
+
+ return (0);
+}
+
--- /dev/null
+PROG = cat
+
+# compiler
+CC = cc
+
+# flags
+CFLAGS = -std=c99 -O2 -Wall -Wextra -Wpedantic -Werror \
+ -Walloca -Wcast-qual -Wconversion -Wformat=2 -Wformat-security \
+ -Wnull-dereference -Wstack-protector -Wvla -Warray-bounds \
+ -Wbad-function-cast -Wconversion -Wshadow -Wstrict-overflow=4 -Wundef \
+ -Wstrict-prototypes -Wswitch-default -Wfloat-equal -Wimplicit-fallthrough \
+ -Wpointer-arith -Wswitch-enum \
+ -D_FORTIFY_SOURCE=2 \
+ -fstack-protector-strong -fPIE -fstack-clash-protection
+
+LDFLAGS = -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -Wl,-z,separate-code
+
+# libs
+LDLIBS =
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = ${PREFIX}/share/man
+
+RM = rm
+
--- /dev/null
+echo -n "this is a test file" > "$TMPDIR"/test-file-1
+echo -n "this is another test file" > "$TMPDIR"/test-file-2
+echo -n "dashed" > ${TMPDIR}/-dashed
+
+cat_should_handle_one_file() {
+ tool=`find ../src -type f -name "cat"`
+ ct="$(${tool} ${TMPDIR}/test-file-1)"
+ ref=$(rev ${TMPDIR}/test-file-1)
+
+ [ "$ct" = "$ref" ]
+}
+
+cat_should_handle_two_files() {
+ tool=`find ../src -type f -name "cat"`
+ ct="$(${tool} ${TMPDIR}/test-file-1 ${TMPDIR}/test-file-2)"
+ ref1=$(rev ${TMPDIR}/test-file-1)
+ ref2=$(rev ${TMPDIR}/test-file-2)
+ ref=$(echo -n "$ref1$ref2")
+
+ [ "$ct" = "$ref" ]
+}
+
+cat_should_handle_stdin() {
+ tool=`find ../src -type f -name "cat"`
+ ct="$(echo -n "this is from stdin" | ${tool} -)"
+ ref=$(echo -n "this is from stdin" | rev)
+ [ "$ct" = "$ref" ] || return 1
+ ct="$(echo -n "this is from stdin" | ${tool})"
+ [ "$ct" = "$ref" ]
+}
+
+cat_should_handle_u_flag() {
+ # The default behavior is to flush at every write
+ ct="$(echo -n "this is from stdin" | ${tool} -u)"
+ ref=$(echo -n "this is from stdin" | rev)
+ [ "$ct" = "$ref" ]
+}
+
+cat_should_handle_dash() {
+ tool=`find ../src -type f -name "cat"`
+ ct="$(${tool} -- ${TMPDIR}/-dashed)"
+ ref=$(echo -n "dashed" | rev)
+ [ "$ct" = "$ref" ]
+}
@@ -7,10 +7,10 @@ mkdir -p "$TMPDIR"
setup() {
# Dashed file
- touch -- ${TMPDIR}/-foo
+ touch -- ${TMPDIR}/-dashed
# Simple file
- echo -e "foo\nbar" > ${TMPDIR}/bar
+ echo -e "foo\nbar" > ${TMPDIR}/simple
# Symbolic link
echo "orig" > ${TMPDIR}/orig
@@ -59,6 +59,7 @@ run_tests() {
. $(dirname "$0")/false.sh
. $(dirname "$0")/true.sh
. $(dirname "$0")/rm.sh
+. $(dirname "$0")/cat.sh
setup
run_tests \
@@ -67,4 +68,9 @@ run_tests \
rm_backup_file \
rm_should_handle_dash \
rm_should_handle_directory \
- rm_should_follow_symlinks
+ rm_should_follow_symlinks \
+ cat_should_handle_one_file \
+ cat_should_handle_two_files \
+ cat_should_handle_stdin \
+ cat_should_handle_u_flag \
+ cat_should_handle_dash
rm_backup_file() {
tool=`find ../src -type f -name "rm"`
- ${tool} ${TMPDIR}/bar
- diff ${TMPDIR}/bar ${TMPDIR}/bar.bak
+ ${tool} ${TMPDIR}/simple
+ diff ${TMPDIR}/simple ${TMPDIR}/simple.bak
}
rm_should_handle_dash() {
tool=`find ../src -type f -name "rm"`
- ${tool} -- ${TMPDIR}/-foo
- [ -e ${TMPDIR}/-foo -a -e ${TMPDIR}/-foo.bak ]
+ ${tool} -- ${TMPDIR}/-dashed
+ [ -e ${TMPDIR}/-dashed -a -e ${TMPDIR}/-dashed.bak ]
}
rm_should_handle_directory() {
tool=`find ../src -type f -name "rm"`
- ${tool} ${TMPDIR}/
+ ${tool} ${TMPDIR}/ 2> /dev/null
[ $? = 0 -a ! -f ${TMPDIR}/.bak ]
}