Skip to content

libc: argp, obstack, argz, backtrace, the res_* resolver API, getaddrinfo_a - #169

Draft
teo1747 wants to merge 27 commits into
mainfrom
libc-more3
Draft

teo1747 wants to merge 27 commits into
mainfrom
libc-more3

Conversation

@teo1747

@teo1747 teo1747 commented Oct 30, 2026

Copy link
Copy Markdown
Member

Stacked on #168 (which is stacked on #167 and #166): until those merge, this diff shows their commits too. The new work is the last two commits.

What it adds

These are the glibc pieces the library still lacked. Sun RPC is the one exception: glibc itself dropped it in 2.32.

  • <argp.h>: GNU's argument parser.
    • Parses by nested option tables, over a private getopt state, so the program's optind is not touched.
    • --help and --usage are laid out exactly as glibc 2.36 lays them out: columns, the order of groups, clusters and entries, where doc strings wrap, and ARGP_HELP_FMT.
    • Also argp_help, argp_state_help, argp_usage, argp_error, argp_failure.
    • On AVR, whose int is 16 bits, the ARGP_KEY_* numbers sit at the top of int's range.
  • <obstack.h>: inline functions where glibc's are macros. <argz.h>, <envz.h>: glibc's edge cases kept; argz_replace counts the entries it changed, not the replacements.
  • <execinfo.h>:
    • backtrace walks with EmbCC's unwinder over .eh_frame on Linux and EmbLinkOS. lib/rt/unwind.c gains libgcc's _Unwind_Backtrace for this.
    • x86-64 code leaves the frame pointer out at -O1, so the frame chain only works on AArch64. The driver's comment saying x86-64 always keeps one was stale and is corrected.
    • Elsewhere backtrace gives the caller only.
  • <resolv.h>, <arpa/nameser.h>, on top of resolv.c's transport:
    • res_init, res_query, res_search, res_querydomain, res_mkquery, res_send, and the res_n* forms.
    • dn_comp, dn_expand, dn_skipname, the ns_name_* codecs, ns_initparse, ns_parserr.
    • res_hnok and its siblings, and BIND's HEADER, T_* and C_*.
  • getaddrinfo_a, gai_suspend, gai_error, gai_cancel: one thread per lookup.
  • Driver: -lresolv and -lanl are taken as the library's own, as -lpthread is.
  • getopt permutes lazily, as GNU's does. While the caller looks at an option, optind is where that option ended in the argv it was given, which is also argp's state->next. Its long-option messages are glibc's wording.

How it was checked

  • glibc 2.36 as referee, by transcript on x86-64 and AArch64 Linux QEMU:
    • argz, envz, obstack, and 26 argp runs: help, usage, errors, ARGP_HELP_FMT, ARGP_IN_ORDER, --, ARGP_LONG_ONLY.
    • The resolver against a DNS server the program forks on 127.0.0.1: query, search order, NXDOMAIN, NODATA, SERVFAIL, a refused port.
    • Both transcripts are identical line for line.
  • tests/exec/posix-lib4.c asserts glibc's answers. glibc passes it on both architectures, and ours passes it on Linux, bare x86-64 (libc.sh) and the boards.
  • linux.sh's glibc-names program now links with -lresolv -lanl and calls a function from each library.
  • Full matrix and fuzz before this is marked ready.

…tory once

The driver appended <binary's directory>/include as a system directory
ahead of the defaults paths.c computes -- a line from before paths.c
existed. That directory holds the compiler's declarations-only
<string.h>, which paths.c deliberately puts LAST so it cannot shadow the
C library's; added early, it did, and every build-tree compile without
-I lost the POSIX half of <string.h> (strtok_r, strlcpy, ...). Run from
$PATH, argv[0] has no '/', and the directory was ./include -- wherever
the user happened to be. It is now added only when paths.c found no
support files, which is what it was for.

With it gone, a directory named both by -I and by the defaults was
searched twice in a row, and #include_next from the C library's
<limits.h> found that same file again, include guard set, so INT_MAX
was never defined (the libc build itself broke). GCC searches such a
directory once, where -I put it, as a system directory; so does EmbCC
now, by plat_same_dir: device and inode where the host has them (macOS,
Linux), spelling elsewhere -- EmbLinkOS's newlib stat has no inodes.

