aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE.txt24
-rw-r--r--Makefile14
-rw-r--r--file.c32
-rw-r--r--file.h9
-rw-r--r--main.c352
-rw-r--r--options.c119
-rw-r--r--options.h20
-rw-r--r--print.c21
-rw-r--r--print.h6
-rw-r--r--resources.c38
-rw-r--r--resources.h17
11 files changed, 652 insertions, 0 deletions
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..88e0aa8
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,24 @@
+Copyright 2019 Ján Sučan <jan@jansucan.com>
+
+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 COPYRIGHT HOLDERS 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 COPYRIGHT
+HOLDER 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.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..03205f0
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+CC=gcc
+CFLAGS=-Wall
+EXEC_NAME=diff-dd
+
+SOURCES=*.c
+HEADERS=*.h
+
+all: $(SOURCES) $(HEADERS)
+ $(CC) $(CFLAGS) -o $(EXEC_NAME) $(SOURCES)
+
+.PHONY: clean
+
+clean:
+ rm -f *.o *~ $(EXEC_NAME)
diff --git a/file.c b/file.c
new file mode 100644
index 0000000..6b70f5e
--- /dev/null
+++ b/file.c
@@ -0,0 +1,32 @@
+#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;
+}
+
+FILE *
+file_open(const char * const path, const char * const mode)
+{
+ /* TODO: odstranit tuto funkciu? */
+ FILE * f = fopen(path, mode);
+ return f;
+}
diff --git a/file.h b/file.h
new file mode 100644
index 0000000..1b973f1
--- /dev/null
+++ b/file.h
@@ -0,0 +1,9 @@
+#ifndef FILE_H
+#define FILE_H
+
+#include <stdio.h>
+
+long file_size(FILE * const file);
+FILE * file_open(const char * const path, const char * const mode);
+
+#endif /* FILE_H */
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..2a21375
--- /dev/null
+++ b/main.c
@@ -0,0 +1,352 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+#include <errno.h>
+#include <endian.h>
+
+#include "options.h"
+#include "resources.h"
+#include "file.h"
+#include "print.h"
+
+void
+usage(int exit_code)
+{
+ printf("Usage: diff-dd [-s SECTOR_SIZE] [-b BUFFER_SIZE] [INFILE] REFFILE OUTFILE\n");
+ 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 = file_open(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 = file_open(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 = file_open(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/options.c b/options.c
new file mode 100644
index 0000000..8755085
--- /dev/null
+++ b/options.c
@@ -0,0 +1,119 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.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->do_not_ask = false;
+
+ 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/options.h b/options.h
new file mode 100644
index 0000000..0c64bbd
--- /dev/null
+++ b/options.h
@@ -0,0 +1,20 @@
+#ifndef OPTIONS_H
+#define OPTIONS_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+typedef struct {
+ bool help;
+ uint32_t sector_size;
+ uint32_t buffer_size;
+ bool do_not_ask;
+
+ 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/print.c b/print.c
new file mode 100644
index 0000000..311276b
--- /dev/null
+++ b/print.c
@@ -0,0 +1,21 @@
+#include <stdio.h>
+#include <stdarg.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/print.h b/print.h
new file mode 100644
index 0000000..0613dfc
--- /dev/null
+++ b/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/resources.c b/resources.c
new file mode 100644
index 0000000..b58e963
--- /dev/null
+++ b/resources.c
@@ -0,0 +1,38 @@
+#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);
+ }
+ if (res->ref_file != NULL) {
+ fclose(res->ref_file);
+ }
+ if (res->out_file != NULL) {
+ fclose(res->out_file);
+ }
+ if (res->in_buffer != NULL) {
+ free(res->in_buffer);
+ }
+ if (res->ref_buffer != NULL) {
+ free(res->ref_buffer);
+ }
+ if (res->out_buffer != NULL) {
+ free(res->out_buffer);
+ }
+}
diff --git a/resources.h b/resources.h
new file mode 100644
index 0000000..4b16312
--- /dev/null
+++ b/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 */