Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/ext2/src/xattr.rs
5394 views
1
// Copyright 2024 The ChromiumOS Authors
2
// Use of this source code is governed by a BSD-style license that can be
3
// found in the LICENSE file.
4
5
//! Provides utilites for extended attributes.
6
7
use std::ffi::c_char;
8
use std::ffi::CString;
9
use std::os::unix::ffi::OsStrExt;
10
use std::path::Path;
11
12
use anyhow::bail;
13
use anyhow::Context;
14
use anyhow::Result;
15
use zerocopy::FromBytes;
16
use zerocopy::Immutable;
17
use zerocopy::IntoBytes;
18
use zerocopy::KnownLayout;
19
20
use crate::inode::Inode;
21
22
fn listxattr(path: &CString) -> Result<Vec<Vec<u8>>> {
23
// SAFETY: Passing valid pointers and values.
24
let size = unsafe { libc::llistxattr(path.as_ptr(), std::ptr::null_mut(), 0) };
25
if size < 0 {
26
bail!(
27
"failed to get xattr size: {}",
28
std::io::Error::last_os_error()
29
);
30
}
31
32
if size == 0 {
33
// No extended attributes were set.
34
return Ok(vec![]);
35
}
36
37
let mut buf = vec![0 as c_char; size as usize];
38
39
// SAFETY: Passing valid pointers and values.
40
let size = unsafe { libc::llistxattr(path.as_ptr(), buf.as_mut_ptr(), buf.len()) };
41
if size < 0 {
42
bail!(
43
"failed to list of xattr: {}",
44
std::io::Error::last_os_error()
45
);
46
}
47
48
buf.pop(); // Remove null terminator
49
50
// While `c_char` is `i8` on x86_64, it's `u8` on ARM. So, disable the clippy for the cast.
51
#[cfg_attr(
52
any(target_arch = "aarch64", target_arch = "riscv64"),
53
allow(clippy::unnecessary_cast)
54
)]
55
let keys = buf
56
.split(|c| *c == 0)
57
.map(|v| v.iter().map(|c| *c as u8).collect::<Vec<_>>())
58
.collect::<Vec<Vec<_>>>();
59
60
Ok(keys)
61
}
62
63
fn lgetxattr(path: &CString, name: &CString) -> Result<Vec<u8>> {
64
// SAFETY: passing valid pointers.
65
let size = unsafe { libc::lgetxattr(path.as_ptr(), name.as_ptr(), std::ptr::null_mut(), 0) };
66
if size < 0 {
67
bail!(
68
"failed to get xattr size for {:?}: {}",
69
name,
70
std::io::Error::last_os_error()
71
);
72
}
73
let mut buf = vec![0; size as usize];
74
// SAFETY: passing valid pointers and length.
75
let size = unsafe {
76
libc::lgetxattr(
77
path.as_ptr(),
78
name.as_ptr(),
79
buf.as_mut_ptr() as *mut libc::c_void,
80
buf.len(),
81
)
82
};
83
if size < 0 {
84
bail!(
85
"failed to get xattr for {:?}: {}",
86
name,
87
std::io::Error::last_os_error()
88
);
89
}
90
91
Ok(buf)
92
}
93
94
/// Retrieves the list of pairs of a name and a value of the extended attribute of the given `path`.
95
/// If `path` is a symbolic link, it won't be followed and the value of the symlink itself is
96
/// returned.
97
/// The return values are byte arrays WITHOUT trailing NULL byte.
98
pub fn dump_xattrs(path: &Path) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
99
let mut path_vec = path.as_os_str().as_bytes().to_vec();
100
path_vec.push(0);
101
let path_str = CString::from_vec_with_nul(path_vec)?;
102
103
let keys = listxattr(&path_str).context("failed to listxattr")?;
104
105
let mut kvs = vec![];
106
for key in keys {
107
let mut key_vec = key.to_vec();
108
key_vec.push(0);
109
let name = CString::from_vec_with_nul(key_vec)?;
110
111
let buf = lgetxattr(&path_str, &name).context("failed to getxattr")?;
112
kvs.push((key.to_vec(), buf));
113
}
114
115
Ok(kvs)
116
}
117
118
/// Sets the extended attribute of the given `path` with the given `key` and `value`.
119
pub fn set_xattr(path: &Path, key: &str, value: &str) -> Result<()> {
120
let mut path_bytes = path
121
.as_os_str()
122
.as_bytes()
123
.iter()
124
.map(|i| *i as c_char)
125
.collect::<Vec<_>>();
126
path_bytes.push(0); // null terminator
127
128
// While name must be a nul-terminated string, value is not, as it can be a binary data.
129
let mut key_vec = key.bytes().collect::<Vec<_>>();
130
key_vec.push(0);
131
let name = CString::from_vec_with_nul(key_vec)?;
132
let v = value.bytes().collect::<Vec<_>>();
133
134
// SAFETY: `path_bytes` and `nam` are null-terminated byte arrays.
135
// `v` is valid data.
136
let size = unsafe {
137
libc::lsetxattr(
138
path_bytes.as_ptr(),
139
name.as_ptr(),
140
v.as_ptr() as *const libc::c_void,
141
v.len(),
142
0,
143
)
144
};
145
if size != 0 {
146
bail!(
147
"failed to set xattr for {:?}: {}",
148
path,
149
std::io::Error::last_os_error()
150
);
151
}
152
Ok(())
153
}
154
155
#[repr(C)]
156
#[derive(Default, Debug, Copy, Clone, FromBytes, Immutable, IntoBytes, KnownLayout)]
157
pub(crate) struct XattrEntry {
158
name_len: u8,
159
name_index: u8,
160
value_offs: u16,
161
value_inum: u32,
162
value_size: u32,
163
hash: u32,
164
// name[name_len] follows
165
}
166
167
impl XattrEntry {
168
/// Creates a new `XattrEntry` instance with the name as a byte sequence that follows.
169
pub(crate) fn new_with_name<'a>(
170
name: &'a [u8],
171
value: &[u8],
172
value_offs: u16,
173
) -> Result<(Self, &'a [u8])> {
174
let (name_index, key_str) = Self::split_key_prefix(name);
175
let name_len = key_str.len() as u8;
176
let value_size = value.len() as u32;
177
Ok((
178
XattrEntry {
179
name_len,
180
name_index,
181
value_offs,
182
value_inum: 0,
183
value_size,
184
hash: 0,
185
},
186
key_str,
187
))
188
}
189
190
/// Split the given xatrr key string into it's prefix's name index and the remaining part.
191
/// e.g. "user.foo" -> (1, "foo") because the key prefix "user." has index 1.
192
fn split_key_prefix(name: &[u8]) -> (u8, &[u8]) {
193
// ref. https://docs.kernel.org/filesystems/ext4/dynamic.html#attribute-name-indices
194
for (name_index, key_prefix) in [
195
(1, "user."),
196
(2, "system.posix_acl_access"),
197
(3, "system.posix_acl_default"),
198
(4, "trusted."),
199
// 5 is skipped
200
(6, "security."),
201
(7, "system."),
202
(8, "system.richacl"),
203
] {
204
let prefix_bytes = key_prefix.as_bytes();
205
if name.starts_with(prefix_bytes) {
206
return (name_index, &name[prefix_bytes.len()..]);
207
}
208
}
209
(0, name)
210
}
211
}
212
213
/// Xattr data written into Inode's inline xattr space.
214
#[derive(Default, Debug, PartialEq, Eq)]
215
pub struct InlineXattrs {
216
pub entry_table: Vec<u8>,
217
pub values: Vec<u8>,
218
}
219
220
fn align<T: Clone + Default>(mut v: Vec<T>, alignment: usize) -> Vec<T> {
221
let aligned = v.len().next_multiple_of(alignment);
222
v.extend(vec![T::default(); aligned - v.len()]);
223
v
224
}
225
226
const XATTR_HEADER_MAGIC: u32 = 0xEA020000;
227
228
impl InlineXattrs {
229
// Creates `InlineXattrs` for the given path.
230
pub fn from_path(path: &Path) -> Result<Self> {
231
let v = dump_xattrs(path).with_context(|| format!("failed to get xattr for {path:?}"))?;
232
233
// Assume all the data are in inode record.
234
let mut entry_table = vec![];
235
let mut values = vec![];
236
// Data layout of the inline Inode record is as follows.
237
//
238
// | Inode struct | header | extra region |
239
// <--------- Inode record ------------>
240
//
241
// The value `val_offset` below is an offset from the beginning of the extra region and used
242
// to indicate the place where the next xattr value will be written. While we place
243
// attribute entries from the beginning of the extra region, we place values from the end of
244
// the region. So the initial value of `val_offset` indicates the end of the extra
245
// region.
246
//
247
// See Table 5.1. at https://www.nongnu.org/ext2-doc/ext2.html#extended-attribute-layout for the more details on data layout.
248
// Although this table is for xattr in a separate block, data layout is same.
249
let mut val_offset = Inode::INODE_RECORD_SIZE
250
- std::mem::size_of::<Inode>()
251
- std::mem::size_of_val(&XATTR_HEADER_MAGIC);
252
253
entry_table.extend(XATTR_HEADER_MAGIC.to_le_bytes());
254
for (name, value) in v {
255
let aligned_val_len = value.len().next_multiple_of(4);
256
257
if entry_table.len()
258
+ values.len()
259
+ std::mem::size_of::<XattrEntry>()
260
+ aligned_val_len
261
> Inode::XATTR_AREA_SIZE
262
{
263
bail!("Xattr entry is too large");
264
}
265
266
val_offset -= aligned_val_len;
267
let (entry, name) = XattrEntry::new_with_name(&name, &value, val_offset as u16)?;
268
entry_table.extend(entry.as_bytes());
269
entry_table.extend(name);
270
entry_table = align(entry_table, 4);
271
values.push(align(value, 4));
272
}
273
let values = values.iter().rev().flatten().copied().collect::<Vec<_>>();
274
275
Ok(Self {
276
entry_table,
277
values,
278
})
279
}
280
}
281
282
#[cfg(test)]
283
pub(crate) mod tests {
284
use std::collections::BTreeMap;
285
use std::fs::File;
286
287
use tempfile::tempdir;
288
289
use super::*;
290
291
fn to_char_array(s: &str) -> Vec<u8> {
292
s.bytes().collect()
293
}
294
295
#[test]
296
fn test_attr_name_index() {
297
assert_eq!(
298
XattrEntry::split_key_prefix(b"user.foo"),
299
(1, "foo".as_bytes())
300
);
301
assert_eq!(
302
XattrEntry::split_key_prefix(b"trusted.bar"),
303
(4, "bar".as_bytes())
304
);
305
assert_eq!(
306
XattrEntry::split_key_prefix(b"security.abcdefgh"),
307
(6, "abcdefgh".as_bytes())
308
);
309
310
// "system."-prefix
311
assert_eq!(
312
XattrEntry::split_key_prefix(b"system.posix_acl_access"),
313
(2, "".as_bytes())
314
);
315
assert_eq!(
316
XattrEntry::split_key_prefix(b"system.posix_acl_default"),
317
(3, "".as_bytes())
318
);
319
assert_eq!(
320
XattrEntry::split_key_prefix(b"system.abcdefgh"),
321
(7, "abcdefgh".as_bytes())
322
);
323
324
// unmatched prefix
325
assert_eq!(
326
XattrEntry::split_key_prefix(b"invalid.foo"),
327
(0, "invalid.foo".as_bytes())
328
);
329
}
330
331
#[test]
332
fn test_get_xattr_empty() {
333
let td = tempdir().unwrap();
334
let test_path = td.path().join("test.txt");
335
336
// Don't set any extended attributes.
337
File::create(&test_path).unwrap();
338
339
let kvs = dump_xattrs(&test_path).unwrap();
340
assert_eq!(kvs.len(), 0);
341
}
342
343
#[test]
344
fn test_inline_xattr_from_path() {
345
let td = tempdir().unwrap();
346
let test_path = td.path().join("test.txt");
347
File::create(&test_path).unwrap();
348
349
let key = "key";
350
let xattr_key = &format!("user.{key}");
351
let value = "value";
352
353
set_xattr(&test_path, xattr_key, value).unwrap();
354
355
let xattrs = InlineXattrs::from_path(&test_path).unwrap();
356
let entry = XattrEntry {
357
name_len: key.len() as u8,
358
name_index: 1,
359
value_offs: (Inode::INODE_RECORD_SIZE
360
- std::mem::size_of::<Inode>()
361
- std::mem::size_of_val(&XATTR_HEADER_MAGIC)
362
- value.len().next_multiple_of(4)) as u16,
363
value_size: value.len() as u32,
364
value_inum: 0,
365
..Default::default()
366
};
367
assert_eq!(
368
xattrs.entry_table,
369
align(
370
[
371
XATTR_HEADER_MAGIC.to_le_bytes().to_vec(),
372
entry.as_bytes().to_vec(),
373
key.as_bytes().to_vec(),
374
]
375
.concat(),
376
4
377
),
378
);
379
assert_eq!(xattrs.values, align(value.as_bytes().to_vec(), 4),);
380
}
381
382
#[test]
383
fn test_too_many_values_for_inline_xattr() {
384
let td = tempdir().unwrap();
385
let test_path = td.path().join("test.txt");
386
File::create(&test_path).unwrap();
387
388
// Prepare 10 pairs of xattributes, which will not fit inline space.
389
let mut xattr_pairs = vec![];
390
for i in 0..10 {
391
xattr_pairs.push((format!("user.foo{i}"), "bar"));
392
}
393
394
for (key, value) in &xattr_pairs {
395
set_xattr(&test_path, key, value).unwrap();
396
}
397
398
// Must fail
399
InlineXattrs::from_path(&test_path).unwrap_err();
400
}
401
402
#[test]
403
fn test_get_xattr() {
404
let td = tempdir().unwrap();
405
let test_path = td.path().join("test.txt");
406
File::create(&test_path).unwrap();
407
408
let xattr_pairs = vec![
409
("user.foo", "bar"),
410
("user.hash", "09f7e02f1290be211da707a266f153b3"),
411
("user.empty", ""),
412
];
413
414
for (key, value) in &xattr_pairs {
415
set_xattr(&test_path, key, value).unwrap();
416
}
417
418
let kvs = dump_xattrs(&test_path).unwrap();
419
assert_eq!(kvs.len(), xattr_pairs.len());
420
421
let xattr_map: BTreeMap<Vec<u8>, Vec<u8>> = kvs.into_iter().collect();
422
423
for (orig_k, orig_v) in xattr_pairs {
424
let k = to_char_array(orig_k);
425
let v = to_char_array(orig_v);
426
let got = xattr_map.get(&k).unwrap();
427
assert_eq!(&v, got);
428
}
429
}
430
431
#[test]
432
fn test_get_xattr_symlink() {
433
let td = tempdir().unwrap();
434
435
// Set xattr on test.txt.
436
let test_path = td.path().join("test.txt");
437
File::create(&test_path).unwrap();
438
set_xattr(&test_path, "user.name", "user.test.txt").unwrap();
439
440
// Create a symlink to test.txt.
441
let symlink_path = td.path().join("symlink");
442
std::os::unix::fs::symlink(&test_path, &symlink_path).unwrap();
443
444
// dump_xattrs shouldn't follow a symlink.
445
let kvs = dump_xattrs(&symlink_path).unwrap();
446
assert_eq!(kvs, vec![]);
447
}
448
}
449
450