tests/golden/install.sh compiles <string.h> + <limits.h> with and
without -I lib/libc/include; the first case fails on the old driver and
the second on one without the dedupe. build.ebm regenerated for
platform_posix.c's <sys/stat.h>.
The first of the POSIX layer EmbLinkOS needs to leave newlib behind --
the part that asks the operating system nothing new.

  <string.h>  strtok_r, strerror_r (XSI's), strsep, strlcpy, strlcat,
              memmem; strcasestr, memrchr, strchrnul, explicit_bzero
  <stdlib.h>  setenv, unsetenv (declared before, never defined), putenv,
              clearenv; the mkstemp family, mkdtemp, mktemp; realpath;
              posix_memalign, memalign, reallocarray; qsort_r; getsubopt;
              random/srandom/initstate/setstate, the *rand48 family and
              rand_r, each giving glibc's sequence for a seed
  modules     <libgen.h>, <fnmatch.h>, <getopt.h> (getopt_long, and
              permuting as glibc's and newlib's do), <search.h> (AVL
              tsearch, a growing hsearch, the _r forms)

<features.h> decides what a C header declares beyond ISO C: everything
unless the program asked for ISO C alone, and GNU's additions hidden
too when it names a POSIX version and nothing more, as glibc does.

The environment owns what it allocates: the startup vector is copied on
the first change, setenv's strings are freed when replaced, putenv's
are the caller's and never are. realpath resolves a component at a time,
so ".." after a symlink leaves the link's target. The entropy behind
temporary names is one function now (__tmp_bits), which stdio's tmpnam
will share.

tests/exec/posix-strings.c holds the values glibc 2.36 printed for the
same calls, run against both on a Linux kernel; where they differ the
test says why (tree shape, a growing hash table). Its tree checks catch
an AVL delete that skips rebalancing.
<stdio.h> gains fdopen, fileno, fmemopen, open_memstream, getline,
getdelim, dprintf, asprintf and their v forms, fseeko, ftello, ctermid,
flockfile/ftrylockfile/funlockfile and the _unlocked functions, and
GNU's fopencookie, setbuffer and setlinebuf. <err.h> is new; its
messages begin with program_invocation_short_name, which Linux's
startup now sets from argv[0].

What it took inside:

  a FILE may be something other than a descriptor -- four functions and
  a cookie, laid out as glibc's cookie_io_functions_t -- and the seven
  places stdio reached the backend go through one dispatcher each;

  the stream lock NESTS. flockfile(stdout) and then printf takes it
  twice, and the old lock, which is not recursive, deadlocked -- and on
  a target with no futex spun forever, every single-threaded program;

  freopen reopens the stream it is given. It closed it and called
  fopen, which handed back a different FILE, so freopen(.., stdout)
  left stdout closed;

  past FOPEN_MAX, streams come in chunks from the heap that are never
  freed, so the flush at exit cannot walk into a closed one.

fmemopen follows glibc since 2.22 (where a NUL goes); open_memstream's
length moves only when a write passes it, which is POSIX's rule and not
glibc 2.36's. tests/exec/posix-stdio.c and posix-files.c (mkstemp,
realpath through symlinks, freopen of stdout) run on our libc and,
posix-stdio at least, on glibc, on a Linux kernel; linux-libc-exec.sh
runs both, and posix-strings, with no -I.
EmbLD wrote every image it linked with the mode a plain file gets,
0644 under the usual umask -- so `embcc hello.c -o hello` on a Linux
host made a program that could not be run, and a test that installed
one in a guest as /bin/sh got EACCES from the kernel's exec. It now
gets 0777 less the umask, as GNU ld gives its output: ELF executables,
script-linked images and EMBX alike, through plat_make_executable
(chmod where the host has modes, nothing in the ISO host layer).
os/backend.h gains POSIX's half: about a hundred primitives, each
shaped like the call it serves (dup, pipe, fcntl, fstat, the *at forms,
fork/execve/spawn/waitpid, sigaction and the masks, clocks, mmap, ppoll,
sockets, termios, ptys, uname and sysconf's questions), with Linux's
numbers for everything that needs one. A backend that cannot do one
still defines it, failing with ENOSYS: os/absent.inc is the list,
guarded per primitive, which the bare-metal and posixlike backends
include whole.

os/linux/posix.c implements every one as syscalls on both 64-bit ABIs,
the numbers read out of the kernel's own uapi headers: open's flags
translated where aarch64 moves them, the kernel's shorter termios and
utsname converted, a signal restorer placed as bytes (checked against
both assemblers), posix_spawn as fork + actions + execve with the
child's error returned over a close-on-exec pipe.

Above them, src/posix: <unistd.h>, <fcntl.h>, <sys/stat.h>,
<dirent.h> (`.` and `..` returned, positions kept), the *at functions,
the exec family with PATH search and the script rule, <sys/wait.h>,
<spawn.h>, system and popen on posix_spawn (so they work where fork
does not), <sys/mman.h>, poll and select, sockets, inet_pton/ntop,
getaddrinfo over the hosts file, the OS's resolver or a DNS client
(resolv.conf, search domains, TCP for truncated answers), termios and
ptys, the user and group files, sysconf and the rest. Every public
POSIX name is weak -- none is reserved to the implementation, and a
program's own `read` or `getline` must win. sys/types.h takes hosted
widths (64-bit ino_t and dev_t, 32-bit uids).

Signals are two models: the kernel's where it delivers (Linux) and
lib/libc's own elsewhere -- a handler table, a mask and a pending set,
so a blocked raise is delivered on unblock -- and abort now raises
SIGABRT first, ending the program even if a handler returns.

tests/exec/posix-{fd,process,signals,net}.c hold what glibc 2.36 gave
for the same calls on the same kernel (they were run against both);
posix-process is its own /bin/sh and child. linux-libc-exec.sh runs
all 19 of its programs on x86-64 and aarch64 Linux: 76 runs exit 42.
The EmbLinkOS backend now defines every primitive of os/backend.h. It
carries over what EmbLinkOS's newlib adapter (user/lib/syscalls.c,
spawn_compat.inc) worked out POSIX means on that kernel, under its
three honest categories -- REAL, VACUOUS (true whatever is done: a
mask where nothing is delivered), ABSENT (ENOSYS, never pretended):

  paths       normalized here ("..", ".", the 256-byte limit), the
              working directory this library's, a child's named in its
              spawn or by PWD
  files       O_CLOEXEC translated (the kernel's is 0x100000),
              O_DIRECTORY and O_NOFOLLOW honoured by looking, rename the
              kernel's atomic one -- it was unlink-then-rename, which a
              crash between the two turned into a lost destination
  directories a snapshot that grows until the kernel's silent
              truncation is ruled out -- it was a fixed 512 entries
  descriptors dup, dup2, pipes, close-on-exec and the status flags;
              record locks ENOSYS
  processes   posix_spawn on the native actions (descriptor snapshot,
              cwd, kept status), attributes refused; waitpid on owned
              child handles, a cancelled wait EINTR; fork and exec
              ENOSYS
  time        the RTC in seconds, uptime in milliseconds, a thread's CPU
              time, each at the precision it has
  memory      mmap's refusals where the kernel cannot honour a request
  network     IPv4 sockets and the kernel's resolver; poll on fd_poll
  terminals   termios and the window size through terminal control

tests/golden/libc-backends.sh reads the primitives out of os/backend.h
and requires every backend -- bare metal (Cortex-M and AVR), posixlike,
Linux on both ABIs, EmbLinkOS -- to define all 139; it fails naming the
one a backend left out.
…efuse

time:
- tz.c reads TZ as POSIX writes it (quoted names, Mm.w.d / Jn / n rules,
  times past 24h or negative), and zone names as TZif files (v1-3, with the
  footer rule) under TZDIR or /usr/share/zoneinfo; no TZ is /etc/localtime,
  an unusable one is UTC. tzset sets tzname/timezone/daylight.
- struct tm carries tm_gmtoff and tm_zone; strftime's %z and %Z read them.
- mktime reads tm_isdst as glibc does on the mornings the clocks change.
- strptime: the "C" locale's conversions and glibc's readings -- digits
  stop where one more could not fit, %I joins %p, %C with %y, and the
  fields a date implies (wday, yday, and a date from %U/%W + weekday).

Every value refereed against glibc 2.36 on Linux; tests/exec/posix-time.c
holds them and runs in linux-libc-exec on x86-64 and AArch64.

spawn:
- posix_spawn_file_actions_t and posix_spawnattr_t are handles: init
  allocates, destroy frees, NULL is EINVAL.
- __os_spawn_flags names the attribute flags a backend honours; setflags
  refuses the rest with ENOTSUP instead of a spawn that ignores them.
- posix_spawnp: the system's directories without PATH, and a file the
  kernel cannot run (ENOEXEC) is run by /bin/sh.

EmbLinkOS backend: embk_empty_env for its crt0; libc-emblinkos also makes
an empty libm.a, so -lm finds no other library's.

libc.sh: the harness's kill (newlib's floor, always EINVAL) is left out,
so posix-signals reaches the library's own -- all 366 programs pass.
What EmbLinkOS's own tests asked for, building every program it has
against this library (LIBC=emblibc in EmbLinkOS):

