aboutsummaryrefslogtreecommitdiffhomepage
path: root/system/acl/acl_getfacl_test.go
blob: c20fade7ec8b3cd3541eb9a0e8c68eef8479dbb7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package acl_test

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"os/exec"
	"strconv"
)

type (
	getFAclInvocation struct {
		cmd *exec.Cmd
		val []*getFAclResp
		pe  []error
	}

	getFAclResp struct {
		typ  fAclType
		cred int32
		val  fAclPerm

		raw []byte
	}

	fAclPerm uintptr
	fAclType uint8
)

const fAclBufSize = 16

const (
	fAclPermRead fAclPerm = 1 << iota
	fAclPermWrite
	fAclPermExecute
)

const (
	fAclTypeUser fAclType = iota
	fAclTypeGroup
	fAclTypeMask
	fAclTypeOther
)

func (c *getFAclInvocation) run(name string) error {
	if c.cmd != nil {
		panic("attempted to run twice")
	}

	c.cmd = exec.Command("getfacl", "--omit-header", "--absolute-names", "--numeric", name)

	scanErr := make(chan error, 1)
	if p, err := c.cmd.StdoutPipe(); err != nil {
		return err
	} else {
		go c.parse(p, scanErr)
	}

	if err := c.cmd.Start(); err != nil {
		return err
	}

	return errors.Join(<-scanErr, c.cmd.Wait())
}

func (c *getFAclInvocation) parse(pipe io.Reader, scanErr chan error) {
	c.val = make([]*getFAclResp, 0, 4+fAclBufSize)

	s := bufio.NewScanner(pipe)
	for s.Scan() {
		fields := bytes.SplitN(s.Bytes(), []byte{':'}, 3)
		if len(fields) != 3 {
			continue
		}

		resp := getFAclResp{}

		switch string(fields[0]) {
		case "user":
			resp.typ = fAclTypeUser
		case "group":
			resp.typ = fAclTypeGroup
		case "mask":
			resp.typ = fAclTypeMask
		case "other":
			resp.typ = fAclTypeOther
		default:
			c.pe = append(c.pe, fmt.Errorf("unknown type %s", string(fields[0])))
			continue
		}

		if len(fields[1]) == 0 {
			resp.cred = -1
		} else {
			if cred, err := strconv.Atoi(string(fields[1])); err != nil {
				c.pe = append(c.pe, err)
				continue
			} else {
				resp.cred = int32(cred)
				if resp.cred < 0 {
					c.pe = append(c.pe, fmt.Errorf("credential %d out of range", resp.cred))
					continue
				}
			}
		}

		if len(fields[2]) != 3 {
			c.pe = append(c.pe, fmt.Errorf("invalid perm length %d", len(fields[2])))
			continue
		} else {
			switch fields[2][0] {
			case 'r':
				resp.val |= fAclPermRead
			case '-':
			default:
				c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][0]))
				continue
			}
			switch fields[2][1] {
			case 'w':
				resp.val |= fAclPermWrite
			case '-':
			default:
				c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][1]))
				continue
			}
			switch fields[2][2] {
			case 'x':
				resp.val |= fAclPermExecute
			case '-':
			default:
				c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][2]))
				continue
			}
		}

		resp.raw = make([]byte, len(s.Bytes()))
		copy(resp.raw, s.Bytes())
		c.val = append(c.val, &resp)
	}
	scanErr <- s.Err()
}

func (r *getFAclResp) String() string {
	if r.raw != nil && len(r.raw) > 0 {
		return string(r.raw)
	}

	return "(user-initialised resp value)"
}

func (r *getFAclResp) equals(typ fAclType, cred int32, val fAclPerm) bool {
	return r.typ == typ && r.cred == cred && r.val == val
}