dump: Rewrite Linux ACL handling

The old version was taken from an MPL-licensed library. This is a
cleanroom implementation. The code is shorter and it's now explicit that
only Linux ACLs are supported.
This commit is contained in:
greatroar
2023-08-13 13:37:11 +02:00
parent f96896a9c0
commit aaf5254e26
3 changed files with 120 additions and 224 deletions
+69 -112
View File
@@ -1,131 +1,88 @@
package dump
// Adapted from https://github.com/maxymania/go-system/blob/master/posix_acl/posix_acl.go
import (
"bytes"
"encoding/binary"
"fmt"
"errors"
"strconv"
)
const (
aclUserOwner = 0x0001
aclUser = 0x0002
aclGroupOwner = 0x0004
aclGroup = 0x0008
aclMask = 0x0010
aclOthers = 0x0020
// Permissions
aclPermRead = 0x4
aclPermWrite = 0x2
aclPermExecute = 0x1
// Tags
aclTagUserObj = 0x01 // Owner.
aclTagUser = 0x02
aclTagGroupObj = 0x04 // Owning group.
aclTagGroup = 0x08
aclTagMask = 0x10
aclTagOther = 0x20
)
type aclSID uint64
type aclElem struct {
Tag uint16
Perm uint16
ID uint32
}
type acl struct {
Version uint32
List []aclElement
}
type aclElement struct {
aclSID
Perm uint16
}
func (a aclSID) getType() int {
return int(a >> 32)
}
func (a aclSID) getID() uint32 {
return uint32(a & 0xffffffff)
}
func (a aclSID) String() string {
switch a >> 32 {
case aclUserOwner:
return "user::"
case aclUser:
return fmt.Sprintf("user:%v:", a.getID())
case aclGroupOwner:
return "group::"
case aclGroup:
return fmt.Sprintf("group:%v:", a.getID())
case aclMask:
return "mask::"
case aclOthers:
return "other::"
// formatLinuxACL converts a Linux ACL from its binary format to the POSIX.1e
// long text format.
//
// User and group IDs are printed in decimal, because we may be dumping
// a snapshot from a different machine.
//
// https://man7.org/linux/man-pages/man5/acl.5.html
// https://savannah.nongnu.org/projects/acl
// https://simson.net/ref/1997/posix_1003.1e-990310.pdf
func formatLinuxACL(acl []byte) (string, error) {
if len(acl)-4 < 0 || (len(acl)-4)%8 != 0 {
return "", errors.New("wrong length")
}
return "?:"
}
version := binary.LittleEndian.Uint32(acl)
if version != 2 {
return "", errors.New("unsupported ACL format version")
}
acl = acl[4:]
func (a aclElement) String() string {
str := ""
if (a.Perm & 4) != 0 {
str += "r"
} else {
str += "-"
}
if (a.Perm & 2) != 0 {
str += "w"
} else {
str += "-"
}
if (a.Perm & 1) != 0 {
str += "x"
} else {
str += "-"
}
return fmt.Sprintf("%v%v", a.aclSID, str)
}
text := make([]byte, 0, 2*len(acl))
func (a *acl) decode(xattr []byte) {
var elem aclElement
ae := new(aclElem)
nr := bytes.NewReader(xattr)
e := binary.Read(nr, binary.LittleEndian, &a.Version)
if e != nil {
a.Version = 0
return
}
if len(a.List) > 0 {
a.List = a.List[:0]
}
for binary.Read(nr, binary.LittleEndian, ae) == nil {
elem.aclSID = (aclSID(ae.Tag) << 32) | aclSID(ae.ID)
elem.Perm = ae.Perm
a.List = append(a.List, elem)
}
}
for ; len(acl) >= 8; acl = acl[8:] {
tag := binary.LittleEndian.Uint16(acl)
perm := binary.LittleEndian.Uint16(acl[2:])
id := binary.LittleEndian.Uint32(acl[4:])
func (a *acl) encode() []byte {
buf := new(bytes.Buffer)
ae := new(aclElem)
err := binary.Write(buf, binary.LittleEndian, &a.Version)
// write to a bytes.Buffer always returns a nil error
if err != nil {
panic(err)
}
for _, elem := range a.List {
ae.Tag = uint16(elem.getType())
ae.Perm = elem.Perm
ae.ID = elem.getID()
err := binary.Write(buf, binary.LittleEndian, ae)
// write to a bytes.Buffer always returns a nil error
if err != nil {
panic(err)
switch tag {
case aclTagUserObj:
text = append(text, "user:"...)
case aclTagUser:
text = append(text, "user:"...)
text = strconv.AppendUint(text, uint64(id), 10)
case aclTagGroupObj:
text = append(text, "group:"...)
case aclTagGroup:
text = append(text, "group:"...)
text = strconv.AppendUint(text, uint64(id), 10)
case aclTagMask:
text = append(text, "mask:"...)
case aclTagOther:
text = append(text, "other:"...)
default:
return "", errors.New("unknown tag")
}
text = append(text, ':')
text = append(text, aclPermText(perm)...)
text = append(text, '\n')
}
return buf.Bytes()
return string(text), nil
}
func (a *acl) String() string {
var finalacl string
for _, acl := range a.List {
finalacl += acl.String() + "\n"
func aclPermText(p uint16) []byte {
s := []byte("---")
if p&aclPermRead != 0 {
s[0] = 'r'
}
return finalacl
if p&aclPermWrite != 0 {
s[1] = 'w'
}
if p&aclPermExecute != 0 {
s[2] = 'x'
}
return s
}