From 7afab50ab9954c54af5a3b015896d1a2e38388d9 Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Thu, 14 Mar 2019 16:29:14 +0100 Subject: [PATCH 1/2] Replace buggy libtar with libarchive --- .gitlab-ci.yml | 6 +- README.md | 2 +- configure.ac | 7 +- src/Makefile.am | 5 +- src/urd.cpp | 11 + src/utils.cpp | 20 + src/utils.hpp | 3 + src/utils/tar-archive.cpp | 522 +++++++++++++++++++++---- src/utils/tar-archive.hpp | 21 +- tests/Makefile.am | 8 + tests/compare-files.cpp | 5 +- tests/test-env.cpp | 10 +- tests/utils-tar.cpp | 793 +++++++++++++++++++++++++++++++++++++- 13 files changed, 1315 insertions(+), 98 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ac051b7..6e159b4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,7 +26,7 @@ before_script: protobuf-c-compiler libyaml-cpp-dev libyaml-dev - libtar-dev + libarchive-dev cmake - pushd . && @@ -155,7 +155,7 @@ test:coverage: protobuf-c-compiler libyaml-cpp-dev libyaml-dev - libtar-dev + libarchive-dev libcap2-bin valgrind cmake @@ -275,7 +275,7 @@ test:optimized: protobuf-c-compiler libyaml-cpp-dev libyaml-dev - libtar-dev + libarchive-dev libcap2-bin valgrind cmake diff --git a/README.md b/README.md index 2ee072c..b73965e 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ $ apt-get install -y libboost-system-dev libboost-filesystem-dev \ libboost-program-options-dev libboost-thread-dev \ libboost-regex-dev libprotobuf-dev protobuf-compiler \ libprotobuf-c-dev protobuf-c-compiler \ - libyaml-cpp-dev libyaml-dev libtar-dev + libyaml-cpp-dev libyaml-dev libarchive-dev # Building and installing libfabric (required for Mercury's OFI/libfabric plugin) $ git clone https://github.com/ofiwg/libfabric.git && diff --git a/configure.ac b/configure.ac index cc8858a..47ce66d 100644 --- a/configure.ac +++ b/configure.ac @@ -149,11 +149,8 @@ AC_SEARCH_LIBS([yaml_parser_initialize], [yaml], AC_SUBST(YAML_LIBS)], [AC_MSG_ERROR([This software requires libyaml >= 0.1.4])]) -# check for libtar manually (since it doesn't provide a pkgconfig file) -AC_SEARCH_LIBS([tar_open], [tar], - [TAR_LIBS="-ltar" - AC_SUBST(TAR_LIBS)], - [AC_MSG_ERROR([This software requires libtar >= 1.2.0])]) +# check for libarchive +PKG_CHECK_MODULES([LIBARCHIVE], [libarchive >= 3.1.2]) # Checks for header files. diff --git a/src/Makefile.am b/src/Makefile.am index 82a82ca..7d559b4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -90,7 +90,7 @@ liburd_resources_la_LDFLAGS = \ @BOOST_THREAD_LIB@ \ @MERCURY_LIBS@ \ @PROTOBUF_LIBS@ \ - @TAR_LIBS@ \ + @LIBARCHIVE_LIBS@ \ -pthread @@ -227,7 +227,7 @@ liburd_aux_la_LDFLAGS = \ @MERCURY_LIBS@ \ @PROTOBUF_LIBS@ \ @YAMLCPP_LIBS@ \ - @TAR_LIBS@ \ + @LIBARCHIVE_LIBS@ \ liburd_resources.la \ -pthread @@ -304,7 +304,6 @@ urd_LDFLAGS = \ @BOOST_THREAD_LIB@ \ @MERCURY_LIBS@ \ @PROTOBUF_LIBS@ \ - @TAR_LIBS@ \ liburd_aux.la # we also need to include it as an additional dependency, since automake diff --git a/src/urd.cpp b/src/urd.cpp index f93853d..73bf73d 100644 --- a/src/urd.cpp +++ b/src/urd.cpp @@ -42,6 +42,10 @@ #include #include +#ifdef __LOGGER_ENABLE_DEBUG__ +#include +#endif + #include #include #include @@ -1528,6 +1532,13 @@ int urd::run() { // validate settings check_configuration(); +#ifdef __LOGGER_ENABLE_DEBUG__ + if(::prctl(PR_SET_DUMPABLE, 1) != 0) { + LOGGER_WARN("Failed to set PR_SET_DUMPABLE flag for process. " + "Daemon will not produce core dumps."); + } +#endif + // daemonize if needed if(m_settings->daemonize() && daemonize() != 0) { /* parent clean ups and exits, child continues */ diff --git a/src/utils.cpp b/src/utils.cpp index a42f9f8..2afef9b 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -172,6 +172,26 @@ remove_trailing_separator(const boost::filesystem::path& pathname) { return path{str}; } +// remove a leading separator +// (adapted from boost::filesystem::path::remove_trailing_separator()) +boost::filesystem::path +remove_leading_separator(const boost::filesystem::path& pathname) { + + using boost::filesystem::path; + + std::string str{pathname.generic_string()}; + + auto is_directory_separator = [](path::value_type c) { + return c == '/'; + }; + + if(!str.empty() && is_directory_separator(str[0])) { + str.erase(0, 1); + } + + return path{str}; +} + } // namespace utils } // namespace norns diff --git a/src/utils.hpp b/src/utils.hpp index b1327b5..e02b2b8 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -62,6 +62,9 @@ boost::filesystem::path lexical_normalize(const boost::filesystem::path& pathnam boost::filesystem::path remove_trailing_separator(const boost::filesystem::path& pathname); +boost::filesystem::path +remove_leading_separator(const boost::filesystem::path& pathname); + } // namespace utils } // namespace norns diff --git a/src/utils/tar-archive.cpp b/src/utils/tar-archive.cpp index 73282c8..d6e71db 100644 --- a/src/utils/tar-archive.cpp +++ b/src/utils/tar-archive.cpp @@ -25,10 +25,24 @@ * . * *************************************************************************/ +#include +#include +#include +#include +#include + #include "utils.hpp" +#include "file-handle.hpp" #include "logger.hpp" #include "tar-archive.hpp" +// typedefs for convenience +using archive_ptr = std::unique_ptr; +using entry_ptr = std::unique_ptr; + +// helper functions namespace { constexpr std::size_t @@ -43,6 +57,142 @@ xalign(std::size_t n, std::size_t block_size) { n; } +std::error_code +append_file_data(struct archive* ar, + const bfs::path& name, + std::size_t size) { + + using norns::utils::file_handle; + std::error_code ec; + + file_handle fh(::open(name.c_str(), O_RDONLY)); + + if(!fh) { + ec.assign(errno, std::generic_category()); + return ec; + } + + std::array buffer; + off_t offset = 0; + + while(offset < static_cast(size)) { + ssize_t n = ::pread(fh.native(), buffer.data(), buffer.size(), offset); + + switch(n) { + case 0: + return ec; + + case -1: + if(errno == EINTR) { + continue; + } + + ec.assign(errno, std::generic_category()); + return ec; + + default: + { + ssize_t m = archive_write_data(ar, buffer.data(), n); + + if(m == -1) { + ec.assign(::archive_errno(ar), std::generic_category()); + return ec; + } + + // FIXME: we are assuming that we can successfully append + // the whole buffer to the archive in one call to + // archive_write_data() + assert(m == n); + offset += n; + } + } + } + + return ec; +} + +std::error_code +append_file(struct archive* arc, + const bfs::path& source_path, + const bfs::path& archive_path, + const entry_ptr& entry) { + + std::error_code ec; + + // set attributes and write the header for the file + struct stat stbuf; + if(::stat(source_path.c_str(), &stbuf) != 0) { + ec.assign(errno, std::generic_category()); + return ec; + } + + ::archive_entry_set_filetype(entry.get(), AE_IFREG); + ::archive_entry_copy_sourcepath(entry.get(), source_path.c_str()); + ::archive_entry_copy_pathname(entry.get(), archive_path.c_str()); + ::archive_entry_copy_stat(entry.get(), &stbuf); + + if(::archive_write_header(arc, entry.get()) != ARCHIVE_OK) { + ec.assign(::archive_errno(arc), std::generic_category()); + return ec; + } + + // append the actual file data to the archive + return ::append_file_data(arc, source_path, stbuf.st_size); +} + +std::error_code +append_directory_header(struct archive* arc, + const bfs::path& source_path, + const bfs::path& archive_path, + const entry_ptr& entry) { + std::error_code ec; + + // set attributes and write the header for the file + struct stat stbuf; + if(::stat(source_path.c_str(), &stbuf) != 0) { + ec.assign(errno, std::generic_category()); + return ec; + } + + ::archive_entry_set_filetype(entry.get(), AE_IFDIR); + ::archive_entry_copy_sourcepath(entry.get(), source_path.c_str()); + ::archive_entry_copy_pathname(entry.get(), archive_path.c_str()); + ::archive_entry_copy_stat(entry.get(), &stbuf); + + if(::archive_write_header(arc, entry.get()) != ARCHIVE_OK) { + ec.assign(::archive_errno(arc), std::generic_category()); + return ec; + } + + return ec; +} + +// replace 'real_parent' with 'archive_parent' in 'source' also +// adding a leading '/' if 'archive_parent' is empty +bfs::path +transform(const bfs::path& source, + const bfs::path& real_parent, + const bfs::path& archive_parent) { + + using norns::utils::lexical_normalize; + using norns::utils::remove_leading_separator; + + assert(source.is_absolute()); + assert(real_parent.is_absolute()); + + const bfs::path norm_source = lexical_normalize(source); + const bfs::path norm_real_parent = lexical_normalize(real_parent); + const bfs::path norm_archive_parent = lexical_normalize(archive_parent); + + assert(norm_source.is_absolute()); + assert(norm_real_parent.is_absolute()); + + return (norm_archive_parent.empty() ? + bfs::path("/") : + norm_archive_parent) / + bfs::relative(norm_source, norm_real_parent); +} + } // anonymous namespace namespace norns { @@ -55,84 +205,338 @@ tar::tar(const bfs::path& filename, m_openmode(op) { if(filename.empty()) { + ec.assign(EINVAL, std::generic_category()); return; } - constexpr const std::array flags = { - {O_WRONLY | O_CREAT| O_EXCL, O_RDONLY} - }; + ec.assign(0, std::generic_category()); + + if(op == openmode::create) { + archive_ptr arc(::archive_write_new(), ::archive_write_free); + + if(!arc) { + ec.assign(errno, std::generic_category()); + LOGGER_ERROR("Failed to open archive for writing: {}", + ec.message()); + return; + } + + if(::archive_write_set_format_ustar(arc.get()) == ARCHIVE_FATAL) { + ec.assign(::archive_errno(arc.get()), std::generic_category()); + LOGGER_ERROR("Failed to set output format to USTAR: {}", + ::archive_error_string(arc.get())); + return; + } + + if(::archive_write_open_filename(arc.get(), filename.c_str()) + == ARCHIVE_FATAL) { + ec.assign(::archive_errno(arc.get()), std::generic_category()); + LOGGER_ERROR("Failed to open archive for writing: {}", + ::archive_error_string(arc.get())); + return; + } + + m_archive = arc.release(); + } + else if(op == openmode::open) { + + archive_ptr arc(::archive_read_new(), ::archive_read_free); + + if(!arc) { + ec.assign(errno, std::generic_category()); + LOGGER_ERROR("Failed to open archive for reading: {}", + ec.message()); + return; + } + + if(::archive_read_support_format_tar(arc.get()) == ARCHIVE_FATAL) { + ec.assign(::archive_errno(arc.get()), std::generic_category()); + LOGGER_ERROR("Failed to determine archive format: {}", + ::archive_error_string(arc.get())); + return; + } - constexpr const std::array modes = { - {S_IRUSR | S_IWUSR, 0} - }; + if(::archive_read_open_filename(arc.get(), filename.c_str(), 16384) + == ARCHIVE_FATAL) { + ec.assign(::archive_errno(arc.get()), std::generic_category()); + LOGGER_ERROR("Failed to open archive for reading: {}", + ::archive_error_string(arc.get())); + return; + } - if(tar_open(&m_tar, m_path.c_str(), NULL, - flags[static_cast(op)], - modes[static_cast(op)], TAR_GNU) != 0) { - ec = std::make_error_code(static_cast(errno)); - LOGGER_ERROR("Failed to open archive for writing: {}", - logger::errno_message(ec.value())); + m_archive = arc.release(); + } + else { + ec.assign(EINVAL, std::generic_category()); return; } } +tar::~tar() { + this->release(); +} + void -tar::add_file(const bfs::path& real_name, - const bfs::path& archive_name, +tar::add_file(const bfs::path& source_file, + const bfs::path& archive_file, std::error_code& ec) { - if(m_tar == nullptr) { - ec = std::make_error_code(static_cast(EINVAL)); + if(m_archive == nullptr || m_openmode != tar::create || + source_file.empty()) { + ec.assign(EINVAL, std::generic_category()); return; } - if(tar_append_file(m_tar, const_cast(real_name.c_str()), - const_cast(archive_name.c_str())) != 0) { - ec = std::make_error_code(static_cast(errno)); + ec.assign(0, std::generic_category()); + + boost::system::error_code bec; + const bfs::path source_path = bfs::canonical(source_file, bec); + + if(bec) { + ec.assign(bec.value(), std::generic_category()); return; } - ec = std::make_error_code(static_cast(0)); + if(!bfs::is_regular(source_path)) { + ec.assign(EINVAL, std::generic_category()); + return; + } + + entry_ptr entry(::archive_entry_new2(m_archive), ::archive_entry_free); + + if(!entry) { + ec.assign(::archive_errno(m_archive), std::generic_category()); + return; + } + + ec = ::append_file(m_archive, source_path, archive_file, entry); } void -tar::add_directory(const bfs::path& real_dir, +tar::add_directory(const bfs::path& source_dir, const bfs::path& archive_dir, std::error_code& ec) { - if(m_tar == nullptr) { - ec = std::make_error_code(static_cast(EINVAL)); +// fmt::print(stderr, "::add_directory({}, alias={})\n", +// source_dir, archive_dir); + + if(m_archive == nullptr || m_openmode != tar::create || + source_dir.empty()) { + ec.assign(EINVAL, std::generic_category()); + return; + } + + ec.assign(0, std::generic_category()); + + boost::system::error_code bec; + const bfs::path source_path = bfs::canonical(source_dir, bec); + + if(bec) { + ec.assign(bec.value(), std::generic_category()); + return; + } + + if(!bfs::is_directory(source_path)) { + ec.assign(EINVAL, std::generic_category()); return; } - const bfs::path rd = - norns::utils::remove_trailing_separator(real_dir); - const bfs::path ad = - norns::utils::remove_trailing_separator(archive_dir); + entry_ptr entry(::archive_entry_new2(m_archive), ::archive_entry_free); + + if(!entry) { + ec.assign(::archive_errno(m_archive), std::generic_category()); + return; + } + +#if 0 + // if the transformed_path for the source directory is empty, it means + // that the user specified a transformation where SOURCE_PATH had to be + // replaced either with "" or with "/". In any of those cases, + // we avoid creating a header for SOURCE_PATH since the directory will not + // be included into the archive. + const bfs::path transformed_path = + ::transform(source_path, source_path, archive_dir); + + if(!transformed_path.empty()) { +// fmt::print(stderr, " ::append_directory_header({}, tp: {})\n", +// source_path, transformed_path); + ec = ::append_directory_header(m_archive, source_path, + transformed_path, entry); + if(ec) { + return; + } + } +#else + const bfs::path transformed_path = + ::transform(source_path, source_path, archive_dir); + +// fmt::print(stderr, " ::append_directory_header({}, tp: {})\n", +// source_path, transformed_path); + ec = ::append_directory_header(m_archive, source_path, + transformed_path, entry); + if(ec) { + return; + } +#endif + + // directory_iterator + bfs::recursive_directory_iterator it(source_path); + bfs::recursive_directory_iterator end; + + for(bfs::recursive_directory_iterator it(source_path, bec); + it != bfs::recursive_directory_iterator(); + ++it) { + + if(bec) { + ec.assign(bec.value(), std::generic_category()); + return; + } + +// fmt::print(stderr, " processing: {}\n", *it); + ::archive_entry_clear(entry.get()); + const bfs::path transformed_path = + ::transform(*it, source_path, archive_dir); + + if(bfs::is_regular(*it)) { +// fmt::print(stderr, " ::append_file({}, tp: {})\n", +// *it, transformed_path); + ec = ::append_file(m_archive, *it, transformed_path, entry); + + if(ec) { + return; + } + } + else if(bfs::is_directory(*it)) { +// fmt::print(stderr, " ::append_directory_header({}, tp: {})\n", +// *it, transformed_path); + ec = ::append_directory_header(m_archive, *it, transformed_path, + entry); + + if(ec) { + return; + } + } + else { + /* FIXME ignored */ + LOGGER_WARN("Found unhandled file type when adding " + "directory: {}", *it); + continue; + } + } +} + +void +tar::release() { - if(tar_append_tree(m_tar, const_cast(rd.c_str()), - const_cast(ad.c_str())) != 0) { - ec = std::make_error_code(static_cast(errno)); + if(m_archive == nullptr) { return; } - ec = std::make_error_code(static_cast(0)); + if(m_openmode == tar::create) { + if(::archive_write_close(m_archive) == ARCHIVE_FATAL) { + LOGGER_ERROR("Failed to close TAR archive: {}", + ::archive_error_string(m_archive)); + // intentionally fall through + } + + if(::archive_write_free(m_archive) == ARCHIVE_FATAL) { + LOGGER_ERROR("Failed to release TAR archive resources: {}", + ::archive_error_string(m_archive)); + } + } + else { + assert(m_openmode == tar::open); + + if(::archive_read_close(m_archive) == ARCHIVE_FATAL) { + LOGGER_ERROR("Failed to close TAR archive: {}", + ::archive_error_string(m_archive)); + // intentionally fall through + } + + if(::archive_read_free(m_archive) == ARCHIVE_FATAL) { + LOGGER_ERROR("Failed to release TAR archive resources: {}", + ::archive_error_string(m_archive)); + } + } + + m_archive = nullptr; } void tar::extract(const bfs::path& parent_dir, std::error_code& ec) { - if(m_tar == nullptr) { - ec = std::make_error_code(static_cast(EINVAL)); + if(m_archive == nullptr || m_openmode != tar::open || + parent_dir.empty() || !bfs::is_directory(parent_dir)) { + ec.assign(EINVAL, std::generic_category()); return; } - if(tar_extract_all(m_tar, const_cast(parent_dir.c_str())) != 0) { - ec = std::make_error_code(static_cast(errno)); + boost::system::error_code bec; + const bfs::path dest_path = bfs::canonical(parent_dir, bec); + + if(bec) { + ec.assign(bec.value(), std::generic_category()); + return; } -} + ec.assign(0, std::generic_category()); + + // Select which attributes we want to restore + int flags = + // full permissions (including SGID, SUID, and sticky bits) should be + // restored exactly as specified + ARCHIVE_EXTRACT_PERM | + // refuse to extract any object whose final location would be altered + // by a symlink on disk + ARCHIVE_EXTRACT_SECURE_SYMLINKS | + // refuse to extract a path that contains a .. element + // anywhere within it + ARCHIVE_EXTRACT_SECURE_NODOTDOT; + + struct archive_entry* entry; + + for(;;) { + + int rv = ::archive_read_next_header(m_archive, &entry); + + if(rv == ARCHIVE_EOF) { + break; + } + + if(rv != ARCHIVE_OK) { + ec.assign(::archive_errno(m_archive), std::generic_category()); + LOGGER_ERROR("Failed to read next header entry: {}", + ::archive_error_string(m_archive)); + return; + } + + const char* p = ::archive_entry_pathname(entry); + + if(!p) { + ec.assign(ENOMEM, std::generic_category()); + LOGGER_ERROR("Failed to retrieve entry pathname: {}", ec.message()); + return; + } + + const bfs::path archive_pathname(p); + const bfs::path new_pathname(parent_dir / + remove_leading_separator(archive_pathname)); + +// fmt::print("found entry: {}\n", archive_pathname); +// fmt::print(" new path: {}\n", new_pathname); + + ::archive_entry_copy_pathname(entry, new_pathname.c_str()); + + if(::archive_read_extract(m_archive, entry, flags) != ARCHIVE_OK) { + ec.assign(::archive_errno(m_archive), std::generic_category()); + LOGGER_ERROR("Failed to extract archive entry {} to {}: {}", + archive_pathname, new_pathname, + ::archive_error_string(m_archive)); + return; + } + } +} bfs::path tar::path() const { @@ -140,30 +544,31 @@ tar::path() const { } std::size_t -tar::estimate_size_once_packed(const bfs::path& path, +tar::estimate_size_once_packed(const bfs::path& source_path, + /*const bfs::path& packed_path,*/ std::error_code& ec) { std::size_t sz = 0; boost::system::error_code error; - if(bfs::is_directory(path)) { - for(bfs::recursive_directory_iterator it(path, error); + if(bfs::is_directory(source_path)) { + for(bfs::recursive_directory_iterator it(source_path, error); it != bfs::recursive_directory_iterator(); ++it) { if(error) { - LOGGER_ERROR("Failed to traverse path {}", path); + LOGGER_ERROR("Failed to traverse path {}", source_path); ec = std::make_error_code( static_cast(error.value())); return 0; } if(bfs::is_directory(*it)) { - sz += T_BLOCKSIZE; + sz += TAR_BLOCK_SIZE; } else if(bfs::is_regular(*it) || bfs::is_symlink(*it)) { - sz += T_BLOCKSIZE + - ::xalign(bfs::file_size(*it, error), T_BLOCKSIZE); + sz += TAR_BLOCK_SIZE + + ::xalign(bfs::file_size(*it, error), TAR_BLOCK_SIZE); if(error) { LOGGER_ERROR("Failed to determine size for {}", *it); @@ -178,37 +583,22 @@ tar::estimate_size_once_packed(const bfs::path& path, } } - // we need to take into account the header for the basedir 'path' - // since recursive_directory_iterator skips it - sz += T_BLOCKSIZE; +// if(!packed_path.empty() && packed_path == "/") { + // we need to take into account the header for the basedir + // 'source_path' since recursive_directory_iterator skips it + sz += TAR_BLOCK_SIZE; +// } } else { - sz += T_BLOCKSIZE + ::xalign(bfs::file_size(path), T_BLOCKSIZE); + sz += TAR_BLOCK_SIZE + + ::xalign(bfs::file_size(source_path), TAR_BLOCK_SIZE); } // EOF - sz += 2*T_BLOCKSIZE; + sz += 2*TAR_BLOCK_SIZE; return sz; } -tar::~tar() { - - if(m_tar != nullptr) { - - if(m_openmode == tar::create) { - if(tar_append_eof(m_tar)) { - LOGGER_ERROR("Failed to append EOF to TAR archive: {}", - logger::errno_message(errno)); - } - } - - if(tar_close(m_tar) != 0) { - LOGGER_ERROR("Failed to close TAR archive: {}", - logger::errno_message(errno)); - } - } -} - } // namespace utils } // namespace norns diff --git a/src/utils/tar-archive.hpp b/src/utils/tar-archive.hpp index 4054ebe..c901d59 100644 --- a/src/utils/tar-archive.hpp +++ b/src/utils/tar-archive.hpp @@ -30,7 +30,9 @@ #include #include -#include + +// forward declare 'struct archive' +struct archive; namespace bfs = boost::filesystem; @@ -44,11 +46,14 @@ struct tar { open = 1, }; - constexpr static openmode create = openmode::create; - constexpr static openmode open = openmode::open; + constexpr static const auto TAR_BLOCK_SIZE = 512; + constexpr static const openmode create = openmode::create; + constexpr static const openmode open = openmode::open; tar(const bfs::path& filename, openmode op, std::error_code& ec); + ~tar(); + void add_file(const bfs::path& real_name, const bfs::path& archive_name, @@ -59,6 +64,9 @@ struct tar { const bfs::path& archive_dir, std::error_code& ec); + void + release(); + void extract(const bfs::path& parent_dir, std::error_code& ec); @@ -67,12 +75,11 @@ struct tar { path() const; static std::size_t - estimate_size_once_packed(const bfs::path& path, + estimate_size_once_packed(const bfs::path& source_path, + /*const bfs::path& packed_path,*/ std::error_code& ec); - ~tar(); - - TAR* m_tar = nullptr; + struct archive* m_archive = nullptr; bfs::path m_path; openmode m_openmode; }; diff --git a/tests/Makefile.am b/tests/Makefile.am index 9122ac0..32c4218 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -35,6 +35,8 @@ END = api_CXXFLAGS = \ -Wall -Wextra \ + -fsanitize=address \ + -fno-omit-frame-pointer \ $(END) api_CPPFLAGS = \ @@ -137,6 +139,8 @@ EXTRA_api_interactive_DEPENDENCIES = \ core_CXXFLAGS = \ -Wall -Wextra \ + -fsanitize=address \ + -fno-omit-frame-pointer \ $(END) core_CPPFLAGS = \ @@ -154,10 +158,14 @@ core_SOURCES = \ utils-tar.cpp \ test-env.cpp \ test-env.hpp \ + compare-files.cpp \ + compare-files.hpp \ $(END) core_LDFLAGS = \ -no-install \ + -fsanitize=address \ + -fno-omit-frame-pointer \ @BOOST_ASIO_LIB@ \ @BOOST_LDFLAGS@ \ @BOOST_FILESYSTEM_LIB@ \ diff --git a/tests/compare-files.cpp b/tests/compare-files.cpp index 2d6d0ee..12f6856 100644 --- a/tests/compare-files.cpp +++ b/tests/compare-files.cpp @@ -126,7 +126,10 @@ bool compare_directories(const bfs::path& dirname1, const bfs::path& dirname2) { } if(bfs::is_directory(*it) && bfs::is_directory(pdst)) { - continue; + if(!compare_directories(*it, pdst)) { + return false; + } + continue; } else if(bfs::is_directory(pdst)){ return false; diff --git a/tests/test-env.cpp b/tests/test-env.cpp index 452fb89..ec19662 100644 --- a/tests/test-env.cpp +++ b/tests/test-env.cpp @@ -299,7 +299,10 @@ bfs::path test_env::get_from_namespace(const std::string& nsid, const bfs::path& } -bfs::path test_env::create_file(const bfs::path& name, const bfs::path& parent, std::size_t size) { +bfs::path +test_env::create_file(const bfs::path& name, + const bfs::path& parent, + std::size_t size) { auto parent_dirs = bfs::relative(name.parent_path(), "/"); auto abs_mnt = bfs::absolute(parent); @@ -322,8 +325,9 @@ bfs::path test_env::create_file(const bfs::path& name, const bfs::path& parent, REQUIRE(file); if(size != 0) { - std::vector data(size/sizeof(uint64_t), 42); - file.write(reinterpret_cast(&data[0]), size); + std::size_t n = std::ceil(static_cast(size)/sizeof(uint64_t)); + std::vector bindata(n, 42); + file.write(reinterpret_cast(bindata.data()), size); } return fname; diff --git a/tests/utils-tar.cpp b/tests/utils-tar.cpp index 2c0b95f..525dcc2 100644 --- a/tests/utils-tar.cpp +++ b/tests/utils-tar.cpp @@ -28,18 +28,23 @@ #include #include #include "utils.hpp" +#include "compare-files.hpp" #include "test-env.hpp" #include "catch.hpp" namespace { -static constexpr std::size_t tar_header_size = T_BLOCKSIZE; -static constexpr std::size_t tar_eof_size = 2*T_BLOCKSIZE; +constexpr static const std::size_t tar_header_size = + norns::utils::tar::TAR_BLOCK_SIZE; +constexpr static const std::size_t tar_record_size = + norns::utils::tar::TAR_BLOCK_SIZE; +constexpr static const std::size_t tar_eof_size = + 2*norns::utils::tar::TAR_BLOCK_SIZE; constexpr std::size_t tar_aligned_size(std::size_t sz) { - return sz % T_BLOCKSIZE != 0 ? - ((sz & ~(T_BLOCKSIZE - 1)) + T_BLOCKSIZE) : + return sz % tar_record_size != 0 ? + ((sz & ~(tar_record_size - 1)) + tar_record_size) : sz; } @@ -84,6 +89,741 @@ create_hierarchy(test_env& env, return self; } +std::string +exec(const std::string& cmd) { + std::array buffer; + std::string result; + int status; + + std::cout << "exec(\"" << cmd << "\")"; + + auto deleter = [&status](FILE* stream) { + status = ::pclose(stream); + }; + + std::unique_ptr pipe( + ::popen(cmd.c_str(), "r"), deleter); + + if(!pipe) { + throw std::runtime_error("popen() failed!"); + } + + while(::fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { + result += buffer.data(); + } + + return result; +} + +std::error_code +extract(const bfs::path archive_path, + const bfs::path parent_dir) { + + std::error_code ec; + + if(!bfs::exists(parent_dir)) { + boost::system::error_code bec; + bfs::create_directories(parent_dir, bec); + + if(bec) { + ec.assign(bec.value(), std::generic_category()); + return ec; + } + } + + std::string cmd("tar xf " + archive_path.string() + " -C " + + parent_dir.string()); + auto res = ::exec(cmd); + + std::cout << res << "\n"; + + return ec; +} + +} + +SCENARIO("tar creation", "[utils::tar::tar(filename, tar::create)]") { + + GIVEN("an invalid output path") { + test_env env; + using norns::utils::tar; + + bfs::path p = env.basedir(); + + WHEN("creating a tar archive") { + std::error_code ec; + tar t(p, tar::create, ec); + + THEN("constructor fails") { + REQUIRE(ec); + } + } + + env.notify_success(); + } + + GIVEN("a valid output path") { + test_env env; + using norns::utils::tar; + + bfs::path p = env.basedir() / "archive.tar"; + + WHEN("creating a tar archive") { + std::error_code ec; + tar t(p, tar::create, ec); + + THEN("constructor succeeds") { + REQUIRE(!ec); + REQUIRE(bfs::exists(p)); + } + } + + env.notify_success(); + } +} + +SCENARIO("appending files to a TAR archive", + "[utils::tar::add_file(path, alias, error_code)]") { + + GIVEN("an invalid tar instance") { + test_env env; + using norns::utils::tar; + + std::error_code ec; + tar t("", tar::create, ec); + REQUIRE(ec); + + WHEN("trying to add a file") { + + bfs::path file = env.create_file("/regular_file", env.basedir(), 0); + + THEN("add_file() returns EINVAL") { + t.add_file(file, bfs::relative(file, env.basedir()), ec); + REQUIRE(ec); + REQUIRE(ec.value() == EINVAL); + } + } + + env.notify_success(); + } + + GIVEN("a valid tar instance") { + test_env env; + using norns::utils::tar; + + std::array filenames = { + "/regular_file0", "/regular_file1", "/regular_file2", + "/regular_file3", "/regular_file4", "/regular_file5", + }; + + std::array sizes = { + 0, 10000, 20000, 2134513, 42, 50124887 + }; + + std::vector file_paths; + + for(std::size_t i = 0; i < filenames.size(); ++i) { + file_paths.emplace_back( + env.create_file(filenames[i], env.basedir(), sizes[i])); + } + + bfs::path p = env.basedir() / "archive.tar"; + + std::error_code ec; + tar t(p, tar::create, ec); + REQUIRE(!ec); + + WHEN("adding an empty file") { + + t.add_file(file_paths[0], + bfs::relative(file_paths[0], env.basedir()), ec); + + THEN("add_file() succeeds") { + REQUIRE(!ec); + + AND_THEN("the archive contains the added file") { + + t.release(); // make sure data has been written to archive + + const bfs::path parent_dir(env.basedir() / "tmp"); + ec = ::extract(t.path(), parent_dir); + + REQUIRE(!ec); + REQUIRE(bfs::exists(parent_dir / filenames[0])); + REQUIRE(compare_files( + file_paths[0], parent_dir / filenames[0])); + } + } + } + + WHEN("adding a non-empty file") { + t.add_file(file_paths[1], + bfs::relative(file_paths[1], env.basedir()), ec); + + THEN("add_file() succeeds") { + REQUIRE(!ec); + + AND_THEN("the archive contains the added file") { + + t.release(); // make sure data has been written to archive + + const bfs::path parent_dir(env.basedir() / "tmp"); + ec = ::extract(t.path(), parent_dir); + + REQUIRE(!ec); + REQUIRE(bfs::exists(parent_dir / filenames[1])); + REQUIRE(compare_files( + file_paths[1], parent_dir / filenames[1])); + } + } + } + + WHEN("adding several files") { + + std::vector ecs; + + for(std::size_t i = 0; i < file_paths.size(); ++i) { + t.add_file(file_paths[i], + bfs::relative(file_paths[i], env.basedir()), ec); + + ecs.emplace_back(ec); + + } + + THEN("add_file() succeeds") { + + for(const auto err : ecs) { + REQUIRE(!err); + } + + AND_THEN("the archive contains all added files") { + + t.release(); // make sure data has been written to archive + + const bfs::path parent_dir(env.basedir() / "tmp"); + ec = ::extract(t.path(), parent_dir); + REQUIRE(!ec); + + for(std::size_t i = 0; i < file_paths.size(); ++i) { + REQUIRE(bfs::exists(parent_dir / filenames[i])); + REQUIRE(compare_files( + file_paths[i], parent_dir / filenames[i])); + } + } + } + } + + env.notify_success(); + } + +} + +SCENARIO("appending directories to a TAR archive", + "[utils::tar::add_directory(path, alias, error_code)]") { + + GIVEN("an invalid tar instance") { + test_env env; + using norns::utils::tar; + + std::error_code ec; + tar t("", tar::create, ec); + REQUIRE(ec); + + WHEN("trying to add a directory") { + + bfs::path subdir = env.create_directory("/subdir", env.basedir()); + + THEN("add_directory() returns EINVAL") { + t.add_directory(subdir, + bfs::relative(subdir, env.basedir()), ec); + REQUIRE(ec); + REQUIRE(ec.value() == EINVAL); + } + } + + env.notify_success(); + } + + GIVEN("a valid tar instance") { + test_env env; + using norns::utils::tar; + + + std::array filenames = { + "/regular_file0", "/regular_file1", "/regular_file2", + "/regular_file3", "/regular_file4", "/regular_file5", + }; + + std::array sizes = { + 0, 10000, 20000, 2134513, 42, 5012488//7 + }; + + std::vector file_paths; + + bfs::path subdir = + env.create_directory("/subdir", env.basedir()); + + for(std::size_t i = 0; i < filenames.size(); ++i) { + file_paths.emplace_back( + env.create_file(filenames[i], subdir, sizes[i])); + } + + bfs::path p = env.basedir() / "archive.tar"; + + std::error_code ec; + tar t(p, tar::create, ec); + REQUIRE(!ec); + + WHEN("adding an empty directory") { + + bfs::path empty_subdir = + env.create_directory("/empty_subdir", env.basedir()); + + t.add_directory(empty_subdir, + bfs::relative(empty_subdir, env.basedir()), ec); + + THEN("add_directory() succeeds") { + REQUIRE(!ec); + + AND_THEN("the archive contains the added directory") { + + t.release(); // make sure data has been written to archive + + const bfs::path parent_dir(env.basedir() / "tmp"); + ec = ::extract(t.path(), parent_dir); + + REQUIRE(!ec); + REQUIRE(bfs::exists(parent_dir / "/empty_subdir")); + REQUIRE(compare_directories( + empty_subdir, parent_dir / "/empty_subdir")); + } + } + } + + WHEN("adding a non-empty directory containing only files and " + "defining a 1-level alias (e.g. subdir0/ -> subdir1/ ) ") { + + t.add_directory(subdir, + bfs::relative(subdir, env.basedir()), ec); + + THEN("add_directory() succeeds") { + REQUIRE(!ec); + + AND_THEN("the archive contains the added directory") { + + t.release(); // make sure data has been written to archive + + const bfs::path parent_dir(env.basedir() / "tmp"); + ec = ::extract(t.path(), parent_dir); + + REQUIRE(!ec); + REQUIRE(bfs::exists(parent_dir / "/subdir")); + + AND_THEN("both directories are identical") { + REQUIRE(compare_directories( + subdir, parent_dir / "/subdir")); + } + } + } + } + + WHEN("adding a non-empty directory containing only files and defining " + "a multi-level alias (e.g. subdir0/ -> subdir1/a/b/c/ )") { + + t.add_directory(subdir, + bfs::relative(subdir, env.basedir()) / "a/b/c/", ec); + + THEN("add_directory() succeeds") { + REQUIRE(!ec); + + AND_THEN("the archive contains the added directory") { + + t.release(); // make sure data has been written to archive + + const bfs::path parent_dir(env.basedir() / "tmp"); + ec = ::extract(t.path(), parent_dir); + + REQUIRE(!ec); + REQUIRE(bfs::exists(parent_dir / "/subdir")); + REQUIRE(bfs::exists(parent_dir / "/subdir/a/")); + REQUIRE(bfs::exists(parent_dir / "/subdir/a/b/")); + REQUIRE(bfs::exists(parent_dir / "/subdir/a/b/c/")); + + AND_THEN("contents are identical") { + REQUIRE(compare_directories( + subdir, parent_dir / "/subdir/a/b/c")); + } + } + } + } + + WHEN("adding a non-empty directory containing only files and " + "defining an empty alias") { + + t.add_directory(subdir, "", ec); + + THEN("add_directory() succeeds") { + REQUIRE(!ec); + + AND_THEN("the archive does not contain the added directory") { + + t.release(); // make sure data has been written to archive + + const bfs::path parent_dir(env.basedir() / "tmp"); + ec = ::extract(t.path(), parent_dir); + + REQUIRE(!ec); + REQUIRE(!bfs::exists(parent_dir / "/subdir")); + + AND_THEN("contents are identical") { + REQUIRE(compare_directories(subdir, parent_dir)); + } + } + } + } + + WHEN("adding a non-empty directory containing only files and " + "defining '/' as an alias") { + + t.add_directory(subdir, "/", ec); + + THEN("add_directory() succeeds") { + REQUIRE(!ec); + + AND_THEN("the archive does not contain the added directory") { + + t.release(); // make sure data has been written to archive + + const bfs::path parent_dir(env.basedir() / "tmp"); + ec = ::extract(t.path(), parent_dir); + + REQUIRE(!ec); + REQUIRE(!bfs::exists(parent_dir / "/subdir")); + + AND_THEN("contents are identical") { + REQUIRE(compare_directories(subdir, parent_dir)); + } + } + } + } + + WHEN("adding a non-empty directory containing files and subdirs, and " + "defining a 1-level alias (e.g. subdir0/ -> subdir1/ ) ") { + + size_generator gen(rng{42}, distribution{0, 42}); + + bfs::path complex_subdir = + create_hierarchy(env, gen, "/complex_subdir", env.basedir(), + 2, 5, 5); + + t.add_directory(complex_subdir, + bfs::relative(complex_subdir, env.basedir()), ec); + + THEN("add_directory() succeeds") { + REQUIRE(!ec); + + AND_THEN("the archive contains the added directory") { + + t.release(); // make sure data has been written to archive + + const bfs::path parent_dir(env.basedir() / "tmp"); + ec = ::extract(t.path(), parent_dir); + + REQUIRE(!ec); + REQUIRE(bfs::exists(parent_dir / "/complex_subdir")); + + AND_THEN("both directories are identical") { + REQUIRE(compare_directories( + complex_subdir, + parent_dir / "/complex_subdir")); + } + } + } + } + + WHEN("adding a non-empty directory containing files and subdirs, and " + "defining a multi-level alias (e.g. subdir0/ -> " + "subdir1/a/b/c/ )") { + + size_generator gen(rng{42}, distribution{0, 42}); + + bfs::path complex_subdir = + create_hierarchy(env, gen, "/complex_subdir", env.basedir(), + 2, 5, 5); + + t.add_directory( + complex_subdir, + bfs::relative(complex_subdir, env.basedir()) / "a/b/c/", ec); + + THEN("add_directory() succeeds") { + REQUIRE(!ec); + + AND_THEN("the archive contains the added directory") { + + t.release(); // make sure data has been written to archive + + const bfs::path parent_dir(env.basedir() / "tmp"); + ec = ::extract(t.path(), parent_dir); + + REQUIRE(!ec); + REQUIRE(bfs::exists(parent_dir / "/complex_subdir")); + REQUIRE(bfs::exists(parent_dir / "/complex_subdir/a/")); + REQUIRE(bfs::exists(parent_dir / "/complex_subdir/a/b/")); + REQUIRE(bfs::exists(parent_dir / "/complex_subdir/a/b/c/")); + + AND_THEN("both directories are identical") { + REQUIRE(compare_directories( + complex_subdir, + parent_dir / "/complex_subdir/a/b/c")); + } + } + } + } + + env.notify_success(); + } +} + +SCENARIO("extracting from a tar archive", + "[utils::tar::extract(destination_path, error_code)]") { + + GIVEN("an invalid tar instance") { + test_env env; + using norns::utils::tar; + + std::error_code ec; + tar t("", tar::open, ec); + REQUIRE(ec); + + WHEN("trying to extract the archive") { + + std::error_code ec; + bfs::path tmpdir = env.create_directory("/tmp", env.basedir()); + t.extract(tmpdir, ec); + + THEN("extract() returns EINVAL") { + REQUIRE(ec); + REQUIRE(ec.value() == EINVAL); + } + } + + env.notify_success(); + } + + GIVEN("a valid tar instance") { + test_env env; + using norns::utils::tar; + + bfs::path tmp_dir = env.create_directory("/tmp", env.basedir()); + size_generator gen(rng(42), distribution(0, 42)); + bfs::path complex_subdir = + create_hierarchy(env, gen, "/complex_subdir", env.basedir(), + 2, 5, 5); + bfs::path p = env.basedir() / "archive.tar"; + + { + std::error_code ec; + tar t(p, tar::create, ec); + REQUIRE(!ec); + t.add_directory(complex_subdir, "", ec); + REQUIRE(!ec); + } + + WHEN("trying to extract the archive to \"\"") { + + std::error_code ec; + tar t(p, tar::open, ec); + REQUIRE(!ec); + + t.extract("", ec); + + THEN("extract() returns EINVAL") { + REQUIRE(ec); + REQUIRE(ec.value() == EINVAL); + } + } + + env.notify_success(); + } + + GIVEN("a valid tar instance") { + test_env env; + using norns::utils::tar; + + bfs::path tmp_dir = env.create_directory("/tmp", env.basedir()); + size_generator gen(rng(42), distribution(0, 42)); + bfs::path complex_subdir = + create_hierarchy(env, gen, "/complex_subdir", env.basedir(), + 2, 5, 5); + bfs::path p = env.basedir() / "archive.tar"; + + { + std::error_code ec; + tar t(p, tar::create, ec); + REQUIRE(!ec); + t.add_directory(complex_subdir, "", ec); + REQUIRE(!ec); + } + + WHEN("trying to extract the archive to a path leading to a file") { + + std::error_code ec; + tar t(p, tar::open, ec); + REQUIRE(!ec); + + t.extract(p, ec); + + THEN("extract() returns EINVAL") { + REQUIRE(ec); + REQUIRE(ec.value() == EINVAL); + } + } + + env.notify_success(); + } + + GIVEN("a valid tar instance") { + test_env env; + using norns::utils::tar; + + bfs::path tmp_dir = env.create_directory("/tmp", env.basedir()); + bfs::path file = env.create_file("/fake.tar", env.basedir(), 0); + + WHEN("trying to extract from something that looks like an archive ") { + + std::error_code ec; + tar t(file, tar::open, ec); + + THEN("constructor() fails") { + REQUIRE(ec); + } + } + + env.notify_success(); + } + + GIVEN("a valid tar instance and an archive with alias \"\"") { + test_env env; + using norns::utils::tar; + + bfs::path tmp_dir = env.create_directory("/tmp", env.basedir()); + size_generator gen(rng(42), distribution(0, 42)); + bfs::path complex_subdir = + create_hierarchy(env, gen, "/complex_subdir", env.basedir(), + 2, 5, 5); + bfs::path p = env.basedir() / "archive.tar"; + + std::error_code ec; + { + tar t(p, tar::create, ec); + REQUIRE(!ec); + + t.add_directory(complex_subdir, "", ec); + REQUIRE(!ec); + } + + WHEN("trying to extract the archive to a path leading to a directory") { + + tar t(p, tar::open, ec); + REQUIRE(!ec); + + THEN("extract() succeeds") { + t.extract(tmp_dir, ec); + REQUIRE(!ec); + + AND_THEN("the specified directory contains the archive " + "contents and they are identical to the original") { + REQUIRE(compare_directories(complex_subdir, tmp_dir)); + } + } + } + + env.notify_success(); + } + + GIVEN("a valid tar instance and an archive with a 1-level alias") { + test_env env; + using norns::utils::tar; + + bfs::path tmp_dir = env.create_directory("/tmp", env.basedir()); + size_generator gen(rng(42), distribution(0, 42)); + bfs::path complex_subdir = + create_hierarchy(env, gen, "/complex_subdir", env.basedir(), + 2, 5, 5); + bfs::path p = env.basedir() / "archive.tar"; + + std::error_code ec; + { + tar t(p, tar::create, ec); + REQUIRE(!ec); + + t.add_directory(complex_subdir, "/alias_subdir", ec); + REQUIRE(!ec); + } + + WHEN("trying to extract the archive to a path leading to a directory") { + + tar t(p, tar::open, ec); + REQUIRE(!ec); + + THEN("extract() succeeds") { + t.extract(tmp_dir, ec); + REQUIRE(!ec); + + AND_THEN("the specified directory contains the archive " + "contents and they are identical to the original") { + REQUIRE(bfs::exists(tmp_dir / "/alias_subdir")); + REQUIRE(compare_directories( + complex_subdir, tmp_dir / "/alias_subdir")); + } + } + } + + env.notify_success(); + } + + GIVEN("a valid tar instance and an archive with a multi-level alias") { + test_env env; + using norns::utils::tar; + + bfs::path tmp_dir = env.create_directory("/tmp", env.basedir()); + size_generator gen(rng(42), distribution(0, 42)); + bfs::path complex_subdir = + create_hierarchy(env, gen, "/complex_subdir", env.basedir(), + 2, 5, 5); + bfs::path p = env.basedir() / "archive.tar"; + + std::error_code ec; + { + tar t(p, tar::create, ec); + REQUIRE(!ec); + + t.add_directory(complex_subdir, "/alias_subdir/a/b/c", ec); + REQUIRE(!ec); + } + + WHEN("trying to extract the archive to a path leading to a directory") { + + tar t(p, tar::open, ec); + REQUIRE(!ec); + + THEN("extract() succeeds") { + t.extract(tmp_dir, ec); + REQUIRE(!ec); + + AND_THEN("the specified directory contains the archive " + "contents and they are identical to the original") { + REQUIRE(bfs::exists(tmp_dir / "/alias_subdir/a/b/c")); + REQUIRE(compare_directories( + complex_subdir, + tmp_dir / "/alias_subdir/a/b/c")); + } + } + } + + env.notify_success(); + } + } SCENARIO("tar size estimation", "[utils::tar::estimate_packed_size()]") { @@ -124,7 +864,7 @@ SCENARIO("tar size estimation", "[utils::tar::estimate_packed_size()]") { env.notify_success(); } - GIVEN("a path to a regular file of T_BLOCKSIZE bytes") { + GIVEN("a path to a regular file of tar_record_size bytes") { test_env env; using norns::utils::tar; @@ -162,7 +902,7 @@ SCENARIO("tar size estimation", "[utils::tar::estimate_packed_size()]") { env.notify_success(); } - GIVEN("a path to a regular file of 2*T_BLOCKSIZE bytes") { + GIVEN("a path to a regular file of 2*tar_record_size bytes") { test_env env; using norns::utils::tar; @@ -225,8 +965,7 @@ SCENARIO("tar size estimation", "[utils::tar::estimate_packed_size()]") { tar t(file_tar, tar::create, ec); REQUIRE(!ec); - t.add_directory(file, - bfs::relative(file, env.basedir()), ec); + t.add_file(file, bfs::relative(file, env.basedir()), ec); REQUIRE(!ec); } @@ -263,7 +1002,8 @@ SCENARIO("tar size estimation", "[utils::tar::estimate_packed_size()]") { tar t(subdir_tar, tar::create, ec); REQUIRE(!ec); - t.add_file(subdir, bfs::relative(subdir, env.basedir()), ec); + t.add_directory(subdir, + bfs::relative(subdir, env.basedir()), ec); REQUIRE(!ec); } @@ -311,6 +1051,41 @@ SCENARIO("tar size estimation", "[utils::tar::estimate_packed_size()]") { env.notify_success(); } + GIVEN("a path to a directory hierarchy containing empty files and an " + "empty alias") { + + test_env env; + using norns::utils::tar; + + size_generator gen(rng{42}, distribution{0, 0}); + + bfs::path subdir = + create_hierarchy(env, gen, "/subdir", env.basedir(), 5, 5, 5); + + THEN("size of generated TAR archive matches estimated one") { + + std::error_code ec; + std::size_t psize = tar::estimate_size_once_packed(subdir, ec); + + bfs::path subdir_tar = subdir; + subdir_tar.replace_extension(".tar"); + + { + tar t(subdir_tar, tar::create, ec); + REQUIRE(!ec); + + t.add_directory(subdir, "", ec); + REQUIRE(!ec); + } + + // this check needs to be done after tar::~tar() has been + // called, since it appends the EOF blocks + REQUIRE(psize == bfs::file_size(subdir_tar)); + } + + env.notify_success(); + } + GIVEN("a path to a directory hierarchy containing arbitrary files") { test_env env; -- GitLab From 637c63fa90b97d0351dd055332d1df4a07156b91 Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Thu, 14 Mar 2019 17:23:38 +0100 Subject: [PATCH 2/2] Remove sanitize flags --- tests/Makefile.am | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/Makefile.am b/tests/Makefile.am index 32c4218..0136d41 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -35,8 +35,6 @@ END = api_CXXFLAGS = \ -Wall -Wextra \ - -fsanitize=address \ - -fno-omit-frame-pointer \ $(END) api_CPPFLAGS = \ @@ -139,8 +137,6 @@ EXTRA_api_interactive_DEPENDENCIES = \ core_CXXFLAGS = \ -Wall -Wextra \ - -fsanitize=address \ - -fno-omit-frame-pointer \ $(END) core_CPPFLAGS = \ -- GitLab