From 31c14ba6610568c2d634647fed1fb57221178da9 Mon Sep 17 00:00:00 2001
From: Michal Domonkos <mdomonko@redhat.com>
Date: Fri, 26 Jul 2024 10:43:50 +0200
Subject: [PATCH] Make sure dirs are not relocated twice

Conflict:adapt context
Reference:https://github.com/rpm-software-management/rpm/commit/31c14ba6610568c2d634647fed1fb57221178da9

When processing relocations, new dirnames are added to dirNames[] first
and then the rest is relocated.  However, we go through the entire array
in the latter step, meaning that we may accidentally relocate an already
relocated path.

Most relocations are fine as they involve two separate directory trees,
and we already skip over directories that don't match the old prefix.
However, that breaks apart if we're relocating to a nested subdirectory
(e.g. /opt -> /opt/new/dir).

Fix this by simply stopping at the original dirCount as the new entries
are always added to the end of dirNames[].

Such relocations are perhaps not very common (or even unsupported) but
relocating the root directory itself may be useful (see the next commit
for details) and that is subject to the same issue.

Note that we currently don't handle root relocations properly to start
with but that will be addressed in the next commit, this prepares the
ground.
---
 lib/relocation.c |  6 +++---
 tests/rpmi.at    | 35 +++++++++++++++++++++++++++++++++++
 2 files changed, 38 insertions(+), 3 deletions(-)

diff --git a/lib/relocation.c b/lib/relocation.c
index 1eab60211..d31cf4779 100644
--- a/lib/relocation.c
+++ b/lib/relocation.c
@@ -124,7 +124,7 @@ void rpmRelocateFileList(rpmRelocation *relocations, int numRelocations,
     char ** baseNames;
     char ** dirNames;
     uint32_t * dirIndexes;
-    rpm_count_t fileCount, dirCount;
+    rpm_count_t fileCount, dirCount, dirCountOrig;
     int nrelocated = 0;
     int fileAlloced = 0;
     char * fn = NULL;
@@ -163,7 +163,7 @@ void rpmRelocateFileList(rpmRelocation *relocations, int numRelocations,
     baseNames = bnames.data;
     dirIndexes = dindexes.data;
     fileCount = rpmtdCount(&bnames);
-    dirCount = rpmtdCount(&dnames);
+    dirCount = dirCountOrig = rpmtdCount(&dnames);
     /* XXX TODO: use rpmtdDup() instead */
     dirNames = dnames.data = duparray(dnames.data, dirCount);
     dnames.flags |= RPMTD_PTR_ALLOCED;
@@ -297,7 +297,7 @@ assert(fn != NULL);		/* XXX can't happen */
     }
 
     /* Finish off by relocating directories. */
-    for (i = dirCount - 1; i >= 0; i--) {
+    for (i = dirCountOrig - 1; i >= 0; i--) {
 	for (j = numRelocations - 1; j >= 0; j--) {
 
 	    if (relocations[j].oldPath == NULL) /* XXX can't happen */
diff --git a/tests/rpmi.at b/tests/rpmi.at
index 7d1a0a871..372be0a8b 100644
--- a/tests/rpmi.at
+++ b/tests/rpmi.at
@@ -1135,6 +1135,42 @@ runroot rpm -U --relocate /opt/bin=/bin \
 ],
 [])
 RPMTEST_CLEANUP
+
+AT_SETUP([rpm -i relocatable package 2])
+AT_KEYWORDS([install relocate])
+RPMDB_INIT
+
+runroot rpmbuild --quiet -bb /data/SPECS/reloc.spec
+runroot rpmbuild --quiet -bb /data/SPECS/fakeshell.spec
+
+runroot rpm -U /build/RPMS/noarch/fakeshell-1.0-1.noarch.rpm
+
+RPMTEST_CHECK([
+runroot rpm -U \
+  --relocate /opt/bin=/opt/bin/foo/bar \
+  --relocate /opt/etc=/opt/etc/foo/bar \
+  --relocate /opt/lib=/opt/lib/foo/bar \
+  /build/RPMS/noarch/reloc-1.0-1.noarch.rpm
+runroot rpm -ql reloc
+],
+[0],
+[1: /opt/bin/foo/bar
+2: /opt/etc/foo/bar
+3: /opt/lib/foo/bar
+0: /opt/bin/foo/bar
+1: /opt/etc/foo/bar
+2: /opt/lib/foo/bar
+/opt
+/opt/bin/foo/bar
+/opt/bin/foo/bar/typo
+/opt/etc/foo/bar
+/opt/etc/foo/bar/conf
+/opt/lib/foo/bar
+/opt/lib/foo/bar/notlib
+],
+[])
+RPMTEST_CLEANUP
+
 AT_SETUP([rpm -i with/without --excludedocs])
 AT_KEYWORDS([install excludedocs])
 RPMTEST_CHECK([
-- 
2.33.0

