github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/toolkit/internal/common/common_linux.go (about)

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