blob: 5e6ee1ea424a4119daf4904d76ba41e76329f2d1 [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/gdbus_fd_list.h"
#include <fcntl.h>
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include <glib.h>
#include <unistd.h>
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <utility>
#include <vector>
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/files/scoped_file.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/posix/safe_strerror.h"
#include "base/strings/strcat.h"
#include "base/types/expected.h"
#include "remoting/host/base/loggable.h"
#include "ui/base/glib/scoped_gobject.h"
namespace remoting {
namespace {
constexpr int kInvalidFd = -1;
void CloseFds(base::span<int> fds) {
for (int fd : fds) {
if (fd == kInvalidFd) {
continue;
}
int result = close(fd);
if (result == -1) {
if (errno == EBADF) {
LOG(ERROR) << "Attempting to close non-existent fd " << fd;
} else {
// Any other error still results in the fd getting closed.
PLOG(WARNING) << "Error while closing fd " << fd;
}
}
}
}
} // namespace
GDBusFdList::GDBusFdList() = default;
GDBusFdList::~GDBusFdList() {
CloseFds(fds_);
}
GDBusFdList::GDBusFdList(GDBusFdList&& other) {
fds_.swap(other.fds_);
}
GDBusFdList& GDBusFdList::operator=(GDBusFdList&& other) {
if (this != &other) {
CloseFds(fds_);
fds_.clear();
fds_.swap(other.fds_);
}
return *this;
}
GDBusFdList::Handle GDBusFdList::Insert(base::ScopedFD fd) {
std::size_t next_index = fds_.size();
// GUnixFDList requires all file descriptors to be close-on-exec.
fcntl(fd.get(), F_SETFD, FD_CLOEXEC);
fds_.push_back(fd.release());
return Handle{static_cast<int32_t>(next_index)};
}
base::expected<GDBusFdList::Handle, Loggable> GDBusFdList::InsertDup(int fd) {
int new_fd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
if (new_fd == -1) {
return base::unexpected(
Loggable(FROM_HERE, base::StrCat({"Error duplicating file descriptor: ",
base::safe_strerror(errno)})));
}
return base::ok(Insert(base::ScopedFD(new_fd)));
}
std::optional<int> GDBusFdList::Get(Handle handle) {
if (handle.index < 0 ||
static_cast<std::size_t>(handle.index) >= fds_.size()) {
return std::nullopt;
}
return fds_[handle.index];
}
SparseFdList GDBusFdList::MakeSparse() && {
return SparseFdList(std::move(fds_));
}
ScopedGObject<GUnixFDList> GDBusFdList::IntoGUnixFDList() && {
std::vector<int> fds(std::move(fds_));
return TakeGObject(g_unix_fd_list_new_from_array(fds.data(), fds.size()));
}
// static
GDBusFdList GDBusFdList::StealFromGUnixFDList(GUnixFDList* fd_list) {
gint length;
gint* fds = g_unix_fd_list_steal_fds(fd_list, &length);
GDBusFdList result;
// SAFETY: g_unix_fd_list_steal_fds() is guaranteed to return a non-null array
// with |length| elements.
result.fds_.insert(result.fds_.end(), fds, UNSAFE_BUFFERS(fds + length));
g_free(fds);
return result;
}
SparseFdList::SparseFdList() = default;
SparseFdList::~SparseFdList() {
CloseFds(fds_);
}
SparseFdList::SparseFdList(SparseFdList&&) = default;
SparseFdList& SparseFdList::operator=(SparseFdList&& other) {
if (this != &other) {
CloseFds(fds_);
fds_ = std::move(other.fds_);
}
return *this;
}
std::optional<int> SparseFdList::Get(Handle handle) {
if (handle.index < 0 ||
static_cast<std::size_t>(handle.index) >= fds_.size()) {
return std::nullopt;
}
int fd = fds_[handle.index];
if (fd == kInvalidFd) {
return std::nullopt;
}
return fd;
}
base::ScopedFD SparseFdList::Extract(Handle handle) {
auto fd = Get(handle);
if (fd.has_value()) {
fds_[handle.index] = kInvalidFd;
return base::ScopedFD(fd.value());
} else {
return {};
}
}
SparseFdList::SparseFdList(std::vector<int> fds) : fds_(std::move(fds)) {}
} // namespace remoting