From e1225cd3959808b694481d0d4970d29dc3b22d40 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Fri, 18 Sep 2026 02:12:10 -0400 Subject: [PATCH] =?UTF-8?q?[FIX]=20cache=20qemu=20:=20une=20borne=20de=20c?= =?UTF-8?q?l=C3=A9=20par=20famille,=20courte=20pour=20pacman?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Un miroir d'Arch sert « archlinux/core/os/x86_64/ » là où un autre sert « core/os/x86_64/ » : quatre et cinq segments, plus courts que la borne commune de six, qui les garde entiers et en fait deux clés pour un même octet. La famille pacman passe donc à quatre, ce qui retire le segment du miroir et garde le nom du dépôt. La borne haute reste partout ailleurs : deux distributions peuvent publier un « .deb » de même nom pour d'autres octets, et les confondre servirait le paquet de l'une à l'autre. Vérifié : quatre mutations attrapées, aucune collision sur les chemins relevés. --- EN --- An Arch mirror serves « archlinux/core/os/x86_64/ » where another serves « core/os/x86_64/ »: four and five segments, shorter than the common bound of six, which keeps them whole and makes two keys for one byte. The pacman family therefore moves to four, which drops the mirror's segment and keeps the repository name. The high bound stays everywhere else: two distributions may publish a « .deb » of the same name for other bytes, and merging them would serve one's package as the other's. Checked: four mutations caught, no collision over the paths surveyed. Assisted-by: Claude Opus 5 --- script/qemu_cache/classify_test.go | 55 +++++++++++++++++++++++++++--- script/qemu_cache/main.go | 2 +- script/qemu_cache/store.go | 36 ++++++++++++++++--- 3 files changed, 83 insertions(+), 10 deletions(-) diff --git a/script/qemu_cache/classify_test.go b/script/qemu_cache/classify_test.go index c5379f0..e9b6515 100644 --- a/script/qemu_cache/classify_test.go +++ b/script/qemu_cache/classify_test.go @@ -239,10 +239,8 @@ func TestLesMetadonneesRPMParEmpreinteSontImmuables(t *testing.T) { } } -// Un miroir préfixe le chemin à sa guise, et le chemin entier donnait deux -// clés pour un même octet. Relevé sur un journal réel : 1124 noms de paquets -// vivaient sous plusieurs chemins, et 3,18 Gio repartaient à l'amont pour des -// fichiers que le magasin détenait déjà. +// Un miroir préfixe le chemin à sa guise, et le chemin entier donne alors +// deux clés pour un même octet : le cache retélécharge ce qu'il détient. func TestLesPrefixesDeMiroirNeFontQuUneCle(t *testing.T) { for _, cas := range [][2]string{ { @@ -279,6 +277,55 @@ func TestDeuxDistributionsNePartagentPasLaCle(t *testing.T) { } } +// Un miroir d'Arch préfixe « archlinux/ », l'autre non : quatre et cinq +// segments, trop courts pour la borne commune, qui les garde entiers et en +// fait deux clés. +func TestLesDeuxFormesDUnMiroirArchNeFontQuUneCle(t *testing.T) { + for _, cas := range [][2]string{ + { + "https://geo.example/archlinux/core/os/x86_64/linux-7.2.4.arch1-2-x86_64.pkg.tar.zst", + "https://fastly.example/core/os/x86_64/linux-7.2.4.arch1-2-x86_64.pkg.tar.zst", + }, + { + "https://geo.example/archlinux/extra/os/x86_64/llvm-libs-22.1.8-2-x86_64.pkg.tar.zst", + "https://fastly.example/extra/os/x86_64/llvm-libs-22.1.8-2-x86_64.pkg.tar.zst", + }, + } { + a, _ := url.Parse(cas[0]) + b, _ := url.Parse(cas[1]) + if KeySansHote("GET", a) != KeySansHote("GET", b) { + t.Errorf("deux formes du même paquet donnent deux clés :\n %s\n %s", + cas[0], cas[1]) + } + } +} + +// La borne d'Arch s'arrête à quatre pour GARDER le nom du dépôt : trois ou +// deux passeraient sur les données relevées, mais confondraient un paquet de +// « core » avec celui de « extra » si les deux portaient un jour le même nom. +func TestLaCleDArchGardeLeNomDuDepot(t *testing.T) { + a, _ := url.Parse("https://m.example/core/os/x86_64/outil-1.0-1-x86_64.pkg.tar.zst") + b, _ := url.Parse("https://m.example/extra/os/x86_64/outil-1.0-1-x86_64.pkg.tar.zst") + if KeySansHote("GET", a) == KeySansHote("GET", b) { + t.Error("deux dépôts d'Arch partagent une clé : le nom du dépôt est perdu") + } +} + +// La borne courte ne vaut QUE pour la famille pacman : un « .deb » garde la +// borne haute, faute de quoi le paquet de Debian et celui d'Ubuntu, qui +// portent le même nom pour d'autres octets, se confondraient. +func TestLaBorneCourteNeVautQuePourPacman(t *testing.T) { + deb, _ := url.Parse("https://deb.example/debian/pool/main/p/poppler-data/poppler-data_0.4.12-1_all.deb") + ubu, _ := url.Parse("https://ubu.example/ubuntu/pool/main/p/poppler-data/poppler-data_0.4.12-1_all.deb") + if KeySansHote("GET", deb) == KeySansHote("GET", ubu) { + t.Error("la borne courte a débordé sur la famille deb") + } + if !estPaquetArch("linux-7.2.4.arch1-2-x86_64.pkg.tar.zst") || + estPaquetArch("poppler-data_0.4.12-1_all.deb") { + t.Error("la reconnaissance de la famille pacman est fausse") + } +} + // Le même index par empreinte, servi par deux miroirs. Sans clé portable, // changer de miroir vide le cache de ses index : une installation hors ligne // échoue alors sur des octets que le magasin détient pourtant, et le message diff --git a/script/qemu_cache/main.go b/script/qemu_cache/main.go index e23b32f..296ece5 100644 --- a/script/qemu_cache/main.go +++ b/script/qemu_cache/main.go @@ -25,7 +25,7 @@ import ( "time" ) -const version = "0.2.15" +const version = "0.2.16" func main() { var ( diff --git a/script/qemu_cache/store.go b/script/qemu_cache/store.go index ef7ec4c..ffa16db 100644 --- a/script/qemu_cache/store.go +++ b/script/qemu_cache/store.go @@ -106,11 +106,33 @@ func Key(method, rawURL string) string { // Six, et pas moins : un chemin Debian en porte exactement six — // « debian/pool/main/p//.deb » — si bien que cinq // effaceraient le segment de distribution et donneraient la même clé au -// paquet d'Ubuntu, qui porte le même nom pour d'autres octets. Relevé sur un -// journal d'accès de 7099 noms livrés : six réunit 1021 des 1124 noms vus -// sous plusieurs chemins, sans confondre aucun contenu ; cinq en confond 38. +// paquet d'Ubuntu, qui porte le même nom pour d'autres octets. const SegmentsDeCle = 6 +// SegmentsDeCleArch : les paquets d'Arch en demandent moins. +// +// Un miroir d'Arch sert « archlinux/core/os/x86_64/ » là où un autre +// sert « core/os/x86_64/ » : quatre et cinq segments, donc plus COURTS +// que la borne commune, qui les garde alors entiers et en fait deux clés. +// +// Quatre, et non moins : la clé garde ainsi le nom du dépôt — « core », +// « extra » — et ne perd que le segment décoratif du miroir. Deux ou trois +// réuniraient les mêmes copies, mais effaceraient cette distinction sans +// nécessité. +// +// Le cas est sûr là où celui de Debian ne l'est pas : Ubuntu reprend les +// paquets de Debian en gardant leur version, si bien qu'un même nom « .deb » +// porte deux contenus selon la distribution. Un espace de noms partagé exige +// la borne haute ; celui d'Arch n'appartient qu'à lui. +const SegmentsDeCleArch = 4 + +// estPaquetArch reconnaît un paquet de la famille pacman à son nom. +func estPaquetArch(nom string) bool { + nom = strings.ToLower(nom) + return strings.HasSuffix(nom, ".pkg.tar.zst") || + strings.HasSuffix(nom, ".pkg.tar.xz") +} + func KeySansHote(method string, u *url.URL) string { segments := make([]string, 0, SegmentsDeCle+2) for _, s := range strings.Split(u.Path, "/") { @@ -120,8 +142,12 @@ func KeySansHote(method string, u *url.URL) string { segments = append(segments, s) } } - if len(segments) > SegmentsDeCle { - segments = segments[len(segments)-SegmentsDeCle:] + borne := SegmentsDeCle + if len(segments) > 0 && estPaquetArch(segments[len(segments)-1]) { + borne = SegmentsDeCleArch + } + if len(segments) > borne { + segments = segments[len(segments)-borne:] } chemin := strings.Join(segments, "/") if u.RawQuery != "" {