diff --git a/Makefile.am b/Makefile.am index 552e2ad0b221367edecaa3ac6e8f551fd693c265..856a745367adaad17f4c38fef6c58722120ede63 100644 --- a/Makefile.am +++ b/Makefile.am @@ -467,7 +467,7 @@ tests_utils_t_SOURCES = tests/utils.c \ tests/test-helpers.h \ utils/library-cmp.c \ utils/library-cmp.h -tests_utils_t_LDADD = utils/libutils.la $(GLIB_LIBS) +tests_utils_t_LDADD = utils/libutils.la $(GLIB_LIBS) $(LIBELF_LIBS) test_scripts = tests/capture-libs.pl \ tests/symbols.pl \ diff --git a/utils/library-cmp.c b/utils/library-cmp.c index d7644525bb1cb6edcdd4d16da3ad2be7b912d7c4..1e7a82626d21b3b6f9a987ae3fa0c7a3f13e78af 100644 --- a/utils/library-cmp.c +++ b/utils/library-cmp.c @@ -17,12 +17,699 @@ #include "library-cmp.h" +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> #include <stdio.h> #include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <libelf.h> +#include <gelf.h> #include "debug.h" #include "utils.h" +// From binutils/include/elf/common.h (this doesn't appear to be documented +// anywhere else). +// +// /* This flag appears in a Versym structure. It means that the symbol +// is hidden, and is only visible with an explicit version number. +// This is a GNU extension. */ +// #define VERSYM_HIDDEN 0x8000 +// +// /* This is the mask for the rest of the Versym information. */ +// #define VERSYM_VERSION 0x7fff + +#define VERSYM_HIDDEN 0x8000 +#define VERSYM_VERSION 0x7fff + +static int +bsearch_strcmp_cb( const void *n, const void *ip ) +{ + const char *needle = n; + const char * const *item_p = ip; + return strcmp( needle, *item_p ); +} + +static int +qsort_strcmp_cb( const void* s1, const void* s2 ) +{ + const char * const *a = (const char* const*) s1; + const char* const *b = (const char* const*) s2; + return strcmp( *a, *b ); +} + +/* + * string_set_diff_flags: + * @STRING_SET_DIFF_ONLY_IN_FIRST: At least one element is in the first set but not the second + * @STRING_SET_DIFF_ONLY_IN_SECOND: At least one element is in the second set but not the first + * @STRING_SET_DIFF_NONE: All elements are equal + * + * The result of comparing two sets of strings. If each set + * contains elements that the other does not, then + * both @STRING_SET_DIFF_ONLY_IN_FIRST + * and @STRING_SET_DIFF_ONLY_IN_SECOND will be set. + */ +typedef enum +{ + STRING_SET_DIFF_ONLY_IN_FIRST = (1 << 0), + STRING_SET_DIFF_ONLY_IN_SECOND = (1 << 1), + STRING_SET_DIFF_NONE = 0 +} string_set_diff_flags; + +/* + * compare_string_sets: + * @first: the first set to compare + * @first_length: number of elements in the first set + * @second: the second set to compare + * @second_length: number of elements in the second set + * + * The two sets needs to be ordered because we will use a binary search to do + * the comparison. + */ +static string_set_diff_flags +compare_string_sets ( char **first, size_t first_length, + char **second, size_t second_length ) +{ + string_set_diff_flags result = STRING_SET_DIFF_NONE; + + assert( first != NULL ); + assert( second != NULL ); + + if( first_length > second_length ) + { + result |= STRING_SET_DIFF_ONLY_IN_FIRST; + } + else + { + for( size_t i = 0; i < first_length; i++ ) + { + char *found = bsearch( first[i], second, second_length, sizeof(char *), bsearch_strcmp_cb ); + if( found == NULL ) + { + result |= STRING_SET_DIFF_ONLY_IN_FIRST; + break; + } + } + } + + if( first_length < second_length ) + { + result |= STRING_SET_DIFF_ONLY_IN_SECOND; + } + else + { + for( size_t i = 0; i < second_length; i++ ) + { + char *found = bsearch( second[i], first, first_length, sizeof(char *), bsearch_strcmp_cb ); + if( found == NULL ) + { + result |= STRING_SET_DIFF_ONLY_IN_SECOND; + break; + } + } + } + return result; +} + +static void +close_elf (Elf **elfp, int *fdp) +{ + if (elfp != NULL && *elfp != NULL) + { + elf_end( *elfp ); + *elfp = NULL; + } + + if (fdp != NULL && *fdp >= 0) + { + close( *fdp ); + *fdp = -1; + } +} + +/* + * open_elf_library: + * @path: (type filename): The path where the library is located + * @fd: (out) (not optional): Used to return a file descriptor of the + * opened library + * @elf: (out) (not optional): Used to return an initialized elf of the + * library + * @code: (out) (optional): Used to return an error code on failure + * @message: (out) (optional) (nullable): Used to return an error message + * on failure + * + * Returns: %TRUE if the elf opening succeded, %FALSE otherwise. + */ +static bool +open_elf_library ( const char *path, int *fd, Elf **elf, + int *code, char **message) +{ + GElf_Ehdr ehdr; + bool result = true; + + assert( *elf == NULL ); + + if( elf_version(EV_CURRENT) == EV_NONE ) + { + _capsule_set_error( code, message, EINVAL, + "elf_version(EV_CURRENT): %s", + elf_errmsg( elf_errno() ) ); + result = false; + goto out; + } + + if( ( *fd = open( path, O_RDONLY | O_CLOEXEC, 0 ) ) < 0 ) + { + _capsule_set_error( code, message, EINVAL, + "failed to open %s", path ); + result = false; + goto out; + } + + if( ( *elf = elf_begin( *fd, ELF_C_READ, NULL ) ) == NULL ) + { + _capsule_set_error( code, message, EINVAL, + "elf_begin() failed: %s", + elf_errmsg( elf_errno() ) ); + result = false; + goto out; + } + + if( elf_kind( *elf ) != ELF_K_ELF ) + { + _capsule_set_error( code, message, EINVAL, + "%s is not in ELF format", path ); + result = false; + goto out; + } + + if( gelf_getehdr( *elf, &ehdr ) == NULL ) + { + _capsule_set_error( code, message, EINVAL, + "gelf_getehdr() failed: %s", + elf_errmsg( elf_errno() ) ); + result = false; + goto out; + } + if( ehdr.e_type != ET_DYN ) + { + _capsule_set_error( code, message, EINVAL, + "%s is not a shared library, elf type is %d", + path, ehdr.e_type ); + result = false; + goto out; + } + +out: + if( !result ) + close_elf( elf, fd ); + + return result; +} + +static void +print_debug_string_list( char **list, const char *begin_message ) +{ + if( list == NULL || list[0] == NULL ) + return; + + if( begin_message != NULL ) + DEBUG( DEBUG_ELF, "%s", begin_message ); + + for( size_t i = 0; list[i] != NULL; i++ ) + DEBUG( DEBUG_ELF, "%s", list[i] ); +} + +/* + * get_versions: + * @elf: The object's elf of which we want to get the versions + * @versions_number: (out) (not optional): The number of versions found + * @code: (out) (optional): Used to return an error code on + * failure + * @message: (out) (optional) (nullable): Used to return an error message + * on failure + * + * Returns: (transfer full): The list of versions that the + * shared object has, on failure %NULL. + */ +static char ** +get_versions( Elf *elf, size_t *versions_number, int *code, char **message ) +{ + char **versions = NULL; + Elf_Scn *scn = NULL; + Elf_Data *data; + GElf_Shdr shdr_mem; + GElf_Shdr *shdr = NULL; + GElf_Verdef def_mem; + GElf_Verdef *def; + bool found_verdef = false; + uintptr_t verdef_ptr = 0; + size_t auxoffset; + size_t offset = 0; + size_t phnum; + size_t sh_entsize; + ptr_list *versions_list = NULL; + + assert( versions_number != NULL ); + + *versions_number = 0; + + if( elf_getphdrnum( elf, &phnum ) < 0 ) + { + _capsule_set_error( code, message, EINVAL, + "Unable to determine the number of program headers: %s", + elf_errmsg( elf_errno() ) ); + return versions; + } + + /* Get the dynamic section */ + for( size_t i = 0; i < phnum; i++ ) + { + GElf_Phdr phdr_mem; + GElf_Phdr *phdr = gelf_getphdr( elf, i, &phdr_mem ); + if( phdr != NULL && phdr->p_type == PT_DYNAMIC ) + { + scn = gelf_offscn( elf, phdr->p_offset ); + shdr = gelf_getshdr( scn, &shdr_mem ); + break; + } + } + + if( shdr == NULL ) + { + int err = elf_errno(); + if( err == 0 ) + { + _capsule_set_error( code, message, EINVAL, + "Unable to find the section header from the dynamic section" ); + } + else + { + _capsule_set_error( code, message, EINVAL, + "Unable to get the section header: %s", + elf_errmsg( err ) ); + } + + return versions; + } + + data = elf_getdata( scn, NULL ); + if( data == NULL ) + { + _capsule_set_error( code, message, EINVAL, + "Unable to get the dynamic section data: %s", + elf_errmsg( elf_errno() ) ); + return versions; + } + + sh_entsize = gelf_fsize( elf, ELF_T_DYN, 1, EV_CURRENT ); + for( size_t i = 0; i < shdr->sh_size / sh_entsize; i++ ) + { + GElf_Dyn dyn_mem; + GElf_Dyn *dyn = gelf_getdyn( data, i, &dyn_mem ); + if( dyn == NULL ) + break; + + if( dyn->d_tag == DT_VERDEF ) + { + verdef_ptr = dyn->d_un.d_ptr; + found_verdef = true; + break; + } + } + + if( !found_verdef ) + { + DEBUG( DEBUG_ELF, "The version definition table is not available" ); + versions = calloc( 1, sizeof(char *) ); + versions[0] = NULL; + return versions; + } + scn = gelf_offscn( elf, verdef_ptr ); + data = elf_getdata( scn, NULL ); + if( data == NULL ) + { + _capsule_set_error( code, message, EINVAL, + "Unable to get symbols data: %s", elf_errmsg( elf_errno() ) ); + return versions; + } + + def = gelf_getverdef( data, 0, &def_mem ); + if( def == NULL ) + { + DEBUG( DEBUG_ELF, "Verdef is not available: %s", elf_errmsg( elf_errno() ) ); + versions = calloc( 1, sizeof(char *) ); + versions[0] = NULL; + return versions; + } + + /* Arbitrarily start the list with 8 elements */ + versions_list = ptr_list_alloc( 8 ); + while( def != NULL ) + { + GElf_Verdaux aux_mem, *aux; + const char *version; + + auxoffset = offset + def->vd_aux; + offset += def->vd_next; + + /* The first Verdaux array must exist and it points to the version + * definition string that Verdef defines. Every possible additional + * Verdaux arrays are the dependencies of said version definition. + * In our case we don't need to list the dependencies, so we just + * get the first Verdaux of every Verdef. */ + aux = gelf_getverdaux( data, auxoffset, &aux_mem ); + if( aux == NULL ) + continue; + version = elf_strptr( elf, shdr->sh_link, aux->vda_name ); + if( version == NULL ) + continue; + + if( ( def->vd_flags & VER_FLG_BASE ) == 0 ) + ptr_list_push_ptr( versions_list, strdup( version ) ); + + if( def->vd_next == 0 ) + def = NULL; + else + def = gelf_getverdef( data, offset, &def_mem ); + + } + + versions = (char **) ptr_list_free_to_array ( versions_list, versions_number ); + qsort( versions, *versions_number, sizeof(char *), qsort_strcmp_cb ); + return versions; +} + +static const char * const ignore_symbols[] = +{ + /* Libraries on at least SteamOS 2 'brewmaster' sometimes have + * symbols that appear to have an empty name. */ + "", + + /* These symbols can appear in libraries witout actually being part + * of anyone's ABI. List taken from dpkg-gensymbols. */ + "__bss_end__", + "__bss_end", + "_bss_end__", + "__bss_start", + "__bss_start__", + "__data_start", + "__do_global_ctors_aux", + "__do_global_dtors_aux", + "__do_jv_register_classes", + "_DYNAMIC", + "_edata", + "_end", + "__end__", + "__exidx_end", + "__exidx_start", + "_fbss", + "_fdata", + "_fini", + "_ftext", + "_GLOBAL_OFFSET_TABLE_", + "__gmon_start__", + "__gnu_local_gp", + "_gp", + "_init", + "_PROCEDURE_LINKAGE_TABLE_", + "_SDA2_BASE_", + "_SDA_BASE_", +}; + +/* + * get_symbols: + * @elf: The object's elf of which we want to get the symbols + * @symbols_number: (out) (not optional): The number of symbols found + * @code: (out) (optional): Used to return an error code on + * failure + * @message: (out) (optional) (nullable): Used to return an error message + * on failure + * + * Returns: (transfer full): The list of symbols that the + * shared object has, on failure %NULL. + */ +static char ** +get_symbols ( Elf *elf, size_t *symbols_number, int *code, char **message ) +{ + char **symbols = NULL; + Elf_Scn *scn = NULL; + Elf_Scn *scn_sym = NULL; + Elf_Scn *scn_ver = NULL; + Elf_Scn *scn_verdef = NULL; + Elf_Data *data; + Elf_Data *sym_data; + Elf_Data *versym_data = NULL; + Elf_Data *verdef_data = NULL; + GElf_Ehdr ehdr; + GElf_Shdr shdr_mem; + GElf_Shdr *shdr = NULL; + bool found_symtab = false; + bool found_versym = false; + bool found_verdef = false; + uintptr_t symtab_ptr = 0; + uintptr_t versym_ptr = 0; + uintptr_t verdef_ptr = 0; + size_t elsize = 0; + size_t phnum; + size_t sh_entsize; + ptr_list *symbols_list = NULL; + + assert( symbols_number != NULL ); + + *symbols_number = 0; + + if( elf_getphdrnum( elf, &phnum ) < 0 ) + { + _capsule_set_error( code, message, EINVAL, + "Unable to determine the number of program headers: %s", + elf_errmsg( elf_errno() ) ); + return symbols; + } + + /* Get the dynamic section */ + for( size_t i = 0; i < phnum; i++ ) + { + GElf_Phdr phdr_mem; + GElf_Phdr *phdr = gelf_getphdr( elf, i, &phdr_mem ); + if( phdr != NULL && phdr->p_type == PT_DYNAMIC ) + { + scn = gelf_offscn( elf, phdr->p_offset ); + shdr = gelf_getshdr( scn, &shdr_mem ); + break; + } + } + + if( shdr == NULL ) + { + int err = elf_errno(); + if( err == 0 ) + { + _capsule_set_error( code, message, EINVAL, + "Unable to find the section header from the dynamic section" ); + } + else + { + _capsule_set_error( code, message, EINVAL, + "Unable to get the section header: %s", + elf_errmsg( err ) ); + } + + return symbols; + } + + data = elf_getdata( scn, NULL ); + if( data == NULL ) + { + _capsule_set_error( code, message, EINVAL, + "Unable to get dynamic section data: %s", elf_errmsg( elf_errno() ) ); + return symbols; + } + + sh_entsize = gelf_fsize( elf, ELF_T_DYN, 1, EV_CURRENT ); + + for( size_t i = 0; i < shdr->sh_size / sh_entsize; i++ ) + { + GElf_Dyn dyn_mem; + GElf_Dyn *dyn = gelf_getdyn (data, i, &dyn_mem); + if( dyn == NULL ) + break; + + switch( dyn->d_tag ) + { + case DT_SYMTAB: + symtab_ptr = dyn->d_un.d_ptr; + found_symtab = true; + break; + case DT_VERSYM: + versym_ptr = dyn->d_un.d_ptr; + found_versym = true; + break; + case DT_VERDEF: + verdef_ptr = dyn->d_un.d_ptr; + found_verdef = true; + break; + default: + break; + } + } + + + if( !found_symtab ) + { + _capsule_set_error( code, message, EINVAL, "Unable to find the symbols table" ); + return symbols; + } + scn_sym = gelf_offscn( elf, symtab_ptr ); + sym_data = elf_getdata( scn_sym, NULL ); + if( sym_data == NULL ) + { + _capsule_set_error( code, message, EINVAL, + "Unable to get symbols table data: %s", elf_errmsg( elf_errno() ) ); + return symbols; + } + + if( found_versym ) + { + scn_ver = gelf_offscn( elf, versym_ptr ); + versym_data = elf_getdata( scn_ver, NULL ); + if( versym_data == NULL ) + { + _capsule_set_error( code, message, EINVAL, + "Unable to get symbols version information data: %s", + elf_errmsg( elf_errno() ) ); + return symbols; + } + } + + if( found_verdef ) + { + scn_verdef = gelf_offscn( elf, verdef_ptr ); + verdef_data = elf_getdata( scn_verdef, NULL ); + if( verdef_data == NULL ) + { + _capsule_set_error( code, message, EINVAL, + "Unable to get symbols version definition data: %s", + elf_errmsg( elf_errno() ) ); + return symbols; + } + } + + if( gelf_getehdr( elf, &ehdr ) == NULL ) + { + _capsule_set_error( code, message, EINVAL, + "Unable to retrieve Ehdr header: %s", + elf_errmsg( elf_errno() ) ); + return symbols; + } + + elsize = gelf_fsize( elf, ELF_T_SYM, 1, ehdr.e_version ); + if( elsize == 0 ) + { + _capsule_set_error( code, message, EINVAL, + "Size of symbols in Ehdr array is zero: %s", + elf_errmsg( elf_errno() ) ); + return symbols; + } + + /* Arbitrarily start the list with 8 elements */ + symbols_list = ptr_list_alloc( 8 ); + + for( size_t index = 0; index < sym_data->d_size / elsize; index++ ) + { + GElf_Sym *sym; + GElf_Sym sym_mem; + const char *symbol; + GElf_Versym versym_mem; + GElf_Versym *versym; + GElf_Verdaux aux_mem; + GElf_Verdaux *aux = NULL; + GElf_Verdef def_mem; + GElf_Verdef *def = NULL; + bool interesting = true; + + sym = gelf_getsymshndx( sym_data, NULL, index, &sym_mem, NULL ); + + /* If the symbol info is not available, or undefined, we skip it */ + if( sym == NULL || sym->st_shndx == SHN_UNDEF ) + continue; + + symbol = elf_strptr( elf, shdr->sh_link, sym->st_name ); + if( symbol == NULL ) + break; + + for( size_t i = 0; i < N_ELEMENTS( ignore_symbols ); i++ ) + { + if( strcmp( symbol, ignore_symbols[i] ) == 0 ) + { + interesting = false; + break; + } + } + + if ( !interesting ) + { + DEBUG( DEBUG_ELF, "Symbol '%s' is uninteresting", symbol ); + continue; + } + + /* Search the version of the symbol */ + if( found_versym && found_verdef ) + { + versym = gelf_getversym( versym_data, index, &versym_mem ); + def = gelf_getverdef( verdef_data, 0, &def_mem ); + + size_t auxoffset; + size_t offset = 0; + while( def != NULL ) + { + auxoffset = offset + def->vd_aux; + offset += def->vd_next; + + /* The first Verdaux array must exist and it points to the version + * definition string that Verdef defines. Every possible additional + * Verdaux arrays are the dependencies of said version definition. + * In our case we don't need to list the dependencies, so we just + * get the first Verdaux of every Verdef. */ + aux = gelf_getverdaux( verdef_data, auxoffset, &aux_mem ); + if( aux == NULL ) + continue; + + /* Break if we have found the symbol's version */ + if( def->vd_ndx == ( *versym & VERSYM_VERSION ) ) + break; + + if( def->vd_next == 0 ) + def = NULL; + else + def = gelf_getverdef( verdef_data, offset, &def_mem ); + } + } + + /* If the symbol is versioned */ + if( def != NULL && aux != NULL ) + { + const char *version = elf_strptr( elf, shdr->sh_link, aux->vda_name ); + char *symbol_versioned; + xasprintf( &symbol_versioned, "%s@%s", symbol, version ); + ptr_list_push_ptr( symbols_list, symbol_versioned ); + } + else + { + ptr_list_push_ptr( symbols_list, strdup( symbol ) ); + } + } + + symbols = (char **) ptr_list_free_to_array ( symbols_list, symbols_number ); + qsort( symbols, *symbols_number, sizeof(char *), qsort_strcmp_cb ); + return symbols; +} + /* * library_cmp_by_name: * @soname: The library we are interested in, used in debug logging @@ -102,3 +789,277 @@ library_cmp_by_name( const char *soname, return ( strverscmp( left_basename, right_basename ) ); } + +/* + * library_cmp_by_symbols: + * @soname: (type filename): The library we are interested in, used for debug + * @container_path: (type filename): The path where the container's @soname is + * located + * @provider_path: (type filename): The path where the provider's @soname is + * located + * + * Attempt to determine whether @soname is older, newer or + * the same in the container or the provider inspecting their + * symbols. + * + * Return a strcmp-style result: negative if container < provider, + * positive if container > provider, zero if container == provider + * or if container and provider are non-comparable. + */ +int +library_cmp_by_symbols( const char *soname, + const char *container_path, + const char *container_root, + const char *provider_path, + const char *provider_root ) +{ + string_set_diff_flags symbol_result = STRING_SET_DIFF_NONE; + int cmp_result = 0; + int container_fd = -1; + int provider_fd = -1; + Elf *container_elf = NULL; + Elf *provider_elf = NULL; + char **container_symbols = NULL; + char **provider_symbols = NULL; + size_t container_symbols_number = 0; + size_t provider_symbols_number = 0; + int code = 0; + char *message = NULL; + char *symbol_message = NULL; + + if( !open_elf_library( container_path, &container_fd, &container_elf, + &code, &message ) ) + { + DEBUG( DEBUG_TOOL, + "an error occurred while opening %s (%d): %s", + container_path, code, message ); + goto out; + } + + container_symbols = get_symbols( container_elf, &container_symbols_number, + &code, &message ); + + if( container_symbols == NULL ) + { + warnx( "failed to get container versions for %s (%d): %s", + soname, code, message ); + goto out; + } + + xasprintf( &symbol_message, "Container Symbols of %s:", soname ); + print_debug_string_list( container_symbols, symbol_message ); + _capsule_clear( &symbol_message ); + + if( !open_elf_library( provider_path, &provider_fd, &provider_elf, + &code, &message ) ) + { + DEBUG( DEBUG_TOOL, + "an error occurred while opening %s (%d): %s", + container_path, code, message ); + goto out; + } + + provider_symbols = get_symbols( provider_elf, &provider_symbols_number, + &code, &message ); + + if( provider_symbols == NULL ) + { + warnx( "failed to get provider versions for %s (%d): %s", + soname, code, message ); + goto out; + } + + xasprintf( &symbol_message, "Provider Symbols of %s:", soname ); + print_debug_string_list( provider_symbols, symbol_message ); + _capsule_clear( &symbol_message ); + + symbol_result = compare_string_sets( container_symbols, container_symbols_number, + provider_symbols, provider_symbols_number ); + + /* In container we have strictly more symbols: don't symlink the one + * from the provider */ + if( symbol_result == STRING_SET_DIFF_ONLY_IN_FIRST ) + { + DEBUG( DEBUG_TOOL, + "%s in the container is newer because its symbols are a strict superset", + soname ); + cmp_result = 1; + } + /* In provider we have strictly more symbols: create the symlink */ + else if( symbol_result == STRING_SET_DIFF_ONLY_IN_SECOND ) + { + DEBUG( DEBUG_TOOL, + "%s in the provider is newer because its symbols are a strict superset", + soname ); + cmp_result = -1; + } + /* With the following two cases we are still unsure which library is newer, so we + * will choose the provider */ + else if( symbol_result == STRING_SET_DIFF_NONE ) + { + DEBUG( DEBUG_TOOL, + "%s in the container and the provider have the same symbols", + soname ); + } + else + { + DEBUG( DEBUG_TOOL, + "%s in the container and the provider have different symbols and neither is a superset of the other", + soname ); + } + +out: + _capsule_clear( &message ); + if( container_symbols ) + { + for( size_t i = 0; container_symbols[i] != NULL; i++ ) + free( container_symbols[i] ); + free( container_symbols ); + } + if( provider_symbols ) + { + for( size_t i = 0; provider_symbols[i] != NULL; i++ ) + free( provider_symbols[i] ); + free( provider_symbols ); + } + close_elf( &container_elf, &container_fd ); + close_elf( &provider_elf, &provider_fd ); + return cmp_result; + +} + +/* + * library_cmp_by_versions: + * @soname: (type filename): The library we are interested in, used for debug + * @container_path: (type filename): The path where the container's @soname is + * located + * @provider_path: (type filename): The path where the provider's @soname is + * located + * + * Attempt to determine whether @soname is older, newer or + * the same in the container or the provider inspecting their + * symbols versions. + * + * Return a strcmp-style result: negative if container < provider, + * positive if container > provider, zero if container == provider + * or if container and provider are non-comparable. + */ +int +library_cmp_by_versions( const char *soname, + const char *container_path, + const char *container_root, + const char *provider_path, + const char *provider_root ) +{ + string_set_diff_flags version_result = STRING_SET_DIFF_NONE; + int cmp_result = 0; + int container_fd = -1; + int provider_fd = -1; + Elf *container_elf = NULL; + Elf *provider_elf = NULL; + char **container_versions = NULL; + char **provider_versions = NULL; + size_t container_versions_number = 0; + size_t provider_versions_number = 0; + int code = 0; + char *message = NULL; + char *version_message; + + if( !open_elf_library( container_path, &container_fd, &container_elf, + &code, &message ) ) + { + DEBUG( DEBUG_TOOL, + "an error occurred while opening %s (%d): %s", + container_path, code, message ); + goto out; + } + + container_versions = get_versions( container_elf, &container_versions_number, + &code, &message ); + + if( container_versions == NULL ) + { + warnx( "failed to get container versions for %s (%d): %s", + soname, code, message ); + goto out; + } + + xasprintf( &version_message, "Container versions of %s:", soname); + print_debug_string_list( container_versions, version_message ); + _capsule_clear( &version_message ); + + if( !open_elf_library( provider_path, &provider_fd, &provider_elf, + &code, &message ) ) + { + DEBUG( DEBUG_TOOL, + "an error occurred while opening %s (%d): %s", + container_path, code, message ); + goto out; + } + + provider_versions = get_versions( provider_elf, &provider_versions_number, + &code, &message ); + + if( provider_versions == NULL ) + { + warnx( "failed to get provider versions for %s (%d): %s", + soname, code, message ); + goto out; + } + + xasprintf( &version_message, "Provider versions of %s:", soname); + print_debug_string_list( provider_versions, version_message ); + _capsule_clear( &version_message ); + + version_result = compare_string_sets( container_versions, container_versions_number, + provider_versions, provider_versions_number ); + + /* Version in container is strictly newer: don't symlink the one + * from the provider */ + if( version_result == STRING_SET_DIFF_ONLY_IN_FIRST ) + { + DEBUG( DEBUG_TOOL, + "%s in the container is newer because its version definitions are a strict superset", + soname ); + cmp_result = 1; + } + /* Version in the provider is strictly newer: create the symlink */ + else if( version_result == STRING_SET_DIFF_ONLY_IN_SECOND ) + { + DEBUG( DEBUG_TOOL, + "%s in the provider is newer because its version definitions are a strict superset", + soname ); + cmp_result = -1; + } + /* With the following two cases we are still unsure which library is newer */ + else if( version_result == STRING_SET_DIFF_NONE ) + { + DEBUG( DEBUG_TOOL, + "%s in the container and the provider have the same symbol versions", + soname ); + } + else + { + DEBUG( DEBUG_TOOL, + "%s in the container and the provider have different symbol versions and neither is a superset of the other", + soname ); + } + +out: + _capsule_clear( &message ); + if( container_versions ) + { + for( size_t i = 0; container_versions[i] != NULL; i++ ) + free( container_versions[i] ); + free( container_versions ); + } + if( provider_versions ) + { + for( size_t i = 0; provider_versions[i] != NULL; i++ ) + free( provider_versions[i] ); + free( provider_versions ); + } + close_elf( &container_elf, &container_fd ); + close_elf( &provider_elf, &provider_fd ); + return cmp_result; +} diff --git a/utils/library-cmp.h b/utils/library-cmp.h index 4233a85be69a111780bc5537d608e1982c1868bc..4a43f4cf8c513526a8241c336c1899050618c6ed 100644 --- a/utils/library-cmp.h +++ b/utils/library-cmp.h @@ -22,3 +22,15 @@ int library_cmp_by_name( const char *soname, const char *container_root, const char *provider_path, const char *provider_root ); + +int library_cmp_by_symbols( const char *soname, + const char *container_path, + const char *container_root, + const char *provider_path, + const char *provider_root ); + +int library_cmp_by_versions( const char *soname, + const char *container_path, + const char *container_root, + const char *provider_path, + const char *provider_root );