2 Commits

Author SHA1 Message Date
brent saner
7b0156775c v1.13.0
ADDED:
* Convenience functions to determine if a process is running in an
  elevated/dropped privileges context
2025-04-21 02:29:24 -04:00
brent saner
c6efc2d83c v1.12.0
FIXED:
* paths: Async searching works correctly now, and is consolidated to a
  single struct for searching options for async and synchronous
  searches.
2024-11-18 17:36:14 -05:00
11 changed files with 654 additions and 190 deletions

7
TODO
View File

@@ -1,8 +1,9 @@
- refactor the elevation detection stuff. I'm not terribly happy with it.
- password generator 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
-- incoprporated separately; https://git.r00t2.io/r00t2/PWGen (import r00t2.io/pwgen)
-- 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.

11
fsutils/errs.go Normal file
View File

@@ -0,0 +1,11 @@
package fsutils
import (
`syscall`
)
var (
// Yes, I know. "Why ENOTTY?" I don't know, ask Linus.
// If you see "inappropriate ioctl for device", it's this'un.
ErrFsAttrsUnsupported error = syscall.ENOTTY
)

163
funcs_idstate.go Normal file
View File

@@ -0,0 +1,163 @@
package sysutils
// Checked consolidates all the provided checked functions.
func (i *IDState) Checked() (checked bool) {
if i == nil {
return
}
checked = i.uidsChecked &&
i.gidsChecked &&
i.sudoChecked &&
i.ppidUidChecked &&
i.ppidGidChecked
return
}
/*
IsReal consolidates all the elevation/dropped-privs checks into a single method.
It will only return true if no sudo was detected and *all* UIDs/GIDs match.
*/
func (i *IDState) IsReal(real bool) {
if i == nil {
return
}
real = true
for _, b := range []bool{
i.IsSuid(),
i.IsSgid(),
i.IsSudoUser(),
i.IsSudoGroup(),
} {
if b {
real = false
return
}
}
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).
It will always return false if SudoChecked returns false oor PPIDGIDsChecked returns false.
*/
func (i *IDState) IsSudoGroup() (sudo bool) {
if i == nil || !i.sudoChecked || !i.ppidGidChecked {
return
}
sudo = i.SudoEnvGroup || !i.PPIDGidMatch
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).
It will always return false if SudoChecked returns false or PPIDUIDsChecked returns false.
*/
func (i *IDState) IsSudoUser() (sudo bool) {
if i == nil || !i.sudoChecked || !i.ppidUidChecked {
return
}
sudo = i.SudoEnvUser || !i.PPIDUidMatch
return
}
// IsSuid is true if the RUID does not match EUID or SUID. It will always return false if UIDsChecked returns false.
func (i *IDState) IsSuid() (suid bool) {
if i == nil || !i.uidsChecked {
return
}
suid = i.RUID != i.EUID || i.RUID != i.SUID
return
}
// IsSgid is true if the RGID does not match EGID or SGID. It will always return false if GIDsChecked returns false.
func (i *IDState) IsSgid() (sgid bool) {
if i == nil || !i.gidsChecked {
return
}
sgid = i.RGID != i.EGID || i.RGID != i.SGID
return
}
// GIDsChecked is true if the GIDs presented can be trusted.
func (i *IDState) GIDsChecked() (checked bool) {
if i == nil {
return
}
checked = i.gidsChecked
return
}
// PPIDGIDsChecked is true if PPIDGidMatch can be trusted.
func (i *IDState) PPIDGIDsChecked() (checked bool) {
if i == nil {
return
}
checked = i.ppidGidChecked
return
}
// PPIDUIDsChecked is true if PPIDUidMatch can be trusted.
func (i *IDState) PPIDUIDsChecked() (checked bool) {
if i == nil {
return
}
checked = i.ppidUidChecked
return
}
// SudoChecked is true if SudoEnvVars can be trusted
func (i *IDState) SudoChecked() (checked bool) {
if i == nil {
return
}
checked = i.sudoChecked
return
}
// UIDsChecked is true if the UIDs presented can be trusted.
func (i *IDState) UIDsChecked() (checked bool) {
if i == nil {
return
}
checked = i.uidsChecked
return
}

50
funcs_linux.go Normal file
View File

@@ -0,0 +1,50 @@
package sysutils
import (
`fmt`
`os`
`golang.org/x/sys/unix`
`r00t2.io/sysutils/envs`
)
// 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
}

