356 lines
9.8 KiB
C++
356 lines
9.8 KiB
C++
//
|
|
// 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 "snapshot_reader.h"
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <ext4_utils/ext4_utils.h>
|
|
|
|
namespace android {
|
|
namespace snapshot {
|
|
|
|
using android::base::borrowed_fd;
|
|
|
|
// Not supported.
|
|
bool ReadOnlyFileDescriptor::Open(const char*, int, mode_t) {
|
|
errno = EINVAL;
|
|
return false;
|
|
}
|
|
|
|
bool ReadOnlyFileDescriptor::Open(const char*, int) {
|
|
errno = EINVAL;
|
|
return false;
|
|
}
|
|
|
|
ssize_t ReadOnlyFileDescriptor::Write(const void*, size_t) {
|
|
errno = EINVAL;
|
|
return false;
|
|
}
|
|
|
|
bool ReadOnlyFileDescriptor::BlkIoctl(int, uint64_t, uint64_t, int*) {
|
|
errno = EINVAL;
|
|
return false;
|
|
}
|
|
|
|
ReadFdFileDescriptor::ReadFdFileDescriptor(android::base::unique_fd&& fd) : fd_(std::move(fd)) {}
|
|
|
|
ssize_t ReadFdFileDescriptor::Read(void* buf, size_t count) {
|
|
return read(fd_.get(), buf, count);
|
|
}
|
|
|
|
off64_t ReadFdFileDescriptor::Seek(off64_t offset, int whence) {
|
|
return lseek(fd_.get(), offset, whence);
|
|
}
|
|
|
|
uint64_t ReadFdFileDescriptor::BlockDevSize() {
|
|
return get_block_device_size(fd_.get());
|
|
}
|
|
|
|
bool ReadFdFileDescriptor::Close() {
|
|
fd_ = {};
|
|
return true;
|
|
}
|
|
|
|
bool ReadFdFileDescriptor::IsSettingErrno() {
|
|
return true;
|
|
}
|
|
|
|
bool ReadFdFileDescriptor::IsOpen() {
|
|
return fd_ >= 0;
|
|
}
|
|
|
|
bool ReadFdFileDescriptor::Flush() {
|
|
return true;
|
|
}
|
|
|
|
bool CompressedSnapshotReader::SetCow(std::unique_ptr<CowReader>&& cow) {
|
|
cow_ = std::move(cow);
|
|
|
|
CowHeader header;
|
|
if (!cow_->GetHeader(&header)) {
|
|
return false;
|
|
}
|
|
block_size_ = header.block_size;
|
|
|
|
// Populate the operation map.
|
|
op_iter_ = cow_->GetOpIter();
|
|
while (!op_iter_->Done()) {
|
|
const CowOperation* op = &op_iter_->Get();
|
|
if (IsMetadataOp(*op)) {
|
|
op_iter_->Next();
|
|
continue;
|
|
}
|
|
if (op->new_block >= ops_.size()) {
|
|
ops_.resize(op->new_block + 1, nullptr);
|
|
}
|
|
ops_[op->new_block] = op;
|
|
op_iter_->Next();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CompressedSnapshotReader::SetSourceDevice(const std::string& source_device) {
|
|
source_device_ = {source_device};
|
|
}
|
|
|
|
void CompressedSnapshotReader::SetBlockDeviceSize(uint64_t block_device_size) {
|
|
block_device_size_ = block_device_size;
|
|
}
|
|
|
|
borrowed_fd CompressedSnapshotReader::GetSourceFd() {
|
|
if (source_fd_ < 0) {
|
|
if (!source_device_) {
|
|
LOG(ERROR) << "CompressedSnapshotReader needs source device, but none was set";
|
|
errno = EINVAL;
|
|
return {-1};
|
|
}
|
|
source_fd_.reset(open(source_device_->c_str(), O_RDONLY | O_CLOEXEC));
|
|
if (source_fd_ < 0) {
|
|
PLOG(ERROR) << "open " << *source_device_;
|
|
return {-1};
|
|
}
|
|
}
|
|
return source_fd_;
|
|
}
|
|
|
|
class MemoryByteSink : public IByteSink {
|
|
public:
|
|
MemoryByteSink(void* buf, size_t count) {
|
|
buf_ = reinterpret_cast<uint8_t*>(buf);
|
|
pos_ = buf_;
|
|
end_ = buf_ + count;
|
|
}
|
|
|
|
void* GetBuffer(size_t requested, size_t* actual) override {
|
|
*actual = std::min(remaining(), requested);
|
|
if (!*actual) {
|
|
return nullptr;
|
|
}
|
|
|
|
uint8_t* start = pos_;
|
|
pos_ += *actual;
|
|
return start;
|
|
}
|
|
|
|
bool ReturnData(void*, size_t) override { return true; }
|
|
|
|
uint8_t* buf() const { return buf_; }
|
|
uint8_t* pos() const { return pos_; }
|
|
size_t remaining() const { return end_ - pos_; }
|
|
|
|
private:
|
|
uint8_t* buf_;
|
|
uint8_t* pos_;
|
|
uint8_t* end_;
|
|
};
|
|
|
|
ssize_t CompressedSnapshotReader::Read(void* buf, size_t count) {
|
|
// Find the start and end chunks, inclusive.
|
|
uint64_t start_chunk = offset_ / block_size_;
|
|
uint64_t end_chunk = (offset_ + count - 1) / block_size_;
|
|
|
|
// Chop off the first N bytes if the position is not block-aligned.
|
|
size_t start_offset = offset_ % block_size_;
|
|
|
|
MemoryByteSink sink(buf, count);
|
|
|
|
size_t initial_bytes = std::min(block_size_ - start_offset, sink.remaining());
|
|
ssize_t rv = ReadBlock(start_chunk, &sink, start_offset, initial_bytes);
|
|
if (rv < 0) {
|
|
return -1;
|
|
}
|
|
offset_ += rv;
|
|
|
|
for (uint64_t chunk = start_chunk + 1; chunk < end_chunk; chunk++) {
|
|
ssize_t rv = ReadBlock(chunk, &sink, 0);
|
|
if (rv < 0) {
|
|
return -1;
|
|
}
|
|
offset_ += rv;
|
|
}
|
|
|
|
if (sink.remaining()) {
|
|
ssize_t rv = ReadBlock(end_chunk, &sink, 0, {sink.remaining()});
|
|
if (rv < 0) {
|
|
return -1;
|
|
}
|
|
offset_ += rv;
|
|
}
|
|
|
|
errno = 0;
|
|
|
|
DCHECK(sink.pos() - sink.buf() == count);
|
|
return count;
|
|
}
|
|
|
|
// Discard the first N bytes of a sink request, or any excess bytes.
|
|
class PartialSink : public MemoryByteSink {
|
|
public:
|
|
PartialSink(void* buffer, size_t size, size_t ignore_start)
|
|
: MemoryByteSink(buffer, size), ignore_start_(ignore_start) {}
|
|
|
|
void* GetBuffer(size_t requested, size_t* actual) override {
|
|
// Throw away the first N bytes if needed.
|
|
if (ignore_start_) {
|
|
*actual = std::min({requested, ignore_start_, sizeof(discard_)});
|
|
ignore_start_ -= *actual;
|
|
return discard_;
|
|
}
|
|
// Throw away any excess bytes if needed.
|
|
if (remaining() == 0) {
|
|
*actual = std::min(requested, sizeof(discard_));
|
|
return discard_;
|
|
}
|
|
return MemoryByteSink::GetBuffer(requested, actual);
|
|
}
|
|
|
|
private:
|
|
size_t ignore_start_;
|
|
char discard_[BLOCK_SZ];
|
|
};
|
|
|
|
ssize_t CompressedSnapshotReader::ReadBlock(uint64_t chunk, IByteSink* sink, size_t start_offset,
|
|
const std::optional<uint64_t>& max_bytes) {
|
|
size_t bytes_to_read = block_size_;
|
|
if (max_bytes) {
|
|
bytes_to_read = *max_bytes;
|
|
}
|
|
|
|
// The offset is relative to the chunk; we should be reading no more than
|
|
// one chunk.
|
|
CHECK(start_offset + bytes_to_read <= block_size_);
|
|
|
|
const CowOperation* op = nullptr;
|
|
if (chunk < ops_.size()) {
|
|
op = ops_[chunk];
|
|
}
|
|
|
|
size_t actual;
|
|
void* buffer = sink->GetBuffer(bytes_to_read, &actual);
|
|
if (!buffer || actual < bytes_to_read) {
|
|
// This should never happen unless we calculated the read size wrong
|
|
// somewhere. MemoryByteSink always fulfills the entire requested
|
|
// region unless there's not enough buffer remaining.
|
|
LOG(ERROR) << "Asked for buffer of size " << bytes_to_read << ", got " << actual;
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if (!op || op->type == kCowCopyOp) {
|
|
borrowed_fd fd = GetSourceFd();
|
|
if (fd < 0) {
|
|
// GetSourceFd sets errno.
|
|
return -1;
|
|
}
|
|
|
|
if (op) {
|
|
chunk = op->source;
|
|
}
|
|
|
|
off64_t offset = (chunk * block_size_) + start_offset;
|
|
if (!android::base::ReadFullyAtOffset(fd, buffer, bytes_to_read, offset)) {
|
|
PLOG(ERROR) << "read " << *source_device_;
|
|
// ReadFullyAtOffset sets errno.
|
|
return -1;
|
|
}
|
|
} else if (op->type == kCowZeroOp) {
|
|
memset(buffer, 0, bytes_to_read);
|
|
} else if (op->type == kCowReplaceOp) {
|
|
PartialSink partial_sink(buffer, bytes_to_read, start_offset);
|
|
if (!cow_->ReadData(*op, &partial_sink)) {
|
|
LOG(ERROR) << "CompressedSnapshotReader failed to read replace op";
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
} else if (op->type == kCowXorOp) {
|
|
borrowed_fd fd = GetSourceFd();
|
|
if (fd < 0) {
|
|
// GetSourceFd sets errno.
|
|
return -1;
|
|
}
|
|
|
|
off64_t offset = op->source + start_offset;
|
|
char data[BLOCK_SZ];
|
|
if (!android::base::ReadFullyAtOffset(fd, &data, bytes_to_read, offset)) {
|
|
PLOG(ERROR) << "read " << *source_device_;
|
|
// ReadFullyAtOffset sets errno.
|
|
return -1;
|
|
}
|
|
PartialSink partial_sink(buffer, bytes_to_read, start_offset);
|
|
if (!cow_->ReadData(*op, &partial_sink)) {
|
|
LOG(ERROR) << "CompressedSnapshotReader failed to read xor op";
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
for (size_t i = 0; i < bytes_to_read; i++) {
|
|
((char*)buffer)[i] ^= data[i];
|
|
}
|
|
} else {
|
|
LOG(ERROR) << "CompressedSnapshotReader unknown op type: " << uint32_t(op->type);
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
// MemoryByteSink doesn't do anything in ReturnBuffer, so don't bother calling it.
|
|
return bytes_to_read;
|
|
}
|
|
|
|
off64_t CompressedSnapshotReader::Seek(off64_t offset, int whence) {
|
|
switch (whence) {
|
|
case SEEK_SET:
|
|
offset_ = offset;
|
|
break;
|
|
case SEEK_END:
|
|
offset_ = static_cast<off64_t>(block_device_size_) + offset;
|
|
break;
|
|
case SEEK_CUR:
|
|
offset_ += offset;
|
|
break;
|
|
default:
|
|
LOG(ERROR) << "Unrecognized seek whence: " << whence;
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
return offset_;
|
|
}
|
|
|
|
uint64_t CompressedSnapshotReader::BlockDevSize() {
|
|
return block_device_size_;
|
|
}
|
|
|
|
bool CompressedSnapshotReader::Close() {
|
|
cow_ = nullptr;
|
|
source_fd_ = {};
|
|
return true;
|
|
}
|
|
|
|
bool CompressedSnapshotReader::IsSettingErrno() {
|
|
return true;
|
|
}
|
|
|
|
bool CompressedSnapshotReader::IsOpen() {
|
|
return cow_ != nullptr;
|
|
}
|
|
|
|
bool CompressedSnapshotReader::Flush() {
|
|
return true;
|
|
}
|
|
|
|
} // namespace snapshot
|
|
} // namespace android
|