github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/pkg/sysinfo/sysinfo_linux.go (about)

     1  package sysinfo
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os"
     7  	"os/exec"
     8  	"regexp"
     9  	"strconv"
    10  
    11  	"github.com/patrickmn/go-cache"
    12  )
    13  
    14  // OS returns the system's OS
    15  func OS() OsInfo {
    16  	return Linux
    17  }
    18  
    19  var (
    20  	kernelVersionRegex = regexp.MustCompile(`^(\d+)\D(\d+)\D(\d+)`)
    21  )
    22  
    23  // OSVersion returns the system's OS version.
    24  func OSVersion() (*OSVersionInfo, error) {
    25  	if cached, found := sysinfoCache.Get(osVersionInfoCacheKey); found {
    26  		return cached.(*OSVersionInfo), nil
    27  	}
    28  
    29  	if v := os.Getenv(VersionOverrideEnvVar); v != "" {
    30  		vInfo, err := parseVersionInfo(v)
    31  		if err != nil {
    32  			return nil, fmt.Errorf("Could not parse version info: %w", err)
    33  		}
    34  		return &OSVersionInfo{vInfo, "spoofed"}, nil
    35  	}
    36  
    37  	// Fetch kernel version.
    38  	osrelFile := "/proc/sys/kernel/osrelease"
    39  	osrelData, err := os.ReadFile(osrelFile)
    40  	if err != nil {
    41  		return nil, fmt.Errorf("Unable to read %s: %v", osrelFile, err)
    42  	}
    43  	version := string(bytes.TrimSpace(osrelData))
    44  
    45  	// Parse kernel version parts.
    46  	versionParts := kernelVersionRegex.FindStringSubmatch(version)
    47  	if len(versionParts) != 4 {
    48  		return nil, fmt.Errorf("Unable to parse version string %q", versionParts)
    49  	}
    50  	major, _ := strconv.Atoi(versionParts[1])
    51  	minor, _ := strconv.Atoi(versionParts[2])
    52  	micro, _ := strconv.Atoi(versionParts[3])
    53  	// Fetch distribution name.
    54  	// lsb_release -d returns output of the form "Description:\t[Name]".
    55  	name, err := exec.Command("lsb_release", "-d").Output()
    56  	if err == nil && len(bytes.Split(name, []byte(":"))) > 1 {
    57  		name = bytes.TrimSpace(bytes.SplitN(name, []byte(":"), 2)[1])
    58  	} else {
    59  		etcFiles := []string{
    60  			"/etc/debian_version", // Debians
    61  			"/etc/redhat-release", // RHELs and Fedoras
    62  			"/etc/system-release", // Amazon AMIs
    63  			"/etc/SuSE-release",   // SuSEs
    64  		}
    65  		for _, etcFile := range etcFiles {
    66  			name, err = os.ReadFile(etcFile)
    67  			if err == nil {
    68  				break
    69  			}
    70  		}
    71  		if bytes.Equal(name, []byte("")) {
    72  			name = []byte("Unknown")
    73  		}
    74  	}
    75  	info := &OSVersionInfo{&VersionInfo{version, major, minor, micro}, string(name)}
    76  	sysinfoCache.Set(osVersionInfoCacheKey, info, cache.NoExpiration)
    77  	return info, nil
    78  }
    79  
    80  // Libc returns the system's C library.
    81  func Libc() (*LibcInfo, error) {
    82  	if cached, found := sysinfoCache.Get(libcInfoCacheKey); found {
    83  		return cached.(*LibcInfo), nil
    84  	}
    85  
    86  	// Assume glibc for now, which exposes a "getconf" command.
    87  	libc, err := exec.Command("getconf", "GNU_LIBC_VERSION").Output()
    88  	if err != nil {
    89  		return nil, fmt.Errorf("Unable to fetch glibc version: %s", err)
    90  	}
    91  	regex := regexp.MustCompile(`(\d+)\D(\d+)`)
    92  	parts := regex.FindStringSubmatch(string(libc))
    93  	if len(parts) != 3 {
    94  		return nil, fmt.Errorf("Unable to parse libc string '%s'", libc)
    95  	}
    96  	major, _ := strconv.Atoi(parts[1])
    97  	minor, _ := strconv.Atoi(parts[2])
    98  	info := &LibcInfo{Glibc, major, minor}
    99  	sysinfoCache.Set(libcInfoCacheKey, info, cache.NoExpiration)
   100  	return info, nil
   101  }
   102  
   103  // Compilers returns the system's available compilers.
   104  func Compilers() ([]*CompilerInfo, error) {
   105  	if cached, found := sysinfoCache.Get(compilersCacheKey); found {
   106  		return cached.([]*CompilerInfo), nil
   107  	}
   108  
   109  	compilers := []*CompilerInfo{}
   110  
   111  	// Map of compiler commands to CompilerNameInfos.
   112  	var compilerMap = map[string]CompilerNameInfo{
   113  		"gcc":   Gcc,
   114  		"clang": Clang,
   115  	}
   116  	for command, nameInfo := range compilerMap {
   117  		major, minor, err := getCompilerVersion([]string{command, "--version"})
   118  		if err != nil {
   119  			return compilers, err
   120  		} else if major > 0 {
   121  			compilers = append(compilers, &CompilerInfo{nameInfo, major, minor})
   122  		}
   123  	}
   124  
   125  	sysinfoCache.Set(compilersCacheKey, compilers, cache.NoExpiration)
   126  	return compilers, nil
   127  }