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