pkg.re/essentialkaos/ek.v11@v12.41.0+incompatible/initsystem/initsystem.go (about) 1 //go:build linux || freebsd 2 // +build linux freebsd 3 4 // Package initsystem provides methods for working with different init systems 5 package initsystem 6 7 // ////////////////////////////////////////////////////////////////////////////////// // 8 // // 9 // Copyright (c) 2022 ESSENTIAL KAOS // 10 // Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0> // 11 // // 12 // ////////////////////////////////////////////////////////////////////////////////// // 13 14 import ( 15 "bufio" 16 "bytes" 17 "fmt" 18 "os" 19 "os/exec" 20 "strings" 21 "syscall" 22 23 "pkg.re/essentialkaos/ek.v12/env" 24 "pkg.re/essentialkaos/ek.v12/fsutil" 25 "pkg.re/essentialkaos/ek.v12/strutil" 26 ) 27 28 // ////////////////////////////////////////////////////////////////////////////////// // 29 30 const ( 31 _STATUS_UNKNOWN = 0 32 _STATUS_PRESENT = 1 33 _STATUS_NOT_PRESENT = 2 34 ) 35 36 // ////////////////////////////////////////////////////////////////////////////////// // 37 38 var ( 39 sysvStatus = _STATUS_UNKNOWN 40 upstartStatus = _STATUS_UNKNOWN 41 systemdStatus = _STATUS_UNKNOWN 42 ) 43 44 // ////////////////////////////////////////////////////////////////////////////////// // 45 46 // SysV returns true if SysV is used on system 47 func SysV() bool { 48 if sysvStatus != _STATUS_UNKNOWN { 49 return sysvStatus == _STATUS_PRESENT 50 } 51 52 switch Systemd() { 53 case true: 54 sysvStatus = _STATUS_NOT_PRESENT 55 default: 56 sysvStatus = _STATUS_PRESENT 57 } 58 59 return sysvStatus == _STATUS_PRESENT 60 } 61 62 // Upstart returns true if Upstart is used on system 63 func Upstart() bool { 64 if upstartStatus != _STATUS_UNKNOWN { 65 return upstartStatus == _STATUS_PRESENT 66 } 67 68 switch env.Which("initctl") { 69 case "": 70 upstartStatus = _STATUS_NOT_PRESENT 71 default: 72 upstartStatus = _STATUS_PRESENT 73 } 74 75 return upstartStatus == _STATUS_PRESENT 76 } 77 78 // Systemd returns true if Systemd is used on system 79 func Systemd() bool { 80 if systemdStatus != _STATUS_UNKNOWN { 81 return systemdStatus == _STATUS_PRESENT 82 } 83 84 switch env.Which("systemctl") { 85 case "": 86 systemdStatus = _STATUS_NOT_PRESENT 87 default: 88 systemdStatus = _STATUS_PRESENT 89 } 90 91 return systemdStatus == _STATUS_PRESENT 92 } 93 94 // IsPresent returns true if service is present in any init system 95 func IsPresent(name string) bool { 96 if hasSystemdService(name) { 97 return true 98 } 99 100 if hasSysVService(name) { 101 return true 102 } 103 104 if hasUpstartService(name) { 105 return true 106 } 107 108 return false 109 } 110 111 // IsWorks returns service state 112 func IsWorks(name string) (bool, error) { 113 if hasSystemdService(name) { 114 return getSystemdServiceState(name) 115 } 116 117 if hasUpstartService(name) { 118 return getUpstartServiceState(name) 119 } 120 121 if hasSysVService(name) { 122 return getSysVServiceState(name) 123 } 124 125 return false, fmt.Errorf("Can't find service state") 126 } 127 128 // IsEnabled returns true if auto start enabled for given service 129 func IsEnabled(name string) (bool, error) { 130 if !IsPresent(name) { 131 return false, fmt.Errorf("Service doesn't exist on this system") 132 } 133 134 if hasSystemdService(name) { 135 return isSystemdEnabled(name) 136 } 137 138 if hasUpstartService(name) { 139 return isUpstartEnabled(name) 140 } 141 142 if hasSysVService(name) { 143 return isSysVEnabled(name) 144 } 145 146 return false, fmt.Errorf("Can't find service state") 147 } 148 149 // ////////////////////////////////////////////////////////////////////////////////// // 150 151 func hasSysVService(name string) bool { 152 // Default path for linux 153 initDir := "/etc/rc.d/init.d" 154 155 if fsutil.CheckPerms("FXS", initDir+"/"+name) { 156 return true 157 } 158 159 // Default path for BSD 160 initDir = "/usr/local/etc/rc.d" 161 162 return fsutil.CheckPerms("FXS", initDir+"/"+name) 163 } 164 165 func hasUpstartService(name string) bool { 166 if !strings.HasSuffix(name, ".conf") { 167 name = name + ".conf" 168 } 169 170 return fsutil.IsExist("/etc/init/" + name) 171 } 172 173 func hasSystemdService(name string) bool { 174 if !strings.HasSuffix(name, ".service") { 175 name = name + ".service" 176 } 177 178 if fsutil.IsExist("/etc/systemd/system/" + name) { 179 return true 180 } 181 182 if fsutil.IsExist("/etc/systemd/user/" + name) { 183 return true 184 } 185 186 return fsutil.IsExist("/usr/lib/systemd/system/" + name) 187 } 188 189 func getSysVServiceState(name string) (bool, error) { 190 cmd := exec.Command("/sbin/service", name, "status") 191 192 output, _ := cmd.Output() 193 194 if bytes.Contains(output, []byte("ExecStart")) { 195 return getSystemdServiceState(name) 196 } 197 198 if cmd.ProcessState == nil { 199 return false, fmt.Errorf("Can't get service command process state") 200 } 201 202 waitStatus := cmd.ProcessState.Sys() 203 204 if waitStatus == nil { 205 return false, fmt.Errorf("Can't get service command process state") 206 } 207 208 status, ok := waitStatus.(syscall.WaitStatus) 209 210 if !ok { 211 return false, fmt.Errorf("Can't get service command exit code") 212 } 213 214 exitStatus := status.ExitStatus() 215 216 switch exitStatus { 217 case 0: 218 return true, nil 219 case 3: 220 return false, nil 221 } 222 223 return false, fmt.Errorf("service command return unsupported exit code (%d)", exitStatus) 224 } 225 226 func getUpstartServiceState(name string) (bool, error) { 227 if strings.HasSuffix(name, ".conf") { 228 name = strings.Replace(name, ".conf", "", -1) 229 } 230 231 output, err := exec.Command("/sbin/status", name).Output() 232 233 if err != nil { 234 return false, fmt.Errorf("upstart returned an error") 235 } 236 237 return parseUpstartStatusOutput(string(output)) 238 } 239 240 func getSystemdServiceState(name string) (bool, error) { 241 output, err := exec.Command("/usr/bin/systemctl", "show", name, "-p", "ActiveState", "-p", "LoadState").Output() 242 243 if err != nil { 244 return false, fmt.Errorf("systemd return an error") 245 } 246 247 return parseSystemdStatusOutput(name, string(output)) 248 } 249 250 func isSysVEnabled(name string) (bool, error) { 251 output, err := exec.Command("/sbin/chkconfig", "--list", name).Output() 252 253 if err != nil { 254 return false, fmt.Errorf("chkconfig returned an error") 255 } 256 257 return parseSysvEnabledOutput(string(output)) 258 } 259 260 func isUpstartEnabled(name string) (bool, error) { 261 if !strings.HasSuffix(name, ".conf") { 262 name = name + ".conf" 263 } 264 265 return parseUpstartEnabledData("/etc/init/" + name) 266 } 267 268 func isSystemdEnabled(name string) (bool, error) { 269 output, err := exec.Command("/usr/bin/systemctl", "is-enabled", name).Output() 270 271 if err != nil { 272 return false, fmt.Errorf("systemd return error: %v", err) 273 } 274 275 return parseSystemdEnabledOutput(string(output)), nil 276 } 277 278 // ////////////////////////////////////////////////////////////////////////////////// // 279 280 func parseSystemdEnabledOutput(data string) bool { 281 return strings.TrimRight(data, "\n\r") == "enabled" 282 } 283 284 func parseUpstartEnabledData(file string) (bool, error) { 285 fd, err := os.OpenFile(file, os.O_RDONLY, 0) 286 287 if err != nil { 288 return false, fmt.Errorf("Can't read service unit file") 289 } 290 291 defer fd.Close() 292 293 r := bufio.NewReader(fd) 294 s := bufio.NewScanner(r) 295 296 for s.Scan() { 297 text := strings.TrimLeft(s.Text(), " ") 298 299 if strings.HasPrefix(text, "#") { 300 continue 301 } 302 303 if strings.Contains(text, "start on") { 304 return true, nil 305 } 306 } 307 308 return false, nil 309 } 310 311 func parseSysvEnabledOutput(data string) (bool, error) { 312 switch { 313 case strings.Contains(data, ":on"): 314 return true, nil 315 316 case strings.Contains(data, ":off"): 317 return false, nil 318 319 default: 320 return false, fmt.Errorf("Can't parse chkconfig output") 321 } 322 } 323 324 func parseSystemdStatusOutput(name, data string) (bool, error) { 325 loadState := strutil.ReadField(data, 0, false, "\n") 326 loadStateValue := strutil.ReadField(loadState, 1, false, "=") 327 328 if strings.Trim(loadStateValue, "\r\n") == "not-found" { 329 return false, fmt.Errorf("Unit %s could not be found", name) 330 } 331 332 activeState := strutil.ReadField(data, 1, false, "\n") 333 activeStateValue := strutil.ReadField(activeState, 1, false, "=") 334 335 switch strings.Trim(activeStateValue, "\r\n") { 336 case "active", "activating": 337 return true, nil 338 339 case "inactive", "deactivating", "failed": 340 return false, nil 341 } 342 343 return false, fmt.Errorf("Can't parse systemd output") 344 } 345 346 func parseUpstartStatusOutput(data string) (bool, error) { 347 data = strings.TrimRight(data, "\r\n") 348 status := strutil.ReadField(data, 1, false, " ") 349 350 switch status { 351 case "start/running": 352 return true, nil 353 354 case "stop/waiting": 355 return false, nil 356 357 default: 358 return false, fmt.Errorf("Can't parse upstart output") 359 } 360 }