8 Commits

25 changed files with 684 additions and 458 deletions

View File

@@ -34,8 +34,8 @@ package main
import ( import (
`fmt` `fmt`
`r00t2.io/sysutils/net/ports` `r00t2.io/sysutils/.net.UNFINISHED/ports`
`r00t2.io/sysutils/net/protos` `r00t2.io/sysutils/.net.UNFINISHED/protos`
) )
func main() { func main() {
@@ -77,8 +77,8 @@ import (
`fmt` `fmt`
`log` `log`
`r00t2.io/sysutils/net/ports` `r00t2.io/sysutils/.net.UNFINISHED/ports`
`r00t2.io/sysutils/net/protos` `r00t2.io/sysutils/.net.UNFINISHED/protos`
) )
func main() { func main() {
@@ -114,7 +114,7 @@ import (
`fmt` `fmt`
`log` `log`
`r00t2.io/sysutils/net/ports` `r00t2.io/sysutils/.net.UNFINISHED/ports`
) )
func main() { func main() {
@@ -148,7 +148,7 @@ package main
import ( import (
`fmt` `fmt`
`r00t2.io/sysutils/net/protos` `r00t2.io/sysutils/.net.UNFINISHED/protos`
) )
func main() { func main() {
@@ -184,7 +184,7 @@ import (
`fmt` `fmt`
`log` `log`
`r00t2.io/sysutils/net/protos` `r00t2.io/sysutils/.net.UNFINISHED/protos`
) )
func main() { func main() {
@@ -217,7 +217,7 @@ import (
`fmt` `fmt`
`log` `log`
`r00t2.io/sysutils/net/protos` `r00t2.io/sysutils/.net.UNFINISHED/protos`
) )
func main() { func main() {

View File

@@ -5,7 +5,7 @@ import (
"io" "io"
"net/http" "net/http"
"r00t2.io/sysutils/net/ports" _ "r00t2.io/sysutils/.net.UNFINISHED/ports"
) )
func download(url string) (b *[]byte, err error) { func download(url string) (b *[]byte, err error) {

View File

@@ -8,10 +8,11 @@ import (
"strconv" "strconv"
"strings" "strings"
// https://pkg.go.dev/github.com/jszwec/csvutil but I can't seem to fetch it.
"github.com/jszwec/csvutil" "github.com/jszwec/csvutil"
"r00t2.io/sysutils/net/ports" "r00t2.io/sysutils/.net.UNFINISHED/ports"
"r00t2.io/sysutils/net/protos" "r00t2.io/sysutils/.net.UNFINISHED/protos"
"r00t2.io/sysutils/paths" "r00t2.io/sysutils/paths"
) )

View File

@@ -1,7 +1,7 @@
package ports package ports
import ( import (
"r00t2.io/sysutils/net/protos" "r00t2.io/sysutils/.net.UNFINISHED/protos"
) )
type IPPort struct { type IPPort struct {

30
TODO Normal file
View File

@@ -0,0 +1,30 @@
- password generator utility/library
-- incorporate with https://github.com/tredoe/osutil ?
-- cli flag to dump flat hashes too
--- https://github.com/hlandau/passlib
-- incoprporated separately; https://git.r00t2.io/r00t2/PWGen (import r00t2.io/pwgen)
- 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
-- 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.MustCompile(`^(?P<sign>\+|-)[0-9]+$`)
reMaybeFloat *regexp.Regexp = regexp.MustCompile(`(?P<sign>\+|-)?[0-9]+\.[0-9]+$`)
)

187
envs/funcs.go Normal file
View File

@@ -0,0 +1,187 @@
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
}
/*
GetFirst gets the first instance if populated/set occurrence of varNames.
For example, if you have three potential env vars, FOO, FOOBAR, FOOBARBAZ,
and want to follow the logic flow of:
1.) Check if FOO is set. If not,
2.) Check if FOOBAR is set. If not,
3.) Check if FOOBARBAZ is set.
Then this would be specified as:
GetFirst([]string{"FOO", "FOOBAR", "FOOBARBAZ"})
If val is "" and ok is true, this means that one of the specified variable names IS
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.
*/
func GetFirst(varNames []string) (val string, ok bool) {
val, ok, _ = GetFirstWithRef(varNames)
return
}
/*
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"})
is called and FOO is not set but FOOBAR is, idx will be 1.
If ok is false, idx will always be -1 and should be ignored.
*/
func GetFirstWithRef(varNames []string) (val string, ok bool, idx int) {
idx = -1
for i, vn := range varNames {
if HasEnv(vn) {
ok = true
idx = i
val = os.Getenv(vn)
return
}
}
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
}
/*
HasEnv is much like os.LookupEnv, but only returns a boolean for
if the environment variable key exists or not.
This is useful anywhere you may need to set a boolean in a func call
depending on the *presence* of an env var or not.
*/
func HasEnv(key string) (envIsSet bool) {
_, envIsSet = os.LookupEnv(key)
return
}

87
envs/utils.go Normal file
View File

@@ -0,0 +1,87 @@
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.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{}, 0)
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 module r00t2.io/sysutils
go 1.16 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

@@ -1,174 +0,0 @@
/*
SysUtils - a library to assist with various system-related functions
Copyright (C) 2020 Brent Saner
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package paths
import (
"errors"
`fmt`
"os"
"os/user"
"path/filepath"
`runtime`
// "strconv"
"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.
*/
func ExpandHome(path *string) (err error) {
var usr *user.User
// Props to this guy.
// https://stackoverflow.com/a/43578461/733214
if len(*path) == 0 {
err = errors.New("empty path")
return
} else if (*path)[0] != '~' {
return
}
// E(ffective)UID (e.g. chown'd user for SUID)
/*
uid := strconv.Itoa(syscall.Geteuid())
usr, 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 {
return
}
paths = append(paths, p)
}
return
}
// MakeDirIfNotExist will create a directory at a given path if it doesn't exist.
func MakeDirIfNotExist(path string) (err error) {
var stat os.FileInfo
var exists bool
var locPath string = path
if exists, stat, err = RealPathExistsStat(&locPath); err != nil {
if !exists {
// This, at least as of golang 1.15, uses the user's umask.
// It does not actually create a dir with 0777.
// It's up to the caller to do an os.Chmod() on the path after, if desired.
if err = os.MkdirAll(locPath, 0777); err != nil {
return
}
err = nil
return
} else {
return
}
}
// So it exists, but it probably isn't a dir.
if !stat.Mode().IsDir() {
err = errors.New(fmt.Sprintf("path %v exists but is not a directory", locPath))
return
}
// This should probably never happen. Probably.
err = errors.New("undefined")
return
}
// RealPath will transform a given path into the very best guess for an absolute path in-place.
func RealPath(path *string) (err error) {
if err = ExpandHome(path); err != nil {
return
}
if *path, err = filepath.Abs(*path); err != nil {
return
}
return
}
/*
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.
*/
func RealPathExists(path *string) (exists bool, err error) {
if err = RealPath(path); err != nil {
exists = true
return
}
if _, err = os.Stat(*path); err != nil {
return
}
exists = true
return
}
// RealPathExistsStat is like RealPathExists except it will also return the os.FileInfo for the path (assuming it exists).
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
return
}
if stat, err = os.Stat(*path); err != nil {
return
}
exists = true
return
}

268
paths/funcs.go Normal file
View File

@@ -0,0 +1,268 @@
/*
SysUtils - a library to assist with various system-related functions
Copyright (C) 2020 Brent Saner
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package paths
import (
"errors"
"fmt"
"io/fs"
"os"
"os/user"
"path/filepath"
"strings"
// "syscall"
)
/*
ExpandHome will take a tilde(~)-prefixed path and resolve it to the actual path in-place.
"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 unameSplit []string
var uname string
var u *user.User
// Props to this guy.
// https://stackoverflow.com/a/43578461/733214
if len(*path) == 0 {
err = errors.New("empty path")
return
} else if (*path)[0] != '~' {
return
}
// E(ffective)UID (e.g. chown'd user for SUID)
/*
uid := strconv.Itoa(syscall.Geteuid())
u, err := user.LookupId(euid)
*/
// (Real)UID (invoking user)
/*
if u, err = user.Current(); err != nil {
return
}
*/
// K but do it smarter.
unameSplit = strings.SplitN(*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
}
/*
GetFirst is the file equivalent of envs.GetFirst.
It iterates through paths, normalizing them along the way
(so abstracted paths such as ~/foo/bar.txt and relative paths
such as bar/baz.txt will still work), and returns the content
of the first found existing file. If the first found path
is a directory, content will be nil but isDir will be true
(as will ok).
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.
*/
func GetFirst(paths []string) (content []byte, isDir, ok bool) {
content, isDir, ok, _ = GetFirstWithRef(paths)
return
}
/*
GetFirstWithRef is the file equivalent of envs.GetFirstWithRef.
It behaves exactly like GetFirst, but with an additional returned value, idx,
which specifies the index in paths in which a path was found.
As always, results are not guaranteed due to permissions, etc.
potentially returning an inaccurate result.
*/
func GetFirstWithRef(paths []string) (content []byte, isDir, ok bool, idx int) {
var locPaths []string
var exists bool
var stat os.FileInfo
var err error
idx = -1
// We have to be a little less cavalier about this.
if paths == nil {
return
}
locPaths = make([]string, len(paths))
locPaths = paths[:] // Create an explicit copy so we don't modify paths.
for i, p := range locPaths {
if exists, stat, err = RealPathExistsStat(&p); err != nil {
err = nil
continue
}
if !exists {
continue
}
isDir = stat.IsDir()
if !isDir {
if content, err = os.ReadFile(p); err != nil {
continue
}
}
ok = true
idx = i
return
}
return
}
/*
MakeDirIfNotExist will create a directory at a given path if it doesn't exist.
See also the documentation for RealPath.
This is a bit more sane option than os.MkdirAll as it will normalize paths a little better.
*/
func MakeDirIfNotExist(path string) (err error) {
var stat os.FileInfo
var exists bool
var locPath string = path
if exists, stat, err = RealPathExistsStat(&locPath); err != nil {
if !exists {
// This, at least as of golang 1.15, uses the user's umask.
// It does not actually create a dir with 0777.
// It's up to the caller to do an os.Chmod() on the path after, if desired.
if err = os.MkdirAll(locPath, 0777); err != nil {
return
}
err = nil
return
} else {
return
}
}
// So it exists, but it probably isn't a dir.
if !stat.Mode().IsDir() {
err = errors.New(fmt.Sprintf("path %v exists but is not a directory", locPath))
return
} else {
return
}
// This should probably never happen. Probably.
err = errors.New("undefined")
return
}
/*
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 {
return
}
if *path, err = filepath.Abs(*path); err != nil {
return
}
return
}
/*
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
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 {
return
}
if _, err = os.Stat(*path); err != nil {
if errors.Is(err, fs.ErrNotExist) {
err = nil
}
return
}
exists = true
return
}
/*
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) {
if exists, err = RealPathExists(path); err != nil {
return
}
if stat, err = os.Stat(*path); err != nil {
return
}
return
}

View File

@@ -16,12 +16,10 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package terminal package terminal
import ( import (
"os" "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.). // 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() stdoutStat, _ = os.Stdout.Stat()
if (stdoutStaf.Mode() & os.ModeCharDevice) != 0 { if (stdoutStat.Mode() & os.ModeCharDevice) != 0 {
interactive = True interactive = true
} }
return return