Skip to content

feat: add extraction APIs for caller-owned roots - #123

Open
rcjsuen wants to merge 1 commit into
moby:mainfrom
rcjsuen:extraction-race-fix
Open

rcjsuen wants to merge 1 commit into
moby:mainfrom
rcjsuen:extraction-race-fix

Conversation

@rcjsuen

@rcjsuen rcjsuen commented Sep 30, 2026 •

Copy link
Copy Markdown

Summary

Add TarOptions.DestinationRoot so callers can extract relative to an already-open *os.Root.

Existing callers are unaffected. When DestinationRoot is nil, extraction continues to open dest directly as it does today.

Motivation

For the general case, passing a root is equivalent to allowing Unpack to call os.OpenRoot(dest) itself. The distinction matters when a caller already has a vetted directory handle.

If the caller converts that handle back into a pathname, the pathname can be renamed or replaced before Unpack opens it. Extraction may then operate on a different directory from the one the caller selected. Passing the existing os.Root preserves that directory identity.

This keeps authorization with the application while allowing go-archive to perform extraction through the boundary the application established.

Behavior

When DestinationRoot is set, dest is interpreted relative to it:

root, err := os.OpenRoot(authorizedDirectory)
if err != nil {
    return err
}
defer root.Close()

err = archive.Untar(stream, "output", &archive.TarOptions{
    DestinationRoot: root,
})

This extracts into output beneath root. Passing . extracts directly beneath the supplied root.

The ownership rules are:

  • the caller retains ownership of DestinationRoot;
  • extraction does not close the caller's root;
  • a derived root opened for a relative dest is closed by the library; and
  • absolute paths and paths that escape the supplied root are rejected by os.Root.

DestinationRoot applies only to extraction. Archive creation is unchanged. No exported function signatures change.

Testing

The regression test opens a destination root, renames its directory, and creates a replacement at the original pathname before extracting. It verifies that:

  • extraction writes into the directory represented by the open root;
  • nothing is written into the replacement directory; and
  • extraction does not close the caller-owned root.

Validation performed:

$ go test . ./chrootarchive
ok  github.com/moby/go-archive
ok  github.com/moby/go-archive/chrootarchive

$ GOOS=windows GOARCH=amd64 go test -c -o /tmp/go-archive-windows.test .

$ golangci-lint run ./...
0 issues.

The compression package's separate tests require the external bzip2 and xz executables, which were unavailable in the local environment.

Comment thread archive.go Resolved
@codecov-commenter

codecov-commenter commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.33333% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.59%. Comparing base (953c7df) to head (ed8c1ed).
⚠️ Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
archive.go 83.33% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #123      +/-   ##
==========================================
+ Coverage   65.48%   74.59%   +9.11%     
==========================================
  Files          46       46              
  Lines        2393     2401       +8     
==========================================
+ Hits         1567     1791     +224     
- Misses        605      608       +3     
+ Partials      221        2     -219     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Add DestinationRoot to TarOptions so callers can extract relative to an
already-open os.Root. This preserves a caller-vetted directory identity when
its pathname is renamed or replaced before extraction begins.

Keep the existing behavior when DestinationRoot is unset. Treat dest as
root-relative when it is set, retain caller ownership of the supplied root,
and close only roots derived for nested destinations.

Add coverage that replaces the original destination path after its root is
opened and verifies that extraction still writes to the opened directory and
does not close the caller-owned root.

Signed-off-by: Remy Suen <remy.suen@docker.com>
Co-authored-by: ChatGPT <noreply@openai.com>
@rcjsuen
rcjsuen force-pushed the extraction-race-fix branch from caf4765 to ed8c1ed Compare September 30, 2026 17:06
@vvoland

vvoland commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Left some suggestions: rcjsuen#1

@vvoland

vvoland commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

@rcjsuen I adjusted my changes a bit and opened a second PR: #127

PTAL

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.

3 participants