[PATCH v8 1/5] Add system-wide tunables: ldconfig part
Arjun Shankar
arjun@redhat.com
Wed Jun 24 01:14:41 GMT 2026
Hi DJ,
> Adds support for reading /etc/tunables.conf
>
> The file contains one line per tunable, like this:
>
> glibc.foo.bar=14
> glibc.malloc.more=0
>
> Additionally, each line can be prefixed with a single word or character
> that controls overridability by the GLIBC_TUNABLES env var:
>
> overridable glibc.foo=0
> +glibc.foo=0
> ^ May be overridden (the default)
> nonoverridable glibc.foo=0
> -glibc.foo=0
> ^ May not be overridden
>
> The tunable cache format allows for a filter to be assigned to
> each tunable, to be used at program start to decide if a tunable
> applies to that program. No such filters have yet been specified.
>
> The cache format also stores a pre-parsed value for the tunable, and
> the ID of the tunable, to improve load-time performance.
I looked at this with a focus on what's changed since the last
revision I reviewed.
This looks good to me with three minor changes so I'm going to drop
the RB here already:
Reviewed-by: Arjun Shankar <arjun@redhat.com>
> ---
> elf/Makefile | 1 +
> elf/cache.c | 86 ++++++++++-
> elf/ldconfig.c | 23 ++-
> elf/tunconf.c | 299 +++++++++++++++++++++++++++++++++++++
> elf/tunconf.h | 38 +++++
> sysdeps/generic/dl-cache.h | 20 +++
> sysdeps/generic/ldconfig.h | 6 +-
> 7 files changed, 466 insertions(+), 7 deletions(-)
> create mode 100644 elf/tunconf.c
> create mode 100644 elf/tunconf.h
>
> diff --git a/elf/Makefile b/elf/Makefile
> index 5ede78c590..8ce764705c 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -225,6 +225,7 @@ ldconfig-modules := \
> readlib \
> static-stubs \
> stringtable \
> + tunconf \
> xmalloc \
> xstrdup \
> # ldconfig-modules
> diff --git a/elf/cache.c b/elf/cache.c
> index a6dc85dc0f..58b1412b32 100644
> --- a/elf/cache.c
> +++ b/elf/cache.c
> @@ -36,6 +36,7 @@
> #include <dl-cache.h>
> #include <version.h>
> #include <stringtable.h>
> +#include <tunconf.h>
>
> /* Used to store library names, paths, and other strings. */
> static struct stringtable strings;
> @@ -275,7 +276,8 @@ check_new_cache (struct cache_file_new *cache)
>
> /* Print the extension information in *EXT. */
> static void
> -print_extensions (struct cache_extension_all_loaded *ext)
> +print_extensions (struct cache_extension_all_loaded *ext,
> + const char *cache_data)
> {
> if (ext->sections[cache_extension_tag_generator].base != NULL)
> {
> @@ -284,6 +286,43 @@ print_extensions (struct cache_extension_all_loaded *ext)
> ext->sections[cache_extension_tag_generator].size, stdout);
> putchar ('\n');
> }
> + if (ext->sections[cache_extension_tag_tunables].base != NULL)
> + {
> + struct tunable_header_cached *thc;
> + struct tunable_entry_cached *tec;
> + int i, count;
> +
> + thc = (struct tunable_header_cached *)
> + ext->sections[cache_extension_tag_tunables].base;
> + tec = thc->tunables;
> + count = thc->num_tunables;
> + printf("tunables sig 0x%08x ver 0x%08x count %u\n",
> + thc->signature, thc->version, thc->num_tunables);
> + /* Check that COUNT won't overflow our data block. */
> + assert (ext->sections[cache_extension_tag_tunables].base
> + + ext->sections[cache_extension_tag_tunables].size
> + == (void *) & tec[count]);
OK. Assert exact value.
> + for (i = 0; i < count; ++ i)
> + {
> + printf (" [%d] %s = %s [flags 0x%08x (",
> + i,
> + cache_data + tec[i].name_offset,
> + cache_data + tec[i].value_offset,
> + tec[i].flags);
> + if (tec[i].flags & TUNCONF_FLAG_PARSED)
> + printf ("parsed,");
> + if (tec[i].flags & TUNCONF_FLAG_NEGATIVE)
> + printf ("negative,");
> + if ((tec[i].flags & TUNCONF_FLAG_OVERRIDABLE)
> + == TUNCONF_OVERRIDE_ALLOW)
> + printf ("overridable");
> + else
> + printf ("nonoverridable");
OK. Flag values now printed as strings.
> + if (tec[i].flag_offset != 0)
> + printf (",'%s'", cache_data + tec[i].flag_offset);
> + printf (")]\n");
> + }
> + }
> }
>
> /* Print the whole cache file, if a file contains the new cache format
> @@ -394,7 +433,7 @@ print_cache (const char *cache_name)
> cache_new->libs[i].hwcap, hwcaps_string,
> cache_data + cache_new->libs[i].value);
> }
> - print_extensions (&ext);
> + print_extensions (&ext, cache_data);
> }
> /* Cleanup. */
> munmap (cache, cache_size);
> @@ -466,6 +505,18 @@ write_extensions (int fd, uint32_t str_offset,
> if (p->used)
> hwcaps_array[p->section_index] = str_offset + p->name->offset;
>
> + struct tunable_header_cached *tunable_data;
> + size_t tunable_size;
> + size_t tunable_aligner = 0;
> +
> + tunable_data = get_tunconf_ext (str_offset);
> +
> + if (tunable_data == NULL)
> + {
> + /* There is no section for tunables data. */
> + hwcaps_offset -= sizeof (struct cache_extension_section);
> + }
OK. This happened because we are writing a cache with no tunable data
when the configuration has none. The offset comes from a compile time
calculation that assumes all data, so we reduce it now that there's no
tunable data.
> +
> /* This is the offset of the generator string. */
> uint32_t generator_offset = hwcaps_offset;
> if (hwcaps_count == 0)
> @@ -498,6 +549,23 @@ write_extensions (int fd, uint32_t str_offset,
> ext->sections[xid].size = hwcaps_size;
> }
>
> + if (tunable_data != NULL)
> + {
> + uint32_t tunable_offset_ua;
> + uint32_t tunable_offset;
> +
> + tunable_size = TUNCONF_SIZE (tunable_data);
> + tunable_offset_ua = generator_offset + strlen (generator);
> + tunable_offset = ALIGN_UP (tunable_offset_ua, 8);
> + tunable_aligner = tunable_offset - tunable_offset_ua;
> +
> + ++xid;
> + ext->sections[xid].tag = cache_extension_tag_tunables;
> + ext->sections[xid].flags = 0;
> + ext->sections[xid].offset = tunable_offset;
> + ext->sections[xid].size = tunable_size;
> + }
> +
> ++xid;
> ext->count = xid;
> assert (xid <= cache_extension_count);
> @@ -509,6 +577,14 @@ write_extensions (int fd, uint32_t str_offset,
> || write (fd, generator, strlen (generator)) != strlen (generator))
> error (EXIT_FAILURE, errno, _("Writing of cache extension data failed"));
>
> + if (tunable_data)
> + {
> + if (write (fd, " ", tunable_aligner) != tunable_aligner
> + || write (fd, tunable_data, tunable_size) != tunable_size)
> + error (EXIT_FAILURE, errno, _("Writing of cache tunable data failed"));
> + free (tunable_data);
> + }
> +
> free (hwcaps_array);
> free (ext);
> }
> @@ -1106,3 +1182,9 @@ out_fail:
> free (temp_name);
> free (file_entries);
> }
> +
> +struct stringtable_entry *
> +cache_store_string (const char *string)
> +{
> + return stringtable_add (&strings, string);
> +}
> diff --git a/elf/ldconfig.c b/elf/ldconfig.c
> index 070e933df6..11b063eb5c 100644
> --- a/elf/ldconfig.c
> +++ b/elf/ldconfig.c
> @@ -44,12 +44,17 @@
> #include <dl-cache.h>
> #include <dl-hwcaps.h>
> #include <dl-is_dso.h>
> +#include "tunconf.h"
>
>
> #ifndef LD_SO_CONF
> # define LD_SO_CONF SYSCONFDIR "/ld.so.conf"
> #endif
>
> +#ifndef TUNABLES_CONF
> +# define TUNABLES_CONF SYSCONFDIR "/tunables.conf"
> +#endif
> +
> /* Get libc version number. */
> #include <version.h>
>
> @@ -107,9 +112,12 @@ static int opt_ignore_aux_cache;
> /* Cache file to use. */
> static char *cache_file;
>
> -/* Configuration file. */
> +/* Configuration file for libraries. */
> static const char *config_file;
>
> +/* Configuration file for tunables. */
> +static const char *tunconfig_file;
> +
> /* Name and version of program. */
> static void print_version (FILE *stream, struct argp_state *state);
> void (*argp_program_version_hook) (FILE *, struct argp_state *)
> @@ -127,7 +135,8 @@ static const struct argp_option options[] =
> { NULL, 'X', NULL, 0, N_("Don't update symbolic links"), 0},
> { NULL, 'r', N_("ROOT"), 0, N_("Change to and use ROOT as root directory"), 0},
> { NULL, 'C', N_("CACHE"), 0, N_("Use CACHE as cache file"), 0},
> - { NULL, 'f', N_("CONF"), 0, N_("Use CONF as configuration file"), 0},
> + { NULL, 'f', N_("CONF"), 0, N_("Use CONF as configuration file for libraries"), 0},
> + { NULL, 't', N_("TUNCONF"), 0, N_("Use TUNCONF as configuration file for tunables"), 0},
> { NULL, 'n', NULL, 0, N_("Only process directories specified on the command line. Don't build cache."), 0},
> { NULL, 'l', NULL, 0, N_("Manually link individual libraries."), 0},
> { "format", 'c', N_("FORMAT"), 0, N_("Format to use: new (default), old, or compat"), 0},
> @@ -164,6 +173,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
> case 'f':
> config_file = arg;
> break;
> + case 't':
> + tunconfig_file = arg;
> + break;
> case 'i':
> opt_ignore_aux_cache = 1;
> break;
> @@ -421,7 +433,7 @@ add_dir_1 (const char *line, const char *from_file, int from_line)
> }
>
> static void
> -add_dir_callback (const char *line, const char *from_file, int from_line)
> +add_dir_callback (char *line, const char *from_file, int from_line)
> {
> if (!strncasecmp (line, "hwcap", 5) && isblank (line[5]))
> error (0, 0, _("%s:%u: hwcap directive ignored"), from_file, from_line);
> @@ -1089,6 +1101,9 @@ main (int argc, char **argv)
> if (config_file == NULL)
> config_file = LD_SO_CONF;
>
> + if (tunconfig_file == NULL)
> + tunconfig_file = TUNABLES_CONF;
> +
> if (opt_print_cache)
> {
> if (opt_chroot != NULL)
> @@ -1164,6 +1179,8 @@ main (int argc, char **argv)
>
> search_dirs ();
>
> + parse_tunconf (tunconfig_file, opt_chroot);
> +
> if (opt_build_cache)
> {
> save_cache (cache_file);
> diff --git a/elf/tunconf.c b/elf/tunconf.c
> new file mode 100644
> index 0000000000..9ccbf5f560
> --- /dev/null
> +++ b/elf/tunconf.c
> @@ -0,0 +1,299 @@
> +/* Manage /etc/tunables.*
> + Copyright (C) 1999-2023 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + This program is free software; you can redistribute it and/or modify
> + it under the terms of the GNU General Public License as published
> + by the Free Software Foundation; version 2 of the License, or
> + (at your option) any later version.
> +
> + This program is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + GNU General Public License for more details.
> +
> + You should have received a copy of the GNU General Public License
> + along with this program; if not, see <https://www.gnu.org/licenses/>. */
> +
> +#include <alloca.h>
> +#include <argp.h>
> +#include <assert.h>
> +#include <error.h>
> +#include <inttypes.h>
> +#include <glob.h>
> +#include <libgen.h>
> +#include <libintl.h>
> +#include <locale.h>
> +#include <programs/xmalloc.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdio_ext.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#define TUNABLES_INTERNAL
> +#include <elf/dl-tunables.h>
> +#include <unistd.h>
> +
> +#include <ldconfig.h>
> +#include <dl-cache.h>
> +#include <version.h>
> +#include <stringtable.h>
> +#include <array_length.h>
> +
> +#include "tunconf.h"
> +
> +/*----------------------------------------------------------------------*/
> +
> +#ifndef TUNABLES_CONF
> +# define TUNABLES_CONF SYSCONFDIR "/tunables.conf"
> +#endif
> +
> +#ifndef TUNABLES_CACHE
> +# define TUNABLES_CACHE SYSCONFDIR "/tunables.cache"
> +#endif
> +
> +/* Tunable Override Policies. */
> +typedef enum {
> + TOP_ALLOW = 0, /* let the environment variable override */
> + TOP_DENY /* no override allowed */
> +} TOP;
OK. Strict and Deny removed.
> +
> +struct tunable_entry_int {
> + struct stringtable_entry *name;
> + struct stringtable_entry *value;
> + TOP top;
> + int tunable_id;
> + int value_is_negative:1;
> + int value_was_parsed:1;
> + unsigned long long value_ull;
> + signed long long value_sll;
> +
> + struct tunable_entry_int *next;
> +};
> +
> +struct tunable_entry_int *entry_list;
> +
> +/*----------------------------------------------------------------------*/
> +
> +static void
> +add_tunable (char *line, const char *filename, int lineno)
> +{
> + TOP top = TOP_ALLOW;
> + char *name;
> + char *value;
> + char *eq;
> + char *orig_line;
> + struct tunable_entry_int *entry;
> + int i, id;
> + static struct tunable_entry_int **entry_list_next = &entry_list;
OK. This is now a local.
> +
> + orig_line = line;
> +
> + /* Leading whitespace has already been stripped. */
> +
> + /* Canonicalize the line. */
> + for (i=0; line[i]; i++)
> + {
> + if (line[i] == '\t')
> + line[i] = ' ';
> + if (line[i] == '\n' || line[i] == '\r')
> + {
> + line[i] = '\0';
> + break;
> + }
> + }
OK.
> +
> + /* Parse modifiers. */
> + while (*line)
> + {
> + if (strncmp (line, "overwritable ", 13) == 0)
> + {
> + top = TOP_ALLOW;
> + /* The line++ below skips the space. */
> + line += 12;
> + }
> + else if (strncmp (line, "nonoverwritable ", 16) == 0)
> + {
> + top = TOP_DENY;
> + line += 15;
> + }
New words for +/-.
Should be "overridable" and "nonoverridable" to match the commit
message and manual.
> + else switch (*line)
> + {
> + case '+':
> + top = TOP_ALLOW;
> + break;
> + case '-':
> + top = TOP_DENY;
> + break;
> + case ' ':
> + break;
> +
> + default:
> + goto done;
> + }
> + line ++;
> + }
> + done:
> +
> + /* NAME now points to the start of the tunable name. */
> + name = line;
> +
> + /* Look for the '=' separator. */
> + eq = strchr (line, '=');
> + if (eq == NULL)
> + {
> + error_at_line (0, 0, filename, lineno,
> + "syntax error, line ignored: `%s' (missing '=')",
> + orig_line);
> + return;
> + }
> +
> + if (eq == name)
> + {
> + error_at_line (0, 0, filename, lineno,
> + "syntax error, line ignored: `%s' (missing tunable name)",
> + orig_line);
> + return;
> + }
> +
> + /* At this point, EQ actually points to '='. */
> + value = eq + 1;
> +
> + while (*value && isspace(*value))
> + value ++;
> +
> + if (*value == 0)
> + {
> + error_at_line (0, 0, filename, lineno,
> + "syntax error, line ignored: `%s' (missing value)",
> + orig_line);
> + return;
> + }
> +
> + /* VALUE now points to the start of the value. */
> +
> + /* Split the string into name and value c-strings. */
> + *eq = 0;
> + /* Trim trailing whitespace off NAME. */
> + while (*name && isspace (name[strlen(name)-1]))
> + name[strlen(name)-1] = 0;
> + /* Trim trailing whitespace off VALUE. */
> + while (*value && isspace (value[strlen(value)-1]))
> + value[strlen(value)-1] = 0;
> +
> + id = -1;
> + for (i = 0; i < array_length (tunable_list); i ++)
> + if (strcmp (tunable_list[i].name, name) == 0)
> + {
> + id = i;
> + break;
> + }
> + if (id == -1)
> + printf("%s:%d: Warning: tunable %s not recognized.\n",
> + filename, lineno, name);
Now we warn for unrecognized tunables.
Style issue with missing space before "(".
> +
> + entry = (struct tunable_entry_int *) xcalloc (sizeof (struct tunable_entry_int), 1);
> + entry->name = cache_store_string (name);
> + entry->value = cache_store_string (value);
> + entry->tunable_id = id;
> + entry->top = top;
> +
> + if (value[0] == '-')
> + {
> + entry->value_is_negative = 1;
> + if (sscanf (value, "%lld", &entry->value_sll) == 1)
> + entry->value_was_parsed = 1;
> + }
> + else
> + {
> + entry->value_is_negative = 0;
> + if (sscanf (value, "%lld", &entry->value_ull) == 1)
> + entry->value_was_parsed = 1;
> + }
So value_is_negative and value_was_parsed being read now.
The unsigned value should be scanned with "%llu".
> +
> + *entry_list_next = entry;
> + entry_list_next = & (entry->next);
> +}
> +
> +void
> +parse_tunconf (const char *filename, char *opt_chroot)
> +{
> + ldconfig_parse_config (filename, opt_chroot, add_tunable);
> +}
> +
> +struct tunable_header_cached *
> +get_tunconf_ext (uint32_t string_table_offset)
> +{
> + struct tunable_entry_int *tei;
> + struct tunable_header_cached *thc;
> + size_t count;
> + size_t size;
> +
> + /* First, count the number of entries we have. */
> + tei = entry_list;
> + count = 0;
> + while (tei != NULL)
> + {
> + ++ count;
> + tei = tei->next;
> + }
> + if (count == 0)
> + return NULL;
OK. Return NULL for zero entries.
> +
> + /* Allocate enough space for the whole cached block. */
> + size = sizeof (struct tunable_header_cached)
> + + sizeof (struct tunable_entry_cached) * count;
> + thc = (struct tunable_header_cached *) malloc (size);
> +
> + if (thc == NULL)
> + {
> + error (0, 0, _("Unable to allocate %zu bytes in get_tunconf_ext"), size);
> + return NULL;
> + }
> +
> + /* Now, fill in the structures. */
> +
> + thc->signature = TUNCONF_SIGNATURE;
> + thc->version = TUNCONF_VERSION;
> + thc->num_tunables = count;
> + thc->unused_1 = 0;
> +
> + tei = entry_list;
> + count = 0;
> + while (tei != NULL)
> + {
> + struct tunable_entry_cached *tec;
> +
> + tec = & ( thc->tunables[count] );
> +
> + tec->flags = 0;
> + if (tei->value_was_parsed)
> + tec->flags |= TUNCONF_FLAG_PARSED;
> + if (tei->value_is_negative)
> + tec->flags |= TUNCONF_FLAG_NEGATIVE;
> + switch (tei->top)
> + {
> + case TOP_ALLOW:
> + tec->flags |= TUNCONF_OVERRIDE_ALLOW;
> + break;
> + case TOP_DENY:
> + tec->flags |= TUNCONF_OVERRIDE_DENY;
> + break;
> + }
> +
> + tec->tunable_id = tei->tunable_id;
> + tec->name_offset = tei->name->offset + string_table_offset;
> + tec->value_offset = tei->value->offset + string_table_offset;
> + tec->flag_offset = 0;
> + tec->unused_1 = 0;
> + if (tei->value_is_negative)
> + tec->parsed_value = (uint64_t) tei->value_sll;
> + else
> + tec->parsed_value = (uint64_t) tei->value_ull;
> +
> + ++ count;
> + tei = tei->next;
> + }
> +
> + return thc;
> +}
> diff --git a/elf/tunconf.h b/elf/tunconf.h
> new file mode 100644
> index 0000000000..85e1c142fa
> --- /dev/null
> +++ b/elf/tunconf.h
> @@ -0,0 +1,38 @@
> +#define TUNCONF_SIGNATURE 0x7c3ba94f
> +#define TUNCONF_VERSION 0x01000000
> +
> +#define TUNCONF_FLAG_PARSED 0x00000001
> +#define TUNCONF_FLAG_NEGATIVE 0x00000002
> +
> +#define TUNCONF_FLAG_OVERRIDABLE 0x0000000C
> +#define TUNCONF_OVERRIDE_DENY 0x00000004
> +#define TUNCONF_OVERRIDE_ALLOW 0x00000000
OK. Number of policies went down and so did the flags.
> +
> +#define TUNCONF_FLAG_FILTER 0x0000ff00
> +#define TUNCONF_FILTER_PERPROC 0x00000100
> +
> +/* An array of [num_tunables] of these follows the below. */
> +struct tunable_entry_cached {
> + uint32_t flags;
> + uint32_t tunable_id;
> + uint32_t name_offset;
> + uint32_t value_offset;
> + uint32_t flag_offset;
> + uint32_t unused_1; /* for alignment */
> + uint64_t parsed_value;
> +};
> +
> +/* One of these is at the beginning of the tunable data block. */
> +struct tunable_header_cached {
> + uint32_t signature;
> + uint32_t version;
> + uint32_t num_tunables;
> + uint32_t unused_1; /* for alignment */
> + struct tunable_entry_cached tunables[0 /* num_tunables */];
> +};
> +
> +void parse_tunconf (const char *filename, char *opt_chroot);
> +
> +struct tunable_header_cached * get_tunconf_ext (uint32_t str_offset);
> +#define TUNCONF_SIZE(thc_p) (sizeof(struct tunable_header_cached) \
> + + thc_p->num_tunables * sizeof (struct tunable_entry_cached))
> diff --git a/sysdeps/generic/dl-cache.h b/sysdeps/generic/dl-cache.h
> index 26f0c1e0bd..972ab32b86 100644
> --- a/sysdeps/generic/dl-cache.h
> +++ b/sysdeps/generic/dl-cache.h
> @@ -220,6 +220,12 @@ enum cache_extension_tag
> size must be a multiple of 4. */
> cache_extension_tag_glibc_hwcaps,
>
> + /* Array of system-wide tunable information.
> +
> + For this section, 8-byte alignment is required, and the section
> + size must be a multiple of 8. */
> + cache_extension_tag_tunables,
> +
> /* Total number of known cache extension tags. */
> cache_extension_count
> };
> @@ -293,6 +299,20 @@ cache_extension_verify (struct cache_extension_all_loaded *loaded)
> hwcaps->flags = 0;
> }
> }
> + {
> + /* Section must not be empty, it must be aligned at 8 bytes, and
> + the size must be a multiple of 8. */
> + struct cache_extension_loaded *tun
> + = &loaded->sections[cache_extension_tag_tunables];
> + if (tun->size == 0
> + || ((uintptr_t) tun->base % 8) != 0
> + || (tun->size % 8) != 0)
> + {
> + tun->base = NULL;
> + tun->size = 0;
> + tun->flags = 0;
> + }
> + }
OK.
> }
>
> static bool __attribute__ ((unused))
> diff --git a/sysdeps/generic/ldconfig.h b/sysdeps/generic/ldconfig.h
> index 22d0fd0f82..800714659e 100644
> --- a/sysdeps/generic/ldconfig.h
> +++ b/sysdeps/generic/ldconfig.h
> @@ -74,6 +74,8 @@ extern void add_to_cache (const char *path, const char *filename,
> unsigned int isa_level,
> struct glibc_hwcaps_subdirectory *);
>
> +extern struct stringtable_entry *cache_store_string (const char *string);
> +
> extern void init_aux_cache (void);
>
> extern void load_aux_cache (const char *aux_cache_name);
> @@ -112,8 +114,8 @@ enum opt_format
> extern enum opt_format opt_format;
>
> /* Declared in ldconfig-parse.c */
> -typedef void (*ldconfig_parse_config_cb) (const char *line,
> - const char *from_file, int from_line);
> +typedef void (*ldconfig_parse_config_cb) (char *line,
> + const char *from_file, int from_line);
>
> void ldconfig_parse_config (const char *filename, char *opt_chroot,
> ldconfig_parse_config_cb cb);
> --
> 2.47.3
>
--
Arjun Shankar
he/him/his
More information about the Libc-alpha
mailing list