libutil++: Add freebsd::pidfile wrapper class around struct pidfh

This class wraps the pidfile_* API from libutil.  The destructor calls
pidfile_remove() when an object is destroyed.  This class is similar
to std::unique_ptr<> in that it retains exclusive ownership of the
pidfh object.

In addition to release and reset methods, write, close, and fileno
methods are provided as wrappers for pidfile_*.

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 2bb9180bb5
commit 7be913e00d
5 changed files with 232 additions and 0 deletions

View file

@ -9,6 +9,7 @@ MAN+= freebsd::FILE_up.3 \
freebsd::fd_up.3 \
freebsd::malloc_up.3 \
freebsd::nvlist_up.3 \
freebsd::pidfile.3 \
freebsd::stringf.3
.include <src.opts.mk>

View file

@ -0,0 +1,110 @@
.\"
.\" 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::pidfile
.Nd own a PID file handle
.Sh LIBRARY
.Lb libutil++
.Sh SYNOPSIS
.In libutil++.hh
.Pp
.Vt class freebsd::pidfile
{
.Bd -ragged -offset indent
.Fn pidfile
.Fn pidfile "struct pidfh *pfh"
.Fn pidfile "pidfile &&other"
.Fn ~pidfile
.Ft struct pidfh *
.Fn release
.Ft void
.Fn reset "struct pidfh *newpfh = nullptr"
.Ft int
.Fn write
.Ft int
.Fn close
.Ft int
.Fn fileno
.Ft "pidfile &"
.Fn operator= "pidfile &&other"
.Ft "pidfile &"
.Fn operator= "struct pidfh *pfh"
.Fn "explicit operator bool"
.Ed
};
.Sh DESCRIPTION
Each instance of this class owns a PID file handle created by
.Xr pidfile_open 3 .
This class is patterned on std::unique_ptr;
however,
rather than exporting the raw pointer via a
.Fn get
method,
this class provides wrapper methods for each of the other
.Xr pidfile 3
functions.
The currently-owned PID file is removed by invoking
.Xr pidfile_remove 3
when an instance of this class is destroyed.
The currently-owned PID file is also removed if it is replaced by the
.Fn reset
method or assignment operators.
.Pp
The
.Fn release
method relinquishes ownership of the current PID file handle and returns the
value of the previously-owned PID file handle.
.Pp
The
.Fn write
method writes out the PID of the current process to the PID file via
.Xr pidfile_write 3 .
.Pp
The
.Fn close
method closes the current PID file without removing it via
.Xr pidfile_close 3 .
If the close succeeds, the PID file handle is no longer valid.
.Pp
The
.Fn fileno
method returns the underlying file descriptor for the current PID file via
.Xr pidfile_fileno 3 .
.Pp
The explicit
.Vt bool
conversion operator permits testing the validity of an object.
The operator returns true if the instance owns a valid PID file handle.
.Sh EXAMPLES
.Bd -literal -offset indent
int
main()
{
freebsd::pidfile pf(pidfile_open("/var/run/daemon.pid",
0600, NULL));
if (!pf)
err(1, "pidfile_open");
if (daemon(0, 0) == -1) {
warn("daemon");
return 1;
}
pf->write();
for (;;) {
/* Do Work */
}
return 0;
}
.Ed
.Sh SEE ALSO
.Xr pidfile 3

View file

@ -9,6 +9,7 @@
#define __LIBUTILPP_HH__
#include <sys/nv.h>
#include <libutil.h>
#include <netdb.h>
#include <unistd.h>
@ -145,6 +146,79 @@ namespace freebsd {
using nvlist_up = std::unique_ptr<nvlist_t, nvlist_deleter>;
/*
* A wrapper class for the pidfile_* API. The destructor
* calls pidfile_remove() when an object is destroyed. This
* class is similar to std::unique_ptr<> in that it retains
* exclusive ownership of the pidfh object.
*
* In addition to release() and reset methods(), write(),
* close(), and fileno() methods are provided as wrappers for
* pidfile_*.
*/
class pidfile {
public:
pidfile() = default;
pidfile(struct pidfh *pfh) : pfh(pfh) {}
pidfile(pidfile &&other) : pfh(other.release()) {}
pidfile(pidfile const &) = delete;
~pidfile() { reset(); }
struct pidfh *release()
{
struct pidfh *oldpfh = pfh;
pfh = nullptr;
return (oldpfh);
}
void reset(struct pidfh *newpfh = nullptr)
{
if (pfh != nullptr)
pidfile_remove(pfh);
pfh = newpfh;
}
int write()
{
return (pidfile_write(pfh));
}
int close()
{
int rv = pidfile_close(pfh);
if (rv == 0)
pfh = nullptr;
return (rv);
}
int fileno()
{
return (pidfile_fileno(pfh));
}
pidfile &operator=(pidfile &&other) noexcept
{
if (this == &other)
return *this;
reset(other.release());
return *this;
}
pidfile &operator=(pidfile const &) = delete;
pidfile &operator=(struct pidfh *newpfh)
{
reset(newpfh);
return *this;
}
explicit operator bool() const { return pfh != nullptr; }
private:
struct pidfh *pfh = nullptr;
};
/*
* Returns a std::string containing the same output as
* sprintf(). Throws std::bad_alloc if an error occurs.

View file

@ -1,9 +1,12 @@
PACKAGE= tests
ATF_TESTS_CXX+= pidfile_test
ATF_TESTS_CXX+= stringf_test
ATF_TESTS_CXX+= up_test
CFLAGS+= -I${SRCTOP}/lib/libutil++
LIBADD+= util++
LIBADD.pidfile_test+= util
.include <bsd.test.mk>

View file

@ -0,0 +1,44 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2025 Chelsio Communications, Inc.
* Written by: John Baldwin <jhb@FreeBSD.org>
*/
#include <atf-c++.hpp>
#include <sys/stat.h>
#include <libutil.h>
#include <libutil++.hh>
ATF_TEST_CASE_WITHOUT_HEAD(basic);
ATF_TEST_CASE_BODY(basic)
{
pid_t other;
struct pidfh *pfh = pidfile_open("test_pidfile", 0600, &other);
ATF_REQUIRE(pfh != nullptr);
ATF_REQUIRE(pidfile_fileno(pfh) >= 0);
struct stat sb;
ATF_REQUIRE(fstat(pidfile_fileno(pfh), &sb) == 0);
ATF_REQUIRE_EQ(0, sb.st_size);
freebsd::pidfile pf(pfh);
ATF_REQUIRE_EQ(pidfile_fileno(pfh), pf.fileno());
ATF_REQUIRE(pf.write() == 0);
ATF_REQUIRE(fstat(pf.fileno(), &sb) == 0);
ATF_REQUIRE(sb.st_size > 0);
ATF_REQUIRE(pf.close() == 0);
ATF_REQUIRE(pf.fileno() == -1);
ATF_REQUIRE_EQ(EDOOFUS, errno);
ATF_REQUIRE(unlink("test_pidfile") == 0);
}
ATF_INIT_TEST_CASES(tcs)
{
ATF_ADD_TEST_CASE(tcs, basic);
}