blob: f96f174c5291fcef6e39ba101686e886818bd20e [file] [log] [blame]
//
// Copyright (C) 2020 The Android Open Source Project
//
// 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 <sys/types.h>
#include <unistd.h>
#include <limits>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <brotli/encode.h>
#include <libsnapshot/cow_reader.h>
#include <libsnapshot/cow_writer.h>
#include <zlib.h>
namespace android {
namespace snapshot {
static_assert(sizeof(off_t) == sizeof(uint64_t));
using android::base::borrowed_fd;
using android::base::unique_fd;
bool ICowWriter::AddCopy(uint64_t new_block, uint64_t old_block) {
if (!ValidateNewBlock(new_block)) {
return false;
}
return EmitCopy(new_block, old_block);
}
bool ICowWriter::AddRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
if (size % options_.block_size != 0) {
LOG(ERROR) << "AddRawBlocks: size " << size << " is not a multiple of "
<< options_.block_size;
return false;
}
uint64_t num_blocks = size / options_.block_size;
uint64_t last_block = new_block_start + num_blocks - 1;
if (!ValidateNewBlock(last_block)) {
return false;
}
return EmitRawBlocks(new_block_start, data, size);
}
bool ICowWriter::AddZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
uint64_t last_block = new_block_start + num_blocks - 1;
if (!ValidateNewBlock(last_block)) {
return false;
}
return EmitZeroBlocks(new_block_start, num_blocks);
}
bool ICowWriter::ValidateNewBlock(uint64_t new_block) {
if (options_.max_blocks && new_block >= options_.max_blocks.value()) {
LOG(ERROR) << "New block " << new_block << " exceeds maximum block count "
<< options_.max_blocks.value();
return false;
}
return true;
}
CowWriter::CowWriter(const CowOptions& options) : ICowWriter(options), fd_(-1) {
SetupHeaders();
}
void CowWriter::SetupHeaders() {
header_ = {};
header_.magic = kCowMagicNumber;
header_.major_version = kCowVersionMajor;
header_.minor_version = kCowVersionMinor;
header_.header_size = sizeof(CowHeader);
header_.block_size = options_.block_size;
}
bool CowWriter::ParseOptions() {
if (options_.compression == "gz") {
compression_ = kCowCompressGz;
} else if (options_.compression == "brotli") {
compression_ = kCowCompressBrotli;
} else if (options_.compression == "none") {
compression_ = kCowCompressNone;
} else if (!options_.compression.empty()) {
LOG(ERROR) << "unrecognized compression: " << options_.compression;
return false;
}
return true;
}
bool CowWriter::Initialize(unique_fd&& fd, OpenMode mode) {
owned_fd_ = std::move(fd);
return Initialize(borrowed_fd{owned_fd_}, mode);
}
bool CowWriter::Initialize(borrowed_fd fd, OpenMode mode) {
fd_ = fd;
if (!ParseOptions()) {
return false;
}
switch (mode) {
case OpenMode::WRITE:
return OpenForWrite();
case OpenMode::APPEND:
return OpenForAppend();
default:
LOG(ERROR) << "Unknown open mode in CowWriter";
return false;
}
}
bool CowWriter::OpenForWrite() {
// This limitation is tied to the data field size in CowOperation.
if (header_.block_size > std::numeric_limits<uint16_t>::max()) {
LOG(ERROR) << "Block size is too large";
return false;
}
if (lseek(fd_.get(), 0, SEEK_SET) < 0) {
PLOG(ERROR) << "lseek failed";
return false;
}
// Headers are not complete, but this ensures the file is at the right
// position.
if (!android::base::WriteFully(fd_, &header_, sizeof(header_))) {
PLOG(ERROR) << "write failed";
return false;
}
header_.ops_offset = header_.header_size;
return true;
}
bool CowWriter::OpenForAppend() {
auto reader = std::make_unique<CowReader>();
if (!reader->Parse(fd_) || !reader->GetHeader(&header_)) {
return false;
}
options_.block_size = header_.block_size;
// Reset this, since we're going to reimport all operations.
header_.num_ops = 0;
auto iter = reader->GetOpIter();
while (!iter->Done()) {
auto& op = iter->Get();
AddOperation(op);
iter->Next();
}
// Free reader so we own the descriptor position again.
reader = nullptr;
// Seek to the end of the data section.
if (lseek(fd_.get(), header_.ops_offset, SEEK_SET) < 0) {
PLOG(ERROR) << "lseek failed";
return false;
}
return true;
}
bool CowWriter::EmitCopy(uint64_t new_block, uint64_t old_block) {
CowOperation op = {};
op.type = kCowCopyOp;
op.new_block = new_block;
op.source = old_block;
AddOperation(op);
return true;
}
bool CowWriter::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
uint64_t pos;
if (!GetDataPos(&pos)) {
return false;
}
const uint8_t* iter = reinterpret_cast<const uint8_t*>(data);
for (size_t i = 0; i < size / header_.block_size; i++) {
CowOperation op = {};
op.type = kCowReplaceOp;
op.new_block = new_block_start + i;
op.source = pos;
if (compression_) {
auto data = Compress(iter, header_.block_size);
if (data.empty()) {
PLOG(ERROR) << "AddRawBlocks: compression failed";
return false;
}
if (data.size() > std::numeric_limits<uint16_t>::max()) {
LOG(ERROR) << "Compressed block is too large: " << data.size() << " bytes";
return false;
}
if (!WriteRawData(data.data(), data.size())) {
PLOG(ERROR) << "AddRawBlocks: write failed";
return false;
}
op.compression = compression_;
op.data_length = static_cast<uint16_t>(data.size());
pos += data.size();
} else {
op.data_length = static_cast<uint16_t>(header_.block_size);
pos += header_.block_size;
}
AddOperation(op);
iter += header_.block_size;
}
if (!compression_ && !WriteRawData(data, size)) {
PLOG(ERROR) << "AddRawBlocks: write failed";
return false;
}
return true;
}
bool CowWriter::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
for (uint64_t i = 0; i < num_blocks; i++) {
CowOperation op = {};
op.type = kCowZeroOp;
op.new_block = new_block_start + i;
op.source = 0;
AddOperation(op);
}
return true;
}
std::basic_string<uint8_t> CowWriter::Compress(const void* data, size_t length) {
switch (compression_) {
case kCowCompressGz: {
auto bound = compressBound(length);
auto buffer = std::make_unique<uint8_t[]>(bound);
uLongf dest_len = bound;
auto rv = compress2(buffer.get(), &dest_len, reinterpret_cast<const Bytef*>(data),
length, Z_BEST_COMPRESSION);
if (rv != Z_OK) {
LOG(ERROR) << "compress2 returned: " << rv;
return {};
}
return std::basic_string<uint8_t>(buffer.get(), dest_len);
}
case kCowCompressBrotli: {
auto bound = BrotliEncoderMaxCompressedSize(length);
if (!bound) {
LOG(ERROR) << "BrotliEncoderMaxCompressedSize returned 0";
return {};
}
auto buffer = std::make_unique<uint8_t[]>(bound);
size_t encoded_size = bound;
auto rv = BrotliEncoderCompress(
BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, length,
reinterpret_cast<const uint8_t*>(data), &encoded_size, buffer.get());
if (!rv) {
LOG(ERROR) << "BrotliEncoderCompress failed";
return {};
}
return std::basic_string<uint8_t>(buffer.get(), encoded_size);
}
default:
LOG(ERROR) << "unhandled compression type: " << compression_;
break;
}
return {};
}
// TODO: Fix compilation issues when linking libcrypto library
// when snapuserd is compiled as part of ramdisk.
static void SHA256(const void*, size_t, uint8_t[]) {
#if 0
SHA256_CTX c;
SHA256_Init(&c);
SHA256_Update(&c, data, length);
SHA256_Final(out, &c);
#endif
}
bool CowWriter::Flush() {
header_.ops_size = ops_.size();
memset(header_.ops_checksum, 0, sizeof(uint8_t) * 32);
memset(header_.header_checksum, 0, sizeof(uint8_t) * 32);
SHA256(ops_.data(), ops_.size(), header_.ops_checksum);
SHA256(&header_, sizeof(header_), header_.header_checksum);
if (lseek(fd_.get(), 0, SEEK_SET)) {
PLOG(ERROR) << "lseek failed";
return false;
}
if (!android::base::WriteFully(fd_, &header_, sizeof(header_))) {
PLOG(ERROR) << "write header failed";
return false;
}
if (lseek(fd_.get(), header_.ops_offset, SEEK_SET) < 0) {
PLOG(ERROR) << "lseek ops failed";
return false;
}
if (!WriteFully(fd_, ops_.data(), ops_.size())) {
PLOG(ERROR) << "write ops failed";
return false;
}
// Re-position for any subsequent writes.
if (lseek(fd_.get(), header_.ops_offset, SEEK_SET) < 0) {
PLOG(ERROR) << "lseek ops failed";
return false;
}
return true;
}
uint64_t CowWriter::GetCowSize() {
return header_.ops_offset + header_.num_ops * sizeof(CowOperation);
}
bool CowWriter::GetDataPos(uint64_t* pos) {
off_t offs = lseek(fd_.get(), 0, SEEK_CUR);
if (offs < 0) {
PLOG(ERROR) << "lseek failed";
return false;
}
*pos = offs;
return true;
}
void CowWriter::AddOperation(const CowOperation& op) {
header_.num_ops++;
ops_.insert(ops_.size(), reinterpret_cast<const uint8_t*>(&op), sizeof(op));
}
bool CowWriter::WriteRawData(const void* data, size_t size) {
if (!android::base::WriteFully(fd_, data, size)) {
return false;
}
header_.ops_offset += size;
return true;
}
} // namespace snapshot
} // namespace android