Date: Sun, 16 Nov 2025 16:14:26 -0700
From: Warner Losh <imp%bsdimp.com@localhost>
Message-ID: <CANCZdfp8F5qxwWKGL1A7aOqpcahqjv=c__9DEET+KDngt1LLLA%mail.gmail.com@localhost>
| But now you are committing to have this forever, even though it's not in
| the documentation or the user's namespace.
It is part of the implementation, so in the implementation namespace, it
would never work using the user namespace, as it would clash with any
legitimate use of the same name ... unless we could somehow get the C
and POSIX standards (though the latter would just follow the former)
to agree upon that, and since <sys/exec_elf.h> isn't a header file that
either of those would be likely to ever adopt in the first place, defining
user namespace symbols it is permitted to usurp would be very unlikely.
We can easily add it to the documentation, if & when we ever get any which
covers <sys/exec_elf.h> (until then it is documented, kind of, in that file
itself, and in the CVS log ... it is not a much used header in userland).
The include guard needs to be retained forever anyway, just as it has been
there almost forever, and even if sometime we could rely upon all C (& C++)
compilers implementing multiple inclusion suppression automatically -- which
I'm not sure I'd like, as what a header defines can be controlled by
visible pre-defined symbols, so sometimes being able to include a header
twice, with different expectations of what it actually defines -- that is
never going away, that it happens to be defined to 2 instead of nothing is
not going to bother anyone.
I don't follow why the guard needs to be there forever. It's in implementation
namespace and can therefore be removed or renamed at any time. It's an implementation
detail, but it's not one people can rely on. Just like you can't rely on the internals
of FILE to necessarily be the same if the changes can be made in a binary
compatible way.
So the issue of being able to include a header twice is a red herring. Only assert.h
needs to do that. Everything else can, but I've not seen very many things that do
do that, and certainly not in publicly facing interfaces.
| And nobody is going to notice at commit when they do this if things break
| since they will be out of tree.
Maybe you can explain what you mean by that? Commit what? Changes to header
files which would warrant something like this (in which case if they don't
realise that they need this, they wouldn't be bumping anything at all, no
matter what it is called) or user code using it - which is only ever going to
care when something breaks, somewhere, which is kind of a noticeable thing,
If I were to make everything #pragma once and remove the guards, NetBSD would
compile just fine. But any users of the guard in their code to gate different interfaces
would suddenly break, maybe noisily at compile time, but maybe not until runtime when
the wrong results are created, or features are suddenly missing.
| With netbsd_version bumping, you don't have those issues.
First, it is __NetBSD_Version__ which is not in the user's namespace either,
and also not really documented anywhere (other than in its code) which is
why it sometimes gets used for purposes for which it wasn't intended.
Nitpicking over spelling isn't helping win me over.
Further, it ends up being visible in almost everything in the system,
as it gets included (often indirectly) in just about everything (which
is not good). That means that every time it changes, what should
have been someone (who doesn't care about ELF definitions in any way
at all) doing what should be a very quick update build (just recompile
what I just changed) ends up effectively getting a full rebuild done,
because most object files end up with a hidden dependency on <sys/param.h>.
[ Incidentally, that's why I have been planning, for some time now,
to rip the definition of __NetBSD_Version__ out of that header, and
put it elsewhere - even the vast majority of the kernel doesn't care
about its value. ]
Further __NetBSD_Version__'s value is what uname -r prints, which makes
the current kernel version visible to scripting level userland, not
just compilation - but that version is that of the currently running
kernel, and has almost nothing to do with what is in /usr/include
(including /usr/include/sys) - people routinely build and run new
kernels without updating anything else at all.
I've used FreeBSD for 30 years, and we've done this there for 25
of those years. None of these issues are real problems. In fact,
we use the version to detect mismatches between the kernel and
userland so we can report both versions.
| It seems overly complicated for little to no gain.
That of course is a matter of opinion, and everyone is entitled
to their own.
| That system works great in FreeBSD land:
>From what I saw (I haven't been to look), I don't believe that the FreeBSD
solution is quite the same as bumping __NetBSD_Version__ would be.
But does that (a single integer I think it was described as) really
work?
n
Consider the following (and since I don't know what the symbol name is,
just for the purposes of this e-mail, I will call it V ... which it
(I hope) obviously would not really be).
Start with V=1000 (its state before anything described below happens).
Then someone decides that one of the private FreeBSD functions, visible
from one of the libraries, was badly designed (maybe as simple as a
missing "const" in the prototype, or perhaps missing an essential
argument for some useful operation) and (after whatever review is
appropriate) commits a change (to the current development sources,
not yet officially released - it can always just be reverted before
any release if it turns out to be a bad idea), and so that user code
can detect which version they should use, bumps V, it is now 1001.
Next we have something exactly like what happened here, what can be
argued to be a bug (or at least a gratuitous difference to every other
system around) in a rarely used facility is discovered, that gets fixed
so now it is compatible with everything else, and all uses (if there
are any - in our case, I believe there were none) of that in the managed
sources gets fixed. Of course, external code needs to be able to
detect the change (which is what caused all of this discussion in our
case in the first place) so V is bumped again. It is now 1002.
Then someone realises that the 2nd of those is (was) really a bug,
which hadn't been noticed, as nothing in the tree had ever used the
facility which had been defined incompatibly, and proposes that the
change should be pulled up to the existing supported released versions.
It is easy to see how we will handle that - the header file in the
released version is just made the same as that in HEAD, including its
include guard, and any external code which has been fixed to deal
with building in HEAD (which broke, as the definition change wouldn't
compile without fixes) simply works on the earlier versions, as the
test is identical.
But with FreeBSD's V - what happens? If that then also pulls up
the change to V (so the old stable release now has V==1002 as well)
then how does code built there not assume that it also has the first
(totally unsuitable for pullups) change in it as well?
FreeBSD's V also encodes the major and minor versions, so the
whole scenario is avoided. You get unique numbers that you can easily
test for.
Or, if the V in each released version has its own number space, how
is any external code ever going to cope with that? They'd need to
have a #ifdef so complex (to handle all the versions they support,
which can be lots more than FreeBSD still do) that it would become
unworkable.
Or, one could simply prohibit pullups of anything which affects the
way things compile - the released versions would have V frozen
forever to the value it had when the release was made, and while
external code would need to be fixed once, changing from the old
#ifdef FreeBSD (or NetBSD in our case)
into
#if defined(FreeBSD) && V < 1002
/* the old way */
#else
/* the new way */
#endif
or is the whole thing is FreeBSD unique
#ifdef FreeBSD
#if V < 1002
/* the old way */
#else
/* the new way */
#endif
#else
/* the alternative for other systems
#endif
That seems safe enough, but it really isn't.
Consider instead a different kind of source incompat change.
Say someone discovers that BrokenSYS has a prototype (and
implementation) of a standard interface, that is different
than the standards require. That might be because it is an
old version, pre-standardisation, and the standard decided
to ignore BrokenSYS and define it the way everyone else did
it. Or it might just have been an error by whoever implemented
the standard function on BrokenSYS.
External code which detects this just do (as <sys/exec_elf.h>
users who wanted the Elf_Versym stuff did wrt NetBSD) just handle
this like
#ifdef BrokenSYS
/*
* BrokenSYS has been broken forever, and refuse to
* change their definition!
*/
/* implement a workaround */
#else
/* do it the standard way */
#endif
Now, let's assume that management of BrokenSYS "evolves" and
it is decided to make the change, and what's more, since it
clearly is a non-conformance that has existed in their systems
since forever, implement the change in all released versions,
as well as the forthcoming one.
But just above we (almost) decided that pulling up source-breaking
changes is to be prohibited using the V implementation, so if
BrokenSYS happened to be FreeBSD, what would they do then?
This makes no sense to me. You'd have version bumps and the
code would just cope. You're also able to layer on to of the version
bumps the extra #defines you suggest. Some software does this:
define different things based on the version, but others test it directly.
With all software, it evolves, things break, unbreak, and weird stuff
happens. With the version bump you can know what to do. If you
rely solely on the programmer adding this or that define and then
trying to base stuff off that, you wind up with trouble when items
are added to structures: there's no compile time way to effectively
detect that.
| people are able to directly test to since all the bumps are
| documented,
That's no unreasonable.
| and out of tree authors have one place to look for a workaround.
as they do with what I am proposing, just the once place is even
more obvious - if something breaks because of a change that was
made in a header, look in the header, it will be documented there.
That then affects exactly the people who need to be aware of it, and
is (as it should be) invisible to everyone else (even others who
use the header, but not the part that changed).
To me (again, a matter of opinion) that's clearly better than being
pointed at a list of several thousand version bumps (in our example,
there would be 1002) and expecting them to read and understand all of
those to try and work out which one of them it was that affected their
code.
Anyway, I've offered an alternative that works for another project so
you don't have to reinvent it or use a scheme that sounds fragile or
that caused us problems in the past. You're free to do with that info
what you will.
Warner