The BSD.lv tools I wanted to port all usd oconfigure as a simple portability shim. This script was originally forked from mandoc in 2014 for use in kcgi. Originally, it feature-tested for memmem(3) and strtonum(3), which weren't (aren't) available on Mac OS X, and provided compatible functions if these weren't detected. You can see its original import in commit a5ff55c, with the original file called configure.
The script simply declared whether these functions existed or not as defined from a shell script compiled and run during configuration.
The results were output into a header file config.h, which was
in turn included by the system sources.
Below you can see an example.
HAVE_xxx defines were inserted based if the test passes.
Compatibility functions were all in compat.c, which also included
ifndef guards around compatibility function.
(Can you spot the portability issue in the above? If a system without
memmem(3) includes this, it's possible that
sys/types.h wouldn't be included, which defines
size_t, resulting in a compilation error.)
Sources would include the configuration header config.h and link to the compatibility sources. Let main.c below be an example file.
By 2016, there were about a dozen tests in the script (you can see it in configure for this date), with roughly half of them providing a compatibility function if not found. I started to want this functionality for other systems, so I created a new repository called oconfigure to abstract the work.
First I pulled in mandoc's new features for a site configuration script, which overrode test results, and to emit Makefile configuration variables as well. To keep things simple, I then had all tests and compatibility functions put into single files. This way, new projects needed only to copy configure, compats.c, and tests.c to use the shim.
At the time and for some time after, the extra Makefile.configure file
didn't do much, but compats.c and tests.c,
and the logic of
configure, grew significantly.
Prior to the porting effort,
configure had a bit under three dozen feature tests,
going so far as to provide the queue(3) and
tree(3) macro sets.
One of the biggest problems with portability when I started this adventure was the location of
Most of the tools used
-lsqlite3 sometimes needed
-lpthread, sometimes not.
More importantly, some portable functions existed on target platforms but required special libraries.
For example, md5(3) exists on FreeBSD, but requires
-lmd for linking.
Then on IllumOS, all socket functions required
To date, it was the job of the porter to know which libraries and library paths were required and pass them to the script. On these many new systems, I was the porter, so this got old real fast.
The first big change was to allow for the portability layer to stand on its own and not require
To wit, I taught
configure to test and export whether platform-specific libraries
were required for functions in the portability shim.
For example, FreeBSD configuration set
LDADD_MD5 in the generated
Makefile.configure to the required library.
Other systems left this empty.
Thus, a Makefile including this would no longer need a porter to pass
the library during configuration—it added
$(LDADD_MD5) where required.
The second big change was to take advantage of a utility in OpenBSD's base specifically designed for the use case of locating libraries: pkg-config(1). By using this, I no longer needed to supply the include and library paths for target systems: I would use pkg-config(1) to do it for me.
Once these basic steps were accomplished, I no longer needed to pass system-specific flags each time I moved to another system. I was able to focus on the nitty-gritty of providing compatibility functions, macros, and handling diverse header layouts.
configure script was smart enough to plumb its environment, next came
handling discrepencies between systems.
This section covers some of the major issues encountered, and isn't specific to the
systems involved in this porting effort.
This isn't a name and shame section. Working in a diverse environment is what it is. (On some systems, however, there can be… questionable design choices.)
This was one of the first components managed by the script. I've read many arguments of why folks don't like strlcpy(3) and friends, but I also haven't read much code coming from those people. Anyway, I won't say anything more about this.
It's fairly simple to test whether string functions exist: though some systems require macro
soup to enable these functions during compilation.
On glibc systems, for example, it's often
necessary to include
OpenBSD's better memory handling functions (e.g., reallocarray(3), explicit_bzero(3), etc.) are slowly making their way into other systems. There seems to be less contention over these than the string functions.
Unfortunately, including these extensions requires some macro soup.
On Linux machines, either
_DEFAULT_SOURCE must be
defined (for older and newer systems, respectively).
_XOPEN_SOURCE is also defined, such as for endian functions, then this
macro conflicts with
The solution is for both
_DEFAULT_SOURCE are required.
What a pain…
As it currently stands, only OpenBSD supports high-quality non-blocking random numbers with the arc4random(3) family. Users of other systems must either depend on low-quality numbers or system-specific measures.
While the traditional ntohs(3) are fairly standard, the new le32toh(3) style is more readable and handles more cases. Unfortunately, this type of interface is very diverse and requires both macro soup and compatibility.
Mac OS X (Darwin) has its set of byte-swapping functions in libkern/OSByteOrder.h. SunOS (Solaris, IllumOS) has its own in sys/byteorder.h. Neither are documented. FreeBSD has the correct functions in the wrong place. All of these need to be detected and the proper macros set up for sane names.
What's more is that, while the byte-swapping functions themselves exist, actually testing for
endianness is an entirely different problem.
While OpenBSD provides
BYTE_ORDER and a simple test for little or big endian, other
systems have their own versions of
_BYTE_ORDER and so on.
Fortunately, most modern compilers emit a
__BYTE_ORDER__ macro that can be used as
a relatively-safe fall-back without relying on obscure system headers.
The familiar minor(3) and related functions are all over the place on different systems. Fortunately, the functions themselves are named in the same way, so it's simply a matter of finding the correct header file.
The POSIX *at functions (e.g., mknodat(2)) are still not broadly supported. I anticipate this will get better, though. (Only recently did OpenBSD gain most of these!) Though these functions are standardised, or becoming standardised, they do require some macro soup for systems (such as SunOS) that require specific features be defined for usage.
Unfortunately, these functions can't be portably emulated since pairing fchdir(2) and the target function, e.g. mknod(2), has a race between the two.
To any actual programmer, OpenBSD's restricted operation functions pledge(2) and unveil(2) are gifts from heaven. No other system's facilities even come close in terms of practical security.
The user-level restriction features, such as setresgid(2), seem to be slowly migrating. On earlier Mac OS X machines, these were entirely broken, which required additional plumbing to detect since the functions were there. Recent versions do not have this issue, so it remains simply a matter of portability.
OpenBSD has the simple md5(3) and sha2(3) header files for MD5 and SHA2 (e.g., SHA256) hashing. This is incredibly useful because one can use these powerful functions without needing to pull in external libraries such as OpenSSL or LibreSSL.
FreeBSD has the MD5 header but splits the SHA2 header into SHA256, etc. NetBSD has a single header for both but different variable types and slightly different function naming. (FreeBSD also requires linking to another library for its hash functions.) SunOS, which also needs the hash function library, almost has everything, but is missing several key functions (e.g., SHA256File).
right way to do this—all using the same type, or different types, or
different header files—but the disparity causes big headaches for programmers.
In the end, it became easier to simply test for all functions and provide compatibility
straight-up instead of testing for each variant and providing macro-soup to work between them.
OpenBSD has crypt_checkpass(3), which
makes password hash generation and checking super easy.
The fallback is the traditional crypt(3)
interface, which is a nightmare.
Unfortunately, this function requires a tremendous amount of macro goop to properly use.
_XOPEN_SOURCE but defining
_XOPEN_SOURCE pull this in, while defining both
_GNU_SOURCE on newer glibcs will cause warnings).
Many systems require a further
-lcrypt, which is easy to test for.
Linux (glibc) further notes that this function may be deprecated and hand-waves a replacement. It does not have a manpage on some systems, but the function still exists.
The supported hashes is where it gets interesting.
OpenBSD, FreeBSD, and newer Linux support Blowfish.
NetBSD and Solaris do not. IllumOS does (undocumented).
If this weren't confusing enough, NetBSD's function behaves differently than the others: if it
does not find the requested hash algorithm, it returns a
magic string instead of
It's a mess.
The most portable is simply to use DES encryption.
Finding the necessary macro incantations proved immediately problematic. Especially on Linux: adding a feature test on an older glibc would break on newer ones. This seemed to go on forever and required each change to be tested manually on many systems.
In the next section, I introduce BSD.lv's continuous integration that helped with this.