From 948a65cf39c8ce31c5adc0f24979e0cb55bc33c3 Mon Sep 17 00:00:00 2001 From: Jan Sucan Date: Sat, 18 Jun 2022 08:50:45 +0200 Subject: Refactor the command line interface Select operation by its name instead of by number of the arguments. --- README.md | 19 +- src/Makefile | 2 +- src/backup.c | 41 +--- src/backup.h | 3 +- src/main.c | 65 +----- src/operation_id.h | 37 ++++ src/options.c | 222 +++++++++++++++------ src/options.h | 26 ++- src/resources.c | 187 +++++++++++++++-- src/resources.h | 31 ++- src/restore.c | 94 ++++----- src/restore.h | 3 +- tests/001-missing_arguments.sh | 10 - tests/001-no_arguments.sh | 9 + tests/002-missing_arguments.sh | 10 + tests/002-too_many_arguments.sh | 9 - tests/003-too_many_arguments.sh | 10 + tests/003-unknown_option.sh | 9 - tests/004-missing_argument_for_option.sh | 10 - tests/004-unknown_option.sh | 10 + tests/005-incorrect_buffer_size.sh | 11 - tests/005-missing_argument_for_option.sh | 13 ++ tests/006-incorrect_buffer_size.sh | 15 ++ tests/006-incorrect_sector_size.sh | 11 - tests/007-incorrect_sector_size.sh | 15 ++ tests/008-help_option.sh | 11 + tests/100-cannot_open_files.sh | 6 +- tests/200-input_and_reference_size_differs.sh | 2 +- ...eference_size_is_not_multiple_of_sector_size.sh | 4 +- tests/300-incorrect_reference_file.sh | 14 +- tests/400-successful_backup_restore.sh | 4 +- tests/assert.sh | 67 ++++--- 32 files changed, 641 insertions(+), 339 deletions(-) create mode 100644 src/operation_id.h delete mode 100644 tests/001-missing_arguments.sh create mode 100644 tests/001-no_arguments.sh create mode 100644 tests/002-missing_arguments.sh delete mode 100644 tests/002-too_many_arguments.sh create mode 100644 tests/003-too_many_arguments.sh delete mode 100644 tests/003-unknown_option.sh delete mode 100644 tests/004-missing_argument_for_option.sh create mode 100644 tests/004-unknown_option.sh delete mode 100644 tests/005-incorrect_buffer_size.sh create mode 100644 tests/005-missing_argument_for_option.sh create mode 100644 tests/006-incorrect_buffer_size.sh delete mode 100644 tests/006-incorrect_sector_size.sh create mode 100644 tests/007-incorrect_sector_size.sh create mode 100644 tests/008-help_option.sh diff --git a/README.md b/README.md index 9d6e155..cfb6a4e 100644 --- a/README.md +++ b/README.md @@ -9,21 +9,18 @@ e.g. the differential image file is read twice when restoring it. ## Synopsis -> diff-dd [-s SECTOR_SIZE] [-b BUFFER_SIZE] [INFILE] REFFILE OUTFILE +> diff-dd help -## Usage +> diff-dd backup [-s SECTOR_SIZE] [-b BUFFER_SIZE] INFILE REFFILE OUTFILE -The utility is used for creating and restoring differential images -created by it. Meaning of the ```INFILE``` and ```REFFILE``` depends -on whether backup mode or restore mode is requested. Providing ```INFILE``` -selects the backup mode. Omitting it selects the restore mode. +> diff-dd restore [-s SECTOR_SIZE] [-b BUFFER_SIZE] INFILE OUTFILE ## Backup Using ```diff-dd ``` for backup requires the full backup image to exist. Differential backup is created with: -> diff-dd INFILE REFFILE OUTFILE +> diff-dd backup INFILE REFFILE OUTFILE The ```INFILE``` is a path to the file to backup differentially, the ```REFFILE``` is the full image, and the ```OUTFILE``` is the file to @@ -33,9 +30,9 @@ which only the changed sectors of the ```INFILE```, compared to the ## Restore The restoration means application of the changed sectors saved in the -```REFFILE```, which is the differential image, to the ```OUTFILE```: +```INFILE```, which is the differential image, to the ```OUTFILE```: -> diff-dd REFFILE OUTFILE +> diff-dd restore INFILE OUTFILE ## Options @@ -55,13 +52,13 @@ First, the full image of the partition to backup has to be created: When the user decides to create the differential image, he or she runs: -> diff-dd /dev/sda1 full.img diff.img +> diff-dd backup /dev/sda1 full.img diff.img If a data accident happens, the partition can be restored by running: > dd bs=4M if=full.img of=/dev/sda1 -> diff-dd diff.img /dev/sda1 +> diff-dd restore diff.img /dev/sda1 The first command restores the old full image. The second one applies the differences. diff --git a/src/Makefile b/src/Makefile index 8b24b35..4deaaec 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,5 +1,5 @@ CC=gcc -CFLAGS=-Wall +CFLAGS=-Wall -fmax-errors=2 SOURCES=*.c HEADERS=*.h diff --git a/src/backup.c b/src/backup.c index 4691bac..4c9dfbd 100644 --- a/src/backup.c +++ b/src/backup.c @@ -34,15 +34,14 @@ #include #include -static int check_files(const options_t *const opts, - const resources_t *const res); -static int allocate_buffers(const options_t *const opts, size_t out_buffer_size, - resources_t *const res); +static int check_files(const options_backup_t *const opts, + const resources_backup_t *const res); static int write_out_buffer(const char *const buffer, size_t size, FILE *const file); static int -check_files(const options_t *const opts, const resources_t *const res) +check_files(const options_backup_t *const opts, + const resources_backup_t *const res) { const long in_size = file_size(res->in_file); @@ -72,25 +71,6 @@ check_files(const options_t *const opts, const resources_t *const res) return 0; } -static int -allocate_buffers(const options_t *const opts, size_t out_buffer_size, - resources_t *const res) -{ - - 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; - } - - return 0; -} - static int write_out_buffer(const char *const buffer, size_t size, FILE *const file) { @@ -105,21 +85,12 @@ write_out_buffer(const char *const buffer, size_t size, FILE *const file) } int -backup(const options_t *const opts, resources_t *const res) +backup(const options_backup_t *const opts, const resources_backup_t *const res) { if (check_files(opts, res) != 0) { return 1; } - /* 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 (allocate_buffers(opts, out_buffer_size, res) != 0) { - return 1; - } - size_t out_buffer_index = 0; uint64_t input_file_offset = 0; @@ -148,7 +119,7 @@ backup(const options_t *const opts, resources_t *const res) res->ref_buffer + buffer_offset, opts->sector_size) != 0) { /* Backup the changed sector */ - if (out_buffer_index >= out_buffer_size) { + if (out_buffer_index >= res->out_buffer_size) { /* The output buffer is full. Write it to the output file */ if (write_out_buffer(res->out_buffer, out_buffer_index, res->out_file) != 0) { diff --git a/src/backup.h b/src/backup.h index 057aa15..9f14559 100644 --- a/src/backup.h +++ b/src/backup.h @@ -30,6 +30,7 @@ #include "options.h" #include "resources.h" -int backup(const options_t *const opts, resources_t *const res); +int backup(const options_backup_t *const opts, + const resources_backup_t *const res); #endif /* BACKUP_H */ diff --git a/src/main.c b/src/main.c index 80e6129..35e5c3d 100644 --- a/src/main.c +++ b/src/main.c @@ -25,19 +25,11 @@ */ #include "backup.h" -#include "file.h" #include "options.h" -#include "print.h" #include "resources.h" #include "restore.h" -#include -#include -#include -#include -#include #include -#include static void clean_exit(resources_t *const res, int exit_code) @@ -46,67 +38,26 @@ clean_exit(resources_t *const res, int exit_code) exit(exit_code); } -static 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; -} - int main(int argc, char **argv) { options_t opts; - if (options_parse(argc, argv, &opts)) { + if (!options_parse(argc, argv, &opts)) { options_usage(1); - } else if (opts.help) { + } else if (options_is_operation(&opts, OPERATION_ID_HELP)) { options_usage(0); } - 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) { + if (resources_allocate(&opts, &res)) { clean_exit(&res, 1); - } else if (is_action_backup) { - clean_exit(&res, backup(&opts, &res)); + } else if (options_is_operation(&opts, OPERATION_ID_BACKUP)) { + clean_exit(&res, backup(options_get_for_backup(&opts), + resources_get_for_backup(&res))); } else { - clean_exit(&res, restore(&opts, &res)); + clean_exit(&res, restore(options_get_for_restore(&opts), + resources_get_for_restore(&res))); } } diff --git a/src/operation_id.h b/src/operation_id.h new file mode 100644 index 0000000..99536af --- /dev/null +++ b/src/operation_id.h @@ -0,0 +1,37 @@ +/* Copyright 2019 Ján Sučan + * + * 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. + */ + +#ifndef OPERATION_ID_H +#define OPERATION_ID_H + +typedef enum { + OPERATION_ID_UNKNOWN, + OPERATION_ID_HELP, + OPERATION_ID_BACKUP, + OPERATION_ID_RESTORE, +} operation_id_t; + +#endif /* OPERATION_ID_H */ diff --git a/src/options.c b/src/options.c index 002b36c..e2fcfdb 100644 --- a/src/options.c +++ b/src/options.c @@ -38,22 +38,126 @@ */ #include "program_info.h" +#define OPTIONS_MAX_OPERATION_NAME_LENGTH 8 #define OPTIONS_DEFAULT_SECTOR_SIZE 512 #define OPTIONS_DEFAULT_BUFFER_SIZE (4 * 1024 * 1024) -static void options_init(options_t *const opts); +struct common_options { + bool help; + uint32_t sector_size; + uint32_t buffer_size; +}; + static int options_parse_unsigned(const char *const arg, uint32_t *const value); +static void options_parse_operation(const char *const argv, + options_t *const opts); +static bool options_parse_common(int *const argc, char ***const argv, + struct common_options *const opts); +static bool options_parse_backup(int *const argc, char ***const argv, + options_t *const opts); +static bool options_parse_restore(int *const argc, char ***const argv, + options_t *const opts); -int +bool options_parse(int argc, char **argv, options_t *const opts) { - options_init(opts); + // Skip the executable name + --argc; + ++argv; + + options_parse_operation(argv[0], opts); + + if (options_is_operation(opts, OPERATION_ID_UNKNOWN)) { + return false; + } + + if (options_is_operation(opts, OPERATION_ID_BACKUP)) { + if (!options_parse_backup(&argc, &argv, opts)) { + return false; + } + } else if (options_is_operation(opts, OPERATION_ID_RESTORE)) { + if (!options_parse_restore(&argc, &argv, opts)) { + return false; + } + } + return true; +} + +void +options_usage(int exit_code) +{ + printf("Usage: %s [-s SECTOR_SIZE] [-b BUFFER_SIZE] [INFILE] REFFILE " + "OUTFILE\n", + PROGRAM_NAME_STR); + exit(exit_code); +} + +bool +options_is_operation(const options_t *const opts, operation_id_t operation_id) +{ + return (opts->operation_id == operation_id); +} + +const options_backup_t * +options_get_for_backup(const options_t *const opts) +{ + return &(opts->op.backup); +} + +const options_restore_t * +options_get_for_restore(const options_t *const opts) +{ + return &(opts->op.restore); +} + +static 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; +} + +static void +options_parse_operation(const char *const op_name, options_t *const opts) +{ + if (op_name == NULL) { + opts->operation_id = OPERATION_ID_UNKNOWN; + } else if (strncmp(op_name, "help", OPTIONS_MAX_OPERATION_NAME_LENGTH) == + 0) { + opts->operation_id = OPERATION_ID_HELP; + } else if (strncmp(op_name, "backup", OPTIONS_MAX_OPERATION_NAME_LENGTH) == + 0) { + opts->operation_id = OPERATION_ID_BACKUP; + } else if (strncmp(op_name, "restore", OPTIONS_MAX_OPERATION_NAME_LENGTH) == + 0) { + opts->operation_id = OPERATION_ID_RESTORE; + } else { + opts->operation_id = OPERATION_ID_UNKNOWN; + } +} + +static void +options_init_common(struct common_options *const opts) +{ + opts->help = false; + opts->sector_size = OPTIONS_DEFAULT_SECTOR_SIZE; + opts->buffer_size = OPTIONS_DEFAULT_BUFFER_SIZE; +} +static bool +options_parse_common(int *const argc, char ***const argv, + struct common_options *const opts) +{ int ch; char *arg_sector_size = NULL; char *arg_buffer_size = NULL; - while ((ch = getopt(argc, argv, ":b:hs:")) != -1) { + while ((ch = getopt(*argc, *argv, ":b:hs:")) != -1) { switch (ch) { case 'b': arg_buffer_size = optarg; @@ -69,90 +173,94 @@ options_parse(int argc, char **argv, options_t *const opts) case ':': print_error("missing argument for option '-%c'", optopt); - return 1; - + return false; default: print_error("unknown option '-%c'", optopt); - return 1; + return false; } } - 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; - } + *argc -= optind; + *argv += optind; /* 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; + return false; } else if ((arg_buffer_size != NULL) && options_parse_unsigned(arg_buffer_size, &(opts->buffer_size))) { print_error("incorrect buffer size"); - return 1; + return false; } else if (opts->sector_size == 0) { print_error("sector size cannot be 0"); - return 1; + return false; } else if (opts->buffer_size == 0) { print_error("buffer size cannot be 0"); - return 1; + return false; } else if (opts->sector_size > opts->buffer_size) { print_error("sector size cannot larger than buffer size"); - return 1; + return false; } else if ((opts->buffer_size % opts->sector_size) != 0) { print_error("buffer size is not multiple of sector size"); - return 1; + return false; } - /* 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; + return true; } -void -options_usage(int exit_code) +static bool +options_parse_backup(int *const argc, char ***const argv, options_t *const opts) { - printf("Usage: %s [-s SECTOR_SIZE] [-b BUFFER_SIZE] [INFILE] REFFILE " - "OUTFILE\n", - PROGRAM_NAME_STR); - exit(exit_code); -} + struct common_options common_opts; + options_init_common(&common_opts); + if (!options_parse_common(argc, argv, &common_opts)) { + return false; + } -static void -options_init(options_t *const opts) -{ - opts->help = false; - opts->sector_size = OPTIONS_DEFAULT_SECTOR_SIZE; - opts->buffer_size = OPTIONS_DEFAULT_BUFFER_SIZE; + if (common_opts.help) { + opts->operation_id = OPERATION_ID_HELP; + } else if (*argc < 3) { + print_error("missing arguments"); + return false; + } else if (*argc > 3) { + print_error("too many arguments"); + return false; + } else { + opts->op.backup.sector_size = common_opts.sector_size; + opts->op.backup.buffer_size = common_opts.buffer_size; + opts->op.backup.in_file_path = (*argv)[0]; + opts->op.backup.ref_file_path = (*argv)[1]; + opts->op.backup.out_file_path = (*argv)[2]; + } - opts->in_file_path = NULL; - opts->ref_file_path = NULL; - opts->out_file_path = NULL; + return true; } -static int -options_parse_unsigned(const char *const arg, uint32_t *const value) +static bool +options_parse_restore(int *const argc, char ***const argv, + options_t *const opts) { - char *end; - - errno = 0; + struct common_options common_opts; + options_init_common(&common_opts); + if (!options_parse_common(argc, argv, &common_opts)) { + return false; + } - *value = strtoul(arg, &end, 0); + if (common_opts.help) { + opts->operation_id = OPERATION_ID_HELP; + } else if (*argc < 2) { + print_error("missing arguments"); + return false; + } else if (*argc > 2) { + print_error("too many arguments"); + return false; + } else { + opts->op.restore.sector_size = common_opts.sector_size; + opts->op.restore.buffer_size = common_opts.buffer_size; + opts->op.restore.in_file_path = (*argv)[0]; + opts->op.restore.out_file_path = (*argv)[1]; + } - return ((*end != '\0') || (errno != 0)) ? -1 : 0; + return true; } diff --git a/src/options.h b/src/options.h index 9ffa14f..e162fbe 100644 --- a/src/options.h +++ b/src/options.h @@ -27,20 +27,40 @@ #ifndef OPTIONS_H #define OPTIONS_H +#include "operation_id.h" + #include #include 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_backup_t; + +typedef struct { + uint32_t sector_size; + uint32_t buffer_size; + const char *in_file_path; + const char *out_file_path; +} options_restore_t; + +typedef struct { + operation_id_t operation_id; + + union { + options_backup_t backup; + options_restore_t restore; + } op; } options_t; -int options_parse(int argc, char **argv, options_t *const opts); +bool options_parse(int argc, char **argv, options_t *const opts); void options_usage(int exit_code); +bool options_is_operation(const options_t *const opts, + operation_id_t operation_id); +const options_backup_t *options_get_for_backup(const options_t *const opts); +const options_restore_t *options_get_for_restore(const options_t *const opts); #endif /* OPTIONS_H */ diff --git a/src/resources.c b/src/resources.c index 65b7f71..0829c7c 100644 --- a/src/resources.c +++ b/src/resources.c @@ -26,45 +26,188 @@ #include "resources.h" +#include "print.h" + +#include #include #include +#include -void -resources_init(resources_t *const res) +static void +resources_init_for_backup(resources_backup_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) +static int +resources_allocate_for_backup(const options_backup_t *const opts, + resources_backup_t *const res) { - if (res->in_file != NULL) { - fclose(res->in_file); - res->in_file = NULL; + if ((res->in_file = fopen(opts->in_file_path, "r")) == NULL) { + print_error("cannot open input file: %s", strerror(errno)); + return 1; } - if (res->ref_file != NULL) { - fclose(res->ref_file); - res->ref_file = NULL; + + if ((res->ref_file = fopen(opts->ref_file_path, "r")) == NULL) { + print_error("cannot open reference file: %s", strerror(errno)); + return 1; } - if (res->out_file != NULL) { - fclose(res->out_file); - res->out_file = NULL; + + /* When backing up, the output file is truncated to hold the + * new data + */ + if ((res->out_file = fopen(opts->out_file_path, "w+")) == NULL) { + print_error("cannot open output file: %s", strerror(errno)); + return 1; } - if (res->in_buffer != NULL) { - free(res->in_buffer); - res->in_buffer = NULL; + + /* The output buffer contains also the offsets */ + res->out_buffer_size = + ((opts->buffer_size / opts->sector_size) * sizeof(uint64_t)) + + opts->buffer_size; + + // TODO: separate function + 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(res->out_buffer_size)) == + NULL) { + print_error("cannot allocate buffer for output file data"); + return 1; } - if (res->ref_buffer != NULL) { - free(res->ref_buffer); - res->ref_buffer = NULL; + + return 0; +} + +static void +resources_init_for_restore(resources_restore_t *const res) +{ + res->in_file = NULL; + res->out_file = NULL; + + res->in_buffer = NULL; + res->out_buffer = NULL; +} + +static int +resources_allocate_for_restore(const options_restore_t *const opts, + resources_restore_t *const res) +{ + if ((res->in_file = fopen(opts->in_file_path, "r")) == NULL) { + print_error("cannot open input file: %s", strerror(errno)); + return 1; } - if (res->out_buffer != NULL) { - free(res->out_buffer); - res->out_buffer = NULL; + + /* When restoring, the file must be opened for writing and not + * truncated + */ + if ((res->out_file = fopen(opts->out_file_path, "r+")) == NULL) { + print_error("cannot open output file: %s", strerror(errno)); + return 1; } + + /* Allocate the buffer for data from the input file */ + /* The input buffer contains also the offsets */ + res->in_sector_size = sizeof(uint64_t) + opts->sector_size; + const size_t in_buffer_sector_count = + opts->buffer_size / res->in_sector_size; + res->in_buffer_size = in_buffer_sector_count * res->in_sector_size; + + if ((res->in_buffer = (char *)malloc(res->in_buffer_size)) == NULL) { + print_error("cannot allocate buffer for input file data"); + return 1; + } + + return 0; +} + +static void +resources_close_file(FILE **const file) +{ + if (*file != NULL) { + fclose(*file); + *file = NULL; + } +} + +static void +resources_close_buffer(char **const buffer) +{ + if (*buffer != NULL) { + free(*buffer); + *buffer = NULL; + } +} + +static void +resources_free_backup(resources_backup_t *const res) +{ + resources_close_file(&(res->in_file)); + resources_close_file(&(res->ref_file)); + resources_close_file(&(res->out_file)); + + resources_close_buffer(&(res->in_buffer)); + resources_close_buffer(&(res->ref_buffer)); + resources_close_buffer(&(res->out_buffer)); +} + +static void +resources_free_restore(resources_restore_t *const res) +{ + resources_close_file(&(res->in_file)); + resources_close_file(&(res->out_file)); + + resources_close_buffer(&(res->in_buffer)); + resources_close_buffer(&(res->out_buffer)); +} + +int +resources_allocate(const options_t *const opts, resources_t *const res) +{ + res->operation_id = opts->operation_id; + + if (res->operation_id == OPERATION_ID_BACKUP) { + resources_init_for_backup(&(res->res.backup)); + return resources_allocate_for_backup(&(opts->op.backup), + &(res->res.backup)); + } else if (res->operation_id == OPERATION_ID_RESTORE) { + resources_init_for_restore(&(res->res.restore)); + return resources_allocate_for_restore(&(opts->op.restore), + &(res->res.restore)); + } + + return 0; +} + +const resources_backup_t * +resources_get_for_backup(const resources_t *const res) +{ + return &(res->res.backup); +} + +const resources_restore_t * +resources_get_for_restore(const resources_t *const res) +{ + return &(res->res.restore); +} + +void +resources_free(resources_t *const res) +{ + if (res->operation_id == OPERATION_ID_BACKUP) { + resources_free_backup(&(res->res.backup)); + } else if (res->operation_id == OPERATION_ID_RESTORE) { + resources_free_restore(&(res->res.restore)); + } + + res->operation_id = OPERATION_ID_UNKNOWN; } diff --git a/src/resources.h b/src/resources.h index 3c6e768..3cc0dff 100644 --- a/src/resources.h +++ b/src/resources.h @@ -27,6 +27,9 @@ #ifndef RESOURCES_H #define RESOURCES_H +#include "operation_id.h" +#include "options.h" + #include typedef struct { @@ -37,9 +40,35 @@ typedef struct { char *in_buffer; char *ref_buffer; char *out_buffer; + + size_t out_buffer_size; +} resources_backup_t; + +typedef struct { + FILE *in_file; + FILE *out_file; + + char *in_buffer; + char *out_buffer; + + size_t in_sector_size; + size_t in_buffer_size; +} resources_restore_t; + +typedef struct { + operation_id_t operation_id; + + union { + resources_backup_t backup; + resources_restore_t restore; + } res; } resources_t; -void resources_init(resources_t *const res); +int resources_allocate(const options_t *const opts, resources_t *const res); +const resources_backup_t * +resources_get_for_backup(const resources_t *const res); +const resources_restore_t * +resources_get_for_restore(const resources_t *const res); void resources_free(resources_t *const res); #endif /* RESOURCES_H */ diff --git a/src/restore.c b/src/restore.c index 6318766..df58ab8 100644 --- a/src/restore.c +++ b/src/restore.c @@ -37,22 +37,21 @@ #include static bool -is_reference_file_valid(resources_t *const res, uint32_t sector_size) +is_input_file_valid(const resources_restore_t *const res, uint32_t sector_size) { - const long ref_size = file_size(res->ref_file); + const long in_size = file_size(res->in_file); - if (ref_size < 0) { - print_error("cannot get size of reference file: %s", strerror(errno)); + if (in_size < 0) { + print_error("cannot get size of input file: %s", strerror(errno)); return false; - } else if (ref_size == 0) { - print_error("reference file is empty"); + } else if (in_size == 0) { + print_error("input 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 + } else if ((in_size % (sizeof(uint64_t) + sector_size)) != 0) { + /* The input 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"); + print_error("input file has size that cannot contain valid diff data"); return false; } @@ -66,18 +65,18 @@ is_reference_file_valid(resources_t *const res, uint32_t sector_size) uint64_t prev_out_offset = 0; bool is_first_reading = true; - /* Scan the reference file and check */ + /* Scan the input 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); + const size_t in_read = + fread(&out_offset, sizeof(out_offset), 1U, res->in_file); out_offset = le64toh(out_offset); - if (feof(res->ref_file)) { + if (feof(res->in_file)) { break; - } else if ((ref_read != 1U) || ferror(res->ref_file)) { - print_error("cannot read from reference file: %s", strerror(errno)); + } else if ((in_read != 1U) || ferror(res->in_file)) { + print_error("cannot read from input file: %s", strerror(errno)); return false; } else if (!is_first_reading && (out_offset <= prev_out_offset)) { print_error("a sector offset points behind the previous offset"); @@ -86,8 +85,8 @@ is_reference_file_valid(resources_t *const res, uint32_t sector_size) print_error( "a sector offset points past the end of the output file"); return false; - } else if (fseek(res->ref_file, sector_size, SEEK_CUR) != 0) { - print_error("cannot seek in reference file: %s", strerror(errno)); + } else if (fseek(res->in_file, sector_size, SEEK_CUR) != 0) { + print_error("cannot seek in input file: %s", strerror(errno)); return false; } @@ -95,13 +94,13 @@ is_reference_file_valid(resources_t *const res, uint32_t sector_size) 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"); + if (ftell(res->in_file) != in_size) { + /* The input file must be read completely */ + print_error("input file is not valid"); return false; - } else if (fseek(res->ref_file, 0L, SEEK_SET) != 0) { + } else if (fseek(res->in_file, 0L, SEEK_SET) != 0) { /* The file must be prepared for the restoring */ - print_error("cannot seek in reference file: %s", strerror(errno)); + print_error("cannot seek in input file: %s", strerror(errno)); return false; } @@ -109,28 +108,18 @@ is_reference_file_valid(resources_t *const res, uint32_t sector_size) } int -restore(const options_t *const opts, resources_t *const res) +restore(const options_restore_t *const opts, + const resources_restore_t *const res) { - /* Check validity of the reference file */ - if (!is_reference_file_valid(res, opts->sector_size)) { + /* Check validity of the input file */ + if (!is_input_file_valid(res, opts->sector_size)) { return 1; } - const long ref_size = file_size(res->ref_file); + const long in_size = file_size(res->in_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_sector_size = sizeof(uint64_t) + opts->sector_size; - const size_t ref_buffer_sector_count = opts->buffer_size / ref_sector_size; - const size_t ref_buffer_size = ref_buffer_sector_count * ref_sector_size; - - if ((res->ref_buffer = (char *)malloc(ref_buffer_size)) == NULL) { - print_error("cannot allocate buffer for reference file data"); + if (in_size < 0) { + print_error("cannot get size of input file: %s", strerror(errno)); return 1; } @@ -138,18 +127,19 @@ restore(const options_t *const opts, resources_t *const res) for (;;) { /* Read data of the offset and the next sector */ - const size_t ref_sectors_read = file_read_sectors( - res->ref_file, res->ref_buffer, ref_buffer_size, ref_sector_size); + const size_t in_sectors_read = + file_read_sectors(res->in_file, res->in_buffer, res->in_buffer_size, + res->in_sector_size); - if (ref_sectors_read == 0) { + if (in_sectors_read == 0) { break; } - char *ref_buffer = res->ref_buffer; + char *in_buffer = res->in_buffer; - for (size_t s = 0; s < ref_sectors_read; ++s) { - const uint64_t out_offset = le64toh(*((uint64_t *)ref_buffer)); - ref_buffer += sizeof(uint64_t); + for (size_t s = 0; s < in_sectors_read; ++s) { + const uint64_t out_offset = le64toh(*((uint64_t *)in_buffer)); + in_buffer += sizeof(uint64_t); if (fseek(res->out_file, out_offset, SEEK_SET) != 0) { print_error("cannot seek in output file: %s", strerror(errno)); @@ -157,8 +147,8 @@ restore(const options_t *const opts, resources_t *const res) } const size_t out_written = - fwrite(ref_buffer, opts->sector_size, 1U, res->out_file); - ref_buffer += opts->sector_size; + fwrite(in_buffer, opts->sector_size, 1U, res->out_file); + in_buffer += opts->sector_size; if (out_written != 1U) { print_error("cannot write to output file: %s", strerror(errno)); @@ -167,9 +157,9 @@ restore(const options_t *const opts, resources_t *const res) } } - /* The reference file must be read completely */ - if (ftell(res->ref_file) != ref_size) { - print_error("reference file is not valid"); + /* The input file must be read completely */ + if (ftell(res->in_file) != in_size) { + print_error("input file is not valid"); return 1; } diff --git a/src/restore.h b/src/restore.h index 02d70ff..a6c09d1 100644 --- a/src/restore.h +++ b/src/restore.h @@ -30,6 +30,7 @@ #include "options.h" #include "resources.h" -int restore(const options_t *const opts, resources_t *const res); +int restore(const options_restore_t *const opts, + const resources_restore_t *const res); #endif /* RESTORE_H */ diff --git a/tests/001-missing_arguments.sh b/tests/001-missing_arguments.sh deleted file mode 100644 index 8e00bf0..0000000 --- a/tests/001-missing_arguments.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -source ./assert.sh - -PROGRAM_EXEC="$1" - -assert_error "missing arguments" $PROGRAM_EXEC -assert_error "missing arguments" $PROGRAM_EXEC arg - -exit 0 diff --git a/tests/001-no_arguments.sh b/tests/001-no_arguments.sh new file mode 100644 index 0000000..150b0df --- /dev/null +++ b/tests/001-no_arguments.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +source ./assert.sh + +PROGRAM_EXEC="$1" + +assert "Usage:" "" 1 $PROGRAM_EXEC + +exit 0 diff --git a/tests/002-missing_arguments.sh b/tests/002-missing_arguments.sh new file mode 100644 index 0000000..4e1d0e3 --- /dev/null +++ b/tests/002-missing_arguments.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +source ./assert.sh + +PROGRAM_EXEC="$1" + +assert "Usage" "missing arguments" 1 $PROGRAM_EXEC backup +assert "Usage" "missing arguments" 1 $PROGRAM_EXEC restore + +exit 0 diff --git a/tests/002-too_many_arguments.sh b/tests/002-too_many_arguments.sh deleted file mode 100644 index b6a1990..0000000 --- a/tests/002-too_many_arguments.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -source ./assert.sh - -PROGRAM_EXEC="$1" - -assert_error "too many arguments" $PROGRAM_EXEC arg1 arg2 arg3 arg4 - -exit 0 diff --git a/tests/003-too_many_arguments.sh b/tests/003-too_many_arguments.sh new file mode 100644 index 0000000..fc51a42 --- /dev/null +++ b/tests/003-too_many_arguments.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +source ./assert.sh + +PROGRAM_EXEC="$1" + +assert "Usage" "too many arguments" 1 $PROGRAM_EXEC backup arg1 arg2 arg3 arg4 +assert "Usage" "too many arguments" 1 $PROGRAM_EXEC restore arg1 arg2 arg3 + +exit 0 diff --git a/tests/003-unknown_option.sh b/tests/003-unknown_option.sh deleted file mode 100644 index 919f125..0000000 --- a/tests/003-unknown_option.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -source ./assert.sh - -PROGRAM_EXEC="$1" - -assert_error "unknown option '-x'" $PROGRAM_EXEC -x ref out - -exit 0 diff --git a/tests/004-missing_argument_for_option.sh b/tests/004-missing_argument_for_option.sh deleted file mode 100644 index 071ea07..0000000 --- a/tests/004-missing_argument_for_option.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -source ./assert.sh - -PROGRAM_EXEC="$1" - -assert_error "missing argument for option '-b'" $PROGRAM_EXEC -b -assert_error "missing argument for option '-s'" $PROGRAM_EXEC -s - -exit 0 diff --git a/tests/004-unknown_option.sh b/tests/004-unknown_option.sh new file mode 100644 index 0000000..35ba4d6 --- /dev/null +++ b/tests/004-unknown_option.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +source ./assert.sh + +PROGRAM_EXEC="$1" + +assert "Usage" "unknown option '-x'" 1 $PROGRAM_EXEC backup -x in ref out +assert "Usage" "unknown option '-x'" 1 $PROGRAM_EXEC restore -x ref out + +exit 0 diff --git a/tests/005-incorrect_buffer_size.sh b/tests/005-incorrect_buffer_size.sh deleted file mode 100644 index 8c88b54..0000000 --- a/tests/005-incorrect_buffer_size.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -source ./assert.sh - -PROGRAM_EXEC="$1" - -assert_error "incorrect sector size" $PROGRAM_EXEC -s abc123 ref out -assert_error "sector size cannot be 0" $PROGRAM_EXEC -s 0 ref out -assert_error "sector size cannot larger than buffer size" $PROGRAM_EXEC -s 2 -b 1 ref out - -exit 0 diff --git a/tests/005-missing_argument_for_option.sh b/tests/005-missing_argument_for_option.sh new file mode 100644 index 0000000..faccbdd --- /dev/null +++ b/tests/005-missing_argument_for_option.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +source ./assert.sh + +PROGRAM_EXEC="$1" + +assert "Usage" "missing argument for option '-b'" 1 $PROGRAM_EXEC backup -b +assert "Usage" "missing argument for option '-s'" 1 $PROGRAM_EXEC backup -s + +assert "Usage" "missing argument for option '-b'" 1 $PROGRAM_EXEC restore -b +assert "Usage" "missing argument for option '-s'" 1 $PROGRAM_EXEC restore -s + +exit 0 diff --git a/tests/006-incorrect_buffer_size.sh b/tests/006-incorrect_buffer_size.sh new file mode 100644 index 0000000..476a30e --- /dev/null +++ b/tests/006-incorrect_buffer_size.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +source ./assert.sh + +PROGRAM_EXEC="$1" + +assert "Usage" "incorrect sector size" 1 $PROGRAM_EXEC backup -s abc123 in ref out +assert "Usage" "sector size cannot be 0" 1 $PROGRAM_EXEC backup -s 0 in ref out +assert "Usage" "sector size cannot larger than buffer size" 1 $PROGRAM_EXEC backup -s 2 -b 1 in ref out + +assert "Usage" "incorrect sector size" 1 $PROGRAM_EXEC restore -s abc123 in ref out +assert "Usage" "sector size cannot be 0" 1 $PROGRAM_EXEC restore -s 0 in ref out +assert "Usage" "sector size cannot larger than buffer size" 1 $PROGRAM_EXEC restore -s 2 -b 1 in ref out + +exit 0 diff --git a/tests/006-incorrect_sector_size.sh b/tests/006-incorrect_sector_size.sh deleted file mode 100644 index 484c09e..0000000 --- a/tests/006-incorrect_sector_size.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -source ./assert.sh - -PROGRAM_EXEC="$1" - -assert_error "incorrect buffer size" $PROGRAM_EXEC -b abc123 ref out -assert_error "buffer size cannot be 0" $PROGRAM_EXEC -b 0 ref out -assert_error "buffer size is not multiple of sector size" $PROGRAM_EXEC -b 3 -s 2 ref out - -exit 0 diff --git a/tests/007-incorrect_sector_size.sh b/tests/007-incorrect_sector_size.sh new file mode 100644 index 0000000..3a170c4 --- /dev/null +++ b/tests/007-incorrect_sector_size.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +source ./assert.sh + +PROGRAM_EXEC="$1" + +assert "Usage" "incorrect buffer size" 1 $PROGRAM_EXEC backup -b abc123 in ref out +assert "Usage" "buffer size cannot be 0" 1 $PROGRAM_EXEC backup -b 0 in ref out +assert "Usage" "buffer size is not multiple of sector size" 1 $PROGRAM_EXEC backup -b 3 -s 2 in ref out + +assert "Usage" "incorrect buffer size" 1 $PROGRAM_EXEC restore -b abc123 in ref out +assert "Usage" "buffer size cannot be 0" 1 $PROGRAM_EXEC restore -b 0 in ref out +assert "Usage" "buffer size is not multiple of sector size" 1 $PROGRAM_EXEC restore -b 3 -s 2 in ref out + +exit 0 diff --git a/tests/008-help_option.sh b/tests/008-help_option.sh new file mode 100644 index 0000000..d874780 --- /dev/null +++ b/tests/008-help_option.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +source ./assert.sh + +PROGRAM_EXEC="$1" + +assert "Usage" "" 0 $PROGRAM_EXEC help +assert "Usage" "" 0 $PROGRAM_EXEC backup -h +assert "Usage" "" 0 $PROGRAM_EXEC restore -h + +exit 0 diff --git a/tests/100-cannot_open_files.sh b/tests/100-cannot_open_files.sh index 4b49acd..07eb574 100644 --- a/tests/100-cannot_open_files.sh +++ b/tests/100-cannot_open_files.sh @@ -6,18 +6,18 @@ PROGRAM_EXEC="$1" rm -f input ref out touch ref out -assert_error "cannot open input file" $PROGRAM_EXEC input ref out +assert "" "cannot open input file" 1 $PROGRAM_EXEC backup input ref out rm -f input ref out touch input out -assert_error "cannot open reference file" $PROGRAM_EXEC input ref out +assert "" "cannot open reference file" 1 $PROGRAM_EXEC backup input ref out rm -f input ref out rmdir outdir 2>/dev/null touch input ref mkdir outdir chmod -w outdir -assert_error "cannot open output file" $PROGRAM_EXEC input ref outdir/out +assert "" "cannot open output file" 1 $PROGRAM_EXEC backup input ref outdir/out rm -f input ref out rmdir outdir diff --git a/tests/200-input_and_reference_size_differs.sh b/tests/200-input_and_reference_size_differs.sh index bf6c49f..50e7ff6 100644 --- a/tests/200-input_and_reference_size_differs.sh +++ b/tests/200-input_and_reference_size_differs.sh @@ -8,7 +8,7 @@ rm -f input ref dd if=/dev/zero of=input bs=500 count=1 1>/dev/null 2>&1 dd if=/dev/zero of=ref bs=501 count=1 1>/dev/null 2>&1 -assert_error "input file and reference file differ in size" $PROGRAM_EXEC input ref out +assert "" "input file and reference file differ in size" 1 $PROGRAM_EXEC backup input ref out rm -f input ref out diff --git a/tests/201-input_or_reference_size_is_not_multiple_of_sector_size.sh b/tests/201-input_or_reference_size_is_not_multiple_of_sector_size.sh index 055df09..0cb8207 100644 --- a/tests/201-input_or_reference_size_is_not_multiple_of_sector_size.sh +++ b/tests/201-input_or_reference_size_is_not_multiple_of_sector_size.sh @@ -8,8 +8,8 @@ rm -f input ref dd if=/dev/zero of=input bs=513 count=1 1>/dev/null 2>&1 dd if=/dev/zero of=ref bs=513 count=1 1>/dev/null 2>&1 -assert_error "size of input file and reference file is not multiple of [0-9]" \ - $PROGRAM_EXEC -s 512 input ref out +assert "" "size of input file and reference file is not multiple of [0-9]" \ + 1 $PROGRAM_EXEC backup -s 512 input ref out rm -f input ref out diff --git a/tests/300-incorrect_reference_file.sh b/tests/300-incorrect_reference_file.sh index b3de8c0..5e03c72 100644 --- a/tests/300-incorrect_reference_file.sh +++ b/tests/300-incorrect_reference_file.sh @@ -6,11 +6,11 @@ PROGRAM_EXEC="$1" rm -f ref out touch ref out -assert_error "reference file is empty" $PROGRAM_EXEC ref out +assert "" "input file is empty" 1 $PROGRAM_EXEC restore ref out dd if=/dev/zero of=ref bs=513 count=1 1>/dev/null 2>&1 -assert_error "reference file has size that cannot contain valid diff data" \ - $PROGRAM_EXEC -s 512 ref out +assert "" "input file has size that cannot contain valid diff data" \ + 1 $PROGRAM_EXEC restore -s 512 ref out rm -f ref out dd if=/dev/zero of=out bs=512 count=2 1>/dev/null 2>&1 @@ -20,8 +20,8 @@ dd if=/dev/zero of=ref bs=$(( 512 + 8 )) count=2 1>/dev/null 2>&1 printf '\x02' | dd of=ref bs=1 count=1 seek=0 conv=notrunc 1>/dev/null 2>&1 # The second offset will be 1 printf '\x01' | dd of=ref bs=1 count=1 seek=520 conv=notrunc 1>/dev/null 2>&1 -assert_error "a sector offset points behind the previous offset" \ - $PROGRAM_EXEC -s 512 ref out +assert "" "a sector offset points behind the previous offset" \ + 1 $PROGRAM_EXEC restore -s 512 ref out rm -f ref out dd if=/dev/zero of=out bs=512 count=1 1>/dev/null 2>&1 @@ -29,8 +29,8 @@ dd if=/dev/zero of=out bs=512 count=1 1>/dev/null 2>&1 dd if=/dev/zero of=ref bs=$(( 512 + 8 )) count=2 1>/dev/null 2>&1 # The first offset will be 1 printf '\x01' | dd of=ref bs=1 count=1 seek=0 conv=notrunc 1>/dev/null 2>&1 -assert_error "a sector offset points past the end of the output file" \ - $PROGRAM_EXEC -s 512 ref out +assert "" "a sector offset points past the end of the output file" \ + 1 $PROGRAM_EXEC restore -s 512 ref out rm -f ref out diff --git a/tests/400-successful_backup_restore.sh b/tests/400-successful_backup_restore.sh index ce5949a..517a458 100644 --- a/tests/400-successful_backup_restore.sh +++ b/tests/400-successful_backup_restore.sh @@ -29,7 +29,7 @@ printf '\xFF' | dd of=input bs=1 count=1 seek=$(( (512 * 3) - 1 )) conv=notrunc # The fourth sector will have the middle byte changed printf '\xFF' | dd of=input bs=1 count=1 seek=$(( (512 * 4) - (512 / 2) )) conv=notrunc 1>/dev/null 2>&1 -assert_success $PROGRAM_EXEC -s 512 input ref out +assert "" "" 0 $PROGRAM_EXEC backup -s 512 input ref out if ! files_are_the_same out 400-expected_backup_output.bin; then echo "assert: Backup output file differs from the expected one" @@ -44,7 +44,7 @@ if ! files_are_the_same ref input; then exit 1 fi -assert_success $PROGRAM_EXEC -s 512 out input +assert "" "" 0 $PROGRAM_EXEC restore -s 512 out input if ! files_are_the_same input backedup_input; then echo "assert: Cannot restore the backup" diff --git a/tests/assert.sh b/tests/assert.sh index 73b5dd4..cae9924 100644 --- a/tests/assert.sh +++ b/tests/assert.sh @@ -1,34 +1,55 @@ -function assert_error() +test_error=0 + +function print_assert_out_error() { - expected_stderr="$1" + echo "assert: $1 does not contain expected string" + echo " actual: $2" + echo " expected: $3" +} - shift 1 - actual_stderr="$("$@" 2>&1 1>/dev/null)" - retval=$? - is_stderr_expected="$(echo "$actual_stderr" | grep -i "$expected_stderr")" +function assert_retval() +{ + if [ $1 -ne $2 ]; then + echo "assert: Return value is $1, expected $2" + test_error=1 + fi +} - if [ $retval -eq 0 ]; then - echo "assert_error: Return value is $retval, expected != 0" - exit 1 - elif [ -z "$is_stderr_expected" ]; then - echo "assert_error: stderr does not contain expected string" - echo " actual: $actual_stderr" - echo " expected: $expected_stderr" - exit 1 +function assert_out() +{ + if [ -z "$2" -a -z "$3" ]; then + return + elif [ -z "$2" -a -n "$3" ]; then + print_assert_out_error $1 "$2" "$3" + elif [ -n "$2" -a -z "$3" ]; then + print_assert_out_error $1 "$2" "$3" + else + is_stderr_expected="$(echo "$2" | grep -i "$3")" + if [ -z "$is_stderr_expected" ]; then + print_assert_out_error $1 "$2" "$3" + fi fi } -function assert_success() +function assert() { - actual_stderr="$("$@" 2>&1 1>/dev/null)" - retval=$? + expected_stdout="$1" + expected_stderr="$2" + expected_retval="$3" + shift 3 - if [ $retval -ne 0 ]; then - echo "assert_error: Return value is $retval, expected 0" - exit 1 - elif [ -n "$actual_stderr" ]; then - echo "assert_error: stderr is not empty" - echo " actual: $actual_stderr" + "$@" 1>actual_stdout 2>actual_stderr + actual_retval=$? + actual_stdout="$(cat actual_stdout)" + actual_stderr="$(cat actual_stderr)" + rm actual_stdout actual_stderr + + test_error=0 + assert_retval $actual_retval $expected_retval + assert_out stdout "$actual_stdout" "$expected_stdout" + assert_out stderr "$actual_stderr" "$expected_stderr" + + if [ $test_error -ne 0 ]; then exit 1 fi } -- cgit v1.2.3