910e62b5创建于 1月15日历史提交
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/fileapi/copy_from_fd.h"

#include "base/posix/eintr_wrapper.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/io_buffer.h"

namespace ash {

namespace {

int ReadFromScopedFDOffTheIOThread(int fd,
                                   scoped_refptr<net::IOBuffer> buffer) {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);

  char* ptr = buffer->data();
  int len = buffer->size();
  ssize_t n = HANDLE_EINTR(read(fd, ptr, len));
  return (n >= 0) ? static_cast<int>(n)
                  : static_cast<int>(net::Error::ERR_FAILED);
}

class FromFDCopier {
 public:
  FromFDCopier(base::ScopedFD src_scoped_fd,
               std::unique_ptr<storage::FileStreamWriter> dst_fs_writer,
               storage::FlushPolicy dst_flush_policy,
               CopyFromFileDescriptorCallback callback);

  void CallRead();

 private:
  // For the "int result" arguments: If negative, it's a net error code. If
  // non-negative, it's the number of bytes read.
  void OnRead(int result);
  void CallWrite(int result);
  void MaybeFlush(int result);
  void Finish(int result);

  static constexpr size_t kBufferLen = 65536;

  base::ScopedFD scoped_fd_;
  std::unique_ptr<storage::FileStreamWriter> fs_writer_;
  storage::FlushPolicy dst_flush_policy_;
  CopyFromFileDescriptorCallback callback_;

  scoped_refptr<net::IOBuffer> buffer_;
  scoped_refptr<net::DrainableIOBuffer> drainable_buffer_;
};

FromFDCopier::FromFDCopier(
    base::ScopedFD src_scoped_fd,
    std::unique_ptr<storage::FileStreamWriter> dst_fs_writer,
    storage::FlushPolicy dst_flush_policy,
    CopyFromFileDescriptorCallback callback)
    : scoped_fd_(std::move(src_scoped_fd)),
      fs_writer_(std::move(dst_fs_writer)),
      dst_flush_policy_(dst_flush_policy),
      callback_(std::move(callback)),
      buffer_(base::MakeRefCounted<net::IOBufferWithSize>(kBufferLen)) {}

void FromFDCopier::CallRead() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::MayBlock()},
      base::BindOnce(&ReadFromScopedFDOffTheIOThread, scoped_fd_.get(),
                     buffer_),
      base::BindOnce(&FromFDCopier::OnRead,
                     // base::Unretained is safe because |this| isn't deleted
                     // until Finish is called.
                     base::Unretained(this)));
}

void FromFDCopier::OnRead(int result) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  CHECK(!drainable_buffer_);

  if (result <= 0) {
    MaybeFlush(result);
    return;
  }
  drainable_buffer_ = base::MakeRefCounted<net::DrainableIOBuffer>(
      buffer_, static_cast<size_t>(result));
  CallWrite(0);
}

void FromFDCopier::CallWrite(int result) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  CHECK(drainable_buffer_);

  while (true) {
    if (result < 0) {
      MaybeFlush(result);
      return;
    }

    drainable_buffer_->DidConsume(result);
    if (drainable_buffer_->BytesRemaining() <= 0) {
      drainable_buffer_.reset();
      CallRead();
      return;
    }

    result = fs_writer_->Write(
        drainable_buffer_.get(), drainable_buffer_->BytesRemaining(),
        base::BindOnce(&FromFDCopier::CallWrite,
                       // base::Unretained is safe because |this|
                       // isn't deleted until Finish is called.
                       base::Unretained(this)));
    if (result == net::ERR_IO_PENDING) {  // The write was asynchronous.
      return;
    }
  }
}

void FromFDCopier::MaybeFlush(int result) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  if ((result >= 0) &&
      (dst_flush_policy_ == storage::FlushPolicy::FLUSH_ON_COMPLETION)) {
    result = fs_writer_->Flush(
        storage::FlushMode::kEndOfFile,
        base::BindOnce(&FromFDCopier::Finish,
                       // base::Unretained is safe because |this|
                       // isn't deleted until Finish is called.
                       base::Unretained(this)));
    if (result == net::ERR_IO_PENDING) {  // The flush was asynchronous.
      return;
    }
  }

  Finish(result);
}

void FromFDCopier::Finish(int result) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  std::move(callback_).Run(
      std::move(scoped_fd_), std::move(fs_writer_),
      (result >= 0) ? net::OK : static_cast<net::Error>(result));

  delete this;
}

}  // namespace

void CopyFromFileDescriptor(
    base::ScopedFD src_scoped_fd,
    std::unique_ptr<storage::FileStreamWriter> dst_fs_writer,
    storage::FlushPolicy dst_flush_policy,
    CopyFromFileDescriptorCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  if (!callback) {
    return;
  } else if (!src_scoped_fd.is_valid() || !dst_fs_writer) {
    std::move(callback).Run(std::move(src_scoped_fd), std::move(dst_fs_writer),
                            net::Error::ERR_INVALID_ARGUMENT);
    return;
  }

  // This new-ly created object is deleted in FromFDCopier::Finish.
  FromFDCopier* copier =
      new FromFDCopier(std::move(src_scoped_fd), std::move(dst_fs_writer),
                       dst_flush_policy, std::move(callback));

  copier->CallRead();
}

}  // namespace ash