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 != "" {