RFD/patch:system-wide tunables: parser preview

DJ Delorie dj@redhat.com
Tue Dec 5 03:48:14 GMT 2023


I've gotten to the point where the ldconfig half of the problem has a
working solution, which is probably the best time to show and tell so
people can argue whether I'm doing it the right way or not ;-)

Here's the chunk of the cache file that's worth discussion:

/* 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 */];
};

/* An array of [num_tunables] of these follows the header.  */
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;
};

The cache data allows for a filter (specified in the flags, with a
corresponding string at flag_offset), as it's been requested to have
per-program overrides in /etc/tunables.conf for programs that need it.
I have not yet written the code to *read* this cached data, so no
filters have yet been defined.

I was originally going to have a separate program and cache, but it
was argued that training sysadmins to do something new was much harder
than just extending something they already knew how and when to do.

As for testing, I'm running a new-built cache on my RHEL 8 work
machine (glibc 2.28) without problems.  In theory, this patch could be
applied today (sans debug printfs ;) without affecting systems.


>From 1c8f70636d761b5559df34d3e78697a38cced53e Mon Sep 17 00:00:00 2001
From: DJ Delorie <dj@redhat.com>
Date: Mon, 4 Dec 2023 20:48:18 -0500
Subject: Add system-wide tunables: ldconfig part

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 character
that controls overridability by the GLIBC_TUNABLES env var:

!glibc.foo=0
   ^ May be made more secure
+glibc.foo=0
   ^ May be overridden
-glibc.foo=0
   ^ May not be overridden

Internally, each tunable will later have a default "overridability"
and logic for what "more secure" means.

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 can store a pre-parsed value for the tunable,
and the ID of the tunable, to improve load-time performance.

Note that the parser still contains my debug code in case it helps
understand the code.

diff --git a/elf/Makefile b/elf/Makefile
index c1e9038a44..4a7885f93f 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -221,6 +221,7 @@ ldconfig-modules := \
   readlib \
   static-stubs \
   stringtable \
+  tunconf \
   xmalloc \
   xstrdup \
   # ldconfig-modules
diff --git a/elf/cache.c b/elf/cache.c
index 8149f889ba..4d58d24c0b 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;
@@ -278,7 +279,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)
     {
@@ -287,6 +289,30 @@ 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);
+      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].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
@@ -397,7 +423,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);
@@ -501,6 +527,28 @@ write_extensions (int fd, uint32_t str_offset,
       ext->sections[xid].size = hwcaps_size;
     }
 
+  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)
+    {
+      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);
@@ -512,6 +560,13 @@ 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 (hwcaps_array);
   free (ext);
 }
@@ -1109,3 +1164,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 02387a169c..b2e2fc844d 100644
--- a/elf/ldconfig.c
+++ b/elf/ldconfig.c
@@ -43,6 +43,7 @@
 #include <dl-cache.h>
 #include <dl-hwcaps.h>
 #include <dl-is_dso.h>
+#include "tunconf.h"
 
 #include <dl-procinfo.h>
 
@@ -50,6 +51,10 @@
 # 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;
@@ -1229,6 +1241,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)
@@ -1304,6 +1319,8 @@ main (int argc, char **argv)
 
   search_dirs ();
 
