// // 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 #include #include 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&& 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(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& 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(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