Skip to content

libfatx: fix intermediate path component matching - #99

Merged
mborgerson merged 1 commit into
XboxDev:masterfrom
Akashijk:fix/path-component-prefix-match
Oct 30, 2026
Merged

mborgerson merged 1 commit into
XboxDev:masterfrom
Akashijk:fix/path-component-prefix-match

Conversation

@Akashijk

@Akashijk Akashijk commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Fix fatx_open_dir() incorrectly resolving an intermediate directory component as a longer sibling that shares the same prefix.

For example, with sibling directories:

wolf/
wosk/
w/

@xmutantson

Copy link
Copy Markdown
Contributor

Confirming this one on a real drive. rsync sorts directories as if they had a trailing /, so BloodRayne 2/ is created before BloodRayne/, and every later lookup of BloodRayne/... resolved into BloodRayne 2. The two games' files ended up merged in one directory. With an exact-length comparison like the one here, each directory keeps its own files. In a small test that creates Game 2/video/a.txt and then Game/video/b.txt, on master both Game 2/video and Game/video list only b.txt; with an exact-length comparison, Game 2/video has a.txt and Game/video has b.txt.

It'd be great to see this merged; it's an easy one to hit when copying a game collection.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues were identified for the change preventing intermediate path components from matching longer sibling names.

No open issues 🎉

What changed in this PR

Fixes intermediate directory matching in fatx_open_dir() so a path component does not resolve to a longer sibling with the same prefix.

Changes:

  • Preserves the original component length while scanning sibling entries.
  • Requires an exact filename length for slash-terminated components before comparing their contents.
File Description
libfatx/​fatx_dir.c Uses a separate comparison length and checks intermediate component lengths to prevent prefix matches.

Review effort: Balanced

@mborgerson

Copy link
Copy Markdown
Member

Could you please add some test cases for this while we are at it?

@xmutantson

Copy link
Copy Markdown
Contributor

@Akashijk here are two test cases for this, written for pyfatx/tests/test.py, in case they're useful. On current master (now with #106 merged) both fail; with this PR applied on top they pass, along with the rest of the suite (21/21). This PR also still applies cleanly on top of current master.

	@with_formatted_disk
	def test_path_component_does_not_match_longer_sibling(self, path):
		fs = Fatx(path)

		# '/Game 2' is created first, so a lookup of the intermediate
		# component 'Game/' that matched by prefix would resolve to it.
		fs.mkdir('/Game 2')
		fs.mkdir('/Game 2/video')
		fs.write('/Game 2/video/a.txt', b'two')
		fs.mkdir('/Game')
		fs.mkdir('/Game/video')
		fs.write('/Game/video/b.txt', b'one')

		assert [a.filename for a in fs.listdir('/Game 2/video')] == ['a.txt']
		assert [a.filename for a in fs.listdir('/Game/video')] == ['b.txt']
		assert fs.read('/Game 2/video/a.txt') == b'two'
		assert fs.read('/Game/video/b.txt') == b'one'


	@with_formatted_disk
	def test_path_component_prefix_is_not_found(self, path):
		fs = Fatx(path)
		fs.mkdir('/top')
		fs.mkdir('/top/wolf')
		fs.mkdir('/top/wolf/inner')
		fs.write('/top/wolf/inner/x', b'x')

		# Prefixes of an existing directory name, used as intermediate
		# components, are not that directory
		for missing in ('/top/w/inner/x', '/top/wol/inner/x'):
			found = False
			try:
				fs.get_attr(missing)
				found = True
			except AssertionError:
				pass
			assert not found, missing

		assert fs.read('/top/wolf/inner/x') == b'x'
  • The first is the case I hit on a real drive: Game 2/ is created before Game/, so on master Game/video resolves to Game 2/video and both files end up in one directory.
  • The second checks that a prefix of a directory name used as an intermediate component (/top/w/inner/x) doesn't resolve into /top/wolf/. The last component of the parent path is already compared exactly, so the prefix has to sit in the middle of the path to hit this.

@mborgerson

Copy link
Copy Markdown
Member

@xmutantson Thanks for the tests. I'll merge this PR now, would you then please submit a PR with the tests you created in a follow up?

@mborgerson
mborgerson merged commit 4fd8e81 into XboxDev:master Oct 30, 2026
mborgerson pushed a commit that referenced this pull request Nov 2, 2026
Covers the lookup fixed in 4fd8e81 (#99): an intermediate path
component must match a directory name exactly, not by prefix.

- test_path_component_does_not_match_longer_sibling: '/Game 2' is
  created before '/Game', as rsync orders directories, and each
  directory's files must stay in that directory.
- test_path_component_prefix_is_not_found: '/top/w/inner/x' and
  '/top/wol/inner/x' must not resolve into '/top/wolf/'. The prefix
  has to sit in the middle of the path, because the last component of
  a parent path is already compared exactly.

Both fail before 4fd8e81 and pass with it.
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.

4 participants