You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by jo...@apache.org on 2018/06/05 00:01:27 UTC
[4/4] mesos git commit: Added a stout utility header which interfaces
with libarchive.
Added a stout utility header which interfaces with libarchive.
This archiver utility can be invoked to extract an archive with several
supported compression formats, including `.zip`, `.gz`, `.bz2`,
and `.xz` formats.
Review: https://reviews.apache.org/r/67065/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/894d06e8
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/894d06e8
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/894d06e8
Branch: refs/heads/master
Commit: 894d06e8c50d5080ea0a0e43400adc34b06717ad
Parents: 106d6e8
Author: John Kordich <jo...@microsoft.com>
Authored: Mon Jun 4 13:44:20 2018 -0700
Committer: Joseph Wu <jo...@apache.org>
Committed: Mon Jun 4 15:50:56 2018 -0700
----------------------------------------------------------------------
3rdparty/stout/CMakeLists.txt | 1 +
3rdparty/stout/Makefile.am | 11 +
3rdparty/stout/include/Makefile.am | 1 +
3rdparty/stout/include/stout/archiver.hpp | 172 +++++++++
3rdparty/stout/tests/CMakeLists.txt | 1 +
3rdparty/stout/tests/archiver_tests.cpp | 493 +++++++++++++++++++++++++
6 files changed, 679 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/894d06e8/3rdparty/stout/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/3rdparty/stout/CMakeLists.txt b/3rdparty/stout/CMakeLists.txt
index 24a1f0a..9cbb6f2 100644
--- a/3rdparty/stout/CMakeLists.txt
+++ b/3rdparty/stout/CMakeLists.txt
@@ -29,6 +29,7 @@ target_link_libraries(
picojson
protobuf
Threads::Threads
+ libarchive
zlib
$<$<PLATFORM_ID:Linux>:rt dl svn>
$<$<PLATFORM_ID:Darwin>:svn>
http://git-wip-us.apache.org/repos/asf/mesos/blob/894d06e8/3rdparty/stout/Makefile.am
----------------------------------------------------------------------
diff --git a/3rdparty/stout/Makefile.am b/3rdparty/stout/Makefile.am
index ef22a02..5b922af 100644
--- a/3rdparty/stout/Makefile.am
+++ b/3rdparty/stout/Makefile.am
@@ -53,6 +53,7 @@ GLOG = $(BUNDLED_DIR)/glog-$(GLOG_VERSION)
GOOGLETEST = $(BUNDLED_DIR)/googletest-release-$(GOOGLETEST_VERSION)
GMOCK = $(GOOGLETEST)/googlemock
GTEST = $(GOOGLETEST)/googletest
+LIBARCHIVE = $(BUNDLED_DIR)/libarchive-$(LIBARCHIVE_VERSION)
PROTOBUF = $(BUNDLED_DIR)/protobuf-$(PROTOBUF_VERSION)
PICOJSON = $(BUNDLED_DIR)/picojson-$(PICOJSON_VERSION)
@@ -97,6 +98,13 @@ PICOJSON_INCLUDE_FLAGS = \
-DPICOJSON_USE_INT64 \
-D__STDC_FORMAT_MACROS
+if WITH_BUNDLED_LIBARCHIVE
+LIBARCHIVE_INCLUDE_FLAGS = -I$(LIBARCHIVE)/libarchive
+LIB_LIBARCHIVE = $(LIBARCHIVE)/.libs/libarchive.la
+else
+LIB_LIBARCHIVE = -larchive
+endif
+
if WITH_BUNDLED_PICOJSON
PICOJSON_INCLUDE_FLAGS += -I$(PICOJSON)
BUNDLED_DEPS += $(PICOJSON)-stamp
@@ -124,6 +132,7 @@ check_PROGRAMS = stout-tests
stout_tests_SOURCES = \
tests/adaptor_tests.cpp \
+ tests/archiver_tests.cpp \
tests/base64_tests.cpp \
tests/bits_tests.cpp \
tests/boundedhashmap_tests.cpp \
@@ -189,6 +198,7 @@ stout_tests_CPPFLAGS = \
$(GLOG_INCLUDE_FLAGS) \
$(GMOCK_INCLUDE_FLAGS) \
$(GTEST_INCLUDE_FLAGS) \
+ $(LIBARCHIVE_INCLUDE_FLAGS) \
$(PICOJSON_INCLUDE_FLAGS) \
$(PROTOBUF_INCLUDE_FLAGS) \
$(AM_CPPFLAGS)
@@ -199,6 +209,7 @@ stout_tests_CPPFLAGS = \
stout_tests_LDADD = \
$(LIB_GMOCK) \
$(LIB_GLOG) \
+ $(LIB_LIBARCHIVE) \
$(LIB_PROTOBUF) \
-lsvn_subr-1 \
-lsvn_delta-1 \
http://git-wip-us.apache.org/repos/asf/mesos/blob/894d06e8/3rdparty/stout/include/Makefile.am
----------------------------------------------------------------------
diff --git a/3rdparty/stout/include/Makefile.am b/3rdparty/stout/include/Makefile.am
index e0097c4..0a4ea7b 100644
--- a/3rdparty/stout/include/Makefile.am
+++ b/3rdparty/stout/include/Makefile.am
@@ -14,6 +14,7 @@
nobase_include_HEADERS = \
stout/abort.hpp \
stout/adaptor.hpp \
+ stout/archiver.hpp \
stout/assert.hpp \
stout/attributes.hpp \
stout/base64.hpp \
http://git-wip-us.apache.org/repos/asf/mesos/blob/894d06e8/3rdparty/stout/include/stout/archiver.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/stout/include/stout/archiver.hpp b/3rdparty/stout/include/stout/archiver.hpp
new file mode 100644
index 0000000..f66da36
--- /dev/null
+++ b/3rdparty/stout/include/stout/archiver.hpp
@@ -0,0 +1,172 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef __STOUT_ARCHIVER_HPP__
+#define __STOUT_ARCHIVER_HPP__
+
+#include <archive.h>
+#include <archive_entry.h>
+
+#include <stout/nothing.hpp>
+#include <stout/path.hpp>
+#include <stout/try.hpp>
+
+#include <stout/os/close.hpp>
+#include <stout/os/int_fd.hpp>
+#include <stout/os/open.hpp>
+
+namespace archiver {
+
+// Extracts the archive in source to the destination folder (if specified).
+// If destination is not specified, it will use the working directory.
+// Flags can be any of (or together):
+// ARCHIVE_EXTRACT_ACL
+// ARCHIVE_EXTRACT_FFLAGS
+// ARCHIVE_EXTRACT_PERM
+// ARCHIVE_EXTRACT_TIME
+inline Try<Nothing> extract(
+ const std::string& source,
+ const std::string& destination,
+ const int flags = ARCHIVE_EXTRACT_TIME)
+{
+ // Get references to libarchive for reading/handling a compressed file.
+ std::unique_ptr<struct archive, std::function<void(struct archive*)>> reader(
+ archive_read_new(),
+ [](struct archive* p) {
+ archive_read_close(p);
+ archive_read_free(p);
+ });
+
+ // Enable auto-detection of the archive type/format.
+ archive_read_support_format_all(reader.get());
+ archive_read_support_filter_all(reader.get());
+
+ std::unique_ptr<struct archive, std::function<void(struct archive*)>> writer(
+ archive_write_disk_new(),
+ [](struct archive* p) {
+ archive_write_close(p);
+ archive_write_free(p);
+ });
+
+ archive_write_disk_set_options(writer.get(), flags);
+ archive_write_disk_set_standard_lookup(writer.get());
+
+ // Open the compressed file for decompression.
+ //
+ // We do not use libarchive to open the file to ensure we have proper
+ // file descriptor and long path handling on both Posix and Windows.
+ Try<int_fd> fd = os::open(source, O_RDONLY | O_CLOEXEC);
+ if (fd.isError()) {
+ return Error(fd.error());
+ }
+
+#ifdef __WINDOWS__
+ int fd_real = fd->crt();
+#else
+ int fd_real = fd.get();
+#endif
+
+ // Ensure the CRT file descriptor is closed when leaving scope.
+ // NOTE: On Windows, we need to explicitly allocate a CRT file descriptor
+ // because libarchive requires it. Once the CRT fd is allocated, it must
+ // be closed with _close instead of os::close.
+ struct Closer
+ {
+ int fd_value;
+#ifdef __WINDOWS__
+ ~Closer() { ::_close(fd_value); }
+#else
+ ~Closer() { os::close(fd_value); }
+#endif
+ } closer = {fd_real};
+
+ const size_t archive_block_size = 10240;
+ int result = archive_read_open_fd(reader.get(), fd_real, archive_block_size);
+ if (result != ARCHIVE_OK) {
+ return Error(archive_error_string(reader.get()));
+ }
+
+ // Loop through file headers in the archive stream.
+ while (true) {
+ // Read the next header from the input stream.
+ struct archive_entry* entry;
+ result = archive_read_next_header(reader.get(), &entry);
+
+ if (result == ARCHIVE_EOF) {
+ break;
+ } else if (result <= ARCHIVE_WARN) {
+ return Error(
+ std::string("Failed to read archive header: ") +
+ archive_error_string(reader.get()));
+ }
+
+ // If a destination path is specified, update the entry to reflect it.
+ // We assume the destination directory already exists.
+ if (!destination.empty()) {
+ std::string path = path::join(destination, archive_entry_pathname(entry));
+ archive_entry_update_pathname_utf8(entry, path.c_str());
+ }
+
+ result = archive_write_header(writer.get(), entry);
+ if (result <= ARCHIVE_WARN) {
+ return Error(
+ std::string("Failed to write archive header: ") +
+ archive_error_string(writer.get()));
+ }
+
+ if (archive_entry_size(entry) > 0) {
+ const void* buff;
+ size_t size;
+#if ARCHIVE_VERSION_NUMBER >= 3000000
+ int64_t offset;
+#else
+ off_t offset;
+#endif
+
+ // Loop through file data blocks until end of file.
+ while (true) {
+ result = archive_read_data_block(reader.get(), &buff, &size, &offset);
+ if (result == ARCHIVE_EOF) {
+ break;
+ } else if (result <= ARCHIVE_WARN) {
+ return Error(
+ std::string("Failed to read archive data block: ") +
+ archive_error_string(reader.get()));
+ }
+
+ result = archive_write_data_block(writer.get(), buff, size, offset);
+ if (result <= ARCHIVE_WARN) {
+ return Error(
+ std::string("Failed to write archive data block: ") +
+ archive_error_string(writer.get()));
+ }
+ }
+ }
+
+ result = archive_write_finish_entry(writer.get());
+ if (result <= ARCHIVE_WARN) {
+ return Error(
+ std::string("Failed to write archive finish entry: ") +
+ archive_error_string(writer.get()));
+ }
+ }
+
+ return Nothing();
+}
+
+} // namespace archiver {
+
+#endif // __STOUT_ARCHIVER_HPP__
http://git-wip-us.apache.org/repos/asf/mesos/blob/894d06e8/3rdparty/stout/tests/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/3rdparty/stout/tests/CMakeLists.txt b/3rdparty/stout/tests/CMakeLists.txt
index 86111a8..7c644b7 100644
--- a/3rdparty/stout/tests/CMakeLists.txt
+++ b/3rdparty/stout/tests/CMakeLists.txt
@@ -17,6 +17,7 @@
# STOUT TESTS.
##############
set(STOUT_ROOT_TESTS_SRC
+ archiver_tests.cpp
base64_tests.cpp
bits_tests.cpp
bytes_tests.cpp
http://git-wip-us.apache.org/repos/asf/mesos/blob/894d06e8/3rdparty/stout/tests/archiver_tests.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/stout/tests/archiver_tests.cpp b/3rdparty/stout/tests/archiver_tests.cpp
new file mode 100644
index 0000000..fc8db56
--- /dev/null
+++ b/3rdparty/stout/tests/archiver_tests.cpp
@@ -0,0 +1,493 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License
+
+#include <string>
+
+#ifdef __WINDOWS__
+#include <stout/windows.hpp>
+#endif
+
+#include <stout/archiver.hpp>
+#include <stout/base64.hpp>
+#include <stout/gtest.hpp>
+#include <stout/os.hpp>
+
+#include <stout/os/write.hpp>
+
+#include <stout/tests/utils.hpp>
+
+using std::string;
+
+
+class ArchiverTest : public TemporaryDirectoryTest {};
+
+// No input file should return some error, not read from stdin.
+TEST_F(ArchiverTest, ExtractEmptyInputFile)
+{
+ EXPECT_ERROR(archiver::extract("", ""));
+}
+
+// File that does not exist should return some error.
+TEST_F(ArchiverTest, ExtractInputFileNotFound)
+{
+ // Construct a temporary file path that is guarenteed unique.
+ Try<string> dir = os::mkdtemp(path::join(sandbox.get(), "XXXXXX"));
+ ASSERT_SOME(dir);
+
+ string path = path::join(dir.get(), "ThisFileDoesNotExist");
+
+ EXPECT_ERROR(archiver::extract(path, ""));
+}
+
+TEST_F(ArchiverTest, ExtractTarGzFile)
+{
+ // Construct a hello.tar.gz file that can be extracted.
+ string dir = path::join(sandbox.get(), "somedir");
+ ASSERT_SOME(os::mkdir(dir));
+
+ Try<string> path = os::mktemp(path::join(dir, "XXXXXX"));
+ ASSERT_SOME(path);
+
+ // Length Size Date Time Name Content
+ // -------- ------- ---------- ----- ---- ------
+ // 22 22 2018-02-21 10:06 hello Howdy there, partner!\n
+ // -------- ------- ------- ------
+ // 22 22 1 file
+
+ ASSERT_SOME(os::write(path.get(), base64::decode(
+ "H4sICE61jVoAA2hlbGxvLnRhcgDtzjEOwjAQRNGtOcXSU9hx7FyBa0RgK0IR"
+ "RsYIcfsEaGhQqggh/VfsFLPFDHEcs6zLzEJon2k7bz7zrQliXdM6Nx/vxVgb"
+ "uiBqVt71crvWvqjKKaZ0yOnr31L/p/b5fnxoHWKJO730pZ5j2W5+vQoAAAAA"
+ "AAAAAAAAAAAAsGQC2DPIjgAoAAA=").get()));
+
+ // Note: The file does NOT have a .tar.gz extension. We could rename
+ // it, but libarchive doesn't care about extensions. It determines
+ // the format from the contents of the file. So this is tested here
+ // as well.
+ EXPECT_SOME(archiver::extract(path.get(), ""));
+
+ string extractedFile = path::join(sandbox.get(), "hello");
+ ASSERT_TRUE(os::exists(extractedFile));
+
+ ASSERT_SOME_EQ("Howdy there, partner!\n", os::read(extractedFile));
+}
+
+
+TEST_F(ArchiverTest, ExtractTarFile)
+{
+ // Construct a hello.tar file that can be extracted.
+ string dir = path::join(sandbox.get(), "somedir");
+ ASSERT_SOME(os::mkdir(dir));
+
+ Try<string> path = os::mktemp(path::join(dir, "XXXXXX"));
+ ASSERT_SOME(path);
+
+ // The .tar file, since not compressed, is long. So go through some
+ // pains to construct the contents to the file programmatically.
+ //
+ // We could skip the .tar file test, but it's worth having it.
+ //
+ // Length Size Date Time Name Content
+ // -------- ------- ---------- ----- ---- ------
+ // 10240 10240 2018-02-21 10:06 hello Howdy there, partner (.tar)!\n
+ // -------- ------- ------- ------
+ // 10240 10240 1 file
+
+ string tarContents =
+ "aGVsbG8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAADAwMDA2NjQAMDAwMTc1MAAwMDAxNzUwADAwMDAwMDAwMDM1"
+ "ADEzMjQ1MTA2NTE1ADAxMTY3NAAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhciAgAGplZmZj"
+ "b2YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAamVmZmNvZgAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAABIb3dkeSB0aGVyZSwgcGFydG5lciEgKC50YXIp"
+ "CgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+
+ for (int i = 0; i < 214; i++)
+ {
+ tarContents +=
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+ }
+
+ tarContents += "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
+
+ // Now write out the .tar file, extract contents, and verify results
+ ASSERT_SOME(os::write(path.get(), base64::decode(tarContents).get()));
+
+ EXPECT_SOME(archiver::extract(path.get(), ""));
+
+ string extractedFile = path::join(sandbox.get(), "hello");
+ ASSERT_TRUE(os::exists(extractedFile));
+
+ ASSERT_SOME_EQ("Howdy there, partner! (.tar)\n", os::read(extractedFile));
+}
+
+
+TEST_F(ArchiverTest, ExtractZipFile)
+{
+ // Construct a hello.zip file that can be extracted.
+ string dir = path::join(sandbox.get(), "somedir");
+ ASSERT_SOME(os::mkdir(dir));
+
+ Try<string> path = os::mktemp(path::join(dir, "XXXXXX"));
+ ASSERT_SOME(path);
+
+ // Length Size Date Time Name Content
+ // -------- ------- ---------- ----- ---- ------
+ // 189 189 2018-02-26 15:06 hello Howdy there, partner! (.zip)\n
+ // -------- ------- ------- ------
+ // 189 189 1 file
+
+ ASSERT_SOME(os::write(path.get(), base64::decode(
+ "UEsDBAoAAAAAAMZ4WkxFOXeVHQAAAB0AAAAFABwAaGVsbG9VVAkAA+SSlFrk"
+ "kpRadXgLAAEE6AMAAAToAwAASG93ZHkgdGhlcmUsIHBhcnRuZXIhICguemlw"
+ "KQpQSwECHgMKAAAAAADGeFpMRTl3lR0AAAAdAAAABQAYAAAAAAABAAAAtIEA"
+ "AAAAaGVsbG9VVAUAA+SSlFp1eAsAAQToAwAABOgDAABQSwUGAAAAAAEAAQBL"
+ "AAAAXAAAAAAA").get()));
+
+ EXPECT_SOME(archiver::extract(path.get(), ""));
+
+ string extractedFile = path::join(sandbox.get(), "hello");
+ ASSERT_TRUE(os::exists(extractedFile));
+
+ ASSERT_SOME_EQ("Howdy there, partner! (.zip)\n", os::read(extractedFile));
+}
+
+
+TEST_F(ArchiverTest, ExtractInvalidZipFile)
+{
+ // Construct a hello.zip file that can be extracted.
+ string dir = path::join(sandbox.get(), "somedir");
+ ASSERT_SOME(os::mkdir(dir));
+
+ Try<string> path = os::mktemp(path::join(dir, "XXXXXX"));
+ ASSERT_SOME(path);
+
+ // Write broken zip to file [bad CRC 440a6aa5 (should be af083b2d)].
+ //
+ // Length Date Time CRC expected CRC actual Name Content
+ // -------- ---------- ----- ------------ ---------- ---- ------
+ // 12 2016-03-19 10:08 af083b2d 440a6aa5 world hello hello\n
+ // -------- ------- ------
+ // 12 1 file
+
+ ASSERT_SOME(os::write(path.get(), base64::decode(
+ "UEsDBAoAAAAAABBRc0gtOwivDAAAAAwAAAAFABwAd29ybG9VVAkAAxAX7VYQ"
+ "F+1WdXgLAAEE6AMAAARkAAAAaGVsbG8gaGVsbG8KUEsBAh4DCgAAAAAAEFFz"
+ "SC07CK8MAAAADAAAAAUAGAAAAAAAAQAAAKSBAAAAAHdvcmxkVVQFAAMQF+1W"
+ "dXgLAAEE6AMAAARkAAAAUEsFBgAAAAABAAEASwAAAEsAAAAAAA==").get()));
+
+ EXPECT_ERROR(archiver::extract(path.get(), ""));
+}
+
+
+TEST_F(ArchiverTest, ExtractZipFileWithDuplicatedEntries)
+{
+ string dir = path::join(sandbox.get(), "somedir");
+ ASSERT_SOME(os::mkdir(dir));
+
+ Try<string> path = os::mktemp(path::join(dir, "XXXXXX"));
+ ASSERT_SOME(path);
+
+ // Create zip file with duplicates.
+ //
+ // Length Method Size Cmpr Date Time CRC-32 Name Content
+ // -------- ------ ------- ---- ---------- ----- -------- ---- -------
+ // 1 Stored 1 0% 2016-03-18 22:49 83dcefb7 A 1
+ // 1 Stored 1 0% 2016-03-18 22:49 1ad5be0d A 2
+ // -------- ------- --- ------- -------
+ // 2 2 0% 2 files
+
+ ASSERT_SOME(os::write(path.get(), base64::decode(
+ "UEsDBBQAAAAAADC2cki379yDAQAAAAEAAAABAAAAQTFQSwMEFAAAAAAAMrZy"
+ "SA2+1RoBAAAAAQAAAAEAAABBMlBLAQIUAxQAAAAAADC2cki379yDAQAAAAEA"
+ "AAABAAAAAAAAAAAAAACAAQAAAABBUEsBAhQDFAAAAAAAMrZySA2+1RoBAAAA"
+ "AQAAAAEAAAAAAAAAAAAAAIABIAAAAEFQSwUGAAAAAAIAAgBeAAAAQAAAAAAA").get()));
+
+ EXPECT_SOME(archiver::extract(path.get(), ""));
+
+ string extractedFile = path::join(sandbox.get(), "A");
+ ASSERT_TRUE(os::exists(extractedFile));
+
+ ASSERT_SOME_EQ("2", os::read(extractedFile));
+}
+
+
+TEST_F(ArchiverTest, ExtractTarXZFile)
+{
+ string dir = path::join(sandbox.get(), "somedir");
+ ASSERT_SOME(os::mkdir(dir));
+
+ Try<string> path = os::mktemp(path::join(dir, "XXXXXX"));
+ ASSERT_SOME(path);
+
+ // Create a tar.xz compressed file.
+ //
+ // Length Size Date Time Name Content
+ // -------- ------- ---------- ----- ---- ------
+ // 192 192 2018-02-27 15:34 hello Hello world (xz)\n
+ // -------- ------- ------- ------
+ // 192 192 1 file
+
+ ASSERT_SOME(os::write(path.get(), base64::decode(
+ "/Td6WFoAAATm1rRGAgAhARYAAAB0L+Wj4Cf/AH5dADQZSe6N1/i4P8k3jGgA"
+ "rB4mJjQrf8ka7ajHWIxeYZoS+eGuA0Br4ooXZVdW4dnh8GpgDlbdfMQrOOPA"
+ "aJE3B9L56mP0ThtjwNuMhhc8/xiXsFOVeUf/xbgcqognut0NZNetr0p+FA/O"
+ "K6NqFHAjzSaANcbNj+iFfqY3sC/mAAAAADpda78LIiMIAAGaAYBQAADDUC3D"
+ "scRn+wIAAAAABFla").get()));
+
+ EXPECT_SOME(archiver::extract(path.get(), ""));
+
+ string extractedFile = path::join(sandbox.get(), "hello");
+ ASSERT_TRUE(os::exists(extractedFile));
+
+ ASSERT_SOME_EQ("Hello world (xz)\n", os::read(extractedFile));
+}
+
+
+TEST_F(ArchiverTest, ExtractTarBZ2File)
+{
+ string dir = path::join(sandbox.get(), "somedir");
+ ASSERT_SOME(os::mkdir(dir));
+
+ Try<string> path = os::mktemp(path::join(dir, "XXXXXX"));
+ ASSERT_SOME(path);
+
+ // Create an tar.bz2 compressed file.
+ //
+ // Length Size Date Time Name Content
+ // -------- ------- ---------- ----- ---- ------
+ // 148 148 2018-02-27 15:34 hello Hello world (bzip2)\n
+ // -------- ------- ------- ------
+ // 148 148 1 file
+
+ ASSERT_SOME(os::write(path.get(), base64::decode(
+ "QlpoOTFBWSZTWZo+haYAAH//hMIRAgBAYH+AAEAACH903pAABAAIIAB0EpEa"
+ "IeiMJtAIeRP1BlNCA00AAAA+x2lRZBAgaACRM0TvUjA5RJAR6BfGS3MjVUIh"
+ "IUI0Yww9tmran651Du0Hk5ZN4pbSxgs5xlAlIjtgOImyv+auHhIXnipV/xXy"
+ "iIHQu5IpwoSE0fQtMA==").get()));
+
+ EXPECT_SOME(archiver::extract(path.get(), ""));
+
+ string extractedFile = path::join(sandbox.get(), "hello");
+ ASSERT_TRUE(os::exists(extractedFile));
+
+ ASSERT_SOME_EQ("Hello world (bzip2)\n", os::read(extractedFile));
+}
+
+
+TEST_F(ArchiverTest, ExtractTarBz2GzFile)
+{
+ string dir = path::join(sandbox.get(), "somedir");
+ ASSERT_SOME(os::mkdir(dir));
+
+ Try<string> path = os::mktemp(path::join(dir, "XXXXXX"));
+ ASSERT_SOME(path);
+
+ // Create an tar.bz2.gz compressed file.
+ //
+ // Verify that archives compressed twice (in this case, .bzip2.gz)
+ // work. Libarchive will keep processing until fully extracted.
+ //
+ // Length Size Date Time Name Content
+ // -------- ------- ---------- ----- ---- ------
+ // 185 185 2018-02-27 15:46 hello Hello world (bzip2)\n
+ // -------- ------- ------- ------
+ // 185 185 1 file
+
+ ASSERT_SOME(os::write(path.get(), base64::decode(
+ "H4sICOPtlVoAA2hlbGxvLnRhci5iejIAAZQAa/9CWmg5MUFZJlNZmj6FpgAA"
+ "f/+EwhECAEBgf4AAQAAIf3TekAAEAAggAHQSkRoh6Iwm0Ah5E/UGU0IDTQAA"
+ "AD7HaVFkECBoAJEzRO9SMDlEkBHoF8ZLcyNVQiEhQjRjDD22atqfrnUO7QeT"
+ "lk3iltLGCznGUCUiO2A4ibK/5q4eEheeKlX/FfKIgdC7kinChITR9C0wSQeY"
+ "TJQAAAA=").get()));
+
+ EXPECT_SOME(archiver::extract(path.get(), ""));
+
+ string extractedFile = path::join(sandbox.get(), "hello");
+ ASSERT_TRUE(os::exists(extractedFile));
+
+ ASSERT_SOME_EQ("Hello world (bzip2)\n", os::read(extractedFile));
+}
+
+
+TEST_F(ArchiverTest, ExtractBz2FileFails)
+{
+ string dir = path::join(sandbox.get(), "somedir");
+ ASSERT_SOME(os::mkdir(dir));
+
+ Try<string> path = os::mktemp(path::join(dir, "XXXXXX"));
+ ASSERT_SOME(path);
+
+ // Create an .bz2 compressed file.
+ //
+ // Libarchive does not appear to work without some sort of container
+ // (tar or zip or whatever). Verify that a regular file, compressed,
+ // will fail.
+ //
+ // Length Size Date Time Name Content
+ // -------- ------- ---------- ----- ---- ------
+ // 63 63 2018-02-27 17:00 hello Hello world (bzip2)\n
+ // -------- ------- ------- ------
+ // 63 63 1 file
+
+ ASSERT_SOME(os::write(path.get(), base64::decode(
+ "QlpoOTFBWSZTWTMaBKkAAANdgAAQQGAQAABAFiTQkCAAIhGCD1HoUwAE0auv"
+ "Imhs/86EgGxdyRThQkDMaBKk").get()));
+
+ EXPECT_ERROR(archiver::extract(path.get(), ""));
+}
+
+
+TEST_F(ArchiverTest, ExtractGzFileFails)
+{
+ string dir = path::join(sandbox.get(), "somedir");
+ ASSERT_SOME(os::mkdir(dir));
+
+ Try<string> path = os::mktemp(path::join(dir, "XXXXXX"));
+ ASSERT_SOME(path);
+
+ // Create an .gz compressed file.
+ //
+ // Libarchive does not appear to work without some sort of container
+ // (tar or zip or whatever). Verify that a regular file, compressed,
+ // will fail.
+ //
+ // Length Size Date Time Name Content
+ // -------- ------- ---------- ----- ---- ------
+ // 43 43 2018-03-21 16:59 hello Hello world (gz)\n
+ // -------- ------- ------- ------
+ // 43 43 1 file
+
+ ASSERT_SOME(os::write(path.get(), base64::decode(
+ "H4sICNjxsloAA2hlbGxvAPNIzcnJVyjPL8pJUdBIr9LkAgAwtvTdEQAAAA==").get()));
+
+ EXPECT_ERROR(archiver::extract(path.get(), ""));
+}
+
+
+TEST_F(ArchiverTest, ExtractTarGzFileWithDestinationDir)
+{
+ // Construct a hello.tar.gz file that can be extracted.
+ string dir = path::join(sandbox.get(), "somedir");
+ ASSERT_SOME(os::mkdir(dir));
+
+ Try<string> sourcePath = os::mktemp(path::join(dir, "XXXXXX"));
+ ASSERT_SOME(sourcePath);
+
+ // Length Size Date Time Name Content
+ // -------- ------- ---------- ----- ---- ------
+ // 22 22 2018-02-21 10:06 hello Howdy there, partner!\n
+ // -------- ------- ------- ------
+ // 22 22 1 file
+
+ ASSERT_SOME(os::write(sourcePath.get(), base64::decode(
+ "H4sICE61jVoAA2hlbGxvLnRhcgDtzjEOwjAQRNGtOcXSU9hx7FyBa0RgK0IR"
+ "RsYIcfsEaGhQqggh/VfsFLPFDHEcs6zLzEJon2k7bz7zrQliXdM6Nx/vxVgb"
+ "uiBqVt71crvWvqjKKaZ0yOnr31L/p/b5fnxoHWKJO730pZ5j2W5+vQoAAAAA"
+ "AAAAAAAAAAAAsGQC2DPIjgAoAAA=").get()));
+
+ // Make a destination directory to extract the archive to.
+ string destDir = path::join(dir, "somedestination");
+ ASSERT_SOME(os::mkdir(destDir));
+
+ // Note: The file does NOT have a .tar.gz extension. We could rename
+ // it, but libarchive doesn't care about extensions. It determines
+ // the format from the contents of the file. So this is tested here
+ // as well.
+ //
+ // Note: In this test, we extract the file to a destination directory
+ // and expect to find it there.
+ EXPECT_SOME(archiver::extract(sourcePath.get(), destDir));
+
+ string extractedFile = path::join(destDir, "hello");
+ ASSERT_TRUE(os::exists(extractedFile));
+
+ ASSERT_SOME_EQ("Howdy there, partner!\n", os::read(extractedFile));
+}
+
+
+TEST_F(ArchiverTest, ExtractZipFileWithDestinationDir)
+{
+ // Construct a hello.zip file that can be extracted.
+ string dir = path::join(sandbox.get(), "somedir");
+ ASSERT_SOME(os::mkdir(dir));
+
+ Try<string> sourcePath = os::mktemp(path::join(dir, "XXXXXX"));
+ ASSERT_SOME(sourcePath);
+
+ // Length Size Date Time Name Content
+ // -------- ------- ---------- ----- ---- ------
+ // 189 189 2018-02-26 15:06 hello Howdy there, partner! (.zip)\n
+ // -------- ------- ------- ------
+ // 189 189 1 file
+
+ ASSERT_SOME(os::write(sourcePath.get(), base64::decode(
+ "UEsDBAoAAAAAAMZ4WkxFOXeVHQAAAB0AAAAFABwAaGVsbG9VVAkAA+SSlFrk"
+ "kpRadXgLAAEE6AMAAAToAwAASG93ZHkgdGhlcmUsIHBhcnRuZXIhICguemlw"
+ "KQpQSwECHgMKAAAAAADGeFpMRTl3lR0AAAAdAAAABQAYAAAAAAABAAAAtIEA"
+ "AAAAaGVsbG9VVAUAA+SSlFp1eAsAAQToAwAABOgDAABQSwUGAAAAAAEAAQBL"
+ "AAAAXAAAAAAA").get()));
+
+ // Make a destination directory to extract the archive to.
+ string destDir = path::join(dir, "somedestination");
+ ASSERT_SOME(os::mkdir(destDir));
+
+ EXPECT_SOME(archiver::extract(sourcePath.get(), destDir));
+
+ string extractedFile = path::join(destDir, "hello");
+ ASSERT_TRUE(os::exists(extractedFile));
+
+ ASSERT_SOME_EQ("Howdy there, partner! (.zip)\n", os::read(extractedFile));
+}
+
+
+TEST_F(ArchiverTest, ExtractZipFileWithLongDestinationDir)
+{
+ // Construct a hello.zip file that can be extracted.
+ string dir = path::join(sandbox.get(), "somedir");
+ ASSERT_SOME(os::mkdir(dir));
+
+ Try<string> sourcePath = os::mktemp(path::join(dir, "XXXXXX"));
+ ASSERT_SOME(sourcePath);
+
+ // Length Size Date Time Name Content
+ // -------- ------- ---------- ----- ---- ------
+ // 189 189 2018-02-26 15:06 hello Howdy there, partner! (.zip)\n
+ // -------- ------- ------- ------
+ // 189 189 1 file
+
+ ASSERT_SOME(os::write(sourcePath.get(), base64::decode(
+ "UEsDBAoAAAAAAMZ4WkxFOXeVHQAAAB0AAAAFABwAaGVsbG9VVAkAA+SSlFrk"
+ "kpRadXgLAAEE6AMAAAToAwAASG93ZHkgdGhlcmUsIHBhcnRuZXIhICguemlw"
+ "KQpQSwECHgMKAAAAAADGeFpMRTl3lR0AAAAdAAAABQAYAAAAAAABAAAAtIEA"
+ "AAAAaGVsbG9VVAUAA+SSlFp1eAsAAQToAwAABOgDAABQSwUGAAAAAAEAAQBL"
+ "AAAAXAAAAAAA").get()));
+
+ // Make a destination directory to extract the archive to.
+ const size_t max_path_length = 248;
+ string destDir = path::join(dir, string(max_path_length + 1, 'b'));
+
+ ASSERT_SOME(os::mkdir(destDir));
+
+ EXPECT_SOME(archiver::extract(sourcePath.get(), destDir));
+
+ string extractedFile = path::join(destDir, "hello");
+ ASSERT_TRUE(os::exists(extractedFile));
+
+ ASSERT_SOME_EQ("Howdy there, partner! (.zip)\n", os::read(extractedFile));
+}