add filesystem attr (on linux) support
This commit is contained in:
parent
8254fd21a3
commit
187ad868db
101
fsutils/consts.go
Normal file
101
fsutils/consts.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
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",
|
||||||
|
}
|
||||||
|
)
|
44
fsutils/funcs.go
Normal file
44
fsutils/funcs.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
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
|
||||||
|
}
|
43
fsutils/funcs_fsattrs.go
Normal file
43
fsutils/funcs_fsattrs.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package fsutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
`os`
|
||||||
|
`reflect`
|
||||||
|
|
||||||
|
`github.com/g0rbe/go-chattr`
|
||||||
|
`r00t2.io/sysutils/paths`
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f *FsAttrs) Apply(path string) (err error) {
|
||||||
|
|
||||||
|
var file *os.File
|
||||||
|
var reflectVal reflect.Value
|
||||||
|
var fieldVal reflect.Value
|
||||||
|
|
||||||
|
var myPath string = path
|
||||||
|
|
||||||
|
if err = paths.RealPath(&myPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if file, err = os.Open(myPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
reflectVal = reflect.ValueOf(*f)
|
||||||
|
|
||||||
|
for attrNm, attrVal := range AttrNameValueMap {
|
||||||
|
fieldVal = reflectVal.FieldByName(attrNm)
|
||||||
|
if fieldVal.Bool() {
|
||||||
|
if err = chattr.SetAttr(file, attrVal); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = chattr.UnsetAttr(file, attrVal); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
71
fsutils/funcs_test.go
Normal file
71
fsutils/funcs_test.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package fsutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
`errors`
|
||||||
|
`fmt`
|
||||||
|
`os`
|
||||||
|
`os/user`
|
||||||
|
`testing`
|
||||||
|
|
||||||
|
`r00t2.io/sysutils/paths`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testFilename string = "testfile"
|
||||||
|
testErrBadUser error = errors.New("test must be run as root, on Linux")
|
||||||
|
)
|
||||||
|
|
||||||
|
func testChkUser() (err error) {
|
||||||
|
var u *user.User
|
||||||
|
|
||||||
|
if u, err = user.Current(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if u.Uid != "0" {
|
||||||
|
err = testErrBadUser
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetAttrs(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var attrs *FsAttrs
|
||||||
|
|
||||||
|
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)
|
||||||
|
attrs.CompressFile = 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(t *testing.M) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var rslt int
|
||||||
|
|
||||||
|
if err = testChkUser(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err = paths.RealPath(&testFilename); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
if err = os.WriteFile(testFilename, []byte("This is a test file."), 0o0644); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
rslt = t.Run()
|
||||||
|
|
||||||
|
if err = os.Remove(testFilename); err != nil {
|
||||||
|
fmt.Printf("Failed to remove test file %v: %v", testFilename, err)
|
||||||
|
}
|
||||||
|
os.Exit(rslt)
|
||||||
|
}
|
32
fsutils/types.go
Normal file
32
fsutils/types.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package fsutils
|
||||||
|
|
||||||
|
// FsAttrs is a convenience struct around github.com/g0rbe/go-chattr.
|
||||||
|
type FsAttrs struct {
|
||||||
|
SecureDelete bool
|
||||||
|
UnDelete bool
|
||||||
|
CompressFile bool
|
||||||
|
SyncUpdatechattr bool
|
||||||
|
Immutable bool
|
||||||
|
AppendOnly bool
|
||||||
|
NoDumpFile bool
|
||||||
|
NoUpdateAtime bool
|
||||||
|
IsDirty bool
|
||||||
|
CompressedClusters bool
|
||||||
|
NoCompress bool
|
||||||
|
EncFile bool
|
||||||
|
BtreeFmt bool
|
||||||
|
HashIdxDir bool
|
||||||
|
AfsDir bool
|
||||||
|
ReservedExt3 bool
|
||||||
|
NoMergeTail bool
|
||||||
|
DirSync bool
|
||||||
|
DirTop bool
|
||||||
|
ReservedExt4a bool
|
||||||
|
Extents bool
|
||||||
|
LargeEaInode bool
|
||||||
|
ReservedExt4b bool
|
||||||
|
NoCOWFile bool
|
||||||
|
ReservedExt4c bool
|
||||||
|
UseParentProjId bool
|
||||||
|
ReservedExt2 bool
|
||||||
|
}
|
7
go.mod
7
go.mod
@ -1,3 +1,8 @@
|
|||||||
module r00t2.io/sysutils
|
module r00t2.io/sysutils
|
||||||
|
|
||||||
go 1.16
|
go 1.21
|
||||||
|
|
||||||
|
require github.com/g0rbe/go-chattr v1.0.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
|
||||||
|
Loading…
Reference in New Issue
Block a user