libutil++: Add freebsd::fd_up class to manage file descriptors

This class aims to provide a std::unique_ptr<>-like interface in that
it assumes exclusive ownership of a file descriptor.  The descriptor
is closed when the object is destroyed.

Sponsored by:	Chelsio Communications
Pull Request:	https://github.com/freebsd/freebsd-src/pull/1794
This commit is contained in:
John Baldwin 2025-08-04 15:38:07 -04:00
parent 1ee9931d6e
commit 937e92b5f7
3 changed files with 150 additions and 0 deletions

View file

@ -6,6 +6,7 @@ SRCS= stringf.cc
MAN+= freebsd::FILE_up.3 \
freebsd::addrinfo_up.3 \
freebsd::fd_up.3 \
freebsd::malloc_up.3 \
freebsd::stringf.3

View file

@ -0,0 +1,78 @@
.\"
.\" SPDX-License-Identifier: BSD-2-Clause
.\"
.\" Copyright (c) 2025 Chelsio Communications, Inc.
.\" Written by: John Baldwin <jhb@FreeBSD.org>
.\"
.Dd July 31, 2025
.Dt FREEBSD::STRINGF 3
.Os
.Sh NAME
.Nm freebsd::fd_up
.Nd own a file descriptor
.Sh LIBRARY
.Lb libutil++
.Sh SYNOPSIS
.In libutil++.hh
.Pp
.Vt class freebsd::fd_up
{
.Bd -ragged -offset indent
.Fn fd_up
.Fn fd_up "int fd"
.Fn fd_up "fd_up &&other"
.Fn ~fd_up
.Ft int
.Fn get
.Ft int
.Fn release
.Ft void
.Fn reset "int newfd = -1"
.Ft "fd_up &"
.Fn operator= "fd_up &&other"
.Ft "fd_up &"
.Fn operator= "int fd"
.Fn "explicit operator bool"
.Fn "operator int"
.Ed
};
.Sh DESCRIPTION
Each instance of this class owns a file descriptor.
This class is patterned on std::unique_ptr,
but instead of owning a pointer to an object,
this class owns a file descriptor.
The currently-owned file descriptor is disposed by invoking
.Xr close 2
when an instance of this class is destroyed.
The currently-owned file descriptor is also disposed if it is replaced by the
.Fn reset
method or assignment operators.
.Pp
The
.Fn get
method returns the current file descriptor value while retaining ownership.
.Pp
The
.Fn release
method relinquishes ownership of the current file descriptor and returns the
value of the previously-owned file descriptor.
.Pp
The explicit
.Vt bool
conversion operator permits testing the validity of an object.
The operator returns true if the instance owns a valid file descriptor.
.Pp
The implicit
.Vt int
conversion operator permits passing an instance of this class directly as
an argument to existing functions which expect a file descriptor.
.Sh EXAMPLES
.Bd -literal -offset indent
freebsd::fd_up fd(open("/dev/null", O_RDWR));
if (!fd)
err(1, "open");
write(fd, "test", 4);
// `fd' is implicitly closed on destruction
.Ed
.Sh SEE ALSO
.Xr close 2

View file

@ -9,6 +9,7 @@
#define __LIBUTILPP_HH__
#include <netdb.h>
#include <unistd.h>
#include <cstdarg>
#include <cstdio>
@ -43,6 +44,76 @@ namespace freebsd {
using addrinfo_up = std::unique_ptr<addrinfo, freeaddrinfo_deleter>;
/*
* This class is intended to function similar to unique_ptr<>,
* but it contains a file descriptor rather than a pointer to
* an object. On destruction the descriptor is closed via
* close(2).
*
* Similar to unique_ptr<>, release() returns ownership of the
* file descriptor to the caller. reset() closes the current
* file descriptor and takes ownership of a new one. A move
* constructor permits ownership to be transferred via
* std::move(). An integer file descriptor can be assigned
* directly which is equivalent to calling reset().
*
* An explicit bool conversion operator permits testing this
* class in logical expressions. It returns true if it
* contains a valid descriptor.
*
* An implicit int conversion operator returns the underlying
* file descriptor allowing objects of this type to be passed
* directly to APIs such as connect(), listen(), etc.
*/
class fd_up {
public:
fd_up() : fd(-1) {}
fd_up(int fd) : fd(fd) {}
fd_up(fd_up &&other) : fd(other.release()) {}
fd_up(fd_up const &) = delete;
~fd_up() { reset(); }
int get() const { return (fd); }
int release()
{
int oldfd = fd;
fd = -1;
return (oldfd);
}
void reset(int newfd = -1)
{
if (fd >= 0)
close(fd);
fd = newfd;
}
fd_up &operator=(fd_up &&other) noexcept
{
if (this == &other)
return *this;
reset(other.release());
return *this;
}
fd_up &operator=(fd_up const &) = delete;
fd_up &operator=(int newfd)
{
reset(newfd);
return *this;
}
explicit operator bool() const { return fd >= 0; }
operator int() const { return fd; }
private:
int fd;
};
/*
* malloc_up<T> is a std::unique_ptr<> which uses free() to
* destroy the wrapped pointer. This can be used to wrap