what a rabbithole. lots of goodies now.

This commit is contained in:
brent s. 2021-12-18 04:23:35 -05:00
parent 11b0744e5c
commit 8f582d37f1
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
24 changed files with 399 additions and 326 deletions

View File

@ -34,8 +34,8 @@ package main
import (
`fmt`
`r00t2.io/sysutils/net/ports`
`r00t2.io/sysutils/net/protos`
`r00t2.io/sysutils/.net.UNFINISHED/ports`
`r00t2.io/sysutils/.net.UNFINISHED/protos`
)

func main() {
@ -77,8 +77,8 @@ import (
`fmt`
`log`

`r00t2.io/sysutils/net/ports`
`r00t2.io/sysutils/net/protos`
`r00t2.io/sysutils/.net.UNFINISHED/ports`
`r00t2.io/sysutils/.net.UNFINISHED/protos`
)

func main() {
@ -114,7 +114,7 @@ import (
`fmt`
`log`

`r00t2.io/sysutils/net/ports`
`r00t2.io/sysutils/.net.UNFINISHED/ports`
)

func main() {
@ -148,7 +148,7 @@ package main
import (
`fmt`

`r00t2.io/sysutils/net/protos`
`r00t2.io/sysutils/.net.UNFINISHED/protos`
)

func main() {
@ -184,7 +184,7 @@ import (
`fmt`
`log`

`r00t2.io/sysutils/net/protos`
`r00t2.io/sysutils/.net.UNFINISHED/protos`
)

func main() {
@ -217,7 +217,7 @@ import (
`fmt`
`log`

`r00t2.io/sysutils/net/protos`
`r00t2.io/sysutils/.net.UNFINISHED/protos`
)

func main() {

View File

@ -5,7 +5,7 @@ import (
"io"
"net/http"

"r00t2.io/sysutils/net/ports"
_ "r00t2.io/sysutils/.net.UNFINISHED/ports"
)

func download(url string) (b *[]byte, err error) {

View File

@ -8,10 +8,11 @@ import (
"strconv"
"strings"

// https://pkg.go.dev/github.com/jszwec/csvutil but I can't seem to fetch it.
"github.com/jszwec/csvutil"

"r00t2.io/sysutils/net/ports"
"r00t2.io/sysutils/net/protos"
"r00t2.io/sysutils/.net.UNFINISHED/ports"
"r00t2.io/sysutils/.net.UNFINISHED/protos"
"r00t2.io/sysutils/paths"
)

View File

@ -1,7 +1,7 @@
package ports

import (
"r00t2.io/sysutils/net/protos"
"r00t2.io/sysutils/.net.UNFINISHED/protos"
)

type IPPort struct {

18
TODO Normal file
View File

@ -0,0 +1,18 @@
- password generator utility/library
-- incorporate with https://github.com/tredoe/osutil ?
-- cli flag to dump flat hashes too
--- https://github.com/hlandau/passlib

- 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)

- 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://support.microsoft.com/en-us/topic/2dc5c4b9-0881-2e0a-48df-f120493a2d3e
-- https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/-2000-server/cc959336(v=technet.10)?redirectedfrom=MSDN
-- https://stackoverflow.com/questions/33078854/what-is-the-regex-for-windows-domain-username-in-c

- finish net

60
envs/.TODO.go.UNFINISHED Normal file
View File

@ -0,0 +1,60 @@
package envs

/*
EnvMapper contains the environment variables as grouped by their basic type.
If a variable's type cannot be determined, it's placed in Strings.
If a variable's type is a list, it will be an []interface{} as each item may be a different variable type.
It essentially is the same as EnvMap except with the types split out for convenience.
*/
type EnvMapper struct {
Booleans map[string]bool `json:"bools"`
Numbers map[string]int `json:"nums"`
Strings map[string]string `json:"strings"`
Lists map[string][]interface{} `json:"lists"`
}

// GetEnvMapper returns a pointer to a populated EnvMapper.
func GetEnvMapper() (e *EnvMapper, err error) {

var em map[string]interface{}
var env EnvMapper

env = EnvMapper{
Booleans: nil,
Numbers: nil,
Strings: nil,
Lists: nil,
}

for k, v := range em {

switch t := v.(type) {
case bool:
if env.Booleans == nil {
env.Booleans = make(map[string]bool, 0)
}
env.Booleans[k] = t
continue
case int:
if env.Numbers == nil {
env.Numbers = make(map[string]int, 0)
}
env.Numbers[k] = t
continue
case []interface{}:
if env.Lists == nil {
env.Lists = make(map[string][]interface{}, 0)
}
env.Lists[k] = t
case string: // the "default" since everything could probably be typeswitched to a string otherwise.
if env.Strings == nil {
env.Strings = make(map[string]string, 0)
}
env.Strings[k] = t
}
}

*e = env

return
}

11
envs/consts.go Normal file
View File

@ -0,0 +1,11 @@
package envs

import (
`regexp`
)

// Compiled regex patterns.
var (
reMaybeInt *regexp.Regexp = regexp.MustCompilePOSIX(`^(?P<sign>\+|-)[0-9]+$`)
reMaybeFloat *regexp.Regexp = regexp.MustCompilePOSIX(`(?P<sign>\+|-)?[0-9]+\.[0-9]+$`)
)

120
envs/funcs.go Normal file
View File

@ -0,0 +1,120 @@
package envs

import (
`bytes`
`errors`
`fmt`
`io/ioutil`
`os`
`strings`

`r00t2.io/sysutils/internal`
`r00t2.io/sysutils/paths`
)

// GetPathEnv returns a slice of the PATH variable's items.
func GetPathEnv() (pathList []string, err error) {

var pathVar string = internal.GetPathEnvName()

pathList = make([]string, 0)

for _, p := range strings.Split(os.Getenv(pathVar), string(os.PathListSeparator)) {
if err = paths.RealPath(&p); err != nil {
return
}
pathList = append(pathList, p)
}
return
}

// GetEnvMap returns a map of all environment variables. All values are strings.
func GetEnvMap() (envVars map[string]string) {

var envList []string = os.Environ()

envVars = envListToMap(envList)

return
}

/*
GetEnvMapNative returns a map of all environment variables, but attempts to "nativize" them.
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,
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).
*/
func GetEnvMapNative() (envMap map[string]interface{}) {

var stringMap map[string]string = GetEnvMap()

envMap = nativizeEnvMap(stringMap)

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, 0)

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 = 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.
All values are interfaces. It is up to the caller to typeswitch them to proper types.

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 {
return
}

envMap = nativizeEnvMap(stringMap)

return
}

85
envs/utils.go Normal file
View File

@ -0,0 +1,85 @@
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, 0)

for _, ev := range envs {
kv = strings.SplitAfterN(ev, "=", 2)
// I *think* SplitAfterN 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

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
}

264
funcs.go
View File

@ -1,264 +0,0 @@
package sysutils

import (
`bytes`
`errors`
`fmt`
`io/ioutil`
`os`
`runtime`
`strconv`
`strings`

`r00t2.io/sysutils/paths`
)

/*
EnvMapper contains the environment variables as grouped by their basic type.
If a variable's type cannot be determined, it's placed in Strings.
If a variable's type is a list, it will be an []interface{} as each item may be a different variable type.
It essentially is the same as EnvMap except with the types split out for convenience.
*/
type EnvMapper struct {
Booleans map[string]bool `json:"bools"`
Numbers map[string]int `json:"nums"`
Strings map[string]string `json:"strings"`
Lists map[string][]interface{} `json:"lists"`
}

// GetEnvMapper returns a pointer to a populated EnvMapper.
func GetEnvMapper() (e *EnvMapper, err error) {

var em map[string]interface{}
var env EnvMapper

env = EnvMapper{
Booleans: nil,
Numbers: nil,
Strings: nil,
Lists: nil,
}

for k, v := range em {

switch t := v.(type) {
case bool:
if env.Booleans == nil {
env.Booleans = make(map[string]bool, 0)
}
env.Booleans[k] = t
continue
case int:
if env.Numbers == nil {
env.Numbers = make(map[string]int, 0)
}
env.Numbers[k] = t
continue
case []interface{}:
if env.Lists == nil {
env.Lists = make(map[string][]interface{}, 0)
}
env.Lists[k] = t
case string: // the "default" since everything could probably be typeswitched to a string otherwise.
if env.Strings == nil {
env.Strings = make(map[string]string, 0)
}
env.Strings[k] = t
}
}

*e = env

return
}

/*
EnvMap returns a map of environment variables.
The variable is an interface due to it attempting to use a variable's value to its "true" native type.
If a type cannot be determined, it will be treated a string.
*/
func EnvMap() (envs map[string]interface{}, err error) {

var ems map[string]string

if ems, err = EnvMapString(); err != nil {
return
}

for k, v := range ems {

// Is int?
if i, ok := getNum(v); ok {
envs[k] = i
continue
}

// Is bool?
if b, ok := getBool(v); ok {
envs[k] = b
continue
}

// Is array?
if a, ok := getArr(v); ok {
envs[k] = a
continue
}

// It's a string (probably).
envs[k] = v
}

return
}

// EnvMapString is like EnvMap, but all values are treated as strings.
func EnvMapString() (envs map[string]string, err error) {

var envArray []string

envs = make(map[string]string, 0)

envArray = os.Environ()

for _, e := range envArray {
var k, v string
var kv []string

kv = strings.SplitN(e, "=", 2)
k = kv[0]
if len(kv) == 2 {
v = kv[1]
} else {
v = ""
}
envs[k] = v
}

return
}

// UTILITY FUNCS

// getBool attempts to convert a string value to a boolean.
func getBool(s string) (b bool, ok bool) {

switch s2 := strings.ToLower(strings.TrimSpace(s)); s2 {
case "true", "yes", "y":
b = true
ok = true
case "false", "no", "n":
b = false
ok = true
}

return
}

// getNum attempts to convert a string value to an int.
func getNum(s string) (n int, ok bool) {

var err error

if n, err = strconv.Atoi(s); err == nil {
ok = true
}

return
}

// getArr attempts to convert a string value to an array of interface{}.
func getArr(s string) (a []interface{}, ok bool) {

var arrS []string

if arrS, ok = getArrStr(s); !ok {
return
}

a = make([]interface{}, len(arrS))

for idx, i := range arrS {
if b, ok := getBool(i); ok {
a[idx] = b
} else if n, ok := getNum(i); ok {
a[idx] = n
} else if l, ok := getArr(i); ok {
a[idx] = l
} else {
a[idx] = i
}
}

return
}

// getArrStr attempts to convert a string value to an array of strings.
func getArrStr(s string) (a []string, ok bool) {

var sep string = ":"

if runtime.GOOS == "windows" {
sep = ";"
}

a = strings.Split(s, sep)
l := len(s)
if l <= 1 {
return
}

ok = true

return
}

/*
GetEnvPid will only work on *NIX-like systems with procfs.
It gets the environment variables of a given process' PID.
*/
func GetEnvPid(pid uint32) (env map[string]string, err error) {

var envBytes []byte
var envArr [][]byte
var procPath string
var exists bool

env = make(map[string]string, 0)

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 = ioutil.ReadFile(procPath); err != nil {
return
}

envArr = bytes.Split(envBytes, []byte{0x0})

for _, b := range envArr {

// s := strings.TrimSpace(string(b))
s := string(b)
e := strings.SplitN(s, "=", 2)

for _, i := range e {

if len(i) != 2 {
env[string(i[0])] = ""
continue
}

env[string(i[0])] = string(i[1])

}
}

return
}

2
go.mod
View File

@ -1,5 +1,3 @@
module r00t2.io/sysutils

go 1.16

require github.com/jszwec/csvutil v1.5.0

2
go.sum
View File

@ -1,2 +0,0 @@
github.com/jszwec/csvutil v1.5.0 h1:ErLnF1Qzzt9svk8CUY7CyLl/W9eET+KWPIZWkE1o6JM=
github.com/jszwec/csvutil v1.5.0/go.mod h1:Rpu7Uu9giO9subDyMCIQfHVDuLrcaC36UA4YcJjGBkg=

8
internal/consts.go Normal file
View File

@ -0,0 +1,8 @@
package internal

// OS-specific path environment variable name. The default is "PATH".
var (
pathEnvVarName map[string]string = map[string]string{
"windows": "Path",
}
)

18
internal/utils.go Normal file
View File

@ -0,0 +1,18 @@
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
}

View File

@ -21,23 +21,25 @@ package paths
import (
"errors"
`fmt`
`io/fs`
"os"
"os/user"
"path/filepath"
`runtime`
// "strconv"
"strings"
`strings`

// "syscall"
)

/*
ExpandHome will take a tilde(~)-prefixed path and resolve it to the actual path in-place.
Note that it only works for current user; the syntax ~someotheruser/foo/bar is currently unsupported.
"Nested" user paths (~someuser/somechroot/~someotheruser) are not supported as home directories are expected to be absolute paths.
*/
func ExpandHome(path *string) (err error) {

var usr *user.User
var unameSplit []string
var uname string

var u *user.User

// Props to this guy.
// https://stackoverflow.com/a/43578461/733214
@ -51,40 +53,41 @@ func ExpandHome(path *string) (err error) {
// E(ffective)UID (e.g. chown'd user for SUID)
/*
uid := strconv.Itoa(syscall.Geteuid())
usr, err := user.LookupId(euid)
u, err := user.LookupId(euid)
*/
// (Real)UID (invoking user)
if usr, err = user.Current(); err != nil {
return err
}
*path = filepath.Join(usr.HomeDir, (*path)[1:])

return
}

// GetPathEnv returns a slice of the PATH variable's items.
func GetPathEnv() (paths []string, err error) {

var pathVar string = "PATH"
var sep string = ":"

paths = make([]string, 0)

if runtime.GOOS == "windows" {
pathVar = "Path"
sep = ";"
}

for _, p := range strings.Split(os.Getenv(pathVar), sep) {
if err = RealPath(&p); err != nil {
/*
if u, err = user.Current(); err != nil {
return
}
paths = append(paths, p)
*/
// K but do it smarter.
unameSplit = strings.SplitAfterN(*path, string(os.PathSeparator), 2)
if len(unameSplit) != 2 {
unameSplit = append(unameSplit, "")
}

uname = strings.TrimPrefix(unameSplit[0], "~")
if uname == "" {
if u, err = user.Current(); err != nil {
return
}
} else {
if u, err = user.Lookup(uname); err != nil {
return
}
}

*path = filepath.Join(u.HomeDir, unameSplit[1])

return
}

// MakeDirIfNotExist will create a directory at a given path if it doesn't exist.
/*
MakeDirIfNotExist will create a directory at a given path if it doesn't exist.

See also the documentation for RealPath.
*/
func MakeDirIfNotExist(path string) (err error) {

var stat os.FileInfo
@ -117,7 +120,14 @@ func MakeDirIfNotExist(path string) (err error) {
return
}

// RealPath will transform a given path into the very best guess for an absolute path in-place.
/*
RealPath will transform a given path into the very best guess for an absolute path in-place.

It is recommended to check err (if not nil) for an invalid path error. If this is true, the
path syntax/string itself is not supported on the runtime OS. This can be done via:

if errors.Is(err, fs.ErrInvalid) {...}
*/
func RealPath(path *string) (err error) {

if err = ExpandHome(path); err != nil {
@ -135,18 +145,28 @@ func RealPath(path *string) (err error) {
RealPathExists is like RealPath, but will also return a boolean as to whether the path
actually exists or not.

It's hacky, but the "exists" bool along with err is a sort of proto-state-machine.
If err != nil and bool is true, the error occurred during path absolution.
If err != nil and bool is false, the path does not exist.
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
via:

if errors.Is(err, fs.ErrPermission) {...}

See also the documentation for RealPath.

In those cases, it may be preferable to use RealPathExistsStat and checking stat for nil.
*/
func RealPathExists(path *string) (exists bool, err error) {

if err = RealPath(path); err != nil {
exists = true
return
}

if _, err = os.Stat(*path); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
err = nil
}
return
}

@ -155,12 +175,16 @@ func RealPathExists(path *string) (exists bool, err error) {
return
}

// RealPathExistsStat is like RealPathExists except it will also return the os.FileInfo for the path (assuming it exists).
/*
RealPathExistsStat is like RealPathExists except it will also return the os.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.
*/
func RealPathExistsStat(path *string) (exists bool, stat os.FileInfo, err error) {

// See the comments for RealPathExists for details on this.
if err = RealPath(path); err != nil {
exists = true
if exists, err = RealPathExists(path); err != nil {
return
}

@ -168,7 +192,5 @@ func RealPathExistsStat(path *string) (exists bool, stat os.FileInfo, err error)
return
}

exists = true

return
}

View File

@ -16,12 +16,10 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/


package terminal

import (
"os"
"fmt"
)

// IsShell returns true if the program is running inside an interactive shell (interactive invocation, sudo, etc.), and false if not (cron, ssh exec, pipe, etc.).
@ -32,8 +30,8 @@ func IsShell() (interactive bool) {

stdoutStat, _ = os.Stdout.Stat()

if (stdoutStaf.Mode() & os.ModeCharDevice) != 0 {
interactive = True
if (stdoutStat.Mode() & os.ModeCharDevice) != 0 {
interactive = true
}

return