1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// +build freebsd netbsd
6
7package unix
8
9import (
10	"strings"
11	"unsafe"
12)
13
14// Derive extattr namespace and attribute name
15
16func xattrnamespace(fullattr string) (ns int, attr string, err error) {
17	s := strings.IndexByte(fullattr, '.')
18	if s == -1 {
19		return -1, "", ENOATTR
20	}
21
22	namespace := fullattr[0:s]
23	attr = fullattr[s+1:]
24
25	switch namespace {
26	case "user":
27		return EXTATTR_NAMESPACE_USER, attr, nil
28	case "system":
29		return EXTATTR_NAMESPACE_SYSTEM, attr, nil
30	default:
31		return -1, "", ENOATTR
32	}
33}
34
35func initxattrdest(dest []byte, idx int) (d unsafe.Pointer) {
36	if len(dest) > idx {
37		return unsafe.Pointer(&dest[idx])
38	} else {
39		return unsafe.Pointer(_zero)
40	}
41}
42
43// FreeBSD and NetBSD implement their own syscalls to handle extended attributes
44
45func Getxattr(file string, attr string, dest []byte) (sz int, err error) {
46	d := initxattrdest(dest, 0)
47	destsize := len(dest)
48
49	nsid, a, err := xattrnamespace(attr)
50	if err != nil {
51		return -1, err
52	}
53
54	return ExtattrGetFile(file, nsid, a, uintptr(d), destsize)
55}
56
57func Fgetxattr(fd int, attr string, dest []byte) (sz int, err error) {
58	d := initxattrdest(dest, 0)
59	destsize := len(dest)
60
61	nsid, a, err := xattrnamespace(attr)
62	if err != nil {
63		return -1, err
64	}
65
66	return ExtattrGetFd(fd, nsid, a, uintptr(d), destsize)
67}
68
69func Lgetxattr(link string, attr string, dest []byte) (sz int, err error) {
70	d := initxattrdest(dest, 0)
71	destsize := len(dest)
72
73	nsid, a, err := xattrnamespace(attr)
74	if err != nil {
75		return -1, err
76	}
77
78	return ExtattrGetLink(link, nsid, a, uintptr(d), destsize)
79}
80
81// flags are unused on FreeBSD
82
83func Fsetxattr(fd int, attr string, data []byte, flags int) (err error) {
84	var d unsafe.Pointer
85	if len(data) > 0 {
86		d = unsafe.Pointer(&data[0])
87	}
88	datasiz := len(data)
89
90	nsid, a, err := xattrnamespace(attr)
91	if err != nil {
92		return
93	}
94
95	_, err = ExtattrSetFd(fd, nsid, a, uintptr(d), datasiz)
96	return
97}
98
99func Setxattr(file string, attr string, data []byte, flags int) (err error) {
100	var d unsafe.Pointer
101	if len(data) > 0 {
102		d = unsafe.Pointer(&data[0])
103	}
104	datasiz := len(data)
105
106	nsid, a, err := xattrnamespace(attr)
107	if err != nil {
108		return
109	}
110
111	_, err = ExtattrSetFile(file, nsid, a, uintptr(d), datasiz)
112	return
113}
114
115func Lsetxattr(link string, attr string, data []byte, flags int) (err error) {
116	var d unsafe.Pointer
117	if len(data) > 0 {
118		d = unsafe.Pointer(&data[0])
119	}
120	datasiz := len(data)
121
122	nsid, a, err := xattrnamespace(attr)
123	if err != nil {
124		return
125	}
126
127	_, err = ExtattrSetLink(link, nsid, a, uintptr(d), datasiz)
128	return
129}
130
131func Removexattr(file string, attr string) (err error) {
132	nsid, a, err := xattrnamespace(attr)
133	if err != nil {
134		return
135	}
136
137	err = ExtattrDeleteFile(file, nsid, a)
138	return
139}
140
141func Fremovexattr(fd int, attr string) (err error) {
142	nsid, a, err := xattrnamespace(attr)
143	if err != nil {
144		return
145	}
146
147	err = ExtattrDeleteFd(fd, nsid, a)
148	return
149}
150
151func Lremovexattr(link string, attr string) (err error) {
152	nsid, a, err := xattrnamespace(attr)
153	if err != nil {
154		return
155	}
156
157	err = ExtattrDeleteLink(link, nsid, a)
158	return
159}
160
161func Listxattr(file string, dest []byte) (sz int, err error) {
162	d := initxattrdest(dest, 0)
163	destsiz := len(dest)
164
165	// FreeBSD won't allow you to list xattrs from multiple namespaces
166	s := 0
167	for _, nsid := range [...]int{EXTATTR_NAMESPACE_USER, EXTATTR_NAMESPACE_SYSTEM} {
168		stmp, e := ExtattrListFile(file, nsid, uintptr(d), destsiz)
169
170		/* Errors accessing system attrs are ignored so that
171		 * we can implement the Linux-like behavior of omitting errors that
172		 * we don't have read permissions on
173		 *
174		 * Linux will still error if we ask for user attributes on a file that
175		 * we don't have read permissions on, so don't ignore those errors
176		 */
177		if e != nil && e == EPERM && nsid != EXTATTR_NAMESPACE_USER {
178			continue
179		} else if e != nil {
180			return s, e
181		}
182
183		s += stmp
184		destsiz -= s
185		if destsiz < 0 {
186			destsiz = 0
187		}
188		d = initxattrdest(dest, s)
189	}
190
191	return s, nil
192}
193
194func Flistxattr(fd int, dest []byte) (sz int, err error) {
195	d := initxattrdest(dest, 0)
196	destsiz := len(dest)
197
198	s := 0
199	for _, nsid := range [...]int{EXTATTR_NAMESPACE_USER, EXTATTR_NAMESPACE_SYSTEM} {
200		stmp, e := ExtattrListFd(fd, nsid, uintptr(d), destsiz)
201		if e != nil && e == EPERM && nsid != EXTATTR_NAMESPACE_USER {
202			continue
203		} else if e != nil {
204			return s, e
205		}
206
207		s += stmp
208		destsiz -= s
209		if destsiz < 0 {
210			destsiz = 0
211		}
212		d = initxattrdest(dest, s)
213	}
214
215	return s, nil
216}
217
218func Llistxattr(link string, dest []byte) (sz int, err error) {
219	d := initxattrdest(dest, 0)
220	destsiz := len(dest)
221
222	s := 0
223	for _, nsid := range [...]int{EXTATTR_NAMESPACE_USER, EXTATTR_NAMESPACE_SYSTEM} {
224		stmp, e := ExtattrListLink(link, nsid, uintptr(d), destsiz)
225		if e != nil && e == EPERM && nsid != EXTATTR_NAMESPACE_USER {
226			continue
227		} else if e != nil {
228			return s, e
229		}
230
231		s += stmp
232		destsiz -= s
233		if destsiz < 0 {
234			destsiz = 0
235		}
236		d = initxattrdest(dest, s)
237	}
238
239	return s, nil
240}
241