blob: f9e56ab5aa8d1790fce3ba999c166e0078f8a23a [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/linux/fd_string_writer.h"
#include <unistd.h>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/numerics/safe_conversions.h"
#include "base/posix/eintr_wrapper.h"
#include "base/posix/safe_strerror.h"
#include "base/strings/strcat.h"
namespace remoting {
FdStringWriter::~FdStringWriter() = default;
// static
std::unique_ptr<FdStringWriter> FdStringWriter::Write(std::string data,
base::ScopedFD fd,
Callback callback) {
return base::WrapUnique(
new FdStringWriter(std::move(data), std::move(fd), std::move(callback)));
}
FdStringWriter::FdStringWriter(std::string data,
base::ScopedFD fd,
Callback callback)
: fd_(std::move(fd)),
callback_(std::move(callback)),
write_data_(std::move(data)) {
write_remaining_ = write_data_;
// Unretained is safe because no callback will occur after the controller is
// destroyed.
fd_watcher_ = base::FileDescriptorWatcher::WatchWritable(
fd_.get(), base::BindRepeating(&FdStringWriter::OnFdWritable,
base::Unretained(this)));
}
void FdStringWriter::OnFdWritable() {
while (true) {
// Check at the start of the loop, in case the caller asked to write an
// empty string.
if (write_remaining_.empty()) {
// All data was written successfully. Reset the watcher to ensure no more
// callbacks occur.
fd_watcher_.reset();
std::move(callback_).Run(base::ok());
return;
}
ssize_t bytes_written = HANDLE_EINTR(
write(fd_.get(), write_remaining_.data(), write_remaining_.size()));
if (bytes_written < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// Write was blocked, so return without doing anything. This method will
// be called again when the FD becomes writable.
return;
}
// A permanent (non-blocking-related) write error occurred. Reset the
// watcher to ensure no more callbacks occur.
fd_watcher_.reset();
std::move(callback_).Run(base::unexpected(Loggable(
FROM_HERE,
base::StrCat({"write() failed: ", base::safe_strerror(errno)}))));
return;
}
// bytes_written >= 0. POSIX write() should never return exactly 0 (unless
// it was asked to write 0 bytes, but this is ruled out by the check at the
// start).
// write() should never return more bytes than it was asked to write. Crash
// here instead of risking undefined behavior on the string_view.
size_t bytes_written_as_size_t = base::checked_cast<size_t>(bytes_written);
CHECK_LE(bytes_written_as_size_t, write_remaining_.size());
// At least some of the data was successfully written, so update the write
// position. Then continue writing until all data is written or the stream
// is blocked.
write_remaining_.remove_prefix(bytes_written_as_size_t);
}
}
} // namespace remoting