]> git.scottworley.com Git - overonion/commitdiff
Merge reverse + overonion shell implementation
authorScott Worley <scottworley@scottworley.com>
Fri, 20 Oct 2017 08:30:02 +0000 (01:30 -0700)
committerScott Worley <scottworley@scottworley.com>
Fri, 20 Oct 2017 08:30:02 +0000 (01:30 -0700)
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
reverse.c [new file with mode: 0644]
reverse_lib.c [new file with mode: 0644]
reverse_lib.h [new file with mode: 0644]
reverse_test.c [new file with mode: 0644]
temp_file.c [new file with mode: 0644]
temp_file.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..188a328
--- /dev/null
@@ -0,0 +1,5 @@
+reverse
+reverse_test
+*.o
+deps.makefile
+deps.makefile.bak
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..6e2eacb
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,27 @@
+CC = gcc
+CFLAGS = -Wall -Wextra -pedantic -Wstrict-overflow
+
+all: reverse
+
+clean:
+       -rm *.o reverse reverse_test
+
+distclean: clean
+       -rm deps.makefile deps.makefile.bak
+
+depend: *.c
+       makedepend -f- $^ > deps.makefile
+
+test: reverse_test
+       ./reverse_test
+
+.PHONY: clean distclean depend test
+
+reverse: reverse.o reverse_lib.o temp_file.o
+reverse_test: reverse_test.o reverse_lib.o temp_file.o
+       $(CC) -o $@ $^ $(CFLAGS)
+
+%.o: %.c
+       $(CC) -c -o $@ $< $(CFLAGS)
+
+-include deps.makefile
diff --git a/reverse.c b/reverse.c
new file mode 100644 (file)
index 0000000..02a349f
--- /dev/null
+++ b/reverse.c
@@ -0,0 +1,17 @@
+#include "reverse_lib.h"
+
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <sysexits.h>
+
+int main(int argc, char** argv) {
+  if (argc == 1 || (argc == 2 && strcmp(argv[1], "-") == 0)) {
+    reverse_stream(stdin, stdout);
+  } else if (argc == 2) {
+    reverse_file(argv[1], stdout);
+  } else {
+    errx(EX_USAGE, "Usage: reverse filename");
+  }
+  return 0;
+}
diff --git a/reverse_lib.c b/reverse_lib.c
new file mode 100644 (file)
index 0000000..f55bedf
--- /dev/null
@@ -0,0 +1,76 @@
+#define _FILE_OFFSET_BITS 64
+
+#include "temp_file.h"
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+static off_t ceil_div(off_t dividend, off_t divisor) {
+  return (dividend - 1) / divisor + 1;
+}
+
+void reverse_file(const char* input_filename, FILE* output_stream) {
+  const off_t mmap_chunk_size = 512 << 20;
+
+  int fd = open(input_filename, O_RDONLY);
+  if (fd == -1) err(EX_NOINPUT, "Could not open specified file");
+
+  struct stat stats;
+  if (fstat(fd, &stats) == -1) err(EX_NOINPUT, "Could not stat input");
+
+  long page_size = sysconf(_SC_PAGE_SIZE);
+  off_t num_chunks = ceil_div(stats.st_size, mmap_chunk_size);
+  for (off_t chunk = num_chunks - 1; chunk >= 0; chunk--) {
+    off_t start_offset = chunk * mmap_chunk_size;
+    off_t end_offset = (chunk + 1) * mmap_chunk_size;
+    if (end_offset > stats.st_size) {
+      end_offset = stats.st_size;
+    }
+    off_t pages = ceil_div(end_offset - start_offset, page_size);
+    long map_size = pages * page_size;
+    char *m = mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, start_offset);
+    if (m == MAP_FAILED) err(EX_NOINPUT, "Could not mmap chunk %lld of %lld", chunk, num_chunks);
+
+    for (off_t p = (end_offset - start_offset) - 1; p >= 0; p--) {
+      if (fputc(m[p], output_stream) == EOF) errx(EX_IOERR, "Could not write");
+    }
+
+    if (munmap(m, map_size) == -1) err(EX_IOERR, "Could not unmap chunk %lld of %lld", chunk, num_chunks);
+  }
+
+  if (close(fd) == -1) err(EX_IOERR, "Could not close input");
+}
+
+/* Copy data from input to output until EOF is reached. */
+static void copy(FILE* input, FILE* output) {
+  for (;;) {
+    int c = fgetc(input);
+    if (c == EOF) {
+      if (ferror(input)) errx(EX_IOERR, "Could not read");
+      if (!feof(input)) errx(EX_IOERR, "Unexpected end of file");
+      break;
+    }
+    if (fputc(c, output) == EOF) errx(EX_IOERR, "Could not write");
+  }
+}
+
+void reverse_stream(FILE* input_stream, FILE* output_stream) {
+  char* temp_filename;
+  FILE* temp_file;
+  make_temporary_file(&temp_filename, &temp_file);
+
+  copy(input_stream, temp_file);
+  if (fclose(temp_file) != 0) err(EX_IOERR, "Could not close temporary file");
+
+  reverse_file(temp_filename, output_stream);
+
+  if (unlink(temp_filename) == -1) err(EX_IOERR, "Could not remove temporary file");
+  free(temp_filename);
+}
diff --git a/reverse_lib.h b/reverse_lib.h
new file mode 100644 (file)
index 0000000..d8b0480
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef _OVERONION_REVERSE_LIB_H
+#define _OVERONION_REVERSE_LIB_H
+
+#include <stdio.h>
+
+/* Copy the contents of input_filename to output_stream backwards.
+ * input_filename must be a real file, not a pipe. */
+void reverse_file(const char* input_filename, FILE* output_stream);
+
+/* Save the contents of input_stream to a temporary file.  On EOF,
+ * emit them backwards to output_stream.  Temporary files go in
+ * $TMPDIR if set, /tmp otherwise. */
+void reverse_stream(FILE* input_stream, FILE* output_stream);
+
+#endif /* _OVERONION_REVERSE_LIB_H */
diff --git a/reverse_test.c b/reverse_test.c
new file mode 100644 (file)
index 0000000..46c7a97
--- /dev/null
@@ -0,0 +1,99 @@
+#include "reverse_lib.h"
+#include "temp_file.h"
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+/* Reverse input_file.  Dump the result in a temporary file.  Return the temp
+ * file's name.  The caller must free the filename. */
+char* reverse_to_temp_file(const char* input_file) {
+  char* temp_filename;
+  FILE* f;
+  make_temporary_file(&temp_filename, &f);
+  reverse_file(input_file, f);
+  if (fclose(f) == EOF) err(EX_IOERR, "Couldn't close temporary file");
+  return temp_filename;
+}
+
+/* Compare the contents of two files.  Terminate with a diagnostic if they
+ * differ. */
+void compare(const char* filename1, const char* filename2) {
+  FILE* f1 = fopen(filename1, "r");
+  if (f1 == NULL) err(EX_IOERR, "Couldn't open file %s", filename1);
+  FILE* f2 = fopen(filename2, "r");
+  if (f2 == NULL) err(EX_IOERR, "Couldn't open file %s", filename2);
+
+  for (int pos = 0; ; pos++) {
+    int c1 = fgetc(f1);
+    int c2 = fgetc(f2);
+    if (c1 == EOF && ferror(f1)) err(EX_IOERR, "Error reading file %s", filename1);
+    if (c2 == EOF && ferror(f2)) err(EX_IOERR, "Error reading file %s", filename2);
+    if (c1 != c2) {
+      errx(EX_SOFTWARE, "Unexpected difference found at offset %d after reversing twice.  "
+                        "Expected %d but found %d", pos, c1, c2);
+    }
+    if (c1 == EOF) break;
+  }
+
+  if (fclose(f1) != 0) err(EX_IOERR, "Couldn't close file %s", filename1);
+  if (fclose(f2) != 0) err(EX_IOERR, "Couldn't close file %s", filename2);
+}
+
+void test_reverse_twice_from_files_is_the_same(const char* test_filename) {
+  char* intermediate = reverse_to_temp_file(test_filename);
+  char* back_to_normal = reverse_to_temp_file(intermediate);
+
+  compare(test_filename, back_to_normal);
+
+  if (unlink(intermediate) == -1) err(EX_IOERR, "Couldn't remove intermediate temp file");
+  if (unlink(back_to_normal) == -1) err(EX_IOERR, "Couldn't remove twice-reversed temp file");
+  free(intermediate);
+  free(back_to_normal);
+}
+
+void test_reverse_from_file_then_from_stream_is_the_same(const char* test_filename) {
+  int pipefd[2];
+  if (pipe(pipefd) == -1) err(EX_OSERR, "Couldn't create pipe");
+
+  pid_t pid = fork();
+  if (pid == -1) err(EX_OSERR, "Couldn't fork");
+  if (pid == 0) {
+    if (close(pipefd[0]) == -1) err(EX_OSERR, "Couldn't close unneeded pipe descriptor");
+    FILE* to_second = fdopen(pipefd[1], "w");
+    if (to_second == NULL) err(EX_IOERR, "Couldn't open pipe for writing");
+    reverse_file(test_filename, to_second);
+    exit(0);
+  }
+  if (close(pipefd[1]) == -1) err(EX_OSERR, "Couldn't close unneeded pipe descriptor");
+  FILE* from_first = fdopen(pipefd[0], "r");
+  if (from_first == NULL) err(EX_IOERR, "Couldn't open pipe for reading");
+  char* out_temp_filename;
+  FILE* out_file;
+  make_temporary_file(&out_temp_filename, &out_file);
+  reverse_stream(from_first, out_file);
+  if (fclose(out_file) == EOF) err(EX_IOERR, "Couldn't close temporary file");
+
+  compare(test_filename, out_temp_filename);
+
+  if (unlink(out_temp_filename) == -1) err(EX_IOERR, "Couldn't remove temp output file");
+  free(out_temp_filename);
+}
+
+int main(int argc, char** argv) {
+  char* test_filename = "reverse.c";
+  if (argc > 2) {
+    errx(EX_USAGE, "usage: reverse_test [datafile]");
+  } else if (argc == 2) {
+    test_filename = argv[1];
+  }
+
+  test_reverse_twice_from_files_is_the_same(test_filename);
+  test_reverse_from_file_then_from_stream_is_the_same(test_filename);
+
+  puts("PASS");
+  return 0;
+}
diff --git a/temp_file.c b/temp_file.c
new file mode 100644 (file)
index 0000000..68bfb88
--- /dev/null
@@ -0,0 +1,22 @@
+#define _GNU_SOURCE
+
+#include "temp_file.h"
+
+#include <err.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sysexits.h>
+
+void make_temporary_file(char** temp_filename, FILE** temp_file) {
+  char* TMPDIR = getenv("TMPDIR");
+  if (TMPDIR == NULL) {
+    TMPDIR = "/tmp";
+  }
+  if (asprintf(temp_filename, "%s/reverse.XXXXXX", TMPDIR) == -1) {
+    errx(EX_OSERR, "Could not assemble temporary filename");
+  }
+  int fd = mkstemp(*temp_filename);
+  if (fd == -1) err(EX_IOERR, "Could not make a temporary file");
+  *temp_file = fdopen(fd, "w");
+  if (*temp_file == NULL) err(EX_IOERR, "Could not open temporary file");
+}
diff --git a/temp_file.h b/temp_file.h
new file mode 100644 (file)
index 0000000..d4fe41e
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef _OVERONION_TEMP_FILE_H
+#define _OVERONION_TEMP_FILE_H
+
+#include <stdio.h>
+
+/* Create a temporary file in $TMPDIR, or /tmp if TMPDIR is not set.
+ * Caller must free temp_filename and fclose temp_file.  Succeeds or terminates
+ * the process. */
+void make_temporary_file(char** temp_filename, FILE** temp_file);
+
+#endif /* _OVERONION_TEMP_FILE_H */