diff options
| author | Jan Sucan <jan@jansucan.com> | 2021-05-02 10:19:23 +0200 |
|---|---|---|
| committer | Jan Sucan <jan@jansucan.com> | 2021-05-02 10:20:29 +0200 |
| commit | 02ffe0ed4113e7b479197d0ba1b160e6280b4099 (patch) | |
| tree | c7a3573039ba02983eec38267d61d86e1965bbab /src | |
| parent | 36a92af6ee3c20c93835a34ddeb148c34ccf023c (diff) | |
Move the source files to a src directory
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile | 19 | ||||
| -rw-r--r-- | src/file.c | 23 | ||||
| -rw-r--r-- | src/file.h | 8 | ||||
| -rw-r--r-- | src/main.c | 376 | ||||
| -rw-r--r-- | src/options.c | 118 | ||||
| -rw-r--r-- | src/options.h | 19 | ||||
| -rw-r--r-- | src/print.c | 21 | ||||
| -rw-r--r-- | src/print.h | 6 | ||||
| -rw-r--r-- | src/resources.c | 44 | ||||
| -rw-r--r-- | src/resources.h | 17 |
10 files changed, 651 insertions, 0 deletions
diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..8b24b35 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,19 @@ +CC=gcc +CFLAGS=-Wall + +SOURCES=*.c +HEADERS=*.h + +all: $(PROGRAM_NAME) + +$(PROGRAM_NAME): $(SOURCES) $(HEADERS) program_info.h + $(CC) $(CFLAGS) -o $(PROGRAM_NAME) $(SOURCES) + +program_info.h: + echo "#define PROGRAM_NAME_STR \"$(PROGRAM_NAME)\"" >program_info.h + echo "#define PROGRAM_VERSION_STR \"$(PROGRAM_VERSION)\"" >>program_info.h + +.PHONY: clean + +clean: + rm -f *.o *~ $(PROGRAM_NAME) program_info.h diff --git a/src/file.c b/src/file.c new file mode 100644 index 0000000..c6823b9 --- /dev/null +++ b/src/file.c @@ -0,0 +1,23 @@ +#include <errno.h> +#include <string.h> + +#include "file.h" +#include "print.h" + +long +file_size(FILE *const file) +{ + fpos_t p; + + if ((fgetpos(file, &p) != 0) || (fseek(file, 0L, SEEK_END) != 0)) { + return -1; + } + + const long size = ftell(file); + + if (fsetpos(file, &p) != 0) { + return -1; + } + + return size; +} diff --git a/src/file.h b/src/file.h new file mode 100644 index 0000000..d62b1ba --- /dev/null +++ b/src/file.h @@ -0,0 +1,8 @@ +#ifndef FILE_H +#define FILE_H + +#include <stdio.h> + +long file_size(FILE *const file); + +#endif /* FILE_H */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..ea94921 --- /dev/null +++ b/src/main.c @@ -0,0 +1,376 @@ +#include <endian.h> +#include <errno.h> +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "file.h" +#include "options.h" +#include "print.h" +#include "resources.h" + +/* This header file is automatically generated at build time from the Makefile + */ +#include "program_info.h" + +void +usage(int exit_code) +{ + printf("Usage: %s [-s SECTOR_SIZE] [-b BUFFER_SIZE] [INFILE] REFFILE " + "OUTFILE\n", + PROGRAM_NAME_STR); + exit(exit_code); +} + +void +clean_exit(resources_t *const res, int exit_code) +{ + resources_free(res); + exit(exit_code); +} + +int +open_files(const options_t *const opts, resources_t *const res, + bool is_action_backup) +{ + /* Open the input file */ + if ((opts->in_file_path != NULL) && + ((res->in_file = fopen(opts->in_file_path, "r")) == NULL)) { + print_error("cannot open input file: %s", strerror(errno)); + return 1; + } + + /* Open the reference file */ + if ((res->ref_file = fopen(opts->ref_file_path, "r")) == NULL) { + print_error("cannot open reference file: %s", strerror(errno)); + return 1; + } + + /* Open the output file + * + * When restoring, the file must be opened for writing and not + * truncated + */ + char out_mode[] = "r+"; + + if (is_action_backup) { + /* When backing up, the output file is truncated to hold the + * new data + */ + out_mode[0] = 'w'; + out_mode[1] = '\0'; + } + + if ((res->out_file = fopen(opts->out_file_path, out_mode)) == NULL) { + print_error("cannot open output file: %s", strerror(errno)); + return 1; + } + + return 0; +} + +bool +is_reference_file_valid(resources_t *const res, uint32_t sector_size) +{ + const long ref_size = file_size(res->ref_file); + + if (ref_size < 0) { + print_error("cannot get size of reference file: %s", strerror(errno)); + return false; + } else if (ref_size == 0) { + print_error("reference file is empty"); + return false; + } else if ((ref_size % (sizeof(uint64_t) + sector_size)) != 0) { + /* The reference file must hold equally sized sectors and the + * offset of each of them + */ + print_error( + "reference file has size that cannot contain valid diff data"); + return false; + } + + const long out_size = file_size(res->out_file); + + if (out_size < 0) { + print_error("cannot get size of output file: %s", strerror(errno)); + return 1; + } + + uint64_t ref_offset = 0; + uint64_t prev_out_offset; + + /* Scan the reference file and check */ + for (;;) { + uint64_t out_offset; + /* Read the next offset */ + const size_t ref_read = + fread(&out_offset, sizeof(out_offset), 1U, res->ref_file); + out_offset = le64toh(out_offset); + + if (feof(res->ref_file)) { + break; + } else if ((ref_read != 1U) || ferror(res->ref_file)) { + print_error("cannot read from reference file: %s", strerror(errno)); + return false; + } else if (((ref_offset != 0) && (out_offset <= prev_out_offset)) || + ((out_offset + sector_size) > out_size)) { + /* The offset must be higher than the previous one and it + * must point into the file + */ + print_error("reference file is not valid"); + return false; + } else if (fseek(res->ref_file, sector_size, SEEK_CUR) != 0) { + print_error("cannot seek in reference file: %s", strerror(errno)); + return false; + } + + prev_out_offset = out_offset; + } + + if (ftell(res->ref_file) != ref_size) { + /* The reference file must be read completely */ + print_error("reference file is not valid"); + return false; + } else if (fseek(res->ref_file, 0L, SEEK_SET) != 0) { + /* The file must be prepared for the restoring */ + print_error("cannot seek in reference file: %s", strerror(errno)); + return false; + } + + return true; +} + +int +diff_backup(const options_t *const opts, resources_t *const res) +{ + const long in_size = file_size(res->in_file); + + if (in_size < 0) { + print_error("cannot get size of input file: %s", strerror(errno)); + return 1; + } + + const long ref_size = file_size(res->ref_file); + + if (ref_size < 0) { + print_error("cannot get size of reference file: %s", strerror(errno)); + return 1; + } + + /* Check sizes of the input file and the reference file */ + if (in_size != ref_size) { + print_error("input file and reference file differ in size"); + return 1; + } else if ((in_size % opts->sector_size) != 0) { + print_error( + "size of input file and reference file is not multiple of" PRIu32, + opts->sector_size); + return 1; + } + + /* Allocate the buffers */ + /* The output buffer contains also the offsets */ + const size_t out_buffer_size = + ((opts->buffer_size / opts->sector_size) * sizeof(uint64_t)) + + opts->buffer_size; + + if ((res->in_buffer = (char *)malloc(opts->buffer_size)) == NULL) { + print_error("cannot allocate buffer for input file data"); + return 1; + } else if ((res->ref_buffer = (char *)malloc(opts->buffer_size)) == NULL) { + print_error("cannot allocate buffer for reference file data"); + return 1; + } else if ((res->out_buffer = (char *)malloc(out_buffer_size)) == NULL) { + print_error("cannot allocate buffer for output file data"); + return 1; + } + + size_t out_buffer_index = 0; + uint64_t offset = 0; + + for (;;) { + /* Read the sectors from the input file to the buffer */ + const size_t in_read = + fread(res->in_buffer, 1U, opts->buffer_size, res->in_file); + + if ((in_read % opts->sector_size) != 0) { + print_error( + "data read from input file is not multiple of sector size"); + return 1; + } else if (ferror(res->in_file)) { + print_error("cannot read from input file: %s", strerror(errno)); + return 1; + } + + /* Read sectors from the reference file to the buffer */ + const size_t ref_read = + fread(res->ref_buffer, 1U, opts->buffer_size, res->ref_file); + + if ((ref_read % opts->sector_size) != 0) { + print_error( + "data read from reference file is not multiple of sector size"); + return 1; + } else if (ferror(res->ref_file)) { + print_error("%s", strerror(errno)); + return 1; + } + + if (in_read != ref_read) { + print_error( + "data read from input file and reference file differ in size"); + return 1; + } + + /* Process sectors in the buffer */ + for (size_t i = 0; i < in_read; i += opts->sector_size) { + /* Compare the sectors */ + int changed = 0; + const size_t j_end = i + opts->sector_size; + for (size_t j = i; j < j_end; ++j) { + if (res->in_buffer[j] != res->ref_buffer[j]) { + changed = 1; + break; + } + } + + if (changed) { + /* Write changed sector */ + if (out_buffer_index >= out_buffer_size) { + /* The output buffer is full. Write it to the + * output file. + */ + const size_t x = fwrite(res->out_buffer, 1U, + out_buffer_index, res->out_file); + + if (x != out_buffer_index) { + print_error("cannot write to output file: %s", + strerror(errno)); + return 1; + } + + out_buffer_index = 0; + } + + const uint64_t o = htole64(offset); + + memcpy(res->out_buffer + out_buffer_index, (void *)&o, + sizeof(o)); + memcpy(res->out_buffer + out_buffer_index, &(res->in_buffer[i]), + opts->sector_size); + out_buffer_index += sizeof(o) + opts->sector_size; + } + + offset += opts->sector_size; + } + + if (feof(res->in_file)) { + break; + } + } + + /* Write out the output buffer */ + if (out_buffer_index >= 0) { + const size_t x = + fwrite(res->out_buffer, 1U, out_buffer_index, res->out_file); + + if (x != out_buffer_index) { + print_error("cannot write to output file: %s", strerror(errno)); + return 1; + } + } + + return 0; +} + +int +diff_restore(const options_t *const opts, resources_t *const res) +{ + /* Check validity of the reference file */ + if (!is_reference_file_valid(res, opts->sector_size)) { + return 1; + } + + const long ref_size = file_size(res->ref_file); + + if (ref_size < 0) { + print_error("cannot get size of reference file: %s", strerror(errno)); + return 1; + } + + /* Allocate the buffer for data from the reference file */ + /* The reference buffer contains also the offsets */ + const size_t ref_buffer_size = + ((opts->buffer_size / opts->sector_size) * sizeof(uint64_t)) + + opts->buffer_size; + + if ((res->ref_buffer = (char *)malloc(ref_buffer_size)) == NULL) { + print_error("cannot allocate buffer for reference file data"); + return 1; + } + + /* Restore data from the differential image */ + uint64_t out_offset; + + for (;;) { + /* Read data of the offset and the next sector */ + const size_t ref_read = + fread(res->ref_buffer, ref_buffer_size, 1U, res->ref_file); + + if (feof(res->ref_file)) { + break; + } else if ((ref_read != 1U) || ferror(res->ref_file)) { + print_error("cannot read from reference file: %s", strerror(errno)); + return 1; + } + + /* Get offset */ + out_offset = le64toh(*((uint64_t *)res->ref_buffer)); + + if (fseek(res->out_file, out_offset, SEEK_SET) != 0) { + print_error("cannot seek in output file: %s", strerror(errno)); + return 1; + } + + /* Write the sector data to the output file */ + const size_t out_written = fwrite(res->ref_buffer + sizeof(out_offset), + opts->sector_size, 1U, res->out_file); + + if (out_written != 1U) { + print_error("cannot write to output file: %s", strerror(errno)); + return 1; + } + } + + /* The reference file must be read completely */ + if (ftell(res->ref_file) != ref_size) { + print_error("reference file is not valid"); + return 1; + } + + return 0; +} + +int +main(int argc, char **argv) +{ + options_t opts; + + if (options_parse(argc, argv, &opts) || opts.help) { + usage(1); + } + + const bool is_action_backup = (opts.in_file_path != NULL); + resources_t res; + + resources_init(&res); + + if (open_files(&opts, &res, is_action_backup) != 0) { + clean_exit(&res, 1); + } else if (is_action_backup) { + clean_exit(&res, diff_backup(&opts, &res)); + } else { + clean_exit(&res, diff_restore(&opts, &res)); + } +} diff --git a/src/options.c b/src/options.c new file mode 100644 index 0000000..2017ba5 --- /dev/null +++ b/src/options.c @@ -0,0 +1,118 @@ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "options.h" +#include "print.h" + +#define OPTIONS_DEFAULT_SECTOR_SIZE 512 +#define OPTIONS_DEFAULT_BUFFER_SIZE (4 * 1024 * 1024) + +void options_init(options_t *const opts); +int options_parse_unsigned(const char *const arg, uint32_t *const value); + +int +options_parse(int argc, char **argv, options_t *const opts) +{ + options_init(opts); + + int ch; + char *arg_sector_size = NULL; + char *arg_buffer_size = NULL; + + while ((ch = getopt(argc, argv, ":s:b:h")) != -1) { + switch (ch) { + case 's': + arg_sector_size = optarg; + break; + + case 'b': + arg_buffer_size = optarg; + break; + + case 'h': + opts->help = true; + break; + + case ':': + print_error("missing argument for option '-%c'", optopt); + return 1; + + default: + print_error("unknown option '-%c'", optopt); + return 1; + } + } + + argc -= optind; + argv += optind; + + if (opts->help) { + return 0; + } else if (argc < 2) { + print_error("missing arguments"); + return 1; + } else if (argc > 3) { + print_error("too many arguments"); + return 1; + } + + /* Convert numbers in the arguments */ + if ((arg_sector_size != NULL) && + options_parse_unsigned(arg_sector_size, &(opts->sector_size))) { + print_error("incorrect sector size"); + return 1; + } else if ((arg_buffer_size != NULL) && + options_parse_unsigned(arg_buffer_size, &(opts->buffer_size))) { + print_error("incorrect buffer size"); + return 1; + } else if (opts->sector_size == 0) { + print_error("sector size cannot be 0"); + return 1; + } else if (opts->buffer_size == 0) { + print_error("buffer size cannot be 0"); + return 1; + } else if (opts->sector_size > opts->buffer_size) { + print_error("sector size cannot larger than buffer size"); + return 1; + } else if ((opts->buffer_size % opts->sector_size) != 0) { + print_error("buffer size is not multiple of sector size"); + return 1; + } + + /* Pick the file paths */ + int last_path_index = argc - 1; + opts->out_file_path = argv[last_path_index--]; + opts->ref_file_path = argv[last_path_index--]; + if (last_path_index >= 0) { + opts->in_file_path = argv[last_path_index]; + } + + return 0; +} + +void +options_init(options_t *const opts) +{ + opts->help = false; + opts->sector_size = OPTIONS_DEFAULT_SECTOR_SIZE; + opts->buffer_size = OPTIONS_DEFAULT_BUFFER_SIZE; + + opts->in_file_path = NULL; + opts->ref_file_path = NULL; + opts->out_file_path = NULL; +} + +int +options_parse_unsigned(const char *const arg, uint32_t *const value) +{ + char *end; + + errno = 0; + + *value = strtoul(arg, &end, 0); + + return ((*end != '\0') || (errno != 0)) ? -1 : 0; +} diff --git a/src/options.h b/src/options.h new file mode 100644 index 0000000..71259aa --- /dev/null +++ b/src/options.h @@ -0,0 +1,19 @@ +#ifndef OPTIONS_H +#define OPTIONS_H + +#include <stdbool.h> +#include <stdint.h> + +typedef struct { + bool help; + uint32_t sector_size; + uint32_t buffer_size; + + const char *in_file_path; + const char *ref_file_path; + const char *out_file_path; +} options_t; + +int options_parse(int argc, char **argv, options_t *const opts); + +#endif /* OPTIONS_H */ diff --git a/src/print.c b/src/print.c new file mode 100644 index 0000000..824f818 --- /dev/null +++ b/src/print.c @@ -0,0 +1,21 @@ +#include <stdarg.h> +#include <stdio.h> + +#include "print.h" + +#define PRINT_ERROR_PREFIX "ERROR: " +#define PRINT_ERROR_SUFFIX "\n" + +void +print_error(const char *const format, ...) +{ + fprintf(stderr, PRINT_ERROR_PREFIX); + + va_list args; + + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + + fprintf(stderr, PRINT_ERROR_SUFFIX); +} diff --git a/src/print.h b/src/print.h new file mode 100644 index 0000000..7992087 --- /dev/null +++ b/src/print.h @@ -0,0 +1,6 @@ +#ifndef PRINT_H +#define PRINT_H + +void print_error(const char *const format, ...); + +#endif /* PRINT_H */ diff --git a/src/resources.c b/src/resources.c new file mode 100644 index 0000000..3eebd1c --- /dev/null +++ b/src/resources.c @@ -0,0 +1,44 @@ +#include <stdio.h> +#include <stdlib.h> + +#include "resources.h" + +void +resources_init(resources_t *const res) +{ + res->in_file = NULL; + res->ref_file = NULL; + res->out_file = NULL; + res->in_buffer = NULL; + res->ref_buffer = NULL; + res->out_buffer = NULL; +} + +void +resources_free(resources_t *const res) +{ + if (res->in_file != NULL) { + fclose(res->in_file); + res->in_file = NULL; + } + if (res->ref_file != NULL) { + fclose(res->ref_file); + res->ref_file = NULL; + } + if (res->out_file != NULL) { + fclose(res->out_file); + res->out_file = NULL; + } + if (res->in_buffer != NULL) { + free(res->in_buffer); + res->in_buffer = NULL; + } + if (res->ref_buffer != NULL) { + free(res->ref_buffer); + res->ref_buffer = NULL; + } + if (res->out_buffer != NULL) { + free(res->out_buffer); + res->out_buffer = NULL; + } +} diff --git a/src/resources.h b/src/resources.h new file mode 100644 index 0000000..0cf9777 --- /dev/null +++ b/src/resources.h @@ -0,0 +1,17 @@ +#ifndef RESOURCES_H +#define RESOURCES_H + +typedef struct { + FILE *in_file; + FILE *ref_file; + FILE *out_file; + + char *in_buffer; + char *ref_buffer; + char *out_buffer; +} resources_t; + +void resources_init(resources_t *const res); +void resources_free(resources_t *const res); + +#endif /* RESOURCES_H */ |
