I wrote an updated finger daemon in modern c++ several months ago to run on a different domain of mine. That server is running docker on top of Debian, so I'd written it with an aim for that platform. With my recent move to operate more internet services from the 90s, I wanted to port my service over to FreeBSD and get it running inside of a jail on my main BSD server.
The project uses meson. The meson.build for the main executable had these link args:
link_args : ['-static', '-static-libgcc', '-static-libstdc++'],
Those are GCC-isms. FreeBSD ships clang, and clang doesn't have -static-libgcc
or -static-libstdc++ -- it uses libc++ and handles its runtime differently.
Trying to build with those flags just dies at link time. The fix was simple: remove the
whole link_args line. We don't need a statically linked binary inside a jail
anyway.
After stripping the bad flags the linker still failed, this time with a pile of undefined symbols:
ld: error: undefined symbol: pthread_condattr_init
>>> referenced by posix_event.ipp:41
ld: error: undefined symbol: pthread_create
>>> referenced by posix_thread.ipp:60
Boost ASIO uses pthreads internally for its event loop. On Linux with GCC and the old static flags, this got pulled in implicitly. On FreeBSD with clang it does not -- you have to ask for it explicitly. The boost-libs package even tells you this in its install message: "Don't forget to add -pthread to your linker options when linking your code." I missed it the first time.
The right way to do this in meson is a threads dependency:
threads_dep = dependency('threads')
Then add threads_dep to the dependencies list for every build target. The final
meson.build for the main executable looks like:
executable('finger',
'main.cpp','handler.cpp',
dependencies : [boost_dep, threads_dep],
install : true)
After that, clean configure and compile:
meson setup builddir
meson compile -C builddir
meson install -C builddir
Binary ends up at /usr/local/bin/finger. The C++ source itself needed zero
changes -- coroutines, std::filesystem, ASIO, all of it compiled clean under clang 19.
I created a thin Bastille jail called finger at 192.168.1.104.
The daemon reads plan files from /var/finger/users/ -- one file per username,
contents returned verbatim. Rather than manage those files from inside the jail, I put them
on the host at /srv/finger/users/ and nullfs bind-mounted that directory in:
/srv/finger/users /usr/local/bastille/jails/finger/root/var/finger/users nullfs rw 0 0
That goes in the jail's fstab. Now I can add or edit plan files from the host
without touching the jail at all:
echo "Just another hacker." > /srv/finger/users/pete
Port 79 forwarding is handled by a one-liner in Bastille's rdr.conf:
dual re0 any any tcp 79 79
Bastille translates that into a pf rdr rule on reload.
The daemon runs in the foreground -- no self-daemonizing. FreeBSD's daemon(8)
handles that. The rc.d script is minimal:
#!/bin/sh
# PROVIDE: fingerd
# REQUIRE: NETWORKING
# KEYWORD: shutdown
. /etc/rc.subr
name="fingerd"
rcvar="fingerd_enable"
command="/usr/sbin/daemon"
command_args="-f -p /var/run/fingerd.pid /usr/local/bin/finger"
pidfile="/var/run/fingerd.pid"
load_rc_config $name
: ${fingerd_enable:=NO}
run_rc_command "$1"
Drop that in /usr/local/etc/rc.d/fingerd, chmod 755 it, then:
sysrc fingerd_enable=YES
service fingerd start
The whole thing works. The C++ code was perfectly portable -- no Linux assumptions baked in. The only friction was the build system carrying GCC baggage and the implicit pthread link that Linux let slide. Two changes to meson.build, a jail, a bind mount, and an rc script.
$ finger pete@peteftw.com
Just another hacker.