mirror of
https://github.com/restic/restic.git
synced 2026-09-15 16:57:58 +00:00
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:
+69
-112
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user