-1 was used everywhere, but -EBADF or -EBADFD started being used in various
places. Let's make things consistent in the new style.
Note that there are two candidates:
EBADF 9 Bad file descriptor
EBADFD 77 File descriptor in bad state
Since we're initializating the fd, we're just assigning a value that means
"no fd yet", so it's just a bad file descriptor, and the first errno fits
better. If instead we had a valid file descriptor that became invalid because
of some operation or state change, the other errno would fit better.
In some places, initialization is dropped if unnecessary.
So far recurse_dir() will call the callback whenever we enter a
directory, and then pass the struct dirent for that directory, and an fd
for the directory the dirent is part of (i.e. the parent of the
directory we call things for). For the top-level dir the function is
invoked for we will not call the callback however, because we have no
dirent for that, and not fd for the directory the top-level dir is part
of. Let's add a flag to call it anyway, and in that case pass a NULL
dirent and -1 as directory fd.
This is useful when we want to treat the top-level dir the same as any
dir further down.
This is done opt-in since the callback must be ablet to handle a NULL
dirent and a -1 directory fd.
One of the niceties of recurse_dir()/recurse_dir_at() is that the path
argument is decoration, it's not used for actually accessing the fs in
anyway. That's very handy in environments where chroots and relative
paths are used, as we can path in any path we like and the recursion
function will suffix with whatever it discovers but will not try to make
sense of the prefix you pass.
This works great, except that the recurse_dir_at() wrapper broke that:
it adjusted the path if NULL to "." simply for the sake of making
openat() on the top work. Let's make this adjustment more local and do
it only for the openat() itself, and otherwise pass the path through the
way we got it. This means: if a caller really wants the paths that are
concatenated to start with a "." it can just pass that. This way the
caller gets full control back of the path prefix. Win!
Note that all current users of recurse_dir_at() don't pass NULL as
second arg, hence this check is without any real effect for now. It's
preparation for future uses however.
Apparently memory sanitizer doesn't grok getdents64() properly. Let's
address that by explicitly marken memory initialized by getdents64() as
unpoisoned.
We already have a similar loop twice, let's make it easier to read via
an iteration macro.
(The new macro is a bit more careful even, as it verifies the full
dirent fits into the remaining buffer when returning it)
Let's use the underlying Linux API directly, instead of
opendir()/readdir(). This makes it possible for us to do a single memory
allocation for all directory entries in common cases, instead of one for
each entry.
Apparently glibc already has a helper for this. (Not in the man pages
for Linux, but FreeBSD does document these cryptic helpers, and its
exported by glibc. That should be good enough for us.)
libc nftw() shows its age a bit, let's replace it with a more moden
infra that is built around openat(), O_PATH, statx(). This makes the
interface less prone to races and cleans up the API a bit adding
substantially more functionality.