1 //! FreeBSD and NetBSD xattr support.
2 
3 use std::ffi::{CString, OsStr, OsString};
4 use std::io;
5 use std::mem;
6 use std::os::unix::ffi::{OsStrExt, OsStringExt};
7 use std::os::unix::io::RawFd;
8 use std::path::Path;
9 
10 use libc::{c_char, c_int, c_void, size_t, ssize_t, EPERM};
11 use util::{allocate_loop, path_to_c};
12 
13 const EXTATTR_NAMESPACE_USER_STRING: &'static str = "user";
14 const EXTATTR_NAMESPACE_SYSTEM_STRING: &'static str = "system";
15 const EXTATTR_NAMESPACE_NAMES: [&'static str; 3] = [
16     "empty",
17     EXTATTR_NAMESPACE_USER_STRING,
18     EXTATTR_NAMESPACE_SYSTEM_STRING,
19 ];
20 const EXTATTR_NAMESPACE_USER: c_int = 1;
21 const EXTATTR_NAMESPACE_SYSTEM: c_int = 2;
22 
23 extern "C" {
extattr_list_fd( fd: c_int, attrnamespace: c_int, data: *mut c_void, nbytes: size_t, ) -> ssize_t24     pub fn extattr_list_fd(
25         fd: c_int,
26         attrnamespace: c_int,
27         data: *mut c_void,
28         nbytes: size_t,
29     ) -> ssize_t;
extattr_get_fd( fd: c_int, attrnamespace: c_int, attrname: *const c_char, data: *mut c_void, nbytes: size_t, ) -> ssize_t30     pub fn extattr_get_fd(
31         fd: c_int,
32         attrnamespace: c_int,
33         attrname: *const c_char,
34         data: *mut c_void,
35         nbytes: size_t,
36     ) -> ssize_t;
extattr_delete_fd(fd: c_int, attrname: c_int, attrname: *const c_char) -> c_int37     pub fn extattr_delete_fd(fd: c_int, attrname: c_int, attrname: *const c_char) -> c_int;
extattr_set_fd( fd: c_int, attrname: c_int, attrname: *const c_char, data: *const c_void, nbytes: size_t, ) -> ssize_t38     pub fn extattr_set_fd(
39         fd: c_int,
40         attrname: c_int,
41         attrname: *const c_char,
42         data: *const c_void,
43         nbytes: size_t,
44     ) -> ssize_t;
45 
extattr_list_link( path: *const c_char, attrnamespace: c_int, data: *mut c_void, nbytes: size_t, ) -> ssize_t46     pub fn extattr_list_link(
47         path: *const c_char,
48         attrnamespace: c_int,
49         data: *mut c_void,
50         nbytes: size_t,
51     ) -> ssize_t;
extattr_get_link( path: *const c_char, attrnamespace: c_int, attrname: *const c_char, data: *mut c_void, nbytes: size_t, ) -> ssize_t52     pub fn extattr_get_link(
53         path: *const c_char,
54         attrnamespace: c_int,
55         attrname: *const c_char,
56         data: *mut c_void,
57         nbytes: size_t,
58     ) -> ssize_t;
extattr_delete_link( path: *const c_char, attrname: c_int, attrname: *const c_char, ) -> c_int59     pub fn extattr_delete_link(
60         path: *const c_char,
61         attrname: c_int,
62         attrname: *const c_char,
63     ) -> c_int;
extattr_set_link( path: *const c_char, attrname: c_int, attrname: *const c_char, data: *const c_void, nbytes: size_t, ) -> ssize_t64     pub fn extattr_set_link(
65         path: *const c_char,
66         attrname: c_int,
67         attrname: *const c_char,
68         data: *const c_void,
69         nbytes: size_t,
70     ) -> ssize_t;
71 }
72 
73 /// An iterator over a set of extended attributes names.
74 pub struct XAttrs {
75     user_attrs: Box<[u8]>,
76     system_attrs: Box<[u8]>,
77     offset: usize,
78 }
79 
80 impl Clone for XAttrs {
clone(&self) -> Self81     fn clone(&self) -> Self {
82         XAttrs {
83             user_attrs: Vec::from(&*self.user_attrs).into_boxed_slice(),
84             system_attrs: Vec::from(&*self.system_attrs).into_boxed_slice(),
85             offset: self.offset,
86         }
87     }
88 
clone_from(&mut self, other: &XAttrs)89     fn clone_from(&mut self, other: &XAttrs) {
90         self.offset = other.offset;
91 
92         let mut data = mem::replace(&mut self.user_attrs, Box::new([])).into_vec();
93         data.extend(other.user_attrs.iter().cloned());
94         self.user_attrs = data.into_boxed_slice();
95 
96         data = mem::replace(&mut self.system_attrs, Box::new([])).into_vec();
97         data.extend(other.system_attrs.iter().cloned());
98         self.system_attrs = data.into_boxed_slice();
99     }
100 }
101 
102 impl Iterator for XAttrs {
103     type Item = OsString;
next(&mut self) -> Option<OsString>104     fn next(&mut self) -> Option<OsString> {
105         if self.user_attrs.is_empty() && self.system_attrs.is_empty() {
106             return None;
107         }
108 
109         if self.offset == self.user_attrs.len() + self.system_attrs.len() {
110             return None;
111         }
112 
113         let data = if self.offset < self.system_attrs.len() {
114             &self.system_attrs[self.offset..]
115         } else {
116             &self.user_attrs[self.offset - self.system_attrs.len()..]
117         };
118 
119         let siz = data[0] as usize;
120 
121         self.offset += siz + 1;
122         if self.offset < self.system_attrs.len() {
123             Some(prefix_namespace(
124                 OsStr::from_bytes(&data[1..siz + 1]),
125                 EXTATTR_NAMESPACE_SYSTEM,
126             ))
127         } else {
128             Some(prefix_namespace(
129                 OsStr::from_bytes(&data[1..siz + 1]),
130                 EXTATTR_NAMESPACE_USER,
131             ))
132         }
133     }
134 
size_hint(&self) -> (usize, Option<usize>)135     fn size_hint(&self) -> (usize, Option<usize>) {
136         if self.user_attrs.len() + self.system_attrs.len() == self.offset {
137             (0, Some(0))
138         } else {
139             (1, None)
140         }
141     }
142 }
143 
name_to_ns(name: &OsStr) -> io::Result<(c_int, CString)>144 fn name_to_ns(name: &OsStr) -> io::Result<(c_int, CString)> {
145     let mut groups = name.as_bytes().splitn(2, |&b| b == b'.').take(2);
146     let nsname = match groups.next() {
147         Some(s) => s,
148         None => {
149             return Err(io::Error::new(
150                 io::ErrorKind::InvalidInput,
151                 "couldn't find namespace",
152             ))
153         }
154     };
155 
156     let propname = match groups.next() {
157         Some(s) => s,
158         None => {
159             return Err(io::Error::new(
160                 io::ErrorKind::InvalidInput,
161                 "couldn't find attribute",
162             ))
163         }
164     };
165 
166     let ns_int = match EXTATTR_NAMESPACE_NAMES
167         .iter()
168         .position(|&s| s.as_bytes() == nsname)
169     {
170         Some(i) => i,
171         None => {
172             return Err(io::Error::new(
173                 io::ErrorKind::InvalidInput,
174                 "no matching namespace",
175             ))
176         }
177     };
178 
179     Ok((ns_int as c_int, CString::new(propname)?))
180 }
181 
prefix_namespace(attr: &OsStr, ns: c_int) -> OsString182 fn prefix_namespace(attr: &OsStr, ns: c_int) -> OsString {
183     let nsname = EXTATTR_NAMESPACE_NAMES[ns as usize];
184     let mut v = Vec::with_capacity(nsname.as_bytes().len() + attr.as_bytes().len() + 1);
185     v.extend(nsname.as_bytes());
186     v.extend(".".as_bytes());
187     v.extend(attr.as_bytes());
188     OsString::from_vec(v)
189 }
190 
get_fd(fd: RawFd, name: &OsStr) -> io::Result<Vec<u8>>191 pub fn get_fd(fd: RawFd, name: &OsStr) -> io::Result<Vec<u8>> {
192     let (ns, name) = name_to_ns(name)?;
193     unsafe {
194         allocate_loop(|buf, len| {
195             extattr_get_fd(fd, ns, name.as_ptr(), buf as *mut c_void, len as size_t)
196         })
197     }
198 }
199 
set_fd(fd: RawFd, name: &OsStr, value: &[u8]) -> io::Result<()>200 pub fn set_fd(fd: RawFd, name: &OsStr, value: &[u8]) -> io::Result<()> {
201     let (ns, name) = name_to_ns(name)?;
202     let ret = unsafe {
203         extattr_set_fd(
204             fd,
205             ns,
206             name.as_ptr(),
207             value.as_ptr() as *const c_void,
208             value.len() as size_t,
209         )
210     };
211     if ret == -1 {
212         Err(io::Error::last_os_error())
213     } else {
214         Ok(())
215     }
216 }
217 
remove_fd(fd: RawFd, name: &OsStr) -> io::Result<()>218 pub fn remove_fd(fd: RawFd, name: &OsStr) -> io::Result<()> {
219     let (ns, name) = name_to_ns(name)?;
220     let ret = unsafe { extattr_delete_fd(fd, ns, name.as_ptr()) };
221     if ret != 0 {
222         Err(io::Error::last_os_error())
223     } else {
224         Ok(())
225     }
226 }
227 
list_fd(fd: RawFd) -> io::Result<XAttrs>228 pub fn list_fd(fd: RawFd) -> io::Result<XAttrs> {
229     let sysvec = unsafe {
230         let res = allocate_loop(|buf, len| {
231             extattr_list_fd(
232                 fd,
233                 EXTATTR_NAMESPACE_SYSTEM,
234                 buf as *mut c_void,
235                 len as size_t,
236             )
237         });
238         // On FreeBSD, system attributes require root privileges to view. However,
239         // to mimic the behavior of listxattr in linux and osx, we need to query
240         // them anyway and return empty results if we get EPERM
241         match res {
242             Ok(v) => v,
243             Err(err) => {
244                 if err.raw_os_error() == Some(EPERM) {
245                     Vec::new()
246                 } else {
247                     return Err(err);
248                 }
249             }
250         }
251     };
252 
253     let uservec = unsafe {
254         let res = allocate_loop(|buf, len| {
255             extattr_list_fd(
256                 fd,
257                 EXTATTR_NAMESPACE_USER,
258                 buf as *mut c_void,
259                 len as size_t,
260             )
261         });
262         match res {
263             Ok(v) => v,
264             Err(err) => return Err(err),
265         }
266     };
267 
268     Ok(XAttrs {
269         system_attrs: sysvec.into_boxed_slice(),
270         user_attrs: uservec.into_boxed_slice(),
271         offset: 0,
272     })
273 }
274 
get_path(path: &Path, name: &OsStr) -> io::Result<Vec<u8>>275 pub fn get_path(path: &Path, name: &OsStr) -> io::Result<Vec<u8>> {
276     let (ns, name) = name_to_ns(name)?;
277     let path = path_to_c(path)?;
278     unsafe {
279         allocate_loop(|buf, len| {
280             extattr_get_link(
281                 path.as_ptr(),
282                 ns,
283                 name.as_ptr(),
284                 buf as *mut c_void,
285                 len as size_t,
286             )
287         })
288     }
289 }
290 
set_path(path: &Path, name: &OsStr, value: &[u8]) -> io::Result<()>291 pub fn set_path(path: &Path, name: &OsStr, value: &[u8]) -> io::Result<()> {
292     let (ns, name) = name_to_ns(name)?;
293     let path = path_to_c(path)?;
294     let ret = unsafe {
295         extattr_set_link(
296             path.as_ptr(),
297             ns,
298             name.as_ptr(),
299             value.as_ptr() as *const c_void,
300             value.len() as size_t,
301         )
302     };
303     if ret == -1 {
304         Err(io::Error::last_os_error())
305     } else {
306         Ok(())
307     }
308 }
309 
remove_path(path: &Path, name: &OsStr) -> io::Result<()>310 pub fn remove_path(path: &Path, name: &OsStr) -> io::Result<()> {
311     let (ns, name) = name_to_ns(name)?;
312     let path = path_to_c(path)?;
313     let ret = unsafe { extattr_delete_link(path.as_ptr(), ns, name.as_ptr()) };
314     if ret != 0 {
315         Err(io::Error::last_os_error())
316     } else {
317         Ok(())
318     }
319 }
320 
list_path(path: &Path) -> io::Result<XAttrs>321 pub fn list_path(path: &Path) -> io::Result<XAttrs> {
322     let path = path_to_c(path)?;
323     let sysvec = unsafe {
324         let res = allocate_loop(|buf, len| {
325             extattr_list_link(
326                 path.as_ptr(),
327                 EXTATTR_NAMESPACE_SYSTEM,
328                 buf as *mut c_void,
329                 len as size_t,
330             )
331         });
332         // On FreeBSD, system attributes require root privileges to view. However,
333         // to mimic the behavior of listxattr in linux and osx, we need to query
334         // them anyway and return empty results if we get EPERM
335         match res {
336             Ok(v) => v,
337             Err(err) => {
338                 if err.raw_os_error() == Some(EPERM) {
339                     Vec::new()
340                 } else {
341                     return Err(err);
342                 }
343             }
344         }
345     };
346 
347     let uservec = unsafe {
348         let res = allocate_loop(|buf, len| {
349             extattr_list_link(
350                 path.as_ptr(),
351                 EXTATTR_NAMESPACE_USER,
352                 buf as *mut c_void,
353                 len as size_t,
354             )
355         });
356         match res {
357             Ok(v) => v,
358             Err(err) => return Err(err),
359         }
360     };
361 
362     Ok(XAttrs {
363         system_attrs: sysvec.into_boxed_slice(),
364         user_attrs: uservec.into_boxed_slice(),
365         offset: 0,
366     })
367 }
368