diff options
author | Eelco Dolstra <edolstra@gmail.com> | 2019-12-19 14:47:18 +0100 |
---|---|---|
committer | Eelco Dolstra <edolstra@gmail.com> | 2019-12-19 14:47:18 +0100 |
commit | ee235e764c6a2af1ff3ae24ce8ad9ae0e1928f39 (patch) | |
tree | 36929ecdd82a22d3eba38c31d4be11208f35e94a /src/libutil/tarfile.cc | |
parent | 9f7b4d068cc106e5d902dc6f52bf46d4a057fd00 (diff) | |
parent | f765e441237cb6679c33cb44372a5b30168c42c8 (diff) |
Merge branch 'libarchive' of https://github.com/yorickvP/nix
Diffstat (limited to 'src/libutil/tarfile.cc')
-rw-r--r-- | src/libutil/tarfile.cc | 146 |
1 files changed, 120 insertions, 26 deletions
diff --git a/src/libutil/tarfile.cc b/src/libutil/tarfile.cc index 1be0ba24c..68e918891 100644 --- a/src/libutil/tarfile.cc +++ b/src/libutil/tarfile.cc @@ -1,38 +1,132 @@ -#include "rust-ffi.hh" -#include "compression.hh" +#include <archive.h> +#include <archive_entry.h> -extern "C" { - rust::Result<std::tuple<>> * - unpack_tarfile(rust::Source source, rust::StringSlice dest_dir, rust::Result<std::tuple<>> & out); -} +#include "serialise.hh" namespace nix { +struct TarArchive { + struct archive *archive; + Source *source; + std::vector<unsigned char> buffer; + + void check(int err, const char *reason = "Failed to extract archive (%s)") { + if (err == ARCHIVE_EOF) + throw EndOfFile("reached end of archive"); + else if (err != ARCHIVE_OK) + throw Error(reason, archive_error_string(this->archive)); + } + + TarArchive(Source& source) : buffer(4096) { + this->archive = archive_read_new(); + this->source = &source; + + archive_read_support_filter_all(archive); + archive_read_support_format_all(archive); + check(archive_read_open(archive, (void *)this, TarArchive::callback_open, TarArchive::callback_read, TarArchive::callback_close), "Failed to open archive (%s)"); + } + + TarArchive(const Path &path) { + this->archive = archive_read_new(); + + archive_read_support_filter_all(archive); + archive_read_support_format_all(archive); + check(archive_read_open_filename(archive, path.c_str(), 16384), "Failed to open archive (%s)"); + } + + // disable copy constructor + TarArchive(const TarArchive&) = delete; + + void close() { + check(archive_read_close(archive), "Failed to close archive (%s)"); + } + + ~TarArchive() { + if (this->archive) archive_read_free(this->archive); + } + +private: + static int callback_open(struct archive *, void *self) { + return ARCHIVE_OK; + } + + static ssize_t callback_read(struct archive *archive, void *_self, const void **buffer) { + TarArchive *self = (TarArchive *)_self; + *buffer = self->buffer.data(); + + try { + return self->source->read(self->buffer.data(), 4096); + } catch (EndOfFile &) { + return 0; + } catch (std::exception &err) { + archive_set_error(archive, EIO, "Source threw exception: %s", err.what()); + + return -1; + } + } + + static int callback_close(struct archive *, void *self) { + return ARCHIVE_OK; + } +}; + +struct PushD { + char * oldDir; + + PushD(const std::string &newDir) { + oldDir = getcwd(0, 0); + if (!oldDir) throw SysError("getcwd"); + int r = chdir(newDir.c_str()); + if (r != 0) throw SysError("changing directory to tar output path"); + } + + ~PushD() { + int r = chdir(oldDir); + free(oldDir); + if (r != 0) + std::cerr << "warning: failed to change directory back after tar extraction"; + /* can't throw out of a destructor */ + } +}; + +static void extract_archive(TarArchive &archive, const Path & destDir) { + // need to chdir back *after* archive closing + PushD newDir(destDir); + struct archive_entry *entry; + int flags = ARCHIVE_EXTRACT_FFLAGS + | ARCHIVE_EXTRACT_PERM + | ARCHIVE_EXTRACT_SECURE_SYMLINKS + | ARCHIVE_EXTRACT_SECURE_NODOTDOT + | ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS; + + for(;;) { + int r = archive_read_next_header(archive.archive, &entry); + if (r == ARCHIVE_EOF) break; + else if (r == ARCHIVE_WARN) + std::cerr << "warning: " << archive_error_string(archive.archive) << std::endl; + else + archive.check(r); + + archive.check(archive_read_extract(archive.archive, entry, flags)); + } + + archive.close(); +} + void unpackTarfile(Source & source, const Path & destDir) { - rust::Source source2(source); - rust::Result<std::tuple<>> res; - unpack_tarfile(source2, destDir, res); - res.unwrap(); + auto archive = TarArchive(source); + + createDirs(destDir); + extract_archive(archive, destDir); } -void unpackTarfile(const Path & tarFile, const Path & destDir, - std::optional<std::string> baseName) +void unpackTarfile(const Path & tarFile, const Path & destDir) { - if (!baseName) baseName = std::string(baseNameOf(tarFile)); - - auto source = sinkToSource([&](Sink & sink) { - // FIXME: look at first few bytes to determine compression type. - auto decompressor = - hasSuffix(*baseName, ".bz2") ? makeDecompressionSink("bzip2", sink) : - hasSuffix(*baseName, ".gz") ? makeDecompressionSink("gzip", sink) : - hasSuffix(*baseName, ".xz") ? makeDecompressionSink("xz", sink) : - makeDecompressionSink("none", sink); - readFile(tarFile, *decompressor); - decompressor->finish(); - }); - - unpackTarfile(*source, destDir); + auto archive = TarArchive(tarFile); + + createDirs(destDir); + extract_archive(archive, destDir); } } |