s6-netdev/s6.go

205 lines
4.3 KiB
Go

package s6netdev
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
)
type S6SvcTree struct {
s map[S6SvcName]*S6Svc
rcdir, envdir string
root string
}
type kv_file struct {
name, value string
perm int
}
type S6Svc struct {
Name S6SvcName
Type S6SvcType
Children []*S6Svc // Either dependencies or contents depending on type
ProducerFor, ConsumerFor *S6Svc
NotifFd int
FlagEssential bool
Up, Down, Run string
env map[string]string
}
type S6SvcName string
func (s S6SvcName) Sanitize() S6SvcName {
// Only sanitize slashes for now
return S6SvcName(strings.ReplaceAll(string(s), "/", "_"))
}
type S6SvcType *string
var S6SvcTypes = struct {
Oneshot, Longrun, Bundle string
}{
Oneshot: "oneshot",
Longrun: "longrun",
Bundle: "bundle",
}
func S6NewTree() *S6SvcTree {
t := new(S6SvcTree)
t.s = make(map[S6SvcName]*S6Svc)
t.root = "/etc/s6"
t.rcdir = "rc"
t.envdir = "env"
return t
}
func (t *S6SvcTree) S6New(name S6SvcName, svc_type S6SvcType) *S6Svc {
if name == "" || svc_type == nil {
return nil
}
if e, ok := t.s[name]; ok && e.Type == svc_type {
return e
}
s := new(S6Svc)
s.Name = name
s.Type = svc_type
t.s[name] = s
return s
}
func S6Pipe(src, dst *S6Svc) {
src.ProducerFor = dst
dst.ConsumerFor = src
}
func (s *S6Svc) S6Children(children ...*S6Svc) {
s.Children = append(s.Children, children...)
}
func (s *S6Svc) S6AddEnv(key, value string) {
s.env[key] = value
}
func (s *S6Svc) S6DelEnv(key string) {
delete(s.env, key)
}
func (t *S6SvcTree) S6AddSvc(s *S6Svc) {
t.s[s.Name] = s
}
func (t *S6SvcTree) S6GetSvc(name S6SvcName) *S6Svc {
return t.s[name]
}
func (t *S6SvcTree) S6Services() (r []*S6Svc) {
for _, v := range t.s {
r = append(r, v)
}
return
}
func (t *S6SvcTree) S6CommitService(s *S6Svc) (err error) {
if err = os.Mkdir(t.rcdir, fs.ModeDir|0777); err != nil && !errors.Is(err, os.ErrExist) {
return
}
sdir := filepath.Join(t.rcdir, string(s.Name))
if err = os.Mkdir(sdir, fs.ModeDir|0777); err != nil && !errors.Is(err, os.ErrExist) {
return
}
var kvfiles []kv_file
kvfiles = append(kvfiles, kv_file{"type", *s.Type, 0666})
if s.FlagEssential {
kvfiles = append(kvfiles, kv_file{"flag-essential", "", 0666})
}
if s.ProducerFor != nil {
kvfiles = append(kvfiles, kv_file{"producer-for", string(s.ProducerFor.Name), 0666})
}
if s.ConsumerFor != nil {
kvfiles = append(kvfiles, kv_file{"consumer-for", string(s.ConsumerFor.Name), 0666})
}
if s.NotifFd != 0 {
kvfiles = append(kvfiles, kv_file{"notification-fd", fmt.Sprintf("%d", s.NotifFd), 0666})
}
// Scripts
switch s.Type {
case &S6SvcTypes.Longrun:
{
if s.Run != "" {
kvfiles = append(kvfiles, kv_file{"run", s.Run, 0777})
}
}
case &S6SvcTypes.Oneshot:
{
if s.Up != "" {
kvfiles = append(kvfiles, kv_file{"up", s.Up, 0777})
}
if s.Down != "" {
kvfiles = append(kvfiles, kv_file{"down", s.Down, 0777})
}
}
}
for _, v := range kvfiles {
var f *os.File
if f, err = os.OpenFile(filepath.Join(sdir, v.name), os.O_CREATE|os.O_RDWR, fs.FileMode(v.perm)); err != nil {
return
}
_, err = fmt.Fprintln(f, v.value)
f.Close()
if err != nil {
return
}
}
// Children
var dirname string
switch s.Type {
case &S6SvcTypes.Bundle:
dirname = "contents.d"
default:
dirname = "dependencies.d"
}
cdir := filepath.Join(sdir, dirname)
if err = os.Mkdir(cdir, fs.ModeDir|0777); err != nil && !errors.Is(err, os.ErrExist) {
return
}
for _, v := range s.Children {
var f *os.File
if f, err = os.Create(filepath.Join(cdir, string(v.Name))); err != nil && !errors.Is(err, os.ErrExist) {
return
}
f.Close()
}
if len(s.env) != 0 {
if err = os.Mkdir(t.envdir, fs.ModeDir|0777); err != nil && !errors.Is(err, os.ErrExist) {
return
}
sdir := filepath.Join(t.envdir, string(s.Name))
if err = os.Mkdir(sdir, fs.ModeDir|0777); err != nil && !errors.Is(err, os.ErrExist) {
return
}
for k, v := range s.env {
var f *os.File
if f, err = os.Create(filepath.Join(sdir, string(k))); err != nil && !errors.Is(err, os.ErrExist) {
return
}
_, err = fmt.Fprintln(f, v)
f.Close()
if err != nil {
return
}
}
}
return
}