carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "errors"
5 "fmt"
6 "log"
7 "os"
8 "path/filepath"
9 "time"
10
11 "github.com/carlosframework/platform/internal/registry"
12 "github.com/carlosframework/platform/internal/release"
13 "github.com/carlosframework/platform/internal/rootparticipation"
14)
15
16var errRestartBinding = errors.Join(errors.New("named-unit binding changed"), os.ErrPermission)
17
18// A retained census keeps the strict path even after the flag is disabled.
19// An unreadable census is never evidence that this is a legacy box.
20// The claims flag alone cannot select bound effects without a census.
21func legacyRootRestart() (bool, error) {
22 l, required, err := rootparticipation.OpenForUpdate()
23 if l.State != nil {
24 l.State.Close()
25 }
26 return !required && err == nil, err
27}
28
29func (c rootRestartChecks) allowLegacy() (bool, error) {
30 if c.legacy == nil {
31 return false, nil
32 }
33 return c.legacy()
34}
35
36func (c rootRestartChecks) noteLegacy() {
37 if c.legacyLog != nil {
38 c.legacyLog.Do(func() { log.Print("adopt: legacy restart (no root census)") })
39 }
40}
41
42func restartEffectFailed(err error) bool {
43 var notUp *release.UnitNotUpError
44 return errors.As(err, &notUp)
45}
46
47// This is main's stamped-unit path, including its fallback on Show or
48// ExecStart mismatch. Binding cannot drive effects without a root census.
49func legacyCheckedUnitRestart(sys unitSystem, optDir string, getenv func(string) string, rt registry.Route, settled func(registry.Route) error, c rootRestartChecks) error {
50 before, err := sys.Show(rt.Unit)
51 link := release.LiveLink(optDir, rt.Host)
52 if err != nil {
53 log.Printf("adopt: %s: cannot read %s (%v); restart not checked", rt.Host, rt.Unit, err)
54 return sys.TryRestart(rt.Unit)
55 }
56 if before.ExecPath != link {
57 log.Printf("adopt: binding: %s runs %s, but the unit stamp is on %s; restart not checked", rt.Unit, before.ExecPath, rt.Host)
58 return sys.TryRestart(rt.Unit)
59 }
60 if before.ActiveState != "active" {
61 return sys.TryRestart(rt.Unit)
62 }
63 want, err := filepath.EvalSymlinks(link)
64 if err != nil {
65 return sys.TryRestart(rt.Unit)
66 }
67 if err := sys.TryRestart(rt.Unit); err != nil {
68 return err
69 }
70 err = settleUnit(sys, rt.Unit, before.InvocationID, legacySettleOptions(optDir, getenv, rt, want, c))
71 if errors.Is(err, errUnitStopped) {
72 log.Printf("adopt: %s: %s was stopped during settle; not judged, not restarted", rt.Host, rt.Unit)
73 return nil
74 }
75 if err == nil {
76 log.Printf("adopt: %s: %s settled on %s", rt.Host, rt.Unit, filepath.Dir(want))
77 if settled != nil {
78 if e := settled(rt); e != nil {
79 log.Print(e)
80 }
81 }
82 }
83 return err
84}
85
86func legacyRollbackRestart(sys unitSystem, optDir string, getenv func(string) string, rt registry.Route, c rootRestartChecks) error {
87 before, _ := sys.Show(rt.Unit)
88 if err := sys.ResetFailed(rt.Unit); err != nil {
89 log.Printf("adopt: %s: reset-failed %s: %v (continuing)", rt.Host, rt.Unit, err)
90 }
91 if err := sys.Restart(rt.Unit); err != nil {
92 return err
93 }
94 want, err := filepath.EvalSymlinks(release.LiveLink(optDir, rt.Host))
95 if err != nil {
96 return err
97 }
98 err = settleUnit(sys, rt.Unit, before.InvocationID, legacySettleOptions(optDir, getenv, rt, want, c))
99 if errors.Is(err, errUnitStopped) {
100 log.Printf("adopt: %s: %s was stopped during the rollback's settle; not judged, not restarted", rt.Host, rt.Unit)
101 return nil
102 }
103 if err != nil {
104 return fmt.Errorf("rollback did not come up either: %w", err)
105 }
106 return nil
107}
108
109func legacySettleOptions(optDir string, getenv func(string) string, rt registry.Route, want string, c rootRestartChecks) settleOpts {
110 now, sleep := c.now, c.sleep
111 if now == nil {
112 now = time.Now
113 }
114 if sleep == nil {
115 sleep = time.Sleep
116 }
117 ready := routeReady(rt, 2*time.Second)
118 if c.legacyReady != nil {
119 ready = c.legacyReady(rt)
120 }
121 return settleOpts{Timeout: unitSettleTimeout(getenv), Poll: 500 * time.Millisecond, Hold: 5 * time.Second, WantDir: filepath.Dir(want), Ready: ready, Now: now, Sleep: sleep}
122}
123