#include #include #include #include #include #include #include #include #include namespace Kernel { struct InodeRefPtrHash { BAN::hash_t operator()(const BAN::RefPtr& inode) { return BAN::hash()(inode.ptr()); } }; static Mutex s_fcntl_lock_mutex; static ThreadBlocker s_fcntl_lock_thread_blocker; static BAN::HashMap, BAN::Vector, InodeRefPtrHash> s_fcntl_locks; static BAN::ErrorOr fcntl_lock(BAN::RefPtr inode, int cmd, struct flock& flock); OpenFileDescriptorSet::OpenFileDescriptorSet(const Credentials& credentials) : m_credentials(credentials) { } OpenFileDescriptorSet::~OpenFileDescriptorSet() { close_all(); } OpenFileDescriptorSet& OpenFileDescriptorSet::operator=(OpenFileDescriptorSet&& other) { LockGuard _(m_mutex); for (size_t i = 0; i < m_open_files.size(); i++) m_open_files[i] = BAN::move(other.m_open_files[i]); return *this; } BAN::ErrorOr OpenFileDescriptorSet::clone_from(const OpenFileDescriptorSet& other) { LockGuard _(m_mutex); close_all(); for (int fd = 0; fd < static_cast(other.m_open_files.size()); fd++) { if (other.validate_fd(fd).is_error()) continue; auto& open_file = m_open_files[fd]; open_file = other.m_open_files[fd]; open_file->file.inode->on_clone(open_file->status_flags); } for (size_t i = 0; i < m_cloexec_files.size(); i++) m_cloexec_files[i] = other.m_cloexec_files[i]; return {}; } BAN::ErrorOr OpenFileDescriptorSet::open(VirtualFileSystem::File&& file, int flags) { ASSERT(file.inode); if (flags & ~(O_ACCMODE | O_NOFOLLOW | O_APPEND | O_TRUNC | O_CLOEXEC | O_TTY_INIT | O_NOCTTY | O_DIRECTORY | O_CREAT | O_EXCL | O_NONBLOCK)) return BAN::Error::from_errno(ENOTSUP); if ((flags & O_ACCMODE) != O_RDWR && __builtin_popcount(flags & O_ACCMODE) != 1) return BAN::Error::from_errno(EINVAL); if ((flags & O_DIRECTORY) && !file.inode->mode().ifdir()) return BAN::Error::from_errno(ENOTDIR); if ((flags & O_TRUNC) && (flags & O_WRONLY) && file.inode->mode().ifreg()) TRY(file.inode->truncate(0)); constexpr int status_mask = O_APPEND | O_DSYNC | O_NONBLOCK | O_RSYNC | O_SYNC | O_ACCMODE; LockGuard _(m_mutex); const int fd = TRY(get_free_fd()); m_open_files[fd] = TRY(BAN::RefPtr::create(BAN::move(file), 0, flags & status_mask)); if (flags & O_CLOEXEC) add_cloexec(fd); return fd; } BAN::ErrorOr OpenFileDescriptorSet::open(BAN::StringView absolute_path, int flags) { return open(TRY(VirtualFileSystem::get().file_from_absolute_path(Process::current().root_file().inode, m_credentials, absolute_path, flags)), flags); } struct SocketInfo { Socket::Domain domain; Socket::Type type; int status_flags; bool cloexec; }; static BAN::ErrorOr parse_socket_info(int domain, int type, int protocol) { SocketInfo info; bool valid_protocol = true; switch (domain) { case AF_INET: info.domain = Socket::Domain::INET; break; case AF_INET6: info.domain = Socket::Domain::INET6; break; case AF_UNIX: info.domain = Socket::Domain::UNIX; valid_protocol = false; break; default: return BAN::Error::from_errno(EPROTOTYPE); } info.status_flags = 0; info.cloexec = false; if (type & SOCK_NONBLOCK) info.status_flags |= O_NONBLOCK; if (type & SOCK_CLOEXEC) info.cloexec = true; type &= ~(SOCK_NONBLOCK | SOCK_CLOEXEC); switch (type) { case SOCK_STREAM: info.type = Socket::Type::STREAM; if (protocol != IPPROTO_TCP) valid_protocol = false; break; case SOCK_DGRAM: info.type = Socket::Type::DGRAM; if (protocol != IPPROTO_UDP) valid_protocol = false; break; case SOCK_SEQPACKET: info.type = Socket::Type::SEQPACKET; valid_protocol = false; break; default: return BAN::Error::from_errno(EPROTOTYPE); } if (protocol && !valid_protocol) return BAN::Error::from_errno(EPROTONOSUPPORT); return info; } BAN::ErrorOr OpenFileDescriptorSet::socket(int domain, int type, int protocol) { auto sock_info = TRY(parse_socket_info(domain, type, protocol)); auto socket = TRY(NetworkManager::get().create_socket(sock_info.domain, sock_info.type, 0777, m_credentials.euid(), m_credentials.egid())); auto socket_sv = ""_sv; if (sock_info.domain == Socket::Domain::UNIX) socket_sv = ""_sv; else if (sock_info.type == Socket::Type::STREAM) socket_sv = ""; else if (sock_info.type == Socket::Type::DGRAM) socket_sv = ""; LockGuard _(m_mutex); const int fd = TRY(get_free_fd()); m_open_files[fd] = TRY(BAN::RefPtr::create(VirtualFileSystem::File(socket, socket_sv), 0, O_RDWR | sock_info.status_flags)); if (sock_info.cloexec) add_cloexec(fd); return fd; } BAN::ErrorOr OpenFileDescriptorSet::socketpair(int domain, int type, int protocol, int socket_vector[2]) { auto sock_info = TRY(parse_socket_info(domain, type, protocol)); auto socket1 = TRY(NetworkManager::get().create_socket(sock_info.domain, sock_info.type, 0600, m_credentials.euid(), m_credentials.egid())); auto socket2 = TRY(NetworkManager::get().create_socket(sock_info.domain, sock_info.type, 0600, m_credentials.euid(), m_credentials.egid())); TRY(NetworkManager::get().connect_sockets(sock_info.domain, socket1, socket2)); LockGuard _(m_mutex); TRY(get_free_fd_pair(socket_vector)); m_open_files[socket_vector[0]] = TRY(BAN::RefPtr::create(VirtualFileSystem::File(socket1, ""_sv), 0, O_RDWR | sock_info.status_flags)); m_open_files[socket_vector[1]] = TRY(BAN::RefPtr::create(VirtualFileSystem::File(socket2, ""_sv), 0, O_RDWR | sock_info.status_flags)); if (sock_info.cloexec) { add_cloexec(socket_vector[0]); add_cloexec(socket_vector[1]); } return {}; } BAN::ErrorOr OpenFileDescriptorSet::pipe(int fds[2]) { LockGuard _(m_mutex); TRY(get_free_fd_pair(fds)); auto pipe = TRY(Pipe::create(m_credentials)); m_open_files[fds[0]] = TRY(BAN::RefPtr::create(VirtualFileSystem::File(pipe, ""_sv), 0, O_RDONLY)); m_open_files[fds[1]] = TRY(BAN::RefPtr::create(VirtualFileSystem::File(pipe, ""_sv), 0, O_WRONLY)); ASSERT(!is_cloexec(fds[0])); ASSERT(!is_cloexec(fds[1])); return {}; } BAN::ErrorOr OpenFileDescriptorSet::dup2(int fildes, int fildes2) { if (fildes2 < 0 || fildes2 >= (int)m_open_files.size()) return BAN::Error::from_errno(EBADF); LockGuard _(m_mutex); TRY(validate_fd(fildes)); if (fildes == fildes2) return fildes; (void)close(fildes2); auto& open_file = m_open_files[fildes2]; open_file = m_open_files[fildes]; open_file->file.inode->on_clone(open_file->status_flags); ASSERT(!is_cloexec(fildes2)); return fildes2; } BAN::ErrorOr OpenFileDescriptorSet::fcntl(int fd, int cmd, uintptr_t extra) { if (cmd == F_SETLK || cmd == F_SETLKW || cmd == F_GETLK) { struct flock flock; TRY(Process::current().read_from_user(reinterpret_cast(extra), &flock, sizeof(struct flock))); flock.l_pid = Process::current().pid(); BAN::RefPtr inode; { LockGuard _(m_mutex); TRY(validate_fd(fd)); inode = m_open_files[fd]->file.inode; switch (flock.l_whence) { case SEEK_SET: break; case SEEK_CUR: if (BAN::Math::will_addition_overflow(flock.l_start, m_open_files[fd]->offset)) return BAN::Error::from_errno(EOVERFLOW); flock.l_start += m_open_files[fd]->offset; break; case SEEK_END: if (BAN::Math::will_addition_overflow(flock.l_start, inode->size())) return BAN::Error::from_errno(EOVERFLOW); flock.l_start += inode->size(); break; default: return BAN::Error::from_errno(EINVAL); } if (BAN::Math::will_addition_overflow(flock.l_start, flock.l_len)) return BAN::Error::from_errno(EOVERFLOW); if (flock.l_len < 0) { flock.l_start += flock.l_len; if (flock.l_len == BAN::numeric_limits::min()) return BAN::Error::from_errno(EOVERFLOW); flock.l_len = -flock.l_len; } flock.l_whence = SEEK_SET; } auto lock_ret = fcntl_lock(inode, cmd, flock); if (lock_ret.is_error()) { if (lock_ret.error().get_error_code() == ENOMEM) return BAN::Error::from_errno(ENOLCK); return lock_ret.release_error(); } if (cmd == F_GETLK) TRY(Process::current().write_to_user(reinterpret_cast(extra), &flock, sizeof(struct flock))); return 0; } LockGuard _(m_mutex); TRY(validate_fd(fd)); switch (cmd) { case F_DUPFD: case F_DUPFD_CLOEXEC: { const int new_fd = TRY(get_free_fd()); auto& open_file = m_open_files[new_fd]; open_file = m_open_files[fd]; open_file->file.inode->on_clone(open_file->status_flags); if (cmd == F_DUPFD_CLOEXEC) add_cloexec(new_fd); return new_fd; } case F_GETFD: return is_cloexec(fd) ? O_CLOEXEC : 0; case F_SETFD: if (extra & FD_CLOEXEC) add_cloexec(fd); else remove_cloexec(fd); return 0; case F_GETFL: return m_open_files[fd]->status_flags; case F_SETFL: extra &= O_APPEND | O_DSYNC | O_NONBLOCK | O_RSYNC | O_SYNC; m_open_files[fd]->status_flags &= O_ACCMODE; m_open_files[fd]->status_flags |= extra; return 0; default: break; } dwarnln("TODO: fcntl command {}", cmd); return BAN::Error::from_errno(ENOTSUP); } BAN::ErrorOr OpenFileDescriptorSet::seek(int fd, off_t offset, int whence) { LockGuard _(m_mutex); TRY(validate_fd(fd)); off_t base_offset; switch (whence) { case SEEK_SET: base_offset = 0; break; case SEEK_CUR: base_offset = m_open_files[fd]->offset; break; case SEEK_END: base_offset = m_open_files[fd]->file.inode->size(); break; default: return BAN::Error::from_errno(EINVAL); } const off_t new_offset = base_offset + offset; if (new_offset < 0) return BAN::Error::from_errno(EINVAL); m_open_files[fd]->offset = new_offset; return new_offset; } BAN::ErrorOr OpenFileDescriptorSet::tell(int fd) const { LockGuard _(m_mutex); TRY(validate_fd(fd)); return m_open_files[fd]->offset; } BAN::ErrorOr OpenFileDescriptorSet::truncate(int fd, off_t length) { LockGuard _(m_mutex); TRY(validate_fd(fd)); return m_open_files[fd]->file.inode->truncate(length); } BAN::ErrorOr OpenFileDescriptorSet::close(int fd) { LockGuard _(m_mutex); TRY(validate_fd(fd)); auto& open_file = m_open_files[fd]; if (auto& flock = open_file->flock; Thread::current().has_process() && flock.lockers.contains(Process::current().pid())) { flock.lockers.remove(Process::current().pid()); if (flock.lockers.empty()) { flock.locked = false; flock.thread_blocker.unblock(); } } { LockGuard _(s_fcntl_lock_mutex); if (auto it = s_fcntl_locks.find(open_file->file.inode); it != s_fcntl_locks.end()) { auto& flocks = it->value; for (size_t i = 0; i < flocks.size(); i++) if (flocks[i].l_pid == Process::current().pid()) flocks.remove(i--); s_fcntl_lock_thread_blocker.unblock(); } } open_file->file.inode->on_close(open_file->status_flags); open_file = {}; remove_cloexec(fd); return {}; } void OpenFileDescriptorSet::close_all() { LockGuard _(m_mutex); for (int fd = 0; fd < static_cast(m_open_files.size()); fd++) (void)close(fd); } void OpenFileDescriptorSet::close_cloexec() { LockGuard _(m_mutex); for (int fd = 0; fd < static_cast(m_open_files.size()); fd++) if (is_cloexec(fd)) (void)close(fd); } bool OpenFileDescriptorSet::is_cloexec(int fd) { return m_cloexec_files[fd / 32] & (1u << (fd % 32)); } void OpenFileDescriptorSet::add_cloexec(int fd) { m_cloexec_files[fd / 32] |= 1u << (fd % 32); } void OpenFileDescriptorSet::remove_cloexec(int fd) { m_cloexec_files[fd / 32] &= ~(1u << (fd % 32)); } static BAN::ErrorOr fcntl_lock(BAN::RefPtr inode, int cmd, struct flock& flock) { if (!inode->mode().ifreg()) return BAN::Error::from_errno(EINVAL); LockGuard _(s_fcntl_lock_mutex); static constexpr auto locks_overlap = [](struct flock a, struct flock b) -> bool { if (a.l_len == 0 && b.l_len == 0) return true; if (a.l_len == 0 && a.l_start < b.l_start + b.l_len) return true; if (b.l_len == 0 && b.l_start < a.l_start + a.l_len) return true; if (a.l_start + a.l_len <= b.l_start) return false; if (b.l_start + b.l_len <= a.l_start) return false; return true; }; const auto lock_preventer = [flock](BAN::Span locks) -> struct flock* { for (auto& lock : locks) { if (lock.l_pid == flock.l_pid) continue; if (!locks_overlap(lock, flock)) continue; if (lock.l_type == F_RDLCK && flock.l_type == F_RDLCK) continue; return &lock; } return nullptr; }; switch (cmd) { case F_GETLK: { auto it = s_fcntl_locks.find(inode); if (it == s_fcntl_locks.end()) flock.l_type = F_UNLCK; else if (auto* preventer = lock_preventer(it->value.span())) flock = *preventer; else flock.l_type = F_UNLCK; return {}; } case F_SETLK: case F_SETLKW: { if (flock.l_type == F_UNLCK) { auto it = s_fcntl_locks.find(inode); if (it == s_fcntl_locks.end()) return {}; auto& flocks = it->value; for (size_t i = 0; i < flocks.size(); i++) { if (flocks[i].l_pid != flock.l_pid) continue; if (!locks_overlap(flocks[i], flock)) continue; struct flock new_flocks[2]; size_t new_flock_count { 0 }; if (flocks[i].l_start < flock.l_start) { const off_t flock_len = flocks[i].l_len ? flocks[i].l_len : inode->size(); new_flocks[new_flock_count] = flock; new_flocks[new_flock_count].l_len = flocks[i].l_start + flock_len - flock.l_start; new_flock_count++; } if (flock.l_len == 0) ; else if (flocks[i].l_len == 0) { new_flocks[new_flock_count] = flock; new_flocks[new_flock_count].l_start = flock.l_start + flock.l_len; new_flocks[new_flock_count].l_len = 0; new_flock_count++; } else if (flocks[i].l_start + flocks[i].l_len > flock.l_start + flock.l_len) { new_flocks[new_flock_count] = flock; new_flocks[new_flock_count].l_start = flock.l_start + flock.l_len; new_flocks[new_flock_count].l_len = (flocks[i].l_start + flocks[i].l_len) - (flock.l_start + flock.l_len); new_flock_count++; } switch (new_flock_count) { case 0: flocks.remove(i--); break; case 1: flocks[i] = new_flocks[0]; break; case 2: TRY(flocks.insert(i + 1, new_flocks[1])); flocks[i++] = new_flocks[0]; break; } } if (flocks.empty()) s_fcntl_locks.remove(it); s_fcntl_lock_thread_blocker.unblock(); return {}; } else for (;;) { auto it = s_fcntl_locks.find(inode); if (it == s_fcntl_locks.end()) it = TRY(s_fcntl_locks.emplace(inode)); if (lock_preventer(it->value.span()) == nullptr) { TRY(it->value.push_back(flock)); return {}; } if (cmd == F_SETLK) return BAN::Error::from_errno(EAGAIN); TRY(Thread::current().block_or_eintr_indefinite(s_fcntl_lock_thread_blocker, &s_fcntl_lock_mutex)); } } default: ASSERT_NOT_REACHED(); } } BAN::ErrorOr OpenFileDescriptorSet::flock(int fd, int op) { const auto pid = Process::current().pid(); LockGuard _(m_mutex); for (;;) { TRY(validate_fd(fd)); auto& flock = m_open_files[fd]->flock; switch (op & ~LOCK_NB) { case LOCK_UN: flock.lockers.remove(pid); if (flock.lockers.empty()) { flock.locked = false; flock.thread_blocker.unblock(); } return {}; case LOCK_SH: if (!flock.locked) { TRY(flock.lockers.insert(pid)); flock.locked = true; flock.shared = true; return {}; } if (flock.shared) { TRY(flock.lockers.insert(pid)); return {}; } break; case LOCK_EX: if (!flock.locked) { TRY(flock.lockers.insert(pid)); flock.locked = true; flock.shared = false; return {}; } if (flock.lockers.contains(pid)) return {}; break; default: return BAN::Error::from_errno(EINVAL); } if (op & LOCK_NB) return BAN::Error::from_errno(EWOULDBLOCK); TRY(Thread::current().block_or_eintr_indefinite(flock.thread_blocker, &m_mutex)); } } BAN::ErrorOr OpenFileDescriptorSet::read(int fd, BAN::ByteSpan buffer) { BAN::RefPtr inode; bool is_nonblock; off_t offset; { LockGuard _(m_mutex); TRY(validate_fd(fd)); auto& open_file = m_open_files[fd]; if (!(open_file->status_flags & O_RDONLY)) return BAN::Error::from_errno(EBADF); inode = open_file->file.inode; is_nonblock = !!(open_file->status_flags & O_NONBLOCK); offset = open_file->offset; } if (inode->mode().ifsock()) { iovec iov { .iov_base = buffer.data(), .iov_len = buffer.size(), }; msghdr message { .msg_name = nullptr, .msg_namelen = 0, .msg_iov = &iov, .msg_iovlen = 1, .msg_control = nullptr, .msg_controllen = 0, .msg_flags = 0, }; return recvmsg(fd, message, 0); } size_t nread; { if (!inode->can_read() && inode->has_hungup()) return 0; // FIXME: race condition, pass flags to read if (is_nonblock && !inode->can_read()) return BAN::Error::from_errno(EAGAIN); nread = TRY(inode->read(offset, buffer)); } LockGuard _(m_mutex); // NOTE: race condition with offset, its UB per POSIX if (!validate_fd(fd).is_error()) m_open_files[fd]->offset = offset + nread; return nread; } BAN::ErrorOr OpenFileDescriptorSet::write(int fd, BAN::ConstByteSpan buffer) { BAN::RefPtr inode; bool is_nonblock; off_t offset; { LockGuard _(m_mutex); TRY(validate_fd(fd)); auto& open_file = m_open_files[fd]; if (!(open_file->status_flags & O_WRONLY)) return BAN::Error::from_errno(EBADF); inode = open_file->file.inode; is_nonblock = !!(open_file->status_flags & O_NONBLOCK); offset = (open_file->status_flags & O_APPEND) ? inode->size() : open_file->offset; } if (inode->mode().ifsock()) { iovec iov { .iov_base = const_cast(buffer.data()), .iov_len = buffer.size(), }; msghdr message { .msg_name = nullptr, .msg_namelen = 0, .msg_iov = &iov, .msg_iovlen = 1, .msg_control = nullptr, .msg_controllen = 0, .msg_flags = 0, }; return sendmsg(fd, message, 0); } size_t nwrite; { if (inode->has_error()) { Thread::current().add_signal(SIGPIPE, {}); return BAN::Error::from_errno(EPIPE); } if (is_nonblock && !inode->can_write()) return BAN::Error::from_errno(EAGAIN); // FIXME: race condition, pass flags to write nwrite = TRY(inode->write(offset, buffer)); } LockGuard _(m_mutex); // NOTE: race condition with offset, its UB per POSIX if (!validate_fd(fd).is_error()) m_open_files[fd]->offset = offset + nwrite; return nwrite; } BAN::ErrorOr OpenFileDescriptorSet::read_dir_entries(int fd, struct dirent* list, size_t list_len) { BAN::RefPtr inode; off_t offset; { LockGuard _(m_mutex); TRY(validate_fd(fd)); auto& open_file = m_open_files[fd]; if (!(open_file->status_flags & O_RDONLY)) return BAN::Error::from_errno(EACCES); inode = open_file->file.inode; offset = open_file->offset; } for (;;) { auto ret = inode->list_next_inodes(offset, list, list_len); if (ret.is_error() && ret.error().get_error_code() != ENODATA) return ret; offset++; if (ret.is_error()) continue; LockGuard _(m_mutex); // NOTE: race condition with offset, its UB per POSIX if (!validate_fd(fd).is_error()) m_open_files[fd]->offset = offset; return ret; } } BAN::ErrorOr OpenFileDescriptorSet::recvmsg(int fd, msghdr& message, int flags) { BAN::RefPtr inode; bool is_nonblock; { LockGuard _(m_mutex); TRY(validate_fd(fd)); auto& open_file = m_open_files[fd]; if (!open_file->file.inode->mode().ifsock()) return BAN::Error::from_errno(ENOTSOCK); inode = open_file->file.inode; is_nonblock = !!(open_file->status_flags & O_NONBLOCK); } if (is_nonblock && !inode->can_read()) return BAN::Error::from_errno(EAGAIN); return inode->recvmsg(message, flags); } BAN::ErrorOr OpenFileDescriptorSet::sendmsg(int fd, const msghdr& message, int flags) { BAN::RefPtr inode; bool is_nonblock; { LockGuard _(m_mutex); TRY(validate_fd(fd)); auto& open_file = m_open_files[fd]; if (!open_file->file.inode->mode().ifsock()) return BAN::Error::from_errno(ENOTSOCK); inode = open_file->file.inode; is_nonblock = !!(open_file->status_flags & O_NONBLOCK); } if (inode->has_hungup()) { if (!(flags & MSG_NOSIGNAL)) Thread::current().add_signal(SIGPIPE, {}); return BAN::Error::from_errno(EPIPE); } if (is_nonblock && !inode->can_write()) return BAN::Error::from_errno(EAGAIN); return inode->sendmsg(message, flags | (is_nonblock ? MSG_DONTWAIT : 0)); } BAN::ErrorOr OpenFileDescriptorSet::file_of(int fd) const { LockGuard _(m_mutex); TRY(validate_fd(fd)); return TRY(m_open_files[fd]->file.clone()); } BAN::ErrorOr OpenFileDescriptorSet::path_of(int fd) const { LockGuard _(m_mutex); TRY(validate_fd(fd)); BAN::String path; TRY(path.append(m_open_files[fd]->file.canonical_path)); return path; } BAN::ErrorOr> OpenFileDescriptorSet::inode_of(int fd) { LockGuard _(m_mutex); TRY(validate_fd(fd)); return m_open_files[fd]->file.inode; } BAN::ErrorOr OpenFileDescriptorSet::status_flags_of(int fd) const { LockGuard _(m_mutex); TRY(validate_fd(fd)); return m_open_files[fd]->status_flags; } BAN::ErrorOr OpenFileDescriptorSet::validate_fd(int fd) const { LockGuard _(m_mutex); if (fd < 0 || fd >= (int)m_open_files.size()) return BAN::Error::from_errno(EBADF); if (!m_open_files[fd]) return BAN::Error::from_errno(EBADF); return {}; } BAN::ErrorOr OpenFileDescriptorSet::get_free_fd() const { LockGuard _(m_mutex); for (int fd = 0; fd < (int)m_open_files.size(); fd++) if (!m_open_files[fd]) return fd; return BAN::Error::from_errno(EMFILE); } BAN::ErrorOr OpenFileDescriptorSet::get_free_fd_pair(int fds[2]) const { LockGuard _(m_mutex); size_t found = 0; for (int fd = 0; fd < (int)m_open_files.size(); fd++) { if (!m_open_files[fd]) fds[found++] = fd; if (found == 2) return {}; } return BAN::Error::from_errno(EMFILE); } using FDWrapper = OpenFileDescriptorSet::FDWrapper; FDWrapper::FDWrapper(BAN::RefPtr description) : m_description(description) { if (m_description) m_description->file.inode->on_clone(m_description->status_flags); } FDWrapper::~FDWrapper() { clear(); } FDWrapper& FDWrapper::operator=(const FDWrapper& other) { clear(); m_description = other.m_description; if (m_description) m_description->file.inode->on_clone(m_description->status_flags); return *this; } FDWrapper& FDWrapper::operator=(FDWrapper&& other) { clear(); m_description = BAN::move(other.m_description); return *this; } void FDWrapper::clear() { if (m_description) m_description->file.inode->on_close(m_description->status_flags); } BAN::ErrorOr OpenFileDescriptorSet::get_fd_wrapper(int fd) { LockGuard _(m_mutex); TRY(validate_fd(fd)); return FDWrapper { m_open_files[fd] }; } size_t OpenFileDescriptorSet::open_all_fd_wrappers(BAN::Span fd_wrappers) { LockGuard _(m_mutex); for (size_t i = 0; i < fd_wrappers.size(); i++) { auto fd_or_error = get_free_fd(); if (fd_or_error.is_error()) return i; const int fd = fd_or_error.release_value(); m_open_files[fd] = BAN::move(fd_wrappers[i].m_description); fd_wrappers[i].m_fd = fd; } return fd_wrappers.size(); } }