e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
318 lines
7.2 KiB
Go
318 lines
7.2 KiB
Go
// Copyright 2025 Alibaba Group Holding Ltd.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//go:build windows
|
|
// +build windows
|
|
|
|
package controller
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/alibaba/opensandbox/execd/pkg/util/pathutil"
|
|
"github.com/alibaba/opensandbox/execd/pkg/web/model"
|
|
)
|
|
|
|
func DeleteFile(filePath string) error {
|
|
absPath, err := pathutil.ExpandAbsPath(filePath)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid path: %w", err)
|
|
}
|
|
|
|
fileInfo, err := os.Stat(absPath)
|
|
if err != nil {
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
if fileInfo.IsDir() {
|
|
return fmt.Errorf("path is a directory: %s", filePath)
|
|
}
|
|
|
|
if err := os.Remove(absPath); err != nil {
|
|
return fmt.Errorf("failed to remove file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ChmodFile(file string, perms model.Permission) error {
|
|
abs, err := pathutil.ExpandAbsPath(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if perms.Mode != 0 {
|
|
mode, err := strconv.ParseUint(strconv.Itoa(perms.Mode), 8, 32)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = os.Chmod(abs, os.FileMode(mode))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return SetFileOwnership(abs, perms.Owner, perms.Group)
|
|
}
|
|
|
|
// SetFileOwnership is a placeholder on Windows where POSIX ownership is not supported.
|
|
func SetFileOwnership(_ string, _ string, _ string) error {
|
|
// TODO: add Windows ACL support if needed.
|
|
return nil
|
|
}
|
|
|
|
func RenameFile(item model.RenameFileItem) error {
|
|
srcPath, err := pathutil.ExpandAbsPath(item.Src)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid source path: %w", err)
|
|
}
|
|
|
|
dstPath, err := pathutil.ExpandAbsPath(item.Dest)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid destination path: %w", err)
|
|
}
|
|
|
|
if _, err := os.Stat(srcPath); os.IsNotExist(err) {
|
|
return fmt.Errorf("source path not found: %s: %w", item.Src, err)
|
|
}
|
|
|
|
dstDir := filepath.Dir(dstPath)
|
|
|
|
if err := os.MkdirAll(dstDir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create destination directory: %w", err)
|
|
}
|
|
|
|
if _, err := os.Stat(dstPath); err == nil {
|
|
return fmt.Errorf("destination path already exists: %s", item.Dest)
|
|
}
|
|
|
|
if err := os.Rename(srcPath, dstPath); err != nil {
|
|
return fmt.Errorf("failed to rename file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MkdirAllWithOwnership creates targetDir and any missing parents, then applies
|
|
// owner/group only to the directories that were actually created (not pre-existing ones).
|
|
func MkdirAllWithOwnership(targetDir string, dirPerm os.FileMode, owner, group string) error {
|
|
firstNew := ""
|
|
cur := targetDir
|
|
for {
|
|
if _, err := os.Stat(cur); err == nil {
|
|
break
|
|
}
|
|
firstNew = cur
|
|
parent := filepath.Dir(cur)
|
|
if parent == cur {
|
|
break
|
|
}
|
|
cur = parent
|
|
}
|
|
|
|
if err := os.MkdirAll(targetDir, dirPerm); err != nil {
|
|
return err
|
|
}
|
|
|
|
if firstNew == "" || (owner == "" && group == "") {
|
|
return nil
|
|
}
|
|
|
|
rel, err := filepath.Rel(firstNew, targetDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
parts := strings.Split(rel, string(filepath.Separator))
|
|
cur = firstNew
|
|
if err := SetFileOwnership(cur, owner, group); err != nil {
|
|
return err
|
|
}
|
|
for _, p := range parts {
|
|
if p == "." {
|
|
continue
|
|
}
|
|
cur = filepath.Join(cur, p)
|
|
if err := SetFileOwnership(cur, owner, group); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func MakeDir(dir string, perm model.Permission) error {
|
|
abs, err := pathutil.ExpandAbsPath(dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, statErr := os.Stat(abs)
|
|
existed := statErr == nil
|
|
|
|
if err := MkdirAllWithOwnership(abs, os.ModePerm, perm.Owner, perm.Group); err != nil {
|
|
return err
|
|
}
|
|
|
|
if !existed && perm.Mode != 0 {
|
|
mode, err := strconv.ParseUint(strconv.Itoa(perm.Mode), 8, 32)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.Chmod(abs, os.FileMode(mode))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func fileType(fileInfo os.FileInfo) string {
|
|
mode := fileInfo.Mode()
|
|
if mode&os.ModeSymlink != 0 {
|
|
return "symlink"
|
|
}
|
|
if fileInfo.IsDir() {
|
|
return "directory"
|
|
}
|
|
if mode.IsRegular() {
|
|
return "file"
|
|
}
|
|
return "other"
|
|
}
|
|
|
|
func buildFileInfo(absPath string, fileInfo os.FileInfo) (model.FileInfo, error) {
|
|
createdAt := getFileCreateTime(fileInfo)
|
|
if data, ok := fileInfo.Sys().(*syscall.Win32FileAttributeData); ok && data != nil {
|
|
createdAt = time.Unix(0, data.CreationTime.Nanoseconds())
|
|
}
|
|
|
|
mode := strconv.FormatInt(int64(fileInfo.Mode().Perm()), 8)
|
|
|
|
return model.FileInfo{
|
|
Path: absPath,
|
|
Type: fileType(fileInfo),
|
|
Size: fileInfo.Size(),
|
|
ModifiedAt: fileInfo.ModTime(),
|
|
CreatedAt: createdAt,
|
|
Permission: model.Permission{
|
|
Owner: "",
|
|
Group: "",
|
|
Mode: func() int {
|
|
i, _ := strconv.Atoi(mode)
|
|
return i
|
|
}(),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func GetFileInfo(filePath string) (model.FileInfo, error) {
|
|
absPath, err := pathutil.ExpandAbsPath(filePath)
|
|
if err != nil {
|
|
return model.FileInfo{}, fmt.Errorf("invalid path %s: %w", filePath, err)
|
|
}
|
|
|
|
fileInfo, err := os.Lstat(absPath)
|
|
if err != nil {
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
return model.FileInfo{}, fmt.Errorf("file not found: %s: %w", filePath, err)
|
|
}
|
|
return model.FileInfo{}, fmt.Errorf("error accessing file %s: %w", filePath, err)
|
|
}
|
|
|
|
return buildFileInfo(absPath, fileInfo)
|
|
}
|
|
|
|
func SearchFileMetadata(metadata map[string]model.FileMetadata, filePath string) (string, model.FileMetadata, bool) {
|
|
base := filepath.Base(filePath)
|
|
for path, info := range metadata {
|
|
if filepath.Base(path) == base {
|
|
return path, info, true
|
|
}
|
|
}
|
|
|
|
return "", model.FileMetadata{}, false
|
|
}
|
|
|
|
type httpRange struct {
|
|
start, length int64
|
|
}
|
|
|
|
func ParseRange(s string, size int64) ([]httpRange, error) {
|
|
if !strings.HasPrefix(s, "bytes=") {
|
|
return nil, errors.New("invalid range")
|
|
}
|
|
|
|
ranges := strings.Split(s[6:], ",")
|
|
result := make([]httpRange, 0, len(ranges))
|
|
|
|
for _, ra := range ranges {
|
|
ra = strings.TrimSpace(ra)
|
|
if ra == "" {
|
|
continue
|
|
}
|
|
i := strings.Index(ra, "-")
|
|
if i < 0 {
|
|
return nil, errors.New("invalid range")
|
|
}
|
|
start, end := strings.TrimSpace(ra[:i]), strings.TrimSpace(ra[i+1:])
|
|
var r httpRange
|
|
|
|
if start == "" {
|
|
// suffix-length
|
|
n, err := strconv.ParseInt(end, 10, 64)
|
|
if err != nil || n < 0 {
|
|
return nil, errors.New("invalid range")
|
|
}
|
|
if n > size {
|
|
n = size
|
|
}
|
|
r.start = size - n
|
|
r.length = size - r.start
|
|
} else {
|
|
// start-end
|
|
i, err := strconv.ParseInt(start, 10, 64)
|
|
if err != nil || i < 0 {
|
|
return nil, errors.New("invalid range")
|
|
}
|
|
if end == "" {
|
|
// start-
|
|
r.start = i
|
|
r.length = size - i
|
|
} else {
|
|
// start-end
|
|
j, err := strconv.ParseInt(end, 10, 64)
|
|
if err != nil || j < i {
|
|
return nil, errors.New("invalid range")
|
|
}
|
|
r.start = i
|
|
r.length = j - i + 1
|
|
}
|
|
}
|
|
if r.start >= size {
|
|
continue
|
|
}
|
|
if r.start+r.length > size {
|
|
r.length = size - r.start
|
|
}
|
|
result = append(result, r)
|
|
}
|
|
return result, nil
|
|
}
|