12 Commits

Author SHA1 Message Date
brent saner
903dd00c81 v1.10.0
ADDED:
* paths.SearchFsPaths, which lets a user provide a fairly flexible
  function for searching files/directories/etc.
2024-11-12 06:32:04 -05:00
brent saner
70a88ca8b4 v1.9.0
IMPROVED:
* Removed *BROKEN* dep. lrn2fixurshitk
2024-11-07 04:15:45 -05:00
brent saner
9dbc3a00fe v1.8.2
Fix bad tag/version/go.mod
2024-10-29 12:19:54 -04:00
brent saner
e9b7c5539a v1.8.1
ADDED:
* A way to actually use Auger externally. lel.
2024-10-29 12:17:05 -04:00
brent saner
236165bec8 v1.8.0
API CHANGES:
* Technically should be a v2.x, but cryptparse has been moved to its own
  module: r00t2.io/cryptparse
2024-10-17 15:34:26 -04:00
brent saner
4cb0403e08 v1.7.1
FIX:
* cryptparse/ParseCipher* funcs were not properly building cipher name
  comparison map
2024-09-09 13:42:56 -04:00
brent saner
0318a9759b v1.7.0
ADDED:
* cryptparse/ParseTlsCipherStrict()
* cryptparse/ParseTlsCipherSuiteStrict()
2024-09-09 13:06:07 -04:00
brent saner
1a93d5d9f3 v1.6.0
ADDED:
- env/DefEnv(), env/DefEnvBlank()
2024-09-06 12:50:23 -04:00
brent saner
5dc944cf21 v1.5.1
FIXES:
* cryptparse.TlsUri.ToConn and cryptparse.TlsUri.ToTlsConn would
  previously use incorrect "host" parameter during dial for UDS/IPC
  sockets.
2024-08-12 15:59:38 -04:00
brent saner
77a85a4f84 v1.5.0
ADDED:
* cryptpartse.TlsUri now has methods to returned dialed net.Conn and
  tls.Conn, or can use WithConn to add TLS to an already-dialed net.Conn.
2024-08-08 19:40:11 -04:00
brent saner
43d1ddfeb8 v1.4.1
FIX:
* missing dep for windows env
2024-06-27 16:36:45 -04:00
brent saner
db20c70d86 v1.4.0
ADDED:
* cryptparse, which makes managing certs/keys MUCH much easier (and
  better) than the stdlib utilities.
2024-06-21 17:18:19 -04:00
27 changed files with 770 additions and 190 deletions

3
.gitignore vendored
View File

@@ -29,6 +29,9 @@
# Test binary, built with `go test -c` # Test binary, built with `go test -c`
*.test *.test
# Test file
fsutils/testfile
# Output of the go coverage tool, specifically when used with LiteIDE # Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out

1
auger/TODO Normal file
View File

@@ -0,0 +1 @@
This module is still under work.

View File

@@ -7,3 +7,35 @@ const (
augInclTfm string = "incl" // The transformer keyword for Augeas includes. augInclTfm string = "incl" // The transformer keyword for Augeas includes.
augAppendSuffix string = "[last()+1]" augAppendSuffix string = "[last()+1]"
) )
var (
dstPtrTrue bool = true
dstPtrFalse bool = false
)
var (
// PtrTrue and PtrFalse are convenience references for constructing an AugFlags if needed. It is recommended you do not change these values if you do not like being confused.
PtrTrue *bool = &dstPtrTrue
PtrFalse *bool = &dstPtrFalse
)
/*
IncludeOptNone is the default include recursion option for Aug.RecursiveInclude.
* No special behavior is defined
* All include directives are assumed to refer:
* Explicitly/exclusively to file paths
* That must exist
*/
const IncludeOptNone includeOpt = 0
const (
// IncludeOptNoExist specifies that inclusions are allowed to not exist, otherwise an error will be raised while attempting to parse them.
IncludeOptNoExist includeOpt = 1 << iota
// IncludeOptGlobbing indicates that the inclusion system supports globbing (as supported by (github.com/gobwas/glob).Match).
IncludeOptGlobbing
// IncludeOptRegex indicates that the inclusion system supports matching by regex (as supported by regexp).
IncludeOptRegex
// IncludeOptDirs indicates that the inclusion system supports matching by directory.
IncludeOptDirs
// IncludeOptDirsRecursive indicates that the inclusion system also recurses into subdirectories of matched directories. Only used if IncludeOptDirs is also set.
IncludeOptDirsRecursive
)

View File

