teknoraver / rpms / rpm

Forked from rpms/rpm 4 months ago
Clone
Blob Blame History Raw
From 46b763e54de34d0dd28d82e3a87b59c6a94a4c07 Mon Sep 17 00:00:00 2001
From: Mark Wielaard <mjw@redhat.com>
Date: Tue, 14 Jun 2016 17:07:12 +0200
Subject: [PATCH] Add build-id links to rpm for all ELF files.

This patch moves the main ELF file build-id symlinks from the
debuginfo package into the main package. And uses different
base directories for the main ELF file build-id symlink.
For the main build-id use /usr/lib/.build-id and for the debug
build-id use /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.

To support the new logic the symlink code has been moved from
find-debuginfo.sh to build/files.c.

This also includes support for a new config %_build_id_links that
defaults to compat. The other settings are none, alldebug (the old
style) and separate. compat is like separate, but adds a compatibility
link under /usr/lib/debug/.build-id for the main build-id symlink.

There are several new testcases added to test the various settings
using the new keyword "buildid".

Signed-off-by: Mark Wielaard <mjw@redhat.com>
(cherry picked from commit bbfe1f86b2e4b5c0bd499d9f3dd9de9c9c20fff2)
---
 build/Makefile.am              |   4 +
 build/files.c                  | 375 ++++++++++++++++++++
 configure.ac                   |  15 +
 macros.in                      |  28 ++
 scripts/find-debuginfo.sh      |  60 ----
 tests/Makefile.am              |   1 +
 tests/data/SPECS/hello2cp.spec |  64 ++++
 tests/data/SPECS/hello2ln.spec |  63 ++++
 tests/rpmbuildid.at            | 761 +++++++++++++++++++++++++++++++++++++++++
 tests/rpmtests.at              |   1 +
 10 files changed, 1312 insertions(+), 60 deletions(-)
 create mode 100644 tests/data/SPECS/hello2cp.spec
 create mode 100644 tests/data/SPECS/hello2ln.spec
 create mode 100644 tests/rpmbuildid.at

diff --git a/build/Makefile.am b/build/Makefile.am
index dbda716ac..71edbd1c4 100644
--- a/build/Makefile.am
+++ b/build/Makefile.am
@@ -26,3 +26,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 b76ce048f..b010483e4 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>
@@ -1551,6 +1556,368 @@ exit:
     return rc;
 }
 