- kill of another process is ENOSYS where the kernel cannot signal one
  (ESRCH claimed there was none; mrsh's job control hung on it).
- spawn: POSIX's EBADF for a descriptor at or past OPEN_MAX from the
  add call itself, and __os_spawn_check, a new primitive, lets a backend
  refuse at that moment what its spawn can never do (EmbLinkOS: more
  than 61 actions, a path past 255 bytes, fchdir, an open flag it has no
  bit for).
- EmbLinkOS backend: getrlimit(RLIMIT_NOFILE) is the kernel's 64, and
  fcntl(F_SETFL) with a flag it cannot set is EINVAL, as that OS says.
- getpass, with echo off; <malloc.h> (malloc_usable_size, mallinfo2,
  malloc_stats, valloc, pvalloc -- QuickJS sizes its heap with the
  first); <string.h> brings <strings.h> and <stdlib.h> <alloca.h>, as
  glibc's do; ARG_MAX gives way to a build's own.

C++ on EmbLinkOS: make libcxx-emblinkos, and libc-emblinkos now also
makes librt.a with lib/rt/unwind.c (-D__EMBCC_RT_UNWIND) -- its programs
start with -nostartfiles, so nothing registers tables for libgcc's.
Everything there is built with --unwind-tables: for x86_64-elf C code
has none by default, and the unwinder's own frames need them or every
throw ends where it starts. EMBLINKOS_ARCH=aarch64 builds the other set.

Fixed on the way:
- <filesystem> kept its own copy of __os_fileinfo, shorter than the
  backend's since the POSIX fields: every status() wrote past it (a
  hang). libc-backends.sh now compares libcxx's copies of the contract
  with it, structs and all 45 prototypes; it fails on the old copy.
