diff --git a/consts_nix.go b/consts_nix.go new file mode 100644 index 0000000..dc68bbf --- /dev/null +++ b/consts_nix.go @@ -0,0 +1,11 @@ +//go:build !(windows || plan9 || wasip1 || js || ios) + +package sysutils + +const ( + envSudoCmd string = "SUDO_COMMAND" + envSudoHome string = "SUDO_HOME" + envSudoGrp string = "SUDO_GID" + envSudoUid string = "SUDO_UID" + envSudoUname string = "SUDO_USER" +) diff --git a/envs/consts.go b/envs/consts.go index 3e75701..6320645 100644 --- a/envs/consts.go +++ b/envs/consts.go @@ -1,15 +1,5 @@ package envs -import ( - "regexp" -) - -// Compiled regex patterns. -var ( - reMaybeInt *regexp.Regexp = regexp.MustCompile(`^(?P\+|-)[0-9]+$`) - reMaybeFloat *regexp.Regexp = regexp.MustCompile(`(?P\+|-)?[0-9]+\.[0-9]+$`) -) - var ( StructTagInterpolate string = "envsub" ) diff --git a/envs/funcs.go b/envs/funcs.go index 6f73897..eda66c1 100644 --- a/envs/funcs.go +++ b/envs/funcs.go @@ -1,10 +1,6 @@ package envs import ( - "bytes" - "errors" - "fmt" - "io/ioutil" "os" "reflect" "strings" @@ -14,7 +10,6 @@ import ( "r00t2.io/goutils/structutils" "r00t2.io/sysutils/errs" "r00t2.io/sysutils/internal" - "r00t2.io/sysutils/paths" ) /* @@ -34,7 +29,7 @@ func DefEnv(key, fallback string) (value string) { return } -// DefEnvBlank is like DefEnv but will ADDITIONALLY/ALSO apply fallback if key is *defined/exists but is an empty string*. +// DefEnvBlank is like [DefEnv] but will ADDITIONALLY/ALSO apply fallback if key is *defined/exists but is an empty string*. func DefEnvBlank(key, fallback string) (value string) { value = DefEnv(key, fallback) @@ -45,7 +40,7 @@ func DefEnvBlank(key, fallback string) (value string) { return } -// GetEnvErr returns the value of key if it exists. If it does not exist, err will be an EnvErrNoVal. +// GetEnvErr returns the value of key if it exists. If it does not exist, err will be an [EnvErrNoVal]. func GetEnvErr(key string) (value string, err error) { var exists bool @@ -61,13 +56,13 @@ func GetEnvErr(key string) (value string, err error) { } /* -GetEnvErrNoBlank behaves exactly like GetEnvErr with the +GetEnvErrNoBlank behaves exactly like [GetEnvErr] with the additional stipulation that the value must not be empty. An error for a value that is non-empty but whitespace only (e.g. VARNM="\t") can be returned if ignoreWhitespace == true. -(If it is, an EnvErrNoVal will also be returned.) +(If it is, an [EnvErrNoVal] will also be returned.) */ func GetEnvErrNoBlank(key string, ignoreWhitespace bool) (value string, err error) { @@ -98,7 +93,7 @@ func GetEnvMap() (envVars map[string]string) { var envList []string = os.Environ() - envVars = envListToMap(envList) + envVars = internal.EnvListToMap(envList) return } @@ -108,20 +103,20 @@ GetEnvMapNative returns a map of all environment variables, but attempts to "nat All values are interfaces. It is up to the caller to typeswitch them to proper types. Note that the PATH/Path environment variable (for *Nix and Windows, respectively) will be -a []string (as per GetPathEnv). No other env vars, even if they contain os.PathListSeparator, +a []string (as per [GetPathEnv]). No other env vars, even if they contain [os.PathListSeparator], will be transformed to a slice or the like. If an error occurs during parsing the path env var, it will be rendered as a string. All number types will attempt to be their 64-bit version (i.e. int64, uint64, float64, etc.). If a type cannot be determined for a value, its string form will be used -(as it would be found in GetEnvMap). +(as it would be found in [GetEnvMap]). */ func GetEnvMapNative() (envMap map[string]interface{}) { var stringMap map[string]string = GetEnvMap() - envMap = nativizeEnvMap(stringMap) + envMap = internal.NativizeEnvMap(stringMap) return } @@ -144,7 +139,7 @@ If val is "" and ok is true, this means that one of the specified variable names set but is set to an empty value. If ok is false, none of the specified variables are set. -It is a thin wrapper around GetFirstWithRef. +It is a thin wrapper around [GetFirstWithRef]. */ func GetFirst(varNames []string) (val string, ok bool) { @@ -154,7 +149,7 @@ func GetFirst(varNames []string) (val string, ok bool) { } /* -GetFirstWithRef behaves exactly like GetFirst, but with an additional returned value, idx, +GetFirstWithRef behaves exactly like [GetFirst], but with an additional returned value, idx, which specifies the index in varNames in which a set variable was found. e.g. if: GetFirstWithRef([]string{"FOO", "FOOBAR", "FOOBAZ"}) @@ -182,16 +177,10 @@ func GetFirstWithRef(varNames []string) (val string, ok bool, idx int) { // GetPathEnv returns a slice of the PATH variable's items. func GetPathEnv() (pathList []string, err error) { - var pathVar string = internal.GetPathEnvName() - - pathList = make([]string, 0) - - for _, p := range strings.Split(os.Getenv(pathVar), string(os.PathListSeparator)) { - if err = paths.RealPath(&p); err != nil { - return - } - pathList = append(pathList, p) + if pathList, err = internal.GetPathEnv(); err != nil { + return } + return } @@ -201,60 +190,34 @@ It gets the environment variables of a given process' PID. */ func GetPidEnvMap(pid uint32) (envMap map[string]string, err error) { - var envBytes []byte - var envList []string - var envArr [][]byte - var procPath string - var exists bool - - envMap = make(map[string]string) - - procPath = fmt.Sprintf("/proc/%v/environ", pid) - - if exists, err = paths.RealPathExists(&procPath); err != nil { + if envMap, err = internal.GetPidEnvMap(pid); err != nil { return } - if !exists { - err = errors.New(fmt.Sprintf("information for pid %v does not exist", pid)) - return - } - - if envBytes, err = ioutil.ReadFile(procPath); err != nil { - return - } - - envArr = bytes.Split(envBytes, []byte{0x0}) - envList = make([]string, len(envArr)) - for idx, b := range envArr { - envList[idx] = string(b) - } - - envMap = envListToMap(envList) return } /* -GetPidEnvMapNative, like GetEnvMapNative, returns a map of all environment variables, but attempts to "nativize" them. +GetPidEnvMapNative returns a map of all environment variables (like [GetEnvMapNative]), but attempts to "nativize" them. All values are interfaces. It is up to the caller to typeswitch them to proper types. -See the documentation for GetEnvMapNative for details. +See the documentation for [GetEnvMapNative] for details. */ func GetPidEnvMapNative(pid uint32) (envMap map[string]interface{}, err error) { var stringMap map[string]string - if stringMap, err = GetPidEnvMap(pid); err != nil { + if stringMap, err = internal.GetPidEnvMap(pid); err != nil { return } - envMap = nativizeEnvMap(stringMap) + envMap = internal.NativizeEnvMap(stringMap) return } /* -HasEnv is much like os.LookupEnv, but only returns a boolean for +HasEnv is much like [os.LookupEnv], but only returns a boolean indicating if the environment variable key exists or not. This is useful anywhere you may need to set a boolean in a func call @@ -280,7 +243,7 @@ and performs variable substitution on strings from environment variables. It supports both UNIX/Linux/POSIX syntax formats (e.g. $VARNAME, ${VARNAME}) and, if on Windows, it *additionally* supports the EXPAND_SZ format (e.g. %VARNAME%). -For structs, the tag name used can be changed by setting the StructTagInterpolate +For structs, the tag name used can be changed by setting the [StructTagInterpolate] variable in this submodule; the default is `envsub`. If the tag value is "-", the field will be skipped. For map fields within structs etc., the default is to apply interpolation to both keys and values. @@ -347,9 +310,9 @@ from environment variables. It supports both UNIX/Linux/POSIX syntax formats (e.g. $VARNAME, ${VARNAME}) and, if on Windows, it *additionally* supports the EXPAND_SZ format (e.g. %VARNAME%). -If s is nil, nothing will be done and err will be ErrNilPtr. +If s is nil, nothing will be done and err will be [errs.ErrNilPtr]. -This is a standalone function that is much more performant than Interpolate +This is a standalone function that is much more performant than [Interpolate] at the cost of rigidity. */ func InterpolateString(s *string) (err error) { @@ -369,7 +332,7 @@ func InterpolateString(s *string) (err error) { return } -// interpolateMap is used by Interpolate for maps. v should be a reflect.Value of a map. +// interpolateMap is used by [Interpolate] for maps. v should be a [reflect.Value] of a map. func interpolateMap(v reflect.Value) (err error) { var kVal reflect.Value @@ -467,7 +430,7 @@ func interpolateMap(v reflect.Value) (err error) { return } -// interpolateSlice is used by Interpolate for slices and arrays. v should be a reflect.Value of a slice/array. +// interpolateSlice is used by [Interpolate] for slices and arrays. v should be a [reflect.Value] of a slice/array. func interpolateSlice(v reflect.Value) (err error) { var wg sync.WaitGroup @@ -558,7 +521,7 @@ func interpolateStringReflect(v reflect.Value) (err error) { return } -// interpolateStruct is used by Interpolate for structs. v should be a reflect.Value of a struct. +// interpolateStruct is used by [Interpolate] for structs. v should be a [reflect.Value] of a struct. func interpolateStruct(v reflect.Value) (err error) { var field reflect.StructField diff --git a/envs/utils.go b/envs/utils.go deleted file mode 100644 index ffa53ef..0000000 --- a/envs/utils.go +++ /dev/null @@ -1,87 +0,0 @@ -package envs - -import ( - "strconv" - "strings" - - "r00t2.io/sysutils/internal" -) - -// envListToMap splits a []string of env var keypairs to a map. -func envListToMap(envs []string) (envMap map[string]string) { - - var kv []string - var k, v string - - envMap = make(map[string]string) - - for _, ev := range envs { - kv = strings.SplitN(ev, "=", 2) - // I *think* SplitN does this for me, but... - if len(kv) == 1 { - kv = append(kv, "") - } - k = kv[0] - v = kv[1] - envMap[k] = v - } - - return -} - -// nativizeEnvMap returns a native-typed env map from a string version. -func nativizeEnvMap(stringMap map[string]string) (envMap map[string]interface{}) { - - var pathVar string = internal.GetPathEnvName() - var err error - - envMap = make(map[string]interface{}) - - for k, v := range stringMap { - - // Check for PATH/Path - we handle this uniquely. - if k == pathVar { - if envMap[k], err = GetPathEnv(); err != nil { - envMap[k] = v - err = nil - } - continue - } - - // It might be... - // a float - if reMaybeFloat.MatchString(v) { - if envMap[k], err = strconv.ParseFloat(v, 64); err == nil { - continue - } - err = nil - } - - // an int - if reMaybeInt.MatchString(v) { - if envMap[k], err = strconv.Atoi(v); err == nil { - continue - } - err = nil - } - - // a uint - if envMap[k], err = strconv.ParseUint(v, 10, 64); err == nil { - continue - } else { - err = nil - } - - // a boolean - if envMap[k], err = strconv.ParseBool(v); err == nil { - continue - } else { - err = nil - } - - // ok so... guess it's a string, then. - envMap[k] = v - } - - return -} diff --git a/errs/errs.go b/errs/errs.go index f41c224..72d1ddc 100644 --- a/errs/errs.go +++ b/errs/errs.go @@ -8,3 +8,9 @@ var ( ErrBadType error = errors.New("a bad type was passed") ErrNilPtr error = errors.New("a nil pointer was passed") ) + +var ( + ErrHighPid error = errors.New("a provided PID exceeds the possible maximum") + // ErrInvalidNs indicates an invalid Linux namespace identifier format. + ErrInvalidNs error = errors.New("invalid namespace identifier") +) diff --git a/errs/errs_linux.go b/errs/errs_linux.go deleted file mode 100644 index 4c13a46..0000000 --- a/errs/errs_linux.go +++ /dev/null @@ -1,9 +0,0 @@ -package errs - -import ( - "errors" -) - -var ( - ErrInvalidNs error = errors.New("invalid namespace identifier") -) diff --git a/funcs.go b/funcs.go new file mode 100644 index 0000000..90f4763 --- /dev/null +++ b/funcs.go @@ -0,0 +1,32 @@ +package sysutils + +import ( + "strconv" + "strings" + + "r00t2.io/sysutils/errs" +) + +// NsToInode splits a Linux namespace identifier (e.g. `net:[12345]`) to its type (e.g. `net`) and inode (e.g. `12345`). +func NsToInode(ns string) (typ string, inode uint64, err error) { + + var fields []string + + fields = strings.SplitN(ns, ":", 2) + + if len(fields) != 2 { + err = errs.ErrInvalidNs + return + } + + fields[1] = strings.TrimPrefix(fields[1], "[") + fields[1] = strings.TrimSuffix(fields[1], "]") + + if inode, err = strconv.ParseUint(fields[1], 10, 64); err != nil { + return + } + + typ = fields[0] + + return +} diff --git a/funcs_idstate.go b/funcs_idstate_nix.go similarity index 98% rename from funcs_idstate.go rename to funcs_idstate_nix.go index e40a313..9ff4c51 100644 --- a/funcs_idstate.go +++ b/funcs_idstate_nix.go @@ -1,3 +1,5 @@ +//go:build !(windows || plan9 || wasip1 || js || ios) + package sysutils // Checked consolidates all the provided checked functions. diff --git a/funcs_linux.go b/funcs_linux.go deleted file mode 100644 index feb8782..0000000 --- a/funcs_linux.go +++ /dev/null @@ -1,77 +0,0 @@ -package sysutils - -import ( - "fmt" - "os" - "strconv" - "strings" - - "golang.org/x/sys/unix" - "r00t2.io/sysutils/envs" - "r00t2.io/sysutils/errs" -) - -// GetIDState returns current ID/elevation information. An IDState should *not* be explicitly created/defined. -func GetIDState() (ids IDState) { - - var err error - - ids.RUID, ids.EUID, ids.SUID = unix.Getresuid() - ids.uidsChecked = true - ids.RGID, ids.EGID, ids.SGID = unix.Getresgid() - ids.gidsChecked = true - - ids.SudoEnvCmd = envs.HasEnv("SUDO_COMMAND") - ids.SudoEnvHome = envs.HasEnv("SUDO_HOME") - ids.SudoEnvGroup = envs.HasEnv("SUDO_GID") - ids.SudoEnvUser = envs.HasEnv("SUDO_UID") || envs.HasEnv("SUDO_USER") - if ids.SudoEnvCmd || ids.SudoEnvHome || ids.SudoEnvGroup || ids.SudoEnvUser { - ids.SudoEnvVars = true - } - ids.sudoChecked = true - - // PID 1 will *always* be root, so that can return a false positive for sudo. - if os.Getppid() != 1 { - ids.stat = new(unix.Stat_t) - if err = unix.Stat( - fmt.Sprintf("/proc/%d/stat", os.Getppid()), - ids.stat, - ); err != nil { - err = nil - } else { - ids.PPIDUidMatch = ids.RUID == int(ids.stat.Uid) - ids.ppidUidChecked = true - ids.PPIDGidMatch = ids.RGID == int(ids.stat.Gid) - ids.ppidGidChecked = true - } - } else { - ids.ppidUidChecked = true - ids.ppidGidChecked = true - } - - return -} - -// NsToInode splits a namespace identifier (e.g. `net:[12345]`) to its type (e.g. `net`) and inode (e.g. `12345`). -func NsToInode(ns string) (typ string, inode uint64, err error) { - - var fields []string - - fields = strings.SplitN(ns, ":", 2) - - if len(fields) != 2 { - err = errs.ErrInvalidNs - return - } - - fields[1] = strings.TrimPrefix(fields[1], "[") - fields[1] = strings.TrimSuffix(fields[1], "]") - - if inode, err = strconv.ParseUint(fields[1], 10, 64); err != nil { - return - } - - typ = fields[0] - - return -} diff --git a/funcs_nix.go b/funcs_nix.go new file mode 100644 index 0000000..72610d1 --- /dev/null +++ b/funcs_nix.go @@ -0,0 +1,158 @@ +//go:build !(windows || plan9 || wasip1 || js || ios) + +package sysutils + +import ( + "fmt" + `math` + "os" + + `github.com/shirou/gopsutil/v3/process` + "golang.org/x/sys/unix" + "r00t2.io/sysutils/envs" + `r00t2.io/sysutils/errs` + `r00t2.io/sysutils/internal` +) + +// GetIDState returns current ID/elevation information. An IDState should *not* be explicitly created/defined. +func GetIDState() (ids IDState) { + + var err error + + ids.RUID, ids.EUID, ids.SUID = unix.Getresuid() + ids.uidsChecked = true + ids.RGID, ids.EGID, ids.SGID = unix.Getresgid() + ids.gidsChecked = true + + ids.SudoEnvCmd = envs.HasEnv(envSudoCmd) + ids.SudoEnvHome = envs.HasEnv(envSudoHome) + ids.SudoEnvGroup = envs.HasEnv(envSudoGrp) + ids.SudoEnvUser = envs.HasEnv(envSudoUid) || envs.HasEnv(envSudoUname) + if ids.SudoEnvCmd || ids.SudoEnvHome || ids.SudoEnvGroup || ids.SudoEnvUser { + ids.SudoEnvVars = true + } + ids.sudoChecked = true + + // PID 1 will *always* be root, so that can return a false positive for sudo. + if os.Getppid() != 1 { + ids.stat = new(unix.Stat_t) + if err = unix.Stat( + fmt.Sprintf("/proc/%d/stat", os.Getppid()), + ids.stat, + ); err != nil { + err = nil + } else { + ids.PPIDUidMatch = ids.RUID == int(ids.stat.Uid) + ids.ppidUidChecked = true + ids.PPIDGidMatch = ids.RGID == int(ids.stat.Gid) + ids.ppidGidChecked = true + } + } else { + ids.ppidUidChecked = true + ids.ppidGidChecked = true + } + + return +} + +// GetIDStateProc is like GetIDState but for an arbitrary PID. +func GetIDStateProc(pid uint32) (ids IDState, err error) { + + var i32 int32 + var ints []int32 + var sudoUid bool + var sudoUname bool + var proc *process.Process + var envMap map[string]string + + if pid > math.MaxInt32 { + err = errs.ErrHighPid + return + } + + ids = IDState{ + RUID: -1, + EUID: -1, + SUID: -1, + RGID: -1, + EGID: -1, + SGID: -1, + } + + if proc, err = process.NewProcess(int32(pid)); err != nil { + return + } + + // UIDs + if ints, err = proc.Uids(); err != nil { + return + } + if ints != nil && len(ints) > 0 { + if len(ints) >= 3 { + ids.SUID = int(ints[2]) + } + if len(ints) >= 2 { + ids.EUID = int(ints[1]) + } + if len(ints) >= 1 { + ids.RUID = int(ints[0]) + } + } + ids.uidsChecked = true + + // GIDs + if ints, err = proc.Gids(); err != nil { + return + } + if ints != nil && len(ints) > 0 { + if len(ints) >= 3 { + ids.SGID = int(ints[2]) + } + if len(ints) >= 2 { + ids.EGID = int(ints[1]) + } + if len(ints) >= 1 { + ids.SGID = int(ints[0]) + } + } + ids.gidsChecked = true + + // Sudo (env check) + if envMap, err = internal.GetPidEnvMap(uint32(pid)); err != nil { + return + } + _, ids.SudoEnvCmd = envMap[envSudoCmd] + _, ids.SudoEnvHome = envMap[envSudoHome] + _, ids.SudoEnvGroup = envMap[envSudoGrp] + _, sudoUid = envMap[envSudoUid] + _, sudoUname = envMap[envSudoUname] + ids.SudoEnvUser = sudoUid || sudoUname + if ids.SudoEnvCmd || ids.SudoEnvHome || ids.SudoEnvGroup || ids.SudoEnvUser { + ids.SudoEnvVars = true + } + ids.sudoChecked = true + + // Sudo (PPID check) + if i32, err = proc.Ppid(); err != nil { + return + } + if i32 != 1 { + ids.stat = new(unix.Stat_t) + if err = unix.Stat( + fmt.Sprintf("/proc/%d/stat", i32), + ids.stat, + ); err != nil { + err = nil + } else { + ids.PPIDUidMatch = ids.RUID == int(ids.stat.Uid) + ids.ppidUidChecked = true + ids.PPIDGidMatch = ids.SGID == int(ids.stat.Gid) + ids.ppidGidChecked = true + } + } else { + ids.ppidUidChecked = true + ids.ppidGidChecked = true + } + + return +} diff --git a/go.mod b/go.mod index 3c3d5e1..b58ca33 100644 --- a/go.mod +++ b/go.mod @@ -6,18 +6,20 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/djherbis/times v1.6.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 - github.com/shirou/gopsutil/v4 v4.25.7 - golang.org/x/sync v0.16.0 - golang.org/x/sys v0.35.0 + github.com/shirou/gopsutil/v3 v3.24.5 + github.com/shirou/gopsutil/v4 v4.25.10 + golang.org/x/sync v0.17.0 + golang.org/x/sys v0.37.0 honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 - r00t2.io/goutils v1.9.6 + r00t2.io/goutils v1.10.3 ) require ( - github.com/ebitengine/purego v0.8.4 // indirect + github.com/ebitengine/purego v0.9.1 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect + github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/shoenig/go-m1cpu v0.1.7 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect diff --git a/go.sum b/go.sum index 124e109..5b50af0 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= -github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= -github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -11,46 +11,39 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= -github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= -github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= -github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k= +github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= -github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= -github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= -github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shirou/gopsutil/v4 v4.25.10 h1:at8lk/5T1OgtuCp+AwrDofFRjnvosn0nkN2OLQ6g8tA= +github.com/shirou/gopsutil/v4 v4.25.10/go.mod h1:+kSwyC8DRUD9XXEHCAFjK+0nuArFJM0lva+StQAcskM= +github.com/shoenig/go-m1cpu v0.1.7 h1:C76Yd0ObKR82W4vhfjZiCp0HxcSZ8Nqd84v+HZ0qyI0= +github.com/shoenig/go-m1cpu v0.1.7/go.mod h1:KkDOw6m3ZJQAPHbrzkZki4hnx+pDRR1Lo+ldA56wD5w= +github.com/shoenig/test v1.7.0 h1:eWcHtTXa6QLnBvm0jgEabMRN/uJ4DMV3M8xUGgRkZmk= +github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 h1:FW42yWB1sGClqswyHIB68wo0+oPrav1IuQ+Tdy8Qp8E= honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8/go.mod h1:44w9OfBSQ9l3o59rc2w3AnABtE44bmtNnRMNC7z+oKE= -r00t2.io/goutils v1.9.2 h1:1rcDgJ3MorWVBmZSvLpbAUNC+J+ctRfJQq5Wliucjww= -r00t2.io/goutils v1.9.2/go.mod h1:76AxpXUeL10uFklxRB11kQsrtj2AKiNm8AwG1bNoBCA= -r00t2.io/goutils v1.9.3 h1:pR9Ggu5JBpVjfrqNBrZg9bZpKan0TCcwt3MXrSdkhLo= -r00t2.io/goutils v1.9.3/go.mod h1:76AxpXUeL10uFklxRB11kQsrtj2AKiNm8AwG1bNoBCA= -r00t2.io/goutils v1.9.4 h1:+Bm72mKhgXs6DRtU3P4sBjqUNwAKAFfdF9lx5bomwQY= -r00t2.io/goutils v1.9.4/go.mod h1:76AxpXUeL10uFklxRB11kQsrtj2AKiNm8AwG1bNoBCA= -r00t2.io/goutils v1.9.5 h1:tIBtXKbGPLCkdhHZSESdTZ2QzC1e+8jDToNr/BauWe0= -r00t2.io/goutils v1.9.5/go.mod h1:76AxpXUeL10uFklxRB11kQsrtj2AKiNm8AwG1bNoBCA= -r00t2.io/goutils v1.9.6/go.mod h1:76AxpXUeL10uFklxRB11kQsrtj2AKiNm8AwG1bNoBCA= +r00t2.io/goutils v1.10.3 h1:GmEtsM/nrrVWooYJllXDRsgInobEinv2dn5ccU4zGAA= +r00t2.io/goutils v1.10.3/go.mod h1:76AxpXUeL10uFklxRB11kQsrtj2AKiNm8AwG1bNoBCA= diff --git a/internal/consts.go b/internal/consts.go index 268ac23..c35e927 100644 --- a/internal/consts.go +++ b/internal/consts.go @@ -1,8 +1,18 @@ package internal +import ( + `regexp` +) + // OS-specific path environment variable name. The default is "PATH". var ( pathEnvVarName map[string]string = map[string]string{ "windows": "Path", } ) + +// Compiled regex patterns. +var ( + reMaybeInt *regexp.Regexp = regexp.MustCompile(`^(?P\+|-)[0-9]+$`) + reMaybeFloat *regexp.Regexp = regexp.MustCompile(`(?P\+|-)?[0-9]+\.[0-9]+$`) +) diff --git a/internal/funcs.go b/internal/funcs.go new file mode 100644 index 0000000..3f58864 --- /dev/null +++ b/internal/funcs.go @@ -0,0 +1,160 @@ +package internal + +import ( + `bytes` + `errors` + `fmt` + `os` + "runtime" + "strconv" + "strings" + + `r00t2.io/sysutils/paths` +) + +// EnvListToMap splits a []string of env var keypairs to a map. +func EnvListToMap(envs []string) (envMap map[string]string) { + + var kv []string + var k, v string + + envMap = make(map[string]string) + + for _, ev := range envs { + kv = strings.SplitN(ev, "=", 2) + // I *think* SplitN does this for me, but... + if len(kv) == 1 { + kv = append(kv, "") + } + k = kv[0] + v = kv[1] + envMap[k] = v + } + + return +} + +// GetPathEnv returns a slice of the PATH variable's items. +func GetPathEnv() (pathList []string, err error) { + + var pathVar string = GetPathEnvName() + + pathList = make([]string, 0) + + for _, p := range strings.Split(os.Getenv(pathVar), string(os.PathListSeparator)) { + if err = paths.RealPath(&p); err != nil { + return + } + pathList = append(pathList, p) + } + return +} + +/* +GetPidEnvMap will only work on *NIX-like systems with procfs. +It gets the environment variables of a given process' PID. +*/ +func GetPidEnvMap(pid uint32) (envMap map[string]string, err error) { + + var envBytes []byte + var envList []string + var envArr [][]byte + var procPath string + var exists bool + + envMap = make(map[string]string) + + procPath = fmt.Sprintf("/proc/%v/environ", pid) + + if exists, err = paths.RealPathExists(&procPath); err != nil { + return + } + if !exists { + err = errors.New(fmt.Sprintf("information for pid %v does not exist", pid)) + return + } + + if envBytes, err = os.ReadFile(procPath); err != nil { + return + } + + envArr = bytes.Split(envBytes, []byte{0x0}) + envList = make([]string, len(envArr)) + for idx, b := range envArr { + envList[idx] = string(b) + } + + envMap = EnvListToMap(envList) + + return +} + +// GetPathEnvName gets the OS-specific path environment variable name. +func GetPathEnvName() (envVarName string) { + + var ok bool + + if envVarName, ok = pathEnvVarName[runtime.GOOS]; !ok { + // *NIX/the default. + envVarName = "PATH" + } + + return +} + +// NativizeEnvMap returns a native-typed env map from a string version. +func NativizeEnvMap(stringMap map[string]string) (envMap map[string]interface{}) { + + var pathVar string = GetPathEnvName() + var err error + + envMap = make(map[string]interface{}) + + for k, v := range stringMap { + + // Check for PATH/Path - we handle this uniquely. + if k == pathVar { + if envMap[k], err = GetPathEnv(); err != nil { + envMap[k] = v + err = nil + } + continue + } + + // It might be... + // a float + if reMaybeFloat.MatchString(v) { + if envMap[k], err = strconv.ParseFloat(v, 64); err == nil { + continue + } + err = nil + } + + // an int + if reMaybeInt.MatchString(v) { + if envMap[k], err = strconv.Atoi(v); err == nil { + continue + } + err = nil + } + + // a uint + if envMap[k], err = strconv.ParseUint(v, 10, 64); err == nil { + continue + } else { + err = nil + } + + // a boolean + if envMap[k], err = strconv.ParseBool(v); err == nil { + continue + } else { + err = nil + } + + // ok so... guess it's a string, then. + envMap[k] = v + } + + return +} diff --git a/internal/utils.go b/internal/utils.go deleted file mode 100644 index 657a4af..0000000 --- a/internal/utils.go +++ /dev/null @@ -1,18 +0,0 @@ -package internal - -import ( - `runtime` -) - -// GetPathEnvName gets the OS-specific path environment variable name. -func GetPathEnvName() (envVarName string) { - - var ok bool - - if envVarName, ok = pathEnvVarName[runtime.GOOS]; !ok { - // *NIX/the default. - envVarName = "PATH" - } - - return -} diff --git a/types_linux.go b/types_nix.go similarity index 96% rename from types_linux.go rename to types_nix.go index 2c69a7f..b26f6a5 100644 --- a/types_linux.go +++ b/types_nix.go @@ -1,3 +1,5 @@ +//go:build !(windows || plan9 || wasip1 || js || ios) + package sysutils import (