//
// 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
