cmd/ctrld: support older GL-inet devices

The openwrt version in old GL-inet devices do not support checking
status using /etc/init.d/<service_name>, 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.
This commit is contained in:
Cuong Manh Le
2023-06-13 00:28:22 +07:00
committed by Cuong Manh Le
parent e684c7d8c4
commit 60d6734e1f
2 changed files with 67 additions and 30 deletions

View File

@@ -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")

View File

@@ -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/<service_name>".
//
// 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/<service_name>", status command base on parsing "ps" command output.
//
// Use this on system where "/etc/init.d/<service_name> 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 {