blob: 4981a54aa6807315f8bf2984f1b5381b07d75da5 [file] [edit]
// Copyright 2026 The Bazel Authors. All rights reserved.
//
// 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 <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(_WIN32)
#include <windows.h>
#include <winioctl.h>
#else
#include <fcntl.h>
#include <unistd.h>
#endif
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include "googletest/include/gtest/gtest.h"
#include "third_party/ijar/common.h"
#include "third_party/ijar/zip.h"
namespace devtools_ijar {
namespace {
std::string GetTempPath(const std::string& name) {
const char* tmpdir = getenv("TEST_TMPDIR");
if (tmpdir == nullptr) {
tmpdir = "/tmp";
}
return std::string(tmpdir) + "/" + name;
}
void CreateSparseFileWithMarkers(const std::string& path, uint64_t size,
uint8_t start_marker, uint8_t end_marker) {
#if defined(_WIN32)
HANDLE h = CreateFileA(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(h, INVALID_HANDLE_VALUE);
DWORD bytes_returned = 0;
DeviceIoControl(h, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytes_returned,
nullptr);
LARGE_INTEGER li;
li.QuadPart = static_cast<LONGLONG>(size);
ASSERT_NE(0, SetFilePointerEx(h, li, nullptr, FILE_BEGIN));
ASSERT_NE(0, SetEndOfFile(h));
if (size > 0) {
li.QuadPart = 0;
ASSERT_NE(0, SetFilePointerEx(h, li, nullptr, FILE_BEGIN));
DWORD written = 0;
ASSERT_NE(0, WriteFile(h, &start_marker, 1, &written, nullptr));
ASSERT_EQ(1u, written);
li.QuadPart = static_cast<LONGLONG>(size - 1);
ASSERT_NE(0, SetFilePointerEx(h, li, nullptr, FILE_BEGIN));
ASSERT_NE(0, WriteFile(h, &end_marker, 1, &written, nullptr));
ASSERT_EQ(1u, written);
}
ASSERT_NE(0, CloseHandle(h));
#else
int fd = open(path.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0644);
ASSERT_GE(fd, 0) << strerror(errno);
ASSERT_EQ(0, ftruncate(fd, static_cast<off_t>(size))) << strerror(errno);
if (size > 0) {
ASSERT_EQ(1, pwrite(fd, &start_marker, 1, 0)) << strerror(errno);
ASSERT_EQ(1, pwrite(fd, &end_marker, 1, static_cast<off_t>(size - 1)))
<< strerror(errno);
}
ASSERT_EQ(0, close(fd));
#endif
}
struct ExtractedEntry {
std::string filename;
uint32_t attr;
size_t size;
uint8_t first_byte;
uint8_t last_byte;
};
class RecordingExtractorProcessor : public ZipExtractorProcessor {
public:
bool Accept(const char* filename, const u4 attr) override { return true; }
void Process(const char* filename, const u4 attr, const u1* data,
const size_t size) override {
std::string_view content(reinterpret_cast<const char*>(data), size);
ExtractedEntry entry;
entry.filename = filename;
entry.attr = attr;
entry.size = size;
entry.first_byte =
content.empty() ? 0 : static_cast<uint8_t>(content.front());
entry.last_byte =
content.empty() ? 0 : static_cast<uint8_t>(content.back());
entries.push_back(entry);
}
std::vector<ExtractedEntry> entries;
};
TEST(Zip64Test, ArchiveLargerThan4GiBWithMultipleFiles) {
std::string file1 = GetTempPath("multi_file1.bin");
std::string file2 = GetTempPath("multi_file2.bin");
std::string file3 = GetTempPath("multi_file3.txt");
std::string zip_path = GetTempPath("multi_4g.zip");
constexpr uint64_t kSize1 = 2500ULL * 1024 * 1024; // 2.5 GiB
constexpr uint64_t kSize2 = 2000ULL * 1024 * 1024; // 2.0 GiB
CreateSparseFileWithMarkers(file1, kSize1, 0x11, 0x22);
CreateSparseFileWithMarkers(file2, kSize2, 0x33, 0x44);
CreateSparseFileWithMarkers(file3, 64, 'A', 'Z');
const char* files[] = {file1.c_str(), file2.c_str(), file3.c_str(), nullptr};
const char* zip_paths[] = {"file1.bin", "file2.bin", "file3.txt", nullptr};
u8 estimated_size = ZipBuilder::EstimateSize(files, zip_paths, 3);
ASSERT_GT(estimated_size, 0xFFFFFFFFULL);
std::unique_ptr<ZipBuilder> builder(
ZipBuilder::Create(zip_path.c_str(), estimated_size));
ASSERT_NE(builder, nullptr);
EXPECT_EQ(0, builder->AddFile("file1.bin", file1.c_str(), 0100644 << 16,
/*compress=*/false, /*compute_crc=*/true));
EXPECT_EQ(0, builder->AddFile("file2.bin", file2.c_str(), 0100644 << 16,
/*compress=*/false, /*compute_crc=*/true));
EXPECT_EQ(0, builder->AddFile("file3.txt", file3.c_str(), 0100644 << 16,
/*compress=*/false, /*compute_crc=*/true));
EXPECT_EQ(0, builder->Finish());
RecordingExtractorProcessor processor;
std::unique_ptr<ZipExtractor> extractor(
ZipExtractor::Create(zip_path.c_str(), &processor));
ASSERT_NE(extractor, nullptr);
EXPECT_EQ(0, extractor->ProcessAll());
ASSERT_EQ(3u, processor.entries.size());
EXPECT_EQ("file1.bin", processor.entries[0].filename);
EXPECT_EQ(kSize1, processor.entries[0].size);
EXPECT_EQ(0x11, processor.entries[0].first_byte);
EXPECT_EQ(0x22, processor.entries[0].last_byte);
EXPECT_EQ("file2.bin", processor.entries[1].filename);
EXPECT_EQ(kSize2, processor.entries[1].size);
EXPECT_EQ(0x33, processor.entries[1].first_byte);
EXPECT_EQ(0x44, processor.entries[1].last_byte);
EXPECT_EQ("file3.txt", processor.entries[2].filename);
EXPECT_EQ(64u, processor.entries[2].size);
EXPECT_EQ('A', processor.entries[2].first_byte);
EXPECT_EQ('Z', processor.entries[2].last_byte);
remove(file1.c_str());
remove(file2.c_str());
remove(file3.c_str());
remove(zip_path.c_str());
}
TEST(Zip64Test, SingleEntryLargerThan4GiB) {
std::string huge_file = GetTempPath("huge_entry.bin");
std::string trailing_file = GetTempPath("trailing.txt");
std::string zip_path = GetTempPath("single_4g.zip");
constexpr uint64_t kHugeSize = 0x100000100ULL; // 4 GiB + 256 bytes
CreateSparseFileWithMarkers(huge_file, kHugeSize, 0x55, 0xAA);
CreateSparseFileWithMarkers(trailing_file, 32, 'B', 'E');
const char* files[] = {huge_file.c_str(), trailing_file.c_str(), nullptr};
const char* zip_paths[] = {"huge_entry.bin", "trailing.txt", nullptr};
u8 estimated_size = ZipBuilder::EstimateSize(files, zip_paths, 2);
ASSERT_GT(estimated_size, kHugeSize);
std::unique_ptr<ZipBuilder> builder(
ZipBuilder::Create(zip_path.c_str(), estimated_size));
ASSERT_NE(builder, nullptr);
EXPECT_EQ(0, builder->AddFile("huge_entry.bin", huge_file.c_str(),
0100644 << 16, /*compress=*/false,
/*compute_crc=*/true));
EXPECT_EQ(0, builder->AddFile("trailing.txt", trailing_file.c_str(),
0100644 << 16, /*compress=*/false,
/*compute_crc=*/true));
EXPECT_EQ(0, builder->Finish());
RecordingExtractorProcessor processor;
std::unique_ptr<ZipExtractor> extractor(
ZipExtractor::Create(zip_path.c_str(), &processor));
ASSERT_NE(extractor, nullptr);
EXPECT_EQ(0, extractor->ProcessAll());
ASSERT_EQ(2u, processor.entries.size());
EXPECT_EQ("huge_entry.bin", processor.entries[0].filename);
EXPECT_EQ(kHugeSize, processor.entries[0].size);
EXPECT_EQ(0x55, processor.entries[0].first_byte);
EXPECT_EQ(0xAA, processor.entries[0].last_byte);
EXPECT_EQ("trailing.txt", processor.entries[1].filename);
EXPECT_EQ(32u, processor.entries[1].size);
EXPECT_EQ('B', processor.entries[1].first_byte);
EXPECT_EQ('E', processor.entries[1].last_byte);
remove(huge_file.c_str());
remove(trailing_file.c_str());
remove(zip_path.c_str());
}
} // namespace
} // namespace devtools_ijar