From 4724e178c19815040e734bef3abe192042b7f6e2 Mon Sep 17 00:00:00 2001 From: Andrew Grechkin Date: Mon, 19 Aug 2019 21:00:08 +0200 Subject: [PATCH] first version --- .gitignore | 1 + CMakeLists.txt | 14 +++++++++ src/config.cpp | 52 +++++++++++++++++++++++++++++++ src/file.cpp | 69 ++++++++++++++++++++++++++++++++++++++++++ src/include/config.hpp | 24 +++++++++++++++ src/include/file.hpp | 19 ++++++++++++ src/include/memory.hpp | 8 +++++ src/include/stats.hpp | 14 +++++++++ src/main.cpp | 29 ++++++++++++++++++ src/memory.cpp | 9 ++++++ src/stats.cpp | 10 ++++++ 11 files changed, 249 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 src/config.cpp create mode 100644 src/file.cpp create mode 100644 src/include/config.hpp create mode 100644 src/include/file.hpp create mode 100644 src/include/memory.hpp create mode 100644 src/include/stats.hpp create mode 100644 src/main.cpp create mode 100644 src/memory.cpp create mode 100644 src/stats.cpp diff --git a/.gitignore b/.gitignore index 9ea395f..8461cb4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ cmake_install.cmake install_manifest.txt compile_commands.json CTestTestfile.cmake +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3f27ef0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.0.0) +project(sparse VERSION 1.0.0) + +add_executable(sparse src/main.cpp src/config.cpp src/file.cpp src/memory.cpp src/stats.cpp) + +target_include_directories(sparse PRIVATE src/include) + +include(CTest) +enable_testing() + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CPACK_PROJECT_NAME ${PROJECT_NAME}) +set(CPACK_PROJECT_VERSION ${PROJECT_VERSION}) +include(CPack) diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 0000000..c2f83fa --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,52 @@ +#include "config.hpp" + +#include +#include +#include + +const char *argp_program_version = "v1.0.0"; +const char *argp_program_bug_address = "Andrew Grechkin "; + +auto args_doc = "FILE"; +auto doc = "sparse -- a simple tool to pipe standard input into a sparse file"; + +const argp_option options[] = { + {"input", 'i', "FILE", 0, "Input from FILE instead of standard input" }, + {"verbose", 'v', 0, 0, "Produce verbose output" }, + { 0 }, +}; + +error_t parse_opt(int key, char *arg, struct argp_state *state) { + auto args = static_cast(state->input); + switch (key) { + case 'v': + args->verbose = 1; + break; + case 'i': + args->input_file = arg; + break; + case ARGP_KEY_NO_ARGS: + argp_usage(state); + case ARGP_KEY_ARG: + args->file = arg; + state->next = state->argc; + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +const argp argp = { options, parse_opt, args_doc, doc, 0, 0, 0 }; + +int Config::parse(int argc, char ** argv) +{ + buf_size = getpagesize(); + return argp_parse(&argp, argc, argv, 0, nullptr, this); +} + +Config& Config::get() +{ + static Config inst; + return inst; +} diff --git a/src/file.cpp b/src/file.cpp new file mode 100644 index 0000000..1879a0e --- /dev/null +++ b/src/file.cpp @@ -0,0 +1,69 @@ +#include "config.hpp" +#include "file.hpp" +#include "stats.hpp" +#include "memory.hpp" + +#include +#include +#include +#include +#include + +File::~File() +{ + if (close(fd)) + warn("Unable to properly close destination file"); +} + +File::File(const char* path) +{ + auto mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + + fd = open(path, O_WRONLY | O_CREAT | O_EXCL, mode); + + if (fd == -1) + err(EXIT_FAILURE, "Unable to create file '%s'", path); +} + +void File::save_sparce_file(int src) +{ + auto& config = Config::get(); + + auto buf = malloc(config.buf_size); + if (!buf) + err(EXIT_FAILURE, "Unable to create buffer"); + + auto stats = Stats(); + while (true) { + auto bytes_read = read(src, buf, config.buf_size); + + if (bytes_read == 0) { + break; + } else if (bytes_read < 0) { + err(EXIT_FAILURE, "Unable to read data from source"); + } + + stats.total_read += bytes_read; + if (is_memory_dirty(buf, bytes_read)) { + auto bytes_written = write(fd, buf, bytes_read); + if (bytes_written != bytes_read) { + err(EXIT_FAILURE, "Unable to write into destination file"); + } + stats.total_written += bytes_written; + } else { + lseek(fd, bytes_read, SEEK_CUR); + ftruncate(fd, current_offset()); + stats.total_saved += bytes_read; + } + } + + if (config.verbose) { + printf("Buffer used: %ld bytes\n", config.buf_size); + stats.print(); + } +} + +size_t File::current_offset() const +{ + return lseek(fd, 0, SEEK_CUR); +} diff --git a/src/include/config.hpp b/src/include/config.hpp new file mode 100644 index 0000000..428a034 --- /dev/null +++ b/src/include/config.hpp @@ -0,0 +1,24 @@ +#ifndef __SPARSE__CONFIG_HPP__ +#define __SPARSE__CONFIG_HPP__ + +#include + +struct Config +{ + static Config& get(); + + size_t verbose = 0; + size_t buf_size = 0; + char *input_file = nullptr; + char *file = nullptr; + + int parse(int argc, char ** argv); + + Config(const Config&) = delete; + void operator= (const Config&) = delete; + +private: + Config() {} +}; + +#endif diff --git a/src/include/file.hpp b/src/include/file.hpp new file mode 100644 index 0000000..92266d1 --- /dev/null +++ b/src/include/file.hpp @@ -0,0 +1,19 @@ +#ifndef __SPARSE__FILE_HPP__ +#define __SPARSE__FILE_HPP__ + +#include + +class File +{ + public: + ~File(); + File(const char* path); + void save_sparce_file(int src); + void save_sparce_file(int src, int buf_size); + size_t current_offset() const; + + private: + int fd; +}; + +#endif diff --git a/src/include/memory.hpp b/src/include/memory.hpp new file mode 100644 index 0000000..c1c4fbc --- /dev/null +++ b/src/include/memory.hpp @@ -0,0 +1,8 @@ +#ifndef __SPARSE__MEMORY_HPP__ +#define __SPARSE__MEMORY_HPP__ + +#include + +bool is_memory_dirty(const void* buf, size_t size); + +#endif diff --git a/src/include/stats.hpp b/src/include/stats.hpp new file mode 100644 index 0000000..22d7ed2 --- /dev/null +++ b/src/include/stats.hpp @@ -0,0 +1,14 @@ +#ifndef __SPARSE__STATS_HPP__ +#define __SPARSE__STATS_HPP__ + +#include + +struct Stats +{ + size_t total_read = 0; + size_t total_written = 0; + size_t total_saved = 0; + void print() const; +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..fb4f81f --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,29 @@ +#include "config.hpp" +#include "file.hpp" + +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + auto& config = Config::get(); + + auto err = config.parse(argc, argv); + if (!err) { + auto src = 0; + if (config.input_file) { + } + + auto dst = File(config.file); + dst.save_sparce_file(src); + + if (src && close(src)) + ::err(EXIT_FAILURE, "Unable to close source"); + + return EXIT_SUCCESS; + } + + return err; +} diff --git a/src/memory.cpp b/src/memory.cpp new file mode 100644 index 0000000..c4011e3 --- /dev/null +++ b/src/memory.cpp @@ -0,0 +1,9 @@ +#include "memory.hpp" + +#include + +bool is_memory_dirty(const void* buf, size_t size) +{ + auto ptr = static_cast(buf); + return *ptr || std::memcmp(ptr, ptr + 1, size - 1); +} diff --git a/src/stats.cpp b/src/stats.cpp new file mode 100644 index 0000000..1994f6d --- /dev/null +++ b/src/stats.cpp @@ -0,0 +1,10 @@ +#include "stats.hpp" + +#include + +void Stats::print() const +{ + std::printf("Read total: %lu bytes\n", total_read); + std::printf("Saved total: %lu bytes\n", total_saved); + std::printf("Written total: %lu bytes\n", total_written); +}