[Rpm-maint] [PATCH] RFC: Add build-id links to rpm for all ELF.

Mark Wielaard mjw at redhat.com
Wed Apr 13 18:59:18 UTC 2016


This is an RFC patch to move the main ELF file build-id symlinks
from the debuginfo package into the main package and to use a
different base directory for the main ELF file build-id symlink.
Use /usr/lib/.build-id instead of /usr/lib/debug/.build-id

There are two reasons for doing this. The main package and the
debuginfo package might get out of sync, or the debuginfo package
might not be installed at all. In which case finding the main ELF
file through the build-id symlink becomes impossible. Secondly by
moving the main ELF build-id symlink in its own directory the
/usr/lib/debug directory gets populated with only debuginfo files
which is convenient if the user might want to have that directory
populated through a network mountpoint.

This patch is still a work in progress. It seems to work, but might
not be the best way to implement the above idea. The idea itself
might not be perfect (comments more than welcome). I tried to
follow the rpm coding style as much as I understood it. Please let
me know if I use some wrong constructs, or if there are alternate
ways to do what I am doing. It contains one testcase, that is fairly
minimal. I'll add more if we agree on the semantics of the idea
(help writing better testcases also very welcome). It obviously will
need some config setting (pointers to examples that use configs
for this kind of rpmbuild settings would be appreciated). And it
needs corresponding adjustments to scripts/find-debuginfo.sh,
including, when enabled, a backwards compatible link from the old
/usr/lib/debug/.build-id/xx/yyy -> /usr/lib/.build-id/xx/yyy

Comments, ideas and suggestions how to move a change like this
forward more than welcome.
---
 build/Makefile.am   |   4 ++
 build/files.c       | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 configure.ac        |  15 ++++++
 tests/Makefile.am   |   1 +
 tests/rpmbuildid.at |  47 +++++++++++++++++++
 tests/rpmtests.at   |   1 +
 6 files changed, 197 insertions(+)
 create mode 100644 tests/rpmbuildid.at

diff --git a/build/Makefile.am b/build/Makefile.am
index 8318cc8..f0f55a9 100644
--- a/build/Makefile.am
+++ b/build/Makefile.am
@@ -27,3 +27,7 @@ librpmbuild_la_LIBADD = \
 	@LTLIBICONV@ \
 	@WITH_POPT_LIB@ \
 	@WITH_MAGIC_LIB@
+
+if LIBDW
+librpmbuild_la_LIBADD += @WITH_LIBELF_LIB@ @WITH_LIBDW_LIB@
+endif
diff --git a/build/files.c b/build/files.c
index 07bc94e..0586503 100644
--- a/build/files.c
+++ b/build/files.c
@@ -14,6 +14,11 @@
 #include <sys/capability.h>
 #endif
 
+#if HAVE_LIBDW
+#include <libelf.h>
+#include <elfutils/libdwelf.h>
+#endif
+
 #include <rpm/rpmpgp.h>
 #include <rpm/argv.h>
 #include <rpm/rpmfc.h>
@@ -1544,6 +1549,122 @@ exit:
     return rc;
 }
 
