github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/system/common/common_linux.go (about)

     1  //go:build linux
     2  
     3  package common
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  )
    16  
    17  func DoSysctrl(mib string) ([]string, error) {
    18  	sysctl, err := exec.LookPath("sysctl")
    19  	if err != nil {
    20  		return []string{}, err
    21  	}
    22  	cmd := exec.Command(sysctl, "-n", mib)
    23  	cmd.Env = getSysctrlEnv(os.Environ())
    24  	out, err := cmd.Output()
    25  	if err != nil {
    26  		return []string{}, err
    27  	}
    28  	v := strings.Replace(string(out), "{ ", "", 1)
    29  	v = strings.Replace(v, " }", "", 1)
    30  	values := strings.Fields(v)
    31  
    32  	return values, nil
    33  }
    34  
    35  func NumProcs() (uint64, error) {
    36  	f, err := os.Open(HostProc())
    37  	if err != nil {
    38  		return 0, err
    39  	}
    40  	defer f.Close()
    41  
    42  	list, err := f.Readdirnames(-1)
    43  	if err != nil {
    44  		return 0, err
    45  	}
    46  	var cnt uint64
    47  
    48  	for _, v := range list {
    49  		if _, err = strconv.ParseUint(v, 10, 64); err == nil {
    50  			cnt++
    51  		}
    52  	}
    53  
    54  	return cnt, nil
    55  }
    56  
    57  func BootTimeWithContext(ctx context.Context) (uint64, error) {
    58  	system, role, err := Virtualization()
    59  	if err != nil {
    60  		return 0, err
    61  	}
    62  
    63  	statFile := "stat"
    64  	if system == "lxc" && role == "guest" {
    65  		// if lxc, /proc/uptime is used.
    66  		statFile = "uptime"
    67  	} else if system == "docker" && role == "guest" {
    68  		// also docker, guest
    69  		statFile = "uptime"
    70  	}
    71  
    72  	filename := HostProc(statFile)
    73  	lines, err := ReadLines(filename)
    74  	if err != nil {
    75  		return 0, err
    76  	}
    77  
    78  	if statFile == "uptime" {
    79  		if len(lines) != 1 {
    80  			return 0, fmt.Errorf("wrong uptime format")
    81  		}
    82  		f := strings.Fields(lines[0])
    83  		b, err := strconv.ParseFloat(f[0], 64)
    84  		if err != nil {
    85  			return 0, err
    86  		}
    87  		t := uint64(time.Now().Unix()) - uint64(b)
    88  		return t, nil
    89  	}
    90  	if statFile == "stat" {
    91  		for _, line := range lines {
    92  			if strings.HasPrefix(line, "btime") {
    93  				f := strings.Fields(line)
    94  				if len(f) != 2 {
    95  					return 0, fmt.Errorf("wrong btime format")
    96  				}
    97  				b, err := strconv.ParseInt(f[1], 10, 64)
    98  				if err != nil {
    99  					return 0, err
   100  				}
   101  				t := uint64(b)
   102  				return t, nil
   103  			}
   104  		}
   105  	}
   106  
   107  	return 0, fmt.Errorf("could not find btime")
   108  }
   109  
   110  func Virtualization() (string, string, error) {
   111  	return VirtualizationWithContext(context.Background())
   112  }
   113  
   114  // required variables for concurrency safe virtualization caching.
   115  var (
   116  	cachedVirtMap   map[string]string
   117  	cachedVirtMutex sync.RWMutex
   118  	cachedVirtOnce  sync.Once
   119  )
   120  
   121  func VirtualizationWithContext(ctx context.Context) (string, string, error) {
   122  	var system, role string
   123  
   124  	// if cached already, return from cache
   125  	cachedVirtMutex.RLock() // unlock won't be deferred so concurrent reads don't wait for long
   126  	if cachedVirtMap != nil {
   127  		cachedSystem, cachedRole := cachedVirtMap["system"], cachedVirtMap["role"]
   128  		cachedVirtMutex.RUnlock()
   129  		return cachedSystem, cachedRole, nil
   130  	}
   131  	cachedVirtMutex.RUnlock()
   132  
   133  	filename := HostProc("xen")
   134  	if PathExists(filename) {
   135  		system = "xen"
   136  		role = "guest" // assume guest
   137  
   138  		if PathExists(filepath.Join(filename, "capabilities")) {
   139  			contents, err := ReadLines(filepath.Join(filename, "capabilities"))
   140  			if err == nil && StringsContains(contents, "control_d") {
   141  				role = "host"
   142  			}
   143  		}
   144  	}
   145  
   146  	filename = HostProc("modules")
   147  	if PathExists(filename) {
   148  		contents, err := ReadLines(filename)
   149  		if err == nil {
   150  			switch {
   151  			case StringsContains(contents, "kvm"):
   152  				system = "kvm"
   153  				role = "host"
   154  			case StringsContains(contents, "vboxdrv"):
   155  				system = "vbox"
   156  				role = "host"
   157  			case StringsContains(contents, "vboxguest"):
   158  				system = "vbox"
   159  				role = "guest"
   160  			case StringsContains(contents, "vmware"):
   161  				system = "vmware"
   162  				role = "guest"
   163  			}
   164  		}
   165  	}
   166  
   167  	filename = HostProc("cpuinfo")
   168  	if PathExists(filename) {
   169  		contents, err := ReadLines(filename)
   170  		if err == nil {
   171  			if StringsContains(contents, "QEMU Virtual CPU") ||
   172  				StringsContains(contents, "Common KVM processor") ||
   173  				StringsContains(contents, "Common 32-bit KVM processor") {
   174  				system = "kvm"
   175  				role = "guest"
   176  			}
   177  		}
   178  	}
   179  
   180  	filename = HostProc("bus/pci/devices")
   181  	if PathExists(filename) {
   182  		contents, err := ReadLines(filename)
   183  		if err == nil {
   184  			if StringsContains(contents, "virtio-pci") {
   185  				role = "guest"
   186  			}
   187  		}
   188  	}
   189  
   190  	filename = HostProc()
   191  	if PathExists(filepath.Join(filename, "bc", "0")) {
   192  		system = "openvz"
   193  		role = "host"
   194  	} else if PathExists(filepath.Join(filename, "vz")) {
   195  		system = "openvz"
   196  		role = "guest"
   197  	}
   198  
   199  	// not use dmidecode because it requires root
   200  	if PathExists(filepath.Join(filename, "self", "status")) {
   201  		contents, err := ReadLines(filepath.Join(filename, "self", "status"))
   202  		if err == nil {
   203  			if StringsContains(contents, "s_context:") ||
   204  				StringsContains(contents, "VxID:") {
   205  				system = "linux-vserver"
   206  			}
   207  		}
   208  	}
   209  
   210  	if PathExists(filepath.Join(filename, "1", "environ")) {
   211  		contents, err := ReadFile(filepath.Join(filename, "1", "environ"))
   212  
   213  		if err == nil {
   214  			if strings.Contains(contents, "container=lxc") {
   215  				system = "lxc"
   216  				role = "guest"
   217  			}
   218  		}
   219  	}
   220  
   221  	if PathExists(filepath.Join(filename, "self", "cgroup")) {
   222  		contents, err := ReadLines(filepath.Join(filename, "self", "cgroup"))
   223  		if err == nil {
   224  			switch {
   225  			case StringsContains(contents, "lxc"):
   226  				system = "lxc"
   227  				role = "guest"
   228  			case StringsContains(contents, "docker"):
   229  				system = "docker"
   230  				role = "guest"
   231  			case StringsContains(contents, "machine-rkt"):
   232  				system = "rkt"
   233  				role = "guest"
   234  			case PathExists("/usr/bin/lxc-version"):
   235  				system = "lxc"
   236  				role = "host"
   237  			}
   238  		}
   239  	}
   240  
   241  	if PathExists(HostEtc("os-release")) {
   242  		p, _, err := GetOSRelease()
   243  		if err == nil && p == "coreos" {
   244  			system = "rkt" // Is it true?
   245  			role = "host"
   246  		}
   247  	}
   248  
   249  	// before returning for the first time, cache the system and role
   250  	cachedVirtOnce.Do(func() {
   251  		cachedVirtMutex.Lock()
   252  		defer cachedVirtMutex.Unlock()
   253  		cachedVirtMap = map[string]string{
   254  			"system": system,
   255  			"role":   role,
   256  		}
   257  	})
   258  
   259  	return system, role, nil
   260  }
   261  
   262  func GetOSRelease() (platform string, version string, err error) {
   263  	contents, err := ReadLines(HostEtc("os-release"))
   264  	if err != nil {
   265  		return "", "", nil // return empty
   266  	}
   267  	for _, line := range contents {
   268  		field := strings.Split(line, "=")
   269  		if len(field) < 2 {
   270  			continue
   271  		}
   272  		switch field[0] {
   273  		case "ID": // use ID for lowercase
   274  			platform = trimQuotes(field[1])
   275  		case "VERSION":
   276  			version = trimQuotes(field[1])
   277  		}
   278  	}
   279  	return platform, version, nil
   280  }
   281  
   282  // trimQuotes removes quotes in the source string.
   283  func trimQuotes(s string) string {
   284  	if len(s) >= 2 {
   285  		if s[0] == '"' && s[len(s)-1] == '"' {
   286  			return s[1 : len(s)-1]
   287  		}
   288  	}
   289  	return s
   290  }