summaryrefslogtreecommitdiffstats
path: root/src/libutil/tarfile.cc
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2019-12-19 14:47:18 +0100
committerEelco Dolstra <edolstra@gmail.com>2019-12-19 14:47:18 +0100
commitee235e764c6a2af1ff3ae24ce8ad9ae0e1928f39 (patch)
tree36929ecdd82a22d3eba38c31d4be11208f35e94a /src/libutil/tarfile.cc
parent9f7b4d068cc106e5d902dc6f52bf46d4a057fd00 (diff)
parentf765e441237cb6679c33cb44372a5b30168c42c8 (diff)
Merge branch 'libarchive' of https://github.com/yorickvP/nix
Diffstat (limited to 'src/libutil/tarfile.cc')
-rw-r--r--src/libutil/tarfile.cc146
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);
}
}