[v1,1/3] Add system-wide tunables: ldconfig part

Message ID xnh6htdwqt.fsf@greed.delorie.com (mailing list archive)
State Superseded
Headers
Series System-wide tunables |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_glibc_build--master-arm fail Testing failed

Commit Message

DJ Delorie Feb. 28, 2024, 4:43 a.m. UTC
  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 stores a pre-parsed value for the tunable, and
the ID of the tunable, to improve load-time performance.
---
 elf/Makefile               |   1 +
 elf/cache.c                |  65 +++++-
 elf/ldconfig.c             |  21 +-
 elf/tunconf.c              | 408 +++++++++++++++++++++++++++++++++++++
 elf/tunconf.h              |  40 ++++
 sysdeps/generic/dl-cache.h |   6 +
 sysdeps/generic/ldconfig.h |   2 +
 7 files changed, 539 insertions(+), 4 deletions(-)
 create mode 100644 elf/tunconf.c
 create mode 100644 elf/tunconf.h
  

Comments

Florian Weimer Jan. 22, 2026, 10:05 a.m. UTC | #1
* DJ Delorie:

> +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);

Debug code?

> +  // Leading whitespace has already been stripped.

Style guide still says not to use //, maybe we should change this?

> +  // 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;

Typo: second trimming should check value for spaces, not name.

> +  //  printf("get_tunconf_ext: found %lu entries\n", count);

Looks like debug left-overs?

> +  { NULL, 't', N_("TUNCONF"), 0, N_("Use TUNCONF as configuration file for tunables"), 0},

I suggest to add a “tunable NAME VALUE” directive to the existing
configuration files.  This way, we automatically get include file
processing and /etc/ld.so.conf.d support.

I would use a minimal parser: split the tunable by whitespace, and copy
over the rest of the line (and following lines if ending with \)
verbatim into the cache.  This way, we can reuse the existing string
parser (mostly, we need to update the ':' splitting, which isn't needed
anymore).

Thanks,
Florian
  
DJ Delorie Jan. 29, 2026, 5:28 p.m. UTC | #2
Florian Weimer <fweimer@redhat.com> writes:
>> +  printf("%s:%d: `%s'\n", filename, lineno, line);
>
> Debug code?

Yup.  I took it out in 4/4 but I'll move that edit.

>> +  // Leading whitespace has already been stripped.
>
> Style guide still says not to use //, maybe we should change this?

Fixed throughout.

>> +  // 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;
>
> Typo: second trimming should check value for spaces, not name.

Fixed.

>> +  //  printf("get_tunconf_ext: found %lu entries\n", count);
>
> Looks like debug left-overs?

Yup, removed.

>> +  { NULL, 't', N_("TUNCONF"), 0, N_("Use TUNCONF as configuration file for tunables"), 0},
>
> I suggest to add a “tunable NAME VALUE” directive to the existing
> configuration files.  This way, we automatically get include file
> processing and /etc/ld.so.conf.d support.

The original goal was to not change the syntax of ld.so.conf or anything
that would require retraining sysadmins (which is why we use ldconfig to
do things; admins doing "the same old thing" get the new functionality).
The existing tunconf patch does have include functionality, though.

The code to read the files is not very big.

> I would use a minimal parser: split the tunable by whitespace, and copy
> over the rest of the line (and following lines if ending with \)
> verbatim into the cache.  This way, we can reuse the existing string
> parser (mostly, we need to update the ':' splitting, which isn't needed
> anymore).

This would not allow us to warn or error on invalid tunables, nor would
it help improve load speed.  The existing cache pre-parses everything to
help the programs skip a step at load time.
  

Patch

diff --git a/elf/Makefile b/elf/Makefile
index 36c04baf02..eb2c2b3f58 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 8a618e11fa..1146bba877 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,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
@@ -394,7 +420,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);
@@ -498,6 +524,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);
@@ -509,6 +557,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);
 }
@@ -1106,3 +1161,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 b64c54b53e..b6463b6464 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;
@@ -1240,6 +1252,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)
@@ -1315,6 +1330,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..70e04e5328
--- /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 919e49ffc8..41521f5a96 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 7dafa791f2..31ea2ec5d6 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);