From: Scott Worley Date: Fri, 20 Oct 2017 08:30:02 +0000 (-0700) Subject: Merge reverse + overonion shell implementation X-Git-Url: http://git.scottworley.com/overonion/commitdiff_plain/f5ec0b4b91687c7a34d4760381f6e7424f981127?hp=673e2d68a8813804502df1923a6c2f6e727e69a0 Merge reverse + overonion shell implementation --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..188a328 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +reverse +reverse_test +*.o +deps.makefile +deps.makefile.bak diff --git a/Makefile b/Makefile new file mode 100644 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 index 0000000..02a349f --- /dev/null +++ b/reverse.c @@ -0,0 +1,17 @@ +#include "reverse_lib.h" + +#include +#include +#include +#include + +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 index 0000000..f55bedf --- /dev/null +++ b/reverse_lib.c @@ -0,0 +1,76 @@ +#define _FILE_OFFSET_BITS 64 + +#include "temp_file.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 index 0000000..d8b0480 --- /dev/null +++ b/reverse_lib.h @@ -0,0 +1,15 @@ +#ifndef _OVERONION_REVERSE_LIB_H +#define _OVERONION_REVERSE_LIB_H + +#include + +/* 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 index 0000000..46c7a97 --- /dev/null +++ b/reverse_test.c @@ -0,0 +1,99 @@ +#include "reverse_lib.h" +#include "temp_file.h" + +#include +#include +#include +#include +#include +#include + +/* 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 index 0000000..68bfb88 --- /dev/null +++ b/temp_file.c @@ -0,0 +1,22 @@ +#define _GNU_SOURCE + +#include "temp_file.h" + +#include +#include +#include +#include + +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 index 0000000..d4fe41e --- /dev/null +++ b/temp_file.h @@ -0,0 +1,11 @@ +#ifndef _OVERONION_TEMP_FILE_H +#define _OVERONION_TEMP_FILE_H + +#include + +/* 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 */