Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance requires with version information from the build root. #2372

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ include(CheckVariableExists)
set(OPTFUNCS
stpcpy stpncpy putenv mempcpy fdatasync lutimes mergesort
getauxval setprogname __progname syncfs sched_getaffinity unshare
secure_getenv __secure_getenv mremap strchrnul
secure_getenv __secure_getenv mremap strchrnul dlmopen dlinfo
)
set(REQFUNCS
mkstemp getcwd basename dirname realpath setenv unsetenv regcomp
Expand Down
125 changes: 121 additions & 4 deletions build/files.c
Original file line number Diff line number Diff line change
Expand Up @@ -1713,6 +1713,105 @@ static void argvAddAttr(ARGV_t *filesp, rpmfileAttrs attrs, const char *path)
free(line);
}

static int generateElfSoVers(FileList fl, ARGV_t *files)
{
int rc = 0;
int i;
FileListRec flp;
const char *errdir = _("failed to create directory");

/* How are we supposed to create the build-id links? */
char *elf_so_version_macro = rpmExpand("%{?_elf_so_version}\n", NULL);
if (*elf_so_version_macro == '\n') {
rc = 1;
rpmlog(RPMLOG_WARNING,
_("_elf_so_version macro not set, skipping elf-version generation\n"));
}

if (rc != 0) {
free(elf_so_version_macro);
return rc;
}

/* Save _elf_so_version for ELF shared objects in this package. */
for (i = 0, flp = fl->files.recs; i < fl->files.used; i++, flp++) {
struct stat sbuf;
if (lstat(flp->diskPath, &sbuf) == 0 && S_ISREG (sbuf.st_mode)) {
/* We determine whether this is a main or
debug ELF based on path. */
int isDbg = strncmp (flp->cpioPath,
DEBUG_LIB_PREFIX, strlen (DEBUG_LIB_PREFIX)) == 0;

/* Only save elf_so_version executable files in the main package. */
if (isDbg
|| (sbuf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0)
continue;

int fd = open (flp->diskPath, O_RDONLY);
if (fd >= 0) {
/* Only real ELF files, that are ET_DYN should have _elf_so_version. */
GElf_Ehdr ehdr;
#ifdef HAVE_DWELF_ELF_BEGIN
Elf *elf = dwelf_elf_begin (fd);
#else
Elf *elf = elf_begin (fd, ELF_C_READ, NULL);
#endif
if (elf != NULL && elf_kind (elf) == ELF_K_ELF
&& gelf_getehdr (elf, &ehdr) != NULL
&& (ehdr.e_type == ET_DYN)) {

int versionFd = 0;
char *pathCopy, *base, *versionPath, *versionDirPath;
rasprintf (&pathCopy, "%s", flp->diskPath);
/* TODO: ".elf-version" should be added to the "filesystem"
package so that it's not owned by every package with SOs. */
base = strrchr (pathCopy, '/');
if (base) {
base[0] = 0;
base++;
rasprintf (&versionPath, "%s/.elf-version/%s",
pathCopy, base);
rasprintf (&versionDirPath, "%s/.elf-version",
pathCopy);
} else {
rasprintf (&versionPath, ".elf-version/%s",
pathCopy);
rasprintf (&versionDirPath, ".elf-version");
}

if (*files == NULL) {
/* Make sure to reset all file flags to defaults. */
char *attrstr = mkattr();
argvAdd(files, attrstr);
free (attrstr);
}

/* We only need to create and add the subdir once. */
if (access (versionDirPath, F_OK) == -1) {
if ((rc = rpmioMkpath(versionDirPath, 0755, -1, -1)) != 0)
rpmlog(RPMLOG_ERR, "%s %s: %m\n", errdir, versionDirPath);
else
argvAddAttr(files, RPMFILE_DIR|RPMFILE_ARTIFACT, versionDirPath);
}

versionFd = open(versionPath, O_WRONLY);
if (versionFd >= 0 &&
write(versionFd, elf_so_version_macro, strlen(elf_so_version_macro)))
argvAddAttr(files, RPMFILE_ARTIFACT, versionPath);
else
rpmlog(RPMLOG_ERR, "Error writing %s\n", versionDirPath);
free(versionPath);
free(versionDirPath);
}
elf_end (elf);
close (fd);
}
}
}
free(elf_so_version_macro);
return rc;
}

#ifdef HAVE_LIBDW
/* How build id links are generated. See macros.in for description. */
#define BUILD_IDS_NONE 0
Expand Down Expand Up @@ -2624,12 +2723,30 @@ static rpmRC processPackageFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags,
if (fl.processingFailed)
goto exit;

#ifdef HAVE_LIBDW
/* Check build-ids and add build-ids links for files to package list. */
const char *arch = headerGetString(pkg->header, RPMTAG_ARCH);
if (!rstreq(arch, "noarch")) {
/* Go through the current package list and generate a files list. */
ARGV_t idFiles = NULL;
/* Go through the current package list and generate a files list. */
if (generateElfSoVers (&fl, &idFiles) != 0) {
rpmlog(RPMLOG_ERR, _("Generating elf-so-version failed\n"));
fl.processingFailed = 1;
argvFree(idFiles);
goto exit;
}

if (idFiles != NULL) {
resetPackageFilesDefaults (&fl, pkgFlags);
addPackageFileList (&fl, pkg, &idFiles, NULL, NULL, 0);
}
argvFree(idFiles);
idFiles = NULL;

if (fl.processingFailed)
goto exit;

#ifdef HAVE_LIBDW
/* Check build-ids and add build-ids links for files to package list. */
/* Go through the current package list and generate a files list. */
if (generateBuildIDs (&fl, &idFiles) != 0) {
rpmlog(RPMLOG_ERR, _("Generating build-id links failed\n"));
fl.processingFailed = 1;
Expand All @@ -2645,8 +2762,8 @@ static rpmRC processPackageFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags,

if (fl.processingFailed)
goto exit;
}
#endif
}

/* Verify that file attributes scope over hardlinks correctly. */
if (checkHardLinks(&fl.files))
Expand Down
2 changes: 2 additions & 0 deletions config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#cmakedefine HAVE_DIRENT_H @HAVE_DIRENT_H@
#cmakedefine HAVE_DIRNAME @HAVE_DIRNAME@
#cmakedefine HAVE_DLFCN_H @HAVE_DLFCN_H@
#cmakedefine HAVE_DLINFO @HAVE_DLINFO@
#cmakedefine HAVE_DLMOPEN @HAVE_DLMOPEN@
#cmakedefine HAVE_DSA_SET0_KEY @HAVE_DSA_SET0_KEY@
#cmakedefine HAVE_DSA_SET0_PQG @HAVE_DSA_SET0_PQG@
#cmakedefine HAVE_DSA_SIG_SET0 @HAVE_DSA_SIG_SET0@
Expand Down
8 changes: 6 additions & 2 deletions docs/manual/more_dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,16 @@ This has two "side-effects":
It's a fairly common mistake to replace legacy PreReq dependencies with Requires(pre), but this is not the same, due to the latter point above!

## Automatic Dependencies
To reduce the amount of work required by the package builder, RPM scans the file list of a package when it is being built. Any files in the file list which require shared libraries to work (as determined by ldd) cause that package to require the shared library.
To reduce the amount of work required by the package builder, RPM scans the file list of a package when it is being built. Any files in the file list which require shared libraries to work (as determined by elfdeps for ELF objects) cause that package to require the shared library.

For example, if your package contains /bin/vi, RPM will add dependencies for both libtermcap.so.2 and libc.so.5. These are treated as virtual packages, so no version numbers are used.
For example, if your package contains /bin/vi, RPM will add dependencies for both libtermcap.so.2 and libc.so.5. These are treated as virtual packages.

A similar process allows RPM to add Provides information automatically. Any shared library in the file list is examined for its soname (the part of the name which must match for two shared libraries to be considered equivalent) and that soname is automatically provided by the package. For example, the libc-5.3.12 package has provides information added for libm.so.5 and libc.so.5. We expect this automatic dependency generation to eliminate the need for most packages to use explicit Requires: lines.

As of rpm version (TBD), the internal dependency generator provides optional support for generating versioned ELF object dependencies on objects that do not provide versioned symbols. Distributions that wish to make use of this support should enable the \_elf\_provide\_fallback\_versions and \_elf\_require\_fallback\_versions macros. When these macros are enabled, the dependency generator will attempt to resolve shared object dependencies to a full path. If the shared object does not provide versioned symbols, and the soname is a symlink to a full name that differs from the soname, and the full name ends in ".so." followed by a sequence of numbers optionally separated by the '.' character, then that trailing sequence will be treated as a version and included in the automatically generated dependencies. While shared objects with versioned symbols are well supported, and are the preferred approach to ensuring that a dependency actually provides the ABI that another package requires, this fallback approach to versioning dependencies provides reasonably good coverage for the large body of ELF shared objects that do not yet maintain versioned symbols.

The most likely case where the fallback version system will break is a shared object being updated from a purely numeric version suffix to a version suffix with non-numeric characters (e.g. "libfoo.so.3.0.1" is updated to "libfoo.so.3.0.1a"). In that case, the dependency generator would not provide a version for that shared object, and the package maintainer would need to manually add a Provides: tag to the package in order to continue satisfying existing package dependencies.

## Custom Automatic Dependency
Customizing automatic dependency generation is covered in [dependency generator documentation]().

Expand Down
4 changes: 2 additions & 2 deletions fileattrs/elf.attr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
%__elf_provides %{_rpmconfigdir}/elfdeps --provides --multifile
%__elf_requires %{_rpmconfigdir}/elfdeps --requires --multifile
%__elf_provides %{_rpmconfigdir}/elfdeps %{?_elf_provide_fallback_versions} --provides --multifile
%__elf_requires %{_rpmconfigdir}/elfdeps %{?_elf_require_fallback_versions} --requires --multifile
%__elf_magic ^(setuid,? )?(setgid,? )?(sticky )?ELF (32|64)-bit.*$
%__elf_exclude_path ^/lib/modules/.*\.ko?(\.[[:alnum:]]*)$
%__elf_protocol multifile
12 changes: 12 additions & 0 deletions macros.in
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,18 @@ Supplements: (%{name} = %{version}-%{release} and langpacks-%{1})\
# Use internal dependency generator rather than external helpers?
%_use_internal_dependency_generator 1

#
# Generate minimum versions for ELF libraries that don't provide
# versioned symbol?
#%_elf_provide_fallback_versions --full-name-version-fallback
#%_elf_require_fallback_versions --full-name-version-fallback

#
# ELF libraries will usually inherit a version from their package,
# but this can be overridden by defining _elf_so_version,
# especially for interchangeable implementations.
%_elf_so_version %{version}

# Directories whose contents should be considered as documentation.
%__docdir_path %{_datadir}/doc:%{_datadir}/man:%{_datadir}/info:%{_datadir}/gtk-doc/html:%{_datadir}/gnome/help:%{?_docdir}:%{?_mandir}:%{?_infodir}:%{?_javadocdir}:/usr/doc:/usr/man:/usr/info:/usr/X11R6/man

Expand Down
6 changes: 6 additions & 0 deletions tests/atlocal.in
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ if [ "@WITH_CAP@" == "ON" ]; then
else
CAP_DISABLED=true;
fi
if (grep -q '#define HAVE_DLINFO 1' "${abs_top_builddir}/config.h" &&
grep -q '#define HAVE_DLMOPEN 1' "${abs_top_builddir}/config.h"); then
HAVE_GNU_DLFCN=true;
else
HAVE_GNU_DLFCN=false;
fi
if mknod foodev c 123 123 2>/dev/null; then
MKNOD_DISABLED=false
rm -f foodev
Expand Down
1 change: 1 addition & 0 deletions tests/data/misc/.elf-version/libhello.so
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0
Binary file removed tests/data/misc/libhello.so
Binary file not shown.
1 change: 1 addition & 0 deletions tests/data/misc/libhello.so
Binary file added tests/data/misc/libhello.so.1.0.0
Binary file not shown.
97 changes: 97 additions & 0 deletions tests/rpmbuild.at
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,103 @@ rtld(GNU_HASH)
[])
RPMTEST_CLEANUP

