93 lines
2.4 KiB
Go
93 lines
2.4 KiB
Go
package tentacle
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
// LoadLocalSpecs reads an explicit development launch list, not a manifest or
|
|
// installation catalog. It does not execute code or discover directories.
|
|
func LoadLocalSpecs(path string) ([]Spec, error) {
|
|
file, err := os.OpenFile(path, os.O_RDONLY|syscall.O_NONBLOCK, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
info, err := file.Stat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !info.Mode().IsRegular() {
|
|
return nil, fmt.Errorf("local tentacle list must be a regular file")
|
|
}
|
|
data, err := io.ReadAll(io.LimitReader(file, MaxFrameBytes+1))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(data) > MaxFrameBytes {
|
|
return nil, ErrLimit
|
|
}
|
|
if err := validate_json(data); err != nil {
|
|
return nil, err
|
|
}
|
|
var specs []Spec
|
|
decoder := json.NewDecoder(bytes.NewReader(data))
|
|
decoder.DisallowUnknownFields()
|
|
if err := decoder.Decode(&specs); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(specs) > DefaultLimits().Processes {
|
|
return nil, ErrLimit
|
|
}
|
|
seen := make(map[string]bool)
|
|
for _, spec := range specs {
|
|
if err := validate_spec(spec); err != nil {
|
|
return nil, err
|
|
}
|
|
if seen[spec.ID] {
|
|
return nil, fmt.Errorf("duplicate local tentacle ID")
|
|
}
|
|
seen[spec.ID] = true
|
|
}
|
|
return specs, nil
|
|
}
|
|
|
|
// DelegatedParent enables controllers only within the launcher's explicitly
|
|
// delegated unit. The reader already lives in its own leaf; it is never moved
|
|
// here. Other launchers can supply an already configured absolute parent.
|
|
func DelegatedParent() (string, error) {
|
|
data, err := os.ReadFile("/proc/self/cgroup")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
parent, err := delegated_parent(string(data))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if err := write_control(parent, "cgroup.subtree_control", "+cpu +memory +pids"); err != nil {
|
|
return "", err
|
|
}
|
|
return parent, nil
|
|
}
|
|
|
|
func delegated_parent(membership string) (string, error) {
|
|
for _, line := range strings.Split(membership, "\n") {
|
|
if !strings.HasPrefix(line, "0::/") {
|
|
continue
|
|
}
|
|
path := strings.TrimPrefix(line, "0::")
|
|
if filepath.Clean(path) != path || filepath.Base(path) != "reader" ||
|
|
!strings.HasSuffix(filepath.Dir(path), ".service") {
|
|
break
|
|
}
|
|
return filepath.Join("/sys/fs/cgroup", filepath.Dir(path)), nil
|
|
}
|
|
return "", fmt.Errorf("%w: reader requires a delegated service with DelegateSubgroup=reader", ErrContainment)
|
|
}
|