Skip to content

sdk/rust: Fix overlay lookup using wrong delta parent inode#300

Merged
penberg merged 1 commit intomainfrom
overlay-lookup
Jan 28, 2026
Merged

sdk/rust: Fix overlay lookup using wrong delta parent inode#300
penberg merged 1 commit intomainfrom
overlay-lookup

Conversation

@penberg
Copy link
Collaborator

@penberg penberg commented Jan 28, 2026

When looking up an entry whose parent is in the Base layer, the overlay walks the delta to find the corresponding delta parent directory. If any component is missing in the delta, the walk broke early and used whatever intermediate inode it had reached (often the delta root). This caused delta.lookup() to search under the wrong parent, potentially returning an unrelated entry that shadows the correct base directory.

This manifested as ENOTDIR when cargo tried to resolve path dependencies like ../sdk/rust/Cargo.toml — if an unrelated file with a matching name existed under the delta root, lookup returned it as a regular file instead of the base directory, causing the kernel to reject further path traversal.

Fix both lookup() and ensure_parent_dirs():

  • lookup(): skip the delta lookup entirely when the path walk doesn't resolve all components (use Option instead of bare i64).

  • ensure_parent_dirs(): track base and delta parent inodes separately so the base lookup uses the correct base inode, not a delta inode.

When looking up an entry whose parent is in the Base layer, the overlay
walks the delta to find the corresponding delta parent directory. If any
component is missing in the delta, the walk broke early and used whatever
intermediate inode it had reached (often the delta root). This caused
delta.lookup() to search under the wrong parent, potentially returning
an unrelated entry that shadows the correct base directory.

This manifested as ENOTDIR when cargo tried to resolve path dependencies
like ../sdk/rust/Cargo.toml — if an unrelated file with a matching name
existed under the delta root, lookup returned it as a regular file
instead of the base directory, causing the kernel to reject further
path traversal.

Fix both lookup() and ensure_parent_dirs():

- lookup(): skip the delta lookup entirely when the path walk doesn't
  resolve all components (use Option<i64> instead of bare i64).

- ensure_parent_dirs(): track base and delta parent inodes separately
  so the base lookup uses the correct base inode, not a delta inode.
@penberg penberg merged commit aaf0940 into main Jan 28, 2026
34 checks passed
@penberg penberg deleted the overlay-lookup branch January 28, 2026 10:28
broady added a commit to broady/agentfs that referenced this pull request Feb 9, 2026
…directories

When a base-layer file is unlinked or renamed inside a directory that
has been promoted from Base to Delta (via ensure_parent_dirs), the old
code walked the base HostFS to decide whether to create a whiteout.
For promoted directories the base inode mapping was already removed by
promote_to_delta(), so the walk silently returned early — skipping
whiteout creation and leaving the base file visible through the overlay.

This caused dpkg to fail with EEXIST during package installs: unlink
of /var/lib/dpkg/status-old succeeded in delta but never created a
whiteout, so the subsequent link("status", "status-old") found the
base-layer file and returned EEXIST.

Fix by:

