IMPROVED:
* Removed *BROKEN* dep. lrn2fixurshitk
This commit is contained in:
brent saner 2024-11-07 04:15:45 -05:00
parent 9dbc3a00fe
commit 70a88ca8b4
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
21 changed files with 430 additions and 166 deletions

3
.gitignore vendored
View File

@ -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


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

@ -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
}

View File

@ -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
}

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
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -3,3 +3,4 @@

It is now its own module: r00t2.io/cryptparse
*/
package cryptparse

View File

@ -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)


View File

@ -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)

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

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
}
}

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

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) {

View File

@ -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
}

6
go.mod
View File

@ -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

2
go.sum
View File

@ -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=