From 70a88ca8b47f54b4fc5e0738ed6eeb6bcbcf97c9 Mon Sep 17 00:00:00 2001 From: brent saner Date: Thu, 7 Nov 2024 04:15:45 -0500 Subject: [PATCH] v1.9.0 IMPROVED: * Removed *BROKEN* dep. lrn2fixurshitk --- .gitignore | 3 + auger/TODO | 1 + auger/consts.go | 32 ++++ auger/funcs.go | 16 +- auger/funcs_aug.go | 40 ++++- auger/funcs_includeopt.go | 13 ++ auger/funcs_test.go | 15 ++ auger/types.go | 3 + cryptparse/doc.go | 1 + envs/funcs.go | 2 +- envs/utils.go | 2 +- fsutils/consts.go | 101 ------------ fsutils/consts_linux.go | 126 +++++++++++++++ fsutils/doc.go | 7 + fsutils/funcs.go | 44 ------ ...uncs_fsattrs.go => funcs_fsattrs_linux.go} | 7 +- fsutils/funcs_linux.go | 149 ++++++++++++++++++ .../{funcs_test.go => funcs_linux_test.go} | 13 +- fsutils/types.go | 13 +- go.mod | 6 +- go.sum | 2 + 21 files changed, 430 insertions(+), 166 deletions(-) create mode 100644 auger/TODO create mode 100644 auger/funcs_includeopt.go delete mode 100644 fsutils/consts.go create mode 100644 fsutils/consts_linux.go create mode 100644 fsutils/doc.go delete mode 100644 fsutils/funcs.go rename fsutils/{funcs_fsattrs.go => funcs_fsattrs_linux.go} (80%) create mode 100644 fsutils/funcs_linux.go rename fsutils/{funcs_test.go => funcs_linux_test.go} (73%) diff --git a/.gitignore b/.gitignore index 5f32697..ee522ec 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,9 @@ # Test binary, built with `go test -c` *.test +# Test file +fsutils/testfile + # Output of the go coverage tool, specifically when used with LiteIDE *.out diff --git a/auger/TODO b/auger/TODO new file mode 100644 index 0000000..a2583fb --- /dev/null +++ b/auger/TODO @@ -0,0 +1 @@ +This module is still under work. diff --git a/auger/consts.go b/auger/consts.go index 19f57a4..54ae945 100644 --- a/auger/consts.go +++ b/auger/consts.go @@ -7,3 +7,35 @@ const ( augInclTfm string = "incl" // The transformer keyword for Augeas includes. 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 +) diff --git a/auger/funcs.go b/auger/funcs.go index 0ff1152..f264c00 100644 --- a/auger/funcs.go +++ b/auger/funcs.go @@ -6,6 +6,7 @@ import ( `strings` `honnef.co/go/augeas` + `r00t2.io/goutils/bitmask` ) /* @@ -41,12 +42,17 @@ func NewAugerFromAugeas(orig augeas.Augeas) (aug *Aug) { } /* - AugpathToFspath returns the filesystem path from an Augeas path. + 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; if not, it is assumed to be part of the filesystem path. 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) { @@ -95,3 +101,11 @@ func dedupePaths(new, existing []string) (missing []string) { return } + +// getInclPaths applies path options to inclusions. +func getInclPaths(pathSpec string, inclFlags *bitmask.MaskBit) (fpaths []string, err error) { + + // TODO + + return +} diff --git a/auger/funcs_aug.go b/auger/funcs_aug.go index 37f9652..46a1c94 100644 --- a/auger/funcs_aug.go +++ b/auger/funcs_aug.go @@ -12,6 +12,7 @@ import ( `github.com/davecgh/go-spew/spew` `github.com/google/shlex` `honnef.co/go/augeas` + `r00t2.io/goutils/bitmask` `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. 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 } @@ -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. 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 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 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 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 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 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. 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 errors.As(err, augErr) && augErr.Code == augeas.NoMatch { 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. 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. if newInclPaths != nil { 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 err = a.addIncl(includeDirective, augLens, fsRoot, matches); err != nil { + if err = a.addIncl(includeDirective, augLens, fsRoot, matches, inclFlags); err != nil { return } } + // TODO + _, _ = exists, normalizedIncludes + return } diff --git a/auger/funcs_includeopt.go b/auger/funcs_includeopt.go new file mode 100644 index 0000000..47395e7 --- /dev/null +++ b/auger/funcs_includeopt.go @@ -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 +} diff --git a/auger/funcs_test.go b/auger/funcs_test.go index 2bb11b0..8ffa89d 100644 --- a/auger/funcs_test.go +++ b/auger/funcs_test.go @@ -22,3 +22,18 @@ func TestNewAuger(t *testing.T) { _ = 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) + } +} diff --git a/auger/types.go b/auger/types.go index f0e942d..da62670 100644 --- a/auger/types.go +++ b/auger/types.go @@ -2,8 +2,11 @@ package auger import ( `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(). type Aug struct { aug augeas.Augeas diff --git a/cryptparse/doc.go b/cryptparse/doc.go index d06c8da..2b0dc95 100644 --- a/cryptparse/doc.go +++ b/cryptparse/doc.go @@ -3,3 +3,4 @@ It is now its own module: r00t2.io/cryptparse */ +package cryptparse diff --git a/envs/funcs.go b/envs/funcs.go index 0d3d2a2..3eacadb 100644 --- a/envs/funcs.go +++ b/envs/funcs.go @@ -159,7 +159,7 @@ func GetPidEnvMap(pid uint32) (envMap map[string]string, err error) { var procPath string var exists bool - envMap = make(map[string]string, 0) + envMap = make(map[string]string) procPath = fmt.Sprintf("/proc/%v/environ", pid) diff --git a/envs/utils.go b/envs/utils.go index b683878..309c0e5 100644 --- a/envs/utils.go +++ b/envs/utils.go @@ -13,7 +13,7 @@ func envListToMap(envs []string) (envMap map[string]string) { var kv []string var k, v string - envMap = make(map[string]string, 0) + envMap = make(map[string]string) for _, ev := range envs { kv = strings.SplitN(ev, "=", 2) diff --git a/fsutils/consts.go b/fsutils/consts.go deleted file mode 100644 index d03f3e0..0000000 --- a/fsutils/consts.go +++ /dev/null @@ -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", - } -) diff --git a/fsutils/consts_linux.go b/fsutils/consts_linux.go new file mode 100644 index 0000000..f1f2192 --- /dev/null +++ b/fsutils/consts_linux.go @@ -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) +) diff --git a/fsutils/doc.go b/fsutils/doc.go new file mode 100644 index 0000000..a46402f --- /dev/null +++ b/fsutils/doc.go @@ -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 diff --git a/fsutils/funcs.go b/fsutils/funcs.go deleted file mode 100644 index d153565..0000000 --- a/fsutils/funcs.go +++ /dev/null @@ -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 -} diff --git a/fsutils/funcs_fsattrs.go b/fsutils/funcs_fsattrs_linux.go similarity index 80% rename from fsutils/funcs_fsattrs.go rename to fsutils/funcs_fsattrs_linux.go index 30b5b89..5a2dfea 100644 --- a/fsutils/funcs_fsattrs.go +++ b/fsutils/funcs_fsattrs_linux.go @@ -1,10 +1,11 @@ +//go:build linux + package fsutils import ( `os` `reflect` - `github.com/g0rbe/go-chattr` `r00t2.io/sysutils/paths` ) @@ -29,11 +30,11 @@ func (f *FsAttrs) Apply(path string) (err error) { for attrNm, attrVal := range AttrNameValueMap { fieldVal = reflectVal.FieldByName(attrNm) if fieldVal.Bool() { - if err = chattr.SetAttr(file, attrVal); err != nil { + if err = setAttrs(file, attrVal); err != nil { return } } else { - if err = chattr.UnsetAttr(file, attrVal); err != nil { + if err = unsetAttrs(file, attrVal); err != nil { return } } diff --git a/fsutils/funcs_linux.go b/fsutils/funcs_linux.go new file mode 100644 index 0000000..2c53a52 --- /dev/null +++ b/fsutils/funcs_linux.go @@ -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 +} diff --git a/fsutils/funcs_test.go b/fsutils/funcs_linux_test.go similarity index 73% rename from fsutils/funcs_test.go rename to fsutils/funcs_linux_test.go index 5cdb2d6..756d09c 100644 --- a/fsutils/funcs_test.go +++ b/fsutils/funcs_linux_test.go @@ -1,3 +1,5 @@ +//go:build linux + package fsutils import ( @@ -7,12 +9,13 @@ import ( `os/user` `testing` + `github.com/davecgh/go-spew/spew` `r00t2.io/sysutils/paths` ) var ( 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) { @@ -36,12 +39,18 @@ func TestSetAttrs(t *testing.T) { if attrs, err = GetAttrs(testFilename); err != nil { 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.SyncUpdate = true + attrs.SecureDelete = true if err = attrs.Apply(testFilename); err != nil { t.Fatalf("Failed to apply attrs to %v: %v", testFilename, err) } 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) { diff --git a/fsutils/types.go b/fsutils/types.go index 778f7d8..72da5ac 100644 --- a/fsutils/types.go +++ b/fsutils/types.go @@ -1,11 +1,17 @@ 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 { SecureDelete bool UnDelete bool CompressFile bool - SyncUpdatechattr bool + SyncUpdate bool Immutable bool AppendOnly bool NoDumpFile bool @@ -23,10 +29,13 @@ type FsAttrs struct { DirTop bool ReservedExt4a bool Extents bool + VerityProtected bool LargeEaInode bool ReservedExt4b bool NoCOWFile bool + DAX bool ReservedExt4c bool UseParentProjId bool + CaseInsensitive bool ReservedExt2 bool } diff --git a/go.mod b/go.mod index b748674..b8c202c 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,8 @@ go 1.23.2 require ( github.com/davecgh/go-spew v1.1.1 - github.com/g0rbe/go-chattr v1.0.1 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 - r00t2.io/goutils v1.7.0 + r00t2.io/goutils v1.7.1 ) - -// 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 diff --git a/go.sum b/go.sum index ea753cb..97ca174 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,8 @@ honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 h1:FW42yWB1sGClqswyHIB68w 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=