From: Willy Tarreau <w@1wt.eu>
To: "Thomas Weißschuh" <linux@weissschuh.net>
Cc: linux-kernel@vger.kernel.org
Subject: Re: [PATCH] tools/nolibc: rename sys_foo() functions to _sys_foo()
Date: Sun, 29 Mar 2026 07:06:12 +0200 [thread overview]
Message-ID: <acizRIq2xrFUNHNS@1wt.eu> (raw)
In-Reply-To: <6656f7e9-d8f6-4832-abcf-979265528300@t-8ch.de>
Hi Thomas,
On Tue, Mar 24, 2026 at 06:01:35PM +0100, Thomas Weißschuh wrote:
> On 2026-03-22 12:03:30+0100, Willy Tarreau wrote:
> > On Sun, Mar 22, 2026 at 11:43:56AM +0100, Thomas Weißschuh wrote:
> > > On 2026-03-22 10:06:42+0100, Willy Tarreau wrote:
> > > > On Thu, Mar 19, 2026 at 05:20:17PM +0100, Thomas Weißschuh wrote:
> > > > > The sys_foo() naming scheme used by the syscall wrappers may collide
> > > > > with application symbols. Especially as 'sys_' is an obvious naming
> > > > > scheme an application may choose for its own custom systemcall wrappers.
> > > >
> > > > Yes but on the other hand it might implement it when missing the one
> > > > offered by the libc.
> > >
> > > I don't really get this sentence. Do you refer to the '#ifdef sys_foo'
> > > as you mention below?
> >
> > Ah no, but rereading my message shows me it was not really parsable :-)
> > I meant that some applications missing a syscall in nolibc (and detecting
> > this miss via any method) could decide to implement their own equivalent
> > for the time it takes to integrate the feature into nolibc, and thus it
> > can make sense that they call their feature sys_foo like we do, so that
> > their sys_foo is basically the same as ours (i.e. they really just have
> > to detect the conflict one way or another but that's all).
>
> What is the advantage of switching over to the nolibc-provided
> implementation? The custom implementation would be good enough anyways.
It's not an "advantage", it's just remaining compatible. When you have
to locally implement some missing functions, and your build breaks again
once you update nolibc, it's a real pain, and sometimes depending on the
environment you have different toolchain versions, yet you don't want to
be forced to fork your local code which didn't change between the versions.
> > In fact it's not about handling different versions, it's really about
> > working around what's missing. I face this every day as a userland
> > developer. As soon as you depend on a lib, you figure something is
> > missing, and you cannot stop your project because of this. So the
> > right thing to do is to implement what you're missing in a mostly
> > discoverable way, and the try to upstream your work into the lib that
> > was missing the feature.
>
> That makes sense where you have to work with the external library in the
> form it is provided to you by somebody else. nolibc should be vendored,
> so you always know exactly what it supports. Or do you have existing
> usecases where you detect nolibc features?
I'm not sure to understand what you mean here. Let's take this example
for illustration, that I have in my preinit code:
static void enable_signals(void)
{
struct sigaction act = { 0 };
sigset_t blk = { 0 };
#ifdef NOLIBC
#ifdef SA_RESTORER
act.sa_flags = SA_RESTORER;
act.sa_restorer = sig_return;
#endif
act.sa_handler = sig_handler;
my_syscall4(__NR_rt_sigaction, SIGTERM, &act, NULL, sizeof(sigset_t));
my_syscall4(__NR_rt_sigaction, SIGINT, &act, NULL, sizeof(sigset_t));
my_syscall4(__NR_rt_sigprocmask, SIG_SETMASK, &blk, NULL, sizeof(sigset_t));
#else
signal(SIGTERM, sig_handler);
signal(SIGINT, sig_handler);
sigprocmask(SIG_SETMASK, &blk, NULL);
#endif
}
Since this is where nolibc was born, I've always been very careful about
not redefining standard calls (this is where all the my_* stuff came
from). A cleaner implementation would have donne this:
#if defined(NOLIBC)
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler)
{
struct sigaction act = { 0 };
#ifdef SA_RESTORER
act.sa_flags = SA_RESTORER;
act.sa_restorer = sig_return;
#endif
act.sa_handler = sig_handler;
my_syscall4(__NR_rt_sigaction, SIGTERM, &act, NULL, sizeof(sigset_t));
my_syscall4(__NR_rt_sigaction, SIGINT, &act, NULL, sizeof(sigset_t));
my_syscall4(__NR_rt_sigprocmask, SIG_SETMASK, &blk, NULL, sizeof(sigset_t));
}
#endif
static void enable_signals(void)
{
sigset_t blk = { 0 };
signal(SIGTERM, sig_handler);
signal(SIGINT, sig_handler);
sigprocmask(SIG_SETMASK, &blk, NULL);
}
With a patch applied to nolibc to include that new signal() definition.
But the day nolibc is updated, this code would break, with no easy way
to detect the support of the function. By experience I know pretty well
how it would end up, I'd look for a #define that appeared in a very
close version and would detect it as a proxy for the supporting nolibc
version. But this remains ugly.
A cleaner approach would be something like this:
#if defined(NOLIBC) && !defined(signal)
or maybe:
#if defined(NOLIBC) && !defined(_NOLIBC_HAVE_signal)
Note that it could work both ways. For example we removed support for
wait4() a while ago, and it could have been nice to be able to detect
it as well.
> > Inside the kernel, this problem is less visible because it's the same
> > repository, so anyone working on their selftest etc can also directly
> > commit into the nolibc subdir as well. When you're using code outside
> > it's totally different, as you have less control over the selection of
> > kernel headers version, hence nolibc version.
>
> With the UAPI headers the detection makes slightly more sense.
> On the other hand we try to be backwards-compatible to fairly old
> versions.
Not that much actually. I got some breakage a few months ago due to
upgrading nolibc for a coworker to benefit from something I don't
remember and that wouldn't build. It just failed to boot because of
the recent abandon for stat() in favor of statx() that causes silent
failures when __NR_statx is not defined. It turns out that the toolchain
was built with support for kernel 4.9 and above and didn't need to be
upgraded, and since kernel forward compatibility is pretty good, this
has always been fine. But there sys_stat() was silently implemented as
"return -ENOSYS" which made the system fail to boot. I could quickly
bisect and offer him a copy of nolibc from 6.1 which was still
compatible and had the missing feature I needed. As a comparison, I
checked, and glibc-2.44 on my local machine was built with support
for kernel 4.4 and above, and it probably still supports much older
ones for various reasons.
This made me think that it was not a good idea in the end to report
-ENOSYS, it should only be left to the kernel (i.e. runtime detect)
but not build time detection. In other programs I've employed link
time failure for unsupported features. This is quite efficient. For
example in sys_statx() we could have done something like this instead
of return -ENOSYS:
extern int __nolibc_API_failure_statx_requires_NR_statx;
return __nolibc_API_failure_statx_requires_NR_statx;
This way it's discoverable at build time if the function is used.
But I'm digressing (not that much actually since we're discussing
features discoverability).
> > I personally don't think that a single define in front of each syscall
> > definition will make the code harder to read. It can be argued that glibc
> > doesn't offer this and that the user application will have to compose with
> > all of this, however glibc offers lots more stuff than we do, and users
> > rely on its version to guess whether or not something is present, which
> > is something we don't really have.
>
> I don't think it makes the code worse or really have anything against it
> in general. I just don't really see the advantage. For glibc this makes
> sense, as an application will have to do with the version that is
> present during compilation and at runtime. For nolibc this doesn't
> really happen. Or I might be missing something :-)
It's exactly the same actually. You use a nolibc repository to build your
code, your kernel headers come from anywhere, very often from the glibc-
based cross-compiler that includes support for the oldest kernel your
build system supports, and that's done. If you remember, originally there
was only nolibc.h to include and nothing else.
> If we adopt that scheme it should not only cover the _sys_*() functions
> but also the regular ones, no?
I've been wondering about this and probably yes. When I look at my preinit,
there are my_memmove(), my_strcpy(), my_strlen(), my_strlcpy(), my_getenv(),
my_atoul(), in addition for the syscall definitions. So yes, this
illustrates that it would be desirable to have that for the rest as well.
Maybe it ends up being about making the public API discoverable at build
time after all.
We could possibly also just use a version that applications could check
against. While the project has no declared version, we could for example
adopt the kernel's version since it's where it mostly evolves nowadays,
so maybe we could set a pair of _NOLIBC_KVER_MAJOR and _NOLIBC_KVER_MINOR
macros reflecting the kernel version this comes form (which is not the
same as the UAPI version but could be set at install time). At least with
a single version it would cover everything, and it's not much different
from what can be done with glibc.
Willy
prev parent reply other threads:[~2026-03-29 5:06 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-19 16:20 [PATCH] tools/nolibc: rename sys_foo() functions to _sys_foo() Thomas Weißschuh
2026-03-22 9:06 ` Willy Tarreau
2026-03-22 10:43 ` Thomas Weißschuh
2026-03-22 11:03 ` Willy Tarreau
2026-03-24 17:01 ` Thomas Weißschuh
2026-03-29 5:06 ` Willy Tarreau [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=acizRIq2xrFUNHNS@1wt.eu \
--to=w@1wt.eu \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@weissschuh.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox