blob: 61cfd861a74bcafa6613609fce5662be58448434 [file] [log] [blame]
// 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/cpp/archive_writer.h"
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <limits>
#include <string>
#include <vector>
#include <fbl/unique_fd.h>
#include "src/lib/files/file_descriptor.h"
#include "src/sys/pkg/lib/far/cpp/alignment.h"
#include "src/sys/pkg/lib/far/cpp/file_operations.h"
#include "src/sys/pkg/lib/far/cpp/format.h"
namespace archive {
ArchiveWriter::ArchiveWriter() = default;
ArchiveWriter::~ArchiveWriter() = default;
bool ArchiveWriter::Add(ArchiveEntry entry) {
size_t size = entry.dst_path.size();
if (size > std::numeric_limits<uint16_t>::max())
return false;
if (size > std::numeric_limits<uint32_t>::max() - total_path_length_)
return false;
// TODO(abarth): Add more entry.dst_path validation.
dirty_ = true;
entries_.push_back(std::move(entry));
total_path_length_ += size;
return true;
}
bool ArchiveWriter::Write(int fd) {
if (dirty_) {
std::sort(entries_.begin(), entries_.end());
dirty_ = false;
}
if (HasDuplicateEntries())
return false;
if (lseek(fd, 0, SEEK_SET) < 0) {
fprintf(stderr, "error: Failed to seek to beginning of archive.\n");
return false;
}
uint64_t index_count = entries_.empty() ? 0 : 2;
uint64_t next_chunk = 0;
IndexChunk index;
index.length = index_count * sizeof(IndexEntry);
next_chunk += sizeof(IndexChunk) + index.length;
if (!WriteObject(fd, index)) {
fprintf(stderr, "error: Failed to write index chunk.\n");
return false;
}
if (entries_.empty())
return true; // No files to store in the archive.
IndexEntry dir_entry;
dir_entry.type = kDirType;
dir_entry.offset = next_chunk;
dir_entry.length = entries_.size() * sizeof(DirectoryTableEntry);
next_chunk += dir_entry.length;
if (!WriteObject(fd, dir_entry)) {
fprintf(stderr, "error: Failed to write directory index chunk.\n");
return false;
}
IndexEntry dirnames_entry;
dirnames_entry.type = kDirnamesType;
dirnames_entry.offset = next_chunk;
dirnames_entry.length = AlignTo8ByteBoundary(total_path_length_);
next_chunk += dirnames_entry.length;
if (!WriteObject(fd, dirnames_entry)) {
fprintf(stderr, "error: Failed to write directory names index chunk\n");
return false;
}
uint32_t name_offset = 0;
uint64_t data_offset = AlignToPage(next_chunk);
std::vector<DirectoryTableEntry> directory_table(entries_.size());
for (size_t i = 0; i < entries_.size(); ++i) {
const ArchiveEntry& entry = entries_[i];
DirectoryTableEntry& directory_entry = directory_table[i];
struct stat info;
if (stat(entry.src_path.c_str(), &info) != 0) {
fprintf(stderr, "error: Failed to read length of file: %s\n", entry.src_path.c_str());
return false;
}
uint64_t data_length = info.st_size;
if (data_length > std::numeric_limits<uint64_t>::max() - data_offset) {
fprintf(stderr, "error: File overflowed total archive size: %s\n", entry.src_path.c_str());
return false;
}
directory_entry.name_offset = name_offset;
directory_entry.name_length = entry.dst_path.size();
directory_entry.data_offset = data_offset;
directory_entry.data_length = data_length;
name_offset += directory_entry.name_length;
data_offset = AlignToPage(data_offset + data_length);
}
if (!WriteVector(fd, directory_table)) {
fprintf(stderr, "error: Failed to write directory table.\n");
return false;
}
std::vector<char> path_data(total_path_length_);
char* pos = path_data.data();
for (const auto& entry : entries_) {
memcpy(pos, entry.dst_path.data(), entry.dst_path.size());
pos += entry.dst_path.size();
}
if (!WriteVector(fd, path_data)) {
fprintf(stderr, "error: Failed to write path data.\n");
return false;
}
for (size_t i = 0; i < entries_.size(); ++i) {
const ArchiveEntry& entry = entries_[i];
const DirectoryTableEntry& directory_entry = directory_table[i];
if (lseek(fd, directory_entry.data_offset, SEEK_SET) < 0) {
fprintf(stderr, "error: Failed to seek to data offset.\n");
return false;
}
if (!CopyPathToFile(entry.src_path.c_str(), fd, directory_entry.data_length)) {
fprintf(stderr, "error: Failed to write file data: %s\n", entry.src_path.c_str());
return false;
}
}
if (!entries_.empty()) {
const DirectoryTableEntry& directory_entry = directory_table.back();
uint64_t end = directory_entry.data_offset + directory_entry.data_length;
if (ftruncate(fd, AlignToPage(end)) < 0) {
fprintf(stderr, "error: Failed to truncate archive to proper length.\n");
return false;
}
}
return true;
}
bool ArchiveWriter::HasDuplicateEntries() {
for (size_t i = 0; i + 1 < entries_.size(); ++i) {
if (entries_[i].dst_path == entries_[i + 1].dst_path) {
fprintf(stderr, "error: Archive has duplicate path: '%s'\n", entries_[i].dst_path.c_str());
return true;
}
}
return false;
}
} // namespace archive