starnix_core/vfs/
file_write_guard.rs1use starnix_uapi::errors::Errno;
6use starnix_uapi::seal_flags::SealFlags;
7use starnix_uapi::{errno, error};
8
9#[derive(Copy, Clone, Debug, Eq, PartialEq)]
10pub enum FileWriteGuardMode {
11 WriteFile,
13
14 WriteMapping,
16
17 ExecMapping,
19}
20
21#[derive(Default)]
25pub struct FileWriteGuardState {
26 write_exec_locks: isize,
29
30 num_write_mappings: usize,
32
33 seals: Option<SealFlags>,
35}
36
37impl FileWriteGuardState {
38 pub fn acquire(&mut self, mode: FileWriteGuardMode) -> Result<(), Errno> {
39 match mode {
40 FileWriteGuardMode::WriteFile => {
41 if self.write_exec_locks < 0 {
42 return error!(ETXTBSY);
43 }
44
45 self.write_exec_locks += 1;
49 }
50 FileWriteGuardMode::WriteMapping => {
51 self.check_no_seal(SealFlags::WRITE | SealFlags::FUTURE_WRITE)?;
52
53 assert!(self.write_exec_locks > 0);
55
56 self.write_exec_locks += 1;
57 self.num_write_mappings += 1;
58 }
59 FileWriteGuardMode::ExecMapping => {
60 if self.write_exec_locks > 0 {
61 return error!(ETXTBSY);
62 }
63 self.write_exec_locks -= 1;
64 }
65 }
66 Ok(())
67 }
68
69 pub fn release(&mut self, mode: FileWriteGuardMode) {
70 match mode {
71 FileWriteGuardMode::WriteFile => {
72 assert!(self.write_exec_locks > 0);
73 self.write_exec_locks -= 1;
74 }
75 FileWriteGuardMode::WriteMapping => {
76 assert!(self.write_exec_locks > 0);
77 self.write_exec_locks -= 1;
78 assert!(self.num_write_mappings > 0);
79 self.num_write_mappings -= 1;
80 }
81 FileWriteGuardMode::ExecMapping => {
82 assert!(self.write_exec_locks < 0);
83 self.write_exec_locks += 1;
84 }
85 };
86 }
87
88 pub fn enable_sealing(&mut self, initial_seals: SealFlags) {
89 self.seals = Some(initial_seals);
90 }
91
92 pub fn try_add_seal(&mut self, flags: SealFlags) -> Result<(), Errno> {
94 if let Some(seals) = self.seals.as_mut() {
95 if seals.contains(SealFlags::SEAL) {
96 return error!(EPERM);
98 }
99
100 if flags.contains(SealFlags::WRITE) && self.num_write_mappings > 0 {
102 return error!(EBUSY);
103 }
104
105 seals.insert(flags);
106
107 Ok(())
108 } else {
109 error!(EINVAL)
111 }
112 }
113
114 pub fn check_no_seal(&self, flags: SealFlags) -> Result<(), Errno> {
116 if let Some(seals) = self.seals.as_ref() {
117 if seals.intersects(flags) {
118 return error!(EPERM);
119 }
120 }
121 Ok(())
122 }
123
124 pub fn get_seals(&self) -> Result<SealFlags, Errno> {
126 self.seals.ok_or_else(|| errno!(EINVAL))
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use crate::testing::spawn_kernel_and_run;
134 use crate::vfs::FsNodeHandle;
135 use starnix_uapi::device_id::DeviceId;
136 use starnix_uapi::file_mode::FileMode;
137
138 fn create_fs_node(current_task: &crate::task::CurrentTask) -> FsNodeHandle {
139 current_task
140 .fs()
141 .root()
142 .create_node(current_task, "foo".into(), FileMode::IFREG, DeviceId::NONE)
143 .expect("create_node")
144 .entry
145 .node
146 .clone()
147 }
148
149 #[derive(Debug)]
150 struct FileWriteGuard {
151 mode: FileWriteGuardMode,
152 node: FsNodeHandle,
153 }
154
155 impl FileWriteGuard {
156 pub fn new(node: &FsNodeHandle, mode: FileWriteGuardMode) -> Result<FileWriteGuard, Errno> {
157 let mut state = node.write_guard_state.lock();
158 state.acquire(mode)?;
159 Ok(FileWriteGuard { mode, node: node.clone() })
160 }
161 }
162
163 impl Drop for FileWriteGuard {
164 fn drop(&mut self) {
165 let mut state = self.node.write_guard_state.lock();
166 state.release(self.mode);
167 }
168 }
169
170 #[::fuchsia::test]
171 async fn test_write_exec_locking() {
172 spawn_kernel_and_run(async |current_task| {
173 let fs_node = create_fs_node(current_task);
174
175 let write_guard = FileWriteGuard::new(&fs_node, FileWriteGuardMode::WriteFile)
176 .expect("FsNode::lock failed unexpectedly");
177
178 assert_eq!(
179 FileWriteGuard::new(&fs_node, FileWriteGuardMode::ExecMapping).unwrap_err(),
180 errno!(ETXTBSY)
181 );
182
183 let write_mapping_guard =
184 FileWriteGuard::new(&fs_node, FileWriteGuardMode::WriteMapping)
185 .expect("FsNode::lock failed unexpectedly");
186
187 assert_eq!(
188 FileWriteGuard::new(&fs_node, FileWriteGuardMode::ExecMapping).unwrap_err(),
189 errno!(ETXTBSY)
190 );
191
192 std::mem::drop(write_guard);
193
194 assert_eq!(
195 FileWriteGuard::new(&fs_node, FileWriteGuardMode::ExecMapping).unwrap_err(),
196 errno!(ETXTBSY)
197 );
198
199 std::mem::drop(write_mapping_guard);
200
201 let exec_guard = FileWriteGuard::new(&fs_node, FileWriteGuardMode::ExecMapping)
202 .expect("FsNode::lock failed unexpectedly");
203
204 assert_eq!(
205 FileWriteGuard::new(&fs_node, FileWriteGuardMode::WriteFile).unwrap_err(),
206 errno!(ETXTBSY)
207 );
208
209 std::mem::drop(exec_guard);
210
211 FileWriteGuard::new(&fs_node, FileWriteGuardMode::WriteFile)
212 .expect("FsNode::lock failed unexpectedly");
213 })
214 .await;
215 }
216
217 #[::fuchsia::test]
218 async fn test_no_seals() {
219 let mut state = FileWriteGuardState::default();
220
221 assert_eq!(state.try_add_seal(SealFlags::WRITE), error!(EINVAL));
223 assert_eq!(state.check_no_seal(SealFlags::WRITE), Ok(()));
224 assert_eq!(state.get_seals(), error!(EINVAL));
225 }
226
227 #[::fuchsia::test]
228 async fn test_seals() {
229 spawn_kernel_and_run(async |current_task| {
230 let fs_node = create_fs_node(current_task);
231
232 {
233 let mut state = fs_node.write_guard_state.lock();
234
235 state.enable_sealing(SealFlags::empty());
236
237 assert_eq!(state.check_no_seal(SealFlags::WRITE), Ok(()));
238 assert_eq!(state.get_seals(), Ok(SealFlags::empty()));
239
240 assert_eq!(state.try_add_seal(SealFlags::WRITE), Ok(()));
242 assert_eq!(state.check_no_seal(SealFlags::WRITE), error!(EPERM));
243 assert_eq!(state.get_seals(), Ok(SealFlags::WRITE));
244 }
245
246 let file_guard = FileWriteGuard::new(&fs_node, FileWriteGuardMode::WriteFile)
248 .expect("lock(WriteFile) failed");
249
250 assert_eq!(
252 FileWriteGuard::new(&fs_node, FileWriteGuardMode::WriteMapping).unwrap_err(),
253 errno!(EPERM)
254 );
255
256 std::mem::drop(file_guard);
257 })
258 .await;
259 }
260
261 #[::fuchsia::test]
262 async fn test_seals_block_when_mapped() {
263 spawn_kernel_and_run(async |current_task| {
264 let fs_node = create_fs_node(current_task);
265 fs_node.write_guard_state.lock().enable_sealing(SealFlags::empty());
266
267 let _write_guard = FileWriteGuard::new(&fs_node, FileWriteGuardMode::WriteFile)
268 .expect("FsNode::lock failed unexpectedly");
269 let write_mapping_guard =
270 FileWriteGuard::new(&fs_node, FileWriteGuardMode::WriteMapping)
271 .expect("FsNode::lock failed unexpectedly");
272
273 {
275 let mut state = fs_node.write_guard_state.lock();
276 assert_eq!(state.try_add_seal(SealFlags::WRITE), error!(EBUSY));
277 assert_eq!(state.check_no_seal(SealFlags::WRITE), Ok(()));
278 }
279
280 std::mem::drop(write_mapping_guard);
281
282 {
284 let mut state = fs_node.write_guard_state.lock();
285 assert_eq!(state.try_add_seal(SealFlags::WRITE), Ok(()));
286 assert_eq!(state.check_no_seal(SealFlags::WRITE), error!(EPERM));
287 }
288 })
289 .await;
290 }
291
292 #[::fuchsia::test]
293 async fn test_seals_sealed() {
294 spawn_kernel_and_run(async |current_task| {
295 let fs_node = create_fs_node(current_task);
296 let mut state = fs_node.write_guard_state.lock();
297
298 state.enable_sealing(SealFlags::SEAL);
299
300 assert_eq!(state.get_seals(), Ok(SealFlags::SEAL));
301
302 assert_eq!(state.try_add_seal(SealFlags::WRITE), error!(EPERM));
303 assert_eq!(state.get_seals(), Ok(SealFlags::SEAL));
304 })
305 .await;
306 }
307}