diff options
Diffstat (limited to 'elf/ldconfig.c')
-rw-r--r-- | elf/ldconfig.c | 647 |
1 files changed, 647 insertions, 0 deletions
diff --git a/elf/ldconfig.c b/elf/ldconfig.c new file mode 100644 index 0000000000..6c2bcb2d4f --- /dev/null +++ b/elf/ldconfig.c @@ -0,0 +1,647 @@ +/* Copyright (C) 1999 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Andreas Jaeger <aj@suse.de>, 1999. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#include <argp.h> +#include <dirent.h> +#include <error.h> +#include <errno.h> +#include <libintl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "ldconfig.h" + +#ifndef LD_SO_CACHE +# define LD_SO_CACHE "/etc/ld.so.cache" +#endif + +#ifndef LD_SO_CONF +# define LD_SO_CONF "/etc/ld.so.conf" +#endif + +/* Get libc version number. */ +#include <version.h> + +#define PACKAGE _libc_intl_domainname + +struct lib_entry + { + int flags; + char *lib; + char *path; + }; + +static const struct +{ + const char *name; + int flag; +} lib_types [] = +{ + {"libc4", FLAG_LIBC4}, + {"libc5", FLAG_ELF_LIBC5}, + {"libc6", FLAG_ELF_LIBC6}, + {"glibc2", FLAG_ELF_LIBC6} +}; + + +/* List of directories to handle. */ +struct dir_entry +{ + char *path; + int flag; + struct dir_entry *next; +}; + +/* The list is unsorted, contains no duplicates. Entries are added at + the end. */ +static struct dir_entry *dir_entries; + +/* Flags for different options. */ +/* Print Cache. */ +static int opt_print_cache = 0; + +/* Be verbose. */ +int opt_verbose = 0; + +/* Build cache. */ +static int opt_build_cache = 1; + +/* Generate links. */ +static int opt_link = 1; + +/* Only process directories specified on the command line. */ +static int opt_only_cline = 0; + +/* Path to root for chroot. */ +static char *opt_chroot; + +/* Cache file to use. */ +static const char *cache_file; + +/* Configuration file. */ +static const char *config_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 *) + = print_version; + +/* Definitions of arguments for argp functions. */ +static const struct argp_option options[] = +{ + { "print-cache", 'p', NULL, 0, N_("Print cache"), 0}, + { "verbose", 'v', NULL, 0, N_("Generate verbose messages"), 0}, + { NULL, 'N', NULL, 0, N_("Don't build cache"), 0}, + { NULL, 'X', NULL, 0, N_("Don't generate links"), 0}, + { NULL, 'r', "ROOT", 0, N_("Change to and use ROOT as root directory"), 0}, + { NULL, 'C', "CACHE", 0, N_("Use CACHE as cache file"), 0}, + { NULL, 'f', "CONF", 0, N_("Use CONF as configuration file"), 0}, + { NULL, 'n', NULL, 0, N_("Only process directories specified on the command line. Don't build cache."), 0}, + { NULL, 0, NULL, 0, NULL, 0 } +}; + +/* Short description of program. */ +static const char doc[] = N_("Configure Dynamic Linker Run Time Bindings."); + +/* Prototype for option handler. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state); + +/* Data structure to communicate with argp functions. */ +static struct argp argp = +{ + options, parse_opt, NULL, doc, NULL, NULL, NULL +}; + + + +/* Handle program arguments. */ +static error_t +parse_opt (int key, char *arg, struct argp_state *state) +{ + switch (key) + { + case 'C': + cache_file = arg; + break; + case 'f': + config_file = arg; + break; + case 'N': + opt_build_cache = 0; + break; + case 'n': + opt_build_cache = 0; + opt_only_cline = 1; + break; + case 'p': + opt_print_cache = 1; + break; + case 'r': + opt_chroot = arg; + break; + case 'v': + opt_verbose = 1; + break; + case 'X': + opt_link = 0; + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Print the version information. */ +static void +print_version (FILE *stream, struct argp_state *state) +{ + fprintf (stream, "ldconfig (GNU %s) %s\n", PACKAGE, VERSION); + fprintf (stream, gettext ("\ +Copyright (C) %s Free Software Foundation, Inc.\n\ +This is free software; see the source for copying conditions. There is NO\n\ +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ +"), "1999"); + fprintf (stream, gettext ("Written by %s.\n"), + "Andreas Jaeger"); +} + +/* Add one directory to the list of directories to process. */ +static void +add_dir (const char *line) +{ + char *equal_sign; + struct dir_entry *entry, *ptr, *prev; + unsigned int i; + + entry = xmalloc (sizeof (struct dir_entry)); + entry->next = NULL; + + /* Search for an '=' sign. */ + entry->path = xstrdup (line); + equal_sign = strchr (entry->path, '='); + if (equal_sign) + { + *equal_sign = '\0'; + ++equal_sign; + entry->flag = FLAG_ANY; + for (i = 0; i < sizeof (lib_types) / sizeof (lib_types [0]); ++i) + if (strcmp (equal_sign, lib_types[i].name) == 0) + { + entry->flag = lib_types[i].flag; + break; + } + if (entry->flag == FLAG_ANY) + error (0, 0, _("%s is not a known library type"), equal_sign); + } + else + { + entry->flag = FLAG_ANY; + } + + /* Canonify path: for now only remove trailing slashes. */ + i = strlen (entry->path) - 1; + while (entry->path[i] == '/' && i > 0) + { + entry->path [i] = '\0'; + --i; + } + + ptr = dir_entries; + prev = ptr; + while (ptr != NULL) + { + /* Check for duplicates. */ + if (strcmp (ptr->path, entry->path) == 0) + { + if (opt_verbose) + error (0, 0, _("Path `%s' given more than once"), entry->path); + /* Use the newer information. */ + ptr->flag = entry->flag; + free (entry); + break; + } + prev = ptr; + ptr = ptr->next; + } + /* Is this the first entry? */ + if (ptr == NULL && dir_entries == NULL) + dir_entries = entry; + else if (ptr == NULL) + prev->next = entry; +} + + +/* Create a symbolic link from soname to libname in directory path. */ +static void +create_links (const char *path, const char *libname, const char *soname) +{ + char full_libname [PATH_MAX], full_soname [PATH_MAX]; + struct stat stat_lib, stat_so, lstat_so; + int do_link = 1; + int do_remove = 1; + /* XXX: The logics in this function should be simplified. */ + + /* Get complete path. */ + snprintf (full_libname, sizeof full_libname, "%s/%s", path, libname); + snprintf (full_soname, sizeof full_soname, "%s/%s", path, soname); + + /* Does soname already exist and point to the right library? */ + if (stat (full_soname, &stat_so) == 0) + { + if (stat (full_libname, &stat_lib)) + { + error (0, 0, _("Can't stat %s\n"), full_libname); + return; + } + if (stat_lib.st_dev == stat_so.st_dev + && stat_lib.st_ino == stat_so.st_ino) + /* Link is already correct. */ + do_link = 0; + else if (lstat (full_soname, &lstat_so) == 0 + && !S_ISLNK (lstat_so.st_mode)) + { + error (0, 0, _("%s is not a symbolic link\n"), full_soname); + do_link = 0; + do_remove = 0; + } + } + else if (lstat (full_soname, &lstat_so) != 0 + || !S_ISLNK (lstat_so.st_mode)) + /* Unless it is a stale symlink, there is no need to remove. */ + do_remove = 0; + + if (opt_verbose) + printf ("\t%s -> %s", soname, libname); + + if (do_link && opt_link) + { + /* Remove old link. */ + if (do_remove) + if (unlink (full_soname)) + { + error (0, 0, _("Can't unlink %s"), full_soname); + do_link = 0; + } + /* Create symbolic link. */ + if (do_link && symlink (libname, full_soname)) + { + error (0, 0, _("Can't link %s to %s"), full_soname, libname); + do_link = 0; + } + if (opt_verbose) + { + if (do_link) + fputs (_(" (changed)\n"), stdout); + else + fputs (_(" (SKIPPED)\n"), stdout); + } + } + else if (opt_verbose) + fputs ("\n", stdout); +} + +/* Read a whole directory and search for libraries. + The purpose is two-fold: + - search for libraries which will be added to the cache + - create symbolic links to the soname for each library + + This has to be done separatly for each directory. + + To keep track of which libraries to add to the cache and which + links to create, we save a list of all libraries. + + The algorithm is basically: + for all libraries in the directory do + get soname of library + if soname is already in list + if new library is newer, replace entry + otherwise ignore this library + otherwise add library to list + + For example, if the two libraries libxy.so.1.1 and libxy.so.1.2 + exist and both have the same soname, e.g. libxy.so, a symbolic link + is created from libxy.so.1.2 (the newer one) to libxy.so. + libxy.so.1.2 and libxy.so are added to the cache - but not + libxy.so.1.1. */ + +/* Information for one library. */ +struct dlib_entry +{ + char *name; + char *soname; + int flag; + int is_link; + struct dlib_entry *next; +}; + + +static void +search_dir (const struct dir_entry *entry) +{ + DIR *dir; + struct dirent *direntry; + char buf [PATH_MAX]; + char *soname; + struct dlib_entry *dlibs; + struct dlib_entry *dlib_ptr; + int nchars; + struct stat stat_buf; + int is_link; + + dlibs = NULL; + + if (opt_verbose) + printf ("%s:\n", entry->path); + + dir = opendir (entry->path); + if (dir == NULL) + { + if (opt_verbose) + error (0, errno, _("Can't open directory %s"), entry->path); + return; + } + + + while ((direntry = readdir (dir)) != NULL) + { + int flag; +#ifdef _DIRENT_HAVE_D_TYPE + /* We only look at links and regular files. */ + if (direntry->d_type != DT_UNKNOWN + && direntry->d_type != DT_LNK + && direntry->d_type != DT_REG) + continue; +#endif /* _DIRENT_HAVE_D_TYPE */ + + /* Does this file look like a shared library? The dynamic + linker is also considered as shared library. */ + if ((strncmp (direntry->d_name, "lib", 3) != 0 + && strncmp (direntry->d_name, "ld-", 3) != 0) + || strstr (direntry->d_name, ".so") == NULL) + continue; + nchars = snprintf (buf, sizeof (buf), "%s/%s", entry->path, + direntry->d_name); + /* Check for overflow. */ + if (nchars >= (int) sizeof (buf)) + { + error (0, 0, _("buffer for snprintf too small for %s/%s--file is ignored\n"), + entry->path, direntry->d_name); + continue; + } + if (lstat (buf, &stat_buf)) + { + error (0, errno, _("Can't lstat %s"), buf); + continue; + } + else if (!S_ISREG (stat_buf.st_mode) && !S_ISLNK (stat_buf.st_mode)) + continue; + + is_link = S_ISLNK (stat_buf.st_mode); + + if (process_file (buf, direntry->d_name, &flag, &soname, is_link)) + continue; + + /* Links will just point to itself. */ + if (is_link) + { + free (soname); + soname = xstrdup (direntry->d_name); + } + + if (flag == FLAG_ELF + && (entry->flag == FLAG_ELF_LIBC5 + || entry->flag == FLAG_ELF_LIBC6)) + flag = entry->flag; + /* Some sanity checks to print warnings. */ + if (opt_verbose) + { + if (flag == FLAG_ELF_LIBC5 && entry->flag != FLAG_ELF_LIBC5 + && entry->flag != FLAG_ANY) + error (0, 0, _("libc5 library %s in wrong directory"), buf); + if (flag == FLAG_ELF_LIBC6 && entry->flag != FLAG_ELF_LIBC6 + && entry->flag != FLAG_ANY) + error (0, 0, _("libc6 library %s in wrong directory"), buf); + if (flag == FLAG_LIBC4 && entry->flag != FLAG_LIBC4 + && entry->flag != FLAG_ANY) + error (0, 0, _("libc4 library %s in wrong directory"), buf); + } + + /* Add library to list. */ + for (dlib_ptr = dlibs; dlib_ptr != NULL; dlib_ptr = dlib_ptr->next) + { + /* Is soname already in list? */ + if (strcmp (dlib_ptr->soname, soname) == 0) + { + /* Prefer a file to a link, otherwise check which one + is newer. */ + if ((!is_link && dlib_ptr->is_link) + || (is_link == dlib_ptr->is_link + && cache_libcmp (dlib_ptr->name, direntry->d_name) < 0)) + { + /* It's newer - add it. */ + /* Flag should be the same - sanity check. */ + if (dlib_ptr->flag != flag) + { + if (dlib_ptr->flag == FLAG_ELF + && (flag == FLAG_ELF_LIBC5 || flag == FLAG_ELF_LIBC6)) + dlib_ptr->flag = flag; + else if ((dlib_ptr->flag == FLAG_ELF_LIBC5 + || dlib_ptr->flag == FLAG_ELF_LIBC6) + && flag == FLAG_ELF) + dlib_ptr->flag = flag; + else + error (0, 0, _("libraries %s and %s in directory %s have same soname but different type."), + dlib_ptr->name, direntry->d_name, entry->path); + } + free (dlib_ptr->name); + dlib_ptr->name = xstrdup (direntry->d_name); + dlib_ptr->is_link = is_link; + } + /* Don't add this library, abort loop. */ + /* Also free soname, since it's dynamically allocated. */ + free (soname); + break; + } + } + /* Add the library if it's not already in. */ + if (dlib_ptr == NULL) + { + dlib_ptr = (struct dlib_entry *)xmalloc (sizeof (struct dlib_entry)); + dlib_ptr->name = xstrdup (direntry->d_name); + dlib_ptr->flag = flag; + dlib_ptr->soname = soname; + dlib_ptr->is_link = is_link; + /* Add at head of list. */ + dlib_ptr->next = dlibs; + dlibs = dlib_ptr; + } + } + + closedir (dir); + + /* Now dlibs contains a list of all libs - add those to the cache + and created all symbolic links. */ + for (dlib_ptr = dlibs; dlib_ptr != NULL; dlib_ptr = dlib_ptr->next) + { + /* Don't create links to links. */ + if (dlib_ptr->is_link == 0) + create_links (entry->path, dlib_ptr->name, dlib_ptr->soname); + if (opt_build_cache) + add_to_cache (entry->path, dlib_ptr->soname, dlib_ptr->flag); + } + + /* Free all resources. */ + while (dlibs) + { + dlib_ptr = dlibs; + free (dlib_ptr->soname); + free (dlib_ptr->name); + dlibs = dlibs->next; + free (dlib_ptr); + } +} + +/* Search through all libraries. */ +static void +search_dirs (void) +{ + struct dir_entry *entry; + + for (entry = dir_entries; entry != NULL; entry = entry->next) + search_dir (entry); + + /* Free all allocated memory. */ + while (dir_entries) + { + entry = dir_entries; + dir_entries = dir_entries->next; + free (entry->path); + free (entry); + } +} + + +/* Parse configuration file. */ +static void +parse_conf (const char *filename) +{ + FILE *file; + char *line = NULL; + size_t len = 0; + + file = fopen (filename, "r"); + + if (file == NULL) + { + error (0, errno, _("Can't open configuration file %s"), filename); + return; + } + + do + { + ssize_t n = getline (&line, &len, file); + if (n < 0) + break; + + 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'; + + /* If the line is blank it is ignored. */ + if (line[0] == '\0') + continue; + + add_dir (line); + } while (!feof (file)); + + /* Free buffer and close file. */ + free (line); + fclose (file); +} + + +int +main (int argc, char **argv) +{ + int remaining; + + /* Parse and process arguments. */ + argp_parse (&argp, argc, argv, 0, &remaining, NULL); + + /* Remaining arguments are additional libraries. */ + if (remaining != argc) + { + int i; + for (i = remaining; i < argc; ++i) + add_dir (argv [i]); + } + + if (cache_file == NULL) + cache_file = LD_SO_CACHE; + + if (config_file == NULL) + config_file = LD_SO_CONF; + + /* Chroot first. */ + if (opt_chroot) + { + if (chroot (opt_chroot)) + /* Report failure and exit program. */ + error (EXIT_FAILURE, errno, _("Can't chroot to %s"), opt_chroot); + /* chroot doesn't change the working directory, let's play safe. */ + if (chdir ("/")) + error (EXIT_FAILURE, errno, _("Can't chdir to /")); + } + + if (opt_print_cache) + { + print_cache (cache_file); + exit (0); + } + + if (opt_build_cache) + init_cache (); + + if (!opt_only_cline) + { + /* Always add the standard search paths. */ + add_dir ("/lib"); + add_dir ("/usr/lib"); + + parse_conf (config_file); + } + + search_dirs (); + + if (opt_build_cache) + save_cache (cache_file); + + return 0; +} |