Skip to content

Use os.Root for file operations#4

Merged
et-nik merged 1 commit into
masterfrom
fs-osroot
May 17, 2026
Merged

Use os.Root for file operations#4
et-nik merged 1 commit into
masterfrom
fs-osroot

Conversation

@et-nik

@et-nik et-nik commented May 17, 2026

Copy link
Copy Markdown
Member

No description provided.

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.

Pull request overview

Replaces ad-hoc path joining and the third-party otiai10/copy library with a new in-tree fsutil package and confines all caller-supplied file operations to an os.Root opened at the daemon's work directory. This removes a class of symlink/.. escape vulnerabilities (with explicit regression tests) and eliminates the otiai10/copy dependency.

Changes:

  • New internal/app/fsutil package providing RootRel path normalization and a recursive Copy/CopyTree/CopyInRoot built on os.Root, with unit tests.
  • Refactor TCP Files handler, gRPC file/transfer handlers, and osowner to thread an *os.Root through every operation; old ResolvePath/pathWithin removed.
  • Functional test suites reworked to send work-dir–relative paths, mirror fixtures into the per-suite work dir, and replace otiai10/copy calls with fsutil.Copy.

Reviewed changes

Copilot reviewed 33 out of 34 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/app/fsutil/path.go, copy.go, *_test.go New path normalization + recursive copy primitives confined to os.Root
internal/app/server/files/files.go, response.go TCP file handler converted to per-request os.Root, staging via in-tree temp file
internal/app/server/server.go, services/runner.go Plumb workPath into NewServer/files.NewFiles
internal/app/grpc/file_handler.go, transfer_handler.go gRPC handlers use os.Root + fsutil.RootRel; ResolvePath/pathWithin removed
internal/app/grpc/file_handler_test.go, file_owner_test.go Replace path-resolution unit tests with end-to-end symlink-escape regression tests
internal/app/osowner/owner.go, owner_unix.go, owner_windows.go, owner_test.go Add MissingSegmentsInRoot / ApplyToPathInRoot with platform-specific lchownInRoot and tests
internal/app/game_server_commands/install_server.go, delete_server_test.go Swap otiai10/copy for fsutil.Copy
test/functional/servertest/suite.go and files/*_test.go New WorkPath plumbing, fixtures mirrored into work dir, helpers handing out (rel, abs) pairs
test/functional/{serverscommand,server_tasks,gdtasks}/* Swap otiai10/copy for fsutil.Copy in test setup
go.mod, go.sum Drop otiai10/copy and otiai10/mint dependencies

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +393 to +404
tmpRel := rel + ".upload_tmp"
tmpFile, err := root.OpenFile(tmpRel, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, permissions)
if err != nil {
logger.Error(ctx, errors.WithMessage(err, "failed to create temp file"))
return writeError(readWriter, "Failed to create temp file")
}
defer func(file *os.File) {
err = file.Close()
if err != nil {
logger.Error(ctx, errors.WithMessage(err, "failed to close temp file"))
}
err = os.Remove(file.Name())
if err != nil {
logger.Error(ctx, errors.WithMessage(err, "failed to remove temp file"))
}
}(tmpFile)
defer func() {
// On the success path the temp file has already been closed and
// renamed away; both calls then fail harmlessly.
_ = tmpFile.Close()
_ = root.Remove(tmpRel)
}()
Comment on lines +227 to 233
if _, err := root.Stat(srcRel); errors.Is(err, os.ErrNotExist) {
return writeError(readWriter, fmt.Sprintf("Source \"%s\" not found", message.Source))
}

if _, err := os.Stat(message.Destination); !errors.Is(err, os.ErrNotExist) {
if _, err := root.Stat(dstRel); !errors.Is(err, os.ErrNotExist) {
return writeError(readWriter, fmt.Sprintf("Destination \"%s\" already exists", message.Destination))
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Confirmed

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 25994728228

Coverage increased (+1.1%) to 37.337%

Details

  • Coverage increased (+1.1%) from the base build.
  • Patch coverage: 232 uncovered changes across 9 files (308 of 540 lines covered, 57.04%).
  • 5 coverage regressions across 2 files.

Uncovered Changes

File Changed Covered %
internal/app/grpc/file_handler.go 126 55 43.65%
internal/app/fsutil/copy.go 156 98 62.82%
internal/app/grpc/transfer_handler.go 47 0 0.0%
internal/app/server/files/files.go 152 111 73.03%
internal/app/osowner/owner.go 28 21 75.0%
internal/app/osowner/owner_unix.go 3 0 0.0%
internal/app/fsutil/path.go 16 14 87.5%
internal/app/server/files/response.go 8 6 75.0%
internal/app/services/runner.go 1 0 0.0%

Coverage Regressions

5 previously-covered lines in 2 files lost coverage.

File Lines Losing Coverage Coverage
internal/app/grpc/file_handler.go 3 34.7%
internal/app/server/files/files.go 2 69.4%

Coverage Stats

Coverage Status
Relevant Lines: 13054
Covered Lines: 4874
Line Coverage: 37.34%
Coverage Strength: 25960.6 hits per line

💛 - Coveralls

@et-nik et-nik merged commit f5971f8 into master May 17, 2026
7 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.

3 participants