Skip to content

Support xml:base in repodata <location> elements#568

Open
oliverkurth wants to merge 2 commits intodevfrom
topic/okurth/repodata-base
Open

Support xml:base in repodata <location> elements#568
oliverkurth wants to merge 2 commits intodevfrom
topic/okurth/repodata-base

Conversation

@oliverkurth
Copy link
Copy Markdown
Contributor

@oliverkurth oliverkurth commented Mar 24, 2026

Support xml:base in repodata <location> elements

Background

The RPM repodata format allows a <location> element in primary.xml to carry
an xml:base attribute that overrides the base URL used to locate the actual RPM
file:

<location xml:base="file:///srv/pool" href="attr-2.5.1-6.ph5.x86_64.rpm"/>

This is used in "merged repo" scenarios where multiple repositories share a common
RPM pool but each maintain their own repodata. Without xml:base support, the
only way to achieve this with tdnf was to use symlinks from each repo directory
into the shared pool — which is not always possible (e.g. on servers that don't
support symlinks).

xml:base may be:

  • Absolutefile:///srv/pool or http://host/pool: the RPM location is
    independent of the repo's configured base URL
  • Relativepool/: resolved against the repo's base URL at runtime,
    keeping the repodata relocatable

What was broken

libsolv correctly parses xml:base and stores it as SOLVABLE_MEDIABASE on the
solvable. However, solvable_get_location() only returns SOLVABLE_MEDIADIR/
SOLVABLE_MEDIAFILE (the href components) — it never consults
SOLVABLE_MEDIABASE. As a result, tdnf would ignore xml:base entirely and
construct wrong URLs by joining the repo's configured base URL with just the bare
href filename, losing the path specified in xml:base.

This affected all tdnf operations: list, repoquery --location, install,
install --downloadonly, and --urls.

Changes

solv/tdnfpackage.c, solv/prototypes.h

Added SolvGetPkgMediaBaseFromId() to read SOLVABLE_MEDIABASE from a solvable.
Returns NULL (not an error) when absent, so normal repos without xml:base are
completely unaffected.

client/packageutils.c

Added a static helper GetPkgLocationWithMediaBase() which replaces all four call
sites of SolvGetPkgLocationFromId() that populate pPkgInfo->pszLocation. When
SOLVABLE_MEDIABASE is present it uses TDNFJoinPath(mediabase, href) to compose
the effective location. Since mediabase is always the first argument, URL schemes
such as file:/// are preserved correctly by TDNFJoinPath. The composed value is:

  • An absolute URL (e.g. file:///srv/pool/pkg.rpm) when xml:base is absolute —
    all downstream code then uses it directly
  • A relative path (e.g. pool/pkg.rpm) when xml:base is relative — joined with
    the repo base URL by existing downstream code as normal

client/remoterepo.c

  • TDNFDownloadFileFromRepo: when pszLocation already contains a :// scheme
    (absolute xml:base case), skip joining with the repo base URL and pass it
    directly to TDNFDownloadFile.
  • TDNFCreatePackageUrl: same guard for the same reason, used by
    repoquery --location and --urls.

client/rpmtrans.c

Added an early check in the package fetch path: when pszLocation starts with
file:// (absolute xml:base), strip the scheme and try access() directly to
use the file in-place, before falling through to the existing loop that tries
joining base URLs.

pytests/tests/test_xmlbase.py

New test module covering all xml:base variants against both file:// and
http:// repositories:

Class Variant
TestNoBase No xml:base — regression guard, existing behaviour unchanged
TestRelativeBase Relative xml:base (e.g. pool/)
TestAbsoluteFileBase Absolute xml:base with file:// URL
TestAbsoluteHttpBase Absolute xml:base with http:// URL; also tests repodata via HTTP with packages from a different HTTP location

Each class exercises: list --available, repoquery --location, install, and
install --downloadonly.

Repos are built on-the-fly using createrepo_c --baseurl (to set xml:base) and
--outputdir (to separate repodata from the RPM pool), with no new test packages
or spec files required.

Note

The changes, including the description above, were entirely generated by Cursor, using the Claude Sonnet 4.6 agent, but with human feedback.

Signed-off-by: Oliver Kurth <oliver.kurth@broadcom.com>
Signed-off-by: Oliver Kurth <oliver.kurth@broadcom.com>
@oliverkurth oliverkurth force-pushed the topic/okurth/repodata-base branch from 8a73c09 to 7e8f824 Compare March 24, 2026 21:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant