iop/apps/node/internal/workspace/identity_unix.go
toki dc9a9a8c59 feat(agent): 단일 요청 Agent 실행 경계를 구현한다
승인된 execution preset을 Edge 조정 경계와 Node workspace/tool 실행 경계로 연결해 단일 요청 수명주기와 관측 계약을 일관되게 처리한다.
2026-08-07 07:03:55 +09:00

186 lines
4.7 KiB
Go

//go:build unix
package workspace
import (
"errors"
"io/fs"
"os"
"path"
"strings"
"syscall"
"golang.org/x/sys/unix"
)
func platformFileIdentity(info fs.FileInfo) (uint64, uint64, bool) {
stat, ok := info.Sys().(*syscall.Stat_t)
if !ok {
return 0, 0, false
}
return uint64(stat.Dev), uint64(stat.Ino), true
}
// writeParent pins one validated directory descriptor. All write effects and
// the final rename are relative to this descriptor, never to a re-resolved path.
type writeParent struct {
entry *catalogEntry
relative string
fd int
device uint64
inode uint64
}
func openOrCreateParentNoFollow(entry *catalogEntry, name string) (*writeParent, string, error) {
if entry == nil || entry.directory == nil {
return nil, "", errUnsafePath
}
parent, err := openParentNoFollow(entry, path.Dir(name), true)
if err != nil {
return nil, "", err
}
return parent, path.Base(name), nil
}
func openParentNoFollow(entry *catalogEntry, relative string, create bool) (*writeParent, error) {
fd, err := unix.Dup(int(entry.directory.Fd()))
if err != nil {
return nil, errUnsafePath
}
unix.CloseOnExec(fd)
closeFD := true
defer func() {
if closeFD {
_ = unix.Close(fd)
}
}()
device, inode, err := directoryIdentity(fd, entry.device)
if err != nil || device != entry.device || inode != entry.inode {
return nil, errUnsafePath
}
if relative != "." {
for _, component := range strings.Split(relative, "/") {
next, openErr := unix.Openat(fd, component, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
if openErr != nil && create && errors.Is(openErr, unix.ENOENT) {
if err := unix.Mkdirat(fd, component, 0o700); err != nil && !errors.Is(err, unix.EEXIST) {
return nil, errUnsafePath
}
next, openErr = unix.Openat(fd, component, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
}
if openErr != nil {
return nil, errUnsafePath
}
nextDevice, nextInode, identityErr := directoryIdentity(next, entry.device)
if identityErr != nil {
_ = unix.Close(next)
return nil, errUnsafePath
}
_ = unix.Close(fd)
fd, device, inode = next, nextDevice, nextInode
}
}
closeFD = false
return &writeParent{entry: entry, relative: relative, fd: fd, device: device, inode: inode}, nil
}
func directoryIdentity(fd int, expectedDevice uint64) (uint64, uint64, error) {
var stat unix.Stat_t
if err := unix.Fstat(fd, &stat); err != nil || stat.Mode&unix.S_IFMT != unix.S_IFDIR {
return 0, 0, errUnsafePath
}
device := uint64(stat.Dev)
if device != expectedDevice {
return 0, 0, errUnsafePath
}
return device, uint64(stat.Ino), nil
}
func (p *writeParent) close() error {
if p == nil || p.fd < 0 {
return nil
}
err := unix.Close(p.fd)
p.fd = -1
return err
}
func (p *writeParent) targetIdentity(base string) (targetIdentity, error) {
var stat unix.Stat_t
err := unix.Fstatat(p.fd, base, &stat, unix.AT_SYMLINK_NOFOLLOW)
if errors.Is(err, unix.ENOENT) {
return targetIdentity{}, nil
}
if err != nil {
return targetIdentity{}, errUnsafePath
}
return targetIdentity{
exists: true,
device: uint64(stat.Dev),
inode: uint64(stat.Ino),
mode: unixFileMode(uint32(stat.Mode)),
}, nil
}
func unixFileMode(mode uint32) fs.FileMode {
permissions := fs.FileMode(mode & 0o777)
switch mode & unix.S_IFMT {
case unix.S_IFDIR:
return permissions | fs.ModeDir
case unix.S_IFLNK:
return permissions | fs.ModeSymlink
case unix.S_IFIFO:
return permissions | fs.ModeNamedPipe
case unix.S_IFSOCK:
return permissions | fs.ModeSocket
case unix.S_IFCHR:
return permissions | fs.ModeDevice | fs.ModeCharDevice
case unix.S_IFBLK:
return permissions | fs.ModeDevice
case unix.S_IFREG:
return permissions
default:
return permissions | fs.ModeIrregular
}
}
func (p *writeParent) createTemp(base string) (*os.File, error) {
fd, err := unix.Openat(p.fd, base, unix.O_WRONLY|unix.O_CREAT|unix.O_EXCL|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0o600)
if err != nil {
return nil, errUnsafePath
}
return os.NewFile(uintptr(fd), base), nil
}
func (p *writeParent) remove(base string) error {
if err := unix.Unlinkat(p.fd, base, 0); err != nil && !errors.Is(err, unix.ENOENT) {
return errUnsafePath
}
return nil
}
func (p *writeParent) rename(oldBase, newBase string) error {
if err := unix.Renameat(p.fd, oldBase, p.fd, newBase); err != nil {
return errUnsafePath
}
return nil
}
func (p *writeParent) sync() error {
if err := unix.Fsync(p.fd); err != nil {
return errUnsafePath
}
return nil
}
func (p *writeParent) revalidate() error {
current, err := openParentNoFollow(p.entry, p.relative, false)
if err != nil {
return errUnsafePath
}
defer current.close()
if current.device != p.device || current.inode != p.inode {
return errUnsafePath
}
return nil
}