From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mta1.formilux.org (mta1.formilux.org [51.159.59.229]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CB33F25A2B5 for ; Sun, 29 Mar 2026 05:06:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=51.159.59.229 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774760783; cv=none; b=kLaZ4+BMA6MslcHRUlDBY1CkKtBQcm1ddc5611immavQmb5toTDChk2+BJkHURkbg8xEEdkMZMq/jC+b+Ibmwa1SC+uS8a5xojGL7WMvDK7820DmplNWIYomc0d2/SH3nx6tBHHbsirHuYEOb++zMKCtp9e2CeliXseg600wOjY= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774760783; c=relaxed/simple; bh=dQCGMpvXgcmPWf8ttABAU4UBf9E+kHT/svqWzfCjkl4=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=GeF66RjZX7DwrWHnAH85bP68jHV9BeTaDyB5gPVHbwWl6Fkec2zVaek/LU8P0wydOYBUWZhXJ3qfJaCF/BdXXuZswOtyzkL+fV1F8Yokg80fP3Afeo63Ynd89V+PRv4S+qFeZA8dG9e/fhCHaPzVWH3F9jwU9XJWRW8KXfNc4Fs= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=1wt.eu; spf=pass smtp.mailfrom=1wt.eu; dkim=pass (1024-bit key) header.d=1wt.eu header.i=@1wt.eu header.b=eEH4VHfg; arc=none smtp.client-ip=51.159.59.229 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=1wt.eu Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=1wt.eu Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=1wt.eu header.i=@1wt.eu header.b="eEH4VHfg" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1wt.eu; s=mail; t=1774760772; bh=fxOSEI1v7QXhs06Xs0q6KL0RkmNuN6K71KXaoUj1W+M=; h=From:Message-ID:From; b=eEH4VHfgOlha0LATu8Y4oEr/lBQXvOe4xNmoaFW/7Z1Yn9Bt1DeEJuQEOjXQySxcQ FI+hcSTOumaRwWEttmh11wHfFHNHHATLRHTExaMBq3lW/d8YOtJIQGoY2PWZogC8xC 0wjz18xcrHjbx/wpfoi2zCAdriKbL5GJq07+joM0= Received: from 1wt.eu (ded1.1wt.eu [163.172.96.212]) by mta1.formilux.org (Postfix) with ESMTP id C140DC0AEF; Sun, 29 Mar 2026 07:06:12 +0200 (CEST) Date: Sun, 29 Mar 2026 07:06:12 +0200 From: Willy Tarreau To: Thomas =?iso-8859-1?Q?Wei=DFschuh?= Cc: linux-kernel@vger.kernel.org Subject: Re: [PATCH] tools/nolibc: rename sys_foo() functions to _sys_foo() Message-ID: References: <20260319-nolibc-namespacing-v1-1-33c22eaddb5e@weissschuh.net> <9b30e65c-2ee8-4f0a-b4b9-a48a0964e586@t-8ch.de> <6656f7e9-d8f6-4832-abcf-979265528300@t-8ch.de> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: inline Content-Transfer-Encoding: 8bit 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