Compare commits

..

8 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
brent saner acb4352113 v1.14.4
UPDATED:
* docs for sysutils/paths now link to references.
2025-10-08 12:36:19 -04:00
55 changed files with 1626 additions and 363 deletions
View File
+47 -12
View File
@@ -1,28 +1,63 @@
- 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 https://github.com/tredoe/osutil ?
-- 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.
-- (i think i fixed this)
- unit tests
- 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)
-- 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)
-- 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
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 (
StructTagInterpolate string = "envsub"
)
+48 -61
View File
@@ -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 {
if pathList, err = internal.GetPathEnv(); err != nil {
return
}
pathList = append(pathList, p)
}
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,31 @@ func InterpolateString(s *string) (err error) {
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) {
var kVal reflect.Value
@@ -467,7 +454,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 +545,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
-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")
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
// Checked consolidates all the provided checked functions.
@@ -138,7 +140,7 @@ func (i *IDState) PPIDUIDsChecked() (checked bool) {
return
}
// SudoChecked is true if SudoEnvVars can be trusted
// SudoChecked is true if SudoEnvVars can be trusted.
func (i *IDState) SudoChecked() (checked bool) {
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
go 1.24.5
go 1.26
require (
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/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/v4 v4.26.6
golang.org/x/sync v0.22.0
golang.org/x/sys v0.47.0
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8
r00t2.io/goutils v1.9.6
r00t2.io/goutils v1.16.10
)
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/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/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect
github.com/tklauser/go-sysconf v0.4.0 // indirect
github.com/tklauser/numcpus v0.12.0 // 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/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/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/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.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
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/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/lufia/plan9stats v0.0.0-20260627054121-477a66015f15 h1:YkjVPl/YH5XlJ+/NiwzJtPYXXKRcyjmEUhsDci6YK3c=
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/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/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/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY=
github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs=
github.com/shirou/gopsutil/v4 v4.26.6/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
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.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU=
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/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=
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
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-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.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
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/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.16.6 h1:nZ6lFqhiM3m/1yNvdIdiP2VvuQr5ZRvYPbj3oM657Aw=
r00t2.io/goutils v1.16.6/go.mod h1:VXsgdl3QoiP9bgqykYpJ/NwoxZ992jslMiV32EhaIOc=
r00t2.io/goutils v1.16.10 h1:2XSTVAyW35+hC/VEysQahMKTcSSEH7uaOv1a72lsnXc=
r00t2.io/goutils v1.16.10/go.mod h1:rv7k/RWprVhoZjacmL+Jv6GjvME3BpOO8OsdoXPqy8I=
+10
View File
@@ -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<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)
- 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.
-1
View File
@@ -10,7 +10,6 @@ const (
// Mostly just for reference.
const (
// ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular
modeDir pathMode = pathMode(fs.ModeDir)
modeSymlink pathMode = pathMode(fs.ModeSymlink)
modePipe pathMode = pathMode(fs.ModeNamedPipe)
+1 -1
View File
@@ -7,7 +7,7 @@ const (
MaxSymLinkLevel on Windows is weird; Microsoft calls them "reparse points".
And it changes on the Windows version you're on, but it's been 63 past Windows Server 2003/Windows XP.
They're *very* EOL, so I'm completely ignoring them.
They're *very* EOL, so I'm completely ignoring earlier cases.
https://learn.microsoft.com/en-us/windows/win32/fileio/symbolic-link-programming-consideration
*/
+41 -38
View File
@@ -93,7 +93,7 @@ func ExpandHome(p *string) (err error) {
}
/*
GetFirst is the file equivalent of envs.GetFirst.
GetFirst is the file equivalent of [r00t2.io/sysutils/envs.GetFirst].
It iterates through paths, normalizing them along the way
(so abstracted paths such as ~/foo/bar.txt and relative paths
@@ -107,7 +107,7 @@ If no path exists, ok will be false.
As always, results are not guaranteed due to permissions, etc.
potentially returning an inaccurate result.
This is a thin wrapper around GetFirstWithRef.
This is a thin wrapper around [GetFirstWithRef].
*/
func GetFirst(p []string) (content []byte, isDir, ok bool) {
@@ -117,9 +117,9 @@ func GetFirst(p []string) (content []byte, isDir, ok bool) {
}
/*
GetFirstWithRef is the file equivalent of envs.GetFirstWithRef.
GetFirstWithRef is the file equivalent of [r00t2.io/sysutils/envs.GetFirstWithRef].
It behaves exactly like GetFirst, but with an additional returned value, idx,
It behaves exactly like [GetFirst], but with an additional returned value, idx,
which specifies the index in p in which a path was found.
As always, results are not guaranteed due to permissions, etc.
@@ -162,9 +162,9 @@ func GetFirstWithRef(p []string) (content []byte, isDir, ok bool, idx int) {
}
/*
Len returns the number of path segments in p, as split with the same param signature to Segment.
Len returns the number of path segments in p, as split with the same param signature to [Segment].
See Segment for details on abs and strict.
See [Segment] for details on abs and strict.
*/
func Len(p string, abs, strict bool) (segments int) {
@@ -174,9 +174,9 @@ func Len(p string, abs, strict bool) (segments int) {
}
/*
LenSys returns the number of path segments in p, as split with the same param signature to SegmentSys.
LenSys returns the number of path segments in p, as split with the same param signature to [SegmentSys].
See Segment for details on abs and strict.
See [Segment] for details on abs and strict.
*/
func LenSys(p string, abs, strict bool) (segments int) {
@@ -188,9 +188,9 @@ func LenSys(p string, abs, strict bool) (segments int) {
/*
MakeDirIfNotExist will create a directory at a given path if it doesn't exist.
See also the documentation for RealPath.
See also the documentation for [RealPath].
This is a bit more sane option than os.MkdirAll as it will normalize paths a little better.
This is a bit more sane option than [os.MkdirAll] as it will normalize paths a little better.
*/
func MakeDirIfNotExist(p string) (err error) {
@@ -234,9 +234,9 @@ path syntax/string itself is not supported on the runtime OS. This can be done v
if errors.Is(err, fs.ErrInvalid) {...}
RealPath is simply a wrapper around ExpandHome(path) and filepath.Abs(*path).
RealPath is simply a wrapper around [ExpandHome] and [filepath.Abs].
Note that RealPath does *not* resolve symlinks. Only RealPathExistsStatTarget does that.
Note that RealPath does *not* resolve symlinks. Only [RealPathExistsStatTarget] does that.
*/
func RealPath(p *string) (err error) {
@@ -252,7 +252,7 @@ func RealPath(p *string) (err error) {
}
/*
RealPathJoin combines RealPath with (path).Join.
RealPathJoin combines [RealPath] with [path.Join].
If dst is nil, then p will be updated with the new value.
You probably don't want that.
@@ -283,7 +283,7 @@ func RealPathJoin(p, dst *string, subPaths ...string) (err error) {
}
/*
RealPathJoinSys combines RealPath with (path/filepath).Join.
RealPathJoinSys combines [RealPath] with [path/filepath.Join].
If dst is nil, then path will be updated with the new value.
You probably don't want that.
@@ -314,10 +314,10 @@ func RealPathJoinSys(p, dst *string, subPaths ...string) (err error) {
}
/*
RealPathExists is like RealPath, but will also return a boolean as to whether the path
RealPathExists is like [RealPath], but will also return a boolean as to whether the path
actually exists or not.
Note that err *may* be os.ErrPermission/fs.ErrPermission, in which case the exists value
Note that err *may* be [os.ErrPermission] / [fs.ErrPermission], in which case the exists value
cannot be trusted as a permission error occurred when trying to stat the path - if the
calling user/process does not have read permission on e.g. a parent directory, then
exists may be false but the path may actually exist. This condition can be checked via
@@ -325,9 +325,9 @@ via:
if errors.Is(err, fs.ErrPermission) {...}
See also the documentation for RealPath.
See also the documentation for [RealPath].
In those cases, it may be preferable to use RealPathExistsStat and checking stat for nil.
In those cases, it may be preferable to use [RealPathExistsStat] and checking stat for nil.
*/
func RealPathExists(p *string) (exists bool, err error) {
@@ -348,11 +348,11 @@ func RealPathExists(p *string) (exists bool, err error) {
}
/*
RealPathExistsStat is like RealPathExists except it will also return the fs.FileInfo
RealPathExistsStat is like [RealPathExists] except it will also return the [io/fs.FileInfo]
for the path (assuming it exists).
If stat is nil, it is highly recommended to check err via the methods suggested
in the documentation for RealPath and RealPathExists.
in the documentation for [RealPath] and [RealPathExists].
*/
func RealPathExistsStat(p *string) (exists bool, stat fs.FileInfo, err error) {
@@ -376,7 +376,7 @@ RealPathExistsStatTarget is the only "RealPather" that will resolve p to the (fi
If p is not a symlink but does exist, the tgt* will reflect the same as p*.
See WalkLink for details on relRoot and other assorted rules/logic (RealPathExistsStatTarget wraps WalkLink).
See [WalkLink] for details on relRoot and other assorted rules/logic (RealPathExistsStatTarget wraps [WalkLink]).
*/
func RealPathExistsStatTarget(p *string, relRoot string) (pExists, tgtExists, wasLink bool, pStat fs.FileInfo, tgtStat fs.FileInfo, err error) {
@@ -413,7 +413,7 @@ func RealPathExistsStatTarget(p *string, relRoot string) (pExists, tgtExists, wa
return
}
// SearchFsPaths gets a file/directory/etc. path list based on the provided criteria.
// SearchFsPaths gets a file/directory/etc. path list based on the provided [FsSearchCriteria].
func SearchFsPaths(matcher FsSearchCriteria) (found, miss []*FsSearchResult, err error) {
var matched *FsSearchResult
@@ -465,11 +465,11 @@ func SearchFsPaths(matcher FsSearchCriteria) (found, miss []*FsSearchResult, err
}
/*
SearchFsPathsAsync is exactly like SearchFsPaths, but dispatches off concurrent
SearchFsPathsAsync is exactly like [SearchFsPaths], but dispatches off concurrent
workers for the filtering logic instead of performing iteratively/recursively.
It may, in some cases, be *slightly more* performant and *slightly less* in others.
Note that unlike SearchFsPaths, the results written to the
FsSearchCriteriaAsync.ResChan are not guaranteed to be in any predictable order.
Note that unlike [SearchFsPaths], the results written to the
[FsSearchCriteriaAsync].ResChan are not guaranteed to be in any predictable order.
All channels are expected to have already been initialized by the caller.
They will not be closed by this function.
@@ -574,7 +574,7 @@ func SearchFsPathsAsync(matcher FsSearchCriteriaAsync) {
}
/*
Segment returns path p's segments as a slice of strings, using GenericSeparator as a separator.
Segment returns path p's segments as a slice of strings, using [GenericSeparator] as a separator.
If abs is true, the placeholder leading prefix(es) (if any) of GenericSeparator will be kept in-place;
otherwise it/they will be trimmed out.
@@ -583,14 +583,14 @@ e.g.:
abs == true: //foo/bar/baz => []string{"", "", "foo", "bar", "baz"}
abs == false: /foo/bar/baz => []string{"foo", "bar", "baz"}
If strict is true, any trailing GenericSeparator will be kept in-place;
If strict is true, any trailing [GenericSeparator] will be kept in-place;
otherwise they will be trimmed out.
e.g. (assuming abs == false):
strict == true: /foo/bar/baz// => []string{"foo", "bar", "baz", "", ""}
strict == false: /foo/bar/baz/ => []string{"foo", "bar", "baz"}
It is recommended to call RealPath for path's ptr first for normalization.
It is recommended to call [RealPath] for path's ptr first for normalization.
*/
func Segment(p string, abs, strict bool) (segments []string) {
@@ -606,7 +606,7 @@ func Segment(p string, abs, strict bool) (segments []string) {
return
}
// SegmentSys is exactly like Segment, except using os.PathSeparator instead of GenericSeparator.
// SegmentSys is exactly like [Segment], except using [os.PathSeparator] instead of [GenericSeparator].
func SegmentSys(p string, abs, strict bool) (segments []string) {
if !abs {
@@ -622,14 +622,17 @@ func SegmentSys(p string, abs, strict bool) (segments []string) {
}
/*
Strip is like Segment but trims out the leading n number of segments and reassembles the path using path.Join.
Strip is like [Segment] but trims out the leading n number of segments and reassembles the path using [path.Join].
n may be negative, in which case the *trailing* n number of segments will be trimmed out.
(i.e. n == -1, p == `foo/bar/baz/quux` would be `foo/bar/baz`, not `bar/baz/quux`)
If you require more traditional slicing (e.g. with interval),
you may want to use path.Join with a sliced result of Segment instead.
e.g.: *only* the *last* n segments: path.Join(Segment(p, ...)[Len(p, ...)-n:]...)
you may want to use [path.Join] with a sliced result of [Segment] instead.
e.g.: *only* the *last* n segments:
path.Join(Segment(p, ...)[Len(p, ...)-n:]...)
If n == 0 or int(math.Abs(float64(n))) >= len(Segment(p, ...)), no transformation will be done.
@@ -664,7 +667,7 @@ func Strip(p string, abs, strict bool, n int) (slicedPath string) {
return
}
// StripSys is exactly like Strip but using (path/filepath).Join and SegmentSys.
// StripSys is exactly like [Strip] but using [path/filepath.Join] and [SegmentSys].
func StripSys(p string, abs, strict bool, n int) (slicedPath string) {
var pLen int
@@ -692,19 +695,19 @@ func StripSys(p string, abs, strict bool, n int) (slicedPath string) {
}
/*
WalkLink walks the recursive target(s) of lnk (unless/until MaxSymlinkLevel is hit, which will trigger ErrMaxSymlinkLevel)
WalkLink walks the recursive target(s) of lnk (unless/until [MaxSymlinkLevel] is hit, which will trigger [ErrMaxSymlinkLevel])
until it reaches a real (non-symlink) target.
lnk will have RealPath called on it first.
If lnk is not a symlink, then tgts == []string{lnk} and err = nil.
A broken link will return fs.ErrNotExist, with tgts containing the targets up to and including the path that triggered the error.
A broken link will return [io/fs.ErrNotExist], with tgts containing the targets up to and including the path that triggered the error.
If lnk itself does not exist, tgts will be nil and err will be that of fs.ErrNotExist.
If lnk itself does not exist, tgts will be nil and err will be that of [io/fs.ErrNotExist].
relRoot is a root directory to resolve relative links to. If empty, relative link target `t` from link `l` will be treated
as relative to `(path/filepath).Dir(l)` (that is to say, `t = filepath.Join(filepath.Dir(l), os.Readlink(l))`).
as relative to [path/filepath.Dir] on l (that is to say, `t = filepath.Join(filepath.Dir(l), os.Readlink(l))`).
*/
func WalkLink(lnk, relRoot string) (tgts []string, err error) {
@@ -766,7 +769,7 @@ func WalkLink(lnk, relRoot string) (tgts []string, err error) {
}
/*
filterTimes checks a times.Timespec of a file using:
filterTimes checks a [times.Timespec] of a file using:
- an age specified by the caller
- an ageType bitmask for types of times to compare
- an olderThan bool (if false, the file must be younger than)
+6 -4
View File
@@ -11,13 +11,15 @@ import (
)
/*
Match returns match (a ptr to a FsSearchResult if the specified path matches, otherwise nil),
miss (ptr the specified path does not match, otherwise nil), and an fs.DirEntry and fs.FileInfo
for path. d and/or fi may be nil.
Match returns match (a ptr to a [FsSearchResult] if the specified path matches, otherwise nil),
miss (ptr the specified path does not match, otherwise nil), and an [io/fs.DirEntry] and [io/fs.FileInfo]
for path.
d and/or fi may be nil.
If err is not nil, it represents an unexpected error and as such, both match and miss should be nil.
Match, miss, and err will all be nil if the filesystem object/path does not exist.
match, miss, and err will all be nil if the filesystem object/path does not exist.
*/
func (f *FsSearchCriteria) Match(path string, d fs.DirEntry, fi fs.FileInfo) (match, miss *FsSearchResult, err error) {
+1 -1
View File
@@ -4,7 +4,7 @@ import (
`r00t2.io/goutils/bitmask`
)
// Mask returns a bitmask.MaskBit from a pathTimeType.
// Mask returns a [r00t2.io/goutils/bitmask.MaskBit] from a pathTimeType.
func (p *pathTimeType) Mask() (mask *bitmask.MaskBit) {
mask = bitmask.NewMaskBitExplicit(uint(*p))
+1 -1
View File
@@ -109,7 +109,7 @@ type FsSearchCriteriaAsync struct {
SemaphoreCtx context.Context
}
// FsSearchResult contains a match/miss result for FsSearchCriteria and FsSearchCriteriaAsync.
// FsSearchResult contains a match/miss result for [FsSearchCriteria] and [FsSearchCriteriaAsync].
type FsSearchResult struct {
/*
Path is the path to the object on the filesystem.
+12 -1
View File
@@ -1,3 +1,5 @@
//go:build !(windows || plan9 || wasip1 || js || ios)
package sysutils
import (
@@ -9,7 +11,16 @@ IDState collects information about the current running process.
It should only be used as returned from GetIDState().
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 {
// 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
}