apt silently reinstalls the identical version on every invocation when
Packages metadata diverges from the state dpkg recorded, and gives no
diagnostic and no indication that this is not an upgrade.
Two independent situations each cause this on their own.
1. A Packages entry with Description continuation lines that are not
indented.
Debian Policy 5.1 requires that continuation lines "must start with
a space or a tab", and 5.6.13 defines Description as a multiline field
whose extended description lines start with a space. Such a Packages
file is invalid. apt's parser does not report a parse error; instead
pkgTagSection::Scan looks for the terminating colon with memchr(Stop,
':', End - Stop), which searches the whole remaining buffer rather than
the current line. The unindented line is therefore taken as a new field
whose name runs up to the next colon, which silently absorbs the Depends
(or Pre-Depends, Conflicts, Breaks, Replaces) line that follows.
debListParser::VersionHash() hashes Installed-Size, Depends,
Pre-Depends,
Conflicts, Breaks and Replaces.
With Depends absorbed, the hash of the Packages entry no longer
matches the hash computed from the same version in /var/lib/dpkg/status,
so pkgCacheGenerator::MergeListVersion does not merge them. Two distinct
pkgVersion records now exist for the identical version string.
2. A Packages entry that omits Installed-Size.
Debian Policy 5.3 lists Installed-Size among the fields of
DEBIAN/control without marking it "(mandatory)", and 5.6.20 only defines
its meaning, so a repository that omits it violates no rule.
VersionHash() then skips the field, and apt compares an absent value
against the one dpkg recorded, with the same non-merge result as above.
Effect in pkgDepCache::MarkInstall
if (P.CandidateVer == Pkg.CurrentVer()) // apt-pkg/depcache.cc:1666
compares pkgVersion records, not version strings. With two records
for the same version string the test fails, the package is not kept, and
it is marked for install.
Effect on the reported action
apt-pkg/pkgcache.cc:854, VerIterator::CompareVer, does not compare
version strings despite its name. It walks the package's version list
and returns 1 if the other record appears earlier in the list, -1
otherwise, and 0 only on record identity. Two records with the same
version string therefore always compare as either up or down, never
equal. That value becomes Status at apt-pkg/depcache.cc:2198, and
StateCache::Upgrade() is Status > 0, so
apt-private/private-output.cc:808 prints "The following packages will be
upgraded" and :953 counts it in "N upgraded".
Which of "upgraded" or "downgraded" is reported is therefore decided by
the arbitrary position of two duplicate records in the version list, and
is not meaningful. dpkg then prints "Unpacking foo (1.0) over (1.0)".
Impact
- The output is self-contradictory and gives the operator nothing to act
on. There is no "reinstall" wording and no indication of the diverging
field.
- The loop is unbounded. Every apt-get upgrade re-fetches and re-unpacks
identical bytes. In the reporter's case 4 packages / 14.8 MB were
re-fetched and 20.9 MB churned per run, on every run.
- There is no supported way to diagnose it. Under --simulate the output
for a reinstall is structurally identical to that for a genuine upgrade:
Inst foo [1.0] (1.0 origin) <- reinstall of the same version
Inst foo [1.0] (1.1 origin) <- real upgrade
The only cue is that the two version strings coincide. Enabling
Debug::pkgCacheGen can expose this, but it is not documented and is
unreachable to users without debug knowledge.
Proposed Solution
The fix is to make the existing, intentional cache-convergence behavior
visible and explained at default output level. This is a diagnosability
issue, not a change to what apt does.
A patch is attached implementing:
(a) ReInstallSameVersion() detection: Correctly identify packages where
version trings match but pointer-based comparison detects a difference —
this specific case indicates a metadata hash divergence.
(b) Reclassification: Exclude same-version reinstalls from the
Upgrade/Downgrade counts and display them in the "reinstalled" list instead.
(c) User-facing Notice: When same-version reinstalls are detected, emit
a plain-language message (at default output level, no flags needed)
explaining why it happens, that it will repeat until the repository is
fixed, and how to see per-field detail (Debug::pkgCacheGen=1).
The patch does not change what apt does — the reinstall behavior is
intentional and correct when metadata diverges (it allows the cache to
converge on local package rebuilds). It only makes the situation visible
and explained.
Related
Similar issue reported on the Pulp project (339 packages reinstalled per
run):
https://pulp.plan.io/issues/6982