system/system.go
package system
import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
"runtime"
"strconv"
"strings"
"syscall"
"time"
"unicode"
)
// SaptuneSectionDir defines saptunes saved state directory for section info
const SaptuneSectionDir = "/run/saptune/sections"
// SaptuneSavedStateDir defines saptunes saved state directory for previous
// system values
const SaptuneSavedStateDir = "/run/saptune/saved_state"
// SaptuneParameterStateDir defines the directory where to store the
// parameter state files
// separated from the note state file directory
const SaptuneParameterStateDir = "/run/saptune/parameter"
// RPMBldVers is the version of the RPM build process (suse_version)
// defaults to '15'
// needs to be a string as replacement with -X during build does not work
// with int variables (or const)
// cannot set with -X: not a var of type string (type:int)
var RPMBldVers = "15"
// map to hold the current available systemd services
var services map[string]string
// stdOutOrg contains the origin stdout for resetting, if needed
var stdOutOrg = os.Stdout
// OSExit defines, which exit function should be used
var OSExit = os.Exit
// ErrorExitOut defines, which exit output function should be used
var ErrorExitOut = ErrorLog
// ErrExitOut defines the output function, which should be used in case
// of colored output
var ErrExitOut = errExitOut
// InfoOut defines, which log output function should be used
var InfoOut = InfoLog
// DmiID is the path to the dmidecode representation in the /sys filesystem
var DmiID = "/sys/class/dmi/id"
// IfdefVers returns the integer representation of the RPMBldVers variable
func IfdefVers() int {
intValue, _ := strconv.Atoi(RPMBldVers)
return intValue
}
// SaptuneConfigFile returns the name of the saptune configuration file
// /etc/sysconfig/saptune in SLE12 and SLE15
// /var/lib/saptune/config/saptune in SLE16
func SaptuneConfigFile() string {
if IfdefVers() > 15 {
return "/var/lib/saptune/config/saptune"
} else {
return "/etc/sysconfig/saptune"
}
}
// SaptuneConfigTemplate returns the name of the template file for the
// saptune configuration file
// /usr/share/fillup-templates/sysconfig.saptune in SLE12 and SLE15
// /usr/share/saptune/saptuneTemplate.conf in SLE16
func SaptuneConfigTemplate() string {
if IfdefVers() > 15 {
return "/usr/share/saptune/saptuneTemplate.conf"
} else {
if _, err := os.Stat("/var/adm/fillup-templates/sysconfig.saptune"); err == nil {
return "/var/adm/fillup-templates/sysconfig.saptune"
}
return "/usr/share/fillup-templates/sysconfig.saptune"
}
}
// IsUserRoot return true only if the current user is root.
func IsUserRoot() bool {
return os.Getuid() == 0
}
// GetSolutionSelector returns the architecture string
// needed to select the supported set os solutions
func GetSolutionSelector() string {
solutionSelector := runtime.GOARCH
if IsPagecacheAvailable() {
solutionSelector = solutionSelector + "_PC"
}
return solutionSelector
}
// CmdIsAvailable returns true, if the cmd is available.
func CmdIsAvailable(cmdName string) bool {
if _, err := os.Stat(cmdName); os.IsNotExist(err) {
return false
}
return true
}
// GetOsVers returns the OS version
func GetOsVers() string {
// VERSION="12", VERSION="15"
// VERSION="12-SP1", VERSION="12-SP2", VERSION="12-SP3"
var re = regexp.MustCompile(`VERSION="([\w-]+)"`)
val, err := os.ReadFile("/etc/os-release")
if err != nil {
return ""
}
matches := re.FindStringSubmatch(string(val))
if len(matches) == 0 {
return ""
}
return matches[1]
}
// GetOsName returns the OS name
func GetOsName() string {
// NAME="SLES"
var re = regexp.MustCompile(`NAME="([\w\s]+)"`)
val, err := os.ReadFile("/etc/os-release")
if err != nil {
return ""
}
matches := re.FindStringSubmatch(string(val))
if len(matches) == 0 {
return ""
}
return matches[1]
}
// IsSLE15 returns true, if System is running a SLE15 release
func IsSLE15() bool {
var re = regexp.MustCompile(`15-SP\d+`)
if GetOsName() == "SLES" && (GetOsVers() == "15" || re.MatchString(GetOsVers())) {
return true
}
return false
}
// IsSLE12 returns true, if System is running a SLE12 release
func IsSLE12() bool {
var re = regexp.MustCompile(`12-SP\d+`)
if GetOsName() == "SLES" && (GetOsVers() == "12" || re.MatchString(GetOsVers())) {
return true
}
return false
}
// CheckForPattern returns true, if the file is available and
// contains the expected string
func CheckForPattern(file, pattern string) bool {
if _, err := os.Stat(file); os.IsNotExist(err) {
return false
}
content, err := os.ReadFile(file)
if err != nil {
return false
}
//check whether content contains substring pattern
return strings.Contains(string(content), pattern)
}
// CalledFrom returns the name and the line number of the calling source file
func CalledFrom() string {
ret := ""
_, file, no, ok := runtime.Caller(2)
if ok {
_, relfile := filepath.Split(file)
ret = fmt.Sprintf("%s:%d: ", relfile, no)
}
return ret
}
func errExitOut(writer io.Writer, template string, stuff ...interface{}) {
// stuff is: color, bold, text/template, reset bold, reset color
stuff = stuff[1:]
fmt.Fprintf(writer, "%s%sERROR: "+template+"%s%s\n", stuff...)
if len(stuff) >= 4 {
stuff = stuff[2 : len(stuff)-2]
}
ErrorLogNoStdErr(template+"\n", stuff...)
}
// ErrorExit prints the message to stderr and exit 1.
func ErrorExit(template string, stuff ...interface{}) {
exState := 1
fieldType := ""
field := len(stuff) - 1
if field >= 0 {
fieldType = reflect.TypeOf(stuff[field]).String()
}
if fieldType == "*exec.ExitError" {
// get return code of failed command, if available
if exitError, ok := stuff[field].(*exec.ExitError); ok {
exState = exitError.Sys().(syscall.WaitStatus).ExitStatus()
}
}
if fieldType == "int" {
exState = reflect.ValueOf(stuff[field]).Interface().(int)
stuff = stuff[:len(stuff)-1]
}
if len(template) != 0 {
if len(stuff) > 0 && stuff[0] == "colorPrint" {
ErrExitOut(os.Stderr, template, stuff...)
} else {
ErrorExitOut(template+"\n", stuff...)
}
}
if isOwnLock() {
ReleaseSaptuneLock()
}
if jerr := jOut(exState); jerr != nil {
exState = 130
}
InfoOut("saptune terminated with exit code '%v'", exState)
OSExit(exState)
}
// OutIsTerm returns true, if Stdout is a terminal
func OutIsTerm(writer *os.File) bool {
fileInfo, _ := writer.Stat()
return (fileInfo.Mode() & os.ModeCharDevice) != 0
}
// InitOut initializes the various output methodes
// currently only json and screen are supported
func InitOut(logSwitch map[string]string) {
if GetFlagVal("format") == "json" {
// if writing json format, switch off
// the stdout and stderr output of the log messages
logSwitch["verbose"] = "off"
logSwitch["error"] = "off"
// switch off stdout
if os.Getenv("SAPTUNE_JDEBUG") != "on" {
os.Stdout, _ = os.Open(os.DevNull)
}
jInit()
}
}
// SwitchOffOut disables stdout and stderr
func SwitchOffOut() (*os.File, *os.File) {
oldStdout := os.Stdout
oldSdterr := os.Stderr
os.Stdout, _ = os.Open(os.DevNull)
os.Stderr, _ = os.Open(os.DevNull)
return oldStdout, oldSdterr
}
// SwitchOnOut restores stdout and stderr to the settings before SwitchOffOut
// was called
func SwitchOnOut(stdout *os.File, stderr *os.File) {
os.Stdout = stdout
os.Stderr = stderr
}
// WrapTxt implements something like 'fold' command
// A given text string will be wrapped at word borders into
// lines of a given width
func WrapTxt(text string, width int) (folded []string) {
var words []string
fallback := false
if strings.Contains(text, " ") {
words = strings.Split(text, " ")
} else {
// fallback (e.g. net.ipv4.ip_local_reserved_ports)
words = strings.Split(text, ",")
fallback = true
}
if len(words) == 0 {
return
}
foldedTxt := words[0]
spaceLeft := width - len(foldedTxt)
noSpace := false
for _, word := range words[1:] {
if word == "\n" {
foldedTxt += word
spaceLeft = width
noSpace = true
continue
}
if len(word)+1 > spaceLeft {
// fold; start next row
if fallback {
foldedTxt += ",\n" + word
} else {
foldedTxt += "\n" + word
}
if strings.HasSuffix(word, "\n") {
spaceLeft = width
noSpace = true
} else {
spaceLeft = width - len(word)
noSpace = false
}
} else {
if noSpace {
foldedTxt += word
spaceLeft -= len(word)
noSpace = false
} else if fallback {
foldedTxt += "," + word
spaceLeft -= 1 + len(word)
} else {
foldedTxt += " " + word
spaceLeft -= 1 + len(word)
}
if strings.HasSuffix(word, "\n") {
spaceLeft = width
noSpace = true
}
}
}
folded = strings.Split(foldedTxt, "\n")
return
}
// GetDmiID return the content of /sys/devices/virtual/dmi/id/<file> or
// an empty string
func GetDmiID(file string) (string, error) {
var err error
var content []byte
ret := ""
fileName := fmt.Sprintf("%s/%s", DmiID, file)
if content, err = os.ReadFile(fileName); err == nil {
ret = strings.TrimSpace(string(content))
} else {
InfoLog("failed to read %s - %v", fileName, err)
}
return ret, err
}
// GetHWIdentity returns the hardwar vendor or model of the system
// needs adaption, if the files to identify the hardware will change or
// if we need to look at different files for different vendors
// but the 'open' API GetDmiID exists for workarounds at customer side
func GetHWIdentity(info string) (string, error) {
var err error
var content []byte
fix := ""
fileName := ""
ret := ""
switch info {
case "vendor":
if runtime.GOARCH == "ppc64le" {
fix = "IBM"
} else {
fileName = fmt.Sprintf("%s/board_vendor", DmiID)
}
case "model":
if runtime.GOARCH == "ppc64le" {
fileName = "/sys/firmware/devicetree/base/model"
} else {
fileName = fmt.Sprintf("%s/product_name", DmiID)
}
}
if fileName != "" {
if content, err = os.ReadFile(fileName); err == nil {
ret = strings.TrimSpace(string(content))
} else {
InfoLog("failed to read %s - %v", fileName, err)
}
}
if fix != "" {
ret = fix
err = nil
}
return ret, err
}
// StripComment will strip everything right from the given comment character
// (including the comment character) and returns the resulting string
// comment characters can be '#' or ';' or something else
// or a regex like `\s#[^#]|"\s#[^#]`
func StripComment(str, commentChars string) string {
ret := str
re := regexp.MustCompile(commentChars)
if cut := re.FindStringIndex(str); cut != nil {
ret = strings.TrimRightFunc(str[:cut[0]], unicode.IsSpace)
// strip masked # (\s## -> \s#) inside the text
re = regexp.MustCompile(`\s(##)`)
ret = re.ReplaceAllString(ret, "#")
}
return ret
}
// GetVirtStatus gets the status of virtualization environment
func GetVirtStatus() string {
vtype := ""
// first check vm (-v)
virt, vm, _ := SystemdDetectVirt("-v")
if virt {
// vm detected
vtype = vm
}
// next check container (-c)
virt, container, _ := SystemdDetectVirt("-c")
if virt {
// container detected
if vtype == "" {
vtype = container
} else {
vtype = vtype + " " + container
}
}
// last check for chroot (-r)
// be in mind, that the command will not deliver any output, but only
// return 0, if it found a chroot env or 1, if not
virt, _, _ = SystemdDetectVirt("-r")
if virt {
// chroot detected
if vtype == "" {
vtype = "chroot"
} else {
vtype = vtype + " chroot"
}
}
if vtype == "" {
vtype = "none"
}
return vtype
}
// Watch prints the current time
func Watch() string {
t := time.Now()
//watch := fmt.Sprintf("%s", t.Format(time.UnixDate))
watch := t.Format("2006/01/02 15:04:05.99999999")
return watch
}