1use crate::vfs::{
2 validate_path, VfsError, VfsResult, VirtualDirEntry, VirtualFileSystem, VirtualStat,
3 VirtualUtimeSpec,
4};
5use std::collections::{BTreeMap, HashMap};
6use std::error::Error;
7use std::fmt;
8use std::path::Path;
9use std::sync::Arc;
10
11const IMMUTABLE_XATTR: &str = "user.agentos.immutable";
12
13pub type FsPermissionCheck = Arc<dyn Fn(&FsAccessRequest) -> PermissionDecision + Send + Sync>;
14pub type NetworkPermissionCheck =
15 Arc<dyn Fn(&NetworkAccessRequest) -> PermissionDecision + Send + Sync>;
16pub type CommandPermissionCheck =
17 Arc<dyn Fn(&CommandAccessRequest) -> PermissionDecision + Send + Sync>;
18pub type EnvironmentPermissionCheck =
19 Arc<dyn Fn(&EnvAccessRequest) -> PermissionDecision + Send + Sync>;
20
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct PermissionDecision {
23 pub allow: bool,
24 pub reason: Option<String>,
25}
26
27impl PermissionDecision {
28 pub fn allow() -> Self {
29 Self {
30 allow: true,
31 reason: None,
32 }
33 }
34
35 pub fn deny(reason: impl Into<String>) -> Self {
36 Self {
37 allow: false,
38 reason: Some(reason.into()),
39 }
40 }
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct PermissionError {
45 code: &'static str,
46 message: String,
47}
48
49impl PermissionError {
50 pub fn code(&self) -> &'static str {
51 self.code
52 }
53
54 fn access_denied(subject: impl Into<String>, reason: Option<&str>) -> Self {
55 let subject = subject.into();
56 let message = match reason {
57 Some(reason) => format!("permission denied, {subject}: {reason}"),
58 None => format!("permission denied, {subject}"),
59 };
60
61 Self {
62 code: "EACCES",
63 message,
64 }
65 }
66}
67
68impl fmt::Display for PermissionError {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 write!(f, "{}: {}", self.code, self.message)
71 }
72}
73
74impl Error for PermissionError {}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum FsOperation {
78 Read,
79 Write,
80 Mkdir,
81 CreateDir,
82 ReadDir,
83 Stat,
84 Remove,
85 Rename,
86 Exists,
87 Symlink,
88 ReadLink,
89 Link,
90 Chmod,
91 Chown,
92 Utimes,
93 Truncate,
94 MountSensitive,
95}
96
97impl FsOperation {
98 fn as_str(self) -> &'static str {
99 match self {
100 Self::Read => "read",
101 Self::Write => "write",
102 Self::Mkdir => "mkdir",
103 Self::CreateDir => "createDir",
104 Self::ReadDir => "readdir",
105 Self::Stat => "stat",
106 Self::Remove => "rm",
107 Self::Rename => "rename",
108 Self::Exists => "exists",
109 Self::Symlink => "symlink",
110 Self::ReadLink => "readlink",
111 Self::Link => "link",
112 Self::Chmod => "chmod",
113 Self::Chown => "chown",
114 Self::Utimes => "utimes",
115 Self::Truncate => "truncate",
116 Self::MountSensitive => "mount",
117 }
118 }
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct FsAccessRequest {
123 pub vm_id: String,
124 pub op: FsOperation,
125 pub path: String,
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub enum NetworkOperation {
130 Fetch,
131 Http,
132 Dns,
133 Listen,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq)]
137pub struct NetworkAccessRequest {
138 pub vm_id: String,
139 pub op: NetworkOperation,
140 pub resource: String,
141}
142
143#[derive(Debug, Clone, PartialEq, Eq)]
144pub struct CommandAccessRequest {
145 pub vm_id: String,
146 pub command: String,
147 pub args: Vec<String>,
148 pub cwd: Option<String>,
149 pub env: BTreeMap<String, String>,
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub enum EnvironmentOperation {
154 Read,
155 Write,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq)]
159pub struct EnvAccessRequest {
160 pub vm_id: String,
161 pub op: EnvironmentOperation,
162 pub key: String,
163 pub value: Option<String>,
164}
165
166#[derive(Clone, Default)]
167pub struct Permissions {
168 pub filesystem: Option<FsPermissionCheck>,
169 pub filesystem_unrestricted: bool,
175 pub network: Option<NetworkPermissionCheck>,
176 pub child_process: Option<CommandPermissionCheck>,
177 pub environment: Option<EnvironmentPermissionCheck>,
178}
179
180impl Permissions {
181 pub fn allow_all() -> Self {
182 Self {
183 filesystem: Some(Arc::new(|_: &FsAccessRequest| PermissionDecision::allow())),
184 filesystem_unrestricted: true,
185 network: Some(Arc::new(|_: &NetworkAccessRequest| {
186 PermissionDecision::allow()
187 })),
188 child_process: Some(Arc::new(|_: &CommandAccessRequest| {
189 PermissionDecision::allow()
190 })),
191 environment: Some(Arc::new(|_: &EnvAccessRequest| PermissionDecision::allow())),
192 }
193 }
194}
195
196pub fn permission_glob_matches(pattern: &str, value: &str) -> bool {
197 fn matches(
198 pattern: &[u8],
199 value: &[u8],
200 pattern_index: usize,
201 value_index: usize,
202 memo: &mut HashMap<(usize, usize), bool>,
203 ) -> bool {
204 if let Some(result) = memo.get(&(pattern_index, value_index)) {
205 return *result;
206 }
207
208 let result = if pattern_index == pattern.len() {
209 value_index == value.len()
210 } else {
211 match pattern[pattern_index] {
212 b'?' => {
213 value_index < value.len()
214 && value[value_index] != b'/'
215 && matches(pattern, value, pattern_index + 1, value_index + 1, memo)
216 }
217 b'*' => {
218 let mut next_pattern_index = pattern_index;
219 while next_pattern_index < pattern.len() && pattern[next_pattern_index] == b'*'
220 {
221 next_pattern_index += 1;
222 }
223
224 if matches(pattern, value, next_pattern_index, value_index, memo) {
225 true
226 } else {
227 let crosses_separators = next_pattern_index - pattern_index > 1;
228 let mut next_value_index = value_index;
229 while next_value_index < value.len()
230 && (crosses_separators || value[next_value_index] != b'/')
231 {
232 next_value_index += 1;
233 if matches(pattern, value, next_pattern_index, next_value_index, memo) {
234 return true;
235 }
236 }
237 false
238 }
239 }
240 expected => {
241 value_index < value.len()
242 && expected == value[value_index]
243 && matches(pattern, value, pattern_index + 1, value_index + 1, memo)
244 }
245 }
246 };
247
248 memo.insert((pattern_index, value_index), result);
249 result
250 }
251
252 matches(
253 pattern.as_bytes(),
254 value.as_bytes(),
255 0,
256 0,
257 &mut HashMap::new(),
258 )
259}
260
261pub fn filter_env(
262 vm_id: &str,
263 env: &BTreeMap<String, String>,
264 permissions: &Permissions,
265) -> BTreeMap<String, String> {
266 let Some(check) = permissions.environment.as_ref() else {
267 return BTreeMap::new();
268 };
269
270 env.iter()
271 .filter_map(|(key, value)| {
272 let request = EnvAccessRequest {
273 vm_id: vm_id.to_owned(),
274 op: EnvironmentOperation::Read,
275 key: key.clone(),
276 value: Some(value.clone()),
277 };
278 let decision = check(&request);
279 decision.allow.then(|| (key.clone(), value.clone()))
280 })
281 .collect()
282}
283
284pub fn check_command_execution(
285 vm_id: &str,
286 permissions: &Permissions,
287 command: &str,
288 args: &[String],
289 cwd: Option<&str>,
290 env: &BTreeMap<String, String>,
291) -> Result<(), PermissionError> {
292 let Some(check) = permissions.child_process.as_ref() else {
293 return Err(PermissionError::access_denied(
294 format!("spawn '{command}'"),
295 None,
296 ));
297 };
298
299 let request = CommandAccessRequest {
300 vm_id: vm_id.to_owned(),
301 command: command.to_owned(),
302 args: args.to_vec(),
303 cwd: cwd.map(ToOwned::to_owned),
304 env: env.clone(),
305 };
306 let decision = check(&request);
307 if decision.allow {
308 Ok(())
309 } else {
310 Err(PermissionError::access_denied(
311 format!("spawn '{command}'"),
312 decision.reason.as_deref(),
313 ))
314 }
315}
316
317pub fn check_network_access(
318 vm_id: &str,
319 permissions: &Permissions,
320 op: NetworkOperation,
321 resource: &str,
322) -> Result<(), PermissionError> {
323 let Some(check) = permissions.network.as_ref() else {
324 return Err(PermissionError::access_denied(resource, None));
325 };
326
327 let request = NetworkAccessRequest {
328 vm_id: vm_id.to_owned(),
329 op,
330 resource: resource.to_owned(),
331 };
332 let decision = check(&request);
333 if decision.allow {
334 Ok(())
335 } else {
336 Err(PermissionError::access_denied(
337 resource,
338 decision.reason.as_deref(),
339 ))
340 }
341}
342
343#[derive(Clone)]
344pub struct PermissionedFileSystem<F> {
345 inner: F,
346 vm_id: String,
347 permissions: Permissions,
348}
349
350impl<F> PermissionedFileSystem<F> {
351 pub fn new(inner: F, vm_id: impl Into<String>, permissions: Permissions) -> Self {
352 Self {
353 inner,
354 vm_id: vm_id.into(),
355 permissions,
356 }
357 }
358
359 pub fn into_inner(self) -> F {
360 self.inner
361 }
362
363 pub fn inner(&self) -> &F {
364 &self.inner
365 }
366
367 pub fn inner_mut(&mut self) -> &mut F {
368 &mut self.inner
369 }
370
371 pub fn set_permissions(&mut self, permissions: Permissions) {
372 self.permissions = permissions;
373 }
374
375 fn check(&self, op: FsOperation, path: &str) -> VfsResult<()> {
376 validate_path(path)?;
377 if crate::device_layer::is_standard_device_path(path) {
383 return Ok(());
384 }
385 let Some(check) = self.permissions.filesystem.as_ref() else {
386 return Err(VfsError::access_denied(op.as_str(), path, None));
387 };
388
389 let request = FsAccessRequest {
390 vm_id: self.vm_id.clone(),
391 op,
392 path: path.to_owned(),
393 };
394 let decision = check(&request);
395 if decision.allow {
396 Ok(())
397 } else {
398 Err(VfsError::access_denied(
399 op.as_str(),
400 path,
401 decision.reason.as_deref(),
402 ))
403 }
404 }
405}
406
407impl<F: VirtualFileSystem> PermissionedFileSystem<F> {
408 fn check_not_immutable(&mut self, path: &str, op: &'static str) -> VfsResult<()> {
409 self.check_not_immutable_with_follow(path, op, true)
410 }
411
412 fn check_entry_not_immutable(&mut self, path: &str, op: &'static str) -> VfsResult<()> {
413 self.check_not_immutable_with_follow(path, op, false)
414 }
415
416 fn check_not_immutable_with_follow(
417 &mut self,
418 path: &str,
419 op: &'static str,
420 follow_symlinks: bool,
421 ) -> VfsResult<()> {
422 match self.inner.get_xattr(path, IMMUTABLE_XATTR, follow_symlinks) {
423 Ok(value) if value == b"1" => Err(VfsError::permission_denied(op, path)),
424 Ok(_) => Ok(()),
425 Err(error)
426 if matches!(
427 error.code(),
428 "ENODATA" | "ENOATTR" | "ENOENT" | "EOPNOTSUPP"
429 ) =>
430 {
431 Ok(())
432 }
433 Err(error) => Err(error),
434 }
435 }
436
437 fn resolved_existing_path(&self, path: &str) -> VfsResult<String> {
438 if self.permissions.filesystem_unrestricted {
439 validate_path(path)?;
440 return Ok(crate::vfs::normalize_path(path));
441 }
442 self.inner.realpath(path)
443 }
444
445 fn resolved_destination_path(&self, path: &str) -> VfsResult<String> {
446 if self.permissions.filesystem_unrestricted {
447 validate_path(path)?;
448 return Ok(crate::vfs::normalize_path(path));
449 }
450 let normalized = crate::vfs::normalize_path(path);
451 if normalized == "/" {
452 return Ok(normalized);
453 }
454
455 let parent = Path::new(&normalized)
456 .parent()
457 .unwrap_or_else(|| Path::new("/"))
458 .to_string_lossy()
459 .into_owned();
460 let basename = Path::new(&normalized)
461 .file_name()
462 .map(|value| value.to_string_lossy().into_owned())
463 .unwrap_or_default();
464
465 let mut candidate = parent;
466 let mut unresolved_segments = Vec::new();
467
468 let resolved_parent = loop {
469 match self.inner.realpath(&candidate) {
470 Ok(resolved) => break resolved,
471 Err(error) if matches!(error.code(), "ENOENT" | "ENOTDIR") => {
472 if candidate == "/" {
473 break String::from("/");
474 }
475 let candidate_path = Path::new(&candidate);
476 if let Some(segment) = candidate_path.file_name() {
477 unresolved_segments.push(segment.to_string_lossy().into_owned());
478 }
479 candidate = candidate_path
480 .parent()
481 .unwrap_or_else(|| Path::new("/"))
482 .to_string_lossy()
483 .into_owned();
484 }
485 Err(error) => return Err(error),
486 }
487 };
488
489 let mut resolved = resolved_parent;
490 for segment in unresolved_segments.iter().rev() {
491 if resolved == "/" {
492 resolved = format!("/{segment}");
493 } else {
494 resolved = format!("{resolved}/{segment}");
495 }
496 }
497
498 if resolved == "/" {
499 Ok(format!("/{basename}"))
500 } else {
501 Ok(format!("{resolved}/{basename}"))
502 }
503 }
504
505 fn permission_subject(&self, op: FsOperation, path: &str) -> VfsResult<String> {
506 validate_path(path)?;
507 match op {
508 FsOperation::Read
509 | FsOperation::ReadDir
510 | FsOperation::Stat
511 | FsOperation::ReadLink
512 | FsOperation::Chmod
513 | FsOperation::Chown
514 | FsOperation::Utimes
515 | FsOperation::Truncate => self.resolved_existing_path(path),
516 FsOperation::Exists | FsOperation::Write => self
517 .resolved_existing_path(path)
518 .or_else(|_| self.resolved_destination_path(path)),
519 FsOperation::Mkdir
520 | FsOperation::CreateDir
521 | FsOperation::Rename
522 | FsOperation::Symlink
523 | FsOperation::Link
524 | FsOperation::MountSensitive
525 | FsOperation::Remove => self.resolved_destination_path(path),
526 }
527 }
528
529 fn check_subject(&self, op: FsOperation, path: &str) -> VfsResult<()> {
530 let subject = self.permission_subject(op, path)?;
531 self.check(op, &subject)
532 }
533
534 fn check_existing_subject(&self, op: FsOperation, path: &str) -> VfsResult<()> {
535 validate_path(path)?;
536 let subject = self.resolved_existing_path(path)?;
537 self.check(op, &subject)
538 }
539
540 fn check_destination_subject(&self, op: FsOperation, path: &str) -> VfsResult<()> {
541 validate_path(path)?;
542 let subject = self.resolved_destination_path(path)?;
543 self.check(op, &subject)
544 }
545
546 pub fn check_path(&self, op: FsOperation, path: &str) -> VfsResult<()> {
547 self.check_subject(op, path)
548 }
549
550 pub fn check_virtual_path(&self, op: FsOperation, path: &str) -> VfsResult<()> {
551 self.check(op, path)
552 }
553
554 pub fn exists(&self, path: &str) -> VfsResult<bool> {
555 if let Err(error) = self.check_subject(FsOperation::Exists, path) {
556 if matches!(error.code(), "EACCES" | "ENOENT" | "ENOTDIR" | "ELOOP") {
557 return Ok(false);
558 }
559 return Err(error);
560 }
561 Ok(self.inner.exists(path))
562 }
563}
564
565impl<F: VirtualFileSystem> VirtualFileSystem for PermissionedFileSystem<F> {
566 fn read_file(&mut self, path: &str) -> VfsResult<Vec<u8>> {
567 self.check_subject(FsOperation::Read, path)?;
568 self.inner.read_file(path)
569 }
570
571 fn read_dir(&mut self, path: &str) -> VfsResult<Vec<String>> {
572 self.check_subject(FsOperation::ReadDir, path)?;
573 self.inner.read_dir(path)
574 }
575
576 fn read_dir_limited(&mut self, path: &str, max_entries: usize) -> VfsResult<Vec<String>> {
577 self.check_subject(FsOperation::ReadDir, path)?;
578 self.inner.read_dir_limited(path, max_entries)
579 }
580
581 fn read_dir_with_types(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>> {
582 self.check_subject(FsOperation::ReadDir, path)?;
583 self.inner.read_dir_with_types(path)
584 }
585
586 fn write_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<()> {
587 self.check_subject(FsOperation::Write, path)?;
588 self.check_not_immutable(path, "write")?;
589 self.inner.write_file(path, content)
590 }
591
592 fn create_file_exclusive(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<()> {
593 self.check_subject(FsOperation::Write, path)?;
594 self.inner.create_file_exclusive(path, content)
595 }
596
597 fn append_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<u64> {
598 self.check_subject(FsOperation::Write, path)?;
599 self.check_not_immutable(path, "write")?;
600 self.inner.append_file(path, content)
601 }
602
603 fn create_dir(&mut self, path: &str) -> VfsResult<()> {
604 self.check_subject(FsOperation::CreateDir, path)?;
605 self.inner.create_dir(path)
606 }
607
608 fn mkdir(&mut self, path: &str, recursive: bool) -> VfsResult<()> {
609 self.check_subject(FsOperation::Mkdir, path)?;
610 self.inner.mkdir(path, recursive)
611 }
612
613 fn mknod(&mut self, path: &str, mode: u32, rdev: u64) -> VfsResult<()> {
614 self.check_subject(FsOperation::Write, path)?;
615 self.inner.mknod(path, mode, rdev)
616 }
617
618 fn exists(&self, path: &str) -> bool {
619 PermissionedFileSystem::exists(self, path).unwrap_or(false)
620 }
621
622 fn stat(&mut self, path: &str) -> VfsResult<VirtualStat> {
623 self.check_subject(FsOperation::Stat, path)
624 .map_err(|error| {
625 VfsError::new(
626 error.code(),
627 format!("permission path resolution for stat '{path}' failed: {error}"),
628 )
629 })?;
630 self.inner.stat(path).map_err(|error| {
631 VfsError::new(
632 error.code(),
633 format!("storage stat for '{path}' failed: {error}"),
634 )
635 })
636 }
637
638 fn remove_file(&mut self, path: &str) -> VfsResult<()> {
639 self.check_subject(FsOperation::Remove, path)?;
640 self.check_entry_not_immutable(path, "unlink")?;
641 self.inner.remove_file(path)
642 }
643
644 fn remove_dir(&mut self, path: &str) -> VfsResult<()> {
645 self.check_subject(FsOperation::Remove, path)?;
646 self.check_entry_not_immutable(path, "rmdir")?;
647 self.inner.remove_dir(path)
648 }
649
650 fn rename(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
651 self.check_subject(FsOperation::Rename, old_path)?;
652 self.check_subject(FsOperation::Rename, new_path)?;
653 self.check_entry_not_immutable(old_path, "rename")?;
654 self.check_entry_not_immutable(new_path, "rename")?;
655 self.inner.rename(old_path, new_path)
656 }
657
658 fn realpath(&self, path: &str) -> VfsResult<String> {
659 self.check_subject(FsOperation::Read, path)?;
660 self.inner.realpath(path)
661 }
662
663 fn symlink(&mut self, target: &str, link_path: &str) -> VfsResult<()> {
664 self.check_subject(FsOperation::Symlink, link_path)?;
665 self.inner.symlink(target, link_path)
666 }
667
668 fn read_link(&self, path: &str) -> VfsResult<String> {
669 validate_path(path)?;
674 let subject = self.resolved_destination_path(path)?;
675 self.check(FsOperation::ReadLink, &subject)?;
676 self.inner.read_link(path)
677 }
678
679 fn lstat(&self, path: &str) -> VfsResult<VirtualStat> {
680 validate_path(path)?;
684 let subject = self.resolved_destination_path(path)?;
685 self.check(FsOperation::Stat, &subject)?;
686 self.inner.lstat(path)
687 }
688
689 fn link(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
690 self.check_existing_subject(FsOperation::Link, old_path)?;
691 self.check_destination_subject(FsOperation::Link, new_path)?;
692 self.check_not_immutable(old_path, "link")?;
693 self.inner.link(old_path, new_path)
694 }
695
696 fn chmod(&mut self, path: &str, mode: u32) -> VfsResult<()> {
697 self.check_subject(FsOperation::Chmod, path)?;
698 self.check_not_immutable(path, "chmod")?;
699 self.inner.chmod(path, mode)
700 }
701
702 fn chown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
703 self.check_subject(FsOperation::Chown, path)?;
704 self.check_not_immutable(path, "chown")?;
705 self.inner.chown(path, uid, gid)
706 }
707
708 fn chown_spec(
709 &mut self,
710 path: &str,
711 uid: u32,
712 gid: u32,
713 follow_symlinks: bool,
714 ) -> VfsResult<()> {
715 if follow_symlinks {
716 self.check_subject(FsOperation::Chown, path)?;
717 } else {
718 validate_path(path)?;
719 let subject = self.resolved_destination_path(path)?;
720 self.check(FsOperation::Chown, &subject)?;
721 }
722 self.inner.chown_spec(path, uid, gid, follow_symlinks)
723 }
724
725 fn lchown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
726 self.chown_spec(path, uid, gid, false)
727 }
728
729 fn get_xattr(&mut self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<Vec<u8>> {
730 if follow_symlinks {
731 self.check_subject(FsOperation::Read, path)?;
732 } else {
733 validate_path(path)?;
734 let subject = self.resolved_destination_path(path)?;
735 self.check(FsOperation::Read, &subject)?;
736 }
737 self.inner.get_xattr(path, name, follow_symlinks)
738 }
739
740 fn list_xattrs(&mut self, path: &str, follow_symlinks: bool) -> VfsResult<Vec<String>> {
741 if follow_symlinks {
742 self.check_subject(FsOperation::Read, path)?;
743 } else {
744 validate_path(path)?;
745 let subject = self.resolved_destination_path(path)?;
746 self.check(FsOperation::Read, &subject)?;
747 }
748 self.inner.list_xattrs(path, follow_symlinks)
749 }
750
751 fn set_xattr(
752 &mut self,
753 path: &str,
754 name: &str,
755 value: Vec<u8>,
756 flags: u32,
757 follow_symlinks: bool,
758 ) -> VfsResult<()> {
759 if follow_symlinks {
760 self.check_subject(FsOperation::Write, path)?;
761 } else {
762 validate_path(path)?;
763 let subject = self.resolved_destination_path(path)?;
764 self.check(FsOperation::Write, &subject)?;
765 }
766 self.check_not_immutable(path, "setxattr")?;
767 self.inner
768 .set_xattr(path, name, value, flags, follow_symlinks)
769 }
770
771 fn remove_xattr(&mut self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<()> {
772 if follow_symlinks {
773 self.check_subject(FsOperation::Write, path)?;
774 } else {
775 validate_path(path)?;
776 let subject = self.resolved_destination_path(path)?;
777 self.check(FsOperation::Write, &subject)?;
778 }
779 if name != IMMUTABLE_XATTR {
780 self.check_not_immutable(path, "removexattr")?;
781 }
782 self.inner.remove_xattr(path, name, follow_symlinks)
783 }
784
785 fn utimes(&mut self, path: &str, atime_ms: u64, mtime_ms: u64) -> VfsResult<()> {
786 self.check_subject(FsOperation::Utimes, path)?;
787 self.check_not_immutable(path, "utimes")?;
788 self.inner.utimes(path, atime_ms, mtime_ms)
789 }
790
791 fn utimes_spec(
792 &mut self,
793 path: &str,
794 atime: VirtualUtimeSpec,
795 mtime: VirtualUtimeSpec,
796 follow_symlinks: bool,
797 ) -> VfsResult<()> {
798 self.check_subject(FsOperation::Utimes, path)?;
799 self.check_not_immutable(path, "utimes")?;
800 self.inner.utimes_spec(path, atime, mtime, follow_symlinks)
801 }
802
803 fn truncate(&mut self, path: &str, length: u64) -> VfsResult<()> {
804 self.check_subject(FsOperation::Truncate, path)?;
805 self.check_not_immutable(path, "truncate")?;
806 self.inner.truncate(path, length)
807 }
808
809 fn sync(&mut self, path: &str) -> VfsResult<()> {
810 self.inner.sync(path)
811 }
812
813 fn allocate(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
814 self.check_subject(FsOperation::Write, path)?;
815 self.check_not_immutable(path, "fallocate")?;
816 self.inner.allocate(path, offset, length)
817 }
818
819 fn insert_range(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
820 self.check_subject(FsOperation::Write, path)?;
821 self.check_not_immutable(path, "fallocate")?;
822 self.inner.insert_range(path, offset, length)
823 }
824
825 fn collapse_range(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
826 self.check_subject(FsOperation::Write, path)?;
827 self.check_not_immutable(path, "fallocate")?;
828 self.inner.collapse_range(path, offset, length)
829 }
830
831 fn zero_range(
832 &mut self,
833 path: &str,
834 offset: u64,
835 length: u64,
836 keep_size: bool,
837 ) -> VfsResult<()> {
838 self.check_subject(FsOperation::Write, path)?;
839 self.check_not_immutable(path, "fallocate")?;
840 self.inner.zero_range(path, offset, length, keep_size)
841 }
842
843 fn punch_hole(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
844 self.check_subject(FsOperation::Write, path)?;
845 self.check_not_immutable(path, "fallocate")?;
846 self.inner.punch_hole(path, offset, length)
847 }
848
849 fn allocated_ranges(&mut self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
850 self.check_subject(FsOperation::Read, path)?;
851 self.inner.allocated_ranges(path)
852 }
853
854 fn unwritten_ranges(&mut self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
855 self.check_subject(FsOperation::Read, path)?;
856 self.inner.unwritten_ranges(path)
857 }
858
859 fn pread(&mut self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>> {
860 self.check_subject(FsOperation::Read, path)?;
861 self.inner.pread(path, offset, length)
862 }
863
864 fn pwrite(&mut self, path: &str, content: impl Into<Vec<u8>>, offset: u64) -> VfsResult<()> {
865 self.check_subject(FsOperation::Write, path)?;
866 self.check_not_immutable(path, "write")?;
867 self.inner.pwrite(path, content, offset)
868 }
869}