]> go.fuhry.dev Git - runtime.git/commitdiff
machines agent: initial support for openbsd pid-file services
authorDan Fuhry <dan@fuhry.com>
Thu, 25 Apr 2024 04:09:13 +0000 (00:09 -0400)
committerDan Fuhry <dan@fuhry.com>
Thu, 25 Apr 2024 04:09:13 +0000 (00:09 -0400)
machines/machines_agent/main.go
machines/services_openbsd.go [new file with mode: 0644]

index dba82d895226f0ae99e4b28bb14f785c4aa5bd84..08efb192779b80cecb4521704827a04a7326f07e 100644 (file)
@@ -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 (file)
index 0000000..3ceb26d
--- /dev/null
@@ -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,
+       })
+}