9 Commits

24 changed files with 549 additions and 195 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() {
@@ -63,7 +63,7 @@ func main() {
"\tService: %v\n" +
"\tDesc: %v\n" +
"\tReserved?: %v\n",
p.Number, p.Proto.Name, p.ServiceName, p.Description, p.Reserved)
p.Number, p.Protocol.Name, p.ServiceName, p.Description, p.Reserved)
}
----
@@ -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.MustCompile(`^(?P<sign>\+|-)[0-9]+$`)
reMaybeFloat *regexp.Regexp = regexp.MustCompile(`(?P<sign>\+|-)?[0-9]+\.[0-9]+$`)
)

134
envs/funcs.go Normal file
View File

@@ -0,0 +1,134 @@
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
}
/*
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
}

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

@@ -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 (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"os/user"
"path/filepath"
// "strconv"
"strings"
// "syscall"
)
var err error
func ExpandHome(path *string) (err error) {
// Props to this guy.
// https://stackoverflow.com/a/43578461/733214
if len(*path) == 0 {
return errors.New("empty path")
} 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)
*/
// R(real)UID (invoking user)
usr, err := user.Current()
if err != nil {
return
}
*path = filepath.Join(usr.HomeDir, (*path)[1:])
return
}
func GetPathEnv() (s []string, err error) {
s = make([]string, 0)
for _, p := range strings.Split(os.Getenv("PATH"), ":") {
if err = RealPath(&p); err != nil {
return
}
s = append(s, p)
}
return
}
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)
procPath = fmt.Sprintf("/proc/%v/environ", pid)
if exists, err = RealPathExists(&procPath); err != nil {
return
}
if !exists {
err = errors.New(fmt.Sprintf("information for pid %v does not exist", pid))
}
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 {
continue
}
env[string(i[0])] = string(i[1])
}
}
return
}
func MakeDirIfNotExist(path *string) error {
exists, stat, err := RealPathExistsStat(path)
if 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.
os.MkdirAll(*path, 0777)
return nil
} else {
return err
}
}
// So it exists, but it probably isn't a dir.
if !stat.Mode().IsDir() {
return errors.New(fmt.Sprintf("path %v exists but is not a directory", *path))
}
// This should probably never happen. Probably.
return errors.New("undefined behaviour")
}
func RealPath(path *string) error {
err := ExpandHome(path)
if err != nil {
return err
}
*path, err = filepath.Abs(*path)
if err != nil {
return err
}
return nil
}
func RealPathExists(path *string) (exists bool, err error) {
if err = RealPath(path); err != nil {
return
}
if _, err = os.Stat(*path); err != nil {
if os.IsNotExist(err) {
exists = false
err = nil
} else {
return
}
} else {
exists = true
}
return
}
func RealPathExistsStat(path *string) (exists bool, stat os.FileInfo, err error) {
if err = RealPath(path); err != nil {
return
}
if stat, err = os.Stat(*path); err != nil {
if os.IsNotExist(err) {
exists = false
err = nil
} else {
return
}
} else {
exists = true
}
return
}

197
paths/funcs.go Normal file
View File

@@ -0,0 +1,197 @@
/*
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
}
/*
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
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/>.
*/
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