From 8f582d37f17eca6106a7340cc01c1f240a3414eb Mon Sep 17 00:00:00 2001 From: brent s Date: Sat, 18 Dec 2021 04:23:35 -0500 Subject: [PATCH] what a rabbithole. lots of goodies now. --- {net => .net.UNFINISHED}/README.adoc | 16 +- {net => .net.UNFINISHED}/build.sh | 0 {net => .net.UNFINISHED}/consts.go | 0 {net => .net.UNFINISHED}/genfuncs.go | 2 +- {net => .net.UNFINISHED}/gentypes.go | 0 {net => .net.UNFINISHED}/main.go | 5 +- {net => .net.UNFINISHED}/ports/data.go | 0 {net => .net.UNFINISHED}/ports/funcs.go | 0 {net => .net.UNFINISHED}/ports/types.go | 2 +- {net => .net.UNFINISHED}/protos/data.go | 0 {net => .net.UNFINISHED}/protos/funcs.go | 0 {net => .net.UNFINISHED}/protos/types.go | 0 TODO | 18 ++ envs/.TODO.go.UNFINISHED | 60 ++++++ envs/consts.go | 11 + envs/funcs.go | 120 +++++++++++ envs/utils.go | 85 ++++++++ funcs.go | 264 ----------------------- go.mod | 2 - go.sum | 2 - internal/consts.go | 8 + internal/utils.go | 18 ++ paths/{func.go => funcs.go} | 106 +++++---- terminal/funcs.go | 6 +- 24 files changed, 399 insertions(+), 326 deletions(-) rename {net => .net.UNFINISHED}/README.adoc (92%) rename {net => .net.UNFINISHED}/build.sh (100%) rename {net => .net.UNFINISHED}/consts.go (100%) rename {net => .net.UNFINISHED}/genfuncs.go (90%) rename {net => .net.UNFINISHED}/gentypes.go (100%) rename {net => .net.UNFINISHED}/main.go (96%) rename {net => .net.UNFINISHED}/ports/data.go (100%) rename {net => .net.UNFINISHED}/ports/funcs.go (100%) rename {net => .net.UNFINISHED}/ports/types.go (78%) rename {net => .net.UNFINISHED}/protos/data.go (100%) rename {net => .net.UNFINISHED}/protos/funcs.go (100%) rename {net => .net.UNFINISHED}/protos/types.go (100%) create mode 100644 TODO create mode 100644 envs/.TODO.go.UNFINISHED create mode 100644 envs/consts.go create mode 100644 envs/funcs.go create mode 100644 envs/utils.go delete mode 100644 funcs.go create mode 100644 internal/consts.go create mode 100644 internal/utils.go rename paths/{func.go => funcs.go} (57%) diff --git a/net/README.adoc b/.net.UNFINISHED/README.adoc similarity index 92% rename from net/README.adoc rename to .net.UNFINISHED/README.adoc index c2e3df4..a4541a3 100644 --- a/net/README.adoc +++ b/.net.UNFINISHED/README.adoc @@ -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() { diff --git a/net/build.sh b/.net.UNFINISHED/build.sh similarity index 100% rename from net/build.sh rename to .net.UNFINISHED/build.sh diff --git a/net/consts.go b/.net.UNFINISHED/consts.go similarity index 100% rename from net/consts.go rename to .net.UNFINISHED/consts.go diff --git a/net/genfuncs.go b/.net.UNFINISHED/genfuncs.go similarity index 90% rename from net/genfuncs.go rename to .net.UNFINISHED/genfuncs.go index 8879504..4fc4767 100644 --- a/net/genfuncs.go +++ b/.net.UNFINISHED/genfuncs.go @@ -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) { diff --git a/net/gentypes.go b/.net.UNFINISHED/gentypes.go similarity index 100% rename from net/gentypes.go rename to .net.UNFINISHED/gentypes.go diff --git a/net/main.go b/.net.UNFINISHED/main.go similarity index 96% rename from net/main.go rename to .net.UNFINISHED/main.go index 111b31e..92ff74d 100644 --- a/net/main.go +++ b/.net.UNFINISHED/main.go @@ -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" ) diff --git a/net/ports/data.go b/.net.UNFINISHED/ports/data.go similarity index 100% rename from net/ports/data.go rename to .net.UNFINISHED/ports/data.go diff --git a/net/ports/funcs.go b/.net.UNFINISHED/ports/funcs.go similarity index 100% rename from net/ports/funcs.go rename to .net.UNFINISHED/ports/funcs.go diff --git a/net/ports/types.go b/.net.UNFINISHED/ports/types.go similarity index 78% rename from net/ports/types.go rename to .net.UNFINISHED/ports/types.go index 93f0e2e..cba4e22 100644 --- a/net/ports/types.go +++ b/.net.UNFINISHED/ports/types.go @@ -1,7 +1,7 @@ package ports import ( - "r00t2.io/sysutils/net/protos" + "r00t2.io/sysutils/.net.UNFINISHED/protos" ) type IPPort struct { diff --git a/net/protos/data.go b/.net.UNFINISHED/protos/data.go similarity index 100% rename from net/protos/data.go rename to .net.UNFINISHED/protos/data.go diff --git a/net/protos/funcs.go b/.net.UNFINISHED/protos/funcs.go similarity index 100% rename from net/protos/funcs.go rename to .net.UNFINISHED/protos/funcs.go diff --git a/net/protos/types.go b/.net.UNFINISHED/protos/types.go similarity index 100% rename from net/protos/types.go rename to .net.UNFINISHED/protos/types.go diff --git a/TODO b/TODO new file mode 100644 index 0000000..eeca37f --- /dev/null +++ b/TODO @@ -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 diff --git a/envs/.TODO.go.UNFINISHED b/envs/.TODO.go.UNFINISHED new file mode 100644 index 0000000..45c2914 --- /dev/null +++ b/envs/.TODO.go.UNFINISHED @@ -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 +} diff --git a/envs/consts.go b/envs/consts.go new file mode 100644 index 0000000..754bc02 --- /dev/null +++ b/envs/consts.go @@ -0,0 +1,11 @@ +package envs + +import ( + `regexp` +) + +// Compiled regex patterns. +var ( + reMaybeInt *regexp.Regexp = regexp.MustCompilePOSIX(`^(?P\+|-)[0-9]+$`) + reMaybeFloat *regexp.Regexp = regexp.MustCompilePOSIX(`(?P\+|-)?[0-9]+\.[0-9]+$`) +) diff --git a/envs/funcs.go b/envs/funcs.go new file mode 100644 index 0000000..3fb22b9 --- /dev/null +++ b/envs/funcs.go @@ -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 +} diff --git a/envs/utils.go b/envs/utils.go new file mode 100644 index 0000000..fbf2d2b --- /dev/null +++ b/envs/utils.go @@ -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 +} diff --git a/funcs.go b/funcs.go deleted file mode 100644 index 9082ec6..0000000 --- a/funcs.go +++ /dev/null @@ -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 -} diff --git a/go.mod b/go.mod index 65cb0a2..0b962a7 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ module r00t2.io/sysutils go 1.16 - -require github.com/jszwec/csvutil v1.5.0 diff --git a/go.sum b/go.sum index 70ac115..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/consts.go b/internal/consts.go new file mode 100644 index 0000000..268ac23 --- /dev/null +++ b/internal/consts.go @@ -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", + } +) diff --git a/internal/utils.go b/internal/utils.go new file mode 100644 index 0000000..657a4af --- /dev/null +++ b/internal/utils.go @@ -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 +} diff --git a/paths/func.go b/paths/funcs.go similarity index 57% rename from paths/func.go rename to paths/funcs.go index 05e51b3..3697782 100644 --- a/paths/func.go +++ b/paths/funcs.go @@ -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 } diff --git a/terminal/funcs.go b/terminal/funcs.go index 68b7119..0326ebe 100644 --- a/terminal/funcs.go +++ b/terminal/funcs.go @@ -16,12 +16,10 @@ along with this program. If not, see . */ - 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