1
paths/TODO Normal file
View File

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

View File

@@ -17,6 +17,17 @@ const (
modeAnyExceptRegular pathMode = modeDir | modeSymlink | modePipe | modeSocket | modeDev | modeCharDev | modeIrregular
)
// Miss reasons
const (
MissNoMiss missReason = ""
MissNoMeta missReason = "Could not determine metadata"
MissBadBase missReason = "Base name does not match BasePtrn"
MissBadPath missReason = "Path does not match PathPtrn"
MissBadTime missReason = "Time(s) does not/do not match Age"
MissFile missReason = "Object is a file and NoFiles is set"
MissType missReason = "Object does not match TargetType"
)
// Times
const TimeAny pathTimeType = 0
const (

12
paths/errs.go Normal file
View File

@@ -0,0 +1,12 @@
package paths
import (
`errors`
)
var (
ErrNilErrChan error = errors.New("an initialized error channel is required")
ErrNilMatchChan error = errors.New("an initialized matches channel is required")
ErrNilMismatchChan error = errors.New("an initialized mismatches channel is required")
ErrNilWg error = errors.New("a non-nil sync.WaitGroup is required")
)

View File

@@ -26,8 +26,7 @@ import (
"os"
"os/user"
"path/filepath"
`regexp`
`slices`
`sort`
"strings"
`sync`
`time`
@@ -35,7 +34,6 @@ import (
// "syscall"
`github.com/djherbis/times`
`golang.org/x/sync/semaphore`
`r00t2.io/goutils/bitmask`
)
@@ -277,86 +275,33 @@ func RealPathExistsStat(path *string) (exists bool, stat os.FileInfo, err error)
return
}
/*
SearchPaths gets a file/directory path list based on the provided criteria.
// SearchFsPaths gets a file/directory/etc. path list based on the provided criteria.
func SearchFsPaths(matcher FsSearchCriteria) (found, miss []*FsSearchResult, err error) {
targetType defines what should be included in the path list.
It can consist of one or more (io/)fs.FileMode types OR'd together
(ensure they are part of (io/)fs.ModeType).
(You can use 0 to match regular files explicitly, and/or noFiles = true to exclude them.)
var matched *FsSearchResult
var missed *FsSearchResult
noFiles, if true, will explicitly filter out regular files from the path results.
(Normally they are *always* included regardless of targetType.)
basePtrn may be nil; if it isn't, it will be applied to *base names*
(that is, quux.txt rather than /foo/bar/baz/quux.txt).
pathPtrn is like basePtrn except it applies to the *entire* path,
not just the basename, if not nil (e.g. /foo/bar/baz/quux.txt,
not just quux.txt).
If age is not nil, it will be applied to the path object.
It will match older files/directories/etc. if olderThan is true,
otherwise it will match newer files/directories/etc.
(olderThan is not used otherwise.)
ageType is one or more Time* constants OR'd together to describe which timestamp type to check.
(Note that TimeCreated may not match if specified as it is only available on certain OSes,
kernel versions, and filesystems. This may lead to files being excluded that may have otherwise
been included.)
(You can use TimeAny to specify any supported time.)
*Any* matching timestamp of all specified (and supported) timestamp types matches,
so be judicious with your selection. They are processed in order of:
* btime (birth/creation time) (if supported)
* mtime (modification time -- contents have changed)
* ctime (OS-specific behavior; generally disk metadata has changed) (if supported)
* atime (access time)
olderThan (as mentioned above) will find paths *older* than age if true, otherwise *newer*.
now, if not nil, will be used to compare the age of files. (If nil, it will be populated at time of call.)
*/
func SearchFsPaths(
root string,
targetType fs.FileMode, noFiles bool,
basePtrn, pathPtrn *regexp.Regexp,
age *time.Duration, ageType pathTimeType, olderThan bool, now *time.Time,
) (foundPaths []string, err error) {
if age != nil {
if now == nil {
now = new(time.Time)
*now = time.Now()
}
}
if err = RealPath(&root); err != nil {
if err = RealPath(&matcher.Root); err != nil {
return
}
if err = filepath.WalkDir(
root,
matcher.Root,
func(path string, d fs.DirEntry, inErr error) (outErr error) {
var include bool
if inErr != nil {
outErr = inErr
return
}
if include, outErr = filterPath(
path, d,
targetType, noFiles,
basePtrn, pathPtrn,
age, ageType, olderThan, now,
); outErr != nil {
if matched, missed, outErr = matcher.Match(path, d, nil); outErr != nil {
return
}
if include {
foundPaths = append(foundPaths, path)
if matched != nil && !matcher.NoMatch {
found = append(found, matched)
}
if missed != nil && !matcher.NoMismatch {
miss = append(miss, missed)
}
return
@@ -365,8 +310,18 @@ func SearchFsPaths(
return
}
if found == nil || len(found) == 0 {
return
}
// And sort them.
slices.Sort(foundPaths)
sort.Slice(
found,
func(i, j int) (isLess bool) {
isLess = found[i].Path < found[j].Path
return
},
)
return
}
@@ -375,162 +330,107 @@ func SearchFsPaths(
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.
Additional options are documented below.
Note that unlike SearchFsPaths, the results written to foundPathsChan 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 been initialized by the caller ahead of time,
and all provided channels will be closed upon completion (so they are only safe
to READ from after invoking SearchFsPathsAsync).
foundPathsChan is a channel to which matched filepaths will be written.
sem/semCtx are optional; if not nil, they can be used to limit/"batch" concurrent tasks.
(semCtx is the context.Context used for sem when acquiring. It may be nil;
one will be locally created if so.)
The default will be to spawn all filtering logic concurrently.
For very large directories, you almost assuredly do not want that -- it
can cause a significant amount of I/O and CPU wait.
(See https://pkg.go.dev/golang.org/x/sync/semaphore for details.)
wg *must not* be nil, and must be managed by the caller.
SearchFsPathsAsync will exit with no errors but no-op if wg is nil.
errChan will receive any/all encountered errors.
All channels are expected to have already been initialized by the caller.
They will not be closed by this function.
*/
func SearchFsPathsAsync(
root string,
targetType fs.FileMode, noFiles bool,
basePtrn, pathPtrn *regexp.Regexp,
age *time.Duration, ageType pathTimeType, olderThan bool, now *time.Time,
foundPathsChan chan string,
sem *semaphore.Weighted, semCtx context.Context,
wg *sync.WaitGroup,
errChan chan error,
) {
func SearchFsPathsAsync(matcher FsSearchCriteriaAsync) {
var err error
var localWg sync.WaitGroup
var wgLocal sync.WaitGroup
var doneChan chan bool = make(chan bool, 1)
if wg == nil {
if matcher.ErrChan == nil {
panic(ErrNilErrChan)
return
}
if age != nil {
if now == nil {
now = new(time.Time)
*now = time.Now()
}
if matcher.WG == nil {
matcher.ErrChan <- ErrNilWg
return
}
if sem != nil && semCtx == nil {
semCtx = context.Background()
defer matcher.WG.Done()
if matcher.ResChan == nil && !matcher.NoMatch {
matcher.ErrChan <- ErrNilMatchChan
return
}
if matcher.MismatchChan == nil && !matcher.NoMismatch {
matcher.ErrChan <- ErrNilMismatchChan
return
}
if err = RealPath(&matcher.Root); err != nil {
matcher.ErrChan <- err
return
}
if matcher.Semaphore != nil && matcher.SemaphoreCtx == nil {
matcher.SemaphoreCtx = context.Background()
}
if err = filepath.WalkDir(
root,
matcher.Root,
func(path string, de fs.DirEntry, inErr error) (outErr error) {
localWg.Add(1)
wg.Add(1)
if sem != nil {
if err = sem.Acquire(semCtx, 1); err != nil {
if inErr != nil {
inErr = filterNoFileDir(inErr)
if inErr != nil {
outErr = inErr
return
}
}
wgLocal.Add(1)
if matcher.Semaphore != nil {
if err = matcher.Semaphore.Acquire(matcher.SemaphoreCtx, 1); err != nil {
return
}
}
go func(p string, d fs.DirEntry) {
var pErr error
var pInclude bool
var pResMatch *FsSearchResult
var pResMiss *FsSearchResult
defer localWg.Done()
defer wg.Done()
defer wgLocal.Done()
if sem != nil {
defer sem.Release(1)
if matcher.Semaphore != nil {
defer matcher.Semaphore.Release(1)
}
if pInclude, pErr = filterPath(p, d, targetType, noFiles, basePtrn, pathPtrn, age, ageType, olderThan, now); pErr != nil {
errChan <- pErr
if pResMatch, pResMiss, pErr = matcher.Match(p, d, nil); pErr != nil {
matcher.ErrChan <- pErr
return
}
if pInclude {
foundPathsChan <- p
if pResMatch != nil && !matcher.NoMatch {
matcher.ResChan <- pResMatch
}
if pResMiss != nil && !matcher.NoMismatch {
matcher.MismatchChan <- pResMiss
}
}(path, de)
return
},
); err != nil {
errChan <- err
err = filterNoFileDir(err)
if err != nil {
matcher.ErrChan <- err
return
}
}
go func() {
localWg.Wait()
close(foundPathsChan)
close(errChan)
wgLocal.Wait()
doneChan <- true
}()
return
}
// filterPath applies the filter logic used by SearchFSPaths and SearchFsPathsAync.
func filterPath(
path string, d fs.DirEntry,
targetType fs.FileMode, noFiles bool,
basePtrn, pathPtrn *regexp.Regexp,
age *time.Duration, ageType pathTimeType, olderThan bool, now *time.Time,
) (include bool, err error) {
var typeMode fs.FileMode
var fi fs.FileInfo
var tspec times.Timespec
var typeFilter *bitmask.MaskBit = bitmask.NewMaskBitExplicit(uint(targetType))
if age != nil {
if now == nil {
now = new(time.Time)
*now = time.Now()
}
}
// patterns
if pathPtrn != nil {
if !pathPtrn.MatchString(path) {
return
}
}
if basePtrn != nil {
if !basePtrn.MatchString(filepath.Base(path)) {
return
}
}
// age
if age != nil {
if tspec, err = times.Stat(path); err != nil {
return
}
if !filterTimes(tspec, age, &ageType, olderThan, now) {
return
}
}
// fs object type (file, dir, etc.)
if fi, err = d.Info(); err != nil {
return
}
typeMode = fi.Mode().Type()
if typeMode == 0 && noFiles {
return
} else if typeMode != 0 {
if !typeFilter.HasFlag(bitmask.MaskBit(typeMode)) {
return
}
}
include = true
<-doneChan
return
}
@@ -597,3 +497,13 @@ func filterTimes(tspec times.Timespec, age *time.Duration, ageType *pathTimeType
return
}
func filterNoFileDir(err error) (filtered error) {
filtered = err
if errors.Is(err, fs.ErrNotExist) {
filtered = nil
}
return
}

View File

@@ -0,0 +1,125 @@
package paths
import (
`io/fs`
`os`
`path/filepath`
`time`
`github.com/djherbis/times`
`r00t2.io/goutils/bitmask`
)
/*
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.
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.
*/
func (f *FsSearchCriteria) Match(path string, d fs.DirEntry, fi fs.FileInfo) (match, miss *FsSearchResult, err error) {
var typeMode fs.FileMode
var m FsSearchResult
var typeFilter *bitmask.MaskBit = bitmask.NewMaskBitExplicit(uint(f.TargetType))
m = FsSearchResult{
Path: path,
DirEntry: d,
FileInfo: fi,
Criteria: f,
}
if f == nil {
return
}
// A DirEntry can be created from a FileInfo but not vice versa.
if m.FileInfo == nil {
if m.DirEntry != nil {
if m.FileInfo, err = m.DirEntry.Info(); err != nil {
err = filterNoFileDir(err)
if err != nil {
return
}
}
} else {
if f.FollowSymlinks {
if m.FileInfo, err = os.Stat(path); err != nil {
err = filterNoFileDir(err)
if err != nil {
return
}
}
} else {
if m.FileInfo, err = os.Lstat(path); err != nil {
err = filterNoFileDir(err)
if err != nil {
return
}
}
}
m.DirEntry = fs.FileInfoToDirEntry(m.FileInfo)
}
}
if m.DirEntry == nil {
m.DirEntry = fs.FileInfoToDirEntry(m.FileInfo)
}
if m.DirEntry == nil || m.FileInfo == nil {
m.MissReason = MissNoMeta
miss = &m
return
}
if m.Times, err = times.Stat(path); err != nil {
err = filterNoFileDir(err)
if err != nil {
return
}
}
if f.PathPtrn != nil && !f.PathPtrn.MatchString(path) {
m.MissReason = MissBadPath
miss = &m
return
}
if f.BasePtrn != nil && !f.BasePtrn.MatchString(filepath.Base(path)) {
m.MissReason = MissBadBase
miss = &m
return
}
// age
if f.Age != nil {
if f.Now == nil {
f.Now = new(time.Time)
*f.Now = time.Now()
}
if !filterTimes(m.Times, f.Age, &f.AgeType, f.OlderThan, f.Now) {
m.MissReason = MissBadTime
miss = &m
return
}
}
// fs object type (file, dir, etc.)
typeMode = m.FileInfo.Mode().Type()
if typeMode == 0 && f.NoFiles {
m.MissReason = MissFile
miss = &m
return
} else if typeMode != 0 {
if !typeFilter.HasFlag(bitmask.MaskBit(typeMode)) {
m.MissReason = MissType
miss = &m
return
}
}
// If it gets to here, it matches.
match = &m
return
}

View File

@@ -1,9 +1,136 @@
package paths
import (
`context`
`io/fs`
`regexp`
`sync`
`time`
`github.com/djherbis/times`
`golang.org/x/sync/semaphore`
`r00t2.io/goutils/bitmask`
)
// FsSearchCriteria contains filter criteria for SearchFsPaths* functions.
type FsSearchCriteria struct {
// Root indicates the root to search.
Root string `json:"root" toml:"RootPath" yaml:"Root Path" xml:"root,attr" validate:"dir"`
// NoMatch, if true, will not return matches. If NoMatch and NoMismatch are both true, no results will be returned.
NoMatch bool `json:"no_match" toml:"NoMatch" yaml:"No Matches" xml:"noMatch,attr"`
// NoMismatch, if true, will not return mismatches. If NoMatch and NoMismatch are both true, no results will be returned.
NoMismatch bool `json:"no_miss" toml:"NoMismatch" yaml:"No Mismatches" xml:"noMiss,attr"`
/*
TargetType defines what types of filesystem objects should be matched.
It can consist of one or more (io/)fs.FileMode types OR'd together
(ensure they are part of (io/)fs.ModeType).
(You can use 0 to match regular files explicitly, and/or NoFiles = true to exclude them.)
*/
TargetType fs.FileMode `json:"type_tgt" toml:"TargetType" yaml:"Target Type" xml:"typeTgt,attr"`
// NoFiles excludes files from TargetType-matching (as there isn't a way to explicitly exclude files otherwise if a non-zero mode is given).
NoFiles bool `json:"no_file" toml:"ExcludeFiles" yaml:"Exclude Files" xml:"noFile,attr"`
// FollowSymlinks, if true and a path being tested is a symlink, will use metadata (age, etc.) of the symlink itself rather than the link target.
FollowSymlinks bool `json:"follow_sym" toml:"FollowSymlinks" yaml:"Follow Symlinks" xml:"followSym,attr"`
// BasePtrn, if specified, will apply to the *base name (that is, quux.txt rather than /foo/bar/baz/quux.txt). See also PathPtrn.
BasePtrn *regexp.Regexp `json:"ptrn_base,omitempty" toml:"BaseNamePattern,omitempty" yaml:"Base Name Pattern,omitempty" xml:"ptrnBase,attr,omitempty"`
// PathPtrn, if specified, will apply to the *full path* (e.g. /foo/bar/baz/quux.txt, not just quux.txt). See also BasePtrn.
PathPtrn *regexp.Regexp `json:"ptrn_path,omitempty" toml:"PathPattern,omitempty" yaml:"Path Pattern,omitempty" xml:"ptrnPath,attr,omitempty"`
/*
Age, if specified, indicates the comparison of Now againt the AgeType of filesystem objects.
Use OlderThan to indicate if it should be older or newer.
*/
Age *time.Duration `json:"age,omitempty" toml:"Age,omitempty" yaml:"Age,omitempty" xml:"age,attr,omitempty"`
/*
AgeType can be one (or more, OR'd together) of the Time* constants in this package (TimeAny, TimeAccessed, TimeCreated,
TimeChanged, TimeModified) to indicate what timestamp(s) to use for comparing Age.
The zero-value is TimeAny.
The first matching timestamp will pass all time comparisons.
Be mindful of timestamp type support/limitations per OS/filesystem of Root.
Completely unused if Age is nil.
*/
AgeType pathTimeType `json:"type_age" toml:"AgeType" yaml:"Age Type" xml:"typeAge,attr"`
/*
OlderThan, if true (and Age is not nil), indicates that matching filesystem objects should have their
AgeType older than Now. If false, their AgeType should be *newer* than Now.
Completely unused if Age is nil.
*/
OlderThan bool `json:"older" toml:"OlderThan" yaml:"Older Than" xml:"older,attr"`
/*
Now expresses a time to compare to Age via AgeType and OlderThan.
Note that it may be any valid time, not necessarily "now".
If Age is specified but Now is nil, it will be populated with time.Now() when the search is invoked.
Completely unused if Age is nil.
*/
Now *time.Time `json:"now,omitempty" toml:"Now,omitempty" yaml:"Now,omitempty" xml:"now,attr,omitempty"`
}
// FsSearchCriteriaAsync extends FsSearchCriteria for use in an asynchronous (goroutine) manner.
type FsSearchCriteriaAsync struct {
FsSearchCriteria
/*
WG should be a non-nil pointer to a sync.WaitGroup.
This is used to manage searching completion to the caller.
.Done() will be called once within the search function, but no .Add() will be called;
.Add() should be done by the caller beforehand.
*/
WG *sync.WaitGroup
// ResChan must be a non-nil channel for (positive) match results to be sent to.
ResChan chan *FsSearchResult
// MismatchChan, if not nil, will have negative matches/"misses" sent to it.
MismatchChan chan *FsSearchResult
/*
ErrChan should be a non-nil error channel for any unexpected errors encountered.
If nil, a panic will be raised.
*/
ErrChan chan error
/*
Semaphore is completely optional, but if non-nil
it will be used to limit concurrent filesystem
object processing.
It is generally a Very Good Idea(TM) to use this,
as the default is to dispatch all processing concurrently.
This can lead to some heavy I/O and CPU wait.
(See https://pkg.go.dev/golang.org/x/sync/semaphore for details.)
*/
Semaphore *semaphore.Weighted
/*
SemaphoreCtx is the context.Context to use for Semaphore.
If nil (but Sempaphore is not), one will be created locally/internally.
*/
SemaphoreCtx context.Context
}
// FsSearchResult contains a match/miss result for FsSearchCriteria and FsSearchCriteriaAsync.
type FsSearchResult struct {
/*
Path is the path to the object on the filesystem.
It may or may not exist at the time of return,
but will not be an empty string.
*/
Path string `json:"path" toml:"Path" yaml:"Path" xml:"path,attr"`
// DirEntry is the fs.DirEntry for the Path; note that .Name() is the base name only. TODO: serialization?
DirEntry fs.DirEntry `json:"-" toml:"-" yaml:"-" xml:"-"`
// FileInfo is the fs.FileInfo for the Path; note that .Name() is the base name only. TODO: serialization?
FileInfo fs.FileInfo `json:"-" toml:"-" yaml:"-" xml:"-"`
// Criteria is the evaluated criteria specified that this FsSearchResult matched.
Criteria *FsSearchCriteria `json:"criteria" toml:"Criteria" yaml:"Criteria" xml:"criteria"`
// Times holds the mtime, ctime, etc. of the filesystem object (where supported). TODO: serialization?
Times times.Timespec `json:"-" toml:"-" yaml:"-" xml:"-"`
// MissReason contains the reason the result is a miss (MissNoMiss if a match); see the Miss* constants.
MissReason missReason `json:"miss_reason" toml:"MissReason" yaml:"Miss Reason" xml:"miss,attr"`
}
type missReason string
type pathMode bitmask.MaskBit
type pathTimeType bitmask.MaskBit

53
types_linux.go Normal file
View File

@@ -0,0 +1,53 @@
package sysutils
import (
`golang.org/x/sys/unix`
)
/*
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.
*/
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
// uidsChecked is true if the RUID, EUID, and SUID have been populated. (They will be 0 if unset OR if root.)
uidsChecked bool
// gidsChecked is true if the RGID, EGID, and SGID have been populated. (They will be 0 if unset OR if root.)
gidsChecked bool
// sudoChecked is true if the SudoEnvVars is set.
sudoChecked bool
// ppidUidChecked is true if the PPIDUidMatch is set.
ppidUidChecked bool
// ppidGidChecked is true if the PPIDGidMatch is set.
ppidGidChecked bool
// stat holds the stat information for the parent PID.
stat *unix.Stat_t
}