From 60d6734e1f4a1d0c1bceaee61cc264121f9929b0 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 13 Jun 2023 00:28:22 +0700 Subject: [PATCH] cmd/ctrld: support older GL-inet devices The openwrt version in old GL-inet devices do not support checking status using /etc/init.d/, so the sysV wrapping trick won't work. Instead, we need to parse "ps" command output to check whether ctrld process is running or not. While at it, making newService as a wrapper of service.New function, prevent the caller from calling the latter without following call to the former, causing mismatch in service operations. --- cmd/ctrld/cli.go | 24 ++++++--------- cmd/ctrld/service.go | 73 +++++++++++++++++++++++++++++++++++--------- 2 files changed, 67 insertions(+), 30 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 8cfca02..85b7f85 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -131,11 +131,10 @@ func initCLI() { waitCh: waitCh, stopCh: stopCh, } - s, err := service.New(p, svcConfig) + s, err := newService(p, svcConfig) if err != nil { mainLog.Fatal().Err(err).Msg("failed create new service") } - s = newService(s) if err := s.Run(); err != nil { mainLog.Error().Err(err).Msg("failed to start service") } @@ -305,12 +304,11 @@ func initCLI() { } prog := &prog{} - s, err := service.New(prog, sc) + s, err := newService(prog, sc) if err != nil { mainLog.Error().Msg(err.Error()) return } - s = newService(s) tasks := []task{ {s.Stop, false}, {s.Uninstall, false}, @@ -322,7 +320,7 @@ func initCLI() { mainLog.Warn().Err(err).Msg("post installation failed, please check system/service log for details error") return } - status, err := serviceStatus(s) + status, err := s.Status() if err != nil { mainLog.Warn().Err(err).Msg("could not get service status") return @@ -370,12 +368,11 @@ func initCLI() { Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { prog := &prog{} - s, err := service.New(prog, svcConfig) + s, err := newService(prog, svcConfig) if err != nil { mainLog.Error().Msg(err.Error()) return } - s = newService(s) initLogging() if doTasks([]task{{s.Stop, true}}) { prog.resetDNS() @@ -394,12 +391,11 @@ func initCLI() { Short: "Restart the ctrld service", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { - s, err := service.New(&prog{}, svcConfig) + s, err := newService(&prog{}, svcConfig) if err != nil { mainLog.Error().Msg(err.Error()) return } - s = newService(s) initLogging() if doTasks([]task{{s.Restart, true}}) { mainLog.Notice().Msg("Service restarted") @@ -415,13 +411,12 @@ func initCLI() { initConsoleLogging() }, Run: func(cmd *cobra.Command, args []string) { - s, err := service.New(&prog{}, svcConfig) + s, err := newService(&prog{}, svcConfig) if err != nil { mainLog.Error().Msg(err.Error()) return } - s = newService(s) - status, err := serviceStatus(s) + status, err := s.Status() if err != nil { mainLog.Error().Msg(err.Error()) os.Exit(1) @@ -460,7 +455,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`, Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { prog := &prog{} - s, err := service.New(prog, svcConfig) + s, err := newService(prog, svcConfig) if err != nil { mainLog.Error().Msg(err.Error()) return @@ -712,12 +707,11 @@ func processCDFlags() { logger.Info().Msgf("fetching Controld D configuration from API: %s", cdUID) resolverConfig, err := controld.FetchResolverConfig(cdUID, rootCmd.Version, cdDev) if uer, ok := err.(*controld.UtilityErrorResponse); ok && uer.ErrorField.Code == controld.InvalidConfigCode { - s, err := service.New(&prog{}, svcConfig) + s, err := newService(&prog{}, svcConfig) if err != nil { logger.Warn().Err(err).Msg("failed to create new service") return } - if netIface, _ := netInterface(iface); netIface != nil { if err := restoreNetworkManager(); err != nil { logger.Error().Err(err).Msg("could not restore NetworkManager") diff --git a/cmd/ctrld/service.go b/cmd/ctrld/service.go index bce6503..adf0a28 100644 --- a/cmd/ctrld/service.go +++ b/cmd/ctrld/service.go @@ -3,6 +3,7 @@ package main import ( "bytes" "errors" + "fmt" "os" "os/exec" @@ -11,37 +12,87 @@ import ( "github.com/Control-D-Inc/ctrld/internal/router" ) -func newService(s service.Service) service.Service { - // TODO: unify for other SysV system. - switch { - case router.IsGLiNet(), router.IsOldOpenwrt(): - return &sysV{s} +// newService wraps service.New call to return service.Service +// wrapper which is suitable for the current platform. +func newService(i service.Interface, c *service.Config) (service.Service, error) { + s, err := service.New(i, c) + if err != nil { + return nil, err } - return s + switch { + case router.IsOldOpenwrt(): + return &procd{&sysV{s}}, nil + case router.IsGLiNet(): // TODO: unify for other SysV system. + return &sysV{s}, nil + } + return s, nil } // sysV wraps a service.Service, and provide start/stop/status command // base on "/etc/init.d/". // -// Use this on system wherer "service" command is not available, like GL.iNET router. +// Use this on system where "service" command is not available, like GL.iNET router. type sysV struct { service.Service } +func (s *sysV) installed() bool { + fi, err := os.Stat("/etc/init.d/ctrld") + if err != nil { + return false + } + mode := fi.Mode() + return mode.IsRegular() && (mode&0111) != 0 +} + func (s *sysV) Start() error { + if !s.installed() { + return service.ErrNotInstalled + } _, err := exec.Command("/etc/init.d/ctrld", "start").CombinedOutput() return err } func (s *sysV) Stop() error { + if !s.installed() { + return service.ErrNotInstalled + } _, err := exec.Command("/etc/init.d/ctrld", "stop").CombinedOutput() return err } func (s *sysV) Status() (service.Status, error) { + if !s.installed() { + return service.StatusUnknown, service.ErrNotInstalled + } return unixSystemVServiceStatus() } +// procd wraps a service.Service, and provide start/stop command +// base on "/etc/init.d/", status command base on parsing "ps" command output. +// +// Use this on system where "/etc/init.d/ status" command is not available, +// like old GL.iNET Opal router. +type procd struct { + *sysV +} + +func (s *procd) Status() (service.Status, error) { + if !s.installed() { + return service.StatusUnknown, service.ErrNotInstalled + } + exe, err := os.Executable() + if err != nil { + return service.StatusUnknown, nil + } + // Looking for something like "/sbin/ctrld run ". + shellCmd := fmt.Sprintf("ps | grep -q %q", exe+" [r]un ") + if err := exec.Command("sh", "-c", shellCmd).Run(); err != nil { + return service.StatusStopped, nil + } + return service.StatusRunning, nil +} + type task struct { f func() error abortOnError bool @@ -73,14 +124,6 @@ func checkHasElevatedPrivilege() { } } -func serviceStatus(s service.Service) (service.Status, error) { - status, err := s.Status() - if err != nil && service.Platform() == "unix-systemv" { - return unixSystemVServiceStatus() - } - return status, err -} - func unixSystemVServiceStatus() (service.Status, error) { out, err := exec.Command("/etc/init.d/ctrld", "status").CombinedOutput() if err != nil {