package main import ( "errors" "log" "os" "strconv" "strings" "hakurei.app/check" "hakurei.app/fhs" "hakurei.app/hst" "hakurei.app/internal/env" "hakurei.app/internal/lockedfile" "hakurei.app/internal/outcome" ) // MutationConflictError describes an active mutable instance. type MutationConflictError string func (e MutationConflictError) Error() string { return "mutable instance active at " + string(e) } // informTemplate guards intention of a template or its derivatives. func informTemplate(base *check.Absolute, name string, mutable bool) (func() error, error) { mu := lockedfile.MutexAt(base.Append("lock", name).String()) if unlock, err := mu.Lock(); err != nil { return nil, err } else { defer unlock() } marker := base.Append("lock", "."+name) if p, err := os.ReadFile(marker.String()); err == nil { if _, err = os.Stat(fhs.AbsProc.Append(string(p)).String()); err == nil { return nil, MutationConflictError(p) } else if !errors.Is(err, os.ErrNotExist) { return nil, err } log.Printf("removing stale marker by %s", string(p)) if err = os.Remove(marker.String()); err != nil { return nil, err } } else if !errors.Is(err, os.ErrNotExist) { return nil, err } if !mutable { return nil, nil } var active []hst.ID var sc hst.Paths env.CopyPaths().Copy(&sc, new(outcome.Hsu).MustID(nil)) entries, copyError := outcome.NewStore(&sc).All() var s hst.State for eh := range entries { if _, err := eh.Load(&s); err != nil { return nil, err } if s.Validate(0) != nil || len(s.Container.Filesystem) < 1 { continue } root, ok := s.Container.Filesystem[0].FilesystemConfig.(*hst.FSOverlay) if !ok || root == nil { continue } if !root.Target.Is(fhs.AbsRoot) || len(root.Lower) != 1 || !root.Lower[0].Is(base.Append("initial")) || !root.Upper.Is(base.Append("template", name)) || root.Work != nil { continue } active = append(active, s.ID) } if err := copyError(); err != nil { return nil, err } if len(active) != 0 { var buf strings.Builder buf.WriteString("derivative instances still active:") for _, id := range active { buf.WriteString("\n\t") buf.WriteString(id.String()) } return nil, errors.New(buf.String()) } return func() error { return os.RemoveAll(marker.String()) }, os.WriteFile( marker.String(), []byte(strconv.Itoa(os.Getpid())), 0400, ) } // acquireTemplate obtains exclusivity of a template. func acquireTemplate(base *check.Absolute, name string) (remove func() error, err error) { return informTemplate(base, name, true) } // enterTemplate checks against exclusivity of a template. func enterTemplate(base *check.Absolute, name string) error { _, err := informTemplate(base, name, false) return err }