1use crate::security;
6use crate::task::{CurrentTask, MountsWriteToken};
7use crate::vfs::{ActiveNamespaceNode, CheckAccessReason, Namespace, NamespaceNode};
8use starnix_logging::log_trace;
9use starnix_sync::{FileOpsCore, LockDepRwLock, LockEqualOrBefore, Locked};
10use starnix_uapi::auth::CAP_SYS_CHROOT;
11use starnix_uapi::errno;
12use starnix_uapi::errors::Errno;
13use starnix_uapi::file_mode::{Access, FileMode};
14use std::sync::Arc;
15
16#[derive(Debug, Clone)]
20struct FsContextState {
21 namespace: Arc<Namespace>,
25
26 root: ActiveNamespaceNode,
31
32 cwd: ActiveNamespaceNode,
34
35 umask: FileMode,
37}
38
39impl FsContextState {
40 fn set_namespace(
41 &mut self,
42 new_ns: Arc<Namespace>,
43 mounts_guard: &MountsWriteToken,
44 ) -> Result<(), Errno> {
45 log_trace!("updating namespace");
46 let new_root = Namespace::translate_node(self.root.to_passive(), &new_ns, mounts_guard)
47 .ok_or_else(|| errno!(EINVAL))?;
48 let new_cwd = Namespace::translate_node(self.cwd.to_passive(), &new_ns, mounts_guard)
49 .ok_or_else(|| errno!(EINVAL))?;
50
51 self.root = new_root.into_active();
53 self.cwd = new_cwd.into_active();
54 self.namespace = new_ns;
55 log_trace!("namespace update succeeded");
56 Ok(())
57 }
58}
59
60#[derive(Debug)]
65pub struct FsContext {
66 state: LockDepRwLock<FsContextState, starnix_sync::FsContextStateLock>,
67}
68
69impl FsContext {
70 pub fn new(namespace: Arc<Namespace>) -> Arc<FsContext> {
75 let root = namespace.root();
76 Arc::new(FsContext {
77 state: FsContextState {
78 namespace,
79 root: root.clone().into_active(),
80 cwd: root.into_active(),
81 umask: FileMode::DEFAULT_UMASK,
82 }
83 .into(),
84 })
85 }
86
87 pub fn fork(&self) -> Arc<FsContext> {
88 Arc::new(FsContext { state: self.state.read().clone().into() })
94 }
95
96 pub fn cwd(&self) -> NamespaceNode {
98 let state = self.state.read();
99 state.cwd.to_passive()
100 }
101
102 pub fn root(&self) -> NamespaceNode {
104 let state = self.state.read();
105 state.root.to_passive()
106 }
107
108 pub fn chdir<L>(
110 &self,
111 locked: &mut Locked<L>,
112 current_task: &CurrentTask,
113 name: NamespaceNode,
114 ) -> Result<(), Errno>
115 where
116 L: LockEqualOrBefore<FileOpsCore>,
117 {
118 name.check_access(locked, current_task, Access::EXEC, CheckAccessReason::Chdir)?;
119 let mut state = self.state.write();
120 state.cwd = name.into_active();
121 Ok(())
122 }
123
124 pub fn chroot<L>(
126 &self,
127 locked: &mut Locked<L>,
128 current_task: &CurrentTask,
129 name: NamespaceNode,
130 ) -> Result<(), Errno>
131 where
132 L: LockEqualOrBefore<FileOpsCore>,
133 {
134 name.check_access(locked, current_task, Access::EXEC, CheckAccessReason::Chroot)
135 .map_err(|_| errno!(EACCES))?;
136 security::check_task_capable(current_task, CAP_SYS_CHROOT)?;
137
138 let mut state = self.state.write();
139 state.root = name.into_active();
140 Ok(())
141 }
142
143 pub fn umask(&self) -> FileMode {
144 self.state.read().umask
145 }
146
147 pub fn apply_umask(&self, mode: FileMode) -> FileMode {
148 let umask = self.state.read().umask;
149 mode & !umask
150 }
151
152 pub fn set_umask(&self, umask: FileMode) -> FileMode {
153 let mut state = self.state.write();
154 let old_umask = state.umask;
155
156 state.umask = umask & FileMode::from_bits(0o777);
162
163 old_umask
164 }
165
166 pub fn set_namespace(&self, new_ns: Arc<Namespace>) -> Result<(), Errno> {
167 let mut state = self.state.write();
168 let kernel = state.namespace.kernel();
169 let mounts_guard = kernel.mounts_lock.lock();
170 state.set_namespace(new_ns, &mounts_guard)?;
171 Ok(())
172 }
173
174 pub fn unshare_namespace(&self) {
175 let mut state = self.state.write();
176 let kernel = state.namespace.kernel();
177 let mounts_guard = kernel.mounts_lock.lock();
178
179 let cloned = state.namespace.clone_namespace(&mounts_guard);
180 state
181 .set_namespace(cloned, &mounts_guard)
182 .expect("nodes should exist in the cloned namespace");
183 }
184
185 pub fn namespace(&self) -> Arc<Namespace> {
186 Arc::clone(&self.state.read().namespace)
187 }
188}
189
190#[cfg(test)]
191mod test {
192 use crate::fs::tmpfs::TmpFs;
193 use crate::testing::{spawn_kernel_and_run, spawn_kernel_and_run_with_pkgfs};
194 use crate::vfs::{FsContext, Namespace};
195 use starnix_uapi::file_mode::FileMode;
196 use starnix_uapi::open_flags::OpenFlags;
197
198 #[::fuchsia::test]
199 async fn test_umask() {
200 spawn_kernel_and_run(async |locked, current_task| {
201 let kernel = current_task.kernel();
202 let fs = FsContext::new(Namespace::new(TmpFs::new_fs(locked, &kernel)));
203
204 assert_eq!(FileMode::from_bits(0o22), fs.set_umask(FileMode::from_bits(0o3020)));
205 assert_eq!(FileMode::from_bits(0o646), fs.apply_umask(FileMode::from_bits(0o666)));
206 assert_eq!(FileMode::from_bits(0o3646), fs.apply_umask(FileMode::from_bits(0o3666)));
207 assert_eq!(FileMode::from_bits(0o20), fs.set_umask(FileMode::from_bits(0o11)));
208 })
209 .await;
210 }
211
212 #[::fuchsia::test]
213 async fn test_chdir() {
214 spawn_kernel_and_run_with_pkgfs(async |locked, current_task| {
215 assert_eq!("/", current_task.fs().cwd().path_escaping_chroot());
216
217 let bin = current_task
218 .open_file(locked, "bin".into(), OpenFlags::RDONLY)
219 .expect("missing bin directory");
220 current_task
221 .fs()
222 .chdir(locked, ¤t_task, bin.name.to_passive())
223 .expect("Failed to chdir");
224 assert_eq!("/bin", current_task.fs().cwd().path_escaping_chroot());
225
226 assert!(current_task.open_file(locked, "bin".into(), OpenFlags::RDONLY).is_err());
229
230 assert!(current_task.open_file(locked, "/bin".into(), OpenFlags::RDONLY).is_ok());
232
233 let previous_directory = current_task
234 .open_file(locked, "..".into(), OpenFlags::RDONLY)
235 .expect("failed to open ..")
236 .name
237 .to_passive();
238 current_task
239 .fs()
240 .chdir(locked, ¤t_task, previous_directory)
241 .expect("Failed to chdir");
242 assert_eq!("/", current_task.fs().cwd().path_escaping_chroot());
243
244 assert!(current_task.open_file(locked, "bin".into(), OpenFlags::RDONLY).is_ok());
246
247 let previous_directory = current_task
249 .open_file(locked, "..".into(), OpenFlags::RDONLY)
250 .expect("failed to open ..")
251 .name
252 .to_passive();
253 current_task
254 .fs()
255 .chdir(locked, ¤t_task, previous_directory)
256 .expect("Failed to chdir");
257 assert_eq!("/", current_task.fs().cwd().path_escaping_chroot());
258 assert!(current_task.open_file(locked, "bin".into(), OpenFlags::RDONLY).is_ok());
259 })
260 .await;
261 }
262}