@@ -4,15 +4,55 @@ import (
`io/fs` `io/fs`
`os` `os`
`strings` `strings`
`honnef.co/go/augeas`
`r00t2.io/goutils/bitmask`
) )
/* /*
AugpathToFspath returns the filesystem path from an Augeas path. NewAuger returns an auger.Aug.
See:
https://pkg.go.dev/honnef.co/go/augeas#readme-examples
https://pkg.go.dev/honnef.co/go/augeas#New
for the `root` and `loadPath` parameters
(and, by extension, the `flags` paraemter; note that the `flags`
is an auger.AugFlags, not an augeas.Flag!).
`flags` may be nil.
*/
func NewAuger(root, loadPath string, flags *AugFlags) (aug *Aug, err error) {
aug = new(Aug)
if aug.aug, err = augeas.New(root, loadPath, flags.Eval()); err != nil {
return
}
return
}
// NewAugerFromAugeas returns a wrapped auger.Aug from a (honnef.co/go/augeas).Augeas.
func NewAugerFromAugeas(orig augeas.Augeas) (aug *Aug) {
aug = new(Aug)
aug.aug = orig
return
}
/*
AugpathToFspath returns the filesystem path (i.e. an existing file) from an Augeas path.
It is *required* and expected that the Augeas standard /files prefix be removed first; It is *required* and expected that the Augeas standard /files prefix be removed first;
if not, it is assumed to be part of the filesystem path. if not, it is assumed to be part of the filesystem path.
If a valid path cannot be determined, fsPath will be empty. If a valid path cannot be determined, fsPath will be empty.
To be clear, a file must exist for fsPath to not be empty;
the way AugpathToFsPath works is it recurses bottom-up a
given path and checks for the existence of a file,
continuing upwards if not found.
*/ */
func AugpathToFspath(augPath string) (fsPath string, err error) { func AugpathToFspath(augPath string) (fsPath string, err error) {
@@ -61,3 +101,11 @@ func dedupePaths(new, existing []string) (missing []string) {
return return
} }
// getInclPaths applies path options to inclusions.
func getInclPaths(pathSpec string, inclFlags *bitmask.MaskBit) (fpaths []string, err error) {
// TODO
return
}

View File

@@ -12,6 +12,7 @@ import (
`github.com/davecgh/go-spew/spew` `github.com/davecgh/go-spew/spew`
`github.com/google/shlex` `github.com/google/shlex`
`honnef.co/go/augeas` `honnef.co/go/augeas`
`r00t2.io/goutils/bitmask`
`r00t2.io/sysutils/paths` `r00t2.io/sysutils/paths`
) )
@@ -146,10 +147,21 @@ breakCmd:
An error will be returned if augLens is a nonexistent or not-loaded Augeas lens module. An error will be returned if augLens is a nonexistent or not-loaded Augeas lens module.
Depending on how many files there are and whether globs vs. explicit filepaths are included, this may take a while. Depending on how many files there are and whether globs vs. explicit filepaths are included, this may take a while.
*/
func (a *Aug) RecursiveInclude(augLens, includeDirective, fsRoot string) (err error) {
if err = a.addIncl(includeDirective, augLens, fsRoot, nil); err != nil { optFlags may be nil, multiple includeOpt (see the IncludeOpt* constants) as variadic parameters/expanded slice,
bitwise-OR'd together, or multiple non-OR'd and OR'd together (all will be combined to a single value).
*/
func (a *Aug) RecursiveInclude(augLens, includeDirective, fsRoot string, optFlags ...includeOpt) (err error) {
var flags *bitmask.MaskBit = bitmask.NewMaskBit()
if optFlags != nil && len(optFlags) > 0 {
for _, f := range optFlags {
flags.AddFlag(f.toMb())
}
}
if err = a.addIncl(includeDirective, augLens, fsRoot, nil, flags); err != nil {
return return
} }
@@ -164,14 +176,16 @@ func (a *Aug) RecursiveInclude(augLens, includeDirective, fsRoot string) (err er
newInclPaths are new filesystem paths/Augeas-compatible glob patterns to load into the filetree and recurse into. newInclPaths are new filesystem paths/Augeas-compatible glob patterns to load into the filetree and recurse into.
They may be nil, especially if the first run. They may be nil, especially if the first run.
*/ */
func (a *Aug) addIncl(includeDirective, augLens string, fsRoot string, newInclPaths []string) (err error) { func (a *Aug) addIncl(includeDirective, augLens string, fsRoot string, newInclPaths []string, inclFlags *bitmask.MaskBit) (err error) {
var matches []string // Passed around set of Augeas matches. var matches []string // Passed around set of Augeas matches.
var exists bool // Used to indicate if the include path exists.
var includes []string // Filepath(s)/glob(s) from fetching includeDirective in lensInclPath. These are internal to the application but are recursed. var includes []string // Filepath(s)/glob(s) from fetching includeDirective in lensInclPath. These are internal to the application but are recursed.
var lensInclPath string // The path of the included paths in the tree. These are internal to Augeas, not the application. var lensInclPath string // The path of the included paths in the tree. These are internal to Augeas, not the application.
var appendPath string // The path for new Augeas includes. var appendPath string // The path for new Augeas includes.
var match []string // A placeholder for iterating when populating includes. var match []string // A placeholder for iterating when populating includes.
var fpath string // A placeholder for finding the path of a conf file that contains an includeDirective. var fpath string // A placeholder for finding the path of a conf file that contains an includeDirective.
var normalizedIncludes []string // A temporary slice to hold normalization operations and other dynamic building.
var lensPath string = fmt.Sprintf(augLensTpl, augLens) // The path of the lens (augLens) itself. var lensPath string = fmt.Sprintf(augLensTpl, augLens) // The path of the lens (augLens) itself.
var augErr *augeas.Error = new(augeas.Error) // We use this to skip "nonexistent" lens. var augErr *augeas.Error = new(augeas.Error) // We use this to skip "nonexistent" lens.
@@ -193,7 +207,7 @@ func (a *Aug) addIncl(includeDirective, augLens string, fsRoot string, newInclPa
// First canonize paths. // First canonize paths.
if newInclPaths != nil && len(newInclPaths) > 0 { if newInclPaths != nil && len(newInclPaths) > 0 {
// Existing includes. We don't return on an empty lensInclPath because // Existing includes. We don't return on an empty lensInclPath.
if matches, err = a.aug.Match(lensInclPath); err != nil { if matches, err = a.aug.Match(lensInclPath); err != nil {
if errors.As(err, augErr) && augErr.Code == augeas.NoMatch { if errors.As(err, augErr) && augErr.Code == augeas.NoMatch {
err = nil err = nil
@@ -221,6 +235,17 @@ func (a *Aug) addIncl(includeDirective, augLens string, fsRoot string, newInclPa
// We don't want to bother adding multiple incl's for the same path(s); it can negatively affect Augeas loads. // We don't want to bother adding multiple incl's for the same path(s); it can negatively affect Augeas loads.
newInclPaths = dedupePaths(newInclPaths, matches) newInclPaths = dedupePaths(newInclPaths, matches)
// And then apply things like recursion, globbing, etc.
normalizedIncludes = make([]string, 0, len(newInclPaths))
if inclFlags.HasFlag(IncludeOptGlobbing.toMb()) {
// TODO
/*
if strings.Contains(newInclPaths[idx], "*") {
}
*/
}
// Add the new path(s) as Augeas include entries. // Add the new path(s) as Augeas include entries.
if newInclPaths != nil { if newInclPaths != nil {
for _, fsPath := range newInclPaths { for _, fsPath := range newInclPaths {
@@ -285,10 +310,13 @@ func (a *Aug) addIncl(includeDirective, augLens string, fsRoot string, newInclPa
} }
if matches != nil && len(matches) != 0 { if matches != nil && len(matches) != 0 {
if err = a.addIncl(includeDirective, augLens, fsRoot, matches); err != nil { if err = a.addIncl(includeDirective, augLens, fsRoot, matches, inclFlags); err != nil {
return return
} }
} }
// TODO
_, _ = exists, normalizedIncludes
return return
} }

View File

@@ -7,6 +7,10 @@ import (
// Eval returns an evaluated set of flags. // Eval returns an evaluated set of flags.
func (a *AugFlags) Eval() (augFlags augeas.Flag) { func (a *AugFlags) Eval() (augFlags augeas.Flag) {
if a == nil {
return
}
augFlags = augeas.None augFlags = augeas.None
if a.Backup != nil && *a.Backup { if a.Backup != nil && *a.Backup {

13
auger/funcs_includeopt.go Normal file
View File

@@ -0,0 +1,13 @@
package auger
import (
`r00t2.io/goutils/bitmask`
)
// toMb returns a bitmask.MaskBit of this includeOpt.
func (i includeOpt) toMb() (mb bitmask.MaskBit) {
mb = bitmask.MaskBit(i)
return
}

39
auger/funcs_test.go Normal file
View File

@@ -0,0 +1,39 @@
package auger
import (
"testing"
`honnef.co/go/augeas`
)
func TestNewAuger(t *testing.T) {
var aug *Aug
var augUnder augeas.Augeas
var err error
if aug, err = NewAuger("/", "", nil); err != nil {
t.Fatal(err)
}
augUnder = aug.aug
aug = NewAugerFromAugeas(augUnder)
_ = aug
}
func TestRecursiveInclude(t *testing.T) {
var aug *Aug
var err error
if aug, err = NewAuger("/", "", &AugFlags{DryRun: PtrTrue}); err != nil {
t.Fatal(err)
}
// This requires Nginx to be installed and with a particularly complex nested include system.
if err = aug.RecursiveInclude("Nginx", "include", "/etc/nginx"); err != nil {
t.Fatal(err)
}
}

View File

@@ -2,8 +2,11 @@ package auger
import ( import (
`honnef.co/go/augeas` `honnef.co/go/augeas`
`r00t2.io/goutils/bitmask`
) )
type includeOpt bitmask.MaskBit
// Aug is a wrapper around (honnef.co/go/)augeas.Augeas. Remember to call Aug.Close(). // Aug is a wrapper around (honnef.co/go/)augeas.Augeas. Remember to call Aug.Close().
type Aug struct { type Aug struct {
aug augeas.Augeas aug augeas.Augeas

6
cryptparse/doc.go Normal file
View File

@@ -0,0 +1,6 @@
/*
CRYPTPARSE HAS MOVED.
It is now its own module: r00t2.io/cryptparse
*/
package cryptparse

View File

@@ -17,6 +17,34 @@ import (
`r00t2.io/sysutils/paths` `r00t2.io/sysutils/paths`
) )
/*
DefEnv operates like Python's .get() method on dicts (maps);
if the environment variable specified by key does not exist/is not specified,
then the value specified by fallback will be returned instead
otherwise key's value is returned.
*/
func DefEnv(key, fallback string) (value string) {
var exists bool
if value, exists = os.LookupEnv(key); !exists {
value = fallback
}
return
}
// DefEnvBlank is like DefEnv but will ADDITIONALLY/ALSO apply fallback if key is *defined/exists but is an empty string*.
func DefEnvBlank(key, fallback string) (value string) {
value = DefEnv(key, fallback)
if value == "" {
value = fallback
}
return
}
// GetEnvMap returns a map of all environment variables. All values are strings. // GetEnvMap returns a map of all environment variables. All values are strings.
func GetEnvMap() (envVars map[string]string) { func GetEnvMap() (envVars map[string]string) {
@@ -131,7 +159,7 @@ func GetPidEnvMap(pid uint32) (envMap map[string]string, err error) {
var procPath string var procPath string
var exists bool var exists bool
envMap = make(map[string]string, 0) envMap = make(map[string]string)
procPath = fmt.Sprintf("/proc/%v/environ", pid) procPath = fmt.Sprintf("/proc/%v/environ", pid)

View File

@@ -13,7 +13,7 @@ func envListToMap(envs []string) (envMap map[string]string) {
var kv []string var kv []string
var k, v string var k, v string
envMap = make(map[string]string, 0) envMap = make(map[string]string)
for _, ev := range envs { for _, ev := range envs {
kv = strings.SplitN(ev, "=", 2) kv = strings.SplitN(ev, "=", 2)

View File

@@ -3,6 +3,8 @@
package envs package envs
import ( import (
"os"
"golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/registry"
) )

View File

@@ -1,101 +0,0 @@
package fsutils
import (
`github.com/g0rbe/go-chattr`
)
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/fs.h
const (
SecureDelete uint32 = chattr.FS_SECRM_FL // Secure deletion
UnDelete = chattr.FS_UNRM_FL // Undelete
CompressFile = chattr.FS_COMPR_FL // Compress file
SyncUpdatechattr = chattr.FS_SYNC_FL // Synchronous updates
Immutable = chattr.FS_IMMUTABLE_FL // Immutable file
AppendOnly = chattr.FS_APPEND_FL // Writes to file may only append
NoDumpFile = chattr.FS_NODUMP_FL // Do not dump file
NoUpdateAtime = chattr.FS_NOATIME_FL // Do not update atime
IsDirty = chattr.FS_DIRTY_FL // Nobody knows what this does, lol.
CompressedClusters = chattr.FS_COMPRBLK_FL // One or more compressed clusters
NoCompress = chattr.FS_NOCOMP_FL // Don't compress
EncFile = chattr.FS_ENCRYPT_FL // Encrypted file
BtreeFmt = chattr.FS_BTREE_FL // Btree format dir
HashIdxDir = chattr.FS_INDEX_FL // Hash-indexed directory
AfsDir = chattr.FS_IMAGIC_FL // AFS directory
ReservedExt3 = chattr.FS_JOURNAL_DATA_FL // Reserved for ext3
NoMergeTail = chattr.FS_NOTAIL_FL // File tail should not be merged
DirSync = chattr.FS_DIRSYNC_FL // dirsync behaviour (directories only)
DirTop = chattr.FS_TOPDIR_FL // Top of directory hierarchies
ReservedExt4a = chattr.FS_HUGE_FILE_FL // Reserved for ext4
Extents = chattr.FS_EXTENT_FL // Extents
LargeEaInode = chattr.FS_EA_INODE_FL // Inode used for large EA
ReservedExt4b = chattr.FS_EOFBLOCKS_FL // Reserved for ext4
NoCOWFile = chattr.FS_NOCOW_FL // Do not cow file
ReservedExt4c = chattr.FS_INLINE_DATA_FL // Reserved for ext4
UseParentProjId = chattr.FS_PROJINHERIT_FL // Create with parents projid
ReservedExt2 = chattr.FS_RESERVED_FL // Reserved for ext2 lib
)
var (
// AttrNameValueMap contains a mapping of attribute names (as designated by this package) to their flag values.
AttrNameValueMap map[string]uint32 = map[string]uint32{
"SecureDelete": SecureDelete,
"UnDelete": UnDelete,
"CompressFile": CompressFile,
"SyncUpdatechattr": SyncUpdatechattr,
"Immutable": Immutable,
"AppendOnly": AppendOnly,
"NoDumpFile": NoDumpFile,
"NoUpdateAtime": NoUpdateAtime,
"IsDirty": IsDirty,
"CompressedClusters": CompressedClusters,
"NoCompress": NoCompress,
"EncFile": EncFile,
"BtreeFmt": BtreeFmt,
"HashIdxDir": HashIdxDir,
"AfsDir": AfsDir,
"ReservedExt3": ReservedExt3,
"NoMergeTail": NoMergeTail,
"DirSync": DirSync,
"DirTop": DirTop,
"ReservedExt4a": ReservedExt4a,
"Extents": Extents,
"LargeEaInode": LargeEaInode,
"ReservedExt4b": ReservedExt4b,
"NoCOWFile": NoCOWFile,
"ReservedExt4c": ReservedExt4c,
"UseParentProjId": UseParentProjId,
"ReservedExt2": ReservedExt2,
}
/*
AttrValueNameMap contains a mapping of attribute flags to their names (as designated by this package).
Note the oddball here, BtreeFmt and HashIdxDir are actually the same value, so be forewarned.
*/
AttrValueNameMap map[uint32]string = map[uint32]string{
SecureDelete: "SecureDelete",
UnDelete: "UnDelete",
CompressFile: "CompressFile",
SyncUpdatechattr: "SyncUpdatechattr",
Immutable: "Immutable",
AppendOnly: "AppendOnly",
NoDumpFile: "NoDumpFile",
NoUpdateAtime: "NoUpdateAtime",
IsDirty: "IsDirty",
CompressedClusters: "CompressedClusters",
NoCompress: "NoCompress",
EncFile: "EncFile",
BtreeFmt: "BtreeFmt|HashIdxDir", // Well THIS is silly and seems like an oversight. Both FS_BTREE_FL and FS_INDEX_FL have the same flag. Confirmed in kernel source.
AfsDir: "AfsDir",
ReservedExt3: "ReservedExt3",
NoMergeTail: "NoMergeTail",
DirSync: "DirSync",
DirTop: "DirTop",
ReservedExt4a: "ReservedExt4a",
Extents: "Extents",
LargeEaInode: "LargeEaInode",
ReservedExt4b: "ReservedExt4b",
NoCOWFile: "NoCOWFile",
ReservedExt4c: "ReservedExt4c",
UseParentProjId: "UseParentProjId",
ReservedExt2: "ReservedExt2",
}
)

126
fsutils/consts_linux.go Normal file
View File

@@ -0,0 +1,126 @@
//go:build linux
package fsutils
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/fs.h "Inode flags (FS_IOC_GETFLAGS / FS_IOC_SETFLAGS)"
const (
SecureDelete fsAttr = 1 << iota // Secure deletion
UnDelete // Undelete
CompressFile // Compress file
SyncUpdate // Synchronous updates
Immutable // Immutable file
AppendOnly // Writes to file may only append
NoDumpFile // Do not dump file
NoUpdateAtime // Do not update atime
IsDirty // Nobody knows what this does, lol.
CompressedClusters // One or more compressed clusters
NoCompress // Don't compress
EncFile // Encrypted file
BtreeFmt // Btree format dir
AfsDir // AFS directory
ReservedExt3 // Reserved for ext3
NoMergeTail // File tail should not be merged
DirSync // dirsync behaviour (directories only)
DirTop // Top of directory hierarchies
ReservedExt4a // Reserved for ext4
Extents // Extents
VerityProtected // Verity-protected inode
LargeEaInode // Inode used for large EA
ReservedExt4b // Reserved for ext4
NoCOWFile // Do not cow file
_ // (Unused)
DAX // Inode is DAX
_ // (Unused)
_ // (Unused)
ReservedExt4c // Reserved for ext4
UseParentProjId // Create with parents projid
CaseInsensitive // Folder is case-insensitive
ReservedExt2 // Reserved for ext2 lib
)
// These are the same value. For some reason.
const (
HashIdxDir fsAttr = BtreeFmt // Hash-indexed directory
)
var (
// AttrNameValueMap contains a mapping of attribute names (as designated by this package) to their flag values.
AttrNameValueMap map[string]fsAttr = map[string]fsAttr{
"SecureDelete": SecureDelete,
"UnDelete": UnDelete,
"CompressFile": CompressFile,
"SyncUpdate": SyncUpdate,
"Immutable": Immutable,
"AppendOnly": AppendOnly,
"NoDumpFile": NoDumpFile,
"NoUpdateAtime": NoUpdateAtime,
"IsDirty": IsDirty,
"CompressedClusters": CompressedClusters,
"NoCompress": NoCompress,
"EncFile": EncFile,
"BtreeFmt": BtreeFmt,
"HashIdxDir": HashIdxDir,
"AfsDir": AfsDir,
"ReservedExt3": ReservedExt3,
"NoMergeTail": NoMergeTail,
"DirSync": DirSync,
"DirTop": DirTop,
"ReservedExt4a": ReservedExt4a,
"Extents": Extents,
"VerityProtected": VerityProtected,
"LargeEaInode": LargeEaInode,
"ReservedExt4b": ReservedExt4b,
"NoCOWFile": NoCOWFile,
"DAX": DAX,
"ReservedExt4c": ReservedExt4c,
"UseParentProjId": UseParentProjId,
"CaseInsensitive": CaseInsensitive,
"ReservedExt2": ReservedExt2,
}
/*
AttrValueNameMap contains a mapping of attribute flags to their names (as designated by this package).
Note the oddball here, BtreeFmt and HashIdxDir are actually the same value, so their string value is unpredictable.
*/
AttrValueNameMap map[fsAttr]string = invertMap(AttrNameValueMap)
// KernelNameValueMap allows lookups using the symbol name as used in the Linux kernel source.
KernelNameValueMap map[string]fsAttr = map[string]fsAttr{
"FS_SECRM_FL": SecureDelete,
"FS_UNRM_FL": UnDelete,
"FS_COMPR_FL": CompressFile,
"FS_SYNC_FL": SyncUpdate,
"FS_IMMUTABLE_FL": Immutable,
"FS_APPEND_FL": AppendOnly,
"FS_NODUMP_FL": NoDumpFile,
"FS_NOATIME_FL": NoUpdateAtime,
"FS_DIRTY_FL": IsDirty,
"FS_COMPRBLK_FL": CompressedClusters,
"FS_NOCOMP_FL": NoCompress,
"FS_ENCRYPT_FL": EncFile,
"FS_BTREE_FL": BtreeFmt,
"FS_INDEX_FL": HashIdxDir,
"FS_IMAGIC_FL": AfsDir,
"FS_JOURNAL_DATA_FL": ReservedExt3,
"FS_NOTAIL_FL": NoMergeTail,
"FS_DIRSYNC_FL": DirSync,
"FS_TOPDIR_FL": DirTop,
"FS_HUGE_FILE_FL": ReservedExt4a,
"FS_EXTENT_FL": Extents,
"FS_VERITY_FL": VerityProtected,
"FS_EA_INODE_FL": LargeEaInode,
"FS_EOFBLOCKS_FL": ReservedExt4b,
"FS_NOCOW_FL": NoCOWFile,
"FS_DAX_FL": DAX,
"FS_INLINE_DATA_FL": ReservedExt4c,
"FS_PROJINHERIT_FL": UseParentProjId,
"FS_CASEFOLD_FL": CaseInsensitive,
"FS_RESERVED_FL": ReservedExt2,
}
/*
KernelValueNameMap contains a mapping of attribute flags to their kernel source symbol name.
Note the oddball here, BtreeFmt and HashIdxDir are actually the same value, so their string value is unpredictable.
*/
KernelValueNameMap map[fsAttr]string = invertMap(KernelNameValueMap)
)

7
fsutils/doc.go Normal file
View File

@@ -0,0 +1,7 @@
/*
fsutils is a collection of filesystem-related functions, types, etc.
Currently it's only a (fixed/actually working) reimplementation of github.com/g0rbe/go-chattr.
(Note to library maintainers, if someone reports an integer overflow and even tells you how to fix it, you should probably fix it.)
*/
package fsutils

View File

@@ -1,44 +0,0 @@
package fsutils
import (
`os`
`reflect`
`github.com/g0rbe/go-chattr`
`r00t2.io/sysutils/paths`
)
func GetAttrs(path string) (attrs *FsAttrs, err error) {
var f *os.File
var evalAttrs FsAttrs
var attrVal uint32
var reflectVal reflect.Value
var field reflect.Value
var myPath string = path
if err = paths.RealPath(&myPath); err != nil {
return
}
if f, err = os.Open(myPath); err != nil {
return
}
defer f.Close()
reflectVal = reflect.ValueOf(&evalAttrs).Elem()
if attrVal, err = chattr.GetAttrs(f); err != nil {
return
}
for attrNm, attrInt := range AttrNameValueMap {
field = reflectVal.FieldByName(attrNm)
field.SetBool((attrVal & attrInt) != 0)
}
attrs = new(FsAttrs)
*attrs = evalAttrs
return
}

View File

@@ -1,10 +1,11 @@
//go:build linux
package fsutils package fsutils
import ( import (
`os` `os`
`reflect` `reflect`
`github.com/g0rbe/go-chattr`
`r00t2.io/sysutils/paths` `r00t2.io/sysutils/paths`
) )
@@ -29,11 +30,11 @@ func (f *FsAttrs) Apply(path string) (err error) {
for attrNm, attrVal := range AttrNameValueMap { for attrNm, attrVal := range AttrNameValueMap {
fieldVal = reflectVal.FieldByName(attrNm) fieldVal = reflectVal.FieldByName(attrNm)
if fieldVal.Bool() { if fieldVal.Bool() {
if err = chattr.SetAttr(file, attrVal); err != nil { if err = setAttrs(file, attrVal); err != nil {
return return
} }
} else { } else {
if err = chattr.UnsetAttr(file, attrVal); err != nil { if err = unsetAttrs(file, attrVal); err != nil {
return return
} }
} }

149
fsutils/funcs_linux.go Normal file
View File

@@ -0,0 +1,149 @@
//go:build linux
package fsutils
import (
`os`
`reflect`
`unsafe`
`golang.org/x/sys/unix`
`r00t2.io/goutils/bitmask`
`r00t2.io/sysutils/paths`
)
func GetAttrs(path string) (attrs *FsAttrs, err error) {
var f *os.File
var evalAttrs FsAttrs
var attrVal fsAttr
var attrValBit bitmask.MaskBit
var reflectVal reflect.Value
var field reflect.Value
var myPath string = path
if err = paths.RealPath(&myPath); err != nil {
return
}
if f, err = os.Open(myPath); err != nil {
return
}
defer f.Close()
reflectVal = reflect.ValueOf(&evalAttrs).Elem()
if attrVal, err = getAttrs(f); err != nil {
return
}
attrValBit = bitmask.MaskBit(attrVal)
for attrNm, attrInt := range AttrNameValueMap {
field = reflectVal.FieldByName(attrNm)
field.SetBool(attrValBit.HasFlag(bitmask.MaskBit(attrInt)))
}
attrs = new(FsAttrs)
*attrs = evalAttrs
return
}
// getAttrs is the unexported low-level syscall to get attributes.
func getAttrs(f *os.File) (attrVal fsAttr, err error) {
var u uint
var curFlags int
// var errNo syscall.Errno
/*
if _, _, errNo = unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.FS_IOC_GETFLAGS, uintptr(unsafe.Pointer(&curFlags))); errNo != 0 {
err = os.NewSyscallError("ioctl: FS_IOC_GETFLAGS", errNo)
return
}
*/
if curFlags, err = unix.IoctlGetInt(int(f.Fd()), unix.FS_IOC_GETFLAGS); err != nil {
return
}
u = uint(curFlags)
attrVal = fsAttr(u)
return
}
// invertMap returns some handy consts remapping for easier lookups.
func invertMap(origMap map[string]fsAttr) (newMap map[fsAttr]string) {
if origMap == nil {
return
}
newMap = make(map[fsAttr]string)
for k, v := range origMap {
newMap[v] = k
}
return
}
// setAttrs is the unexported low-level syscall to set attributes. attrs may be OR'd.
func setAttrs(f *os.File, attrs fsAttr) (err error) {
var curAttrs fsAttr
var ab bitmask.MaskBit
var errNo unix.Errno
var val uint
if curAttrs, err = getAttrs(f); err != nil {
return
}
ab = bitmask.MaskBit(curAttrs)
if ab.HasFlag(bitmask.MaskBit(attrs)) {
return
}
ab.AddFlag(bitmask.MaskBit(attrs))
val = ab.Value()
/*
if err = unix.IoctlSetInt(int(f.Fd()), unix.FS_IOC_SETFLAGS, int(ab.Value())); err != nil {
return
}
*/
if _, _, errNo = unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.FS_IOC_SETFLAGS, uintptr(unsafe.Pointer(&val))); errNo != 0 {
err = os.NewSyscallError("ioctl: SYS_IOCTL", errNo)
return
}
return
}
// unsetAttrs is the unexported low-level syscall to remove attributes. attrs may be OR'd.
func unsetAttrs(f *os.File, attrs fsAttr) (err error) {
var curAttrs fsAttr
var ab bitmask.MaskBit
if curAttrs, err = getAttrs(f); err != nil {
return
}
ab = bitmask.MaskBit(curAttrs)
if !ab.HasFlag(bitmask.MaskBit(attrs)) {
return
}
ab.ClearFlag(bitmask.MaskBit(attrs))
/*
if err = unix.IoctlSetInt(int(f.Fd()), unix.FS_IOC_SETFLAGS, int(ab.Value())); err != nil {
return
}
*/
return
}

View File

@@ -1,3 +1,5 @@
//go:build linux
package fsutils package fsutils
import ( import (
@@ -7,12 +9,13 @@ import (
`os/user` `os/user`
`testing` `testing`
`github.com/davecgh/go-spew/spew`
`r00t2.io/sysutils/paths` `r00t2.io/sysutils/paths`
) )
var ( var (
testFilename string = "testfile" testFilename string = "testfile"
testErrBadUser error = errors.New("test must be run as root, on Linux") testErrBadUser error = errors.New("test must be run as root")
) )
func testChkUser() (err error) { func testChkUser() (err error) {
@@ -36,12 +39,18 @@ func TestSetAttrs(t *testing.T) {
if attrs, err = GetAttrs(testFilename); err != nil { if attrs, err = GetAttrs(testFilename); err != nil {
t.Fatalf("Failed to get attrs for %v: %v", testFilename, err) t.Fatalf("Failed to get attrs for %v: %v", testFilename, err)
} }
t.Logf("Attrs for %v:\n%#v", testFilename, attrs) t.Logf("Attrs for %v (before):\n%s", testFilename, spew.Sdump(attrs))
attrs.CompressFile = true attrs.CompressFile = true
attrs.SyncUpdate = true
attrs.SecureDelete = true
if err = attrs.Apply(testFilename); err != nil { if err = attrs.Apply(testFilename); err != nil {
t.Fatalf("Failed to apply attrs to %v: %v", testFilename, err) t.Fatalf("Failed to apply attrs to %v: %v", testFilename, err)
} }
t.Logf("Applied new attrs to %v:\n%#v", testFilename, attrs) t.Logf("Applied new attrs to %v:\n%#v", testFilename, attrs)
if attrs, err = GetAttrs(testFilename); err != nil {
t.Fatalf("Failed to get attrs for %v: %v", testFilename, err)
}
t.Logf("Attrs for %v (after):\n%s", testFilename, spew.Sdump(attrs))
} }
func TestMain(t *testing.M) { func TestMain(t *testing.M) {

View File

@@ -1,11 +1,17 @@
package fsutils package fsutils
// FsAttrs is a convenience struct around github.com/g0rbe/go-chattr. import (
`r00t2.io/goutils/bitmask`
)
type fsAttr bitmask.MaskBit
// FsAttrs is a struct representation of filesystem attributes on Linux.
type FsAttrs struct { type FsAttrs struct {
SecureDelete bool SecureDelete bool
UnDelete bool UnDelete bool
CompressFile bool CompressFile bool
SyncUpdatechattr bool SyncUpdate bool
Immutable bool Immutable bool
AppendOnly bool AppendOnly bool
NoDumpFile bool NoDumpFile bool
@@ -23,10 +29,13 @@ type FsAttrs struct {
DirTop bool DirTop bool
ReservedExt4a bool ReservedExt4a bool
Extents bool Extents bool
VerityProtected bool
LargeEaInode bool LargeEaInode bool
ReservedExt4b bool ReservedExt4b bool
NoCOWFile bool NoCOWFile bool
DAX bool
ReservedExt4c bool ReservedExt4c bool
UseParentProjId bool UseParentProjId bool
CaseInsensitive bool
ReservedExt2 bool ReservedExt2 bool
} }

19
go.mod
View File

@@ -1,19 +1,12 @@
module r00t2.io/sysutils module r00t2.io/sysutils
go 1.21 go 1.23.2
require ( require (
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/g0rbe/go-chattr v1.0.1 github.com/djherbis/times v1.6.0
github.com/google/uuid v1.6.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
golang.org/x/sys v0.19.0 golang.org/x/sys v0.26.0
r00t2.io/goutils v1.6.0 honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8
r00t2.io/goutils v1.7.1
) )
require (
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect
github.com/godbus/dbus v4.1.0+incompatible // indirect
)
// Pending https://github.com/g0rbe/go-chattr/pull/3
replace github.com/g0rbe/go-chattr => github.com/johnnybubonic/go-chattr v0.0.0-20240126141003-459f46177b13

27
go.sum
View File

@@ -1,22 +1,17 @@
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 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/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.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-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
r00t2.io/goutils v1.4.0 h1:/x/etLpMFv3+j1aPtT7KK2G0uOk+gQkGvXIYBCdjn3E= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
r00t2.io/goutils v1.4.0/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk= honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 h1:FW42yWB1sGClqswyHIB68wo0+oPrav1IuQ+Tdy8Qp8E=
r00t2.io/goutils v1.5.0 h1:haVk+wUK1BAk8f4UFGjy3ov3DwGMauZAOv/XYdb9isQ= honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8/go.mod h1:44w9OfBSQ9l3o59rc2w3AnABtE44bmtNnRMNC7z+oKE=
r00t2.io/goutils v1.5.0/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk= r00t2.io/goutils v1.7.1 h1:Yzl9rxX1sR9WT0FcjK60qqOgBoFBOGHYKZVtReVLoQc=
r00t2.io/goutils v1.6.0 h1:oBC6PgBv0y/fdHeCmWgORHpBiU8uWw7IfFQJX5rIuzY= r00t2.io/goutils v1.7.1/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
r00t2.io/goutils v1.6.0/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o= r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=

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"
"os/user" "os/user"
"path/filepath" "path/filepath"
`regexp`
`slices`
"strings" "strings"
`time`
// "syscall" // "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 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