+#if HAVE_LIBDW
+static int generateBuildIDs(FileList fl)
+{
+    int rc = 0;
+    int i;
+    FileListRec flp;
+    char **ids = NULL;
+    char **paths = NULL;
+    size_t nr_ids, allocated;
+    nr_ids = allocated = 0;
+
+    for (i = 0, flp = fl->files.recs; i < fl->files.used; i++, flp++) {
+	int fd;
+	fd = open (flp->diskPath, O_RDONLY);
+	if (fd >= 0) {
+	    struct stat sbuf;
+	    if (fstat (fd, &sbuf) == 0 && S_ISREG (sbuf.st_mode)) {
+		Elf *elf = elf_begin (fd, ELF_C_READ, NULL);
+		if (elf != NULL) {
+		    const void *build_id;
+		    ssize_t len = dwelf_elf_gnu_build_id (elf, &build_id);
+		    /* len == -1 means error (but could also mean, not
+		       really an ELF file), Zero means no build-id. We
+		       want at least a length of 2 so we have at least
+		       a xx/yy (hex) dir/file.  */
+		    if (len >= 2) {
+			const unsigned char *p = build_id;
+			const unsigned char *end = p + len;
+			char *id_str;
+			if (allocated <= nr_ids) {
+			    allocated += 16;
+			    paths = xrealloc (paths,
+					      allocated * sizeof(char *));
+			    ids = xrealloc (ids, allocated * sizeof(char *));
+			}
+			paths[nr_ids] = xstrdup(flp->cpioPath);
+			id_str = ids[nr_ids] = xmalloc(2 * len + 1);
+			while (p < end)
+			    id_str += sprintf(id_str, "%02x", (unsigned)*p++);
+			*id_str = '\0';
+			nr_ids++;
+		    } else if (len > 0) {
+			rpmlog(RPMLOG_WARNING,
+			       _("build-id found in %s too small\n"),
+			       flp->diskPath);
+		    }
+		    elf_end (elf);
+		}
+	    }
+	    close (fd);
+	}
+    }
+
+    if (nr_ids > 0) {
+	/* Add /usr/lib/.build-id to hold the subdirs/symlinks.  */
+	#define BUILD_ID_DIR "/usr/lib/.build-id"
+        const char *errmsg = _("failed to create directory");
+        char *buildiddir = rpmGetPath(fl->buildRoot, BUILD_ID_DIR, NULL);
+        if ((rc = rpmioMkpath(buildiddir, 0755, -1, -1)) != 0) {
+	    rpmlog(RPMLOG_ERR, "%s %s: %m\n", errmsg, buildiddir);
+        } else {
+	    fl->cur.isDir = 1;
+	    rc = addFile(fl, buildiddir, NULL);
+	}
+
+	/* Now add a subdir and symlink for each buildid found.  */
+	for (i = 0; i < nr_ids; i++) {
+	    if (rc == 0) {
+		char *buildidsubdir;
+		char subdir[4];
+		subdir[0] = '/';
+		subdir[1] = ids[i][0];
+		subdir[2] = ids[i][1];
+		subdir[3] = '\0';
+		buildidsubdir = rpmGetPath(buildiddir, subdir, NULL);
+		if ((rc = rpmioMkpath(buildidsubdir, 0755, -1, -1)) != 0) {
+		    rpmlog(RPMLOG_ERR, "%s %s: %m\n", errmsg, buildidsubdir);
+		} else {
+		    fl->cur.isDir = 1;
+		    if ((rc = addFile(fl, buildidsubdir, NULL)) == 0) {
+			char *linkpath, *targetpath;
+			if (asprintf(&linkpath, "%s/%s",
+				     buildidsubdir, &ids[i][2]) < 0
+			    || asprintf(&targetpath, "../../../..%s",
+					paths[i]) < 0) {
+			    const char *stringerr = _("failed string creation");
+			    rc = 1;
+			    rpmlog(RPMLOG_ERR, "%s: %m\n", stringerr);
+			} else {
+			    if (symlink (targetpath, linkpath) < 0) {
+				const char *linkerr = _("failed symlink");
+				rc = 1;
+				rpmlog(RPMLOG_ERR, "%s: %s -> %s: %m\n",
+				       linkerr, linkpath, targetpath);
+			    } else {
+				fl->cur.isDir = 0;
+				rc = addFile(fl, linkpath, NULL);
+			    }
+			}
+			free(linkpath);
+			free(targetpath);
+		    }
+		}
+		free(buildidsubdir);
+	    }
+	    free(paths[i]);
+	    free(ids[i]);
+	}
+	free(buildiddir);
+	free(paths);
+	free(ids);
+    }
+    return rc;
+}
+#endif
+
 /**
  * Add a file to a binary package.
  * @param pkg
@@ -1958,6 +2079,11 @@ static rpmRC processPackageFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags,
     if (fl.processingFailed)
 	goto exit;
 
+#if HAVE_LIBDW
+    if (generateBuildIDs (&fl) != 0)
+	goto exit;
+#endif
+
     /* Verify that file attributes scope over hardlinks correctly. */
     if (checkHardLinks(&fl.files))
 	(void) rpmlibNeedsFeature(pkg, "PartialHardlinkSets", "4.0.4-1");
@@ -2156,6 +2282,9 @@ rpmRC processBinaryFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags,
     Package pkg;
     rpmRC rc = RPMRC_OK;
     
