blob: 4bcd6f23ab21459b92f0f43237a40b68e265ef11 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "archive_writer.h"
#include <filesystem>
#include <iostream>
#include "base/strings/string_number_conversions.h"
#include "tools/android/devil_util/archive_helper.h"
ArchiveWriter::ArchiveWriter(std::vector<ArchiveMember> members)
: members_(std::move(members)) {}
ArchiveWriter::~ArchiveWriter() = default;
void ArchiveWriter::WriteMagicBytes(char** output_buffer,
size_t* output_buffer_size) {
if (*output_buffer_size == 0) {
return;
}
if (magic_bytes_pos_ == kMagicBytesLength) {
return;
}
// Create a string containing the magic bytes that is exactly
// kMagicBytesLength characters long. Add \0 at the end if needed.
std::string magic_bytes_str = std::string(kDevilUtilArchiveV1MagicBytes);
std::string magic_bytes_padding(kMagicBytesLength - magic_bytes_str.length(),
'\0');
std::string magic_bytes_str_padded = magic_bytes_str + magic_bytes_padding;
uint64_t size_to_write = std::min(static_cast<uint64_t>(*output_buffer_size),
kMagicBytesLength - magic_bytes_pos_);
magic_bytes_str_padded.copy(*output_buffer, size_to_write, magic_bytes_pos_);
*output_buffer += size_to_write;
*output_buffer_size -= size_to_write;
magic_bytes_pos_ += size_to_write;
}
void ArchiveWriter::WriteCurMemberPathLength(char** output_buffer,
size_t* output_buffer_size) {
if (*output_buffer_size == 0) {
return;
}
if (cur_member_path_length_pos_ == kPathLengthSize) {
return;
}
// Create a string containing the path length that is exactly kPathLengthSize
// characters long. Add \0 at the end if needed.
size_t path_length = cur_member_path_.length();
std::string path_length_str = base::NumberToString(path_length);
if (path_length_str.length() > kPathLengthSize) {
std::cerr << "This archive path is way too long: " << cur_member_path_
<< std::endl;
exit(1);
}
std::string path_length_padding(kPathLengthSize - path_length_str.length(),
'\0');
std::string path_length_str_padded = path_length_str + path_length_padding;
uint64_t size_to_write =
std::min(static_cast<uint64_t>(*output_buffer_size),
kPathLengthSize - cur_member_path_length_pos_);
path_length_str_padded.copy(*output_buffer, size_to_write,
cur_member_path_length_pos_);
*output_buffer += size_to_write;
*output_buffer_size -= size_to_write;
cur_member_path_length_pos_ += size_to_write;
}
void ArchiveWriter::WriteCurMemberPath(char** output_buffer,
size_t* output_buffer_size) {
if (*output_buffer_size == 0) {
return;
}
uint64_t path_length = cur_member_path_.length();
if (cur_member_path_pos_ == path_length) {
return;
}
uint64_t size_to_write = std::min(static_cast<uint64_t>(*output_buffer_size),
path_length - cur_member_path_pos_);
cur_member_path_.copy(*output_buffer, size_to_write, cur_member_path_pos_);
*output_buffer += size_to_write;
*output_buffer_size -= size_to_write;
cur_member_path_pos_ += size_to_write;
}
void ArchiveWriter::WriteCurMemberContentLength(char** output_buffer,
size_t* output_buffer_size) {
if (*output_buffer_size == 0) {
return;
}
if (cur_member_content_length_pos_ == kContentLengthSize) {
return;
}
// Create a string containing the content length that is exactly
// kContentLengthSize characters long. Add \0 at the end if needed.
std::string content_length_str =
base::NumberToString(cur_member_content_length_);
if (content_length_str.length() > kContentLengthSize) {
std::cerr << "This file is way too large: "
<< members_[cur_member_index_].file_path_in_host << std::endl;
exit(1);
}
std::string content_length_padding(
kContentLengthSize - content_length_str.length(), '\0');
std::string content_length_str_padded =
content_length_str + content_length_padding;
uint64_t size_to_write =
std::min(static_cast<uint64_t>(*output_buffer_size),
kContentLengthSize - cur_member_content_length_pos_);
content_length_str_padded.copy(*output_buffer, size_to_write,
cur_member_content_length_pos_);
*output_buffer += size_to_write;
*output_buffer_size -= size_to_write;
cur_member_content_length_pos_ += size_to_write;
}
void ArchiveWriter::WriteCurMemberContent(char** output_buffer,
size_t* output_buffer_size) {
if (*output_buffer_size == 0) {
return;
}
if (cur_member_content_pos_ == cur_member_content_length_) {
return;
}
uint64_t size_to_write =
std::min(static_cast<uint64_t>(*output_buffer_size),
cur_member_content_length_ - cur_member_content_pos_);
cur_member_ifstream_.read(*output_buffer, size_to_write);
if (cur_member_ifstream_.fail()) {
std::cerr << "Failed to read the file at: "
<< members_[cur_member_index_].file_path_in_host << std::endl;
exit(1);
}
*output_buffer += size_to_write;
*output_buffer_size -= size_to_write;
cur_member_content_pos_ += size_to_write;
}
size_t ArchiveWriter::CreateArchiveStreaming(char* output_buffer,
size_t output_buffer_size) {
if (output_buffer_size == 0) {
return 0;
}
size_t output_buffer_size_original = output_buffer_size;
WriteMagicBytes(&output_buffer, &output_buffer_size);
if (magic_bytes_pos_ < kMagicBytesLength) {
return output_buffer_size_original;
}
for (; cur_member_index_ < members_.size(); ++cur_member_index_) {
// If we are processing a new member, reset all internal states.
if (start_next_member_) {
cur_member_path_ = members_[cur_member_index_].file_path_in_archive;
const std::string& path = members_[cur_member_index_].file_path_in_host;
cur_member_content_length_ = std::filesystem::file_size(path);
cur_member_ifstream_ = std::ifstream(path, std::ios::binary);
if (cur_member_ifstream_.fail()) {
std::cerr << "Failed to open the file at: " << path << std::endl;
exit(1);
}
cur_member_path_length_pos_ = 0;
cur_member_path_pos_ = 0;
cur_member_content_length_pos_ = 0;
cur_member_content_pos_ = 0;
start_next_member_ = false;
}
WriteCurMemberPathLength(&output_buffer, &output_buffer_size);
WriteCurMemberPath(&output_buffer, &output_buffer_size);
WriteCurMemberContentLength(&output_buffer, &output_buffer_size);
WriteCurMemberContent(&output_buffer, &output_buffer_size);
if (cur_member_content_pos_ == cur_member_content_length_ &&
cur_member_content_length_pos_ == kContentLengthSize &&
cur_member_path_pos_ == cur_member_path_.length() &&
cur_member_path_length_pos_ == kPathLengthSize) {
// We have finished processing the current member.
cur_member_ifstream_.close();
start_next_member_ = true;
} else {
// We have only partially processed the current member.
start_next_member_ = false;
return output_buffer_size_original;
}
}
// End the archive with a path length of 0.
if (start_next_member_) {
cur_member_path_ = "";
cur_member_path_length_pos_ = 0;
start_next_member_ = false;
}
WriteCurMemberPathLength(&output_buffer, &output_buffer_size);
if (cur_member_path_length_pos_ == kPathLengthSize) {
return output_buffer_size_original - output_buffer_size;
} else {
return output_buffer_size_original;
}
}