승인된 execution preset을 Edge 조정 경계와 Node workspace/tool 실행 경계로 연결해 단일 요청 수명주기와 관측 계약을 일관되게 처리한다.
186 lines
4.7 KiB
Go
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
|
|
}
|