+#if HAVE_LIBDW
+    elf_version (EV_CURRENT);
+#endif
     check_fileList = newStringBuf();
     genSourceRpmName(spec);
     
diff --git a/configure.ac b/configure.ac
index b77160b..e2d5ec3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -368,6 +368,21 @@ AC_SUBST(WITH_ARCHIVE_LIB)
 AM_CONDITIONAL(WITH_ARCHIVE,[test "$with_archive" = yes])
 
 #=================
+# Check for elfutils libdw library with dwelf_elf_gnu_build_id.
+AS_IF([test "$WITH_LIBELF" = yes],[
+  AC_CHECK_HEADERS([elfutils/libdwelf.h],[
+    AC_CHECK_LIB(dw, dwelf_elf_gnu_build_id, [
+      AC_DEFINE(HAVE_LIBDW, 1,
+                [Define to 1 if you have elfutils libdw library])
+      WITH_LIBDW_LIB="-ldw"
+      WITH_LIBDW=yes
+    ])
+  ])
+  AC_SUBST(WITH_LIBDW_LIB)
+  AM_CONDITIONAL(LIBDW,[test "$WITH_LIBDW" = yes])
+])
+
+#=================
 # Process --with/without-external-db
 AC_ARG_WITH(external_db, [AS_HELP_STRING([--with-external-db],[build against an external Berkeley db])],
 [case "$with_external_db" in
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 779b92f..96c05ff 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -19,6 +19,7 @@ TESTSUITE_AT += rpmquery.at
 TESTSUITE_AT += rpmverify.at
 TESTSUITE_AT += rpmdb.at
 TESTSUITE_AT += rpmbuild.at
+TESTSUITE_AT += rpmbuildid.at
 TESTSUITE_AT += rpmi.at
 TESTSUITE_AT += rpmvercmp.at
 TESTSUITE_AT += rpmdeps.at
diff --git a/tests/rpmbuildid.at b/tests/rpmbuildid.at
new file mode 100644
index 0000000..9a6cf30
--- /dev/null
+++ b/tests/rpmbuildid.at
@@ -0,0 +1,47 @@
+# rpmbuildid.at: test rpmbuild buildid symlink support
+#
+# This file is part of RPM, the RPM Package Manager.
+# Copyright (C) 2016 Mark J. Wielaard <mjw at redhat.com>
+#
+# This file 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# RPM 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 <http://www.gnu.org/licenses/>.
+
+AT_BANNER([RPM buildid tests])
+
+# ------------------------------
+# Check if rpmbuild generates buildid symlinks for hello program
+AT_SETUP([rpmbuild buildid])
+AT_KEYWORDS([buildid])
+AT_CHECK([
+rm -rf ${TOPDIR}
+AS_MKDIR_P(${TOPDIR}/SOURCES)
+
+# Setup sources
+cp "${abs_srcdir}"/data/SOURCES/hello-1.0.tar.gz "${abs_srcdir}"/data/SOURCES/hello-1.0-modernize.patch ${TOPDIR}/SOURCES
+
+# Build, contains one ELF which should have a buildid.
+run rpmbuild -ba "${abs_srcdir}"/data/SPECS/hello.spec > /dev/null
+
+# We expect exactly one new style build-id link for the hello program
+# So it will have 2 dirs and one link:
+# dir:  /usr/lib/.build-id
+# dir:  /usr/lib/.build-id/xx
+# link: /usr/lib/.build-id/xx/yyyy
+runroot rpm -ql -p "${TOPDIR}"/RPMS/*/hello-1.0-1.*.rpm \
+  | grep /usr/lib/.build-id | wc --lines
+],
+[0],
+[3
+],
+[ignore])
+AT_CLEANUP
diff --git a/tests/rpmtests.at b/tests/rpmtests.at
index b51266a..5495cce 100644
--- a/tests/rpmtests.at
+++ b/tests/rpmtests.at
@@ -4,6 +4,7 @@ m4_include([rpmverify.at])
 m4_include([rpmdb.at])
 m4_include([rpmi.at])
 m4_include([rpmbuild.at])
+m4_include([rpmbuildid.at])
 m4_include([rpmscript.at])
 m4_include([rpmvercmp.at])
 m4_include([rpmdeps.at])
-- 
2.5.5



More information about the Rpm-maint mailing list