Add rm
authorAlessio Chiapperini <[email protected]>
Mon, 4 Apr 2022 12:48:29 +0000 (4 14:48 +0200)
committerAlessio Chiapperini <[email protected]>
Mon, 4 Apr 2022 12:48:29 +0000 (4 14:48 +0200)
.gitignore
src/rm/Makefile [new file with mode: 0644]
src/rm/config.mk [new file with mode: 0644]
src/rm/rm.1 [new file with mode: 0644]
src/rm/rm.c [new file with mode: 0644]
test/harness.sh
test/rm.sh [new file with mode: 0644]

index 93ae894..60db41f 100644 (file)
@@ -4,3 +4,4 @@ src/**/*.o
 
 src/false/false
 src/true/true
+src/rm/rm
diff --git a/src/rm/Makefile b/src/rm/Makefile
new file mode 100644 (file)
index 0000000..c184601
--- /dev/null
@@ -0,0 +1,28 @@
+.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
diff --git a/src/rm/config.mk b/src/rm/config.mk
new file mode 100644 (file)
index 0000000..229fb0c
--- /dev/null
@@ -0,0 +1,26 @@
+PROG = true
+
+# 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
+
diff --git a/src/rm/rm.1 b/src/rm/rm.1
new file mode 100644 (file)
index 0000000..5718490
--- /dev/null
@@ -0,0 +1,84 @@
+.\" 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 4, 2022
+.Dt RM 1
+.Os
+.Sh NAME
+.Nm rm
+.Nd backups file.
+.Sh SYNOPSIS
+.Nm
+.Op Fl \&fhi
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility attempts to backup the non-directory type files specified on the command
+line.
+.Pp
+The backed-up file will have the same name as the original file, but with
+the added .bak extension, however it won't have the same permissions, 644 will
+be used instead. If the backed-up file already exists
+.Nm
+will exit with 1 as exit status.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl f
+Attempt to backup files without prompting for confirmation. If the file does not
+exist, do not display a diagnostic message or modify the exit status to reflect
+an error. The
+.Fl f
+option overrides any previous
+.Fl i
+options.
+.It Fl h
+Print usage information and exit.
+.It Fl i
+Request confirmation (on the standard error output) before attempting to backup
+up each file. The
+.Fl i
+option overrides any previous
+.Fl f
+options.
+.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 AUTHOR
+.An Alessio Chiapperini Aq Mt alessio.chiapperini@\:nullbuffer.com 
+
diff --git a/src/rm/rm.c b/src/rm/rm.c
new file mode 100644 (file)
index 0000000..9e2781a
--- /dev/null
@@ -0,0 +1,195 @@
+/*-
+ * 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 200809L
+#elif _POSIX_C_SOURCE < 200809L
+#  error incompatible _POSIX_C_SOURCE level
+#endif
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static char *ext = ".bak";
+
+static int fflag = 0; /* whether or not to avoid diagnostic messages and
+                        confirmation */
+static int iflag = 0; /* whether or not to ask for confirmation */
+
+static void
+usage(void)
+{
+       (void)fprintf(stderr, "usage: rm [-fhi] file ...\n");
+       exit(1);
+}
+
+static int
+backup(const char *path)
+{
+       struct stat st;
+       char buf[BUFSIZ];
+       size_t l1, l2;
+       ssize_t n, offs, o;
+       int c, first;
+       int ifd, ofd;
+       int ret;
+       char *backname;
+
+       ret = 0;
+       if (lstat(path, &st) < 0) {
+               perror("rm");
+               goto out;
+       }
+
+       if (S_ISDIR(st.st_mode) == 1) {
+               errno = EISDIR;
+               perror("rm");
+               goto out;
+       }
+
+       if (iflag == 1) {
+               (void)fprintf(stderr, "backup %s? ", path);
+
+               c = first = getchar();
+               while (c != '\n' && c != EOF) {
+                       c = getchar();
+               }
+               if (first != 'y' && first != 'Y') {
+                       goto out;
+               }
+       }
+
+       ifd = open(path, O_RDONLY);
+       if (ifd < 0) {
+               if (errno == ENOENT) {
+                       if (fflag == 0) {
+                               ret = 1;
+                               perror("rm");
+                       }
+                       goto in_open_err;
+               }
+       }
+
+       l1 = strlen(path);
+       l2 = strlen(ext);
+       backname = malloc(l1 + l2 + 1);
+       if (backname == 0) {
+               perror("rm");
+               goto malloc_err;
+       }
+       (void)memcpy(backname, path, l1);
+       (void)memcpy(backname + l1, ext, l2 + 1);
+
+       ofd = open(backname, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0644);
+       if (ofd < 0) {
+               perror("rm");
+               ret = 1;
+               goto out_open_err;
+       }
+
+       while ((n = read(ifd, buf, sizeof(buf))) > 0) {
+               offs = 0;
+               while (offs < n) {
+                       o = write(ofd, buf, (size_t)n);
+                       if (o < 0) {
+                               perror("rm");
+                               ret = 1;
+                               goto write_err;
+                       }
+
+                       offs += o;
+               }
+       }
+
+       if (n < 0) {
+               perror("rm");
+               ret = 1;
+       }
+
+write_err:
+       (void)close(ofd);
+out_open_err:
+       free(backname);
+malloc_err:
+       (void)close(ifd);
+in_open_err:
+out:
+       return (ret);
+}
+
+int
+main(int argc, char *argv[])
+{
+       int opt;
+       int ret;
+
+       while ((opt = getopt(argc, argv, "fhi")) != -1) {
+               switch (opt) {
+               case 'f':
+                       fflag = 1;
+                       iflag = 0;
+                       break;
+               case 'h':
+                       usage();
+                       break;
+               case 'i':
+                       iflag = 1;
+                       fflag = 0;
+                       break;
+               case '?':
+                       /* FALLTHROUGH */
+               default:
+                       usage();
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+
+       if (argc == 0) {
+               if (fflag == 0) {
+                       (void)fprintf(stderr, "rm: you need to specify a"
+                           " file\n");
+                       exit(1);
+               }
+               exit(0);
+       }
+
+       ret = 0;
+       for (int i = 0; i < argc; i++) {
+               ret = backup(argv[i]);
+       }
+
+       return (ret);
+}
+
index 49cd837..cf27c9b 100755 (executable)
@@ -6,6 +6,16 @@ TMPDIR=$TMPDIR/cursedutils-tests
 mkdir -p "$TMPDIR"
 
 setup() {
+    # Dashed file
+    touch -- ${TMPDIR}/-foo
+
+    # Simple file
+    echo -e "foo\nbar" > ${TMPDIR}/bar
+
+    # Symbolic link
+    echo "orig" > ${TMPDIR}/orig
+    $(cd ${TMPDIR} && ln -s orig lnk)
+
     printf "[----------] Test environment set-up.\n"
 }
 
@@ -48,8 +58,13 @@ run_tests() {
 
 . "false.sh"
 . "true.sh"
+. "rm.sh"
 
 setup
 run_tests \
     false_should_return_zero \
-    true_should_return_nonzero
+    true_should_return_nonzero \
+    rm_backup_file \
+    rm_should_handle_dash \
+    rm_should_handle_directory \
+    rm_should_follow_symlinks
diff --git a/test/rm.sh b/test/rm.sh
new file mode 100644 (file)
index 0000000..aa5bd6c
--- /dev/null
@@ -0,0 +1,23 @@
+rm_backup_file() {
+    tool=`find ../src -type f -name "rm"`
+    ${tool} ${TMPDIR}/bar
+    diff ${TMPDIR}/bar ${TMPDIR}/bar.bak
+}
+
+rm_should_handle_dash() {
+    tool=`find ../src -type f -name "rm"`
+    ${tool} -- ${TMPDIR}/-foo
+    [ -e ${TMPDIR}/-foo -a -e ${TMPDIR}/-foo.bak ]
+}
+
+rm_should_handle_directory() {
+    tool=`find ../src -type f -name "rm"`
+    ${tool} ${TMPDIR}/
+    [ $? == 0 -a ! -f ${TMPDIR}/.bak ]
+}
+
+rm_should_follow_symlinks() {
+    tool=`find ../src -type f -name "rm"`
+    ${tool} ${TMPDIR}/lnk
+    diff ${TMPDIR}/orig ${TMPDIR}/lnk.bak
+}