Compare commits

...

7 Commits

Author SHA1 Message Date
brent saner 8e70ffac38 v1.16.3
ADDED:
* envs.MustEnv(), envs.MustEnvNoBlank()

UPDATED:
* dep mods, Go version
2026-07-09 19:32:21 -04:00
brent saner d248d72b5a v1.16.2
BUMPED:
* deps resynced to goutils
2026-01-30 20:23:35 -05:00
brent saner 002067d3ac v1.16.1
FIXED:
* Some IDState things on *NIX-likes that have no clue what they're doing
2026-01-30 06:37:44 -05:00
brent saner ed44eb6230 v1.16.0
ADDED:
* Dummy implementation for IDState on Windows
2026-01-29 06:05:13 -05:00
brent saner 1130fb028d v1.15.1
UPDATED:
* Dep/go ver bumps
* gated some WIP stuff
2025-12-18 04:54:11 -05:00
brent saner 803be548cf v1.15.0
* IDState cleaned up. Should work on all *NIXes now.
* Can now get IDState of arbitrary PID.
* Shuffled some env stuff around.
2025-11-07 23:11:47 -05:00
brent saner 675a10addd v1.14.5
FIXED:
* Renamed sysutils.NsToFD to sysutils.NsToInode because that number's an
  inode, not an FD. D'oh.
