Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
a17ccda
dominator: add golangci-lint file and pre-commit-config for linting a…
narendraReddy45 Apr 26, 2026
798f11c
imaginator: handle destination symlinks in appendTree
narendraReddy45 Apr 27, 2026
0aaef22
imaginator: enhance evalsymlinks method
narendraReddy45 Apr 27, 2026
fb2dc27
imaginator: fix errors
narendraReddy45 Apr 27, 2026
3dfd205
imaginator: improve AppendTree method doc
narendraReddy45 Apr 27, 2026
1e9e862
Revert "dominator: add golangci-lint file and pre-commit-config for l…
narendraReddy45 Apr 27, 2026
f3c3952
imaginator: enhance append logic for multihop dest symlinks with rela…
narendraReddy45 Apr 30, 2026
7222df5
imaginator: clamp relative paths to root similar to kernel chroot style
narendraReddy45 Apr 30, 2026
fba5d56
imaginator: update api documentation of AppendTree
narendraReddy45 Apr 30, 2026
fb6b7d6
imaginator: implement safe path for chroot style directories in appen…
narendraReddy45 Apr 30, 2026
af933c8
imaginator: implement chroot style clamping to destDir
narendraReddy45 Apr 30, 2026
d08800c
imaginator: break into methods for readability and maintainability
narendraReddy45 Apr 30, 2026
83b6e00
imaginator: implement chroot semantics for copying directories
narendraReddy45 Apr 30, 2026
3aedc99
imaginator: fix edge case of escaping to host from chroot
narendraReddy45 Apr 30, 2026
aa1f991
lib/fsutil: maintain api backward compatibility
narendraReddy45 May 4, 2026
f90c09e
imaginator: implement vfs using openat2 for rootfs-aware copy and app…
narendraReddy45 May 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions lib/fsutil/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,25 @@ func AppendFile(destFilename, sourceFilename string) error {
return appendFile(destFilename, sourceFilename)
}

// AppendFileWithRoot extends AppendFile with safe symlink evaluation. Relative
// symlinks are clamped to the root boundary, and absolute symlinks are rebased
// against the root using chroot-style semantics.
func AppendFileWithRoot(rootFd int, destRelPath, sourcePath string) error {
return appendFileWithRoot(rootFd, destRelPath, sourcePath)
}

// AppendTree recursively merges sourceDir into destDir.
// It appends contents to existing files or copies new ones
// while preserving permissions.
// Directory structures are mirrored.
// Returns an error if symlinks or non-regular files are encountered.
// Existing regular files will have data appended. Files which do not exist in
// destDir will be copied with the source file permissions.
// Directory structures will be mirrored. An error is returned if symlinks or
// non-regular files are encountered in sourceDir. If a destination path is a
// symlink, it is resolved within destDir using chroot-style semantics:
// absolute targets are anchored at destDir and ".." is clamped at its root.
// Dangling symlinks cause an error.
// Valid symlink targets within destDir will have data appended
// to the resolved file; the symlink itself is preserved.
func AppendTree(destDir, sourceDir string) error {
return appendTree(destDir, sourceDir, AppendFile)
return appendTree(destDir, sourceDir, AppendFileWithRoot)
}

// CompareFile will read and compare the content of a file and buffer and will
Expand Down
45 changes: 37 additions & 8 deletions lib/fsutil/append.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"io/fs"
"os"
"path/filepath"

"golang.org/x/sys/unix"
)

func appendToFile(destFilename string, reader io.Reader,
Expand Down Expand Up @@ -40,6 +42,7 @@ func appendFile(destFilename, sourceFilename string) error {
}
return copyFile(destFilename, sourceFilename, mode, false)
}
return err
}
sourceFile, err := os.Open(sourceFilename)
if err != nil {
Expand All @@ -50,28 +53,54 @@ func appendFile(destFilename, sourceFilename string) error {
return appendToFile(destFilename, sourceFile, 0)
}

func appendFileWithRoot(rootFd int, destRelPath, sourcePath string) error {
mode, err := getFilePerms(sourcePath)
if err != nil {
return err
}
destFile, err := secureOpenFile(rootFd, destRelPath, uint32(mode))
if err != nil {
return err
}
defer destFile.Close()
sourceFile, err := os.Open(sourcePath)
if err != nil {
return errors.New(sourcePath + ": " + err.Error())
}
_, err = io.Copy(destFile, sourceFile)
if err != nil {
return fmt.Errorf(
"error copying contents from source %q to dest %q: %w",
sourcePath, destRelPath, err)
}
return nil
}

func appendTree(destDir, sourceDir string,
appendFunc func(dest, src string) error) error {
appendFunc func(rootFd int, destRelPath, sourcePath string) error) error {
rootFd, err := openRoot(destDir)
if err != nil {
return err
}
defer unix.Close(rootFd)
return filepath.WalkDir(sourceDir,
func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if path == sourceDir {
return nil
}
relPath, err := filepath.Rel(sourceDir, path)
if err != nil {
return err
}
destFilename := filepath.Join(destDir, relPath)
fileType := d.Type()
switch {
case fileType.IsDir():
// If path is a directory, create directory and return.
// WalkDir will automatically visit the children next.
if err := os.MkdirAll(destFilename, DirPerms); err != nil {
return err
}
return secureMkdir(rootFd, relPath, DirPerms)
case fileType.IsRegular():
if err := appendFunc(destFilename, path); err != nil {
if err := appendFunc(rootFd, relPath, path); err != nil {
return err
}
case fileType&fs.ModeSymlink != 0:
Expand Down
Loading