From: DJ Delorie Date: Fri, 26 Jun 2026 03:30:39 +0000 (-0400) Subject: Add system-wide tunables: ldconfig part X-Git-Tag: glibc-2.44~87 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e24269f74b8cd37ce33652e752f8012c3bfcdadc;p=thirdparty%2Fglibc.git 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 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 --- diff --git a/elf/Makefile b/elf/Makefile index 6514428294..d01db49171 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 #include #include +#include /* 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); +} 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 #include #include +#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 @@ -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..5e77729e22 --- /dev/null +++ b/elf/tunconf.c @@ -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 . */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define TUNABLES_INTERNAL +#include +#include + +#include +#include +#include +#include +#include + +#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 index 0000000000..b446211f94 --- /dev/null +++ b/elf/tunconf.h @@ -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)) 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; + } + } } 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);