ADDED:
* paths.SearchFsPaths, which lets a user provide a fairly flexible
  function for searching files/directories/etc.
This commit is contained in:
brent saner 2024-11-12 06:32:04 -05:00
parent 70a88ca8b4
commit 903dd00c81
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
6 changed files with 233 additions and 6 deletions

1
go.mod
View File

@ -4,6 +4,7 @@ go 1.23.2

require (
github.com/davecgh/go-spew v1.1.1
github.com/djherbis/times v1.6.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
golang.org/x/sys v0.26.0
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8

9
go.sum
View File

@ -1,20 +1,17 @@
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/johnnybubonic/go-chattr v0.0.0-20240126141003-459f46177b13 h1:tgEbuE4bNVjaCWWIB1u9lDzGqH/ZdBTg33+4vNW2rjg=
github.com/johnnybubonic/go-chattr v0.0.0-20240126141003-459f46177b13/go.mod h1:yQc6VPJfpDDC1g+W2t47+yYmzBNioax/GLiyJ25/IOs=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 h1:FW42yWB1sGClqswyHIB68wo0+oPrav1IuQ+Tdy8Qp8E=
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8/go.mod h1:44w9OfBSQ9l3o59rc2w3AnABtE44bmtNnRMNC7z+oKE=
r00t2.io/goutils v1.7.0 h1:iQluWlkOyBwOKaK94D5QSnSMYpGKtMb/5WjefmdfHgI=
r00t2.io/goutils v1.7.0/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
r00t2.io/goutils v1.7.1 h1:Yzl9rxX1sR9WT0FcjK60qqOgBoFBOGHYKZVtReVLoQc=
r00t2.io/goutils v1.7.1/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=
r00t2.io/sysutils v1.7.0 h1:zk5IbcbZvq11FoXI/fLvcgyq36lBhPDY6fvC9CunfWE=
r00t2.io/sysutils v1.7.0/go.mod h1:Sk/7riJp9fteeW9STkdQ/k22huL1J6r05n6wLh5byHY=

31
paths/consts.go Normal file
View File

@ -0,0 +1,31 @@
package paths

import (
`io/fs`
)

// Mostly just for reference.
const (
// ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular
modeDir pathMode = pathMode(fs.ModeDir)
modeSymlink pathMode = pathMode(fs.ModeSymlink)
modePipe pathMode = pathMode(fs.ModeNamedPipe)
modeSocket pathMode = pathMode(fs.ModeSocket)
modeDev pathMode = pathMode(fs.ModeDevice)
modeCharDev pathMode = pathMode(fs.ModeCharDevice)
modeIrregular pathMode = pathMode(fs.ModeIrregular)
modeAny pathMode = modeDir | modeSymlink | modePipe | modeSocket | modeDev | modeCharDev | modeIrregular
)

// Times
const TimeAny pathTimeType = 0
const (
// TimeAccessed == atime
TimeAccessed pathTimeType = 1 << iota
// TimeCreated == "birth" time (*NOT* ctime! See TimeChanged)
TimeCreated
// TimeChanged == ctime
TimeChanged
// TimeModified == mtime
TimeModified
)

View File

@ -25,8 +25,15 @@ import (
"os"
"os/user"
"path/filepath"
`regexp`
`slices`
"strings"
`time`

// "syscall"

`github.com/djherbis/times`
`r00t2.io/goutils/bitmask`
)

/*
@ -266,3 +273,172 @@ func RealPathExistsStat(path *string) (exists bool, stat os.FileInfo, err error)

return
}

/*
SearchPaths gets a file/directory path list based on the provided criteria.

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 as a shortcut to match anything/all paths.
You can also use (io/)fs.ModeType itself to match anything/all paths.)

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 ptrn 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 would 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.

olderThan (as mentioned above) will find paths *older* than age if true, otherwise *newer*.
*/
func SearchFsPaths(
root string,
targetType fs.FileMode,
basePtrn, pathPtrn *regexp.Regexp,
age *time.Duration, ageType pathTimeType, olderThan bool,
) (foundPaths []string, err error) {

var now time.Time = time.Now()

if err = RealPath(&root); err != nil {
return
}

if err = filepath.WalkDir(
root,
func(path string, d fs.DirEntry, inErr error) (outErr error) {

var typeMode fs.FileMode
var fi fs.FileInfo
var tspec times.Timespec
var typeFilter *bitmask.MaskBit = bitmask.NewMaskBitExplicit(uint(targetType))

if inErr != nil {
outErr = inErr
return
}

// 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, outErr = times.Stat(path); outErr != nil {
return
}
if !filterTimes(tspec, age, &ageType, olderThan, &now) {
return
}
}

// fs object type (file, dir, etc.)
if targetType != 0 && uint(targetType) != uint(modeAny) {
if fi, outErr = d.Info(); outErr != nil {
return
}
typeMode = fi.Mode().Type()
if !typeFilter.HasFlag(bitmask.MaskBit(typeMode)) {
return
}
}

// All filters passed at this point.
foundPaths = append(foundPaths, path)

return
},
); err != nil {
return
}

// And sort them.
slices.Sort(foundPaths)

return
}

/*
filterTimes checks a times.Timespec of a file using:
* an age specified by the caller
* an ageType bitmask for types of times to compare
* an olderThan bool (if false, the file must be younger than)
* an optional "now" timestamp for the age derivation.
*/
func filterTimes(tspec times.Timespec, age *time.Duration, ageType *pathTimeType, olderThan bool, now *time.Time) (include bool) {

var curAge time.Duration
var mask *bitmask.MaskBit
var tfunc func(t *time.Duration) (match bool) = func(t *time.Duration) (match bool) {
if olderThan {
match = *t > *age
} else {
match = *t < *age
}
return
}

if tspec == nil || age == nil || ageType == nil {
return
}

mask = ageType.Mask()

if now == nil {
now = new(time.Time)
*now = time.Now()
}

// ATIME
if mask.HasFlag(bitmask.MaskBit(TimeAny)) || mask.HasFlag(bitmask.MaskBit(TimeAccessed)) {
curAge = now.Sub(tspec.AccessTime())
if include = tfunc(&curAge); include {
return
}
}
// MTIME
if mask.HasFlag(bitmask.MaskBit(TimeAny)) || mask.HasFlag(bitmask.MaskBit(TimeModified)) {
curAge = now.Sub(tspec.ModTime())
if include = tfunc(&curAge); include {
return
}
}
// CTIME (if supported)
if tspec.HasChangeTime() && (mask.HasFlag(bitmask.MaskBit(TimeAny)) || mask.HasFlag(bitmask.MaskBit(TimeChanged))) {
curAge = now.Sub(tspec.ChangeTime())
if include = tfunc(&curAge); include {
return
}
}
// BTIME (if supported)
if tspec.HasBirthTime() && (mask.HasFlag(bitmask.MaskBit(TimeAny)) || mask.HasFlag(bitmask.MaskBit(TimeCreated))) {
curAge = now.Sub(tspec.BirthTime())
if include = tfunc(&curAge); include {
return
}
}

return
}

View File

@ -0,0 +1,13 @@
package paths

import (
`r00t2.io/goutils/bitmask`
)

// Mask returns a bitmask.MaskBit from a pathTimeType.
func (p *pathTimeType) Mask() (mask *bitmask.MaskBit) {

mask = bitmask.NewMaskBitExplicit(uint(*p))

return
}

9
paths/types.go Normal file
View File

@ -0,0 +1,9 @@
package paths

import (
`r00t2.io/goutils/bitmask`
)

type pathMode bitmask.MaskBit

type pathTimeType bitmask.MaskBit