Skip to content

fix: find the Vortex stage by mount point, not by path prefix - #25

Merged
360900 merged 1 commit into
360900:mainfrom
Plasma-Deckery:fix-vortex-stage-root-mount
Oct 17, 2026
Merged

360900 merged 1 commit into
360900:mainfrom
Plasma-Deckery:fix-vortex-stage-root-mount

Conversation

@phischdev

Copy link
Copy Markdown
Contributor

Fixes #24. Branches off main, independent of #23.

The bug

setGameVortexStaging looks for the configured staging directory on the same filesystem as the game — Vortex hardlinks mods from there into the game, so it has to be. It did that by using the game's mount point as a path prefix:

GAMEMP="$( df --output=target "$VGAMEDIR" | tail -n1 )"
CONFSTAGE="$(grep "${GAMEMP}/" "$VORTEXSTAGELIST")"

With the game on the root filesystem, df reports / and the pattern becomes //, which matches no ordinary path:

$ GAMEMP="/"; printf '/home/user/staging/stage\n' | grep "${GAMEMP}/"
(nothing)

$ GAMEMP="/tmp"; printf '/tmp/x/stage\n' | grep "${GAMEMP}/"
/tmp/x/stage

So the lookup worked for every mount point except the most common one. CONFSTAGE came back empty, the auto-stage path ran instead, and nothing said that the user's configured directory had been passed over.

Why the obvious fix is wrong

Treating / as an empty prefix would match every line, so a stage on /mnt/ssd would be accepted for a game on / — and Vortex would then refuse hardlink deployment, which is the thing this lookup exists to prevent.

The question being asked is "is this stage on the same filesystem as the game", so the fix asks df for both and compares. Four cases, two of which the prefix form got wrong:

Game on Stage on Must Prefix form
/ / match never matched
/ /mnt/ssd not match not matched ✓
/mnt/games /mnt/games/Vortex match matched ✓
/mnt/games / not match not matched ✓

Two things that fall out of the loop

Several stages on the game's partition no longer break the lookup. grep returned every matching line, so CONFSTAGE became multi-line, [ -d ] on it failed, and a valid configuration was reported as isn't a useable directory. The loop takes the first usable entry.

The missing-directory diagnostic now names the right entry. It is kept, and reports the listed path that is actually gone rather than whatever grep happened to return. VSTAGEGONE is added to the unset at the top of the function, since there is no local scope here and a leftover would otherwise follow the next game.

Testing

11 tests in tests/unit/vortexstage.bats, covering the table above plus the comment header, paths with spaces, a listed directory that is gone, several candidates, no list at all, and a stale value from a previous call.

df is stubbed, because a test cannot arrange real mount points. The simulated ones are real directories inside the test tmpdir and the paths below them genuinely sit below them, so a prefix match behaves in the test exactly as it would on a machine — except for /, which nothing in a test directory can sit directly under, and which is the broken case.

On main, five of the eleven fail, and I want to be precise about why: two are real defects (the root-filesystem case, and several candidates breaking the lookup), one guards against the wrong fix. The other two fail only because grep returns 1 when it matches nothing, which aborts the function under the test runner's errexit; in production, with no errexit, that path simply continues with an empty CONFSTAGE. The loop has no such exit status.

This also turns setGameVortexStaging: a configured stage is not replaced in tests/unit/vortex.bats green. That test fails on an unmodified main in CI — a GitHub runner has /tmp on /, so GAMEMP is / and the pattern is // — while passing on a machine where /tmp is its own mount. The test was right and the environments where it passed were the lucky ones.

Note on the version bump

Sets PROGVERS to v0.1.0-alpha.12, which collides with #23 since both branch from main. I will rebase whichever lands later.

setGameVortexStaging looks for the configured staging directory that sits on
the same filesystem as the game, because Vortex hardlinks mods from there into
the game. It did that by using the game's mount point as a path prefix:

    GAMEMP="$( df --output=target "$VGAMEDIR" | tail -n1 )"
    CONFSTAGE="$(grep "${GAMEMP}/" "$VORTEXSTAGELIST")"

With the game on the root filesystem, df reports "/" and the pattern becomes
"//", which matches no ordinary path:

    $ GAMEMP="/"; printf '/home/user/staging/stage\n' | grep "${GAMEMP}/"
    (nothing)
    $ GAMEMP="/tmp"; printf '/tmp/x/stage\n' | grep "${GAMEMP}/"
    /tmp/x/stage

So the lookup worked for every mount point except the most common one. The
configured stage was silently ignored and the auto-stage path ran instead, with
no warning that the user's setting had been passed over.

Treating "/" as an empty prefix would be wrong in the other direction: every
absolute path starts with "/", so a stage on /mnt/ssd would be accepted for a
game on /, and Vortex would then refuse hardlink deployment. The question the
lookup is really asking is whether the stage is on the same filesystem as the
game, so ask df for both and compare.

Two further things fall out of replacing the grep with a loop:

Several stages on the game's partition no longer break the lookup. grep
returned every matching line, so CONFSTAGE became multi-line, "[ -d ]" on it
failed, and a perfectly good configuration was reported as "isn't a useable
directory". The loop takes the first usable one.

The diagnostic for a listed directory that no longer exists is kept, and now
names the entry that is actually missing rather than whatever grep returned.
VSTAGEGONE is added to the unset list at the top, because this function has no
local scope and a leftover would otherwise follow the next game.
@360900
360900 merged commit b429220 into 360900:main Oct 17, 2026
4 checks passed
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.

Configured Vortex staging directory is ignored when the game is on the root filesystem

2 participants