mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-12 22:14:37 +00:00
* feat(collaboration): isolate delegated artifacts and child runs Isolate delegated inputs and outputs behind secure artifact exchange lifecycles. Scope Agent Link tasks by tenant and root agent, and enforce delegation spawn-tree boundaries. Add process-wide child-run admission and preserve logical media paths across native, MCP, and sandbox execution. * fix(collaboration): harden delegated task isolation Enforce tenant and root-agent task scope across migrations and stores. Add exactly-once async completion delivery, delegated sandbox boundaries, and confined artifact and media recovery across runtime surfaces. * fix(collaboration): recover interrupted async tasks * fix(collaboration): normalize persisted child-run status --------- Co-authored-by: ntduc <ntduc@cpp.ai.vn>
575 lines
15 KiB
Go
575 lines
15 KiB
Go
//go:build windows
|
|
|
|
package tools
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
type artifactSecureRoot struct {
|
|
handle windows.Handle
|
|
}
|
|
|
|
type artifactFileRenameInformation struct {
|
|
ReplaceIfExists uint32
|
|
RootDirectory windows.Handle
|
|
FileNameLength uint32
|
|
FileName [1]uint16
|
|
}
|
|
|
|
func openArtifactSecureRoot(hostPath string) (*artifactSecureRoot, error) {
|
|
absolute, err := filepath.Abs(hostPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
handle, err := artifactNtOpen(
|
|
0,
|
|
artifactNTAbsolutePath(absolute),
|
|
windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.DELETE|windows.SYNCHRONIZE,
|
|
windows.FILE_OPEN,
|
|
windows.FILE_DIRECTORY_FILE|windows.FILE_OPEN_REPARSE_POINT,
|
|
0,
|
|
)
|
|
if err != nil {
|
|
return nil, translateArtifactWindowsError(err)
|
|
}
|
|
if err := rejectArtifactReparsePoint(handle); err != nil {
|
|
windows.CloseHandle(handle)
|
|
return nil, err
|
|
}
|
|
return &artifactSecureRoot{handle: handle}, nil
|
|
}
|
|
|
|
func (r *artifactSecureRoot) close() error {
|
|
if r == nil || r.handle == 0 || r.handle == windows.InvalidHandle {
|
|
return nil
|
|
}
|
|
handle := r.handle
|
|
r.handle = windows.InvalidHandle
|
|
return windows.CloseHandle(handle)
|
|
}
|
|
|
|
func (r *artifactSecureRoot) mkdirAll(relativePath string, _ fs.FileMode) error {
|
|
components, err := artifactPathComponentsWindows(relativePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
current, err := duplicateArtifactHandle(r.handle)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer windows.CloseHandle(current)
|
|
for _, component := range components {
|
|
next, err := artifactNtOpen(
|
|
current,
|
|
component,
|
|
windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.DELETE|windows.SYNCHRONIZE,
|
|
windows.FILE_OPEN_IF,
|
|
windows.FILE_DIRECTORY_FILE|windows.FILE_OPEN_REPARSE_POINT,
|
|
windows.FILE_ATTRIBUTE_NORMAL,
|
|
)
|
|
if err != nil {
|
|
return translateArtifactWindowsError(err)
|
|
}
|
|
if err := rejectArtifactReparsePoint(next); err != nil {
|
|
windows.CloseHandle(next)
|
|
return err
|
|
}
|
|
windows.CloseHandle(current)
|
|
current = next
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *artifactSecureRoot) createSubroot(relativePath string, _ fs.FileMode) (*artifactSecureRoot, error) {
|
|
parent, name, err := r.openParent(relativePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer windows.CloseHandle(parent)
|
|
handle, err := artifactNtOpen(
|
|
parent,
|
|
name,
|
|
windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.DELETE|windows.SYNCHRONIZE,
|
|
windows.FILE_CREATE,
|
|
windows.FILE_DIRECTORY_FILE|windows.FILE_OPEN_REPARSE_POINT,
|
|
windows.FILE_ATTRIBUTE_NORMAL,
|
|
)
|
|
if err != nil {
|
|
return nil, translateArtifactWindowsError(err)
|
|
}
|
|
if err := rejectArtifactReparsePoint(handle); err != nil {
|
|
windows.CloseHandle(handle)
|
|
return nil, err
|
|
}
|
|
return &artifactSecureRoot{handle: handle}, nil
|
|
}
|
|
|
|
func (r *artifactSecureRoot) openSubroot(relativePath string) (*artifactSecureRoot, error) {
|
|
handle, err := r.openDir(relativePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &artifactSecureRoot{handle: handle}, nil
|
|
}
|
|
|
|
func (r *artifactSecureRoot) createFile(relativePath string, _ fs.FileMode) (*os.File, error) {
|
|
parent, name, err := r.openParent(relativePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer windows.CloseHandle(parent)
|
|
handle, err := artifactNtOpen(
|
|
parent,
|
|
name,
|
|
windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.DELETE|windows.SYNCHRONIZE,
|
|
windows.FILE_CREATE,
|
|
windows.FILE_NON_DIRECTORY_FILE|windows.FILE_OPEN_REPARSE_POINT,
|
|
windows.FILE_ATTRIBUTE_NORMAL,
|
|
)
|
|
if err != nil {
|
|
return nil, translateArtifactWindowsError(err)
|
|
}
|
|
if err := rejectArtifactReparsePoint(handle); err != nil {
|
|
windows.CloseHandle(handle)
|
|
return nil, err
|
|
}
|
|
return os.NewFile(uintptr(handle), name), nil
|
|
}
|
|
|
|
func (r *artifactSecureRoot) openEntry(relativePath string) (*artifactSecureEntry, error) {
|
|
parent, name, err := r.openParent(relativePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer windows.CloseHandle(parent)
|
|
handle, err := artifactNtOpen(
|
|
parent,
|
|
name,
|
|
windows.FILE_GENERIC_READ|windows.SYNCHRONIZE,
|
|
windows.FILE_OPEN,
|
|
windows.FILE_OPEN_REPARSE_POINT,
|
|
0,
|
|
)
|
|
if err != nil {
|
|
return nil, translateArtifactWindowsError(err)
|
|
}
|
|
var info windows.ByHandleFileInformation
|
|
if err := windows.GetFileInformationByHandle(handle, &info); err != nil {
|
|
windows.CloseHandle(handle)
|
|
return nil, err
|
|
}
|
|
if info.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
|
|
windows.CloseHandle(handle)
|
|
return nil, errArtifactReparsePoint
|
|
}
|
|
kind := artifactEntryRegular
|
|
if info.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
|
kind = artifactEntryDirectory
|
|
}
|
|
size := int64(uint64(info.FileSizeHigh)<<32 | uint64(info.FileSizeLow))
|
|
return &artifactSecureEntry{
|
|
file: os.NewFile(uintptr(handle), name),
|
|
kind: kind,
|
|
size: size,
|
|
links: uint64(info.NumberOfLinks),
|
|
}, nil
|
|
}
|
|
|
|
func (r *artifactSecureRoot) readDir(relativePath string) ([]string, error) {
|
|
entry, err := r.openEntry(relativePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer entry.close()
|
|
if entry.kind != artifactEntryDirectory {
|
|
return nil, ErrArtifactNonRegular
|
|
}
|
|
|
|
names := make([]string, 0, artifactSecureReadBatchSize)
|
|
for {
|
|
dirEntries, readErr := entry.readDirBatch()
|
|
if readErr != nil && !errors.Is(readErr, io.EOF) {
|
|
return nil, readErr
|
|
}
|
|
if len(dirEntries) > artifactSecureMaxDirectoryEntries-len(names) {
|
|
return nil, ErrArtifactLimitExceeded
|
|
}
|
|
for _, dirEntry := range dirEntries {
|
|
names = append(names, dirEntry.Name())
|
|
}
|
|
if errors.Is(readErr, io.EOF) {
|
|
return names, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *artifactSecureRoot) exists(relativePath string) (bool, error) {
|
|
parent, name, err := r.openParent(relativePath)
|
|
if err != nil {
|
|
if isArtifactWindowsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
defer windows.CloseHandle(parent)
|
|
handle, err := artifactNtOpen(
|
|
parent,
|
|
name,
|
|
windows.FILE_READ_ATTRIBUTES|windows.SYNCHRONIZE,
|
|
windows.FILE_OPEN,
|
|
windows.FILE_OPEN_REPARSE_POINT,
|
|
0,
|
|
)
|
|
if err != nil {
|
|
if isArtifactWindowsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
return false, translateArtifactWindowsError(err)
|
|
}
|
|
windows.CloseHandle(handle)
|
|
return true, nil
|
|
}
|
|
|
|
func (r *artifactSecureRoot) syncDir(relativePath string) error {
|
|
handle, err := r.openDir(relativePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer windows.CloseHandle(handle)
|
|
err = windows.FlushFileBuffers(handle)
|
|
if errors.Is(err, windows.ERROR_ACCESS_DENIED) || errors.Is(err, windows.ERROR_INVALID_HANDLE) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (r *artifactSecureRoot) removeTree(relativePath string) error {
|
|
parent, name, err := r.openParent(relativePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer windows.CloseHandle(parent)
|
|
remaining := artifactSecureCleanupEntryBudget
|
|
return removeArtifactTreeAt(parent, name, 0, &remaining)
|
|
}
|
|
|
|
func removeArtifactTreeAt(parent windows.Handle, name string, depth int, remaining *int) error {
|
|
if depth > artifactSecureMaxDepth {
|
|
return ErrArtifactLimitExceeded
|
|
}
|
|
if *remaining <= 0 {
|
|
return ErrArtifactLimitExceeded
|
|
}
|
|
*remaining--
|
|
handle, err := artifactNtOpen(
|
|
parent,
|
|
name,
|
|
windows.FILE_GENERIC_READ|windows.DELETE|windows.SYNCHRONIZE,
|
|
windows.FILE_OPEN,
|
|
windows.FILE_OPEN_REPARSE_POINT,
|
|
0,
|
|
)
|
|
if err != nil {
|
|
if isArtifactWindowsNotExist(err) {
|
|
return nil
|
|
}
|
|
return translateArtifactWindowsError(err)
|
|
}
|
|
defer windows.CloseHandle(handle)
|
|
|
|
var info windows.ByHandleFileInformation
|
|
if err := windows.GetFileInformationByHandle(handle, &info); err != nil {
|
|
return err
|
|
}
|
|
isDirectory := info.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY != 0
|
|
isReparsePoint := info.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0
|
|
if isDirectory && !isReparsePoint {
|
|
duplicate, err := duplicateArtifactHandle(handle)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dir := os.NewFile(uintptr(duplicate), name)
|
|
for {
|
|
entries, readErr := dir.ReadDir(artifactSecureReadBatchSize)
|
|
if readErr != nil && !errors.Is(readErr, io.EOF) {
|
|
_ = dir.Close()
|
|
return readErr
|
|
}
|
|
for _, entry := range entries {
|
|
if err := removeArtifactTreeAt(handle, entry.Name(), depth+1, remaining); err != nil {
|
|
_ = dir.Close()
|
|
return err
|
|
}
|
|
}
|
|
if errors.Is(readErr, io.EOF) {
|
|
break
|
|
}
|
|
}
|
|
if err := dir.Close(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
deleteFlag := byte(1)
|
|
return windows.SetFileInformationByHandle(
|
|
handle,
|
|
windows.FileDispositionInfo,
|
|
&deleteFlag,
|
|
1,
|
|
)
|
|
}
|
|
|
|
func (r *artifactSecureRoot) renameNoReplace(oldName, newName string) error {
|
|
return r.rename(oldName, newName, false)
|
|
}
|
|
|
|
func (r *artifactSecureRoot) renameReplace(oldName, newName string) error {
|
|
return r.rename(oldName, newName, true)
|
|
}
|
|
|
|
func (r *artifactSecureRoot) rename(oldName, newName string, replace bool) error {
|
|
oldPath, err := validateArtifactRelativePath(oldName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newPath, err := validateArtifactRelativePath(newName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if path.Dir(oldPath) != "." || path.Dir(newPath) != "." {
|
|
return ErrArtifactInvalidPath
|
|
}
|
|
source, err := artifactNtOpen(
|
|
r.handle,
|
|
oldPath,
|
|
windows.FILE_GENERIC_READ|windows.DELETE|windows.SYNCHRONIZE,
|
|
windows.FILE_OPEN,
|
|
windows.FILE_OPEN_REPARSE_POINT,
|
|
0,
|
|
)
|
|
if err != nil {
|
|
return translateArtifactWindowsError(err)
|
|
}
|
|
defer windows.CloseHandle(source)
|
|
if err := rejectArtifactReparsePoint(source); err != nil {
|
|
return err
|
|
}
|
|
|
|
newNameUTF16, err := windows.UTF16FromString(newPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nameBytes := (len(newNameUTF16) - 1) * 2
|
|
var layout artifactFileRenameInformation
|
|
bufferSize := int(unsafe.Offsetof(layout.FileName)) + nameBytes
|
|
buffer := make([]byte, bufferSize)
|
|
info := (*artifactFileRenameInformation)(unsafe.Pointer(&buffer[0]))
|
|
if replace {
|
|
info.ReplaceIfExists = 1
|
|
}
|
|
info.RootDirectory = r.handle
|
|
info.FileNameLength = uint32(nameBytes)
|
|
copy(
|
|
(*[windows.MAX_LONG_PATH]uint16)(unsafe.Pointer(&info.FileName[0]))[:nameBytes/2:nameBytes/2],
|
|
newNameUTF16[:len(newNameUTF16)-1],
|
|
)
|
|
var iosb windows.IO_STATUS_BLOCK
|
|
err = windows.NtSetInformationFile(
|
|
source,
|
|
&iosb,
|
|
&buffer[0],
|
|
uint32(bufferSize),
|
|
windows.FileRenameInformation,
|
|
)
|
|
if !replace && isArtifactAlreadyExists(err) {
|
|
return errors.Join(ErrArtifactPublishConflict, err)
|
|
}
|
|
return translateArtifactWindowsError(err)
|
|
}
|
|
|
|
func (r *artifactSecureRoot) openParent(relativePath string) (windows.Handle, string, error) {
|
|
components, err := artifactPathComponentsWindows(relativePath)
|
|
if err != nil {
|
|
return windows.InvalidHandle, "", err
|
|
}
|
|
if len(components) == 0 {
|
|
return windows.InvalidHandle, "", ErrArtifactInvalidPath
|
|
}
|
|
current, err := duplicateArtifactHandle(r.handle)
|
|
if err != nil {
|
|
return windows.InvalidHandle, "", err
|
|
}
|
|
for _, component := range components[:len(components)-1] {
|
|
next, err := artifactNtOpen(
|
|
current,
|
|
component,
|
|
windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.DELETE|windows.SYNCHRONIZE,
|
|
windows.FILE_OPEN,
|
|
windows.FILE_DIRECTORY_FILE|windows.FILE_OPEN_REPARSE_POINT,
|
|
0,
|
|
)
|
|
if err != nil {
|
|
windows.CloseHandle(current)
|
|
return windows.InvalidHandle, "", translateArtifactWindowsError(err)
|
|
}
|
|
if err := rejectArtifactReparsePoint(next); err != nil {
|
|
windows.CloseHandle(next)
|
|
windows.CloseHandle(current)
|
|
return windows.InvalidHandle, "", err
|
|
}
|
|
windows.CloseHandle(current)
|
|
current = next
|
|
}
|
|
return current, components[len(components)-1], nil
|
|
}
|
|
|
|
func (r *artifactSecureRoot) openDir(relativePath string) (windows.Handle, error) {
|
|
if relativePath == "." {
|
|
return duplicateArtifactHandle(r.handle)
|
|
}
|
|
components, err := artifactPathComponentsWindows(relativePath)
|
|
if err != nil {
|
|
return windows.InvalidHandle, err
|
|
}
|
|
current, err := duplicateArtifactHandle(r.handle)
|
|
if err != nil {
|
|
return windows.InvalidHandle, err
|
|
}
|
|
for _, component := range components {
|
|
next, err := artifactNtOpen(
|
|
current,
|
|
component,
|
|
windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.DELETE|windows.SYNCHRONIZE,
|
|
windows.FILE_OPEN,
|
|
windows.FILE_DIRECTORY_FILE|windows.FILE_OPEN_REPARSE_POINT,
|
|
0,
|
|
)
|
|
if err != nil {
|
|
windows.CloseHandle(current)
|
|
return windows.InvalidHandle, translateArtifactWindowsError(err)
|
|
}
|
|
if err := rejectArtifactReparsePoint(next); err != nil {
|
|
windows.CloseHandle(next)
|
|
windows.CloseHandle(current)
|
|
return windows.InvalidHandle, err
|
|
}
|
|
windows.CloseHandle(current)
|
|
current = next
|
|
}
|
|
return current, nil
|
|
}
|
|
|
|
func artifactNtOpen(
|
|
root windows.Handle,
|
|
name string,
|
|
access uint32,
|
|
disposition uint32,
|
|
options uint32,
|
|
attributes uint32,
|
|
) (windows.Handle, error) {
|
|
objectName, err := windows.NewNTUnicodeString(name)
|
|
if err != nil {
|
|
return windows.InvalidHandle, err
|
|
}
|
|
oa := &windows.OBJECT_ATTRIBUTES{
|
|
Length: uint32(unsafe.Sizeof(windows.OBJECT_ATTRIBUTES{})),
|
|
RootDirectory: root,
|
|
ObjectName: objectName,
|
|
Attributes: windows.OBJ_CASE_INSENSITIVE,
|
|
}
|
|
var handle windows.Handle
|
|
var iosb windows.IO_STATUS_BLOCK
|
|
var allocationSize int64
|
|
err = windows.NtCreateFile(
|
|
&handle,
|
|
access,
|
|
oa,
|
|
&iosb,
|
|
&allocationSize,
|
|
attributes,
|
|
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
|
|
disposition,
|
|
options,
|
|
0,
|
|
0,
|
|
)
|
|
return handle, err
|
|
}
|
|
|
|
func duplicateArtifactHandle(source windows.Handle) (windows.Handle, error) {
|
|
var target windows.Handle
|
|
err := windows.DuplicateHandle(
|
|
windows.CurrentProcess(),
|
|
source,
|
|
windows.CurrentProcess(),
|
|
&target,
|
|
0,
|
|
false,
|
|
windows.DUPLICATE_SAME_ACCESS,
|
|
)
|
|
return target, err
|
|
}
|
|
|
|
func rejectArtifactReparsePoint(handle windows.Handle) error {
|
|
var info windows.ByHandleFileInformation
|
|
if err := windows.GetFileInformationByHandle(handle, &info); err != nil {
|
|
return err
|
|
}
|
|
if info.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
|
|
return errArtifactReparsePoint
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func artifactNTAbsolutePath(absolute string) string {
|
|
if strings.HasPrefix(absolute, `\\`) {
|
|
return `\??\UNC\` + strings.TrimPrefix(absolute, `\\`)
|
|
}
|
|
return `\??\` + absolute
|
|
}
|
|
|
|
func artifactPathComponentsWindows(relativePath string) ([]string, error) {
|
|
if relativePath == "." {
|
|
return nil, nil
|
|
}
|
|
clean, err := validateArtifactRelativePath(relativePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return strings.Split(clean, "/"), nil
|
|
}
|
|
|
|
func translateArtifactWindowsError(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
func isArtifactWindowsNotExist(err error) bool {
|
|
return errors.Is(err, windows.ERROR_FILE_NOT_FOUND) ||
|
|
errors.Is(err, windows.ERROR_PATH_NOT_FOUND) ||
|
|
errors.Is(err, windows.STATUS_OBJECT_NAME_NOT_FOUND) ||
|
|
errors.Is(err, windows.STATUS_OBJECT_PATH_NOT_FOUND)
|
|
}
|
|
|
|
func isArtifactAlreadyExists(err error) bool {
|
|
return errors.Is(err, windows.ERROR_ALREADY_EXISTS) ||
|
|
errors.Is(err, windows.ERROR_FILE_EXISTS) ||
|
|
errors.Is(err, windows.STATUS_OBJECT_NAME_COLLISION) ||
|
|
errors.Is(err, ErrArtifactPublishConflict)
|
|
}
|
|
|
|
func isArtifactNotExist(err error) bool {
|
|
return isArtifactWindowsNotExist(err)
|
|
}
|