|  | // Copyright 2017 The Fuchsia Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "src/sys/pkg/lib/far/archive_reader.h" | 
|  |  | 
|  | #include <inttypes.h> | 
|  | #include <unistd.h> | 
|  |  | 
|  | #include <limits> | 
|  | #include <utility> | 
|  |  | 
|  | #include "src/lib/files/directory.h" | 
|  | #include "src/lib/files/path.h" | 
|  | #include "src/lib/fxl/strings/concatenate.h" | 
|  | #include "src/sys/pkg/lib/far/file_operations.h" | 
|  | #include "src/sys/pkg/lib/far/format.h" | 
|  |  | 
|  | namespace archive { | 
|  | namespace { | 
|  |  | 
|  | struct PathComparator { | 
|  | const ArchiveReader* reader = nullptr; | 
|  |  | 
|  | bool operator()(const DirectoryTableEntry& lhs, const std::string_view& rhs) { | 
|  | return reader->GetPathView(lhs) < rhs; | 
|  | } | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | ArchiveReader::ArchiveReader(fbl::unique_fd fd) : fd_(std::move(fd)) {} | 
|  |  | 
|  | ArchiveReader::~ArchiveReader() = default; | 
|  |  | 
|  | bool ArchiveReader::Read() { return ReadIndex() && ReadDirectory(); } | 
|  |  | 
|  | bool ArchiveReader::Extract(std::string_view output_dir) const { | 
|  | for (const auto& entry : directory_table_) { | 
|  | std::string path = fxl::Concatenate({output_dir, "/", GetPathView(entry)}); | 
|  | std::string dir = files::GetDirectoryName(path); | 
|  | if (!dir.empty() && !files::IsDirectory(dir) && !files::CreateDirectory(dir)) { | 
|  | fprintf(stderr, "error: Failed to create directory '%s'.\n", dir.c_str()); | 
|  | return false; | 
|  | } | 
|  | if (lseek(fd_.get(), entry.data_offset, SEEK_SET) < 0) { | 
|  | fprintf(stderr, "error: Failed to seek to offset of file.\n"); | 
|  | return false; | 
|  | } | 
|  | if (!CopyFileToPath(fd_.get(), path.c_str(), entry.data_length)) { | 
|  | fprintf(stderr, "error: Failed write contents to '%s'.\n", path.c_str()); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ArchiveReader::ExtractFile(std::string_view archive_path, const char* output_path) const { | 
|  | DirectoryTableEntry entry; | 
|  | if (!GetDirectoryEntryByPath(archive_path, &entry)) | 
|  | return false; | 
|  | if (lseek(fd_.get(), entry.data_offset, SEEK_SET) < 0) { | 
|  | fprintf(stderr, "error: Failed to seek to offset of file.\n"); | 
|  | return false; | 
|  | } | 
|  | if (!CopyFileToPath(fd_.get(), output_path, entry.data_length)) { | 
|  | fprintf(stderr, "error: Failed write contents to '%s'.\n", output_path); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ArchiveReader::CopyFile(std::string_view archive_path, int dst_fd) const { | 
|  | DirectoryTableEntry entry; | 
|  | if (!GetDirectoryEntryByPath(archive_path, &entry)) | 
|  | return false; | 
|  | if (lseek(fd_.get(), entry.data_offset, SEEK_SET) < 0) { | 
|  | fprintf(stderr, "error: Failed to seek to offset of file.\n"); | 
|  | return false; | 
|  | } | 
|  | if (!CopyFileToFile(fd_.get(), dst_fd, entry.data_length)) { | 
|  | fprintf(stderr, "error: Failed write contents.\n"); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ArchiveReader::GetDirectoryEntryByIndex(uint64_t index, DirectoryTableEntry* entry) const { | 
|  | if (index >= directory_table_.size()) | 
|  | return false; | 
|  | *entry = directory_table_[index]; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ArchiveReader::GetDirectoryEntryByPath(std::string_view archive_path, | 
|  | DirectoryTableEntry* entry) const { | 
|  | uint64_t index = 0; | 
|  | return GetDirectoryIndexByPath(archive_path, &index) && GetDirectoryEntryByIndex(index, entry); | 
|  | } | 
|  |  | 
|  | bool ArchiveReader::GetDirectoryIndexByPath(std::string_view archive_path, uint64_t* index) const { | 
|  | PathComparator comparator; | 
|  | comparator.reader = this; | 
|  |  | 
|  | auto it = | 
|  | std::lower_bound(directory_table_.begin(), directory_table_.end(), archive_path, comparator); | 
|  | if (it == directory_table_.end() || GetPathView(*it) != archive_path) | 
|  | return false; | 
|  | *index = it - directory_table_.begin(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | fbl::unique_fd ArchiveReader::TakeFileDescriptor() { return std::move(fd_); } | 
|  |  | 
|  | std::string_view ArchiveReader::GetPathView(const DirectoryTableEntry& entry) const { | 
|  | return std::string_view(path_data_.data() + entry.name_offset, entry.name_length); | 
|  | } | 
|  |  | 
|  | bool ArchiveReader::ReadIndex() { | 
|  | if (lseek(fd_.get(), 0, SEEK_SET) < 0) { | 
|  | fprintf(stderr, "error: Failed to seek to beginning of archive.\n"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | IndexChunk index_chunk; | 
|  | if (!ReadObject(fd_.get(), &index_chunk)) { | 
|  | fprintf(stderr, "error: Failed read index chunk. Is this file an archive?\n"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (index_chunk.magic != kMagic) { | 
|  | fprintf(stderr, "error: Index chunk missing magic. Is this file an archive?\n"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (index_chunk.length % sizeof(IndexEntry) != 0 || | 
|  | index_chunk.length > std::numeric_limits<uint64_t>::max() - sizeof(IndexChunk)) { | 
|  | fprintf(stderr, "error: Invalid index chunk length.\n"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | index_.resize(index_chunk.length / sizeof(IndexEntry)); | 
|  | if (!ReadVector(fd_.get(), &index_)) { | 
|  | fprintf(stderr, "error: Failed to read contents of index chunk.\n"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | uint64_t next_offset = sizeof(IndexChunk) + index_chunk.length; | 
|  | for (const auto& entry : index_) { | 
|  | if (entry.offset != next_offset) { | 
|  | fprintf(stderr, "error: Chunk at offset %" PRIu64 " not tightly packed.\n", entry.offset); | 
|  | return false; | 
|  | } | 
|  | if (entry.length % 8 != 0) { | 
|  | fprintf(stderr, "error: Chunk length %" PRIu64 " not aligned to 8 byte boundary.\n", | 
|  | entry.length); | 
|  | return false; | 
|  | } | 
|  | if (entry.length > std::numeric_limits<uint64_t>::max() - entry.offset) { | 
|  | fprintf(stderr, "error: Chunk length %" PRIu64 " overflowed total archive size.\n", | 
|  | entry.length); | 
|  | return false; | 
|  | } | 
|  | next_offset = entry.offset + entry.length; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ArchiveReader::ReadDirectory() { | 
|  | const IndexEntry* dir_entry = GetIndexEntry(kDirType); | 
|  | if (!dir_entry) { | 
|  | fprintf(stderr, "error: Cannot find directory chunk.\n"); | 
|  | return false; | 
|  | } | 
|  | if (dir_entry->length % sizeof(DirectoryTableEntry) != 0) { | 
|  | fprintf(stderr, "error: Invalid directory chunk length: %" PRIu64 ".\n", dir_entry->length); | 
|  | return false; | 
|  | } | 
|  | uint64_t file_count = dir_entry->length / sizeof(DirectoryTableEntry); | 
|  | directory_table_.resize(file_count); | 
|  |  | 
|  | if (lseek(fd_.get(), dir_entry->offset, SEEK_SET) < 0) { | 
|  | fprintf(stderr, "error: Failed to seek to directory chunk.\n"); | 
|  | return false; | 
|  | } | 
|  | if (!ReadVector(fd_.get(), &directory_table_)) { | 
|  | fprintf(stderr, "error: Failed to read directory table.\n"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const IndexEntry* dirnames_entry = GetIndexEntry(kDirnamesType); | 
|  | if (!dirnames_entry) { | 
|  | fprintf(stderr, "error: Cannot find directory names chunk.\n"); | 
|  | return false; | 
|  | } | 
|  | path_data_.resize(dirnames_entry->length); | 
|  |  | 
|  | if (lseek(fd_.get(), dirnames_entry->offset, SEEK_SET) < 0) { | 
|  | fprintf(stderr, "error: Failed to seek to directory names chunk.\n"); | 
|  | return false; | 
|  | } | 
|  | if (!ReadVector(fd_.get(), &path_data_)) { | 
|  | fprintf(stderr, "error: Failed to read directory names.\n"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | const IndexEntry* ArchiveReader::GetIndexEntry(uint64_t type) const { | 
|  | for (auto& entry : index_) { | 
|  | if (entry.type == type) | 
|  | return &entry; | 
|  | } | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | }  // namespace archive |