]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
Add system-wide tunables: ldconfig part
authorDJ Delorie <dj@redhat.com>
Fri, 26 Jun 2026 03:30:39 +0000 (23:30 -0400)
committerDJ Delorie <dj@redhat.com>
Tue, 30 Jun 2026 20:46:08 +0000 (16:46 -0400)
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.

Reviewed-by: Arjun Shankar <arjun@redhat.com>
elf/Makefile
elf/cache.c
elf/ldconfig.c
elf/tunconf.c [new file with mode: 0644]
elf/tunconf.h [new file with mode: 0644]
sysdeps/generic/dl-cache.h
sysdeps/generic/ldconfig.h

index 65144282941f036dd6b38c6061d23f7e8d086e04..d01db491716b021994b462e755715f1fb74dab8c 100644 (file)
@@ -225,6 +225,7 @@ ldconfig-modules := \
   readlib \
   static-stubs \
   stringtable \
+  tunconf \
   xmalloc \
   xstrdup \
   # ldconfig-modules
index a6dc85dc0f97b36bda4a763851f2fced6c15fbb3..58b1412b321d3c2843078958b573b51006c90363 100644 (file)
@@ -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]);
+      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");
+         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);
+    }
+
   /* 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);
+}
index 070e933df625f3f922195b458490459487436d50..11b063eb5c269e62751b2710cbab71c298f8a35c 100644 (file)
 #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 (file)
index 0000000..5e77729
--- /dev/null
@@ -0,0 +1,293 @@
+/* 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;
+
+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;
+
+  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;
+       }
+    }
+
+  /* Parse modifiers.  */
+  while (*line)
+    {
+      if (strncmp (line, "overridable ", 13) == 0)
+       {
+         top = TOP_ALLOW;
+         /* The line++ below skips the space.  */
+         line += 12;
+       }
+      else if (strncmp (line, "nonoverridable ", 16) == 0)
+       {
+         top = TOP_DENY;
+         line += 15;
+       }
+      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);
+
+  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, "%llu", &entry->value_ull) == 1)
+       entry->value_was_parsed = 1;
+    }
+
+  *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;
+
+  /* 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 *) xmalloc (size);
+
+  /* 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 (file)
index 0000000..b446211
--- /dev/null
@@ -0,0 +1,39 @@
+#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
+
+#define TUNCONF_FLAG_FILTER            0x0000ff00
+#define TUNCONF_FILTER_NONE            0x00000000
+#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))
index 26f0c1e0bd6406b53057313bd48940f8e6c943a0..972ab32b865466b730fb8492f1d1f72fb497a112 100644 (file)
@@ -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;
+      }
+  }
 }
 
 static bool __attribute__ ((unused))
index 22d0fd0f8268e93f4100413065fec002cfe10955..800714659ee30f67e1cd5c47e0650d608a2eefe8 100644 (file)
@@ -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);