| // |
| // Copyright (C) 2021 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 "update_engine/payload_consumer/cow_writer_file_descriptor.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include <base/logging.h> |
| |
| #include "update_engine/common/utils.h" |
| #include "update_engine/payload_consumer/file_descriptor.h" |
| |
| namespace chromeos_update_engine { |
| |
| CowWriterFileDescriptor::CowWriterFileDescriptor( |
| std::unique_ptr<android::snapshot::ICowWriter> cow_writer, |
| std::unique_ptr<FileDescriptor> cow_reader, |
| const std::optional<std::string>& source_device) |
| : cow_writer_(std::move(cow_writer)), |
| cow_reader_(std::move(cow_reader)), |
| source_device_(source_device) { |
| CHECK_NE(cow_writer_, nullptr); |
| CHECK_NE(cow_reader_, nullptr); |
| } |
| |
| bool CowWriterFileDescriptor::Open(const char* path, int flags, mode_t mode) { |
| LOG(ERROR) << "CowWriterFileDescriptor doesn't support Open()"; |
| return false; |
| } |
| bool CowWriterFileDescriptor::Open(const char* path, int flags) { |
| LOG(ERROR) << "CowWriterFileDescriptor doesn't support Open()"; |
| return false; |
| } |
| |
| ssize_t CowWriterFileDescriptor::Read(void* buf, size_t count) { |
| if (dirty_) { |
| // OK, CowReader provides a snapshot view of what the cow contains. Which |
| // means any writes happened after opening a CowReader isn't visible to |
| // that CowReader. Therefore, we re-open CowReader whenever we attempt a |
| // read after write. This does incur an overhead everytime you read after |
| // write. |
| // The usage of |dirty_| flag to coordinate re-open is a very coarse grained |
| // checked. This implementation has suboptimal performance. For better |
| // performance, keep track of blocks which are overwritten, and only re-open |
| // if reading a dirty block. |
| // TODO(b/173432386) Implement finer grained dirty checks |
| const auto offset = cow_reader_->Seek(0, SEEK_CUR); |
| cow_reader_.reset(); |
| if (!cow_writer_->Finalize()) { |
| LOG(ERROR) << "Failed to Finalize() cow writer"; |
| return -1; |
| } |
| cow_reader_ = cow_writer_->OpenFileDescriptor(source_device_); |
| if (cow_reader_ == nullptr) { |
| LOG(ERROR) |
| << "Failed to re-open cow file descriptor after writing to COW"; |
| return -1; |
| } |
| const auto pos = cow_reader_->Seek(offset, SEEK_SET); |
| if (pos != offset) { |
| LOG(ERROR) << "Failed to seek to previous position after re-opening cow " |
| "reader, expected " |
| << offset << " actual: " << pos; |
| return -1; |
| } |
| dirty_ = false; |
| } |
| return cow_reader_->Read(buf, count); |
| } |
| |
| ssize_t CowWriterFileDescriptor::Write(const void* buf, size_t count) { |
| auto offset = cow_reader_->Seek(0, SEEK_CUR); |
| CHECK_EQ(offset % cow_writer_->GetBlockSize(), 0); |
| auto success = cow_writer_->AddRawBlocks( |
| offset / cow_writer_->GetBlockSize(), buf, count); |
| if (success) { |
| if (cow_reader_->Seek(count, SEEK_CUR) < 0) { |
| return -1; |
| } |
| dirty_ = true; |
| return count; |
| } |
| return -1; |
| } |
| |
| off64_t CowWriterFileDescriptor::Seek(const off64_t offset, int whence) { |
| return cow_reader_->Seek(offset, whence); |
| } |
| |
| uint64_t CowWriterFileDescriptor::BlockDevSize() { |
| LOG(ERROR) << "CowWriterFileDescriptor doesn't support BlockDevSize()"; |
| return 0; |
| } |
| |
| bool CowWriterFileDescriptor::BlkIoctl(int request, |
| uint64_t start, |
| uint64_t length, |
| int* result) { |
| LOG(ERROR) << "CowWriterFileDescriptor doesn't support BlkIoctl()"; |
| return false; |
| } |
| |
| bool CowWriterFileDescriptor::Flush() { |
| // CowWriter already automatilly flushes, no need to do anything. |
| return true; |
| } |
| |
| bool CowWriterFileDescriptor::Close() { |
| if (cow_writer_) { |
| // b/186196758 |
| // When calling |
| // InitializeAppend(kEndOfInstall), the SnapshotWriter only reads up to the |
| // given label. But OpenReader() completely disregards the resume label and |
| // reads all ops. Therefore, update_engine sees the verity data. However, |
| // when calling SnapshotWriter::Finalize(), data after resume label are |
| // discarded, therefore verity data is gone. To prevent phantom reads, don't |
| // call Finalize() unless we actually write something. |
| if (dirty_) { |
| TEST_AND_RETURN_FALSE(cow_writer_->Finalize()); |
| } |
| cow_writer_ = nullptr; |
| } |
| if (cow_reader_) { |
| TEST_AND_RETURN_FALSE(cow_reader_->Close()); |
| cow_reader_ = nullptr; |
| } |
| return true; |
| } |
| |
| bool CowWriterFileDescriptor::IsSettingErrno() { |
| return false; |
| } |
| |
| bool CowWriterFileDescriptor::IsOpen() { |
| return cow_writer_ != nullptr && cow_reader_ != nullptr; |
| } |
| |
| CowWriterFileDescriptor::~CowWriterFileDescriptor() { |
| Close(); |
| } |
| |
| } // namespace chromeos_update_engine |