1use crate::block_io::BlockDevice;
24use crate::error::{Error, Result};
25use crate::inode::Inode;
26use crate::xattr;
27
28pub const EXT4_ACL_VERSION: u32 = 0x0001;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum AclKind {
35 Access,
36 Default,
37}
38
39impl AclKind {
40 pub fn xattr_name(self) -> &'static str {
41 match self {
42 AclKind::Access => "system.posix_acl_access",
43 AclKind::Default => "system.posix_acl_default",
44 }
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50#[repr(u16)]
51pub enum AclTag {
52 UserObj = 0x01,
53 User = 0x02,
54 GroupObj = 0x04,
55 Group = 0x08,
56 Mask = 0x10,
57 Other = 0x20,
58}
59
60impl AclTag {
61 pub fn from_u16(v: u16) -> Option<Self> {
62 match v {
63 0x01 => Some(AclTag::UserObj),
64 0x02 => Some(AclTag::User),
65 0x04 => Some(AclTag::GroupObj),
66 0x08 => Some(AclTag::Group),
67 0x10 => Some(AclTag::Mask),
68 0x20 => Some(AclTag::Other),
69 _ => None,
70 }
71 }
72
73 pub fn has_id(self) -> bool {
75 matches!(self, AclTag::User | AclTag::Group)
76 }
77}
78
79pub const ACL_READ: u16 = 4;
80pub const ACL_WRITE: u16 = 2;
81pub const ACL_EXECUTE: u16 = 1;
82
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct AclEntry {
85 pub tag: AclTag,
86 pub perm: u16,
88 pub id: Option<u32>,
90}
91
92pub fn decode(buf: &[u8]) -> Result<Vec<AclEntry>> {
94 if buf.len() < 4 {
95 return Err(Error::Corrupt("acl too short for version header"));
96 }
97 let version = u32::from_le_bytes(buf[0..4].try_into().unwrap());
98 if version != EXT4_ACL_VERSION {
99 return Err(Error::Corrupt("acl version mismatch"));
100 }
101
102 let mut entries = Vec::new();
103 let mut pos = 4;
104 while pos < buf.len() {
105 if pos + 4 > buf.len() {
106 return Err(Error::Corrupt("acl entry truncated"));
107 }
108 let e_tag = u16::from_le_bytes(buf[pos..pos + 2].try_into().unwrap());
109 let e_perm = u16::from_le_bytes(buf[pos + 2..pos + 4].try_into().unwrap());
110 let tag = AclTag::from_u16(e_tag).ok_or(Error::Corrupt("acl entry has unknown tag"))?;
111
112 let (id, size) = if tag.has_id() {
113 if pos + 8 > buf.len() {
114 return Err(Error::Corrupt("acl full entry truncated"));
115 }
116 let e_id = u32::from_le_bytes(buf[pos + 4..pos + 8].try_into().unwrap());
117 (Some(e_id), 8)
118 } else {
119 (None, 4)
120 };
121
122 entries.push(AclEntry {
123 tag,
124 perm: e_perm,
125 id,
126 });
127 pos += size;
128 }
129 Ok(entries)
130}
131
132pub fn read(
138 dev: &dyn BlockDevice,
139 inode: &Inode,
140 inode_raw: &[u8],
141 inode_size: u16,
142 block_size: u32,
143 kind: AclKind,
144) -> Result<Option<Vec<AclEntry>>> {
145 let value = xattr::get(
146 dev,
147 inode,
148 inode_raw,
149 inode_size,
150 block_size,
151 kind.xattr_name(),
152 )?;
153 match value {
154 Some(bytes) => Ok(Some(decode(&bytes)?)),
155 None => Ok(None),
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 fn le32(v: u32) -> [u8; 4] {
164 v.to_le_bytes()
165 }
166 fn le16(v: u16) -> [u8; 2] {
167 v.to_le_bytes()
168 }
169
170 fn build(entries: &[(u16, u16, Option<u32>)]) -> Vec<u8> {
172 let mut v = Vec::new();
173 v.extend_from_slice(&le32(EXT4_ACL_VERSION));
174 for (tag, perm, id) in entries {
175 v.extend_from_slice(&le16(*tag));
176 v.extend_from_slice(&le16(*perm));
177 if let Some(id) = id {
178 v.extend_from_slice(&le32(*id));
179 }
180 }
181 v
182 }
183
184 #[test]
185 fn decodes_minimal_acl() {
186 let buf = build(&[(0x01, 7, None), (0x04, 5, None), (0x20, 4, None)]);
188 let entries = decode(&buf).unwrap();
189 assert_eq!(entries.len(), 3);
190 assert_eq!(
191 entries[0],
192 AclEntry {
193 tag: AclTag::UserObj,
194 perm: 7,
195 id: None
196 }
197 );
198 assert_eq!(
199 entries[1],
200 AclEntry {
201 tag: AclTag::GroupObj,
202 perm: 5,
203 id: None
204 }
205 );
206 assert_eq!(
207 entries[2],
208 AclEntry {
209 tag: AclTag::Other,
210 perm: 4,
211 id: None
212 }
213 );
214 }
215
216 #[test]
217 fn decodes_named_user_and_group() {
218 let buf = build(&[
219 (0x01, 7, None), (0x02, 6, Some(1000)), (0x04, 5, None), (0x08, 4, Some(2000)), (0x10, 7, None), (0x20, 0, None), ]);
226 let entries = decode(&buf).unwrap();
227 assert_eq!(entries.len(), 6);
228 assert_eq!(
229 entries[1],
230 AclEntry {
231 tag: AclTag::User,
232 perm: 6,
233 id: Some(1000)
234 }
235 );
236 assert_eq!(
237 entries[3],
238 AclEntry {
239 tag: AclTag::Group,
240 perm: 4,
241 id: Some(2000)
242 }
243 );
244 assert_eq!(
245 entries[4],
246 AclEntry {
247 tag: AclTag::Mask,
248 perm: 7,
249 id: None
250 }
251 );
252 }
253
254 #[test]
255 fn rejects_wrong_version() {
256 let mut buf = build(&[(0x01, 7, None)]);
257 buf[0] = 0x02; assert!(decode(&buf).is_err());
259 }
260
261 #[test]
262 fn rejects_unknown_tag() {
263 let buf = build(&[(0x99, 7, None)]);
264 assert!(decode(&buf).is_err());
265 }
266
267 #[test]
268 fn rejects_truncated_header() {
269 assert!(decode(&[0x01, 0x00]).is_err());
270 }
271
272 #[test]
273 fn rejects_truncated_full_entry() {
274 let mut v = Vec::new();
276 v.extend_from_slice(&le32(EXT4_ACL_VERSION));
277 v.extend_from_slice(&le16(0x02)); v.extend_from_slice(&le16(7));
279 assert!(decode(&v).is_err());
281 }
282
283 #[test]
284 fn xattr_names_correct() {
285 assert_eq!(AclKind::Access.xattr_name(), "system.posix_acl_access");
286 assert_eq!(AclKind::Default.xattr_name(), "system.posix_acl_default");
287 }
288
289 #[test]
290 fn tag_has_id_matches_spec() {
291 assert!(AclTag::User.has_id());
292 assert!(AclTag::Group.has_id());
293 assert!(!AclTag::UserObj.has_id());
294 assert!(!AclTag::GroupObj.has_id());
295 assert!(!AclTag::Mask.has_id());
296 assert!(!AclTag::Other.has_id());
297 }
298}