# rpm -U silently erases uncolored multilib packages from a different architecture

## Summary

When two packages share the same name but differ in architecture, and neither contains ELF binaries (HEADERCOLOR=0 — typical for `-devel` sub-packages), `rpm -U` of one architecture silently erases the other.  Colored packages (with ELF) coexist correctly; uncolored ones do not.

## Reproduction on stock x86_64 multilib

Build a trivial package for two architectures.  The package contains only a text file — no ELF, so HEADERCOLOR=0:

```
$ cat colortest.spec
Name:    colortest
Version: 1.0
Release: 1
Summary: Uncolored multilib test
License: GPL
%description
%{summary}
%install
mkdir -p %{buildroot}/opt/colortest/
echo "%{_arch}" > %{buildroot}/opt/colortest/%{_arch}
%files
/opt/colortest/

$ rpmbuild -bb --target i686 colortest.spec
$ rpmbuild -bb --target x86_64 colortest.spec
```

Install i686 first, then upgrade with x86_64.  On a real Fedora/RHEL x86_64 system, multilib is enabled by default — `_transaction_color` is set to 3 via the platform macros (`/usr/lib/rpm/platform/x86_64-linux/macros`, generated from `RPMCANONCOLOR` in `platform.in:23`):

```
$ rpm --eval '%_transaction_color'
3
```

So the reproducer is simply:

```
# rpm -U colortest-1.0-1.i686.rpm
# rpm -q colortest
colortest-1.0-1.i686

# rpm -U colortest-1.0-1.x86_64.rpm
# rpm -q colortest
colortest-1.0-1.x86_64          ← i686 silently erased
```

**Expected**: both architectures installed, as happens with ELF-carrying packages.

For comparison, the pre-built `hello` test packages (which contain ELF) coexist correctly when installed the same way:

```
# rpm -U hello-2.0-1.i686.rpm
# rpm -U hello-2.0-1.x86_64.rpm
# rpm -q hello
hello-2.0-1.i686
hello-2.0-1.x86_64              ← both present
```

### Reproducing in a test sandbox

In rpm's test suite or a minimal chroot, platform macros may not be present, so `_transaction_color` defaults to 0 (multilib disabled).  To reproduce the bug in such environments, pass it explicitly:

```
rpm -U --define "_transaction_color 3" colortest-1.0-1.i686.rpm
rpm -U --define "_transaction_color 3" colortest-1.0-1.x86_64.rpm
```

`_transaction_color` is a bitmask of `archcolor` values that tells rpm which ELF families can coexist: 1 = 32-bit (i386 family), 2 = 64-bit (x86_64), 3 (1|2) = both.

## Why this matters

On Fedora/RHEL, dnf typically installs both arches in a single transaction, avoiding this code path.  But `rpm -U` is a user-facing tool and should behave correctly on its own.  The bug affects:

- Direct `rpm -U` usage by users or scripts
- Third-party package managers that call `rpm --upgrade` sequentially
- Multilib systems with three or more arch families (e.g. PLD Linux with i686 + x86_64 + x32), where installing xz-devel.i686 erases the installed xz-devel.x32 — breaking reverse dependencies like libmagic-devel.x32

## Related issue

