// Copyright 2023 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/fusebox/fusebox_copy_to_fd.h"
#include "base/posix/eintr_wrapper.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "chrome/browser/ash/fusebox/fusebox_errno.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "storage/browser/file_system/file_stream_reader.h"
namespace fusebox {
namespace {
using Expected = base::expected<base::ScopedFD, int>;
Expected WriteToScopedFDOffTheIOThread(base::ScopedFD scoped_fd,
scoped_refptr<net::IOBuffer> buffer,
int length) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
int fd = scoped_fd.get();
char* ptr = buffer->data();
if (HANDLE_EINTR(write(fd, ptr, static_cast<size_t>(length))) == -1) {
return base::unexpected(errno);
}
return std::move(scoped_fd);
}
class FDCopier {
public:
FDCopier(scoped_refptr<storage::FileSystemContext> fs_context,
const storage::FileSystemURL& fs_url,
base::ScopedFD scoped_fd,
base::OnceCallback<void(Expected)> callback);
void CallRead();
private:
// The int is *not* a POSIX error code. If negative, it's a net error code.
// If non-negative, it's the number of bytes read.
void OnRead(int result);
void OnWrite(Expected result);
void Finish(Expected result);
static constexpr size_t kBufferLen = 65536;
std::unique_ptr<storage::FileStreamReader> fs_reader_;
scoped_refptr<net::IOBuffer> buffer_;
base::ScopedFD scoped_fd_;
base::OnceCallback<void(Expected)> callback_;
};
FDCopier::FDCopier(scoped_refptr<storage::FileSystemContext> fs_context,
const storage::FileSystemURL& fs_url,
base::ScopedFD scoped_fd,
base::OnceCallback<void(Expected)> callback)
: fs_reader_(fs_context->CreateFileStreamReader(fs_url,
0,
INT64_MAX,
base::Time())),
buffer_(base::MakeRefCounted<net::IOBufferWithSize>(kBufferLen)),
scoped_fd_(std::move(scoped_fd)),
callback_(std::move(callback)) {}
void FDCopier::CallRead() {
int read_result =
fs_reader_->Read(buffer_.get(), kBufferLen,
base::BindOnce(&FDCopier::OnRead,
// base::Unretained is safe because |this|
// isn't deleted until Finish is called.
base::Unretained(this)));
if (read_result != net::ERR_IO_PENDING) { // The read was synchronous.
OnRead(read_result);
}
}
void FDCopier::OnRead(int result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (result == 0) {
Finish(std::move(scoped_fd_));
return;
} else if (result < 0) {
Finish(base::unexpected(NetErrorToErrno(result)));
return;
}
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&WriteToScopedFDOffTheIOThread, std::move(scoped_fd_),
buffer_, result),
base::BindOnce(&FDCopier::OnWrite,
// base::Unretained is safe because |this| isn't deleted
// until Finish is called.
base::Unretained(this)));
}
void FDCopier::OnWrite(Expected result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (!result.has_value()) {
Finish(std::move(result));
return;
}
scoped_fd_ = std::move(result.value());
CallRead();
}
void FDCopier::Finish(Expected result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
std::move(callback_).Run(std::move(result));
delete this;
}
} // namespace
void CopyToFileDescriptor(scoped_refptr<storage::FileSystemContext> fs_context,
const storage::FileSystemURL& src_fs_url,
base::ScopedFD dst_scoped_fd,
base::OnceCallback<void(Expected)> callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// This new-ly created object is deleted in FDCopier::Finish.
FDCopier* copier =
new FDCopier(std::move(fs_context), src_fs_url, std::move(dst_scoped_fd),
std::move(callback));
copier->CallRead();
}
} // namespace fusebox