Bug#1130691: python3-apt: _file_is_same() always returns False, causing packages to re-download every run
Eddy Pronk <[email protected]> Sat, 14 Mar 2026 07:33:40 +0000
| Newsgroups | gmane.linux.debian.apt.devel |
|---|---|
| Message-ID | <177347362089.5713.2200174753039841625.reportbug__49818.8360173785$1773473728$gmane$org@e3b208d7450c> |
Package: python3-apt Version: 3.0.0 Severity: important X-Debbugs-Cc: [email protected] Dear Maintainer, `apt.package._file_is_same()` always returns `False` even when the cached `.deb` file is present and correct. This causes every call to `Version.fetch_binary()` to re-download the package from the network, bypassing the destination-path cache entirely. **Root cause** Two bugs in `apt/package.py`: Bug 1 — file opened in text mode: ```python with open(path) as fobj: # should be open(path, "rb") ``` Bug 2 — order-sensitive hash comparison: ```python return apt_pkg.Hashes(fobj).hashes == hashes ``` `HashStringList.__eq__` compares element-by-element (list semantics). `apt_pkg.Hashes(file)` computes all hash types (MD5, SHA1, SHA256, SHA512), but the package records only store a subset. The lists differ in both order and length, so `==` always returns `False`. **Reproduction** ```python import apt, apt_pkg, tempfile from apt.package import _file_is_same cache = apt.Cache(memonly=True) version = cache["hello"].candidate with tempfile.TemporaryDirectory() as tmpdir: path = version.fetch_binary(tmpdir) print(_file_is_same(path, version.size, version._records.hashes)) # prints: False (expected: True) ``` **Fix** ```python def _file_is_same(path: str, size: int, hashes: apt_pkg.HashStringList) -> bool: """Return ``True`` if the file is the same.""" if os.path.exists(path) and os.path.getsize(path) == size: with open(path, "rb") as fobj: file_set = {str(h) for h in apt_pkg.Hashes(fobj).hashes} return {str(h) for h in hashes} <= file_set return False ``` A patch is attached. -- System Information: Debian Release: 13.3 APT prefers stable-updates APT policy: (500, 'stable-updates'), (500, 'stable-security'), (500, 'stable') Architecture: amd64 (x86_64) Kernel: Linux 6.8.0-101-generic (SMP w/8 CPU threads; PREEMPT) Kernel taint flags: TAINT_PROPRIETARY_MODULE, TAINT_OOT_MODULE Locale: LANG=C, LC_CTYPE=C.UTF-8 (charmap=UTF-8), LANGUAGE not set Shell: /bin/sh linked to /usr/bin/dash Init: unable to detect Versions of packages python3-apt depends on: ii distro-info-data 0.66+deb13u1 ii libapt-pkg7.0 3.0.3 ii libc6 2.41-12+deb13u1 ii libgcc-s1 14.2.0-19 ii libstdc++6 14.2.0-19 ii python-apt-common 3.0.0 ii python3 3.13.5-1 Versions of packages python3-apt recommends: ii iso-codes 4.18.0-1 ii lsb-release 12.1-1 Versions of packages python3-apt suggests: ii apt 3.0.3 pn python-apt-doc <none> -- no debconf information
0001-Fix-_file_is_same-to-use-binary-mode-and-subset-hash.patch
(application/mbox, 1.4 KB)
From abe8bdaec4f0a0cbcc028eb621c05054aa0e86e8 Mon Sep 17 00:00:00 2001 From: Eddy Pronk <[email protected]> Date: Sat, 14 Mar 2026 18:09:48 +1100 Subject: [PATCH] Fix _file_is_same() to use binary mode and subset hash comparison apt_pkg.Hashes(file) always computes all hash types (MD5, SHA1, SHA256, SHA512) while package records may only store a subset. Additionally, HashStringList.__eq__ compares by position (list semantics), so two lists with the same hashes in different order compare as not equal. Fix both issues: - Open the file in binary mode ("rb") instead of text mode - Check that the record hashes are a subset of the computed file hashes instead of requiring exact equality Co-Authored-By: Claude Sonnet 4.6 <[email protected]> --- apt/package.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apt/package.py b/apt/package.py index 50ed6d1..1e8db30 100644 --- a/apt/package.py +++ b/apt/package.py @@ -54,8 +54,9 @@ __all__ = ( def _file_is_same(path: str, size: int, hashes: apt_pkg.HashStringList) -> bool: """Return ``True`` if the file is the same.""" if os.path.exists(path) and os.path.getsize(path) == size: - with open(path) as fobj: - return apt_pkg.Hashes(fobj).hashes == hashes + with open(path, "rb") as fobj: + file_set = {str(h) for h in apt_pkg.Hashes(fobj).hashes} + return {str(h) for h in hashes} <= file_set return False -- 2.34.1