package host import ( "errors" "fmt" "log" "os" "path/filepath" "time" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rootparticipation" ) var errRestartBinding = errors.Join(errors.New("named-unit binding changed"), os.ErrPermission) // A retained census keeps the strict path even after the flag is disabled. // An unreadable census is never evidence that this is a legacy box. // The claims flag alone cannot select bound effects without a census. func legacyRootRestart() (bool, error) { l, required, err := rootparticipation.OpenForUpdate() if l.State != nil { l.State.Close() } return !required && err == nil, err } func (c rootRestartChecks) allowLegacy() (bool, error) { if c.legacy == nil { return false, nil } return c.legacy() } func (c rootRestartChecks) noteLegacy() { if c.legacyLog != nil { c.legacyLog.Do(func() { log.Print("adopt: legacy restart (no root census)") }) } } func restartEffectFailed(err error) bool { var notUp *release.UnitNotUpError return errors.As(err, ¬Up) } // This is main's stamped-unit path, including its fallback on Show or // ExecStart mismatch. Binding cannot drive effects without a root census. func legacyCheckedUnitRestart(sys unitSystem, optDir string, getenv func(string) string, rt registry.Route, settled func(registry.Route) error, c rootRestartChecks) error { before, err := sys.Show(rt.Unit) link := release.LiveLink(optDir, rt.Host) if err != nil { log.Printf("adopt: %s: cannot read %s (%v); restart not checked", rt.Host, rt.Unit, err) return sys.TryRestart(rt.Unit) } if before.ExecPath != link { log.Printf("adopt: binding: %s runs %s, but the unit stamp is on %s; restart not checked", rt.Unit, before.ExecPath, rt.Host) return sys.TryRestart(rt.Unit) } if before.ActiveState != "active" { return sys.TryRestart(rt.Unit) } want, err := filepath.EvalSymlinks(link) if err != nil { return sys.TryRestart(rt.Unit) } if err := sys.TryRestart(rt.Unit); err != nil { return err } err = settleUnit(sys, rt.Unit, before.InvocationID, legacySettleOptions(optDir, getenv, rt, want, c)) if errors.Is(err, errUnitStopped) { log.Printf("adopt: %s: %s was stopped during settle; not judged, not restarted", rt.Host, rt.Unit) return nil } if err == nil { log.Printf("adopt: %s: %s settled on %s", rt.Host, rt.Unit, filepath.Dir(want)) if settled != nil { if e := settled(rt); e != nil { log.Print(e) } } } return err } func legacyRollbackRestart(sys unitSystem, optDir string, getenv func(string) string, rt registry.Route, c rootRestartChecks) error { before, _ := sys.Show(rt.Unit) if err := sys.ResetFailed(rt.Unit); err != nil { log.Printf("adopt: %s: reset-failed %s: %v (continuing)", rt.Host, rt.Unit, err) } if err := sys.Restart(rt.Unit); err != nil { return err } want, err := filepath.EvalSymlinks(release.LiveLink(optDir, rt.Host)) if err != nil { return err } err = settleUnit(sys, rt.Unit, before.InvocationID, legacySettleOptions(optDir, getenv, rt, want, c)) if errors.Is(err, errUnitStopped) { log.Printf("adopt: %s: %s was stopped during the rollback's settle; not judged, not restarted", rt.Host, rt.Unit) return nil } if err != nil { return fmt.Errorf("rollback did not come up either: %w", err) } return nil } func legacySettleOptions(optDir string, getenv func(string) string, rt registry.Route, want string, c rootRestartChecks) settleOpts { now, sleep := c.now, c.sleep if now == nil { now = time.Now } if sleep == nil { sleep = time.Sleep } ready := routeReady(rt, 2*time.Second) if c.legacyReady != nil { ready = c.legacyReady(rt) } return settleOpts{Timeout: unitSettleTimeout(getenv), Poll: 500 * time.Millisecond, Hold: 5 * time.Second, WantDir: filepath.Dir(want), Ready: ready, Now: now, Sleep: sleep} }