1use std::ffi::CString;
7use std::io;
8
9#[derive(Debug, Default, Clone)]
11pub struct XattrOptions {
12 pub no_dereference: bool,
13}
14
15#[cfg(target_os = "macos")]
17pub fn getxattr(path: &str, name: &str, options: &XattrOptions) -> io::Result<Vec<u8>> {
18 let path_c = CString::new(path)
19 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid path"))?;
20 let name_c = CString::new(name)
21 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid attr name"))?;
22
23 let flags = if options.no_dereference {
24 libc::XATTR_NOFOLLOW
25 } else {
26 0
27 };
28
29 let size = unsafe {
30 libc::getxattr(
31 path_c.as_ptr(),
32 name_c.as_ptr(),
33 std::ptr::null_mut(),
34 0,
35 0,
36 flags,
37 )
38 };
39
40 if size < 0 {
41 return Err(io::Error::last_os_error());
42 }
43
44 if size == 0 {
45 return Ok(Vec::new());
46 }
47
48 let mut buf = vec![0u8; size as usize];
49
50 let result = unsafe {
51 libc::getxattr(
52 path_c.as_ptr(),
53 name_c.as_ptr(),
54 buf.as_mut_ptr() as *mut libc::c_void,
55 size as usize,
56 0,
57 flags,
58 )
59 };
60
61 if result < 0 {
62 return Err(io::Error::last_os_error());
63 }
64
65 buf.truncate(result as usize);
66 Ok(buf)
67}
68
69#[cfg(target_os = "linux")]
70pub fn getxattr(path: &str, name: &str, options: &XattrOptions) -> io::Result<Vec<u8>> {
71 let path_c = CString::new(path)
72 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid path"))?;
73 let name_c = CString::new(name)
74 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid attr name"))?;
75
76 let size = if options.no_dereference {
77 unsafe { libc::lgetxattr(path_c.as_ptr(), name_c.as_ptr(), std::ptr::null_mut(), 0) }
78 } else {
79 unsafe { libc::getxattr(path_c.as_ptr(), name_c.as_ptr(), std::ptr::null_mut(), 0) }
80 };
81
82 if size < 0 {
83 return Err(io::Error::last_os_error());
84 }
85
86 if size == 0 {
87 return Ok(Vec::new());
88 }
89
90 let mut buf = vec![0u8; size as usize];
91
92 let result = if options.no_dereference {
93 unsafe {
94 libc::lgetxattr(
95 path_c.as_ptr(),
96 name_c.as_ptr(),
97 buf.as_mut_ptr() as *mut libc::c_void,
98 size as usize,
99 )
100 }
101 } else {
102 unsafe {
103 libc::getxattr(
104 path_c.as_ptr(),
105 name_c.as_ptr(),
106 buf.as_mut_ptr() as *mut libc::c_void,
107 size as usize,
108 )
109 }
110 };
111
112 if result < 0 {
113 return Err(io::Error::last_os_error());
114 }
115
116 buf.truncate(result as usize);
117 Ok(buf)
118}
119
120#[cfg(not(any(target_os = "macos", target_os = "linux")))]
121pub fn getxattr(_path: &str, _name: &str, _options: &XattrOptions) -> io::Result<Vec<u8>> {
122 Err(io::Error::new(
123 io::ErrorKind::Unsupported,
124 "xattr not supported",
125 ))
126}
127
128#[cfg(target_os = "macos")]
130pub fn setxattr(path: &str, name: &str, value: &[u8], options: &XattrOptions) -> io::Result<()> {
131 let path_c = CString::new(path)
132 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid path"))?;
133 let name_c = CString::new(name)
134 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid attr name"))?;
135
136 let flags = if options.no_dereference {
137 libc::XATTR_NOFOLLOW
138 } else {
139 0
140 };
141
142 let result = unsafe {
143 libc::setxattr(
144 path_c.as_ptr(),
145 name_c.as_ptr(),
146 value.as_ptr() as *const libc::c_void,
147 value.len(),
148 0,
149 flags,
150 )
151 };
152
153 if result < 0 {
154 return Err(io::Error::last_os_error());
155 }
156
157 Ok(())
158}
159
160#[cfg(target_os = "linux")]
161pub fn setxattr(path: &str, name: &str, value: &[u8], options: &XattrOptions) -> io::Result<()> {
162 let path_c = CString::new(path)
163 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid path"))?;
164 let name_c = CString::new(name)
165 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid attr name"))?;
166
167 let result = if options.no_dereference {
168 unsafe {
169 libc::lsetxattr(
170 path_c.as_ptr(),
171 name_c.as_ptr(),
172 value.as_ptr() as *const libc::c_void,
173 value.len(),
174 0,
175 )
176 }
177 } else {
178 unsafe {
179 libc::setxattr(
180 path_c.as_ptr(),
181 name_c.as_ptr(),
182 value.as_ptr() as *const libc::c_void,
183 value.len(),
184 0,
185 )
186 }
187 };
188
189 if result < 0 {
190 return Err(io::Error::last_os_error());
191 }
192
193 Ok(())
194}
195
196#[cfg(not(any(target_os = "macos", target_os = "linux")))]
197pub fn setxattr(
198 _path: &str,
199 _name: &str,
200 _value: &[u8],
201 _options: &XattrOptions,
202) -> io::Result<()> {
203 Err(io::Error::new(
204 io::ErrorKind::Unsupported,
205 "xattr not supported",
206 ))
207}
208
209#[cfg(target_os = "macos")]
211pub fn removexattr(path: &str, name: &str, options: &XattrOptions) -> io::Result<()> {
212 let path_c = CString::new(path)
213 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid path"))?;
214 let name_c = CString::new(name)
215 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid attr name"))?;
216
217 let flags = if options.no_dereference {
218 libc::XATTR_NOFOLLOW
219 } else {
220 0
221 };
222
223 let result = unsafe { libc::removexattr(path_c.as_ptr(), name_c.as_ptr(), flags) };
224
225 if result < 0 {
226 return Err(io::Error::last_os_error());
227 }
228
229 Ok(())
230}
231
232#[cfg(target_os = "linux")]
233pub fn removexattr(path: &str, name: &str, options: &XattrOptions) -> io::Result<()> {
234 let path_c = CString::new(path)
235 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid path"))?;
236 let name_c = CString::new(name)
237 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid attr name"))?;
238
239 let result = if options.no_dereference {
240 unsafe { libc::lremovexattr(path_c.as_ptr(), name_c.as_ptr()) }
241 } else {
242 unsafe { libc::removexattr(path_c.as_ptr(), name_c.as_ptr()) }
243 };
244
245 if result < 0 {
246 return Err(io::Error::last_os_error());
247 }
248
249 Ok(())
250}
251
252#[cfg(not(any(target_os = "macos", target_os = "linux")))]
253pub fn removexattr(_path: &str, _name: &str, _options: &XattrOptions) -> io::Result<()> {
254 Err(io::Error::new(
255 io::ErrorKind::Unsupported,
256 "xattr not supported",
257 ))
258}
259
260#[cfg(target_os = "macos")]
262pub fn listxattr(path: &str, options: &XattrOptions) -> io::Result<Vec<String>> {
263 let path_c = CString::new(path)
264 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid path"))?;
265
266 let flags = if options.no_dereference {
267 libc::XATTR_NOFOLLOW
268 } else {
269 0
270 };
271
272 let size = unsafe { libc::listxattr(path_c.as_ptr(), std::ptr::null_mut(), 0, flags) };
273
274 if size < 0 {
275 return Err(io::Error::last_os_error());
276 }
277
278 if size == 0 {
279 return Ok(Vec::new());
280 }
281
282 let mut buf = vec![0u8; size as usize];
283
284 let result = unsafe {
285 libc::listxattr(
286 path_c.as_ptr(),
287 buf.as_mut_ptr() as *mut libc::c_char,
288 size as usize,
289 flags,
290 )
291 };
292
293 if result < 0 {
294 return Err(io::Error::last_os_error());
295 }
296
297 buf.truncate(result as usize);
298 parse_xattr_list(&buf)
299}
300
301#[cfg(target_os = "linux")]
302pub fn listxattr(path: &str, options: &XattrOptions) -> io::Result<Vec<String>> {
303 let path_c = CString::new(path)
304 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid path"))?;
305
306 let size = if options.no_dereference {
307 unsafe { libc::llistxattr(path_c.as_ptr(), std::ptr::null_mut(), 0) }
308 } else {
309 unsafe { libc::listxattr(path_c.as_ptr(), std::ptr::null_mut(), 0) }
310 };
311
312 if size < 0 {
313 return Err(io::Error::last_os_error());
314 }
315
316 if size == 0 {
317 return Ok(Vec::new());
318 }
319
320 let mut buf = vec![0u8; size as usize];
321
322 let result = if options.no_dereference {
323 unsafe {
324 libc::llistxattr(
325 path_c.as_ptr(),
326 buf.as_mut_ptr() as *mut libc::c_char,
327 size as usize,
328 )
329 }
330 } else {
331 unsafe {
332 libc::listxattr(
333 path_c.as_ptr(),
334 buf.as_mut_ptr() as *mut libc::c_char,
335 size as usize,
336 )
337 }
338 };
339
340 if result < 0 {
341 return Err(io::Error::last_os_error());
342 }
343
344 buf.truncate(result as usize);
345 parse_xattr_list(&buf)
346}
347
348#[cfg(not(any(target_os = "macos", target_os = "linux")))]
349pub fn listxattr(_path: &str, _options: &XattrOptions) -> io::Result<Vec<String>> {
350 Err(io::Error::new(
351 io::ErrorKind::Unsupported,
352 "xattr not supported",
353 ))
354}
355
356fn parse_xattr_list(buf: &[u8]) -> io::Result<Vec<String>> {
357 let mut names = Vec::new();
358 let mut start = 0;
359
360 for (i, &byte) in buf.iter().enumerate() {
361 if byte == 0 {
362 if i > start {
363 let name = String::from_utf8_lossy(&buf[start..i]).into_owned();
364 names.push(name);
365 }
366 start = i + 1;
367 }
368 }
369
370 Ok(names)
371}
372
373pub fn builtin_zgetattr(file: &str, attr: &str, options: &XattrOptions) -> (i32, Option<String>) {
375 match getxattr(file, attr, options) {
376 Ok(value) => {
377 let s = String::from_utf8_lossy(&value).into_owned();
378 (0, Some(s))
379 }
380 Err(e) => (1, Some(format!("zgetattr: {}: {}\n", file, e))),
381 }
382}
383
384pub fn builtin_zsetattr(
386 file: &str,
387 attr: &str,
388 value: &str,
389 options: &XattrOptions,
390) -> (i32, String) {
391 match setxattr(file, attr, value.as_bytes(), options) {
392 Ok(()) => (0, String::new()),
393 Err(e) => (1, format!("zsetattr: {}: {}\n", file, e)),
394 }
395}
396
397pub fn builtin_zdelattr(file: &str, attrs: &[&str], options: &XattrOptions) -> (i32, String) {
399 for attr in attrs {
400 if let Err(e) = removexattr(file, attr, options) {
401 return (1, format!("zdelattr: {}: {}\n", file, e));
402 }
403 }
404 (0, String::new())
405}
406
407pub fn builtin_zlistattr(file: &str, options: &XattrOptions) -> (i32, Vec<String>, String) {
409 match listxattr(file, options) {
410 Ok(attrs) => (0, attrs, String::new()),
411 Err(e) => (1, Vec::new(), format!("zlistattr: {}: {}\n", file, e)),
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418
419 #[test]
420 fn test_xattr_options_default() {
421 let opts = XattrOptions::default();
422 assert!(!opts.no_dereference);
423 }
424
425 #[test]
426 fn test_parse_xattr_list_empty() {
427 let buf: &[u8] = &[];
428 let result = parse_xattr_list(buf).unwrap();
429 assert!(result.is_empty());
430 }
431
432 #[test]
433 fn test_parse_xattr_list_single() {
434 let buf = b"user.test\0";
435 let result = parse_xattr_list(buf).unwrap();
436 assert_eq!(result, vec!["user.test"]);
437 }
438
439 #[test]
440 fn test_parse_xattr_list_multiple() {
441 let buf = b"user.test1\0user.test2\0user.test3\0";
442 let result = parse_xattr_list(buf).unwrap();
443 assert_eq!(result, vec!["user.test1", "user.test2", "user.test3"]);
444 }
445
446 #[test]
447 fn test_builtin_zgetattr_nonexistent() {
448 let opts = XattrOptions::default();
449 let (status, _) = builtin_zgetattr("/nonexistent/path", "user.test", &opts);
450 assert_eq!(status, 1);
451 }
452
453 #[test]
454 fn test_builtin_zsetattr_nonexistent() {
455 let opts = XattrOptions::default();
456 let (status, _) = builtin_zsetattr("/nonexistent/path", "user.test", "value", &opts);
457 assert_eq!(status, 1);
458 }
459
460 #[test]
461 fn test_builtin_zlistattr_nonexistent() {
462 let opts = XattrOptions::default();
463 let (status, _, _) = builtin_zlistattr("/nonexistent/path", &opts);
464 assert_eq!(status, 1);
465 }
466}