Files
privatae-bastion/client-go/harness_tar_unix.go
T
Privatae 8822d5df85
ci / personal-data (push) Successful in 5s
ci / test (push) Successful in 3m51s
ci / fuzz (push) Successful in 2m55s
bastion 0.55.3
2026-10-25 23:16:45 +00:00

115 lines
3.3 KiB
Go

//go:build !windows
package main
import (
"errors"
"fmt"
"os"
"golang.org/x/sys/unix"
)
// errNotPlainFile is openTreeEntry's refusal of a name that is no longer a
// plain file when it is opened.
var errNotPlainFile = errors.New("not a plain file")
// packTree hands the tree under root to sink, walking it by open folder
// (packWorktree says why). Each name is looked at with fstatat and opened
// relative to the folder above it (openTreeEntry, per system), never
// following a link, and it goes in only if the opened file is still the plain
// file or folder that was looked at (same type, device and inode). A pipe or
// device seen as one is never opened, so the walk cannot wait on one; on
// Linux one swapped in between is not opened either, and elsewhere O_NONBLOCK
// and O_NOCTTY cover it. Each left-out entry goes to sink.skip.
func packTree(sink treeSink, root string) error {
fd, err := unix.Open(root, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
if err != nil {
return &os.PathError{Op: "open", Path: root, Err: err}
}
d := os.NewFile(uintptr(fd), root)
defer d.Close()
return packFolder(sink, d, fd, "", 0)
}
func packFolder(sink treeSink, d *os.File, dfd int, rel string, depth int) error {
names, err := readTreeNames(sink, d, rel)
if err != nil {
return err
}
if err := sink.folder(rel, names); err != nil {
return err
}
for _, name := range names {
p := name
if rel != "" {
p = rel + "/" + name
}
var st unix.Stat_t
if err := unix.Fstatat(dfd, name, &st, unix.AT_SYMLINK_NOFOLLOW); err != nil {
sink.skip(p) // gone since the folder was read
continue
}
kind := st.Mode & unix.S_IFMT
if kind == unix.S_IFDIR && name == ".git" {
continue
}
if (kind != unix.S_IFREG && kind != unix.S_IFDIR) || (kind == unix.S_IFDIR && depth >= maxTreeDepth) {
sink.skip(p)
continue
}
if packTreeBeforeOpen != nil {
packTreeBeforeOpen(p)
}
flags := unix.O_RDONLY | unix.O_NOFOLLOW | unix.O_CLOEXEC | unix.O_NONBLOCK | unix.O_NOCTTY
if kind == unix.S_IFDIR {
flags |= unix.O_DIRECTORY
}
cfd, err := openTreeEntry(dfd, name, flags)
if err != nil {
sink.skip(p) // a link or special file now, or gone
continue
}
err = packOpened(sink, cfd, name, st, p, depth)
if err != nil {
return err
}
}
return nil
}
// packOpened hands sink the entry opened as fd, and for a folder what is in it,
// if it is still the entry fstatat saw; it closes fd.
func packOpened(sink treeSink, fd int, name string, seen unix.Stat_t, p string, depth int) error {
var got unix.Stat_t
if err := unix.Fstat(fd, &got); err != nil {
unix.Close(fd)
return fmt.Errorf("stat %q: %w", p, err)
}
kind := got.Mode & unix.S_IFMT
if kind != seen.Mode&unix.S_IFMT || got.Dev != seen.Dev || got.Ino != seen.Ino {
unix.Close(fd)
sink.skip(p) // swapped between the look and the open
return nil
}
// A plain file or folder now: back to blocking reads, so os.File reads it
// as it reads any file it opened itself.
if err := unix.SetNonblock(fd, false); err != nil {
unix.Close(fd)
return fmt.Errorf("open %q: %w", p, err)
}
f := os.NewFile(uintptr(fd), name)
defer f.Close()
fi, err := f.Stat()
if err != nil {
return fmt.Errorf("stat %q: %w", p, err)
}
if err := sink.put(f, fi, p); err != nil {
return err
}
if kind == unix.S_IFDIR {
return packFolder(sink, f, fd, p, depth+1)
}
return nil
}