1. Extracting resolve_delta_parent() that walks the delta layer by
   path to find the parent inode, replacing 5 inline walk patterns
   across lookup, unlink, rmdir, and rename. The walk validates that
   each path component is a directory to avoid shadowing by
   non-directory entries (same class of bug fixed in tursodatabase#300).

2. Changing unlink/rmdir to remove from delta first, then use
   self.lookup() to detect whether the file is still visible — if so,
   it must be from the base layer and needs a whiteout.

3. Moving delta parent resolution in rename() after copy_up() and
   ensure_parent_dirs() so the delta directories exist before we try
   to resolve them.

Add 8 unit tests and a shell integration test reproducing the dpkg
EEXIST scenario.
broady added a commit to broady/agentfs that referenced this pull request Feb 9, 2026
When a base-layer file is unlinked or renamed inside a directory that
has been promoted from Base to Delta (via ensure_parent_dirs), the
old code walked the base HostFS to decide whether to create a
whiteout. For promoted directories the base inode mapping was already
removed by promote_to_delta(), so the walk silently returned early
-- skipping whiteout creation and leaving the base file visible.

This caused dpkg to fail with EEXIST during package installs:
unlink of /var/lib/dpkg/status-old succeeded in delta but never
created a whiteout, so the subsequent link("status", "status-old")
found the base-layer file and returned EEXIST.

Fix by:

1. Extracting resolve_delta_parent() that walks the delta layer
   by path to find the parent inode, replacing 5 inline walk
   patterns across lookup, unlink, rmdir, and rename. The walk
   validates that each component is a directory to avoid
   shadowing by non-directory entries (same class as tursodatabase#300).

2. Changing unlink/rmdir to remove from delta first, then use
   self.lookup() to detect whether the file is still visible
   -- if so, it must be from base and needs a whiteout.

3. Moving delta parent resolution in rename() after copy_up()
   and ensure_parent_dirs() so the delta directories exist
   before we try to resolve them.

Add 8 unit tests and a shell integration test reproducing the
dpkg EEXIST scenario.
broady added a commit to broady/agentfs that referenced this pull request Feb 9, 2026
When a base-layer file is unlinked or renamed inside a directory
that has been promoted from Base to Delta (via ensure_parent_dirs),
the old code walked the base HostFS to decide whether to create a
whiteout. For promoted directories the base inode mapping was
already removed by promote_to_delta(), so the walk silently
returned early -- skipping whiteout creation and leaving the base
file visible through the overlay.

This caused dpkg to fail with EEXIST during package installs:
unlink of /var/lib/dpkg/status-old succeeded in delta but never
created a whiteout, so the subsequent link("status", "status-old")
found the base-layer file and returned EEXIST.

Fix by:

1. Extracting resolve_delta_parent() that walks the delta layer
   by path to find the parent inode, replacing 5 inline walk
   patterns across lookup, unlink, rmdir, and rename. The walk
   validates that each component is a directory to avoid
   shadowing by non-directory entries (same class as tursodatabase#300).

2. Changing unlink/rmdir to remove from delta first, then use
   self.lookup() to detect whether the file is still visible
   -- if so, it must be from base and needs a whiteout.

3. Moving delta parent resolution in rename() after copy_up()
   and ensure_parent_dirs() so the delta directories exist
   before we try to resolve them.

Add 8 unit tests and extend the existing shell integration test
with base-file whiteout coverage.
broady added a commit to broady/agentfs that referenced this pull request Feb 9, 2026
When a base-layer file is unlinked or renamed inside a directory
that has been promoted from Base to Delta (via ensure_parent_dirs),
the old code walked the base HostFS to decide whether to create a
whiteout. For promoted directories the base inode mapping was
already removed by promote_to_delta(), so the walk silently
returned early -- skipping whiteout creation and leaving the base
file visible through the overlay.

This caused dpkg to fail with EEXIST during package installs:
unlink of /var/lib/dpkg/status-old succeeded in delta but never
created a whiteout, so the subsequent link("status", "status-old")
found the base-layer file and returned EEXIST.

Fix by:

1. Extracting resolve_delta_parent() that walks the delta layer
   by path to find the parent inode, replacing 5 inline walk
   patterns across lookup, unlink, rmdir, and rename. The walk
   validates that each component is a directory to avoid
   shadowing by non-directory entries (same class as tursodatabase#300).

2. Changing unlink/rmdir to remove from delta first, then use
   self.lookup() to detect whether the file is still visible
   -- if so, it must be from base and needs a whiteout.

3. Moving delta parent resolution in rename() after copy_up()
   and ensure_parent_dirs() so the delta directories exist
   before we try to resolve them.

Add 8 unit tests and extend the existing shell integration test
with base-file whiteout coverage.

Amp-Thread-ID: https://ampcode.com/threads/T-019c404d-2623-70b7-b5d8-2dfa38f4d21f
Co-authored-by: Amp <amp@ampcode.com>
broady added a commit to broady/agentfs that referenced this pull request Feb 9, 2026
When a base-layer file is unlinked or renamed inside a directory
that has been promoted from Base to Delta (via ensure_parent_dirs),
the old code walked the base HostFS to decide whether to create a
whiteout. For promoted directories the base inode mapping was
already removed by promote_to_delta(), so the walk silently
returned early -- skipping whiteout creation and leaving the base
file visible through the overlay.

This caused dpkg to fail with EEXIST during package installs:
unlink of /var/lib/dpkg/status-old succeeded in delta but never
created a whiteout, so the subsequent link("status", "status-old")
found the base-layer file and returned EEXIST.

Fix by:

1. Extracting resolve_delta_parent() that walks the delta layer
   by path to find the parent inode, replacing 9 inline walk
   patterns across lookup, mkdir, create_file, mknod, symlink,
   unlink, rmdir, link, and rename. The walk validates that each
   component is a directory to avoid shadowing by non-directory
   entries (same class as tursodatabase#300).

2. Changing unlink/rmdir to remove from delta first, then use
   self.lookup() to detect whether the file is still visible
   -- if so, it must be from base and needs a whiteout.

3. Moving delta parent resolution in rename() after copy_up()
   and ensure_parent_dirs() so the delta directories exist
   before we try to resolve them.

Add 8 unit tests and extend the existing shell integration test
with base-file whiteout coverage.

Amp-Thread-ID: https://ampcode.com/threads/T-019c404d-2623-70b7-b5d8-2dfa38f4d21f
Co-authored-by: Amp <amp@ampcode.com>
broady added a commit to broady/agentfs that referenced this pull request Feb 9, 2026
When a base-layer file is unlinked or renamed inside a directory
that has been promoted from Base to Delta (via ensure_parent_dirs),
the old code walked the base HostFS to decide whether to create a
whiteout. For promoted directories the base inode mapping was
already removed by promote_to_delta(), so the walk silently
returned early -- skipping whiteout creation and leaving the base
file visible through the overlay.

This caused dpkg to fail with EEXIST during package installs:
unlink of /var/lib/dpkg/status-old succeeded in delta but never
created a whiteout, so the subsequent link("status", "status-old")
found the base-layer file and returned EEXIST.

Fix by:

1. Extracting resolve_delta_parent() that walks the delta layer
   by path to find the parent inode, replacing 9 inline walk
   patterns across lookup, mkdir, create_file, mknod, symlink,
   unlink, rmdir, link, and rename. The walk validates that each
   component is a directory to avoid shadowing by non-directory
   entries (same class as tursodatabase#300).

2. Changing unlink/rmdir to remove from delta first, then use
   self.lookup() to detect whether the file is still visible
   -- if so, it must be from base and needs a whiteout.

3. Moving delta parent resolution in rename() after copy_up()
   and ensure_parent_dirs() so the delta directories exist
   before we try to resolve them.

Add 8 unit tests and extend the existing shell integration test
with base-file whiteout coverage.
broady added a commit to broady/agentfs that referenced this pull request Feb 9, 2026
When a base-layer file is unlinked or renamed inside a directory
that has been promoted from Base to Delta (via ensure_parent_dirs),
the old code walked the base HostFS to decide whether to create a
whiteout. For promoted directories the base inode mapping was
already removed by promote_to_delta(), so the walk silently
returned early -- skipping whiteout creation and leaving the base
file visible through the overlay.

This caused dpkg to fail with EEXIST during package installs:
unlink of /var/lib/dpkg/status-old succeeded in delta but never
created a whiteout, so the subsequent link("status", "status-old")
found the base-layer file and returned EEXIST.

Fix by:

1. Extracting resolve_delta_parent() that walks the delta layer
   by path to find the parent inode, replacing 9 inline walk
   patterns across lookup, mkdir, create_file, mknod, symlink,
   unlink, rmdir, link, and rename. The walk validates that each
   component is a directory to avoid shadowing by non-directory
   entries (same class as tursodatabase#300).

2. Changing unlink/rmdir to remove from delta first, then use
   self.lookup() to detect whether the file is still visible
   -- if so, it must be from base and needs a whiteout.

3. Moving delta parent resolution in rename() after copy_up()
   and ensure_parent_dirs() so the delta directories exist
   before we try to resolve them.

Add 8 unit tests and extend the existing shell integration test
with base-file whiteout coverage.
broady added a commit to broady/agentfs that referenced this pull request Feb 9, 2026
When a base-layer file is unlinked or renamed inside a directory
that has been promoted from Base to Delta (via ensure_parent_dirs),
the old code walked the base HostFS to decide whether to create a
whiteout. For promoted directories the base inode mapping was
already removed by promote_to_delta(), so the walk silently
returned early -- skipping whiteout creation and leaving the base
file visible through the overlay.

This caused dpkg to fail with EEXIST during package installs:
unlink of /var/lib/dpkg/status-old succeeded in delta but never
created a whiteout, so the subsequent link("status", "status-old")
found the base-layer file and returned EEXIST.

Fix by:

1. Extracting resolve_delta_parent() that walks the delta layer
   by path to find the parent inode, replacing 9 inline walk
   patterns across lookup, mkdir, create_file, mknod, symlink,
   unlink, rmdir, link, and rename. The walk validates that each
   component is a directory to avoid shadowing by non-directory
   entries (same class as tursodatabase#300).

2. Changing unlink/rmdir to remove from delta first, then use
   self.lookup() to detect whether the file is still visible
   -- if so, it must be from base and needs a whiteout.

3. Moving delta parent resolution in rename() after copy_up()
   and ensure_parent_dirs() so the delta directories exist
   before we try to resolve them.

Add 8 unit tests and extend the existing shell integration test
with base-file whiteout coverage.
broady added a commit to broady/agentfs that referenced this pull request Feb 13, 2026
When a base-layer file is unlinked or renamed inside a directory
that has been promoted from Base to Delta (via ensure_parent_dirs),
the old code walked the base HostFS to decide whether to create a
whiteout. For promoted directories the base inode mapping was
already removed by promote_to_delta(), so the walk silently
returned early -- skipping whiteout creation and leaving the base
file visible through the overlay.

This caused dpkg to fail with EEXIST during package installs:
unlink of /var/lib/dpkg/status-old succeeded in delta but never
created a whiteout, so the subsequent link("status", "status-old")
found the base-layer file and returned EEXIST.

Fix by:

1. Extracting resolve_delta_parent() that walks the delta layer
   by path to find the parent inode, replacing 9 inline walk
   patterns across lookup, mkdir, create_file, mknod, symlink,
   unlink, rmdir, link, and rename. The walk validates that each
   component is a directory to avoid shadowing by non-directory
   entries (same class as tursodatabase#300).

2. Changing unlink/rmdir to remove from delta first, then use
   self.lookup() to detect whether the file is still visible
   -- if so, it must be from base and needs a whiteout.

3. Moving delta parent resolution in rename() after copy_up()
   and ensure_parent_dirs() so the delta directories exist
   before we try to resolve them.

Add 8 unit tests and extend the existing shell integration test
with base-file whiteout coverage.
broady added a commit to broady/agentfs that referenced this pull request Feb 14, 2026
When a base-layer file is unlinked or renamed inside a directory
that has been promoted from Base to Delta (via ensure_parent_dirs),
the old code walked the base HostFS to decide whether to create a
whiteout. For promoted directories the base inode mapping was
already removed by promote_to_delta(), so the walk silently
returned early -- skipping whiteout creation and leaving the base
file visible through the overlay.

This caused dpkg to fail with EEXIST during package installs:
unlink of /var/lib/dpkg/status-old succeeded in delta but never
created a whiteout, so the subsequent link("status", "status-old")
found the base-layer file and returned EEXIST.

Fix by:

1. Extracting resolve_delta_parent() that walks the delta layer
   by path to find the parent inode, replacing 9 inline walk
   patterns across lookup, mkdir, create_file, mknod, symlink,
   unlink, rmdir, link, and rename. The walk validates that each
   component is a directory to avoid shadowing by non-directory
   entries (same class as tursodatabase#300).

2. Changing unlink/rmdir to remove from delta first, then use
   self.lookup() to detect whether the file is still visible
   -- if so, it must be from base and needs a whiteout.

3. Moving delta parent resolution in rename() after copy_up()
   and ensure_parent_dirs() so the delta directories exist
   before we try to resolve them.

Add 8 unit tests and extend the existing shell integration test
with base-file whiteout coverage.
broady added a commit to broady/agentfs that referenced this pull request Feb 15, 2026
When a base-layer file is unlinked or renamed inside a directory
that has been promoted from Base to Delta (via ensure_parent_dirs),
the old code walked the base HostFS to decide whether to create a
whiteout. For promoted directories the base inode mapping was
already removed by promote_to_delta(), so the walk silently
returned early -- skipping whiteout creation and leaving the base
file visible through the overlay.

This caused dpkg to fail with EEXIST during package installs:
unlink of /var/lib/dpkg/status-old succeeded in delta but never
created a whiteout, so the subsequent link("status", "status-old")
found the base-layer file and returned EEXIST.

Fix by:

1. Extracting resolve_delta_parent() that walks the delta layer
   by path to find the parent inode, replacing 9 inline walk
   patterns across lookup, mkdir, create_file, mknod, symlink,
   unlink, rmdir, link, and rename. The walk validates that each
   component is a directory to avoid shadowing by non-directory
   entries (same class as tursodatabase#300).

2. Changing unlink/rmdir to remove from delta first, then use
   self.lookup() to detect whether the file is still visible
   -- if so, it must be from base and needs a whiteout.

3. Moving delta parent resolution in rename() after copy_up()
   and ensure_parent_dirs() so the delta directories exist
   before we try to resolve them.

Add 8 unit tests and extend the existing shell integration test
with base-file whiteout coverage.
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