This is the same underlying bug as [rpm-software-management/rpm#2837](https://github.com/rpm-software-management/rpm/issues/2837) ("Strange behavior for multilib"), reported by @j-mracek (DNF maintainer) in January 2024.  That issue demonstrates the same symptom: `rpm -U B-2-2.i686.rpm` erases `B-2-2.x86_64` and `B-1-1.x86_64` when package B contains no files (HEADERCOLOR=0).

@pmatilai bisected that issue to a regression introduced by commit `21836bc7` ("Use an erase element to delete packages with same NEVRA"), which landed between rpm 4.14 and 4.16.  However, the regression and the root cause are two different things — see below.

## Root cause

The fundamental problem is in `skipColor()` (`lib/depends.cc:154`), which has been blind to uncolored cross-arch packages since `e10b348c6` (2003).

`addSelfErasures()` (`lib/depends.cc:160`) iterates over all installed packages with the same name and uses `skipColor()` to decide which to erase:

```c
// lib/depends.cc:169
if (skipColor(tscolor, hcolor, headerGetNumber(oh, RPMTAG_HEADERCOLOR)))
    continue;
```

`skipColor()` only skips when **both** packages have non-zero HEADERCOLOR and the colors don't overlap:

```c
// lib/depends.cc:156
return tscolor && color && ocolor && !(color & ocolor);
```

When either HEADERCOLOR is 0, `skipColor()` returns 0 (don't skip), and `addSelfErasures()` schedules the installed package for erasure with no further architecture check.

For colored packages this works: i686 ELF (color=1) and x86_64 ELF (color=2) don't overlap, so `skipColor` keeps them separate.  But `-devel` packages carry no ELF — both have HEADERCOLOR=0, so `skipColor` cannot distinguish them and falls through to erasure.

`checkAdded()` (`lib/depends.cc:285`) does have an architecture check (strict arch string comparison when `tscolor` is set), which is why same-transaction installs work.  `addSelfErasures()` lacks the equivalent for installed-vs-new comparison.

### Why it got worse in 4.16

Prior to `21836bc7`, `addSelfErasures()` had a same-NEVR guard for upgrades:

```c
cmp = rpmVersionCompare(h, oh);
/* On upgrade, skip packages that contain identical NEVR. */
if ((op == RPMTE_UPGRADE) && (cmp == 0))
    continue;
```

This partially masked the `skipColor()` blindness: same-version cross-arch uncolored packages survived because the `cmp == 0` check skipped them before reaching `removePackage()`.  After `21836bc7` removed this guard, same-version packages are also erased — which is the specific regression reported in #2837.

The `skipColor()` limitation existed since 2003, but prior to 4.16 it only caused problems when upgrading to a **different** version across architectures.  After `21836bc7`, it affects **all** versions including identical NEVR.

## Fix

The fix addresses the fundamental `skipColor()` blindness rather than restoring the removed same-NEVR guard.  This fixes both the pre-4.16 different-version case and the post-4.16 same-version regression (#2837), and does so without re-introducing the problems that `21836bc7` was trying to solve (broken `--replacepkgs` behavior with non-identical same-NEVR headers).

Two patches attached:

### Patch 1/2: rpmrc.in — add missing archcolor entries for i386-family sub-architectures

`rpmGetArchColor()` (`lib/rpmrc.cc:1842`) resolves an architecture name to its archcolor from the `archcolor:` table in rpmrc.  The table only had an entry for the base `i386` (color 1).  Sub-architectures i486, i586, i686, athlon, geode, pentium3, and pentium4 were missing.

The INSTARCH table has `hasTranslate=0`, so `lookupInDefaultTable()` (`lib/rpmrc.cc:351`) cannot map "i686" to "i386" the way `buildarchtranslate` does for the BUILDARCH table.  Without explicit entries, `rpmGetArchColor("i686")` returns -1.

Every other arch family already lists all sub-architectures: ARM has armv3l through armv8l, e2k has e2kv4/v5/v6/etc., x86_64 has x86_64_v2/v3/v4.  The i386 family was the only one incomplete.

This also fixes a latent bug in `rpmfcCheckPackageColor()` (`build/rpmfc.cc:1650`): the build-time check that warns when ELF binaries don't match the package architecture was silently skipped for all i386-family targets because `arch_color <= 0` triggered an early return at `build/rpmfc.cc:1666`.

Includes a test verifying `rpm --showrc` reports `archcolor: 1` for an i686 platform.

### Patch 2/2: lib/depends.cc — archcolor check in addSelfErasures()

When both the new and installed package have HEADERCOLOR=0, compare `rpmGetArchColor()` of their architecture strings.  Skip the erasure only when both archcolors are positive (non-zero, i.e. not noarch) and differ.

This preserves:
- **Same-arch upgrades**: i686 (ac=1) replaces i586 (ac=1) — same family
- **Cross-family multilib**: x86_64 (ac=2) coexists with i686 (ac=1) — different families
- **noarch → arch migrations**: noarch has archcolor 0, so the check doesn't apply

Strict arch string comparison (as `checkAdded()` at `lib/depends.cc:285` and `rpmtsTeIterator()` at `lib/depends.cc:132` use) would be too strict here — it would prevent valid same-family upgrades (e.g. i586 → i686).

Includes regression tests for two-way (i686 + x86_64, `_transaction_color 3`) and three-way multilib.  The three-way test injects custom arch definitions via `~/.config/rpm/rpmrc` and is self-contained.