- 16-bit int: tz.c's 1 << 18 buffer and an unsigned-short compare that
  made week 0 a huge day number on AVR; malloc's 64 KB chunk constant.

Tests: malloc-gnu.c (refereed against glibc), posix-process's EBADF;
libc.sh 367, linux-libc-exec 84 runs, libcxx-std's struct tm is 56
bytes now, glibc's.
- newlib's limits.h guards itself with _LIBC_LIMITS_H_, the word this
  library's used both as its guard and to tell the compiler's half it
  was there. With newlib's headers first (tools/build-libstdcxx.sh puts
  them there) its #include_next reached ours, ours was already "done",
  and the compiler's half -- INT_MAX -- was never read: libsupc++ and
  the libstdc++ tests failed to compile. Ours has its own guard now,
  sets the shared word only when it is the first C library's half, and
  always passes the chain on.
- posix-process read argc/argv before knowing it ran where a kernel
  passes them: on Xtensa and the ESP32-C3, whose startup promises no
  argc, argv[1] was a wild load. The /bin/sh behaviour is under
  EMBCC_TEST_HAS_FS with the rest of what needs a filesystem.

Full matrix: 840/860; these were the two real failures. The others --
timeouts and one killed run, all on the busy machine -- pass alone, as
do these two: libc-limits, libm-long-double, rt, sroa, stack-args,
frameless, cxx-libstdcxx, cxx-libsupcxx, xtensa-exec, embsim-esp32c3,
install, libc-system, big-functions, linux-libc-exec, exec-boards.
The bare-metal and posix-like backends implement __os_write by calling write, __os_sbrk by calling sbrk: those names are the program's or the board's (weak defaults it replaces), newlib-style. The POSIX layer defined them too, weakly, calling the primitives back, so whichever an image linked first won -- and when proc.o came first on Xtensa and MIPS, sbrk called __os_sbrk called sbrk until the stack ran out. open, close, read, write, lseek, isatty, _exit and sbrk move to os/posix-names.c, built for Linux and EmbLinkOS only.
- <pthread.h>: threads (create, join, detach, exit, once, atfork, GNU's
  tryjoin/timedjoin/setname/getname), attributes, the three kinds of
  mutex with timedlock and clocklock, condition variables with a clock
  and clockwait, read-write locks (readers first, as glibc's default),
  barriers, spin locks, keys with destructor rounds. What cannot be
  honoured is refused: process-shared objects, robust and priority
  mutexes ENOTSUP, cancellation ENOTSUP (no cancellation points), a
  policy other than SCHED_OTHER EPERM.
- <semaphore.h>: unnamed semaphores; sem_open ENOSYS, pshared ENOTSUP.
- One thread table (src/threads/pthread.c) serves both spellings, keys
  live in each thread's record (no TLS needed beneath them), and
  pthread_exit longjmps to its thread's start, where destructors run.
  fork runs atfork's handlers through a weak reference.

C11's layer is rewritten on it, which fixes three bugs it had:
- mtx_lock could sleep holding the lock: a lock freed between its try
  and its exchange was taken by the exchange and then slept on. The
  three-state loop is the standard one now.
- mtx_timedlock and cnd_timedwait read C11's absolute TIME_UTC deadline
  as a duration -- decades, for any real deadline.
- thrd_exit called exit(), ending the program from any thread.
tss_* work now (they always failed). timespec_get reads the backend's
real-time clock to the nanosecond, not __os_time's whole seconds, so
"now plus 30 ms" is not already past. Linux threads get glibc's 8 MiB
stacks, mapped on demand. Where there is no futex (one thread), a timed
condition wait waits out its deadline instead of returning at once.

tests/exec/posix-threads.c: glibc 2.36 and this library both exit 42 on
x86-64 and AArch64 Linux; on the boards (no threads, maybe no clock) it
checks what one thread can show.
- <regex.h>: POSIX basic and extended syntax with glibc's numbers and
  messages, GNU's \+ \? \| \< \> \b \B \w \W \s \S, back-references,
  REG_STARTEND; POSIX's leftmost-longest match by backtracking, a
  single-character repeat as a loop (no stack per character), and
  REG_ESPACE instead of a hang past 20 million steps at one position.
  tests/exec/posix-regex.c: 88 cases whose expected answers are glibc's
  own output for the same calls.
- <glob.h>: every flag glibc has, braces and tildes included; <ftw.h>:
  nftw with FTW_ACTIONRETVAL, and ftw.
- <syslog.h>: RFC 3164 lines to /dev/log. With no logger there, a line
  is not dropped as glibc drops it: /dev/console under LOG_CONS, else
  standard error -- EmbLinkOS has no syslog daemon and says a capability
  must never silently vanish.
- <langinfo.h>, <nl_types.h>: the "C" locale's items, CODESET "UTF-8";
  no message catalogs, catgets returns the program's own text.

posix-glob.c (glob, nftw, ftw, syslog's LOG_PERROR line) and
posix-regex.c run in linux-libc-exec with glibc's answers; on the
boards they check what needs no filesystem. The manual lists it all.
glibc 2.34 folded those libraries into libc and keeps them as empty
archives, so Makefiles still name them; EmbCC's libc has POSIX threads,
the clocks and openpty in libc.a. The driver takes the names as its own,
as it takes -lm, instead of "cannot find libpthread.a". A program that
calls what the library lacks (dlopen) still fails the link, naming it.
tests/golden/linux.sh links one through the driver and runs it.
…ak symbol

A weak reference left undefined has no PC-relative address in a
position-independent program, and EmbCC's x86-64 code addresses
everything PC-relative: every EmbLinkOS app (linked PIE) that pulled in
fork failed to link on __pthread_atfork_run. The thread library sets
__atfork_hook when it is first used, or on pthread_atfork, so a program
that never names a pthread function still does not link it -- and a
forked child of a threaded program always learns it has one thread.

On EmbLinkOS (LIBC=emblibc, its `test pthreads` running this tree's
tests/exec/posix-threads.c): exit 42, beside cxxlib, posix, posix-shell,
spawn/process-compat, fd-sharing, terminal, tcc, embbuild, env, tz.
…'s does

604db0f moved read, write, open, close and _exit there, out of the portable half this golden builds; without it the archive it checks for closure needs them from nowhere.
addend_for() restated x86-64's end-of-instruction bias for every kind
but the absolute ones, so a TPOFF32 -- an offset from the thread
pointer, not from the pc -- went out as `t-4` while the object said
`t`. Assembled, every __thread access read four bytes below its
variable. It now asks target_reloc_addend(), the function the object
writer uses.

asm-S.sh compared only how MANY relocations the two objects had; it
now compares each code relocation's offset, type, symbol and addend,
and has a __thread case.
The kernel starts a thread with no thread pointer and crt0 built a
block for the first thread only, so errno was one variable that every
thread wrote, and a __thread variable in a second thread faulted.

crt0 now hands over the template it found (os/tls-template.h:
image, filesz, memsz, align), after the first thread's pointer is in
place. From then errno.c answers with a thread-local errno, and the
backend's thread trampoline lays a copy of the template out on the new
thread's own stack and installs it before the thread runs anything
that could touch one. On the stack because then nothing has to free
it after errno's last use; a template over 256 KiB makes
pthread_create fail with EAGAIN.

A crt0 that predates the hand-over leaves the template empty, and the
library behaves as before: one errno, threads with no block.

posix-threads.c: four threads each set errno and an initialised
__thread variable, wait at a barrier, and read both back (glibc agrees;
Linux, all ten boards at every -O, and EmbLinkOS's test pthreads pass).
The spin-lock failure no longer returns 42, the success code.
The shell's expansions of a word, in its order: tildes, parameters
with every ${...} form, command substitution through /bin/sh -c (by
posix_spawn, its standard error dup2'd to /dev/null or closed when
there is none -- a redirection in the command makes a shell without
/dev/null exit), arithmetic with C's operators, field splitting on
IFS, pathname expansion, quote removal.

The field is built as a glob pattern: what came from quotes is
escaped, so glob reads it exactly, and an unmatched field loses its
backslashes, which is quote removal done at the right moment.

posix-wordexp.c: 105 strings with glibc 2.36's answers, except eleven
rows where glibc departs from the shell (bash --posix answers those):
''x'' is two words there, a quoted empty expansion WRDE_NOSPACE, $G
not globbed, % << > ?: stop the arithmetic silently, ${U-ok} under
WRDE_UNDEF an error, a failing command WRDE_NOSPACE. ${U:?} returns 0
in glibc and kills the program on the second call; here it is
WRDE_BADVAL. Under the Linux harness the test is its own /bin/sh.
glibc passes the rest; ours passes all on x86-64 and AArch64 Linux
and the vector/flag checks on all ten boards.
Any to any through Unicode: UTF-8, UTF-16/-32 (and LE, BE), UCS-2/-4
(and LE, BE), WCHAR_T, ASCII, ISO-8859-1, ISO-8859-15, CP1252. glibc's
conventions, found by running it: unmarked UTF-16/UTF-32 follow the
machine's order and write a mark (alone, if the first character does
not fit), a reset writes it again, //IGNORE reports EILSEQ once it has
converted the rest, //TRANSLIT wins when both are given.

iconv-translit.h is what //TRANSLIT substitutes: glibc 2.36's C-locale
replacements, read by converting every code point from U+00A0 to
U+10FFFF to ASCII//TRANSLIT -- 1784 characters, kept as 963 runs; ours
gives the same for every one.

posix-iconv.c: 59 conversions with glibc's results, except two rows:
names like Utf_8 are accepted, and UTF-8 past U+10FFFF is refused (glibc
passes F4 90 80 80 through). Passes on glibc and on our x86-64 and
AArch64 Linux, the ten boards, PowerPC, big-endian MIPS, SPARC and
ColdFire.
…itution

`#define m a.m` gives `a.m` and never more (C11 6.10.3.4p2). The
inner `m` was left alone while m was being expanded, but nothing
remembered that: pre-expanded as an argument and substituted into
another macro's body, it was rescanned and expanded again -- once per
level, so `R(s.m)` through `R(x) Q(x)` read `s.a.a.a.m`. glibc's
<signal.h> and ours give siginfo_t's fields this way (si_pid is
_sifields._kill.si_pid), so `printf("%d", si.si_pid)` through any
variadic macro failed to compile.

A suppressed name is now written with a byte in front (the
preprocessor works on text), which expand_text copies through without
expanding and which is taken out where a line's expansion is finished,
and before # and ## see an argument's spelling.

macro-self-reference.c: through Q, R(Q), S(R(Q)), a variadic macro and
#, and the siginfo shape; clang gives 42, the old compiler refuses it.
setjmp's buffer with the signal mask beside it: sigsetjmp saves the mask
when asked and then is setjmp -- it must be, only setjmp's own frame can
be returned to twice -- and siglongjmp puts the mask back before it
jumps, so a handler left by siglongjmp leaves its signal deliverable.
(The two functions are stdlib/signal.c's, committed with the rest.)

posix-sigjmp.c: saved and unsaved masks, and out of a handler twice;
glibc agrees.
Measured against glibc 2.36's exported functions; what was missing and
is now here, by area:

POSIX and XSI: waitid, posix_fallocate, fexecve, nice and
getpriority/setpriority, the sched_* parameter calls, getresuid and the
rest (POSIX.1-2024), sigqueue, clock_getcpuclockid, POSIX timers
(the kernel's for a signal on Linux; a thread waiting on a condition
variable for SIGEV_THREAD and elsewhere), locale objects and every _l
function (one locale: newlocale gives it for C, POSIX, C.UTF-8),
wcscasecmp, wcpcpy, wcsdup, mbsnrtowcs, wcsnrtombs, wcwidth and
wcswidth (glibc's C.UTF-8 widths for every code point, generated),
a64l/l64a, swab, getw/putw, tempnam, strfmon, fmtmsg, getdate, the
Bessel functions (FreeBSD's msun), utmpx, message queues and System V
IPC (Linux), <aio.h> (a thread per request, as glibc), if_nametoindex
and the rest of <net/if.h>, the hosts/services/protocols/networks
databases one entry at a time and their _r lookups, ftok, shadow
passwords, cuserid, ttyslot.

Linux: <sys/syscall.h> generated from the kernel's headers, and
syscall(); epoll, eventfd, timerfd, signalfd, inotify, sendfile,
splice, tee, copy_file_range, memfd_create, close_range, statfs,
sysinfo, prctl, mount, xattrs, affinity, unshare/setns, getauxval (a
new seam primitive, __os_auxv), getifaddrs, prlimit. Each is ENOSYS
elsewhere unless it means something without that kernel (sendfile
copies, statfs comes from statvfs, close_range closes).

GNU and BSD: error/error_at_line, gettext and its family (msgid in
"C"), dlopen/dlsym/dlerror/dladdr and dl_iterate_phdr, <elf.h>,
<fts.h>, random_r and drand48_r, ecvt/fcvt/gcvt, arc4random,
secure_getenv, on_exit, rawmemchr, strerrorname_np and sigabbrev_np,
fcloseall, the *_unlocked wide calls, getpwent_r/fgetpwent/putpwent and
the group equivalents, getusershell, the large-file *64 names (macros
and symbols), C23's narrowing arithmetic (rounded once, by round-to-
odd), totalorder, payloads, canonicalize, fromfp; isinf returns the
sign, as glibc's.

posix-lib2.c and posix-linux2.c hold every answer to glibc 2.36's (each
ran there first as a transcript, identical line for line but for
dl_iterate_phdr in glibc's dynamically linked program); both pass on
glibc and on ours for x86-64 and AArch64.
…nd arc4random only of a system

Xtensa's wchar_t is 16 bits, as GCC's there, so 0x1F600 arrives as
0xF600 -- a private-use character, width 1 -- which is right; the test
assumed 32 bits. And on a board arc4random's bytes come from its clock
and entropy source, which EmbSim and QEMU give differently, so the Pico's
corpus comparison saw posix-lib2 run 27 more instructions on one.
…thread _np

- getcontext, setcontext, makecontext, swapcontext for x86-64 and
  AArch64: bytes with their disassembly, as setjmp's are (libc.sh now
  checks these too), generated by assembling the source with GNU as.
  The layout is the kernel's ucontext and glibc's ucontext_t, so a
  signal handler's third argument reads the same; on Linux the signal
  mask is saved and restored by rt_sigprocmask, elsewhere there is no
  kernel mask to keep.
- sighold, sigrelse, sigignore, sigpause, sigset (XSI), bsd_signal,
  sysv_signal, ssignal/gsignal, sigblock/sigsetmask/siggetmask.
- errno: all 133 of Linux's numbers in <errno.h>, each with glibc's
  message and name (strerror, strerrorname_np), read from glibc 2.36.
- feenableexcept, fedisableexcept, fegetexcept (x87 and SSE both on
  x86-64; FPCR's optional trap bits on AArch64), fesetexcept,
  fetestexceptflag, fegetmode/fesetmode.
- open_wmemstream; ulimit; <mntent.h>; <netinet/ether.h>; inet_lnaof,
  inet_netof, inet_makeaddr; canonicalize_file_name, euidaccess, lchmod,
  getpt, group_member, wcstoq; qecvt and the long double family;
  twalk_r.
- pthread: setaffinity_np and getaffinity_np by each thread's kernel id,
  attr affinity applied as the thread starts, getattr_np with the real
  stack (a new seam primitive, __os_thread_stack), the default
  attributes, pthread_sigqueue, pthread_clockjoin_np, the mutexattr
  priority ceiling, and GNU's _np aliases.
- Linux: preadv2, pwritev2, recvmmsg, sendmmsg, renameat2, execveat,
  tgkill, statx.

posix-lib3.c holds glibc's answers (but for pthread_getattr_np on the
first thread, which glibc cannot answer without /proc); posix-ucontext.c
runs coroutines on x86-64 and AArch64 and agrees with glibc. Both pass
on Linux (both architectures), the ten boards, PowerPC, MIPS, SPARC,
ColdFire and Xtensa.
…, getaddrinfo_a

argp parses by nested option tables through a private getopt state and
writes --help and --usage laid out as glibc 2.36 lays them out: its
columns, the order of groups, clusters and entries, where doc strings
wrap, ARGP_HELP_FMT. obstack's macros are inline functions where they
can be; argz and envz keep glibc's edge cases (argz_replace counts the
entries it changed).

backtrace walks with the unwinder over .eh_frame where the program has
it (Linux, EmbLinkOS): lib/rt gains libgcc's _Unwind_Backtrace. x86-64
code leaves frame pointers out, so the frame chain is AArch64's only.

<resolv.h> and <arpa/nameser.h>: res_init, res_query, res_search,
res_querydomain, res_mkquery, res_send and the res_n* forms, dn_comp,
dn_expand, dn_skipname, the ns_name_* codecs, ns_initparse and
ns_parserr, res_hnok and its siblings, over resolv.c's transport.
getaddrinfo_a, gai_suspend, gai_error and gai_cancel run a thread per
lookup. The driver takes -lresolv and -lanl as its own.

getopt permutes lazily, as GNU's does, so optind while an option is
looked at is where it ended in the argv given; its long-option messages
are glibc's words, the ambiguous one with its possibilities.

All of it refereed against glibc 2.36 by transcript on x86-64 and
AArch64; tests/exec/posix-lib4.c asserts what glibc printed.
… Linux exec links

Bare x86_64-elf has neither the unwinder nor a frame chain, so its
backtrace is the caller alone. The hand links in linux-libc-exec.sh now
name librt.a after libc.a, as the driver does: backtrace's unwinder is
there, and on AArch64 libgcc's would otherwise answer, knowing none of
the program's frames.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant