pkg.re/essentialkaos/ek.10@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  }