From 8aa9cfbf6f0dbc1a4ed2d63c05b096e53f57ca6b Mon Sep 17 00:00:00 2001 From: Dan Fuhry Date: Thu, 25 Apr 2024 00:09:13 -0400 Subject: [PATCH] machines agent: initial support for openbsd pid-file services --- machines/machines_agent/main.go | 4 + machines/services_openbsd.go | 181 ++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 machines/services_openbsd.go diff --git a/machines/machines_agent/main.go b/machines/machines_agent/main.go index dba82d8..08efb19 100644 --- a/machines/machines_agent/main.go +++ b/machines/machines_agent/main.go @@ -169,6 +169,10 @@ mainLoop: for _, svcName := range svcs { svc := machines.GetService(svcName) + if svc == nil { + logger.Warningf("failed to get service %v: not supported on this platform", svcName) + continue + } if err := svc.ReloadOrRestart(false); err != nil { logger.Warningf("failed to restart service %+v: %v", svcName, err) serviceRestarts.WithLabelValues(mbclient.KV{"service": svcName, "status": "failed"}).Add(1) diff --git a/machines/services_openbsd.go b/machines/services_openbsd.go new file mode 100644 index 0000000..3ceb26d --- /dev/null +++ b/machines/services_openbsd.go @@ -0,0 +1,181 @@ +//go:build openbsd + +package machines + +import ( + "fmt" + "os" + "strconv" + "strings" + "syscall" + "time" +) + +type pidFileService struct { + executable string + arguments []string + pidFile string + daemon bool +} + +const serviceStartTimeout = 10 * time.Second +const serviceStopTimeout = 10 * time.Second + +func (s *pidFileService) EnsureStarted() error { + status, err := s.Status() + if err != nil { + return err + } + + if status.Running { + return nil + } + + args := make([]string, len(s.arguments)+1) + args[0] = s.executable + copy(args[1:], s.arguments) + logger.V(2).Debugf("starting process: %+v", args) + + attrs := &os.ProcAttr{ + Dir: "/", + Env: []string{}, + Files: []*os.File{ + os.Stdin, + os.Stdout, + os.Stderr, + }, + } + + startDeadline := time.Now().Add(serviceStartTimeout) + proc, err := os.StartProcess(s.executable, args, attrs) + if err != nil { + return err + } + + if s.daemon { + defer proc.Wait() + } else { + defer proc.Release() + } + for { + if time.Now().After(startDeadline) { + return fmt.Errorf("failed to start process after %v seconds: %v", serviceStartTimeout, args) + } + + status, err := s.Status() + if err == nil && status.Running { + logger.V(2).Debugf("process started in %v", time.Now().Add(serviceStartTimeout).Sub(startDeadline)) + return nil + } + + time.Sleep(serviceStartTimeout / 100) + } +} + +func (s *pidFileService) EnsureStopped() error { + status, err := s.Status() + if err != nil { + return err + } + if !status.Running { + return nil + } + + proc, err := os.FindProcess(status.Pid) + if err != nil { + return err + } + + signal := syscall.SIGTERM + logger.V(2).Infof("sending signal %v to pid %d", signal, status.Pid) + proc.Signal(signal) + stopDeadline := time.Now().Add(serviceStartTimeout) + for { + err := proc.Signal(syscall.Signal(0)) + if err != nil { + logger.V(2).Infof("pid %d now appears to be dead", status.Pid) + proc.Wait() + os.Remove(s.pidFile) + return nil + } + if time.Now().After(stopDeadline) { + if signal == syscall.SIGTERM { + signal = syscall.SIGKILL + logger.V(2).Infof("sending signal %v to pid %d", signal, status.Pid) + proc.Signal(signal) + stopDeadline = time.Now().Add(serviceStartTimeout) + } else { + return fmt.Errorf("failed to kill pid %d", status.Pid) + } + } + time.Sleep(serviceStartTimeout / 100) + } +} + +func (s *pidFileService) ReloadOrRestart(startIfStopped bool) error { + status, err := s.Status() + if err != nil { + return err + } + if !status.Running && !startIfStopped { + return nil + } + + if status.Running { + if err := s.EnsureStopped(); err != nil { + return err + } + } + + return s.EnsureStarted() +} + +func (s *pidFileService) Status() (*ServiceStatus, error) { + st := &ServiceStatus{} + + logger.V(2).Debugf("read pid file: %s", s.pidFile) + pidContents, err := os.ReadFile(s.pidFile) + if err != nil { + return st, nil + } + + pidStr := strings.TrimSpace(string(pidContents)) + pid, err := strconv.Atoi(pidStr) + if err != nil { + return st, nil + } + logger.V(2).Debugf("got pid: %d", pid) + + proc, err := os.FindProcess(pid) + if err != nil { + return st, nil + } + defer proc.Release() + + logger.V(2).Debugf("sending signal NULL to pid %d", pid) + err = proc.Signal(syscall.Signal(0)) + if err == nil { + logger.V(2).Debugf("pid %d is alive", pid) + st.Running = true + st.Pid = pid + } else { + logger.V(2).Debugf("pid %d not alive: %v", pid, err) + } + + return st, nil +} + +func init() { + registerService("dhcpd4", &pidFileService{ + executable: "/usr/local/sbin/dhcpd", + arguments: []string{"-q", "-pf", "/var/run/dhcpd.pid", "-cf", "/etc/dhcpd4.conf"}, + pidFile: "/var/run/dhcpd.pid", + daemon: true, + }) + registerService("dhcpd6", &pidFileService{ + executable: "/usr/local/sbin/dhcpd", + arguments: []string{"-6", "-q", "-pf", "/var/run/dhcpd6.pid", "-cf", "/etc/dhcpd6.conf"}, + pidFile: "/var/run/dhcpd6.pid", + daemon: true, + }) +} -- 2.50.1