+  parse_tunconf (tunconfig_file, true, 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..a59a2d64c0
--- /dev/null
+++ b/elf/tunconf.c
@@ -0,0 +1,408 @@
+/* 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 "tunconf.h"
+
+/* Declared in chroot_canon.c.  */
+extern char *chroot_canon (const char *chroot, const char *name);
+
+/*----------------------------------------------------------------------*/
+
+#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_DEFAULT = 0,	// let the internal code decide
+      TOP_ALLOW,	// let the environment variable override
+      TOP_STRICT,	// internal code will only allow "stricter" setting
+      TOP_DENY		// no override allowed
+} TOP;
+
+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;
+struct tunable_entry_int **entry_list_next = &entry_list;
+
+/*----------------------------------------------------------------------*/
+
+static void parse_tunconf_include (const char *tunconfig_file, unsigned int lineno,
+				   bool do_chroot, const char *pattern, const char *opt_chroot);
+
+static void
+add_tunable (char *line, const char *filename, int lineno)
+{
+  TOP top = TOP_DEFAULT;
+  char *name;
+  char *value;
+  char *eq;
+  char *orig_line;
+  struct tunable_entry_int *entry;
+  int i, id;
+
+  orig_line = line;
+  printf("%s:%d: `%s'\n", filename, lineno, line);
+
+  // Leading whitespace has already been stripped.
+
+  if (*line == '!' || *line == '+' || *line == '-')
+    {
+      switch (*line)
+	{
+	case '!':
+	  top = TOP_STRICT;
+	  break;
+	case '+':
+	  top = TOP_ALLOW;
+	  break;
+	case '-':
+	  top = TOP_DENY;
+	  break;
+	}
+      printf("TOP: %d\n", top);
+      line ++;
+      while (*line && isspace(*line))
+	line ++;
+    }
+
+  // NAME now points to the start of the tunable name.
+  name = line;
+
+  // Look for the '=' separator.
+  eq = strchr (line, '=');
+  if (eq == NULL)
+    {
+      printf("%s:%d: syntax error, line ignored: `%s' (missing '=')\n",
+	     filename, lineno, orig_line);
+      return;
+    }
+
+  if (eq == name)
+    {
+      printf("%s:%d: syntax error, line ignored: `%s' (missing tunable name)\n",
+	     filename, lineno, orig_line);
+      return;
+    }
+
+  // At this point, EQ actually points to '='
+  value = eq + 1;
+
+  while (*value && isspace(*value))
+    value ++;
+
+  if (*value == 0)
+    {
+      printf("%s:%d: syntax error, line ignored: `%s' (missing value)\n",
+	     filename, lineno, 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 (name[strlen(value)-1]))
+    value[strlen(value)-1] = 0;
+
+  id = -1;
+  for (i=0; i<TUNABLE_NAME_MAX; i++)
+    if (strcmp (tunable_list[i].name, name) == 0)
+      {
+	id = i;
+	break;
+      }
+
+  printf("top %d name `%s' (%d) value `%s'\n", top, name, id, value);
+  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;
+
+  *entry_list_next = entry;
+  entry_list_next = & (entry->next);
+}
+
+void
+parse_tunconf (const char *filename, int do_chroot, char *opt_chroot)
+{
+  FILE *file = NULL;
+  char *line = NULL;
+  const char *canon;
+  size_t len = 0;
+  unsigned int lineno;
+
+  if (do_chroot && opt_chroot)
+    {
+      canon = chroot_canon (opt_chroot, filename);
+      if (canon)
+	file = fopen (canon, "r");
+      else
+	canon = filename;
+    }
+  else
+    {
+      canon = filename;
+      file = fopen (filename, "r");
+    }
+
+  if (file == NULL)
+    {
+      if (errno != ENOENT)
+	error (0, errno, _("\
+Warning: ignoring configuration file that cannot be opened: %s"),
+	       canon);
+      if (canon != filename)
+	free ((char *) canon);
+      return;
+    }
+
+  /* No threads use this stream.  */
+  __fsetlocking (file, FSETLOCKING_BYCALLER);
+
+  if (canon != filename)
+    free ((char *) canon);
+  lineno = 0;
+  do
+    {
+      ssize_t n = getline (&line, &len, file);
+      if (n < 0)
+	break;
+
+      ++lineno;
+      if (line[n - 1] == '\n')
+	line[n - 1] = '\0';
+
+      /* Because the file format does not know any form of quoting we
+	 can search forward for the next '#' character and if found
+	 make it terminating the line.  */
+      *strchrnul (line, '#') = '\0';
+
+      /* Remove leading whitespace.  NUL is no whitespace character.  */
+      char *cp = line;
+      while (isspace (*cp))
+	++cp;
+
+      /* If the line is blank it is ignored.  */
+      if (cp[0] == '\0')
+	continue;
+
+      if (!strncmp (cp, "include", 7) && isblank (cp[7]))
+	{
+	  char *dir;
+	  cp += 8;
+	  while ((dir = strsep (&cp, " \t")) != NULL)
+	    if (dir[0] != '\0')
+	      parse_tunconf_include (filename, lineno, do_chroot, dir, opt_chroot);
+	}
+      else
+	add_tunable (cp, filename, lineno);
+    }
+  while (!feof_unlocked (file));
+
+  /* Free buffer and close file.  */
+  free (line);
+  fclose (file);
+}
+
+/* Handle one word in an `include' line, a glob pattern of additional
+   config files to read.  */
+static void
+parse_tunconf_include (const char *tunconfig_file, unsigned int lineno,
+		       bool do_chroot, const char *pattern, const char *opt_chroot)
+{
+  if (opt_chroot != NULL && pattern[0] != '/')
+    error (EXIT_FAILURE, 0,
+	   _("need absolute file name for configuration file when using -r"));
+
+  char *copy = NULL;
+  if (pattern[0] != '/' && strchr (tunconfig_file, '/') != NULL)
+    {
+      if (asprintf (&copy, "%s/%s", dirname (strdupa (tunconfig_file)),
+		    pattern) < 0)
+	error (EXIT_FAILURE, 0, _("memory exhausted"));
+      pattern = copy;
+    }
+
+  glob64_t gl;
+  int result;
+  if (do_chroot && opt_chroot)
+    {
+      char *canon = chroot_canon (opt_chroot, pattern);
+      if (canon == NULL)
+	return;
+      result = glob64 (canon, 0, NULL, &gl);
+      free (canon);
+    }
+  else
+    result = glob64 (pattern, 0, NULL, &gl);
+
+  switch (result)
+    {
+    case 0:
+      for (size_t i = 0; i < gl.gl_pathc; ++i)
+	parse_tunconf (gl.gl_pathv[i], false, NULL);
+      globfree64 (&gl);
+      break;
+
+    case GLOB_NOMATCH:
+      break;
+
+    case GLOB_NOSPACE:
+      errno = ENOMEM;
+      /* Fall through.  */
+    case GLOB_ABORTED:
+      if (opt_verbose)
+	error (0, errno, _("%s:%u: cannot read directory %s"),
+	       tunconfig_file, lineno, pattern);
+      break;
+
+    default:
+      abort ();
+      break;
+    }
+
+  free (copy);
+}
+
+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;
+    }
+  printf("get_tunconf_ext: found %lu entries\n", count);
+
+  /* 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 %lu bytes in get_tunable_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;
+      printf("tei->top is %d\n", tei->top);
+      switch (tei->top)
+	{
+	case TOP_DEFAULT:
+	  tec->flags |= TUNCONF_OVERRIDE_DEFAULT;
+	  break;
+	case TOP_ALLOW:
+	  tec->flags |= TUNCONF_OVERRIDE_ALLOW;
+	  break;
+	case TOP_STRICT:
+	  tec->flags |= TUNCONF_OVERRIDE_STRICTER;
+	  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;
+      printf("tec->flags is %08x\n", tec->flags);
+
+      ++ count;
+      tei = tei->next;
+    }
+
+  return thc;
+}
diff --git a/elf/tunconf.h b/elf/tunconf.h
new file mode 100644
index 0000000000..a6c5f0dd9a
--- /dev/null
+++ b/elf/tunconf.h
@@ -0,0 +1,40 @@
+#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_DEFAULT	0x00000000
+#define TUNCONF_OVERRIDE_ALLOW		0x00000004
+#define TUNCONF_OVERRIDE_STRICTER	0x00000008
+#define TUNCONF_OVERRIDE_DENY		0x0000000C
+
+#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, int do_chroot, 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 bd39ff7fb7..53dea39ce5 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
   };
diff --git a/sysdeps/generic/ldconfig.h b/sysdeps/generic/ldconfig.h
index 59da12ad89..aa531b781f 100644
--- a/sysdeps/generic/ldconfig.h
+++ b/sysdeps/generic/ldconfig.h
@@ -75,6 +75,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);



More information about the Libc-alpha mailing list