+#if HAVE_LIBDW
+/* How build id links are generated.  See macros.in for description.  */
+#define BUILD_IDS_NONE     0
+#define BUILD_IDS_ALLDEBUG 1
+#define BUILD_IDS_SEPARATE 2
+#define BUILD_IDS_COMPAT   3
+
+static int addNewIDSymlink(FileList fl,
+			   char *targetpath, char *idlinkpath,
+			   int isDbg, int isCompat)
+{
+    const char *linkerr = _("failed symlink");
+    int rc = 0;
+    int nr = 0;
+    char *origpath, *linkpath;
+
+    if (isDbg)
+	rasprintf(&linkpath, "%s.debug", idlinkpath);
+    else
+	linkpath = idlinkpath;
+    origpath = linkpath;
+
+    while (faccessat(AT_FDCWD, linkpath, F_OK, AT_SYMLINK_NOFOLLOW) == 0) {
+	if (nr > 0)
+	    free(linkpath);
+	nr++;
+	rasprintf(&linkpath, "%s.%d%s", idlinkpath, nr,
+		  isDbg ? ".debug" : "");
+    }
+
+    char *symtarget = targetpath;
+    if (nr > 0 && isCompat)
+	rasprintf (&symtarget, "%s.%d", targetpath, nr);
+
+    if (symlink(symtarget, linkpath) < 0) {
+	rc = 1;
+	rpmlog(RPMLOG_ERR, "%s: %s -> %s: %m\n",
+	       linkerr, linkpath, symtarget);
+    } else {
+	fl->cur.isDir = 0;
+	rc = addFile(fl, linkpath, NULL);
+    }
+
+    /* Don't warn (again) if this is a compat id-link, we retarget it. */
+    if (nr > 0 && !isCompat) {
+	/* Lets see why there are multiple build-ids. If the original
+	   targets are hard linked, then it is OK, otherwise warn
+	   something fishy is going on. Would be nice to call
+	   something like eu-elfcmp to see if they are really the same
+	   ELF file or not. */
+	struct stat st1, st2;
+	if (stat (origpath, &st1) != 0) {
+	    rpmlog(RPMLOG_WARNING, _("Duplicate build-id, stat %s: %m\n"),
+		   origpath);
+	} else if (stat (linkpath, &st2) != 0) {
+	    rpmlog(RPMLOG_WARNING, _("Duplicate build-id, stat %s: %m\n"),
+		   linkpath);
+	} else if (!(S_ISREG(st1.st_mode) && S_ISREG(st2.st_mode)
+		  && st1.st_nlink > 1 && st2.st_nlink == st1.st_nlink
+		  && st1.st_ino == st2.st_ino && st1.st_dev == st2.st_dev)) {
+	    char *rpath1 = realpath(origpath, NULL);
+	    char *rpath2 = realpath(linkpath, NULL);
+	    rpmlog(RPMLOG_WARNING, _("Duplicate build-ids %s and %s\n"),
+		   rpath1, rpath2);
+	    free(rpath1);
+	    free(rpath2);
+	}
+    }
+
+    if (isDbg)
+	free(origpath);
+    if (nr > 0)
+	free(linkpath);
+    if (nr > 0 && isCompat)
+	free(symtarget);
+
+    return rc;
+}
+
+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;
+
+    /* How are we supposed to create the build-id links?  */
+    char *build_id_links_macro = rpmExpand("%{?_build_id_links}", NULL);
+    int build_id_links;
+    if (build_id_links_macro == NULL) {
+	rpmlog(RPMLOG_WARNING,
+	       _("_build_id_links macro not set, assuming 'compat'\n"));
+	build_id_links = BUILD_IDS_COMPAT;
+    } else if (strcmp(build_id_links_macro, "none") == 0) {
+	build_id_links = BUILD_IDS_NONE;
+    } else if (strcmp(build_id_links_macro, "alldebug") == 0) {
+	build_id_links = BUILD_IDS_ALLDEBUG;
+    } else if (strcmp(build_id_links_macro, "separate") == 0) {
+	build_id_links = BUILD_IDS_SEPARATE;
+    } else if (strcmp(build_id_links_macro, "compat") == 0) {
+	build_id_links = BUILD_IDS_COMPAT;
+    } else {
+	rc = 1;
+	rpmlog(RPMLOG_ERR,
+	       _("_build_id_links macro set to unknown value '%s'\n"),
+	       build_id_links_macro);
+	build_id_links = BUILD_IDS_NONE;
+    }
+    free(build_id_links_macro);
+
+    if (build_id_links == BUILD_IDS_NONE || rc != 0)
+	return rc;
+
+    int terminate = rpmExpandNumeric("%{?_missing_build_ids_terminate_build}");
+
+    /* Collect and check all build-ids for ELF files in this package.  */
+    int needMain = 0;
+    int needDbg = 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 && elf_kind(elf) == ELF_K_ELF) {
+		    const void *build_id;
+		    ssize_t len = dwelf_elf_gnu_build_id (elf, &build_id);
+		    /* len == -1 means error. Zero means no
+		       build-id. We want at least a length of 2 so we
+		       have at least a xx/yy (hex) dir/file. But
+		       reasonable build-ids are between 16 bytes (md5
+		       is 128 bits) and 64 bytes (largest sha3 is 512
+		       bits), common is 20 bytes (sha1 is 160 bits). */
+		    if (len >= 16 && len <= 64) {
+			/* We determine whether this is a main or
+			   debug ELF based on path.  */
+			#define DEBUGPATH "/usr/lib/debug/"
+			int addid = 0;
+			if (strncmp (flp->cpioPath,
+				     DEBUGPATH, strlen (DEBUGPATH)) == 0) {
+			    needDbg = 1;
+			    addid = 1;
+			}
+			else if (build_id_links != BUILD_IDS_ALLDEBUG) {
+			    needMain = 1;
+			    addid = 1;
+			}
+			if (addid) {
+			    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(terminate ? RPMLOG_ERR : RPMLOG_WARNING,
+				   _("error reading build-id in %s: %s\n"),
+				   flp->diskPath, elf_errmsg (-1));
+			} else if (len == 0) {
+			    rpmlog(terminate ? RPMLOG_ERR : RPMLOG_WARNING,
+				   _("Missing build-id in %s\n"),
+				   flp->diskPath);
+			} else {
+			    rpmlog(terminate ? RPMLOG_ERR : RPMLOG_WARNING,
+				   (len < 16
+				    ? _("build-id found in %s too small\n")
+				    : _("build-id found in %s too large\n")),
+				   flp->diskPath);
+			}
+			if (terminate)
+			    rc = 1;
+		    }
+		    elf_end (elf);
+		}
+	    }
+	    close (fd);
+	}
+    }
+
+    /* Process and clean up all build-ids.  */
+    if (nr_ids > 0) {
+	const char *errdir = _("failed to create directory");
+	char *mainiddir = NULL;
+	char *debugiddir = NULL;
+	if (rc == 0) {
+	    /* Add .build-id directories to hold the subdirs/symlinks.  */
+            #define BUILD_ID_DIR "/usr/lib/.build-id"
+            #define DEBUG_ID_DIR "/usr/lib/debug/.build-id"
+
+	    mainiddir = rpmGetPath(fl->buildRoot, BUILD_ID_DIR, NULL);
+	    debugiddir = rpmGetPath(fl->buildRoot, DEBUG_ID_DIR, NULL);
+
+	    /* Supported, but questionable.  */
+	    if (needMain && needDbg)
+		rpmlog(RPMLOG_WARNING,
+		       _("Mixing main ELF and debug files in package"));
+
+	    if (needMain) {
+		if ((rc = rpmioMkpath(mainiddir, 0755, -1, -1)) != 0) {
+		    rpmlog(RPMLOG_ERR, "%s %s: %m\n", errdir, mainiddir);
+		} else {
+		    fl->cur.isDir = 1;
+		    rc = addFile(fl, mainiddir, NULL);
+		}
+	    }
+
+	    if (rc == 0 && needDbg) {
+		if ((rc = rpmioMkpath(debugiddir, 0755, -1, -1)) != 0) {
+		    rpmlog(RPMLOG_ERR, "%s %s: %m\n", errdir, debugiddir);
+		} else {
+		    fl->cur.isDir = 1;
+		    rc = addFile(fl, debugiddir, NULL);
+		}
+	    }
+	}
+
+	/* Now add a subdir and symlink for each buildid found.  */
+	for (i = 0; i < nr_ids; i++) {
+	    /* Don't add anything more when an error occured. But do
+	       cleanup.  */
+	    if (rc == 0) {
+		int isDbg = strncmp (paths[i], DEBUGPATH,
+				     strlen (DEBUGPATH)) == 0;
+
+		char *buildidsubdir;
+		char subdir[4];
+		subdir[0] = '/';
+		subdir[1] = ids[i][0];
+		subdir[2] = ids[i][1];
+		subdir[3] = '\0';
+		if (isDbg)
+		    buildidsubdir = rpmGetPath(debugiddir, subdir, NULL);
+		else
+		    buildidsubdir = rpmGetPath(mainiddir, subdir, NULL);
+		/* We only need to create and add the subdir once. */
+		int addsubdir = access (buildidsubdir, F_OK) == -1;
+		if (addsubdir
+		    && (rc = rpmioMkpath(buildidsubdir, 0755, -1, -1)) != 0) {
+		    rpmlog(RPMLOG_ERR, "%s %s: %m\n", errdir, buildidsubdir);
+		} else {
+		    fl->cur.isDir = 1;
+		    if (!addsubdir
+			|| (rc = addFile(fl, buildidsubdir, NULL)) == 0) {
+			char *linkpattern, *targetpattern;
+			char *linkpath, *targetpath;
+			if (isDbg) {
+			    linkpattern = "%s/%s";
+			    targetpattern = "../../../../..%s";
+			} else {
+			    linkpattern = "%s/%s";
+			    targetpattern = "../../../..%s";
+			}
+			rasprintf(&linkpath, linkpattern,
+				  buildidsubdir, &ids[i][2]);
+			rasprintf(&targetpath, targetpattern, paths[i]);
+			rc = addNewIDSymlink(fl, targetpath, linkpath,
+					     isDbg, 0);
+
+			/* We might want to have a link from the debug
+			   build_ids dir to the main one. We create it
+			   when we are creating compat links or doing
+			   an old style alldebug build-ids package. In
+			   the first case things are simple since we
+			   just link to the main build-id symlink. The
+			   second case is a bit tricky, since we
+			   cannot be 100% sure the file names in the
+			   main and debug package match. Currently
+			   they do, but when creating parallel
+			   installable debuginfo packages they might
+			   not (in that case we might have to also
+			   strip the nvr from the debug name).
+
+			   In general either method is discouraged
+                           since it might create dangling symlinks if
+                           the package versions get out of sync.  */
+			if (rc == 0 && isDbg
+			    && build_id_links == BUILD_IDS_COMPAT) {
+			    /* buildidsubdir already points to the
+			       debug buildid. We just need to setup
+			       the symlink to the main one.  */
+			    free(linkpath);
+			    free(targetpath);
+			    rasprintf(&linkpath, "%s/%s",
+				      buildidsubdir, &ids[i][2]);
+			    rasprintf(&targetpath,
+				      "../../../.build-id%s/%s",
+				      subdir, &ids[i][2]);
+			    rc = addNewIDSymlink(fl, targetpath, linkpath,
+						 0, 1);
+			}
+
+			if (rc == 0 && isDbg
+			    && build_id_links == BUILD_IDS_ALLDEBUG) {
+			    /* buildidsubdir already points to the
+			       debug buildid. We do have to figure out
+			       the main ELF file though (which is most
+			       likely not in this package). Guess we
+			       can find it by stripping the
+			       /usr/lib/debug path and .debug
+			       prefix. Which might not really be
+			       correct if there was a more involved
+			       transformation (for example for
+			       parallel installable debuginfo
+			       packages), but then we shouldn't be
+			       using ALLDEBUG in the first place.
+			       Also ignore things like .dwz multifiles
+			       which don't end in ".debug". */
+			    int pathlen = strlen(paths[i]);
+			    int debuglen = strlen(".debug");
+			    int prefixlen = strlen("/usr/lib/debug");
+			    if (pathlen > prefixlen
+				&& strcmp (paths[i] + pathlen - debuglen,
+					   ".debug") == 0) {
+				free(linkpath);
+				free(targetpath);
+				char *targetstr = xstrdup (paths[i]
+							   + prefixlen);
+				int targetlen = pathlen - prefixlen;
+				targetstr[targetlen - debuglen] = '\0';
+				rasprintf(&linkpath, "%s/%s",
+					  buildidsubdir, &ids[i][2]);
+				rasprintf(&targetpath, "../../../../..%s",
+					  targetstr);
+				rc = addNewIDSymlink(fl, targetpath,
+						     linkpath, 0, 0);
+				free(targetstr);
+			    }
+			}
+			free(linkpath);
+			free(targetpath);
+		    }
+		}
+		free(buildidsubdir);
+	    }
+	    free(paths[i]);
+	    free(ids[i]);
+	}
+	free(paths);
+	free(ids);
+    }
+    return rc;
+}
+#endif
+
 /**
  * Add a file to a binary package.
  * @param pkg
@@ -1963,6 +2330,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");
@@ -2161,6 +2533,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 91fadbade..8890c3c32 100644
--- a/configure.ac
+++ b/configure.ac
@@ -360,6 +360,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/macros.in b/macros.in
index fd57f2eba..e43d62b0a 100644
--- a/macros.in
+++ b/macros.in
@@ -449,6 +449,34 @@ package or when debugging this package.\
 #%_include_minidebuginfo	1
 
 #
+# Defines how and if build_id links are generated for ELF files.
+# The following settings are supported:
+#
+# - none
+#   No build_id links are generated.
+#
+# - alldebug
+#   build_id links are generated only when the __debug_package global is
+#   defined. This will generate build_id links in the -debuginfo package
+#   for both the main file as /usr/lib/debug/.build-id/xx/yyy and for
+#   the .debug file as /usr/lib/debug/.build-id/xx/yyy.debug.
+#   This is the old style build_id links as generated by the original
+#   find-debuginfo.sh script.
+#
+# - separate
+#   build_id links are generate for all binary packages. If this is a
+#   main package (the __debug_package global isn't set) then the
+#   build_id link is generated as /usr/lib/.build-id/xx/yyy. If this is
+#   a -debuginfo package (the __debug_package global is set) then the
+#   build_id link is generated as /usr/lib/debug/.build-id/xx/yyy.
+#
+# - compat
+#   Same as for "separate" but if the __debug_package global is set then
+#   the -debuginfo package will have a compatibility link for the main
+#   ELF /usr/lib/debug/.build-id/xx/yyy -> /usr/lib/.build-id/xx/yyy
+%_build_id_links compat
+
+#
 # Use internal dependency generator rather than external helpers?
 %_use_internal_dependency_generator	1
 
diff --git a/scripts/find-debuginfo.sh b/scripts/find-debuginfo.sh
index 4293261c9..c9e2293de 100644
--- a/scripts/find-debuginfo.sh
+++ b/scripts/find-debuginfo.sh
@@ -207,57 +207,6 @@ debug_link()
   link_relative "$t" "$l" "$RPM_BUILD_ROOT"
 }
 
-# Provide .2, .3, ... symlinks to all filename instances of this build-id.
-make_id_dup_link()
-{
-  local id="$1" file="$2" idfile
-
-  local n=1
-  while true; do
-    idfile=".build-id/${id:0:2}/${id:2}.$n"
-    [ $# -eq 3 ] && idfile="${idfile}$3"
-    if [ ! -L "$RPM_BUILD_ROOT/usr/lib/debug/$idfile" ]; then
-      break
-    fi
-    n=$[$n+1]
-  done
-  debug_link "$file" "/$idfile"
-}
-
-# Make a build-id symlink for id $1 with suffix $3 to file $2.
-make_id_link()
-{
-  local id="$1" file="$2"
-  local idfile=".build-id/${id:0:2}/${id:2}"
-  [ $# -eq 3 ] && idfile="${idfile}$3"
-  local root_idfile="$RPM_BUILD_ROOT/usr/lib/debug/$idfile"
-
-  if [ ! -L "$root_idfile" ]; then
-    debug_link "$file" "/$idfile"
-    return
-  fi
-
-  make_id_dup_link "$@"
-
-  [ $# -eq 3 ] && return 0
-
-  local other=$(readlink -m "$root_idfile")
-  other=${other#$RPM_BUILD_ROOT}
-  if cmp -s "$root_idfile" "$RPM_BUILD_ROOT$file" ||
-     eu-elfcmp -q "$root_idfile" "$RPM_BUILD_ROOT$file" 2> /dev/null; then
-    # Two copies.  Maybe one has to be setuid or something.
-    echo >&2 "*** WARNING: identical binaries are copied, not linked:"
-    echo >&2 "        $file"
-    echo >&2 "   and  $other"
-  else
-    # This is pathological, break the build.
-    echo >&2 "*** ERROR: same build ID in nonidentical files!"
-    echo >&2 "        $file"
-    echo >&2 "   and  $other"
-    exit 2
-  fi
-}
-
 get_debugfn()
 {
   dn=$(dirname "${1#$RPM_BUILD_ROOT}")
@@ -288,8 +237,6 @@ while read nlinks inum f; do
     eval linked=\$linked_$inum
     if [ -n "$linked" ]; then
       eval id=\$linkedid_$inum
-      make_id_dup_link "$id" "$dn/$(basename $f)"
-      make_id_dup_link "$id" "/usr/lib/debug$dn/$bn" .debug
       link=$debugfn
       get_debugfn "$linked"
       echo "hard linked $link to $debugfn"
@@ -318,7 +265,6 @@ while read nlinks inum f; do
   # just has its file names collected and adjusted.
   case "$dn" in
   /usr/lib/debug/*)
-    [ -z "$id" ] || make_id_link "$id" "$dn/$(basename $f)"
     continue ;;
   esac
 
@@ -336,10 +282,6 @@ while read nlinks inum f; do
 
   echo "./${f#$RPM_BUILD_ROOT}" >> "$ELFBINSFILE"
 
-  if [ -n "$id" ]; then
-    make_id_link "$id" "$dn/$(basename $f)"
-    make_id_link "$id" "/usr/lib/debug$dn/$bn" .debug
-  fi
 done || exit
 
 # Invoke the DWARF Compressor utility.
@@ -367,8 +309,6 @@ if $run_dwz && type dwz >/dev/null 2>&1 \
     if [ -f "${RPM_BUILD_ROOT}/usr/lib/debug/.dwz/${dwz_multifile_name}" ]; then
       id="`readelf -Wn "${RPM_BUILD_ROOT}/usr/lib/debug/.dwz/${dwz_multifile_name}" \
 	     2>/dev/null | sed -n 's/^    Build ID: \([0-9a-f]\+\)/\1/p'`"
-      [ -n "$id" ] \
-	&& make_id_link "$id" "/usr/lib/debug/.dwz/${dwz_multifile_name}" .debug
     fi
 
     # dwz invalidates .gnu_debuglink CRC32 in the main files.
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 7a5cc6544..10555ce9a 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/data/SPECS/hello2cp.spec b/tests/data/SPECS/hello2cp.spec
new file mode 100644
index 000000000..33d8dc260
--- /dev/null
+++ b/tests/data/SPECS/hello2cp.spec
@@ -0,0 +1,64 @@
+Summary: hello2 -- double hello, world rpm
+Name: hello2
+Version: 1.0
+Release: 1
+Group: Utilities
+License: GPL
+Distribution: RPM test suite.
+Vendor: Red Hat Software
+Packager: Red Hat Software <bugs@redhat.com>
+URL: http://www.redhat.com
+Source0: hello-1.0.tar.gz
+Patch0: hello-1.0-modernize.patch
+Excludearch: lsi
+Excludeos: cpm
+Provides: hi
+Conflicts: goodbye
+Obsoletes: howdy
+Prefix: /usr
+
+%description
+Simple rpm demonstration.
+
+%prep
+%setup -q -n hello-1.0
+%patch0 -p1 -b .modernize
+
+%build
+make CFLAGS="-g -O1"
+cp hello hello2
+
+%install
+rm -rf $RPM_BUILD_ROOT
+mkdir -p $RPM_BUILD_ROOT/usr/local/bin
+make DESTDIR=$RPM_BUILD_ROOT install
+cp hello2 $RPM_BUILD_ROOT/usr/local/bin/
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%pre
+
+%post
+
+%preun
+
+%postun
+
+%files
+%defattr(-,root,root)
+%doc	FAQ
+#%readme README
+#%license COPYING
+%attr(0751,root,root)	/usr/local/bin/hello
+%attr(0751,root,root)	/usr/local/bin/hello2
+
+%changelog
+* Mon Jun  6 2016 Mark Wielaard <mjw@redhat.com>
+- Copy hello to hello2 for duplicate build-id testing.
+
+* Wed May 18 2016 Mark Wielaard <mjw@redhat.com>
+- Add hello2 for dwz testing support.
+
+* Tue Oct 20 1998 Jeff Johnson <jbj@redhat.com>
+- create.
diff --git a/tests/data/SPECS/hello2ln.spec b/tests/data/SPECS/hello2ln.spec
new file mode 100644
index 000000000..2c40dcc32
--- /dev/null
+++ b/tests/data/SPECS/hello2ln.spec
@@ -0,0 +1,63 @@
+Summary: hello2 -- double hello, world rpm
+Name: hello2
+Version: 1.0
+Release: 1
+Group: Utilities
+License: GPL
+Distribution: RPM test suite.
+Vendor: Red Hat Software
+Packager: Red Hat Software <bugs@redhat.com>
+URL: http://www.redhat.com
+Source0: hello-1.0.tar.gz
+Patch0: hello-1.0-modernize.patch
+Excludearch: lsi
+Excludeos: cpm
+Provides: hi
+Conflicts: goodbye
+Obsoletes: howdy
+Prefix: /usr
+
+%description
+Simple rpm demonstration.
+
+%prep
+%setup -q -n hello-1.0
+%patch0 -p1 -b .modernize
+
+%build
+make CFLAGS="-g -O1"
+
+%install
+rm -rf $RPM_BUILD_ROOT
+mkdir -p $RPM_BUILD_ROOT/usr/local/bin
+make DESTDIR=$RPM_BUILD_ROOT install
+ln $RPM_BUILD_ROOT/usr/local/bin/hello $RPM_BUILD_ROOT/usr/local/bin/hello2
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%pre
+
+%post
+
+%preun
+
+%postun
+
+%files
+%defattr(-,root,root)
+%doc	FAQ
+#%readme README
+#%license COPYING
+%attr(0751,root,root)	/usr/local/bin/hello
+%attr(0751,root,root)	/usr/local/bin/hello2
+
+%changelog
+* Mon Jun  6 2016 Mark Wielaard <mjw@redhat.com>
+- Hard link hello to hello2 for duplicate build-id testing.
+
+* Wed May 18 2016 Mark Wielaard <mjw@redhat.com>
+- Add hello2 for dwz testing support.
+
+* Tue Oct 20 1998 Jeff Johnson <jbj@redhat.com>
+- create.
diff --git a/tests/rpmbuildid.at b/tests/rpmbuildid.at
new file mode 100644
index 000000000..eddca969b
--- /dev/null
+++ b/tests/rpmbuildid.at
@@ -0,0 +1,761 @@
+# rpmbuildid.at: test rpmbuild buildid symlink support
+#
+# This file is part of RPM, the RPM Package Manager.
+# Copyright (C) 2016 Mark J. Wielaard <mjw@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 "none" doesn't generates buildid symlinks for hello program
+AT_SETUP([rpmbuild buildid none])
+AT_KEYWORDS([build] [debuginfo] [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 \
+  --macros=${abs_top_builddir}/macros:${abs_top_builddir}/tests/testing/usr/local/lib/rpm/platform/%{_target_cpu}-%{_target_os}/macros:${top_srcdir}/macros.debug \
+  --rcfile=${abs_top_builddir}/rpmrc \
+  --define="_build_id_links none" \
+  --quiet -ba "${abs_srcdir}"/data/SPECS/hello.spec
+
+# There should be zero build-id files in both the main and debuginfo package
+echo -n "hello build-id files: "
+run rpm -ql -p "${TOPDIR}"/RPMS/*/hello-1.0-1.*.rpm \
+  | grep /.build-id/ | wc --lines
+
+echo -n "hello debuginfo build-id files: "
+run rpm -ql -p "${TOPDIR}"/RPMS/*/hello-debuginfo-1.0-1.*.rpm \
+  | grep /.build-id/ | wc --lines
+
+],
+[0],
+[hello build-id files: 0
+hello debuginfo build-id files: 0
+],
+[ignore])
+AT_CLEANUP
+
+# ------------------------------
+# Check if rpmbuild "alldebug" generates debuginfo buildid symlinks
+AT_SETUP([rpmbuild buildid alldebug])
+AT_KEYWORDS([build] [debuginfo] [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 \
+  --macros=${abs_top_builddir}/macros:${abs_top_builddir}/tests/testing/usr/local/lib/rpm/platform/%{_target_cpu}-%{_target_os}/macros:${top_srcdir}/macros.debug \
+  --rcfile=${abs_top_builddir}/rpmrc \
+  --define="_build_id_links alldebug" \
+  --quiet -ba "${abs_srcdir}"/data/SPECS/hello.spec
+
+# There should be zero build-id files in the main package
+# Main and debug should be in the debuginfo package,
+# plus the .build-id/xx subdir, 3 in total.
+echo -n "hello build-id files: "
+run rpm -ql -p "${TOPDIR}"/RPMS/*/hello-1.0-1.*.rpm \
+  | grep /.build-id/ | wc --lines
+
+echo -n "hello debuginfo build-id files: "
+run rpm -ql -p "${TOPDIR}"/RPMS/*/hello-debuginfo-1.0-1.*.rpm \
+  | grep /.build-id/ | wc --lines
+
+# Extract the both packages to check the build-id files link to the
+# main and .debug files.
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello-1.0-1.*.rpm \
+  | cpio -diu
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello-debuginfo-1.0-1.*.rpm \
+  | cpio -diu
+
+# Check there is a build-id symlink for the main file.
+main_file=./usr/local/bin/hello
+test -f "${main_file}" || echo "No main file ${main_file}"
+
+# Extract the build-id from the main file
+id_main=$(file $main_file | sed 's/.*, BuildID[.*]=\(.*\),.*/\1/')
+
+id_main_file="./usr/lib/debug/.build-id/${id_main:0:2}/${id_main:2}"
+test -L "$id_main_file" || echo "No build-id file $id_main_file"
+
+canon_main_file=$(readlink -f ${main_file})
+
+test -f "$canon_main_file" \
+  || echo "Cannot resolve main file ${main_file} -> ${canon_main_file}"
+
+canon_main_id_file=$(readlink -f ${id_main_file})
+
+test -f "$canon_main_id_file" \
+  || echo "Cannot resolve main build-id file ${id_main_file} -> ${canon_main_id_file}"
+
+test "$canon_main_file" = "$canon_main_id_file" \
+  || echo "main and build-id file not linked"
+
+# And check the same for the debug file.
+debug_file=./usr/lib/debug/usr/local/bin/hello.debug
+test -f "${debug_file}" || echo "No debug file ${debug_file}"
+
+# Extract the build-id from the .debug file
+id_debug=$(file $debug_file | sed 's/.*, BuildID[.*]=\(.*\),.*/\1/')
+
+test ${id_main} = ${id_debug} || echo "unequal main and debug id"
+
+id_debug_file="./usr/lib/debug/.build-id/${id_debug:0:2}/${id_debug:2}.debug"
+test -L "$id_debug_file" || echo "No build-id file $id_debug_file"
+
+canon_debug_file=$(readlink -f ${debug_file})
+
+test -f "$canon_debug_file" \
+  || echo "Cannot resolve debug file ${debug_file} -> ${canon_debug_file}"
+
+canon_debug_id_file=$(readlink -f ${id_debug_file})
+
+test -f "$canon_debug_id_file" \
+  || echo "Cannot resolve debug build-id file ${id_debug_file} -> ${canon_debug_id_file}"
+
+test "$canon_debug_file" = "$canon_debug_id_file" \
+  || echo "debug and build-id not linked"
+],
+[0],
+[hello build-id files: 0
+hello debuginfo build-id files: 3
+],
+[ignore])
+AT_CLEANUP
+
+# ------------------------------
+# Check if rpmbuild "separate" generates main and debuginfo buildid symlinks
+AT_SETUP([rpmbuild buildid separate])
+AT_KEYWORDS([build] [debuginfo] [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 \
+  --macros=${abs_top_builddir}/macros:${abs_top_builddir}/tests/testing/usr/local/lib/rpm/platform/%{_target_cpu}-%{_target_os}/macros:${top_srcdir}/macros.debug \
+  --rcfile=${abs_top_builddir}/rpmrc \
+  --define="_build_id_links separate" \
+  --quiet -ba "${abs_srcdir}"/data/SPECS/hello.spec
+
+# There should be one build-id files in the main and debuginfo package
+# plus the .build-id/xx subdir, 2 in total.
+echo -n "hello build-id files: "
+run rpm -ql -p "${TOPDIR}"/RPMS/*/hello-1.0-1.*.rpm \
+  | grep /.build-id/ | wc --lines
+
+echo -n "hello debuginfo build-id files: "
+run rpm -ql -p "${TOPDIR}"/RPMS/*/hello-debuginfo-1.0-1.*.rpm \
+  | grep /.build-id/ | wc --lines
+
+# Extract the both packages to check the build-id files link to the
+# main and .debug files.
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello-1.0-1.*.rpm \
+  | cpio -diu
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello-debuginfo-1.0-1.*.rpm \
+  | cpio -diu
+
+# Check there is a build-id symlink for the main file.
+main_file=./usr/local/bin/hello
+test -f "${main_file}" || echo "No main file ${main_file}"
+
+# Extract the build-id from the main file
+id_main=$(file $main_file | sed 's/.*, BuildID[.*]=\(.*\),.*/\1/')
+
+id_main_file="./usr/lib/.build-id/${id_main:0:2}/${id_main:2}"
+test -L "$id_main_file" || echo "No build-id file $id_main_file"
+
+canon_main_file=$(readlink -f ${main_file})
+
+test -f "$canon_main_file" \
+  || echo "Cannot resolve main file ${main_file} -> ${canon_main_file}"
+
+canon_main_id_file=$(readlink -f ${id_main_file})
+
+test -f "$canon_main_id_file" \
+  || echo "Cannot resolve main build-id file ${id_main_file} -> ${canon_main_id_file}"
+
+test "$canon_main_file" = "$canon_main_id_file" \
+  || echo "main and build-id file not linked"
+
+# And check the same for the debug file.
+debug_file=./usr/lib/debug/usr/local/bin/hello.debug
+test -f "${debug_file}" || echo "No debug file ${debug_file}"
+
+# Extract the build-id from the .debug file
+id_debug=$(file $debug_file | sed 's/.*, BuildID[.*]=\(.*\),.*/\1/')
+
+test ${id_main} = ${id_debug} || echo "unequal main and debug id"
+
+id_debug_file="./usr/lib/debug/.build-id/${id_debug:0:2}/${id_debug:2}.debug"
+test -L "$id_debug_file" || echo "No build-id file $id_debug_file"
+
+canon_debug_file=$(readlink -f ${debug_file})
+
+test -f "$canon_debug_file" \
+  || echo "Cannot resolve debug file ${debug_file} -> ${canon_debug_file}"
+
+canon_debug_id_file=$(readlink -f ${id_debug_file})
+
+test -f "$canon_debug_id_file" \
+  || echo "Cannot resolve debug build-id file ${id_debug_file} -> ${canon_debug_id_file}"
+
+test "$canon_debug_file" = "$canon_debug_id_file" \
+  || echo "debug and build-id not linked"
+],
+[0],
+[hello build-id files: 2
+hello debuginfo build-id files: 2
+],
+[ignore])
+AT_CLEANUP
+
+# ------------------------------
+# Check if rpmbuild "compat" generates main and debuginfo buildid symlinks
+AT_SETUP([rpmbuild buildid compat])
+AT_KEYWORDS([build] [debuginfo] [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 \
+  --macros=${abs_top_builddir}/macros:${abs_top_builddir}/tests/testing/usr/local/lib/rpm/platform/%{_target_cpu}-%{_target_os}/macros:${top_srcdir}/macros.debug \
+  --rcfile=${abs_top_builddir}/rpmrc \
+  --define="_build_id_links compat" \
+  --quiet -ba "${abs_srcdir}"/data/SPECS/hello.spec
+
+# There should be one build-id files in the main and debuginfo package.
+# the debuginfo package has one extra main build-id compat symlink
+# plus the .build-id/xx subdir, 2 in total in main, 3 in total in debug
+echo -n "hello build-id files: "
+run rpm -ql -p "${TOPDIR}"/RPMS/*/hello-1.0-1.*.rpm \
+  | grep /.build-id/ | wc --lines
+
+echo -n "hello debuginfo build-id files: "
+run rpm -ql -p "${TOPDIR}"/RPMS/*/hello-debuginfo-1.0-1.*.rpm \
+  | grep /.build-id/ | wc --lines
+
+# Extract the both packages to check the build-id files link to the
+# main and .debug files.
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello-1.0-1.*.rpm \
+  | cpio -diu
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello-debuginfo-1.0-1.*.rpm \
+  | cpio -diu
+
+# Check there is a build-id symlink for the main file.
+main_file=./usr/local/bin/hello
+test -f "${main_file}" || echo "No main file ${main_file}"
+
+# Extract the build-id from the main file
+id_main=$(file $main_file | sed 's/.*, BuildID[.*]=\(.*\),.*/\1/')
+
+id_main_file="./usr/lib/.build-id/${id_main:0:2}/${id_main:2}"
+test -L "$id_main_file" || echo "No build-id file $id_main_file"
+
+canon_main_file=$(readlink -f ${main_file})
+
+test -f "$canon_main_file" \
+  || echo "Cannot resolve main file ${main_file} -> ${canon_main_file}"
+
+canon_main_id_file=$(readlink -f ${id_main_file})
+
+test -f "$canon_main_id_file" \
+  || echo "Cannot resolve main build-id file ${id_main_file} -> ${canon_main_id_file}"
+
+test "$canon_main_file" = "$canon_main_id_file" \
+  || echo "main and build-id file not linked"
+
+# And check the same for the debug file.
+debug_file=./usr/lib/debug/usr/local/bin/hello.debug
+test -f "${debug_file}" || echo "No debug file ${debug_file}"
+
+# Extract the build-id from the .debug file
+id_debug=$(file $debug_file | sed 's/.*, BuildID[.*]=\(.*\),.*/\1/')
+
+test ${id_main} = ${id_debug} || echo "unequal main and debug id"
+
+id_debug_file="./usr/lib/debug/.build-id/${id_debug:0:2}/${id_debug:2}.debug"
+test -L "$id_debug_file" || echo "No build-id file $id_debug_file"
+
+canon_debug_file=$(readlink -f ${debug_file})
+
+test -f "$canon_debug_file" \
+  || echo "Cannot resolve debug file ${debug_file} -> ${canon_debug_file}"
+
+canon_debug_id_file=$(readlink -f ${id_debug_file})
+
+test -f "$canon_debug_id_file" \
+  || echo "Cannot resolve debug build-id file ${id_debug_file} -> ${canon_debug_id_file}"
+
+test "$canon_debug_file" = "$canon_debug_id_file" \
+  || echo "debug and build-id not linked"
+
+# The compat link should also point to the same (indirectly).
+id_compat_file="./usr/lib/debug/.build-id/${id_main:0:2}/${id_main:2}"
+test -L "$id_compat_file" || echo "No build-id compat file $id_compat_file"
+
+canon_compat_file=$(readlink -f ${id_compat_file})
+
+test -f "$canon_compat_file" \
+  || echo "Cannot resolve compat file ${id_compat_file} -> ${canon_compat_file}"
+
+test "$canon_compat_file" = "$canon_main_file" \
+  || echo "compat and build-id not linked"
+],
+[0],
+[hello build-id files: 2
+hello debuginfo build-id files: 3
+],
+[ignore])
+AT_CLEANUP
+
+# ------------------------------
+# Check that (copied) files with duplicate build-ids are handled correctly.
+# This should create "numbered" build-id files.
+# This is simply the hello example with one binary copied.
+AT_SETUP([rpmbuild buildid duplicate alldebug])
+AT_KEYWORDS([build] [debuginfo] [buildid])
+AT_CHECK([
+rm -rf ${TOPDIR}
+AS_MKDIR_P(${TOPDIR}/SOURCES)
+
+cp "${abs_srcdir}"/data/SOURCES/hello-1.0.tar.gz "${abs_srcdir}"/data/SOURCES/hello-1.0-modernize.patch ${TOPDIR}/SOURCES
+
+# Should create two warnings
+run rpmbuild --quiet \
+  --macros=${abs_top_builddir}/macros:${abs_top_builddir}/tests/testing/usr/local/lib/rpm/platform/%{_target_cpu}-%{_target_os}/macros:${top_srcdir}/macros.debug \
+  --rcfile=${abs_top_builddir}/rpmrc \
+  --define="_build_id_links alldebug" \
+  -ba "${abs_srcdir}"/data/SPECS/hello2cp.spec 2>&1 | grep "^warning: " \
+  | cut -f1-3 -d' '
+
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello2-1.0-1.*.rpm \
+  | cpio -diu --quiet
+
+hello_file=./usr/local/bin/hello
+
+# Extract the build-id from the main file
+id=$(file $hello_file | sed 's/.*, BuildID[.*]=\(.*\),.*/\1/')
+
+# alldebug not here...
+id_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}"
+test -L "$id_file" && echo "main id in main package"
+id_dup_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}.1"
+test -L "$id_dup_file" && echo "main dup id in main package"
+
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello2-debuginfo-1.0-1.*.rpm \
+  | cpio -diu --quiet
+
+# alldebug, so they are all here
+test -L "$id_file" && echo "main id in debug package"
+test -L "$id_dup_file" && echo "main dup id in debug package"
+
+debug_id_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}.debug"
+test -L "$debug_id_file" && echo "debug id in debug package"
+debug_dup_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}.1.debug"
+test -L "$debug_dup_file" && echo "debug dup id in debug package"
+
+# We don't know which points to which, but we do know they point
+# to different files.
+canon_id_file=$(readlink -f ${id_file})
+canon_dup_file=$(readlink -f ${id_dup_file})
+test "$canon_id_file" != "$canon_dup_file" \
+  || echo "id and dup same"
+
+canon_debug_id_file=$(readlink -f ${debug_id_file})
+canon_debug_dup_file=$(readlink -f ${debug_dup_file})
+test "$canon_debug_id_file" != "$canon_debug_dup_file" \
+  || echo "debug id and dup same"
+],
+[0],
+[warning: Duplicate build-ids
+warning: Duplicate build-ids
+main id in debug package
+main dup id in debug package
+debug id in debug package
+debug dup id in debug package
+],
+[])
+AT_CLEANUP
+
+# ------------------------------
+# Check that hard linked files are handled correctly.
+# Since the hard linked files have duplicate build-ids,
+# it should create "numbered" build-id files.
+# This is simply the hello example with one binary hard linked.
+AT_SETUP([rpmbuild buildid hardlink alldebug])
+AT_KEYWORDS([build] [debuginfo] [buildid])
+AT_CHECK([
+rm -rf ${TOPDIR}
+AS_MKDIR_P(${TOPDIR}/SOURCES)
+
+cp "${abs_srcdir}"/data/SOURCES/hello-1.0.tar.gz "${abs_srcdir}"/data/SOURCES/hello-1.0-modernize.patch ${TOPDIR}/SOURCES
+
+# No warnings for hard links
+run rpmbuild --quiet \
+  --macros=${abs_top_builddir}/macros:${abs_top_builddir}/tests/testing/usr/local/lib/rpm/platform/%{_target_cpu}-%{_target_os}/macros:${top_srcdir}/macros.debug \
+  --rcfile=${abs_top_builddir}/rpmrc \
+  --define="_build_id_links alldebug" \
+  -ba "${abs_srcdir}"/data/SPECS/hello2ln.spec 2>&1 | grep "^warning: " \
+  | cut -f1-3 -d' '
+
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello2-1.0-1.*.rpm \
+  | cpio -diu --quiet
+
+hello_file=./usr/local/bin/hello
+
+# Extract the build-id from the main file
+id=$(file $hello_file | sed 's/.*, BuildID[.*]=\(.*\),.*/\1/')
+
+# alldebug not here...
+id_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}"
+test -L "$id_file" && echo "main id in main package"
+id_dup_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}.1"
+test -L "$id_dup_file" && echo "main dup id in main package"
+
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello2-debuginfo-1.0-1.*.rpm \
+  | cpio -diu --quiet
+
+# alldebug, so they are all here
+test -L "$id_file" && echo "main id in debug package"
+test -L "$id_dup_file" && echo "main dup id in debug package"
+
+debug_id_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}.debug"
+test -L "$debug_id_file" && echo "debug id in debug package"
+debug_dup_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}.1.debug"
+test -L "$debug_dup_file" && echo "debug dup id in debug package"
+
+# We don't know which points to which, but we do know they point
+# to different files.
+canon_id_file=$(readlink -f ${id_file})
+canon_dup_file=$(readlink -f ${id_dup_file})
+test "$canon_id_file" != "$canon_dup_file" \
+  || echo "id and dup same"
+
+canon_debug_id_file=$(readlink -f ${debug_id_file})
+canon_debug_dup_file=$(readlink -f ${debug_dup_file})
+test "$canon_debug_id_file" != "$canon_debug_dup_file" \
+  || echo "debug id and dup same"
+],
+[0],
+[main id in debug package
+main dup id in debug package
+debug id in debug package
+debug dup id in debug package
+],
+[])
+AT_CLEANUP
+
+# ------------------------------
+# Check that (copied) files with duplicate build-ids are handled correctly.
+# This should create "numbered" build-id files.
+# This is simply the hello example with one binary copied.
+AT_SETUP([rpmbuild buildid duplicate separate])
+AT_KEYWORDS([build] [debuginfo] [buildid])
+AT_CHECK([
+rm -rf ${TOPDIR}
+AS_MKDIR_P(${TOPDIR}/SOURCES)
+
+cp "${abs_srcdir}"/data/SOURCES/hello-1.0.tar.gz "${abs_srcdir}"/data/SOURCES/hello-1.0-modernize.patch ${TOPDIR}/SOURCES
+
+# Should create two warnings
+run rpmbuild --quiet \
+  --macros=${abs_top_builddir}/macros:${abs_top_builddir}/tests/testing/usr/local/lib/rpm/platform/%{_target_cpu}-%{_target_os}/macros:${top_srcdir}/macros.debug \
+  --rcfile=${abs_top_builddir}/rpmrc \
+  --define="_build_id_links separate" \
+  -ba "${abs_srcdir}"/data/SPECS/hello2cp.spec 2>&1 | grep "^warning: " \
+  | cut -f1-3 -d' '
+
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello2-1.0-1.*.rpm \
+  | cpio -diu --quiet
+
+hello_file=./usr/local/bin/hello
+
+# Extract the build-id from the main file
+id=$(file $hello_file | sed 's/.*, BuildID[.*]=\(.*\),.*/\1/')
+
+# separate build-ids split...
+id_file="./usr/lib/.build-id/${id:0:2}/${id:2}"
+test -L "$id_file" && echo "main id in main package"
+id_dup_file="./usr/lib/.build-id/${id:0:2}/${id:2}.1"
+test -L "$id_dup_file" && echo "main dup id in main package"
+
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello2-debuginfo-1.0-1.*.rpm \
+  | cpio -diu --quiet
+
+# seperate, so debug ids are here
+debug_id_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}.debug"
+test -L "$debug_id_file" && echo "debug id in debug package"
+debug_dup_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}.1.debug"
+test -L "$debug_dup_file" && echo "debug dup id in debug package"
+
+# We don't know which points to which, but we do know they point
+# to different files.
+canon_id_file=$(readlink -f ${id_file})
+canon_dup_file=$(readlink -f ${id_dup_file})
+test "$canon_id_file" != "$canon_dup_file" \
+  || echo "id and dup same"
+
+canon_debug_id_file=$(readlink -f ${debug_id_file})
+canon_debug_dup_file=$(readlink -f ${debug_dup_file})
+test "$canon_debug_id_file" != "$canon_debug_dup_file" \
+  || echo "debug id and dup same"
+],
+[0],
+[warning: Duplicate build-ids
+warning: Duplicate build-ids
+main id in main package
+main dup id in main package
+debug id in debug package
+debug dup id in debug package
+],
+[])
+AT_CLEANUP
+
+# ------------------------------
+# Check that hard linked files are handled correctly.
+# Since the hard linked files have duplicate build-ids,
+# it should create "numbered" build-id files.
+# This is simply the hello example with one binary hard linked.
+AT_SETUP([rpmbuild buildid hardlink separate])
+AT_KEYWORDS([build] [debuginfo] [buildid])
+AT_CHECK([
+rm -rf ${TOPDIR}
+AS_MKDIR_P(${TOPDIR}/SOURCES)
+
+cp "${abs_srcdir}"/data/SOURCES/hello-1.0.tar.gz "${abs_srcdir}"/data/SOURCES/hello-1.0-modernize.patch ${TOPDIR}/SOURCES
+
+# No warnings for hard links
+run rpmbuild --quiet \
+  --macros=${abs_top_builddir}/macros:${abs_top_builddir}/tests/testing/usr/local/lib/rpm/platform/%{_target_cpu}-%{_target_os}/macros:${top_srcdir}/macros.debug \
+  --rcfile=${abs_top_builddir}/rpmrc \
+  --define="_build_id_links separate" \
+  -ba "${abs_srcdir}"/data/SPECS/hello2ln.spec 2>&1 | grep "^warning: " \
+  | cut -f1-3 -d' '
+
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello2-1.0-1.*.rpm \
+  | cpio -diu --quiet
+
+hello_file=./usr/local/bin/hello
+
+# Extract the build-id from the main file
+id=$(file $hello_file | sed 's/.*, BuildID[.*]=\(.*\),.*/\1/')
+
+# separate build-ids split...
+id_file="./usr/lib/.build-id/${id:0:2}/${id:2}"
+test -L "$id_file" && echo "main id in main package"
+id_dup_file="./usr/lib/.build-id/${id:0:2}/${id:2}.1"
+test -L "$id_dup_file" && echo "main dup id in main package"
+
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello2-debuginfo-1.0-1.*.rpm \
+  | cpio -diu --quiet
+
+# separate, so debug ids are here
+debug_id_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}.debug"
+test -L "$debug_id_file" && echo "debug id in debug package"
+debug_dup_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}.1.debug"
+test -L "$debug_dup_file" && echo "debug dup id in debug package"
+
+# We don't know which points to which, but we do know they point
+# to different files.
+canon_id_file=$(readlink -f ${id_file})
+canon_dup_file=$(readlink -f ${id_dup_file})
+test "$canon_id_file" != "$canon_dup_file" \
+  || echo "id and dup same"
+
+canon_debug_id_file=$(readlink -f ${debug_id_file})
+canon_debug_dup_file=$(readlink -f ${debug_dup_file})
+test "$canon_debug_id_file" != "$canon_debug_dup_file" \
+  || echo "debug id and dup same"
+],
+[0],
+[main id in main package
+main dup id in main package
+debug id in debug package
+debug dup id in debug package
+],
+[])
+AT_CLEANUP
+
+# ------------------------------
+# Check that (copied) files with duplicate build-ids are handled correctly.
+# This should create "numbered" build-id files.
+# This is simply the hello example with one binary copied.
+AT_SETUP([rpmbuild buildid duplicate compat])
+AT_KEYWORDS([build] [debuginfo] [buildid])
+AT_CHECK([
+rm -rf ${TOPDIR}
+AS_MKDIR_P(${TOPDIR}/SOURCES)
+
+cp "${abs_srcdir}"/data/SOURCES/hello-1.0.tar.gz "${abs_srcdir}"/data/SOURCES/hello-1.0-modernize.patch ${TOPDIR}/SOURCES
+
+# Should create two warnings
+run rpmbuild --quiet \
+  --macros=${abs_top_builddir}/macros:${abs_top_builddir}/tests/testing/usr/local/lib/rpm/platform/%{_target_cpu}-%{_target_os}/macros:${top_srcdir}/macros.debug \
+  --rcfile=${abs_top_builddir}/rpmrc \
+  --define="_build_id_links compat" \
+  -ba "${abs_srcdir}"/data/SPECS/hello2cp.spec 2>&1 | grep "^warning: " \
+  | cut -f1-3 -d' '
+
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello2-1.0-1.*.rpm \
+  | cpio -diu --quiet
+
+hello_file=./usr/local/bin/hello
+
+# Extract the build-id from the main file
+id=$(file $hello_file | sed 's/.*, BuildID[.*]=\(.*\),.*/\1/')
+
+# compat build-ids split...
+id_file="./usr/lib/.build-id/${id:0:2}/${id:2}"
+test -L "$id_file" && echo "main id in main package"
+id_dup_file="./usr/lib/.build-id/${id:0:2}/${id:2}.1"
+test -L "$id_dup_file" && echo "main dup id in main package"
+
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello2-debuginfo-1.0-1.*.rpm \
+  | cpio -diu --quiet
+
+# compat, so main (and debug) ids are (also) here
+compat_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}"
+test -L "$compat_file" && echo "compat id in debug package"
+compat_dup_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}.1"
+test -L "$compat_dup_file" && echo "compat dup id in debug package"
+
+debug_id_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}.debug"
+test -L "$debug_id_file" && echo "debug id in debug package"
+debug_dup_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}.1.debug"
+test -L "$debug_dup_file" && echo "debug dup id in debug package"
+
+# We don't know which points to which, but we do know they point
+# to different files.
+canon_id_file=$(readlink -f ${id_file})
+canon_dup_file=$(readlink -f ${id_dup_file})
+test "$canon_id_file" != "$canon_dup_file" \
+  || echo "id and dup same"
+
+canon_debug_id_file=$(readlink -f ${debug_id_file})
+canon_debug_dup_file=$(readlink -f ${debug_dup_file})
+test "$canon_debug_id_file" != "$canon_debug_dup_file" \
+  || echo "debug id and dup same"
+
+canon_compat_file=$(readlink -f ${compat_file})
+canon_compat_dup_file=$(readlink -f ${compat_dup_file})
+test "$canon_compat_file" != "$canon_compat_dup_file" \
+  || echo "compat id and dup same"
+],
+[0],
+[warning: Duplicate build-ids
+warning: Duplicate build-ids
+main id in main package
+main dup id in main package
+compat id in debug package
+compat dup id in debug package
+debug id in debug package
+debug dup id in debug package
+],
+[])
+AT_CLEANUP
+
+# ------------------------------
+# Check that hard linked files are handled correctly.
+# Since the hard linked files have duplicate build-ids,
+# it should create "numbered" build-id files.
+# This is simply the hello example with one binary hard linked.
+AT_SETUP([rpmbuild buildid hardlink compat])
+AT_KEYWORDS([build] [debuginfo] [buildid])
+AT_CHECK([
+rm -rf ${TOPDIR}
+AS_MKDIR_P(${TOPDIR}/SOURCES)
+
+cp "${abs_srcdir}"/data/SOURCES/hello-1.0.tar.gz "${abs_srcdir}"/data/SOURCES/hello-1.0-modernize.patch ${TOPDIR}/SOURCES
+
+# No warnings for hard links
+run rpmbuild --quiet \
+  --macros=${abs_top_builddir}/macros:${abs_top_builddir}/tests/testing/usr/local/lib/rpm/platform/%{_target_cpu}-%{_target_os}/macros:${top_srcdir}/macros.debug \
+  --rcfile=${abs_top_builddir}/rpmrc \
+  --define="_build_id_links compat" \
+  -ba "${abs_srcdir}"/data/SPECS/hello2ln.spec 2>&1 | grep "^warning: " \
+  | cut -f1-3 -d' '
+
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello2-1.0-1.*.rpm \
+  | cpio -diu --quiet
+
+hello_file=./usr/local/bin/hello
+
+# Extract the build-id from the main file
+id=$(file $hello_file | sed 's/.*, BuildID[.*]=\(.*\),.*/\1/')
+
+# compat build-ids split...
+id_file="./usr/lib/.build-id/${id:0:2}/${id:2}"
+test -L "$id_file" && echo "main id in main package"
+id_dup_file="./usr/lib/.build-id/${id:0:2}/${id:2}.1"
+test -L "$id_dup_file" && echo "main dup id in main package"
+
+rpm2cpio ${abs_builddir}/testing/build/RPMS/*/hello2-debuginfo-1.0-1.*.rpm \
+  | cpio -diu --quiet
+
+# compat, so main (and debug) ids are (also) here
+compat_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}"
+test -L "$compat_file" && echo "compat id in debug package"
+compat_dup_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}.1"
+test -L "$compat_dup_file" && echo "compat dup id in debug package"
+
+debug_id_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}.debug"
+test -L "$debug_id_file" && echo "debug id in debug package"
+debug_dup_file="./usr/lib/debug/.build-id/${id:0:2}/${id:2}.1.debug"
+test -L "$debug_dup_file" && echo "debug dup id in debug package"
+
+# We don't know which points to which, but we do know they point
+# to different files.
+canon_id_file=$(readlink -f ${id_file})
+canon_dup_file=$(readlink -f ${id_dup_file})
+test "$canon_id_file" != "$canon_dup_file" \
+  || echo "id and dup same"
+
+canon_debug_id_file=$(readlink -f ${debug_id_file})
+canon_debug_dup_file=$(readlink -f ${debug_dup_file})
+test "$canon_debug_id_file" != "$canon_debug_dup_file" \
+  || echo "debug id and dup same"
+
+canon_compat_file=$(readlink -f ${compat_file})
+canon_compat_dup_file=$(readlink -f ${compat_dup_file})
+test "$canon_compat_file" != "$canon_compat_dup_file" \
+  || echo "compat id and dup same"
+],
+[0],
+[main id in main package
+main dup id in main package
+compat id in debug package
+compat dup id in debug package
+debug id in debug package
+debug dup id in debug package
+],
+[])
+AT_CLEANUP
\ No newline at end of file
diff --git a/tests/rpmtests.at b/tests/rpmtests.at
index b51266a2d..5495cced1 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])