go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/services/sysv.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package services 5 6 import ( 7 "bufio" 8 "fmt" 9 "io" 10 "regexp" 11 "strings" 12 13 "github.com/rs/zerolog/log" 14 "go.mondoo.com/cnquery/providers/os/connection/shared" 15 "go.mondoo.com/cnquery/utils/stringx" 16 ) 17 18 type SysVServiceManager struct { 19 conn shared.Connection 20 } 21 22 func (s *SysVServiceManager) Name() string { 23 return "SysV Service Manager" 24 } 25 26 func (s *SysVServiceManager) List() ([]*Service, error) { 27 // 1. gather all services 28 services, err := s.services() 29 if err != nil { 30 return nil, err 31 } 32 33 // 2. gather all run levels 34 rl, err := s.serviceRunLevel() 35 if err != nil { 36 return nil, err 37 } 38 39 // eg. we ignore the following run levels since `service halt status` may shutdown the system 40 ignored := []string{"boot", "boot.local", "functions", "halt", "halt.local", "killall", "rc", "reboot", "shutdown", "single", "skeleton", ".depend.boot", ".depend.start", ".depend.stop"} 41 statusServices := []string{} 42 for i := range services { 43 service := services[i] 44 if stringx.Contains(ignored, service) { 45 continue 46 } 47 statusServices = append(statusServices, service) 48 } 49 50 // 3. mimic `service --status-all` by running `service x status` for each detected service 51 running, err := s.running(statusServices) 52 if err != nil { 53 return nil, err 54 } 55 56 // aggregate data into service struct 57 res := []*Service{} 58 59 for i := range statusServices { 60 service := statusServices[i] 61 62 srv := &Service{ 63 Name: service, 64 Enabled: len(rl[service]) > 0, 65 Installed: true, 66 Running: running[service] == true, 67 Type: "sysv", 68 } 69 70 if srv.Running { 71 srv.State = ServiceRunning 72 } else { 73 srv.State = ServiceStopped 74 } 75 res = append(res, srv) 76 } 77 78 return res, nil 79 } 80 81 func (s *SysVServiceManager) services() ([]string, error) { 82 c, err := s.conn.RunCommand("ls -1 /etc/init.d/") 83 if err != nil { 84 return nil, err 85 } 86 87 services := ParseSysvServices(c.Stdout) 88 return services, nil 89 } 90 91 func (s *SysVServiceManager) serviceRunLevel() (map[string][]SysVServiceRunlevel, error) { 92 c, _ := s.conn.RunCommand("find /etc/rc*.d -name 'S*'") 93 // it may happen that /etc/init.d/rc does not exist, eg on centos 6 94 return ParseSysVRunlevel(c.Stdout) 95 } 96 97 func (s *SysVServiceManager) running(services []string) (map[string]bool, error) { 98 res := map[string]bool{} 99 100 for i := range services { 101 service := services[i] 102 running := true 103 104 serviceStatusCmd, err := s.conn.RunCommand(fmt.Sprintf("service %s status", service)) 105 if err != nil || serviceStatusCmd.ExitStatus != 0 { 106 running = false 107 } 108 res[service] = running 109 } 110 111 return res, nil 112 } 113 114 func ParseSysvServices(r io.Reader) []string { 115 services := []string{} 116 scanner := bufio.NewScanner(r) 117 for scanner.Scan() { 118 line := scanner.Text() 119 service := strings.TrimSpace(line) 120 if service == "" { 121 continue 122 } 123 services = append(services, service) 124 } 125 return services 126 } 127 128 var runlevelRegex = regexp.MustCompile(`rc([0-6])\.d\/S(\d+)(.*)$`) 129 130 type SysVServiceRunlevel struct { 131 Level string 132 Order string 133 } 134 135 func ParseSysVRunlevel(r io.Reader) (map[string][]SysVServiceRunlevel, error) { 136 res := map[string][]SysVServiceRunlevel{} 137 scanner := bufio.NewScanner(r) 138 for scanner.Scan() { 139 line := scanner.Text() 140 m := runlevelRegex.FindStringSubmatch(line) 141 if len(m) != 4 { 142 log.Error().Str("line", line).Msg("cannot parse sysv runlevel") 143 continue 144 } 145 146 service := m[3] 147 srl := SysVServiceRunlevel{ 148 Level: m[1], 149 Order: m[2], 150 } 151 152 entry, ok := res[service] 153 if !ok { 154 entry = []SysVServiceRunlevel{} 155 } 156 157 entry = append(entry, srl) 158 res[service] = entry 159 } 160 return res, nil 161 }