AT_SETUP([elf dependencies with fallback])
AT_KEYWORDS([build])
RPMDB_INIT

AT_CHECK([
runroot_other chmod a-x /data/misc/libhello.so
runroot_other ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/libhello.so
runroot_other chmod a+x /data/misc/libhello.so
runroot_other ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/libhello.so
],
[0],
[libc.so.6(GLIBC_2.2.5)(64bit)
libc.so.6()(64bit)
rtld(GNU_HASH)
libc.so.6(GLIBC_2.2.5)(64bit)
libc.so.6()(64bit)
rtld(GNU_HASH)
],
[])

AT_CHECK([
runroot_other chmod a-x /data/misc/libhello.so.1.0.0
runroot_other ${RPM_CONFIGDIR_PATH}/elfdeps -P --full-name-version-fallback /data/misc/libhello.so
runroot_other chmod a+x /data/misc/libhello.so.1.0.0
runroot_other ${RPM_CONFIGDIR_PATH}/elfdeps -P --full-name-version-fallback /data/misc/libhello.so
],
[0],
[libhello.so()(64bit) = 1.0.0
libhello.so()(64bit) = 1.0.0
],
[])

AT_SKIP_IF([! $HAVE_GNU_DLFCN])
AT_CHECK([
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}${RPMTEST}/data/misc
runroot_other chmod a-x /data/misc/helloexe
${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback ${RPMTEST}/data/misc/helloexe
runroot_other chmod a+x /data/misc/helloexe
${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback ${RPMTEST}/data/misc/helloexe
],
[0],
[libc.so.6(GLIBC_2.2.5)(64bit)
libhello.so()(64bit) >= 1.0.0
libc.so.6()(64bit)
rtld(GNU_HASH)
],
[])

AT_SKIP_IF([! $HAVE_GNU_DLFCN])
AT_CHECK([
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}${RPMTEST}/data/misc
runroot_other chmod a-x /data/misc/hellopie
${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback ${RPMTEST}/data/misc/hellopie
runroot_other chmod a+x /data/misc/hellopie
${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback ${RPMTEST}/data/misc/hellopie
],
[0],
[libc.so.6(GLIBC_2.2.5)(64bit)
libhello.so()(64bit) >= 1.0.0
libc.so.6()(64bit)
rtld(GNU_HASH)
],
[])

AT_SKIP_IF([$HAVE_GNU_DLFCN])
AT_CHECK([
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}${RPMTEST}/data/misc
runroot_other chmod a-x /data/misc/helloexe
${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback ${RPMTEST}/data/misc/helloexe
runroot_other chmod a+x /data/misc/helloexe
${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback ${RPMTEST}/data/misc/helloexe
],
[0],
[libc.so.6(GLIBC_2.2.5)(64bit)
libhello.so()(64bit)
libc.so.6()(64bit)
rtld(GNU_HASH)
],
[])

AT_SKIP_IF([$HAVE_GNU_DLFCN])
AT_CHECK([
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}${RPMTEST}/data/misc
runroot_other chmod a-x /data/misc/hellopie
${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback ${RPMTEST}/data/misc/hellopie
runroot_other chmod a+x /data/misc/hellopie
${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback ${RPMTEST}/data/misc/hellopie
],
[0],
[libc.so.6(GLIBC_2.2.5)(64bit)
libhello.so()(64bit)
libc.so.6()(64bit)
rtld(GNU_HASH)
],
[])
AT_CLEANUP

# ------------------------------
# Test spec query functionality
AT_SETUP([rpmspec query 1])
Expand Down
Loading
Loading