2025-10-08 16:38:44 -04:00
49 changed files with 1576 additions and 317 deletions
View File
+47 -12
View File
@@ -1,28 +1,63 @@
- refactor the elevation detection stuff. I'm not terribly happy with it. - refactor the elevation detection stuff. I'm not terribly happy with it.
- password generator utility/library - add generic password prompt func that takes in a `prompt []byte` param and returns `(passwd []byte, err error)`
```
func getPasswd(prompt []byte) (passwd []byte, err error) {
var b []byte
var newLnd bool
var origIn *term.State
var origOut *term.State
var fdIn int = int(os.Stdin.Fd())
var fdOut int = int(os.Stdout.Fd())
if origIn, err = term.GetState(fdIn); err != nil {
return
}
if origOut, err = term.GetState(fdOut); err != nil {
return
}
defer func() {
var tErr error
if !newLnd {
fmt.Println()
}
if tErr = term.Restore(fdIn, origIn); tErr != nil {
return
}
if tErr = term.Restore(fdOut, origOut); tErr != nil {
return
}
}()
os.Stdout.Write(prompt)
if b, err = term.ReadPassword(fdIn); err != nil {
return
}
passwd = b
fmt.Println()
newLnd = true
return
}
```
- password generator/hash+salter and "checksum/hash guesser" utility/library
-- incorporate with r00t2.io/pwgen -- incorporate with r00t2.io/pwgen
-- incorporate with https://github.com/tredoe/osutil ? -- incorporate with https://github.com/tredoe/osutil ?
-- cli flag to dump flat hashes too (https://github.com/hlandau/passlib and others soon in pwgen) -- cli flag to dump flat hashes too (https://github.com/hlandau/passlib and others soon in pwgen)
- auger needs to be build-constrained to linux. - auger needs to be build-constrained to linux.
-- (i think i fixed this)
- unit tests - unit tests
- constants/vars for errors - constants/vars for errors
- func and struct to return segregated system-level env vars vs. user env vars (mostly usable on windows) (see envs/.TODO.go.UNFINISHED) - func and struct to return segregated system-level env vars vs. user env vars (mostly usable on windows) (see envs/.TODO.go.UNFINISHED)
-- https://www3.ntu.edu.sg/home/ehchua/programming/howto/Environment_Variables.html
-- windows:
--- https://docs.microsoft.com/en-us/windows/deployment/usmt/usmt-recognized-environment-variables
--- https://pureinfotech.com/list-environment-variables-windows-10/
--- https://gist.github.com/RebeccaWhit3/5dad8627b8227142e1bea432db3f8824
--- https://ss64.com/nt/syntax-variables.html
-- linux/XDG:
--- https://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html
--- https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
-- macOS:
--- https://ss64.com/osx/syntax-env_vars.html
- validator for windows usernames, domains, etc. (for *NIX, https://unix.stackexchange.com/a/435120/284004) - validator for windows usernames, domains, etc. (for *NIX, https://unix.stackexchange.com/a/435120/284004)
-- https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/naming-conventions-for-computer-domain-site-ou -- https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/naming-conventions-for-computer-domain-site-ou
Executable
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
# go tool dist list for all valid GOOS/GOARCH targets.
for tgt in $(go tool dist list);
do
o="$(echo ${tgt} | cut -f1 -d '/')"
a="$(echo ${tgt} | cut -f2 -d '/')"
out="$(env GOOS=${o} GOARCH=${a} go build ./... 2>&1)"
ret=${?}
if [ $ret -ne 0 ];
then
echo "OS: ${o}"
echo "ARCH: ${a}"
echo "${out}"
echo
echo
fi
done
+11
View File
@@ -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"
)
+8
View File
@@ -0,0 +1,8 @@
//go:build !(windows || plan9 || wasip1 || js || ios)
/*
Package sysutils provides [IDState], a collection of process information as it relates to user/group access/membership.
Take note that this module's main functionality is in its child packages.
*/
package sysutils
+9
View File
@@ -0,0 +1,9 @@
/*
Package sysutils provides [IDState], a collection of process information as it relates to user/group access/membership.
NOTE: an [IDState], and all the functions/methods thereof, are fairly useless on Windows as all methods are NO-OPs and
[GetIDState]/[GetIDStateProc] just return an empty [IDState]. It is primarily provided on Windows for easier cross-platform development.
Take note that this module's main functionality is in its child packages.
*/
package sysutils
+7
View File
@@ -0,0 +1,7 @@
/*
Package osenvs contains information (and, in some cases, defaults and associated methods) for
[environment variables] ("env vars") as provided by/as part of the OS.
[environment variables]: https://www3.ntu.edu.sg/home/ehchua/programming/howto/Environment_Variables.html
*/
package osenvs
+56
View File
@@ -0,0 +1,56 @@
package osenvs
type (
/*
AppleEnv are default environment variables found in all macOS instances.
See also the [ss64 article].
macOS also has [POSIXEnv] env vars.
[ss64 article]: https://ss64.com/osx/syntax-env_vars.html
*/
AppleEnv string
/*
MSEnv are default environment variables found in Windows instances.
These include:
* [USMT environment variables]
* ONLY the [Environment Variables] for Poweshell
** Thus no Powershell ["Automatic variables"] or ["Preference variables"]
* standard system environment variables and user environment variables
See also the [ss64 article] and the [compiled Windows 10 list] and [Windows 10/11 list].
[USMT environment variables]: https://docs.microsoft.com/en-us/windows/deployment/usmt/usmt-recognized-environment-variables
[Environment Variables]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-7.5
["Automatic variables"]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables
["Preference variables"]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables
[ss64 article]: https://ss64.com/nt/syntax-variables.html
[compiled Windows 10 list]: https://gist.github.com/RebeccaWhit3/5dad8627b8227142e1bea432db3f8824
[Windows 10/11 list]: https://pureinfotech.com/list-environment-variables-windows-10/
*/
MSEnv string
/*
POSIXEnv are environment variables defined by/in [POSIX.1-2024] § [8. Environment Variables].
[POSIX.1-2024]: https://pubs.opengroup.org/onlinepubs/9799919799/
[8. Environment Variables]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap08.html
*/
POSIXEnv string
/*
XDGEnv are environment variables defined by the [FreeDesktop] [spec] for the
[XDG Base Directory Specification] § [3 Environment Variables].
Generally these are only found/expected on Linux, and only a subset of them at that.
Linux also has [POSIXEnv] env vars.
[FreeDesktop]: https://www.freedesktop.org/wiki/
[spec]: https://specifications.freedesktop.org/
[XDG Base Directory Specification]: https://specifications.freedesktop.org/basedir/latest/#variables
[3 Environment Variables]: https://specifications.freedesktop.org/basedir/latest/#variables
*/
XDGEnv string
)
+78
View File
@@ -0,0 +1,78 @@
- func and struct to return segregated system-level env vars vs. user env vars (mostly usable on windows) (see .TODO.go.UNFINISHED and ITERATION/EXPECTED DEFAULTS below)
- - for envs, provide helpers: UsernmEnv and PasswdEnv
-- each takes an argument, envVar, that specifies an env var name to lookup via envs.HasEnv.
-- returns (val string, ok bool, err error)
-- for PasswdEnv:
--- param `ask bool`; if envVar doesn't exist and `ask` is true, prompt user for password (https://pkg.go.dev/golang.org/x/term#ReadPassword)
--- param `prompt []byte`; if empty, use generic "enter password (will not echo back)", otherwise use `prompt` as prompt (only prompted if `ask` == true)
--- param `confirm bool`; if true, prompt for user confirmation
-- for UsernmEnv:
--- param `ask int`;
if > 0, always prompt user (using `prompt`) if EnvVar doesn't exist.
if == 0, use current username if `current == true` and prompt for confirmation/override (if `current == false`, always prompt).
if < 0, never prompt and fallback to current username if `current == true`, return empty/not ok if `current == false`.
--- param `current bool`; if envVar doesn't exist and `current` is true,
--- param `prompt []byte`; if empty, use generic "enter username" (use fmt.Scanln), otherwise use `prompt` as prompt (only prompted if `ask` >= 0)
#################################
## ITERATION/EXPECTED DEFAULTS ##
#################################
macOS:
HOME
LOGNAME
OLDPWD
PATH
PWD
SHELL
SHLVL
SSH_CLIENT
SSH_CONNECTION
SSH_TTY
TERM
TMPDIR
USER
_
e.g.
HOME=/Users/bts
LOGNAME=bts
OLDPWD=/Users/bts
PATH=/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin
PWD=/Users/bts
SHELL=/bin/zsh
SHLVL=1
SSH_CLIENT=192.168.2.99 39690 22
SSH_CONNECTION=192.168.2.99 39690 192.168.2.148 22
SSH_TTY=/dev/ttys000
TERM=xterm-256color
TMPDIR=/var/folders/qv/bm6dlyd94x5fs6qpkfpj0jsr0000gq/T/
USER=bts
_=/usr/bin/env
Windows:
https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/shell/csidl.md
https://learn.microsoft.com/en-us/windows/deployment/usmt/usmt-recognized-environment-variables
https://learn.microsoft.com/en-us/windows/win32/procthread/environment-variables
https://cdn.callback.com/help/CXH/RefDoc/html/T_callback_ShellBoost_Core_WindowsShell_CSIDL.htm
https://ss64.com/ps/syntax-env.html
https://ss64.com/nt/syntax-variables.html
Linux:
https://specifications.freedesktop.org/basedir/latest/
https://ss64.com/bash/syntax-variables.html
environ(7)?
Might need to do an LFS install and run `env` there.
get list of standard envs in addn to POSIX (see below)
macOS + Linux:
https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap08.html
enumerate default bash and zsh env vars on each, and switch on $SHELL.
macOS:
https://developer.apple.com/documentation/xcode/environment-variable-reference (XCode only?)
https://ss64.com/mac/syntax-variables.html
needs `env` in clean environment
-10
View File
@@ -1,15 +1,5 @@
package envs package envs
import (
"regexp"
)
// Compiled regex patterns.
var (
reMaybeInt *regexp.Regexp = regexp.MustCompile(`^(?P<sign>\+|-)[0-9]+$`)
reMaybeFloat *regexp.Regexp = regexp.MustCompile(`(?P<sign>\+|-)?[0-9]+\.[0-9]+$`)
)
var ( var (
StructTagInterpolate string = "envsub" StructTagInterpolate string = "envsub"
) )
+48 -61
View File
@@ -1,10 +1,6 @@
package envs package envs
import ( import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os" "os"
"reflect" "reflect"
"strings" "strings"
@@ -14,7 +10,6 @@ import (
"r00t2.io/goutils/structutils" "r00t2.io/goutils/structutils"
"r00t2.io/sysutils/errs" "r00t2.io/sysutils/errs"
"r00t2.io/sysutils/internal" "r00t2.io/sysutils/internal"
"r00t2.io/sysutils/paths"
) )
/* /*
@@ -34,7 +29,7 @@ func DefEnv(key, fallback string) (value string) {
return 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) { func DefEnvBlank(key, fallback string) (value string) {
value = DefEnv(key, fallback) value = DefEnv(key, fallback)
@@ -45,7 +40,7 @@ func DefEnvBlank(key, fallback string) (value string) {
return 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) { func GetEnvErr(key string) (value string, err error) {
var exists bool 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. 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") An error for a value that is non-empty but whitespace only (e.g. VARNM="\t")
can be returned if ignoreWhitespace == true. 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) { 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() var envList []string = os.Environ()
envVars = envListToMap(envList) envVars = internal.EnvListToMap(envList)
return 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. 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 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. 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. 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.). 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 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{}) { func GetEnvMapNative() (envMap map[string]interface{}) {
var stringMap map[string]string = GetEnvMap() var stringMap map[string]string = GetEnvMap()
envMap = nativizeEnvMap(stringMap) envMap = internal.NativizeEnvMap(stringMap)
return 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 set but is set to an empty value. If ok is false, none of the specified variables
are set. are set.
It is a thin wrapper around GetFirstWithRef. It is a thin wrapper around [GetFirstWithRef].
*/ */
func GetFirst(varNames []string) (val string, ok bool) { 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: which specifies the index in varNames in which a set variable was found. e.g. if:
GetFirstWithRef([]string{"FOO", "FOOBAR", "FOOBAZ"}) 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. // GetPathEnv returns a slice of the PATH variable's items.
func GetPathEnv() (pathList []string, err error) { func GetPathEnv() (pathList []string, err error) {
var pathVar string = internal.GetPathEnvName() if pathList, err = internal.GetPathEnv(); err != nil {
pathList = make([]string, 0)
for _, p := range strings.Split(os.Getenv(pathVar), string(os.PathListSeparator)) {
if err = paths.RealPath(&p); err != nil {
return return
} }
pathList = append(pathList, p)
}
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) { func GetPidEnvMap(pid uint32) (envMap map[string]string, err error) {
var envBytes []byte if envMap, err = internal.GetPidEnvMap(pid); err != nil {
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 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 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. 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) { func GetPidEnvMapNative(pid uint32) (envMap map[string]interface{}, err error) {
var stringMap map[string]string var stringMap map[string]string
if stringMap, err = GetPidEnvMap(pid); err != nil { if stringMap, err = internal.GetPidEnvMap(pid); err != nil {
return return
} }
envMap = nativizeEnvMap(stringMap) envMap = internal.NativizeEnvMap(stringMap)
return 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. if the environment variable key exists or not.
This is useful anywhere you may need to set a boolean in a func call 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, 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 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`. variable in this submodule; the default is `envsub`.
If the tag value is "-", the field will be skipped. 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. 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, 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 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. at the cost of rigidity.
*/ */
func InterpolateString(s *string) (err error) { func InterpolateString(s *string) (err error) {
@@ -369,7 +332,31 @@ func InterpolateString(s *string) (err error) {
return return
} }
// interpolateMap is used by Interpolate for maps. v should be a reflect.Value of a map. // MustEnv wraps [GetEnvErr], but will panic on non-nil err.
func MustEnv(key string) (value string) {
var err error
if value, err = GetEnvErr(key); err != nil {
panic(err)
}
return
}
// MustEnvNoBlank wraps [GetEnvErrNoBlank], but will panic on non-nil err.
func MustEnvNoBlank(key string, ignoreWhitespace bool) (value string) {
var err error
if value, err = GetEnvErrNoBlank(key, ignoreWhitespace); err != nil {
panic(err)
}
return
}
// interpolateMap is used by [Interpolate] for maps. v should be a [reflect.Value] of a map.
func interpolateMap(v reflect.Value) (err error) { func interpolateMap(v reflect.Value) (err error) {
var kVal reflect.Value var kVal reflect.Value
@@ -467,7 +454,7 @@ func interpolateMap(v reflect.Value) (err error) {
return 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) { func interpolateSlice(v reflect.Value) (err error) {
var wg sync.WaitGroup var wg sync.WaitGroup
@@ -558,7 +545,7 @@ func interpolateStringReflect(v reflect.Value) (err error) {
return 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) { func interpolateStruct(v reflect.Value) (err error) {
var field reflect.StructField var field reflect.StructField
-87
View File
@@ -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
}
+6
View File
@@ -8,3 +8,9 @@ var (
ErrBadType error = errors.New("a bad type was passed") ErrBadType error = errors.New("a bad type was passed")
ErrNilPtr error = errors.New("a nil pointer 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")
)
-9
View File
@@ -1,9 +0,0 @@
package errs
import (
"errors"
)
var (
ErrInvalidNs error = errors.New("invalid namespace identifier")
)
+32
View File
@@ -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
}
+3 -1
View File
@@ -1,3 +1,5 @@
//go:build !(windows || plan9 || wasip1 || js || ios)
package sysutils package sysutils
// Checked consolidates all the provided checked functions. // Checked consolidates all the provided checked functions.
@@ -138,7 +140,7 @@ func (i *IDState) PPIDUIDsChecked() (checked bool) {
return return
} }
// SudoChecked is true if SudoEnvVars can be trusted // SudoChecked is true if SudoEnvVars can be trusted.
func (i *IDState) SudoChecked() (checked bool) { func (i *IDState) SudoChecked() (checked bool) {
if i == nil { if i == nil {
+72
View File
@@ -0,0 +1,72 @@
//go:build windows
package sysutils
// Checked consolidates all the provided checked functions. This is a NO-OP on Windows.
func (i *IDState) Checked() (checked bool) {
return
}
/*
IsReal consolidates all the elevation/dropped-privs checks into a single method.
This is a NO-OP on Windows.
*/
func (i *IDState) IsReal(real bool) {
return
}
/*
IsSudoGroup is true if any of the group sudo env vars are set,
or the parent process has a different group (and is not PID 1).
This is a NO-OP on Windows.
*/
func (i *IDState) IsSudoGroup() (sudo bool) {
return
}
/*
IsSudoUser is true if any of the user sudo env vars are set,
or the parent process has a different owner (and is not PID 1).
This is a NO-OP on Windows.
*/
func (i *IDState) IsSudoUser() (sudo bool) {
return
}
// IsSuid is true if the RUID does not match EUID or SUID. This is a NO-OP on Windows.
func (i *IDState) IsSuid() (suid bool) {
return
}
// IsSgid is true if the RGID does not match EGID or SGID. This is a NO-OP on Windows.
func (i *IDState) IsSgid() (sgid bool) {
return
}
// GIDsChecked is true if the GIDs presented can be trusted. This is a NO-OP on Windows.
func (i *IDState) GIDsChecked() (checked bool) {
return
}
// PPIDGIDsChecked is true if PPIDGidMatch can be trusted. This is a NO-OP on Windows.
func (i *IDState) PPIDGIDsChecked() (checked bool) {
return
}
// PPIDUIDsChecked is true if PPIDUidMatch can be trusted. This is a NO-OP on Windows.
func (i *IDState) PPIDUIDsChecked() (checked bool) {
return
}
// SudoChecked is true if SudoEnvVars can be trusted. This is a NO-OP on Windows.
func (i *IDState) SudoChecked() (checked bool) {
return
}
// UIDsChecked is true if the UIDs presented can be trusted. This is a NO-OP on Windows.
func (i *IDState) UIDsChecked() (checked bool) {
return
}
-77
View File
@@ -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
}
// NsToFD splits a namespace identifier (e.g. `net:[12345]`) to its type (e.g. `net`) and FD (e.g. `12345`).
func NsToFD(ns string) (typ string, fd 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 fd, err = strconv.ParseUint(fields[1], 10, 64); err != nil {
return
}
typ = fields[0]
return
}
+158
View File
@@ -0,0 +1,158 @@
//go:build !(windows || plan9 || wasip1 || js || ios)
package sysutils
import (
"fmt"
`math`
"os"
`github.com/shirou/gopsutil/v4/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 = getresuid()
ids.uidsChecked = true
ids.RGID, ids.EGID, ids.SGID = 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 []uint32
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
}
+31
View File
@@ -0,0 +1,31 @@
//go:build !(windows || plan9 || wasip1 || js || ios) && (aix || darwin || dragonfly || freebsd || illumos || netbsd || solaris)
package sysutils
import (
`golang.org/x/sys/unix`
)
// getresgid spoofs unix.Getresgid, as this file targets platforms that do not support it.
func getresgid() (rgid, egid, sgid int) {
// rgid, egid, sgid = unix.Getresgid()
rgid = unix.Getgid()
egid = unix.Getegid()
sgid = -1
return
}
// getresuid spoofs unix.Getresuid, as this file targets platforms that do not support it.
func getresuid() (ruid, euid, suid int) {
// ruid, euid, suid = unix.Getresuid()
ruid = unix.Getuid()
euid = unix.Geteuid()
suid = -1
return
}
+23
View File
@@ -0,0 +1,23 @@
//go:build !(windows || plan9 || wasip1 || js || ios) && !(aix || darwin || dragonfly || freebsd || illumos || netbsd || solaris)
package sysutils
import (
`golang.org/x/sys/unix`
)
// getresgid wraps unix.Getresgid, as this file targets platforms that fully support it.
func getresgid() (rgid, egid, sgid int) {
rgid, egid, sgid = unix.Getresgid()
return
}
// getresuid wraps unix.Getresuid, as this file targets platforms that fully support it.
func getresuid() (ruid, euid, suid int) {
ruid, euid, suid = unix.Getresuid()
return
}
+13
View File
@@ -0,0 +1,13 @@
//go:build windows
package sysutils
// GetIDState returns current ID/elevation information. This is a NO-OP on Windows.
func GetIDState() (ids IDState) {
return
}
// GetIDStateProc is like GetIDState but for an arbitrary PID. This is a NO-OP on Windows.
func GetIDStateProc(pid uint32) (ids IDState, err error) {
return
}
+17
View File
@@ -0,0 +1,17 @@
- clean up from PoC into unit tests and exported functions/constants etc.
- "Class" and "String" methods for each of the standard named resolutions
refs:
https://en.wikipedia.org/wiki/Display_resolution
https://en.wikipedia.org/wiki/Display_aspect_ratio
https://en.wikipedia.org/wiki/Display_resolution_standards
https://en.wikipedia.org/wiki/Video_Graphics_Array
https://en.wikipedia.org/wiki/Extended_Graphics_Array
https://en.wikipedia.org/wiki/Super_VGA
https://en.wikipedia.org/wiki/Digital_Cinema_Package
https://www.lenovo.com/us/en/glossary/what-is-sxga/
https://www.corsair.com/us/en/explorer/gamer/monitors/wuxga-resolution-explained/
https://www.lenovo.com/us/en/glossary/wqxga/
https://www.strong-eu.com/blog/hd-fhd-uhd-4k-what-are-the-differences
https://www.unravel.com.au/aspect-ratio-cheat-sheet
https://www.wearethefirehouse.com/aspect-ratio-cheat-sheet
+652
View File
@@ -0,0 +1,652 @@
package main
import (
"fmt"
`math`
`slices`
"strconv"
`strings`
mapset `github.com/deckarep/golang-set/v2`
)
type (
AspectRatio struct {
ar float64
arStr string
arW float64
arH float64
gcd int
res *Resolution
}
Resolution struct {
w int
h int
ar *AspectRatio
}
)
func (a *AspectRatio) AsFloat() (ar float64) {
if a == nil {
return
}
if a.ar == 0 {
a.ar = float64(a.res.w) / float64(a.res.h)
}
ar = a.ar
return
}
func (a *AspectRatio) AsSlice() (ar [2]float64) {
if a.arW == 0 {
a.arW = float64(a.res.w) / float64(a.GCD())
}
if a.arH == 0 {
a.arH = float64(a.res.h) / float64(a.GCD())
}
ar = [2]float64{
a.arW,
a.arH,
}
return
}
/*
Cmp is a comparison function that can be used for sorting.
The return cmp value has the same meaning as in [slices.SortFunc]:
If a comes before other, cmp == -1
If a comes after other, cmp == 1
If a and other are equal, cmp == 0
*/
func (a *AspectRatio) Cmp(other AspectRatio) (cmp int) {
var aSlice [2]float64
var oSlice [2]float64
if a == nil {
return
}
aSlice = a.AsSlice()
oSlice = other.AsSlice()
if aSlice[0] == oSlice[0] && aSlice[1] == oSlice[1] {
return
}
if aSlice[0] == oSlice[0] {
if aSlice[1] < oSlice[1] {
cmp = -1
} else {
cmp = 1
}
} else {
if aSlice[0] < oSlice[0] {
cmp = -1
} else {
cmp = 1
}
}
return
}
func (a *AspectRatio) Equals(other AspectRatio) (isEqual bool) {
isEqual = a.Cmp(other) == 0
return
}
func (a *AspectRatio) FloatString() (s string) {
if a == nil {
return
}
if a.ar == 0 {
a.ar = float64(a.res.w) / float64(a.res.h)
}
s = strconv.FormatFloat(a.ar, 'f', -1, 64)
return
}
func (a *AspectRatio) GCD() (gcd int) {
var x int
var y int
if a == nil {
return
}
if a.gcd == 0 {
x = a.res.w
y = a.res.h
// Euclidean GCD ("Greatest Common Divisor") algorithm
for y != 0 {
x, y = y, x%y
}
a.gcd = x
}
gcd = a.gcd
return
}
func (a *AspectRatio) Resolution() (r Resolution) {
var ar [2]float64
var resInts [2]int
if a == nil {
return
}
resInts = a.res.AsSlice()
ar = a.AsSlice()
// Return a fully populated copy.
r = Resolution{
w: resInts[0],
h: resInts[1],
}
r.ar = &AspectRatio{
ar: a.AsFloat(),
arStr: a.String(),
arW: ar[0],
arH: ar[1],
gcd: a.GCD(),
res: &r,
}
return
}
func (a *AspectRatio) String() (s string) {
if a == nil {
return
}
if a.arStr == "" {
if a.arW == 0 {
a.arW = float64(a.res.w) / float64(a.GCD())
}
if a.arH == 0 {
a.arH = float64(a.res.h) / float64(a.GCD())
}
a.arStr = fmt.Sprintf(
"%d:%d",
int(math.Round(a.arW)),
int(math.Round(a.arH)),
)
}
s = a.arStr
return
}
func (r *Resolution) AsSlice() (res [2]int) {
res = [2]int{r.w, r.h}
return
}
func (r *Resolution) AspectRatio() (ar AspectRatio) {
if r == nil {
return
}
if r.ar == nil {
r.ar = NewAspectRatio("", r.w, r.h)
}
ar = AspectRatio{
ar: r.ar.AsFloat(),
arStr: r.ar.String(),
arW: r.ar.arW,
arH: r.ar.arH,
gcd: r.ar.GCD(),
res: &Resolution{
w: r.w,
h: r.h,
},
}
ar.res.ar = &ar
return
}
/*
Cmp is a comparison function that can be used for sorting.
The return cmp value has the same meaning as in [slices.SortFunc]:
If r comes before other, cmp == -1
If r comes after other, cmp == 1
If r and other are equal, cmp == 0
*/
func (r *Resolution) Cmp(other Resolution) (cmp int) {
if r == nil {
return
}
if r.w == other.w && r.h == other.h {
return
}
if r.w == other.w {
if r.h < other.h {
cmp = -1
} else {
cmp = 1
}
} else {
if r.w < other.w {
cmp = -1
} else {
cmp = 1
}
}
return
}
func (r *Resolution) Equals(other Resolution) (isEqual bool) {
isEqual = r.Cmp(other) == 0
return
}
func (r *Resolution) Inverted() (i *Resolution) {
i = &Resolution{
w: r.h,
h: r.w,
}
i.ar = &AspectRatio{
res: i,
}
return
}
func (r *Resolution) String() (s string) {
s = fmt.Sprintf("%dx%d", r.w, r.h)
return
}
var (
resolutions [][2]int = [][2]int{
/*
Common
*/
// qqVGA ("Quarter-QVGA")
[2]int{120, 160}, // 3:4
[2]int{160, 120}, // 4:3, Standard qqVGA
[2]int{160, 128}, // 5:4
[2]int{320, 240}, // ???, qVGA ("Quarter VGA") (commonly inverted as 240x320 on mobile)
// HQVGA ("Half-qVGA")
[2]int{160, 240}, // ???
[2]int{240, 160}, // ???, Standard HQVGA
[2]int{240, 376}, // 3:2, WQVGA ("Wide qVGA")
[2]int{240, 432}, // 3:2, WQVGA
[2]int{360, 240}, // 3:2, WQVGA
[2]int{384, 240}, // 16:10 (8:5), WQVGA
[2]int{400, 240}, // 5:3, WQVGA
[2]int{426, 240}, // ???, WQVGA
[2]int{428, 240}, // ???, WQVGA
[2]int{432, 240}, // 18:10, WQVGA
[2]int{480, 240}, // ???, WQVGA
[2]int{480, 270}, // ???, WQVGA / HVGA ("Half-Size VGA")
[2]int{480, 272}, // ???, WQXGA / HVGA
[2]int{480, 320}, // 3:2, Standard HVGA
[2]int{480, 360}, // 4:3, HVGA
[2]int{640, 240}, // 8:3, HVGA
[2]int{640, 480}, // 4:3, VGA ("Video Graphics Array")
[2]int{640, 384}, // 15:9 (5:3), WVGA (WGA) ("Wide VGA")
[2]int{720, 480}, // 15:10 (3:2), WVGA
[2]int{768, 480}, // 16:10 (8:5), WVGA
[2]int{800, 450}, // 16:9, WVGA
[2]int{800, 480}, // 15:9 (5:3), Standard WVGA
[2]int{848, 480}, // ???, Standard WVGA
[2]int{852, 480}, // ???, Standard WVGA
[2]int{853, 480}, // ???, WVGA
[2]int{854, 480}, // ???, Full WVGA
[2]int{640, 400}, // ???, SVGA ("Super VGA") / UVGA ("Ultra VGA") / XVGA ("Extended VGA")
[2]int{640, 480}, // ???, SVGA / UVGA / XVGA
[2]int{800, 600}, // ???, Standard SVGA / UVGA
[2]int{830, 624}, // 4:3, SVGA / UVGA
[2]int{960, 640}, // 3:2, DVGA ("Double VGA") ("Retina")
[2]int{1136, 640}, // 16:9, DVGA (iPhone 5 variant)
[2]int{1024, 576}, // 16:9, WSVGA ("Wide SVGA")
[2]int{1024, 600}, // 128:75, WSVGA
[2]int{1024, 768}, // 4:3, XGA ("Extended Graphics Array") / SVGA
[2]int{1120, 832}, // 35:26, XGA+ ("Extended Graphics Array Plus")
[2]int{1152, 864}, // 4:3, Standard XGA+
[2]int{1152, 870}, // 192:145, XGA+
[2]int{1152, 900}, // 32:25, XGA+
[2]int{1152, 768}, // 16:9, WXGA ("Wide XGA")
[2]int{1280, 720}, // 16:9, WXGA
[2]int{1280, 768}, // 15:9, Standard WXGA
[2]int{1280, 800}, // 16:10, Standard WXGA
[2]int{1280, 854}, // ???, WXGA+ ("Wide XGA Plus")
[2]int{1360, 768}, // ???, FWXGA ("Full WXGA")
[2]int{1366, 768}, // ???, Standard WXGA / FWXGA
[2]int{1280, 960}, // 4:3, QuadVGA / SXVGA ("Super XVGA") / SXGA-
[2]int{1280, 1024}, // 5:4, SXGA ("Super XGA") / SVGA
[2]int{1360, 768}, // ???, FWXGA
[2]int{1366, 768}, // ???, WXGA
[2]int{1400, 1050}, // 4:3, SXGA+ ("Super XGA Plus")
[2]int{1440, 900}, // 16:10, WXGA+ / WSXGA ("Wide SXGA")
[2]int{1440, 960}, // 3:2, WSXGA
[2]int{1600, 1024}, // 25:16, WSXGA
[2]int{1600, 1200}, // 4:3, UXGA ("Ultra-XGA")
[2]int{1680, 1050}, // 16:10, WSXGA+
[2]int{1920, 1200}, // 16:10, WUXGA ("Widescreen UXGA")
[2]int{2048, 1152}, // 16:9, QWXGA ("Quad-WXGA")
[2]int{2048, 1536}, // 4:3, QXGA ("Quad-XGA")
[2]int{2560, 1600}, // 16:10, WQXGA ("Wide QXGA")
[2]int{2560, 2048}, // 5:4, QSXGA ("Quad-SXGA")
[2]int{2800, 2100}, // 4:3, QSXGA+ ("Quad-SXGA Plus")
[2]int{2880, 1800}, // 16:10, WQXGA+ ("Wide QXGA Plus")
[2]int{3200, 2048}, // 25:16, WQSXGA ("Wide QSXGA")
[2]int{3200, 2400}, // 4:3, QUXGA ("Quad Ultra-XGA")
[2]int{3840, 2400}, // 16:10, WQUXGA ("Wide QUXGA")
[2]int{1136, 640}, // 16:9, Apple variant
[2]int{1334, 750}, // 16:9, Apple variant
[2]int{1792, 828}, // ???, Apple variant
[2]int{2436, 1125}, // ???, Apple variant
[2]int{2532, 1170}, // ???, Apple variant
[2]int{2556, 1179}, // ???, Apple variant
[2]int{2688, 1242}, // ???, Apple variant
[2]int{2778, 1285}, // ???, Apple variant
[2]int{2796, 1290}, // ???, Apple variant
[2]int{2160, 1080}, // 2:1, Android variant
[2]int{2220, 1080}, // ???, Android variant
[2]int{2280, 1080}, // 19:9, Android variant
[2]int{2400, 1080}, // 20:9, Android variant
[2]int{2960, 1440}, // ???, Android variant
[2]int{3040, 1440}, // 19:9, Android variant
[2]int{3120, 1440}, // ???, Android variant
[2]int{2340, 1080}, // ???, Android/Apple variant
// Assorted rare res
[2]int{576, 720}, // 4:5
[2]int{900, 720}, // 5:4
[2]int{960, 720}, // 4:3
[2]int{1200, 720}, // 5:3
[2]int{1280, 720}, // 16:9
[2]int{1280, 524}, // ???
[2]int{1280, 532}, // ???
[2]int{1280, 536}, // ???
[2]int{1280, 540}, // ???
[2]int{1280, 544}, // ???
[2]int{1280, 640}, // ???
[2]int{1280, 674}, // ???
[2]int{1280, 692}, // ???
// 1:1 - Square
[2]int{1080, 1080}, // Square Standard
// 9:16 - Vertical
[2]int{1080, 1920}, // Standard Mobile
// 16:9 - Standard widescreen
[2]int{1920, 1080}, // 1080p ("Full HD"/"FHD")
[2]int{2560, 1440}, // 1440p ("Quad HD"/ "QHD")
[2]int{3840, 2160}, // 2160p ("4k Ultra-HD"/"4k UHD")
[2]int{7680, 4320}, // 4320p ("8k Ultra-HD"/"8k UHD")
// 16:10 - "Computer monitor" widescreen
[2]int{1920, 1200}, // WUXGA ("Widescreen Ultra Extended Graphics Array"), https://www.corsair.com/us/en/explorer/gamer/monitors/wuxga-resolution-explained/
[2]int{2560, 1600}, // WQXGA ("Wide Quad Extended Graphics Array"), https://www.lenovo.com/us/en/glossary/wqxga/
// 21:9 - Ultrawide
[2]int{2560, 1080}, // Ultrawide FHD
[2]int{3440, 1440}, // Ultrawide QHD
// 32:9 - Super Ultrawide
[2]int{5120, 1440},
/*
Cinema
*/
// 37:20 - Flat
[2]int{1998, 1080}, // 2k Flat DCP ("Digital Cinema Package")
[2]int{3996, 2160}, // 4k Flat DCP
// 1024:429 - Scope
[2]int{2048, 858}, // 2k Scope DCP
[2]int{4096, 1716}, // 4k Scope DCP
// 256:135 - Full Container
[2]int{2048, 1080}, // 2k Full Container DCP
[2]int{4096, 2160}, // 4k Full Container DCP
}
)
/*
NewAspectRatio returns a new Aspect Ratio from string form ar.
At least one of width or height must be > 0;
the other can be calculated from ar.
If both height and width are > 0, ar is ignored and it will be calculated from those instead.
If both height and width are <= 0, a will be nil.
If ar is an invalid format, a will be nil.
*/
func NewAspectRatio(ar string, width, height int) (a *AspectRatio) {
var err error
var arW float64
var arH float64
var arStr []string
if width <= 0 && height <= 0 {
return
} else if width > 0 && height > 0 {
// ar is ignored.
a = &AspectRatio{
ar: float64(width) / float64(height),
res: &Resolution{
w: width,
h: height,
},
}
a.res.ar = a
} else {
arStr = strings.SplitN(strings.TrimSpace(ar), ":", 2)
if len(arStr) != 2 {
return
}
if arH, err = strconv.ParseFloat(strings.TrimSpace(arStr[0]), 64); err != nil {
return
}
if arW, err = strconv.ParseFloat(strings.TrimSpace(arStr[1]), 64); err != nil {
return
}
a = &AspectRatio{
ar: arW / arH,
arW: arW,
arH: arH,
}
a.res.ar = a
if width > 0 {
a.res.w = width
a.res.h = int(math.Round(float64(a.res.w) / a.ar))
} else {
a.res.h = height
a.res.w = int(math.Round(float64(a.res.h) * a.ar))
}
}
return
}
func NewResolution(width, height int) (r Resolution) {
r = Resolution{
w: width,
h: height,
}
r.ar = &AspectRatio{
res: &r,
}
return
}
func NewResolutions(resGeom [][2]int) (rsltns []Resolution) {
var idx int
rsltns = make([]Resolution, len(resGeom))
for idx = range resGeom {
rsltns[idx] = NewResolution(resGeom[idx][0], resGeom[idx][1])
}
return
}
func SortAspectRatios(ar []AspectRatio) (sorted []AspectRatio) {
slices.SortStableFunc(
ar,
func(a, b AspectRatio) (cmp int) {
cmp = a.Cmp(b)
return
},
)
return
}
func SortResolutions(res []Resolution) {
slices.SortStableFunc(
res,
func(a, b Resolution) (cmp int) {
cmp = a.Cmp(b)
return
},
)
return
}
func main() {
var ok bool
var idx int
var arStr string
// var ars []string
var ar AspectRatio
var res Resolution
var rsltns []Resolution
var uniqAr mapset.Set[AspectRatio] = mapset.NewSet[AspectRatio]()
var uniqRes mapset.Set[[2]int] = mapset.NewSet[[2]int](resolutions...)
var arMap map[string]mapset.Set[Resolution] = make(map[string]mapset.Set[Resolution])
rsltns = NewResolutions(uniqRes.ToSlice())
SortResolutions(rsltns)
for idx, res = range rsltns {
uniqAr.Add(res.AspectRatio())
ar = res.AspectRatio()
arStr = ar.String()
if _, ok = arMap[arStr]; !ok {
arMap[arStr] = mapset.NewSet[Resolution]()
}
arMap[arStr].Add(res)
fmt.Printf("#%d: %s (%s)\n", idx, res.String(), ar.FloatString())
}
fmt.Println("\n====\n")
/*
for _, aspRatStr = range aspRatKeys {
fmt.Println(aspRatStr)
slices.SortStableFunc(
aspRatRes[aspRatStr],
func(resA, resB [2]int) (cmp int) {
// default cmp is 0, which means fully equal
if resA[0] == resB[0] {
if resA[1] > resB[1] {
cmp = 1
} else {
cmp = -1
}
} else {
if resA[0] > resB[0] {
cmp = 1
} else {
cmp = -1
}
}
return
},
)
for _, res = range aspRatRes[aspRatStr] {
fmt.Printf("\t%dx%d\n", res[0], res[1])
}
}
*/
}
+11 -9
View File
@@ -1,24 +1,26 @@
module r00t2.io/sysutils module r00t2.io/sysutils
go 1.24.5 go 1.26
require ( require (
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/deckarep/golang-set/v2 v2.9.0
github.com/djherbis/times v1.6.0 github.com/djherbis/times v1.6.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/shirou/gopsutil/v4 v4.25.7 github.com/shirou/gopsutil/v4 v4.26.6
golang.org/x/sync v0.16.0 golang.org/x/sync v0.22.0
golang.org/x/sys v0.35.0 golang.org/x/sys v0.47.0
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8
r00t2.io/goutils v1.9.6 r00t2.io/goutils v1.16.10
) )
require ( require (
github.com/ebitengine/purego v0.8.4 // indirect github.com/ebitengine/purego v0.10.1 // indirect
github.com/go-ole/go-ole v1.3.0 // 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-20260627054121-477a66015f15 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/go-sysconf v0.4.0 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect github.com/tklauser/numcpus v0.12.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.mongodb.org/mongo-driver v1.17.9 // indirect
) )
+40 -31
View File
@@ -1,9 +1,13 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set/v2 v2.9.0 h1:prva4eP9UysWagLyKrtn074ughi0NnkIf0A4M5yOCKI=
github.com/deckarep/golang-set/v2 v2.9.0/go.mod h1:EWknQXbs0mcFpat2QOoXV0Ee57cD+w6ZEN76BR2JVrM=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= 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.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY=
github.com/ebitengine/purego v0.10.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.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 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
@@ -11,46 +15,51 @@ 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/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 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 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-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= github.com/lufia/plan9stats v0.0.0-20260627054121-477a66015f15 h1:YkjVPl/YH5XlJ+/NiwzJtPYXXKRcyjmEUhsDci6YK3c=
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lufia/plan9stats v0.0.0-20260627054121-477a66015f15/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/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 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 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.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY=
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs=
github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= github.com/shirou/gopsutil/v4 v4.26.6/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4=
github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-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.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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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 h1:FW42yWB1sGClqswyHIB68wo0+oPrav1IuQ+Tdy8Qp8E=
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8/go.mod h1:44w9OfBSQ9l3o59rc2w3AnABtE44bmtNnRMNC7z+oKE= 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.16.6 h1:nZ6lFqhiM3m/1yNvdIdiP2VvuQr5ZRvYPbj3oM657Aw=
r00t2.io/goutils v1.9.2/go.mod h1:76AxpXUeL10uFklxRB11kQsrtj2AKiNm8AwG1bNoBCA= r00t2.io/goutils v1.16.6/go.mod h1:VXsgdl3QoiP9bgqykYpJ/NwoxZ992jslMiV32EhaIOc=
r00t2.io/goutils v1.9.3 h1:pR9Ggu5JBpVjfrqNBrZg9bZpKan0TCcwt3MXrSdkhLo= r00t2.io/goutils v1.16.10 h1:2XSTVAyW35+hC/VEysQahMKTcSSEH7uaOv1a72lsnXc=
r00t2.io/goutils v1.9.3/go.mod h1:76AxpXUeL10uFklxRB11kQsrtj2AKiNm8AwG1bNoBCA= r00t2.io/goutils v1.16.10/go.mod h1:rv7k/RWprVhoZjacmL+Jv6GjvME3BpOO8OsdoXPqy8I=
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=
+10
View File
@@ -1,8 +1,18 @@
package internal package internal
import (
`regexp`
)
// OS-specific path environment variable name. The default is "PATH". // OS-specific path environment variable name. The default is "PATH".
var ( var (
pathEnvVarName map[string]string = map[string]string{ pathEnvVarName map[string]string = map[string]string{
"windows": "Path", "windows": "Path",
} }
) )
// Compiled regex patterns.
var (
reMaybeInt *regexp.Regexp = regexp.MustCompile(`^(?P<sign>\+|-)[0-9]+$`)
reMaybeFloat *regexp.Regexp = regexp.MustCompile(`(?P<sign>\+|-)?[0-9]+\.[0-9]+$`)
)
+160
View File
@@ -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
}
-18
View File
@@ -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
}
+14
View File
@@ -1 +1,15 @@
- search criteria should *also* support a timestamp range (e.g. so a search can be restricted to both older than AND newer than; e.g. older than 00:00, newer than 01:00) - search criteria should *also* support a timestamp range (e.g. so a search can be restricted to both older than AND newer than; e.g. older than 00:00, newer than 01:00)
- need an ExpandHomeSys (...which will change ExpandHome behavior, if it switches to path instead of path/filepath...)
-- Should probably split out to a separate module or branch to v2.
-- get rid of the *Sys stuff? Normalize all to os.PathSep? provide package-level bool to control behavior?
-- separate modules ideal:
--- r00t2.io/paths/
<generic>
gopath/
<generic path separator - forward slash>
syspath/
<os.PathSeparator>
I could consolidate a LOT of code if I use a struct that has a mode (or typed against different separators?) etc. but...
--- Need to test with e.g. "network share paths" - URI format (<scheme>://<path>), \\host\share, etc.
+12 -1
View File
@@ -1,3 +1,5 @@
//go:build !(windows || plan9 || wasip1 || js || ios)
package sysutils package sysutils
import ( import (
@@ -9,7 +11,16 @@ IDState collects information about the current running process.
It should only be used as returned from GetIDState(). It should only be used as returned from GetIDState().
Its methods WILL return false information if any of these values are altered. Its methods WILL return false information if any of these values are altered.
FSUID/FSGID are not supported. FSUID/FSGID are not currently supported.
Currently, macOS (and FreeBSD, and a couple others) will not populate:
* SUID
* SGID
due to Apple in their "infinite wisdom" allowing you to *set* these
but exposing no direct syscall whatsoever to *retrieve* them.
Enjoy your crippled OS, fanboys.
*/ */
type IDState struct { type IDState struct {
// RUID: Real UID // RUID: Real UID
+38
View File
@@ -0,0 +1,38 @@
//go:build windows
package sysutils
/*
IDState on *NIX-like platforms collects information about the current running process.
However, it is only present as a sort of dummy on Windows to make cross-platform development easier.
Do not expect any sort of usefulness from this struct on Windows other than parsing an IDState generated on *NIX.
*/
type IDState struct {
// RUID: Real UID
RUID int
// EUID: Effective UID
EUID int
// SUID: Saved Set UID
SUID int
// RGID: Real GID
RGID int
// EGID: Effective GID
EGID int
// SGID: Saved Set GID
SGID int
// SudoEnvUser is true if SUDO_USER or SUDO_UID is set.
SudoEnvUser bool
// SudoEnvGroup is true if SUDO_GID is set.
SudoEnvGroup bool
// SudoEnvCmd is true if SUDO_COMMAND is set.
SudoEnvCmd bool
// SudoEnvHome is true if SUDO_HOME is set.
SudoEnvHome bool
// SudoEnvVars is true if any of the "well-known" sudo environment variables are set.
SudoEnvVars bool
// PPIDUidMatch is true if the parent PID UID matches the current process UID (mismatch usually indicates sudo invocation).
PPIDUidMatch bool
// PPIDGidMatch is true if the parent PID GID matches the current process GID (mismatch usually indicates sudo invocation).
PPIDGidMatch bool
}