sandbox/util.go
Star f9dcf07ba4 first version
supported macOS、linux
2026-03-23 00:35:27 +08:00

191 lines
4.1 KiB
Go

// util.go v1.0
package sandbox
import (
"fmt"
"io"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"github.com/ssgo/log"
"github.com/ssgo/u"
)
var currentIds = map[string]bool{}
var idLock sync.Mutex
func getId() string {
id := u.Id8()
idLock.Lock()
defer idLock.Unlock()
for currentIds[id] {
id = u.Id8()
}
currentIds[id] = true
return id
}
func releaseId(id string) {
idLock.Lock()
defer idLock.Unlock()
delete(currentIds, id)
}
func (s *Sandbox) assertLog(title string, err error, extra ...any) {
if s.config.NoLog {
return
}
if err != nil {
extra = append(extra, "err", err.Error())
log.DefaultLogger.Info("[Sandbox] "+title+" failed", extra...)
} else {
log.DefaultLogger.Info("[Sandbox] "+title+" success", extra...)
}
}
func (s *Sandbox) log(title string, extra ...any) {
if s.config.NoLog {
return
}
log.DefaultLogger.Info("[Sandbox] "+title, extra...)
}
type Volumes struct {
volumes []Volume
indexByTarget map[string]int
}
func (v *Volumes) Add(vv ...Volume) {
for _, v1 := range vv {
if v1.Target == "" {
v1.Target = v1.Source
}
if v1.Target == "/proc" || v1.Target == "/sys" {
continue
}
if _, ok := v.indexByTarget[v1.Target]; ok {
v.volumes[v.indexByTarget[v1.Target]] = v1
} else {
v.volumes = append(v.volumes, v1)
v.indexByTarget[v1.Target] = len(v.volumes) - 1
}
}
}
func (v *Volumes) Get() []Volume {
return v.volumes
}
func NewVolumes() *Volumes {
return &Volumes{
volumes: []Volume{},
indexByTarget: map[string]int{},
}
}
func copyLog(workDir string, startTime int64) (int, error) {
logDate := time.Unix(startTime, 0).Format("20060102")
lastLogFile := filepath.Join(workDir, "stdout.log")
errorLogFile := filepath.Join(workDir, "stderr.log")
lastLogDest := filepath.Join(workDir, "logs", "stdout_"+logDate+".log")
errorLogDest := filepath.Join(workDir, "logs", "stderr_"+logDate+".log")
ok := 0
var err1 error
if !u.FileExists(lastLogDest) {
if err := u.CopyFile(lastLogFile, lastLogDest); err == nil {
ok++
} else {
err1 = err
}
} else {
if to, err := os.OpenFile(lastLogDest, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644); err == nil {
defer to.Close()
if from, err := os.Open(lastLogFile); err == nil {
defer from.Close()
to.WriteString("\n")
if _, err := io.Copy(to, from); err == nil {
ok++
} else {
err1 = err
}
}
}
}
if !u.FileExists(errorLogDest) {
if err := u.CopyFile(errorLogFile, errorLogDest); err == nil {
ok++
} else {
err1 = err
}
} else {
if to, err := os.OpenFile(errorLogDest, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644); err == nil {
defer to.Close()
if from, err := os.Open(errorLogFile); err == nil {
defer from.Close()
to.WriteString("\n")
if _, err := io.Copy(to, from); err == nil {
ok++
} else {
err1 = err
}
}
}
}
return ok, err1
}
type NetRule struct {
IP [16]byte // 统一 IPv6 长度
Mask int8 // -1 代表不校验掩码 (单IP), 0-128 代表掩码位
Port uint16 // 0 代表所有端口
IsV6 uint8 // 0: v4, 1: v6
}
func ParseNetRule(s string) (*NetRule, error) {
rule := &NetRule{Mask: -1}
hostStr := s
// 1. 处理端口 (从后往前找最后一个冒号,且排除 IPv6 的冒号)
if lastColon := strings.LastIndex(s, ":"); lastColon != -1 && !strings.HasSuffix(s, "]") {
// 检查冒号后面是否是纯数字(端口),如果不是,说明是 IPv6 地址的一部分
if port, err := strconv.Atoi(s[lastColon+1:]); err == nil {
rule.Port = uint16(port)
hostStr = s[:lastColon]
}
}
// 2. 处理 CIDR
ipStr := hostStr
if strings.Contains(hostStr, "/") {
_, ipNet, err := net.ParseCIDR(hostStr)
if err != nil {
return nil, err
}
ones, _ := ipNet.Mask.Size()
rule.Mask = int8(ones)
ipStr = ipNet.IP.String()
}
// 3. 处理 IP (去掉 IPv6 的方括号)
ipStr = strings.Trim(ipStr, "[]")
ip := net.ParseIP(ipStr)
if ip == nil {
return nil, fmt.Errorf("invalid ip: %s", ipStr)
}
if ip4 := ip.To4(); ip4 != nil {
copy(rule.IP[:4], ip4)
rule.IsV6 = 0
} else {
copy(rule.IP[:], ip.To16())
rule.IsV6 = 1
}
return rule, nil
}