9

The musl team claims that there is no need of a way of detecting musl libc because they only implement standard functionality and have no quirks that need detecting.

Up until today, that claim may well have been true, but it is no longer true. Normal feature detection is not working because the feature is there but broken, I'd rather not probe for it because I don't want to demand root at compile time and disallow cross-compilation. The bug has been reported with minimized sample code, and the maintainers don't want to fix it at all, nor will they take my patch.

I am not going to penalize every other libc because musl has a broken feature.

Logically speaking I want to do

#if MUSL || APPLE
    pid = fork();
#else
    pid = vfork();
#endif

where I already have #if APPLE because Mac OSX has an untrustworthy vfork().

There is no point telling me vfork() is bad. The situation has changed since 2008, and vfork() is a better choice whenever possible, no matter the complexities involved. Some Source: https://gist.github.com/nicowilliams/a8a07b0fc75df05f684c23c18d7db234

Joshua
  • 40,822
  • 8
  • 72
  • 132
  • 1
    What are you trying to do with `vfork()`? Keep in mind that it was declared obsolete by the POSIX.1-2008 specification, and even before then was subject to extensive limitations. –  Oct 01 '19 at 03:59
  • 1
    @duskwuff: Stop paying a gigabyte's worth of page faults every time I spawn a child process binary is what. `vfork()` making quite the comeback. – Joshua Oct 01 '19 at 04:01
  • 1
    Putting a side the vfork/fork discussion, worth mentioning that POSIX compliant system have posix_spawn which cover the most common use cases for vfork (vfork, followed by exec). For this use case, posix_spawn can be more efficient that vfork, as there is no need to duplicate the current process. – dash-o Oct 01 '19 at 04:59
  • @dash-o: No it can't. That was a long dead end. That was the first thing I tried. – Joshua Oct 01 '19 at 13:35
  • More of a hack than a solution; consider checking for _GNU_SOURCE but NOT _GLIBC_ (or _GLIB_MINOR, or __GNU_LIBRARY). You can put this into a separate file which will be compiled with _GNU_SOURCE, and will define 'my_fork'. – dash-o Oct 01 '19 at 14:07
  • @UnmannedPlayer: The CEO is trying to force politics. The tone is set from the top. – Joshua Mar 01 '20 at 01:54

2 Answers2

6

I'm very late but I noticed one peculiarity of musl libc while trying to find an answer to the same question (in my case, it's that musl doesn't implement pthread_setname_np). It doesn't define __USE_GNU in features.h when _GNU_SOURCE is defined and instead uses _GNU_SOURCE to guard GNU extensions.

So this procedure should work to create a musl libc macro (it will have false positives if another libc implementation has the same behavior or someone without a libc includes a stub features.h for some reason; but Glibc, uClibc, and bionic all define __USE_GNU in features.h or another header included by features.h when _GNU_SOURCE is defined)

So this is working for me rn:

#define _GNU_SOURCE
#include <features.h>
#ifndef __USE_GNU
    #define __MUSL__ 
#endif
#undef _GNU_SOURCE /* don't contaminate other includes unnecessarily */
nfries88
  • 374
  • 6
  • 7
4

I guess this is a bit late, but here's a solution:

#! /bin/sh

tmpf=/tmp/musl.log

# Detect Musl C library.
libc=$(ldd /bin/ls | grep 'musl' | head -1 | cut -d ' ' -f1)
if [ -z $libc ]; then
    # This is not Musl.
    rm -f ${tmpf}
    exit 1
fi
$libc >${tmpf} 2>&1
vstr=$(cat ${tmpf} | grep "Version" | cut -d ' ' -f2)

v_major=$(echo $vstr | cut -d '.' -f1)
v_minor=$(echo $vstr | cut -d '.' -f2)
v_patch=$(echo $vstr | cut -d '.' -f3)

rm -f ${tmpf}

echo "-D__MUSL__ -D__MUSL_VER_MAJOR__=${v_major} -D__MUSL_VER_MINOR__=${v_minor} -D__MUSL_VER_PATCH__=${v_patch}"

This shell script extracts some interesting information and prints out -D friendly values so headers/source files can benefit from them. See,

$ ./detect-musl.sh 
-D__MUSL__ -D__MUSL_VER_MAJOR__=1 -D__MUSL_VER_MINOR__=1 -D__MUSL_VER_PATCH__=24

Please invoke this early on in your Makefile and adjust your CFLAGS accordingly.

This script executes ldd script, gets the library that has musl in its name, then executes that. All libc libraries are executable (because they literally contains _start()) and even produce output. Normally, this is the version information. For example, GNU shows this:

$ /lib/x86_64-linux-gnu/libc.so.6
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27.
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 7.3.0.
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.

and, musl shows this:

$ /lib/ld-musl-x86_64.so.1 
musl libc (x86_64)
Version 1.1.24
Dynamic Program Loader
Usage: /lib/ld-musl-x86_64.so.1 [options] [--] pathname [args]

I've tested my script on Alpine and Linux Mint and it seems to work fine.

Like you, I hate when pedantic ideologies hinder practicalities. If you find a better solution, please feel free to up post it. :)

Edit: In case of cross compiling, ldd can't work. There's a bit of extra work involved where you generate a test program and read its ELF links out then check if its contains musl libc string in it. See GitHub snippet for details.

Unmanned Player
  • 1,109
  • 9
  • 30
  • I am indeed cross-compiling; else I would have gone for detect the broken vfork directly by now. The host machine cannot run musl binaries either. – Joshua Mar 01 '20 at 01:49
  • @Joshua No [candies](https://gist.github.com/unmanned-player/f2421eec512d610116f451249cce5920) for SO. :) – Unmanned Player Mar 01 '20 at 06:02