1use std::collections::{btree_map::Values, BTreeMap, BTreeSet};
2use std::error::Error;
3use std::fmt;
4use std::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering};
5use std::sync::{Arc, Condvar, Mutex, MutexGuard};
6
7use crate::vfs::VirtualStat;
8
9pub const MAX_FDS_PER_PROCESS: usize = 256;
10
11pub const O_RDONLY: u32 = 0;
12pub const O_WRONLY: u32 = 1;
13pub const O_RDWR: u32 = 2;
14pub const O_CREAT: u32 = 0o100;
15pub const O_EXCL: u32 = 0o200;
16pub const O_TRUNC: u32 = 0o1000;
17pub const O_APPEND: u32 = 0o2000;
18pub const O_NONBLOCK: u32 = 0o4000;
19pub const O_DIRECT: u32 = 0o40000;
20pub const O_DIRECTORY: u32 = 0o200000;
21pub const O_NOFOLLOW: u32 = 0o400000;
22pub const F_DUPFD: u32 = 0;
23pub const F_GETFD: u32 = 1;
24pub const F_SETFD: u32 = 2;
25pub const F_GETFL: u32 = 3;
26pub const F_SETFL: u32 = 4;
27pub const FD_CLOEXEC: u32 = 1;
28pub const LOCK_SH: u32 = 1;
29pub const LOCK_EX: u32 = 2;
30pub const LOCK_NB: u32 = 4;
31pub const LOCK_UN: u32 = 8;
32const DEFAULT_MAX_RECORD_LOCKS: usize = 4096;
33
34pub const FILETYPE_UNKNOWN: u8 = 0;
35pub const FILETYPE_BLOCK_DEVICE: u8 = 1;
36pub const FILETYPE_CHARACTER_DEVICE: u8 = 2;
37pub const FILETYPE_DIRECTORY: u8 = 3;
38pub const FILETYPE_REGULAR_FILE: u8 = 4;
39pub const FILETYPE_SOCKET_DGRAM: u8 = 5;
40pub const FILETYPE_SOCKET_STREAM: u8 = 6;
41pub const FILETYPE_PIPE: u8 = FILETYPE_SOCKET_STREAM;
42pub const FILETYPE_SYMBOLIC_LINK: u8 = 7;
43
44pub type FdResult<T> = Result<T, FdTableError>;
45pub type SharedFileDescription = Arc<FileDescription>;
46
47static NEXT_FILE_DESCRIPTION_ID: AtomicU64 = AtomicU64::new(1);
52
53pub(crate) fn allocate_file_description_id() -> u64 {
54 NEXT_FILE_DESCRIPTION_ID
55 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |id| id.checked_add(1))
56 .expect("open-file-description id space exhausted")
57}
58
59#[derive(Debug, Default)]
60pub struct AnonymousFileUsage {
61 bytes: AtomicU64,
62 inodes: AtomicUsize,
63}
64
65impl AnonymousFileUsage {
66 pub fn bytes(&self) -> u64 {
67 self.bytes.load(Ordering::SeqCst)
68 }
69
70 pub fn inodes(&self) -> usize {
71 self.inodes.load(Ordering::SeqCst)
72 }
73
74 fn add_file(&self, size: u64) {
75 self.bytes.fetch_add(size, Ordering::SeqCst);
76 self.inodes.fetch_add(1, Ordering::SeqCst);
77 }
78
79 fn remove_file(&self, size: u64) {
80 self.bytes.fetch_sub(size, Ordering::SeqCst);
81 self.inodes.fetch_sub(1, Ordering::SeqCst);
82 }
83
84 fn resize_file(&self, old_size: u64, new_size: u64) {
85 if new_size >= old_size {
86 self.bytes
87 .fetch_add(new_size.saturating_sub(old_size), Ordering::SeqCst);
88 } else {
89 self.bytes
90 .fetch_sub(old_size.saturating_sub(new_size), Ordering::SeqCst);
91 }
92 }
93}
94
95#[derive(Debug)]
96pub struct AnonymousFile {
97 pub data: Vec<u8>,
98 pub stat: VirtualStat,
99 usage: Arc<AnonymousFileUsage>,
100}
101
102impl AnonymousFile {
103 pub fn new(data: Vec<u8>, stat: VirtualStat, usage: Arc<AnonymousFileUsage>) -> Self {
104 usage.add_file(stat.size);
105 Self { data, stat, usage }
106 }
107}
108
109impl Drop for AnonymousFile {
110 fn drop(&mut self) {
111 self.usage.remove_file(self.stat.size);
112 }
113}
114
115pub type SharedAnonymousFile = Arc<Mutex<AnonymousFile>>;
116
117#[derive(Debug)]
118enum FileBacking {
119 Path(String),
120 LinkedAlias {
121 former_path: String,
122 live_path: String,
123 },
124 Anonymous {
125 former_path: String,
126 file: SharedAnonymousFile,
127 },
128 DetachedDirectory {
129 former_path: String,
130 stat: VirtualStat,
131 },
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub struct FdTableError {
136 code: &'static str,
137 message: String,
138}
139
140impl FdTableError {
141 pub fn code(&self) -> &'static str {
142 self.code
143 }
144
145 fn bad_file_descriptor(fd: u32) -> Self {
146 Self {
147 code: "EBADF",
148 message: format!("bad file descriptor {fd}"),
149 }
150 }
151
152 fn too_many_open_files() -> Self {
153 Self {
154 code: "EMFILE",
155 message: String::from("too many open files"),
156 }
157 }
158
159 fn no_memory(message: impl Into<String>) -> Self {
160 Self {
161 code: "ENOMEM",
162 message: message.into(),
163 }
164 }
165
166 fn invalid_argument(message: impl Into<String>) -> Self {
167 Self {
168 code: "EINVAL",
169 message: message.into(),
170 }
171 }
172
173 fn would_block(message: impl Into<String>) -> Self {
174 Self {
175 code: "EWOULDBLOCK",
176 message: message.into(),
177 }
178 }
179
180 fn deadlock(message: impl Into<String>) -> Self {
181 Self {
182 code: "EDEADLK",
183 message: message.into(),
184 }
185 }
186}
187
188impl fmt::Display for FdTableError {
189 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190 write!(f, "{}: {}", self.code, self.message)
191 }
192}
193
194impl Error for FdTableError {}
195
196#[derive(Debug)]
197pub struct FileDescription {
198 id: u64,
199 backing: Mutex<FileBacking>,
200 lock_target: Option<FileLockTarget>,
201 cursor: AtomicU64,
202 flags: AtomicU32,
203 ref_count: AtomicUsize,
204}
205
206impl FileDescription {
207 pub fn new(id: u64, path: impl Into<String>, flags: u32) -> Self {
208 Self::with_ref_count_and_lock(id, path, flags, 1, None)
209 }
210
211 pub fn new_with_lock(
212 id: u64,
213 path: impl Into<String>,
214 flags: u32,
215 lock_target: Option<FileLockTarget>,
216 ) -> Self {
217 Self::with_ref_count_and_lock(id, path, flags, 1, lock_target)
218 }
219
220 pub fn with_ref_count(id: u64, path: impl Into<String>, flags: u32, ref_count: usize) -> Self {
221 Self::with_ref_count_and_lock(id, path, flags, ref_count, None)
222 }
223
224 pub fn with_ref_count_and_lock(
225 id: u64,
226 path: impl Into<String>,
227 flags: u32,
228 ref_count: usize,
229 lock_target: Option<FileLockTarget>,
230 ) -> Self {
231 Self {
232 id,
233 backing: Mutex::new(FileBacking::Path(path.into())),
234 lock_target,
235 cursor: AtomicU64::new(0),
236 flags: AtomicU32::new(flags),
237 ref_count: AtomicUsize::new(ref_count),
238 }
239 }
240
241 pub fn id(&self) -> u64 {
242 self.id
243 }
244
245 pub fn path(&self) -> String {
246 match &*lock_or_recover(&self.backing) {
247 FileBacking::Path(path) => path.clone(),
248 FileBacking::LinkedAlias { live_path, .. } => live_path.clone(),
249 FileBacking::Anonymous { former_path, .. } => former_path.clone(),
250 FileBacking::DetachedDirectory { former_path, .. } => former_path.clone(),
251 }
252 }
253
254 pub fn proc_display_path(&self) -> String {
257 match &*lock_or_recover(&self.backing) {
258 FileBacking::Path(path) => path.clone(),
259 FileBacking::LinkedAlias { former_path, .. }
260 | FileBacking::Anonymous { former_path, .. }
261 | FileBacking::DetachedDirectory { former_path, .. } => {
262 format!("{former_path} (deleted)")
263 }
264 }
265 }
266
267 pub fn is_path_backed_by(&self, expected: &str) -> bool {
268 match &*lock_or_recover(&self.backing) {
269 FileBacking::Path(path) => path == expected,
270 FileBacking::LinkedAlias { live_path, .. } => live_path == expected,
271 FileBacking::Anonymous { .. } | FileBacking::DetachedDirectory { .. } => false,
272 }
273 }
274
275 pub fn rename_path_prefix(&self, old_path: &str, new_path: &str) {
276 let mut backing = lock_or_recover(&self.backing);
277 let path = match &mut *backing {
278 FileBacking::Path(path) => path,
279 FileBacking::LinkedAlias { live_path, .. } => live_path,
280 FileBacking::Anonymous { .. } | FileBacking::DetachedDirectory { .. } => return,
281 };
282 if path == old_path {
283 *path = new_path.to_string();
284 } else if let Some(suffix) = path
285 .strip_prefix(old_path)
286 .filter(|value| value.starts_with('/'))
287 {
288 *path = format!("{new_path}{suffix}");
289 }
290 }
291
292 pub fn detach_path(&self, expected: &str, file: SharedAnonymousFile) -> bool {
293 let mut backing = lock_or_recover(&self.backing);
294 let former_path = match &*backing {
295 FileBacking::Path(path) if path == expected => expected.to_string(),
296 FileBacking::LinkedAlias {
297 former_path,
298 live_path,
299 } if live_path == expected => former_path.clone(),
300 _ => return false,
301 };
302 *backing = FileBacking::Anonymous { former_path, file };
303 true
304 }
305
306 pub fn rebind_deleted_path(&self, expected: &str, live_path: &str) -> bool {
310 let mut backing = lock_or_recover(&self.backing);
311 let former_path = match &*backing {
312 FileBacking::Path(path) if path == expected => expected.to_string(),
313 FileBacking::LinkedAlias {
314 former_path,
315 live_path: current,
316 } if current == expected => former_path.clone(),
317 _ => return false,
318 };
319 *backing = FileBacking::LinkedAlias {
320 former_path,
321 live_path: live_path.to_string(),
322 };
323 true
324 }
325
326 pub fn detach_directory(&self, _expected: &str, stat: VirtualStat) -> bool {
327 let mut backing = lock_or_recover(&self.backing);
328 let FileBacking::Path(former_path) = &*backing else {
329 return false;
330 };
331 let former_path = former_path.clone();
332 *backing = FileBacking::DetachedDirectory { former_path, stat };
333 true
334 }
335
336 pub fn detached_directory_stat(&self) -> Option<VirtualStat> {
337 match &*lock_or_recover(&self.backing) {
338 FileBacking::DetachedDirectory { stat, .. } => Some(stat.clone()),
339 FileBacking::Path(_)
340 | FileBacking::LinkedAlias { .. }
341 | FileBacking::Anonymous { .. } => None,
342 }
343 }
344
345 pub fn anonymous_stat(&self) -> Option<VirtualStat> {
346 let file = match &*lock_or_recover(&self.backing) {
347 FileBacking::Anonymous { file, .. } => Arc::clone(file),
348 FileBacking::Path(_)
349 | FileBacking::LinkedAlias { .. }
350 | FileBacking::DetachedDirectory { .. } => return None,
351 };
352 let stat = lock_or_recover(&file).stat.clone();
353 Some(stat)
354 }
355
356 pub fn anonymous_pread(&self, offset: u64, length: usize) -> Option<Vec<u8>> {
357 let file = match &*lock_or_recover(&self.backing) {
358 FileBacking::Anonymous { file, .. } => Arc::clone(file),
359 FileBacking::Path(_)
360 | FileBacking::LinkedAlias { .. }
361 | FileBacking::DetachedDirectory { .. } => return None,
362 };
363 let file = lock_or_recover(&file);
364 let start = usize::try_from(offset).unwrap_or(usize::MAX);
365 if start >= file.data.len() {
366 return Some(Vec::new());
367 }
368 let end = start.saturating_add(length).min(file.data.len());
369 Some(file.data[start..end].to_vec())
370 }
371
372 pub fn anonymous_pwrite(&self, offset: u64, data: &[u8]) -> Option<FdResult<u64>> {
373 let file = match &*lock_or_recover(&self.backing) {
374 FileBacking::Anonymous { file, .. } => Arc::clone(file),
375 FileBacking::Path(_)
376 | FileBacking::LinkedAlias { .. }
377 | FileBacking::DetachedDirectory { .. } => return None,
378 };
379 let mut file = lock_or_recover(&file);
380 let Ok(start) = usize::try_from(offset) else {
381 return Some(Err(FdTableError::invalid_argument(
382 "anonymous file offset exceeds host address space",
383 )));
384 };
385 let Some(end) = start.checked_add(data.len()) else {
386 return Some(Err(FdTableError::invalid_argument(
387 "anonymous file write range overflows",
388 )));
389 };
390 if end > file.data.len() {
391 let additional = end - file.data.len();
392 if file.data.try_reserve(additional).is_err() {
393 return Some(Err(FdTableError::no_memory(
394 "anonymous file write allocation failed",
395 )));
396 }
397 file.data.resize(end, 0);
398 }
399 file.data[start..end].copy_from_slice(data);
400 let old_size = file.stat.size;
401 file.stat.size = file.data.len() as u64;
402 file.stat.blocks = file.stat.size.div_ceil(512);
403 file.usage.resize_file(old_size, file.stat.size);
404 Some(Ok(file.stat.size))
405 }
406
407 pub fn anonymous_truncate(&self, length: u64) -> Option<FdResult<()>> {
408 let file = match &*lock_or_recover(&self.backing) {
409 FileBacking::Anonymous { file, .. } => Arc::clone(file),
410 FileBacking::Path(_)
411 | FileBacking::LinkedAlias { .. }
412 | FileBacking::DetachedDirectory { .. } => return None,
413 };
414 let Ok(length) = usize::try_from(length) else {
415 return Some(Err(FdTableError::invalid_argument(
416 "anonymous file length exceeds host address space",
417 )));
418 };
419 let mut file = lock_or_recover(&file);
420 let additional = length.saturating_sub(file.data.len());
421 if additional > 0 && file.data.try_reserve(additional).is_err() {
422 return Some(Err(FdTableError::no_memory(
423 "anonymous file truncate allocation failed",
424 )));
425 }
426 file.data.resize(length, 0);
427 let old_size = file.stat.size;
428 file.stat.size = length as u64;
429 file.stat.blocks = file.stat.size.div_ceil(512);
430 file.usage.resize_file(old_size, file.stat.size);
431 Some(Ok(()))
432 }
433
434 pub fn detached_chmod(&self, mode: u32) -> bool {
435 let mut backing = lock_or_recover(&self.backing);
436 match &mut *backing {
437 FileBacking::Anonymous { file, .. } => {
438 let mut file = lock_or_recover(file);
439 file.stat.mode = (file.stat.mode & !0o7777) | (mode & 0o7777);
440 true
441 }
442 FileBacking::DetachedDirectory { stat, .. } => {
443 stat.mode = (stat.mode & !0o7777) | (mode & 0o7777);
444 true
445 }
446 FileBacking::Path(_) | FileBacking::LinkedAlias { .. } => false,
447 }
448 }
449
450 pub fn detached_chown(&self, uid: u32, gid: u32, changed_mode: Option<u32>) -> bool {
451 let mut backing = lock_or_recover(&self.backing);
452 match &mut *backing {
453 FileBacking::Anonymous { file, .. } => {
454 let mut file = lock_or_recover(file);
455 file.stat.uid = uid;
456 file.stat.gid = gid;
457 if let Some(mode) = changed_mode {
458 file.stat.mode = mode;
459 }
460 true
461 }
462 FileBacking::DetachedDirectory { stat, .. } => {
463 stat.uid = uid;
464 stat.gid = gid;
465 true
466 }
467 FileBacking::Path(_) | FileBacking::LinkedAlias { .. } => false,
468 }
469 }
470
471 pub fn lock_target(&self) -> Option<FileLockTarget> {
472 self.lock_target
473 }
474
475 pub fn cursor(&self) -> u64 {
476 self.cursor.load(Ordering::SeqCst)
477 }
478
479 pub fn set_cursor(&self, cursor: u64) {
480 self.cursor.store(cursor, Ordering::SeqCst);
481 }
482
483 pub fn flags(&self) -> u32 {
484 self.flags.load(Ordering::SeqCst)
485 }
486
487 pub fn update_flags(&self, mask: u32, flags: u32) -> u32 {
488 let mut current = self.flags();
489 loop {
490 let next = (current & !mask) | (flags & mask);
491 match self
492 .flags
493 .compare_exchange(current, next, Ordering::SeqCst, Ordering::SeqCst)
494 {
495 Ok(_) => return next,
496 Err(observed) => current = observed,
497 }
498 }
499 }
500
501 pub fn ref_count(&self) -> usize {
502 self.ref_count.load(Ordering::SeqCst)
503 }
504
505 pub fn increment_ref_count(&self) -> usize {
506 self.ref_count.fetch_add(1, Ordering::SeqCst) + 1
507 }
508
509 pub fn decrement_ref_count(&self) -> usize {
510 let mut current = self.ref_count.load(Ordering::SeqCst);
511 loop {
512 let next = current.saturating_sub(1);
513 match self
514 .ref_count
515 .compare_exchange(current, next, Ordering::SeqCst, Ordering::SeqCst)
516 {
517 Ok(_) => return next,
518 Err(observed) => current = observed,
519 }
520 }
521 }
522}
523
524#[derive(Debug, Clone)]
525pub struct FdEntry {
526 pub fd: u32,
527 pub description: SharedFileDescription,
528 pub status_flags: u32,
529 pub fd_flags: u32,
530 pub rights: u64,
531 pub filetype: u8,
532}
533
534#[derive(Debug)]
535pub struct TransferredFd {
536 description: SharedFileDescription,
537 status_flags: u32,
538 rights: u64,
539 filetype: u8,
540}
541
542impl Clone for TransferredFd {
543 fn clone(&self) -> Self {
544 self.description.increment_ref_count();
545 Self {
546 description: Arc::clone(&self.description),
547 status_flags: self.status_flags,
548 rights: self.rights,
549 filetype: self.filetype,
550 }
551 }
552}
553
554impl PartialEq for TransferredFd {
555 fn eq(&self, other: &Self) -> bool {
556 self.description.id() == other.description.id()
557 && self.status_flags == other.status_flags
558 && self.rights == other.rights
559 && self.filetype == other.filetype
560 }
561}
562
563impl Eq for TransferredFd {}
564
565impl Drop for TransferredFd {
566 fn drop(&mut self) {
567 self.description.decrement_ref_count();
568 }
569}
570
571impl TransferredFd {
572 pub(crate) fn description(&self) -> SharedFileDescription {
573 Arc::clone(&self.description)
574 }
575
576 pub fn description_id(&self) -> u64 {
577 self.description.id()
578 }
579
580 pub fn status_flags(&self) -> u32 {
581 self.status_flags
582 }
583
584 pub fn rights(&self) -> u64 {
585 self.rights
586 }
587
588 pub fn filetype(&self) -> u8 {
589 self.filetype
590 }
591}
592
593#[derive(Debug, Clone, Copy, PartialEq, Eq)]
594pub struct FdStat {
595 pub filetype: u8,
596 pub flags: u32,
597 pub rights: u64,
598}
599
600#[derive(Debug, Clone)]
601pub struct StdioOverride {
602 pub description: SharedFileDescription,
603 pub filetype: u8,
604}
605
606#[derive(Debug, Clone)]
607struct DescriptionFactory {}
608
609impl DescriptionFactory {
610 fn new(_starting_id: u64) -> Self {
611 Self {}
612 }
613
614 fn allocate(&self, path: &str, flags: u32) -> SharedFileDescription {
615 self.allocate_with_lock(path, flags, None)
616 }
617
618 fn allocate_with_lock(
619 &self,
620 path: &str,
621 flags: u32,
622 lock_target: Option<FileLockTarget>,
623 ) -> SharedFileDescription {
624 let next_id = allocate_file_description_id();
625 Arc::new(FileDescription::new_with_lock(
626 next_id,
627 path,
628 flags,
629 lock_target,
630 ))
631 }
632}
633
634#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
635pub struct FileLockTarget {
636 dev: u64,
637 ino: u64,
638}
639
640impl FileLockTarget {
641 pub const fn new(dev: u64, ino: u64) -> Self {
642 Self { dev, ino }
643 }
644
645 pub const fn dev(self) -> u64 {
646 self.dev
647 }
648
649 pub const fn ino(self) -> u64 {
650 self.ino
651 }
652}
653
654#[derive(Debug, Clone, Copy, PartialEq, Eq)]
655enum FileLockMode {
656 Shared,
657 Exclusive,
658}
659
660#[derive(Debug, Clone, Copy, PartialEq, Eq)]
661pub enum RecordLockType {
662 Read,
663 Write,
664 Unlock,
665}
666
667#[derive(Debug, Clone, Copy, PartialEq, Eq)]
668pub struct RecordLock {
669 pub lock_type: RecordLockType,
670 pub start: u64,
671 pub end: Option<u64>,
673 pub pid: u32,
674}
675
676impl RecordLock {
677 pub fn new(lock_type: RecordLockType, start: u64, length: u64, pid: u32) -> FdResult<Self> {
678 let end =
679 if length == 0 {
680 None
681 } else {
682 Some(start.checked_add(length).ok_or_else(|| {
683 FdTableError::invalid_argument("record lock range exceeds u64")
684 })?)
685 };
686 Ok(Self {
687 lock_type,
688 start,
689 end,
690 pid,
691 })
692 }
693
694 pub fn length(self) -> u64 {
695 self.end.map_or(0, |end| end - self.start)
696 }
697}
698
699#[derive(Debug, Clone, Copy, PartialEq, Eq)]
700pub enum FlockOperation {
701 Shared { nonblocking: bool },
702 Exclusive { nonblocking: bool },
703 Unlock,
704}
705
706impl FlockOperation {
707 pub fn from_bits(operation: u32) -> FdResult<Self> {
708 let nonblocking = operation & LOCK_NB != 0;
709 match operation & !LOCK_NB {
710 LOCK_SH => Ok(Self::Shared { nonblocking }),
711 LOCK_EX => Ok(Self::Exclusive { nonblocking }),
712 LOCK_UN => Ok(Self::Unlock),
713 _ => Err(FdTableError::invalid_argument(format!(
714 "invalid flock operation {operation:#x}"
715 ))),
716 }
717 }
718}
719
720#[derive(Debug, Clone)]
721pub struct ProcessFdTable {
722 entries: BTreeMap<u32, FdEntry>,
723 next_fd: u32,
724 alloc_desc: DescriptionFactory,
725 max_fds: usize,
726}
727
728impl ProcessFdTable {
729 fn new(alloc_desc: DescriptionFactory, max_fds: usize) -> Self {
730 Self {
731 entries: BTreeMap::new(),
732 next_fd: 3,
733 alloc_desc,
734 max_fds,
735 }
736 }
737
738 pub fn max_fds(&self) -> usize {
739 self.max_fds
740 }
741
742 pub fn available_fd_capacity(&self) -> usize {
743 self.max_fds.saturating_sub(self.entries.len())
744 }
745
746 pub fn init_stdio(
747 &mut self,
748 stdin_desc: SharedFileDescription,
749 stdout_desc: SharedFileDescription,
750 stderr_desc: SharedFileDescription,
751 ) {
752 self.entries.insert(
753 0,
754 FdEntry {
755 fd: 0,
756 description: stdin_desc,
757 status_flags: 0,
758 fd_flags: 0,
759 rights: 0,
760 filetype: FILETYPE_CHARACTER_DEVICE,
761 },
762 );
763 self.entries.insert(
764 1,
765 FdEntry {
766 fd: 1,
767 description: stdout_desc,
768 status_flags: 0,
769 fd_flags: 0,
770 rights: 0,
771 filetype: FILETYPE_CHARACTER_DEVICE,
772 },
773 );
774 self.entries.insert(
775 2,
776 FdEntry {
777 fd: 2,
778 description: stderr_desc,
779 status_flags: 0,
780 fd_flags: 0,
781 rights: 0,
782 filetype: FILETYPE_CHARACTER_DEVICE,
783 },
784 );
785 }
786
787 pub fn init_stdio_with_types(
788 &mut self,
789 stdin_desc: SharedFileDescription,
790 stdin_type: u8,
791 stdout_desc: SharedFileDescription,
792 stdout_type: u8,
793 stderr_desc: SharedFileDescription,
794 stderr_type: u8,
795 ) {
796 stdin_desc.increment_ref_count();
797 stdout_desc.increment_ref_count();
798 stderr_desc.increment_ref_count();
799 self.entries.insert(
800 0,
801 FdEntry {
802 fd: 0,
803 description: stdin_desc,
804 status_flags: 0,
805 fd_flags: 0,
806 rights: 0,
807 filetype: stdin_type,
808 },
809 );
810 self.entries.insert(
811 1,
812 FdEntry {
813 fd: 1,
814 description: stdout_desc,
815 status_flags: 0,
816 fd_flags: 0,
817 rights: 0,
818 filetype: stdout_type,
819 },
820 );
821 self.entries.insert(
822 2,
823 FdEntry {
824 fd: 2,
825 description: stderr_desc,
826 status_flags: 0,
827 fd_flags: 0,
828 rights: 0,
829 filetype: stderr_type,
830 },
831 );
832 }
833
834 pub fn open(&mut self, path: &str, flags: u32) -> FdResult<u32> {
835 self.open_with_details(path, flags, FILETYPE_REGULAR_FILE, None)
836 }
837
838 pub fn open_with_filetype(&mut self, path: &str, flags: u32, filetype: u8) -> FdResult<u32> {
839 self.open_with_details(path, flags, filetype, None)
840 }
841
842 pub fn open_with_details(
843 &mut self,
844 path: &str,
845 flags: u32,
846 filetype: u8,
847 lock_target: Option<FileLockTarget>,
848 ) -> FdResult<u32> {
849 let fd = self.allocate_fd()?;
850 let description =
851 self.alloc_desc
852 .allocate_with_lock(path, description_flags(flags), lock_target);
853 self.entries.insert(
854 fd,
855 FdEntry {
856 fd,
857 description,
858 status_flags: status_flags(flags),
859 fd_flags: 0,
860 rights: 0,
861 filetype,
862 },
863 );
864 Ok(fd)
865 }
866
867 pub fn open_with(
868 &mut self,
869 description: SharedFileDescription,
870 filetype: u8,
871 target_fd: Option<u32>,
872 ) -> FdResult<u32> {
873 let entry_status_flags = status_flags(description.flags());
874 let fd = match target_fd {
875 Some(fd) => {
876 self.validate_fd_bounds(fd)?;
877 if self.entries.contains_key(&fd) {
878 self.close(fd);
879 }
880 fd
881 }
882 None => self.allocate_fd()?,
883 };
884 description.increment_ref_count();
885 self.entries.insert(
886 fd,
887 FdEntry {
888 fd,
889 description,
890 status_flags: entry_status_flags,
891 fd_flags: 0,
892 rights: 0,
893 filetype,
894 },
895 );
896 Ok(fd)
897 }
898
899 pub fn open_pair_with_details(
900 &mut self,
901 first_path: &str,
902 second_path: &str,
903 status_flags: u32,
904 fd_flags: u32,
905 filetype: u8,
906 ) -> FdResult<(u32, u32, SharedFileDescription, SharedFileDescription)> {
907 if self.entries.len().saturating_add(2) > self.max_fds {
908 return Err(FdTableError::too_many_open_files());
909 }
910 let first_fd = self.allocate_fd()?;
911 let second_fd = match self.allocate_fd() {
912 Ok(fd) => fd,
913 Err(error) => {
914 self.next_fd = first_fd;
915 return Err(error);
916 }
917 };
918 let first = self.alloc_desc.allocate(first_path, O_RDWR);
919 let second = self.alloc_desc.allocate(second_path, O_RDWR);
920 for (fd, description) in [
921 (first_fd, Arc::clone(&first)),
922 (second_fd, Arc::clone(&second)),
923 ] {
924 self.entries.insert(
925 fd,
926 FdEntry {
927 fd,
928 description,
929 status_flags: status_flags & ENTRY_STATUS_FLAG_MASK,
930 fd_flags: fd_flags & FD_CLOEXEC,
931 rights: 0,
932 filetype,
933 },
934 );
935 }
936 Ok((first_fd, second_fd, first, second))
937 }
938
939 pub fn transfer(&self, fd: u32) -> FdResult<TransferredFd> {
940 let entry = self
941 .entries
942 .get(&fd)
943 .ok_or_else(|| FdTableError::bad_file_descriptor(fd))?;
944 entry.description.increment_ref_count();
945 Ok(TransferredFd {
946 description: Arc::clone(&entry.description),
947 status_flags: entry.status_flags,
948 rights: entry.rights,
949 filetype: entry.filetype,
950 })
951 }
952
953 pub fn create_transfer(&self, path: &str, flags: u32, filetype: u8) -> TransferredFd {
959 TransferredFd {
960 description: self.alloc_desc.allocate(path, description_flags(flags)),
961 status_flags: status_flags(flags),
962 rights: 0,
963 filetype,
964 }
965 }
966
967 pub fn install_transferred(
968 &mut self,
969 transfers: &[TransferredFd],
970 close_on_exec: bool,
971 ) -> FdResult<Vec<u32>> {
972 if self.entries.len().saturating_add(transfers.len()) > self.max_fds {
973 return Err(FdTableError::too_many_open_files());
974 }
975 let mut candidates = Vec::with_capacity(transfers.len());
976 let mut cursor = self.next_fd;
977 for _ in transfers {
978 let start = usize::try_from(cursor).unwrap_or(0) % self.max_fds;
979 let candidate = (0..self.max_fds)
980 .map(|offset| ((start + offset) % self.max_fds) as u32)
981 .find(|fd| !self.entries.contains_key(fd) && !candidates.contains(fd))
982 .ok_or_else(FdTableError::too_many_open_files)?;
983 candidates.push(candidate);
984 cursor = candidate.saturating_add(1);
985 }
986 for (fd, transfer) in candidates.iter().copied().zip(transfers) {
987 transfer.description.increment_ref_count();
988 self.entries.insert(
989 fd,
990 FdEntry {
991 fd,
992 description: Arc::clone(&transfer.description),
993 status_flags: transfer.status_flags,
994 fd_flags: if close_on_exec { FD_CLOEXEC } else { 0 },
995 rights: transfer.rights,
996 filetype: transfer.filetype,
997 },
998 );
999 }
1000 self.next_fd = cursor;
1001 Ok(candidates)
1002 }
1003
1004 pub(crate) fn install_transferred_at(
1008 &mut self,
1009 transfer: &TransferredFd,
1010 fd: u32,
1011 fd_flags: u32,
1012 ) -> FdResult<()> {
1013 self.validate_fd_bounds(fd)?;
1014 if self.entries.contains_key(&fd) {
1015 self.close(fd);
1016 }
1017 transfer.description.increment_ref_count();
1018 self.entries.insert(
1019 fd,
1020 FdEntry {
1021 fd,
1022 description: Arc::clone(&transfer.description),
1023 status_flags: transfer.status_flags,
1024 fd_flags: fd_flags & FD_CLOEXEC,
1025 rights: transfer.rights,
1026 filetype: transfer.filetype,
1027 },
1028 );
1029 Ok(())
1030 }
1031
1032 pub fn get(&self, fd: u32) -> Option<&FdEntry> {
1033 self.entries.get(&fd)
1034 }
1035
1036 pub fn values(&self) -> Values<'_, u32, FdEntry> {
1037 self.entries.values()
1038 }
1039
1040 pub fn close(&mut self, fd: u32) -> bool {
1041 let Some(entry) = self.entries.remove(&fd) else {
1042 return false;
1043 };
1044 entry.description.decrement_ref_count();
1045 true
1046 }
1047
1048 pub fn close_on_exec_fds(&self) -> Vec<u32> {
1053 self.entries
1054 .iter()
1055 .filter_map(|(fd, entry)| (entry.fd_flags & FD_CLOEXEC != 0).then_some(*fd))
1056 .collect()
1057 }
1058
1059 pub fn dup(&mut self, fd: u32) -> FdResult<u32> {
1060 self.dup_with_status_flags(fd, None)
1061 }
1062
1063 pub fn dup_with_status_flags(
1064 &mut self,
1065 fd: u32,
1066 status_flags_override: Option<u32>,
1067 ) -> FdResult<u32> {
1068 let entry = self
1069 .entries
1070 .get(&fd)
1071 .cloned()
1072 .ok_or_else(|| FdTableError::bad_file_descriptor(fd))?;
1073 let new_fd = self.allocate_fd()?;
1074 self.duplicate_entry(
1075 &entry,
1076 new_fd,
1077 status_flags_override.unwrap_or(entry.status_flags),
1078 0,
1079 )
1080 }
1081
1082 pub fn dup2(&mut self, old_fd: u32, new_fd: u32) -> FdResult<()> {
1083 let entry = self
1084 .entries
1085 .get(&old_fd)
1086 .cloned()
1087 .ok_or_else(|| FdTableError::bad_file_descriptor(old_fd))?;
1088 self.validate_fd_bounds(new_fd)?;
1089 if old_fd == new_fd {
1090 return Ok(());
1091 }
1092
1093 if self.entries.contains_key(&new_fd) {
1094 self.close(new_fd);
1095 }
1096
1097 self.duplicate_entry(&entry, new_fd, entry.status_flags, 0)?;
1098 Ok(())
1099 }
1100
1101 pub fn stat(&self, fd: u32) -> FdResult<FdStat> {
1102 let entry = self
1103 .entries
1104 .get(&fd)
1105 .ok_or_else(|| FdTableError::bad_file_descriptor(fd))?;
1106 Ok(FdStat {
1107 filetype: entry.filetype,
1108 flags: visible_fd_flags(entry.description.flags(), entry.status_flags),
1109 rights: entry.rights,
1110 })
1111 }
1112
1113 pub fn fcntl(&mut self, fd: u32, command: u32, arg: u32) -> FdResult<u32> {
1114 match command {
1115 F_DUPFD => {
1116 let entry = self
1117 .entries
1118 .get(&fd)
1119 .cloned()
1120 .ok_or_else(|| FdTableError::bad_file_descriptor(fd))?;
1121 let min_fd = self.validate_fcntl_dup_min(arg)?;
1122 let new_fd = self.allocate_fd_from(min_fd)?;
1123 self.duplicate_entry(&entry, new_fd, entry.status_flags, 0)
1124 }
1125 F_GETFD => {
1126 let entry = self
1127 .entries
1128 .get(&fd)
1129 .ok_or_else(|| FdTableError::bad_file_descriptor(fd))?;
1130 Ok(entry.fd_flags & FD_CLOEXEC)
1131 }
1132 F_SETFD => {
1133 let entry = self
1134 .entries
1135 .get_mut(&fd)
1136 .ok_or_else(|| FdTableError::bad_file_descriptor(fd))?;
1137 entry.fd_flags = arg & FD_CLOEXEC;
1138 Ok(0)
1139 }
1140 F_GETFL => {
1141 let entry = self
1142 .entries
1143 .get(&fd)
1144 .ok_or_else(|| FdTableError::bad_file_descriptor(fd))?;
1145 Ok(visible_fd_flags(
1146 entry.description.flags(),
1147 entry.status_flags,
1148 ))
1149 }
1150 F_SETFL => {
1151 let entry = self
1152 .entries
1153 .get_mut(&fd)
1154 .ok_or_else(|| FdTableError::bad_file_descriptor(fd))?;
1155 entry.status_flags = arg & ENTRY_STATUS_FLAG_MASK;
1156 entry.description.update_flags(SHARED_STATUS_FLAG_MASK, arg);
1157 Ok(0)
1158 }
1159 _ => Err(FdTableError::invalid_argument(format!(
1160 "unsupported fcntl command {command}"
1161 ))),
1162 }
1163 }
1164
1165 pub fn fork(&self) -> Self {
1166 self.fork_with_cloexec(false)
1167 }
1168
1169 pub fn fork_preserving_cloexec(&self) -> Self {
1175 self.fork_with_cloexec(true)
1176 }
1177
1178 fn fork_with_cloexec(&self, preserve_cloexec: bool) -> Self {
1179 let mut child = Self::new(self.alloc_desc.clone(), self.max_fds);
1180 child.next_fd = self.next_fd;
1181
1182 for (fd, entry) in &self.entries {
1183 if !preserve_cloexec && entry.fd_flags & FD_CLOEXEC != 0 {
1190 continue;
1191 }
1192 entry.description.increment_ref_count();
1193 child.entries.insert(
1194 *fd,
1195 FdEntry {
1196 fd: *fd,
1197 description: Arc::clone(&entry.description),
1198 status_flags: entry.status_flags,
1199 fd_flags: entry.fd_flags,
1200 rights: entry.rights,
1201 filetype: entry.filetype,
1202 },
1203 );
1204 }
1205
1206 child
1207 }
1208
1209 pub fn len_for_exec(&self) -> usize {
1210 self.entries
1211 .values()
1212 .filter(|entry| entry.fd_flags & FD_CLOEXEC == 0)
1213 .count()
1214 }
1215
1216 pub fn close_all(&mut self) {
1217 let fds: Vec<u32> = self.entries.keys().copied().collect();
1218 for fd in fds {
1219 self.close(fd);
1220 }
1221 }
1222
1223 pub fn len(&self) -> usize {
1224 self.entries.len()
1225 }
1226
1227 pub fn is_empty(&self) -> bool {
1228 self.entries.is_empty()
1229 }
1230
1231 pub fn iter(&self) -> Values<'_, u32, FdEntry> {
1232 self.entries.values()
1233 }
1234
1235 fn allocate_fd(&mut self) -> FdResult<u32> {
1236 if self.entries.len() >= self.max_fds {
1237 return Err(FdTableError::too_many_open_files());
1238 }
1239
1240 let start = usize::try_from(self.next_fd).unwrap_or(0) % self.max_fds;
1241 for offset in 0..self.max_fds {
1242 let candidate = ((start + offset) % self.max_fds) as u32;
1243 if !self.entries.contains_key(&candidate) {
1244 self.next_fd = candidate.saturating_add(1);
1245 return Ok(candidate);
1246 }
1247 }
1248
1249 Err(FdTableError::too_many_open_files())
1250 }
1251
1252 fn allocate_fd_from(&mut self, min_fd: u32) -> FdResult<u32> {
1253 if self.entries.len() >= self.max_fds {
1254 return Err(FdTableError::too_many_open_files());
1255 }
1256
1257 if min_fd as usize >= self.max_fds {
1258 return Err(FdTableError::invalid_argument(format!(
1259 "fd {min_fd} exceeds process fd limit"
1260 )));
1261 }
1262
1263 for candidate in min_fd..self.max_fds as u32 {
1264 if !self.entries.contains_key(&candidate) {
1265 self.next_fd = candidate.saturating_add(1);
1266 return Ok(candidate);
1267 }
1268 }
1269
1270 Err(FdTableError::too_many_open_files())
1271 }
1272
1273 fn duplicate_entry(
1274 &mut self,
1275 entry: &FdEntry,
1276 new_fd: u32,
1277 status_flags: u32,
1278 fd_flags: u32,
1279 ) -> FdResult<u32> {
1280 entry.description.increment_ref_count();
1281 self.entries.insert(
1282 new_fd,
1283 FdEntry {
1284 fd: new_fd,
1285 description: Arc::clone(&entry.description),
1286 status_flags,
1287 fd_flags,
1288 rights: entry.rights,
1289 filetype: entry.filetype,
1290 },
1291 );
1292 Ok(new_fd)
1293 }
1294
1295 fn validate_fd_bounds(&self, fd: u32) -> FdResult<()> {
1296 if fd as usize >= self.max_fds {
1297 return Err(FdTableError::bad_file_descriptor(fd));
1298 }
1299 Ok(())
1300 }
1301
1302 fn validate_fcntl_dup_min(&self, min_fd: u32) -> FdResult<u32> {
1303 if min_fd as usize >= self.max_fds {
1304 return Err(FdTableError::invalid_argument(format!(
1305 "fd {min_fd} exceeds process fd limit"
1306 )));
1307 }
1308 Ok(min_fd)
1309 }
1310}
1311
1312fn description_flags(flags: u32) -> u32 {
1313 flags & !status_flags(flags)
1314}
1315
1316fn status_flags(flags: u32) -> u32 {
1317 flags & ENTRY_STATUS_FLAG_MASK
1318}
1319
1320fn visible_fd_flags(description_flags: u32, entry_status_flags: u32) -> u32 {
1321 (description_flags & (0b11 | SHARED_STATUS_FLAG_MASK))
1322 | (entry_status_flags & ENTRY_STATUS_FLAG_MASK)
1323}
1324
1325const SHARED_STATUS_FLAG_MASK: u32 = O_APPEND;
1326const ENTRY_STATUS_FLAG_MASK: u32 = O_NONBLOCK;
1327
1328impl<'a> IntoIterator for &'a ProcessFdTable {
1329 type Item = &'a FdEntry;
1330 type IntoIter = Values<'a, u32, FdEntry>;
1331
1332 fn into_iter(self) -> Self::IntoIter {
1333 self.entries.values()
1334 }
1335}
1336
1337#[derive(Debug, Clone)]
1338pub struct FdTableManager {
1339 tables: BTreeMap<u32, ProcessFdTable>,
1340 alloc_desc: DescriptionFactory,
1341 max_fds: usize,
1342}
1343
1344impl Default for FdTableManager {
1345 fn default() -> Self {
1346 Self {
1347 tables: BTreeMap::new(),
1348 alloc_desc: DescriptionFactory::new(1),
1349 max_fds: MAX_FDS_PER_PROCESS,
1350 }
1351 }
1352}
1353
1354impl FdTableManager {
1355 pub fn new() -> Self {
1356 Self::default()
1357 }
1358
1359 pub fn with_max_fds(max_fds: usize) -> Self {
1360 Self {
1361 max_fds,
1362 ..Self::default()
1363 }
1364 }
1365
1366 pub fn create(&mut self, pid: u32) -> &mut ProcessFdTable {
1367 let mut table = ProcessFdTable::new(self.alloc_desc.clone(), self.max_fds);
1368 table.init_stdio(
1369 self.alloc_desc.allocate("/dev/stdin", O_RDONLY),
1370 self.alloc_desc.allocate("/dev/stdout", O_WRONLY),
1371 self.alloc_desc.allocate("/dev/stderr", O_WRONLY),
1372 );
1373 self.remove(pid);
1374 self.tables.insert(pid, table);
1375 self.tables
1376 .get_mut(&pid)
1377 .expect("newly created FD table should be stored")
1378 }
1379
1380 pub fn create_with_stdio(
1381 &mut self,
1382 pid: u32,
1383 stdin_override: Option<StdioOverride>,
1384 stdout_override: Option<StdioOverride>,
1385 stderr_override: Option<StdioOverride>,
1386 ) -> &mut ProcessFdTable {
1387 let mut table = ProcessFdTable::new(self.alloc_desc.clone(), self.max_fds);
1388 let stdin_desc = stdin_override
1389 .as_ref()
1390 .map(|entry| Arc::clone(&entry.description))
1391 .unwrap_or_else(|| self.alloc_desc.allocate("/dev/stdin", O_RDONLY));
1392 let stdout_desc = stdout_override
1393 .as_ref()
1394 .map(|entry| Arc::clone(&entry.description))
1395 .unwrap_or_else(|| self.alloc_desc.allocate("/dev/stdout", O_WRONLY));
1396 let stderr_desc = stderr_override
1397 .as_ref()
1398 .map(|entry| Arc::clone(&entry.description))
1399 .unwrap_or_else(|| self.alloc_desc.allocate("/dev/stderr", O_WRONLY));
1400
1401 table.init_stdio_with_types(
1402 stdin_desc,
1403 stdin_override
1404 .as_ref()
1405 .map(|entry| entry.filetype)
1406 .unwrap_or(FILETYPE_CHARACTER_DEVICE),
1407 stdout_desc,
1408 stdout_override
1409 .as_ref()
1410 .map(|entry| entry.filetype)
1411 .unwrap_or(FILETYPE_CHARACTER_DEVICE),
1412 stderr_desc,
1413 stderr_override
1414 .as_ref()
1415 .map(|entry| entry.filetype)
1416 .unwrap_or(FILETYPE_CHARACTER_DEVICE),
1417 );
1418 self.remove(pid);
1419 self.tables.insert(pid, table);
1420 self.tables
1421 .get_mut(&pid)
1422 .expect("newly created FD table should be stored")
1423 }
1424
1425 pub fn fork(&mut self, parent_pid: u32, child_pid: u32) -> &mut ProcessFdTable {
1426 self.fork_with_cloexec(parent_pid, child_pid, false)
1427 }
1428
1429 pub fn fork_preserving_cloexec(
1430 &mut self,
1431 parent_pid: u32,
1432 child_pid: u32,
1433 ) -> &mut ProcessFdTable {
1434 self.fork_with_cloexec(parent_pid, child_pid, true)
1435 }
1436
1437 fn fork_with_cloexec(
1438 &mut self,
1439 parent_pid: u32,
1440 child_pid: u32,
1441 preserve_cloexec: bool,
1442 ) -> &mut ProcessFdTable {
1443 if !self.tables.contains_key(&parent_pid) {
1444 return self.create(child_pid);
1445 }
1446
1447 let parent = self
1448 .tables
1449 .get(&parent_pid)
1450 .expect("parent table presence was checked");
1451 let child = if preserve_cloexec {
1452 parent.fork_preserving_cloexec()
1453 } else {
1454 parent.fork()
1455 };
1456 self.remove(child_pid);
1457 self.tables.insert(child_pid, child);
1458 self.tables
1459 .get_mut(&child_pid)
1460 .expect("forked FD table should be stored")
1461 }
1462
1463 pub fn get(&self, pid: u32) -> Option<&ProcessFdTable> {
1464 self.tables.get(&pid)
1465 }
1466
1467 pub fn get_mut(&mut self, pid: u32) -> Option<&mut ProcessFdTable> {
1468 self.tables.get_mut(&pid)
1469 }
1470
1471 pub fn has(&self, pid: u32) -> bool {
1472 self.tables.contains_key(&pid)
1473 }
1474
1475 pub fn len(&self) -> usize {
1476 self.tables.len()
1477 }
1478
1479 pub fn is_empty(&self) -> bool {
1480 self.tables.is_empty()
1481 }
1482
1483 pub fn total_open_fds(&self) -> usize {
1484 self.tables.values().map(ProcessFdTable::len).sum()
1485 }
1486
1487 pub fn pids(&self) -> Vec<u32> {
1488 self.tables.keys().copied().collect()
1489 }
1490
1491 pub fn remove(&mut self, pid: u32) {
1492 if let Some(mut table) = self.tables.remove(&pid) {
1493 table.close_all();
1494 }
1495 }
1496}
1497
1498#[derive(Debug, Clone, Default)]
1499pub struct FileLockManager {
1500 inner: Arc<FileLockManagerInner>,
1501}
1502
1503#[derive(Debug)]
1504struct FileLockManagerInner {
1505 state: Mutex<FileLockState>,
1506 wake: Condvar,
1507 max_record_locks: usize,
1508}
1509
1510impl Default for FileLockManagerInner {
1511 fn default() -> Self {
1512 Self {
1513 state: Mutex::new(FileLockState::default()),
1514 wake: Condvar::new(),
1515 max_record_locks: DEFAULT_MAX_RECORD_LOCKS,
1516 }
1517 }
1518}
1519
1520#[derive(Debug, Default)]
1521struct FileLockState {
1522 entries: BTreeMap<FileLockTarget, FileLockEntry>,
1523 record_locks: Vec<(FileLockTarget, RecordLock)>,
1524 record_lock_waits: BTreeMap<u32, RecordLockWait>,
1525 warned_near_record_lock_limit: bool,
1526 warned_near_record_lock_wait_limit: bool,
1527}
1528
1529#[derive(Debug, Clone)]
1530struct RecordLockWait {
1531 target: FileLockTarget,
1532 request: RecordLock,
1533 blockers: BTreeSet<u32>,
1534}
1535
1536#[derive(Debug, Default)]
1537struct FileLockEntry {
1538 shared: BTreeSet<u64>,
1539 exclusive: Option<u64>,
1540}
1541
1542impl FileLockManager {
1543 pub fn new() -> Self {
1544 Self::with_record_lock_limit(DEFAULT_MAX_RECORD_LOCKS)
1545 }
1546
1547 pub fn with_record_lock_limit(max_record_locks: usize) -> Self {
1548 Self {
1549 inner: Arc::new(FileLockManagerInner {
1550 state: Mutex::new(FileLockState::default()),
1551 wake: Condvar::new(),
1552 max_record_locks: max_record_locks.max(1),
1553 }),
1554 }
1555 }
1556
1557 pub fn apply(
1558 &self,
1559 owner_id: u64,
1560 target: FileLockTarget,
1561 operation: FlockOperation,
1562 ) -> FdResult<()> {
1563 match operation {
1564 FlockOperation::Shared { nonblocking } => {
1565 self.acquire(owner_id, target, FileLockMode::Shared, nonblocking)
1566 }
1567 FlockOperation::Exclusive { nonblocking } => {
1568 self.acquire(owner_id, target, FileLockMode::Exclusive, nonblocking)
1569 }
1570 FlockOperation::Unlock => {
1571 self.release_owner(owner_id);
1572 Ok(())
1573 }
1574 }
1575 }
1576
1577 pub fn release_owner(&self, owner_id: u64) -> bool {
1578 let mut state = lock_or_recover(&self.inner.state);
1579 let mut released = false;
1580 state.entries.retain(|_, entry| {
1581 let entry_changed = entry.shared.remove(&owner_id) || entry.exclusive == Some(owner_id);
1582 if entry.exclusive == Some(owner_id) {
1583 entry.exclusive = None;
1584 }
1585 released |= entry_changed;
1586 !entry.is_empty()
1587 });
1588 drop(state);
1589 if released {
1590 self.inner.wake.notify_all();
1591 }
1592 released
1593 }
1594
1595 pub fn query_record_lock(
1596 &self,
1597 target: FileLockTarget,
1598 request: RecordLock,
1599 ) -> Option<RecordLock> {
1600 let state = lock_or_recover(&self.inner.state);
1601 state
1602 .record_locks
1603 .iter()
1604 .filter_map(|(candidate_target, candidate)| {
1605 (*candidate_target == target
1606 && candidate.pid != request.pid
1607 && record_locks_conflict(*candidate, request))
1608 .then_some(*candidate)
1609 })
1610 .min_by_key(|candidate| (candidate.start, candidate.pid))
1611 }
1612
1613 pub fn set_record_lock(&self, target: FileLockTarget, request: RecordLock) -> FdResult<()> {
1614 self.set_record_lock_inner(target, request, false)
1615 }
1616
1617 pub fn set_blocking_record_lock(
1618 &self,
1619 target: FileLockTarget,
1620 request: RecordLock,
1621 ) -> FdResult<()> {
1622 self.set_record_lock_inner(target, request, true)
1623 }
1624
1625 fn set_record_lock_inner(
1626 &self,
1627 target: FileLockTarget,
1628 request: RecordLock,
1629 blocking: bool,
1630 ) -> FdResult<()> {
1631 let mut state = lock_or_recover(&self.inner.state);
1632 let blockers = if request.lock_type == RecordLockType::Unlock {
1633 BTreeSet::new()
1634 } else {
1635 conflicting_record_lock_pids(&state.record_locks, target, request)
1636 };
1637 if !blockers.is_empty() {
1638 if blocking {
1639 self.register_record_lock_wait(&mut state, target, request, blockers)?;
1640 } else {
1641 state.record_lock_waits.remove(&request.pid);
1642 }
1643 return Err(FdTableError::would_block(
1644 "POSIX record lock is held by another process",
1645 ));
1646 }
1647
1648 state.record_lock_waits.remove(&request.pid);
1649
1650 let mut next = Vec::with_capacity(state.record_locks.len().saturating_add(2));
1651 for (candidate_target, candidate) in state.record_locks.iter().copied() {
1652 if candidate_target != target
1653 || candidate.pid != request.pid
1654 || !record_ranges_overlap(candidate, request)
1655 {
1656 next.push((candidate_target, candidate));
1657 continue;
1658 }
1659
1660 if candidate.start < request.start {
1661 next.push((
1662 target,
1663 RecordLock {
1664 end: Some(request.start),
1665 ..candidate
1666 },
1667 ));
1668 }
1669 if let Some(request_end) = request.end {
1670 if candidate
1671 .end
1672 .is_none_or(|candidate_end| candidate_end > request_end)
1673 {
1674 next.push((
1675 target,
1676 RecordLock {
1677 start: request_end,
1678 ..candidate
1679 },
1680 ));
1681 }
1682 }
1683 }
1684
1685 if request.lock_type != RecordLockType::Unlock {
1686 next.push((target, request));
1687 }
1688 coalesce_record_locks(&mut next);
1689 let warning_threshold = (self.inner.max_record_locks.saturating_mul(4) / 5).max(1);
1690 if !state.warned_near_record_lock_limit && next.len() >= warning_threshold {
1691 state.warned_near_record_lock_limit = true;
1692 eprintln!(
1693 "[agentos] POSIX record lock usage {}/{} is near the limit derived from limits.resources.maxOpenFds; raise limits.resources.maxOpenFds if needed",
1694 next.len(), self.inner.max_record_locks
1695 );
1696 }
1697 if next.len() > self.inner.max_record_locks {
1698 return Err(FdTableError {
1699 code: "ENOLCK",
1700 message: format!(
1701 "POSIX record lock table limit ({}) derived from limits.resources.maxOpenFds reached; raise limits.resources.maxOpenFds if needed",
1702 self.inner.max_record_locks
1703 ),
1704 });
1705 }
1706 state.record_locks = next;
1707 refresh_record_lock_waits(&mut state);
1708 drop(state);
1709 self.inner.wake.notify_all();
1710 Ok(())
1711 }
1712
1713 pub fn cancel_record_lock_wait(&self, pid: u32) -> bool {
1714 let mut state = lock_or_recover(&self.inner.state);
1715 state.record_lock_waits.remove(&pid).is_some()
1716 }
1717
1718 pub fn release_process_target(&self, pid: u32, target: FileLockTarget) -> bool {
1721 let mut state = lock_or_recover(&self.inner.state);
1722 let previous = state.record_locks.len();
1723 state
1724 .record_locks
1725 .retain(|(candidate_target, lock)| *candidate_target != target || lock.pid != pid);
1726 let wait_cancelled = state.record_lock_waits.remove(&pid).is_some();
1727 let released = state.record_locks.len() != previous;
1728 if released || wait_cancelled {
1729 refresh_record_lock_waits(&mut state);
1730 }
1731 drop(state);
1732 if released || wait_cancelled {
1733 self.inner.wake.notify_all();
1734 }
1735 released || wait_cancelled
1736 }
1737
1738 pub fn release_process(&self, pid: u32) -> bool {
1739 let mut state = lock_or_recover(&self.inner.state);
1740 let previous = state.record_locks.len();
1741 state.record_locks.retain(|(_, lock)| lock.pid != pid);
1742 let wait_cancelled = state.record_lock_waits.remove(&pid).is_some();
1743 let released = state.record_locks.len() != previous;
1744 if released || wait_cancelled {
1745 refresh_record_lock_waits(&mut state);
1746 }
1747 drop(state);
1748 if released || wait_cancelled {
1749 self.inner.wake.notify_all();
1750 }
1751 released || wait_cancelled
1752 }
1753
1754 fn register_record_lock_wait(
1755 &self,
1756 state: &mut FileLockState,
1757 target: FileLockTarget,
1758 request: RecordLock,
1759 blockers: BTreeSet<u32>,
1760 ) -> FdResult<()> {
1761 let new_waiter = !state.record_lock_waits.contains_key(&request.pid);
1762 let waiter_count = state.record_lock_waits.len() + usize::from(new_waiter);
1763 if new_waiter && waiter_count > self.inner.max_record_locks {
1764 return Err(FdTableError {
1765 code: "ENOLCK",
1766 message: format!(
1767 "POSIX record lock waiter limit ({}) derived from limits.resources.maxOpenFds reached; raise limits.resources.maxOpenFds if needed",
1768 self.inner.max_record_locks
1769 ),
1770 });
1771 }
1772 let warning_threshold = (self.inner.max_record_locks.saturating_mul(4) / 5).max(1);
1773 if !state.warned_near_record_lock_wait_limit && waiter_count >= warning_threshold {
1774 state.warned_near_record_lock_wait_limit = true;
1775 eprintln!(
1776 "[agentos] POSIX record lock waiter usage {}/{} is near the limit derived from limits.resources.maxOpenFds; raise limits.resources.maxOpenFds if needed",
1777 waiter_count,
1778 self.inner.max_record_locks
1779 );
1780 }
1781 state.record_lock_waits.insert(
1782 request.pid,
1783 RecordLockWait {
1784 target,
1785 request,
1786 blockers,
1787 },
1788 );
1789 if record_lock_wait_cycle(state, request.pid) {
1790 state.record_lock_waits.remove(&request.pid);
1791 return Err(FdTableError::deadlock(
1792 "POSIX record lock wait would create a deadlock",
1793 ));
1794 }
1795 Ok(())
1796 }
1797
1798 fn acquire(
1799 &self,
1800 owner_id: u64,
1801 target: FileLockTarget,
1802 mode: FileLockMode,
1803 nonblocking: bool,
1804 ) -> FdResult<()> {
1805 let mut state = lock_or_recover(&self.inner.state);
1806 loop {
1807 let entry = state.entries.entry(target).or_default();
1808 if entry.can_grant(owner_id, mode) {
1809 entry.grant(owner_id, mode);
1810 return Ok(());
1811 }
1812
1813 if nonblocking {
1814 return Err(FdTableError::would_block(
1815 "advisory file lock is unavailable",
1816 ));
1817 }
1818
1819 state = wait_or_recover(&self.inner.wake, state);
1820 }
1821 }
1822}
1823
1824fn record_ranges_overlap(left: RecordLock, right: RecordLock) -> bool {
1825 left.end.is_none_or(|end| right.start < end) && right.end.is_none_or(|end| left.start < end)
1826}
1827
1828fn record_locks_conflict(left: RecordLock, right: RecordLock) -> bool {
1829 record_ranges_overlap(left, right)
1830 && (left.lock_type == RecordLockType::Write || right.lock_type == RecordLockType::Write)
1831}
1832
1833fn conflicting_record_lock_pids(
1834 locks: &[(FileLockTarget, RecordLock)],
1835 target: FileLockTarget,
1836 request: RecordLock,
1837) -> BTreeSet<u32> {
1838 locks
1839 .iter()
1840 .filter_map(|(candidate_target, candidate)| {
1841 (*candidate_target == target
1842 && candidate.pid != request.pid
1843 && record_locks_conflict(*candidate, request))
1844 .then_some(candidate.pid)
1845 })
1846 .collect()
1847}
1848
1849fn refresh_record_lock_waits(state: &mut FileLockState) {
1850 let FileLockState {
1851 record_locks,
1852 record_lock_waits,
1853 ..
1854 } = state;
1855 for wait in record_lock_waits.values_mut() {
1856 wait.blockers = conflicting_record_lock_pids(record_locks, wait.target, wait.request);
1857 }
1858}
1859
1860fn record_lock_wait_cycle(state: &FileLockState, requester: u32) -> bool {
1861 let Some(wait) = state.record_lock_waits.get(&requester) else {
1862 return false;
1863 };
1864 let mut pending = wait.blockers.iter().copied().collect::<Vec<_>>();
1865 let mut visited = BTreeSet::new();
1866 while let Some(pid) = pending.pop() {
1867 if pid == requester {
1868 return true;
1869 }
1870 if !visited.insert(pid) {
1871 continue;
1872 }
1873 if let Some(wait) = state.record_lock_waits.get(&pid) {
1874 pending.extend(wait.blockers.iter().copied());
1875 }
1876 }
1877 false
1878}
1879
1880fn coalesce_record_locks(locks: &mut Vec<(FileLockTarget, RecordLock)>) {
1881 locks.sort_by_key(|(target, lock)| (*target, lock.pid, lock.lock_type as u8, lock.start));
1882 let mut merged: Vec<(FileLockTarget, RecordLock)> = Vec::with_capacity(locks.len());
1883 for (target, lock) in locks.drain(..) {
1884 if let Some((previous_target, previous)) = merged.last_mut() {
1885 let touches = previous.end.is_none_or(|end| lock.start <= end);
1886 if *previous_target == target
1887 && previous.pid == lock.pid
1888 && previous.lock_type == lock.lock_type
1889 && touches
1890 {
1891 previous.end = match (previous.end, lock.end) {
1892 (None, _) | (_, None) => None,
1893 (Some(left), Some(right)) => Some(left.max(right)),
1894 };
1895 continue;
1896 }
1897 }
1898 merged.push((target, lock));
1899 }
1900 *locks = merged;
1901}
1902
1903impl FileLockEntry {
1904 fn can_grant(&self, owner_id: u64, mode: FileLockMode) -> bool {
1905 match mode {
1906 FileLockMode::Shared => self.exclusive.is_none_or(|owner| owner == owner_id),
1907 FileLockMode::Exclusive => {
1908 self.exclusive.is_none_or(|owner| owner == owner_id)
1909 && self.shared.iter().all(|owner| *owner == owner_id)
1910 }
1911 }
1912 }
1913
1914 fn grant(&mut self, owner_id: u64, mode: FileLockMode) {
1915 match mode {
1916 FileLockMode::Shared => {
1917 self.exclusive = None;
1918 self.shared.insert(owner_id);
1919 }
1920 FileLockMode::Exclusive => {
1921 self.shared.retain(|owner| *owner != owner_id);
1922 self.exclusive = Some(owner_id);
1923 }
1924 }
1925 }
1926
1927 fn is_empty(&self) -> bool {
1928 self.exclusive.is_none() && self.shared.is_empty()
1929 }
1930}
1931
1932fn lock_or_recover<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
1933 mutex
1934 .lock()
1935 .unwrap_or_else(|poisoned| poisoned.into_inner())
1936}
1937
1938fn wait_or_recover<'a, T>(condvar: &Condvar, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> {
1939 condvar
1940 .wait(guard)
1941 .unwrap_or_else(|poisoned| poisoned.into_inner())
1942}