1use std::collections::BTreeMap;
12use std::collections::BTreeSet;
13use std::collections::HashMap;
14use std::os::fd::BorrowedFd;
15use std::path::Path;
16use std::sync::Arc;
17use std::sync::Mutex;
18use std::sync::MutexGuard;
19use std::time::Duration;
20
21use detcore_model::pedigree::Pedigree;
22use detcore_model::summary::TimesliceStats;
23use detcore_model::syscall_histogram::SyscallHistogram;
24use nix::fcntl::OFlag;
25use nix::sys::stat;
26use nix::unistd::Pid;
27use rand::Rng as _;
28use rand::RngExt as _;
29use rand::SeedableRng;
30use rand_distr::Distribution;
31use rand_distr::Exp;
32use rand_pcg::Pcg64Mcg;
33use reverie::Errno;
34use reverie::Error;
35use reverie::Guest;
36use reverie::syscalls::CloneFlags;
37use reverie::syscalls::Syscall;
38use reverie::syscalls::Sysno;
39use serde::Deserialize;
40use serde::Serialize;
41use sha2::Digest as _;
42use sha2::Sha256;
43use tracing::debug;
44use tracing::info;
45
46use crate::config::Config;
47use crate::detlog;
48use crate::fd::*;
49use crate::memory::MemoryMetadata;
50use crate::preemptions::ThreadHistoryIterator;
51use crate::record_or_replay::NoopTool;
52use crate::record_or_replay::RecordOrReplay;
53use crate::resources::ChaosEpochTransition;
54use crate::resources::Device;
55use crate::resources::ExternalOpId;
56use crate::resources::Permission;
57use crate::resources::ResourceID;
58use crate::resources::Resources;
59use crate::scheduler::Priority;
60use crate::stat::*;
61use crate::types::*;
62
63#[derive(Debug, Serialize, Deserialize)]
65pub struct Detcore<T = NoopTool> {
66 pub(crate) detpid: DetPid,
72
73 pub(crate) cfg: Config,
75
76 pub(crate) record_or_replay: T,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct FileMetadata {
85 pub(crate) files_id: FilesId,
87 next_open_file_sequence: u64,
89 #[serde(default)]
91 next_socket_open_file_sequence: u64,
92 #[serde(default)]
95 next_random_device_sequence: u64,
96 #[serde(default, rename = "has_promoted_random_files")]
101 has_promoted_shared_files: bool,
102 #[serde(default)]
110 has_shared_procfs_sequences: bool,
111 #[serde(deserialize_with = "deserialize_interned_file_handles")]
115 pub(crate) file_handles: HashMap<RawFd, DetFd>,
116 #[serde(skip)]
123 accept_waits: Vec<(u64, RawFd)>,
124 #[serde(skip)]
126 next_accept_wait: u64,
127 #[serde(default)]
137 cpuinfo_placeholders: Option<bool>,
138 #[serde(default)]
145 pending_cpuinfo_placeholders: Vec<RawFd>,
146 #[serde(default)]
158 proc_level: Option<detcore_model::config::ProcLevel>,
159 #[serde(default)]
167 present_unmodelled: BTreeMap<RawFd, bool>,
168 #[serde(default)]
174 unaccounted_receipts: bool,
175 #[serde(default = "default_descriptor_capacity")]
182 descriptor_capacity: u64,
183 #[serde(default)]
190 exec_dropped_lock_tracked: bool,
191 #[serde(skip)]
198 released_rights: ReleasedRights,
199 #[serde(default)]
210 holders: BTreeMap<DetPid, usize>,
211 #[serde(skip)]
221 exec_replaced: ReplacedTable,
222}
223
224#[derive(Default)]
228struct ReplacedTable(Option<(Arc<Mutex<FileMetadata>>, DetPid)>);
229
230impl Clone for ReplacedTable {
231 fn clone(&self) -> Self {
232 Self::default()
233 }
234}
235
236impl std::fmt::Debug for ReplacedTable {
237 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238 f.debug_tuple("ReplacedTable")
239 .field(&self.0.is_some())
240 .finish()
241 }
242}
243
244#[derive(Debug)]
247pub(crate) enum TableAtDeath {
248 Released(Vec<SentDescriptor>),
252 Kept,
254 Undecided,
257}
258
259#[derive(Debug, Default)]
263struct ReleasedRights(Vec<SentDescriptor>);
264
265impl Clone for ReleasedRights {
266 fn clone(&self) -> Self {
267 Self::default()
268 }
269}
270
271fn default_descriptor_capacity() -> u64 {
273 64
274}
275
276fn deserialize_interned_file_handles<'de, D>(
280 deserializer: D,
281) -> Result<HashMap<RawFd, DetFd>, D::Error>
282where
283 D: serde::Deserializer<'de>,
284{
285 let mut handles = <HashMap<RawFd, DetFd> as Deserialize>::deserialize(deserializer)?;
286 crate::fd::intern_open_files(&mut handles);
287 Ok(handles)
288}
289
290#[derive(Debug)]
295pub(crate) struct CapturedDetFd {
296 files_id: FilesId,
297 detfd: DetFd,
298}
299
300#[derive(Debug)]
304pub(crate) struct CapturedDetFdInstallError {
305 pub(crate) expected_files_id: FilesId,
306 pub(crate) actual_files_id: FilesId,
307 returned_fd: RawFd,
308 pub(crate) captured: CapturedDetFd,
309}
310
311#[derive(Debug)]
314pub(crate) struct CapturedDetFdInstallCleanup {
315 pub(crate) close_fd: RawFd,
317 pub(crate) release_open_file: Option<OpenFileId>,
319 pub(crate) released_rights: Vec<SentDescriptor>,
324}
325
326impl CapturedDetFdInstallError {
327 pub(crate) fn into_cleanup(self) -> CapturedDetFdInstallCleanup {
329 let last = self.captured.detfd.is_last_reference();
330 let release_open_file = last.then(|| self.captured.detfd.open_file_id());
331 let released_rights = if last {
332 self.captured.detfd.take_all_queued_rights()
333 } else {
334 Vec::new()
335 };
336 drop(self.captured);
337 CapturedDetFdInstallCleanup {
338 close_fd: self.returned_fd,
339 release_open_file,
340 released_rights,
341 }
342 }
343}
344
345pub(crate) fn pidfd_getfd_targets_calling_task(
353 target: Option<DetPid>,
354 current_tgid: DetPid,
355 current_tid: DetTid,
356) -> bool {
357 target == Some(current_tgid) && current_tid == current_tgid
358}
359
360pub type ExecFdBlockingOverrides = BTreeSet<RawFd>;
365
366#[derive(Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
370pub struct ExecFdState {
371 pub(crate) blocking: ExecFdBlockingOverrides,
372 pub(crate) inherited_reads: BTreeMap<RawFd, crate::fd::InheritedExternalRead>,
373 pub(crate) initialize_inherited_stdio: bool,
375 #[serde(default)]
377 pub(crate) initial_input: Option<reverie::InheritedInputIdentity>,
378 #[serde(default)]
385 pub(crate) robust_wakes: Vec<(DetTid, FutexID)>,
386 #[serde(default)]
389 pub(crate) open_files: Option<ExecOpenFiles>,
390 #[serde(default)]
393 pub(crate) maps: Option<ExecMapsFiles>,
394 #[serde(default)]
400 pub(crate) procfs_records: Option<ExecProcfsRecords>,
401 #[serde(default)]
405 pub(crate) channels: BTreeMap<RawFd, crate::pipe_wake::ChannelEnd>,
406 #[serde(default)]
411 pub(crate) open_file_namespace: Option<(DetTid, u64)>,
412 #[serde(default)]
417 pub(crate) random_device_sequence: u64,
418 #[serde(default)]
424 pub(crate) exe: Option<crate::exec_record::ExecRecord>,
425}
426
427impl std::fmt::Debug for ExecFdState {
430 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
431 let mut debug = f.debug_struct("ExecFdState");
432 debug
433 .field("blocking", &self.blocking)
434 .field("inherited_reads", &self.inherited_reads)
435 .field(
436 "initialize_inherited_stdio",
437 &self.initialize_inherited_stdio,
438 )
439 .field("initial_input", &self.initial_input)
440 .field("robust_wakes", &self.robust_wakes)
441 .field("open_files", &self.open_files);
442 if let Some(maps) = &self.maps {
443 debug.field("maps_survivors", &maps.handles.len());
446 }
447 if let Some(records) = &self.procfs_records {
448 debug.field("procfs_record_survivors", &records.handles.len());
449 }
450 if !self.channels.is_empty() {
451 debug.field("channels", &self.channels);
452 }
453 if let Some(namespace) = &self.open_file_namespace {
454 debug.field("open_file_namespace", namespace);
455 }
456 if self.random_device_sequence != 0 {
457 debug.field("random_device_sequence", &self.random_device_sequence);
458 }
459 if let Some(exe) = &self.exe {
460 debug.field("exe", &exe.exe);
461 }
462 debug.finish()
463 }
464}
465
466#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
467pub(crate) struct ExecOpenFiles {
468 pub(crate) creator: DetTid,
469 next_sequence: u64,
470 next_socket_sequence: u64,
471 handles: BTreeMap<RawFd, ExecOpenFile>,
472}
473
474#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
475pub(crate) struct ExecMapsFiles {
476 creator: DetTid,
477 next_sequence: u64,
478 handles: BTreeMap<RawFd, ExecMapsFile>,
479}
480
481#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
482struct ExecMapsFile {
483 identity: ExecOpenFile,
484 procfs: crate::procfs::ProcfsFile,
485 flags: i32,
486 path: Option<std::path::PathBuf>,
487}
488
489#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
494pub(crate) struct ExecProcfsRecords {
495 creator: DetTid,
496 next_sequence: u64,
497 handles: BTreeMap<RawFd, ExecProcfsRecord>,
498}
499
500#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
507struct ExecProcfsRecord {
508 identity: ExecOpenFile,
509 procfs: crate::procfs::ProcfsFile,
510 sequence: crate::procfs::ProcfsSequence,
511 flags: i32,
512 path: Option<std::path::PathBuf>,
513 sequence_unknown: bool,
514}
515
516#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
517struct ExecOpenFile {
518 id: OpenFileId,
519 dev: u64,
520 ino: u64,
521 rdev: u64,
522 file_type: u32,
523 access: i32,
524}
525
526impl std::fmt::Debug for ExecOpenFile {
527 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
528 f.debug_struct("ExecOpenFile")
529 .field("id", &self.id)
530 .finish_non_exhaustive()
531 }
532}
533
534impl ExecOpenFile {
535 fn capture(fd: RawFd, id: OpenFileId) -> Result<Self, Errno> {
536 let mut stat: libc::stat = unsafe { std::mem::zeroed() };
537 let result = unsafe {
539 crate::util::raw_syscall(
540 libc::SYS_fstat,
541 [fd as u64, (&raw mut stat) as u64, 0, 0, 0, 0],
542 )
543 };
544 if result < 0 {
545 return Err(Errno::new(-result as i32));
546 }
547 let flags = unsafe {
548 crate::util::raw_syscall(
549 libc::SYS_fcntl,
550 [fd as u64, libc::F_GETFL as u64, 0, 0, 0, 0],
551 )
552 };
553 if flags < 0 {
554 return Err(Errno::new(-flags as i32));
555 }
556 Ok(Self {
557 id,
558 dev: stat.st_dev,
559 ino: stat.st_ino,
560 rdev: stat.st_rdev,
561 file_type: stat.st_mode & libc::S_IFMT,
562 access: flags as i32 & libc::O_ACCMODE,
563 })
564 }
565}
566
567impl ExecFdState {
568 pub(crate) fn open_file_creator(&self) -> Option<DetTid> {
569 self.open_files
570 .as_ref()
571 .map(|files| files.creator)
572 .or_else(|| self.open_file_namespace.map(|(creator, _)| creator))
573 .or_else(|| self.maps.as_ref().map(|maps| maps.creator))
574 .or_else(|| self.procfs_records.as_ref().map(|records| records.creator))
575 }
576
577 pub(crate) fn is_empty(&self) -> bool {
578 self.blocking.is_empty()
579 && self.inherited_reads.is_empty()
580 && !self.initialize_inherited_stdio
581 && self.initial_input.is_none()
582 && self.open_files.is_none()
583 && self.maps.is_none()
584 && self.procfs_records.is_none()
585 && self.channels.is_empty()
586 && self.open_file_namespace.is_none()
587 && self.random_device_sequence == 0
588 && self.exe.is_none()
589 }
590}
591
592impl From<ExecFdBlockingOverrides> for ExecFdState {
593 fn from(blocking: ExecFdBlockingOverrides) -> Self {
594 Self {
595 blocking,
596 ..Self::default()
597 }
598 }
599}
600
601pub(crate) fn flatten_exec_robust_list_wakes(
604 wakes: Vec<(DetTid, Vec<RobustListWake>)>,
605) -> Vec<(DetTid, RobustListWake)> {
606 wakes
607 .into_iter()
608 .flat_map(|(owner, mut wakes)| {
609 wakes.sort_by_key(|wake| format!("{:?}", wake.futex));
610 wakes.into_iter().map(move |wake| (owner, wake))
611 })
612 .collect()
613}
614
615#[derive(Debug, Clone, Serialize, Deserialize)]
622struct PosixTimer {
623 interval_ns: u64,
625 deadline: Option<LogicalTime>,
628 signal: Option<i32>,
631 #[serde(default)]
634 thread: Option<DetTid>,
635 #[serde(default)]
638 value: u64,
639 #[serde(default)]
642 cpu_clock: bool,
643}
644
645#[derive(Debug, Clone, Default, Serialize, Deserialize)]
652pub struct PosixTimers {
653 next_id: i32,
656 timers: HashMap<i32, PosixTimer>,
657}
658
659impl PosixTimers {
660 pub(crate) fn create(
668 &mut self,
669 signal: Option<i32>,
670 thread: Option<DetTid>,
671 value: Option<u64>,
672 cpu_clock: bool,
673 ) -> i32 {
674 let id = self.next_id;
675 self.next_id += 1;
676 self.timers.insert(
677 id,
678 PosixTimer {
679 interval_ns: 0,
680 deadline: None,
681 signal,
682 thread,
683 value: value.unwrap_or(id as u32 as u64),
684 cpu_clock,
685 },
686 );
687 id
688 }
689
690 pub(crate) fn on_cpu_clock(&self, id: i32) -> bool {
692 self.timers.get(&id).is_some_and(|timer| timer.cpu_clock)
693 }
694
695 pub(crate) fn value(&self, id: i32) -> Option<u64> {
697 self.timers.get(&id).map(|timer| timer.value)
698 }
699
700 pub(crate) fn settime(
706 &mut self,
707 id: i32,
708 interval_ns: u64,
709 deadline: Option<LogicalTime>,
710 now: LogicalTime,
711 ) -> Option<(u64, u64)> {
712 let timer = self.timers.get_mut(&id)?;
713 let old = (
714 remaining_ns(timer.deadline, timer.interval_ns, now),
715 timer.interval_ns,
716 );
717 timer.interval_ns = interval_ns;
718 timer.deadline = deadline;
719 Some(old)
720 }
721
722 pub(crate) fn gettime(&self, id: i32, now: LogicalTime) -> Option<(u64, u64)> {
725 let timer = self.timers.get(&id)?;
726 Some((
727 remaining_ns(timer.deadline, timer.interval_ns, now),
728 timer.interval_ns,
729 ))
730 }
731
732 pub(crate) fn contains(&self, id: i32) -> bool {
734 self.timers.contains_key(&id)
735 }
736
737 pub(crate) fn signal(&self, id: i32) -> Option<Option<i32>> {
740 self.timers.get(&id).map(|timer| timer.signal)
741 }
742
743 pub(crate) fn thread(&self, id: i32) -> Option<DetTid> {
745 self.timers.get(&id).and_then(|timer| timer.thread)
746 }
747
748 pub(crate) fn remove(&mut self, id: i32) -> bool {
750 self.timers.remove(&id).is_some()
751 }
752
753 pub(crate) fn clear_for_exec(&mut self) {
756 self.timers.clear();
757 }
758}
759
760#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
762pub(crate) struct ResourceLimit {
763 pub(crate) current: u64,
764 pub(crate) maximum: u64,
765}
766
767#[derive(Debug, Clone, Serialize, Deserialize)]
769pub(crate) struct ResourceLimits {
770 limits: Vec<ResourceLimit>,
771}
772
773impl Default for ResourceLimits {
774 fn default() -> Self {
775 let unlimited = ResourceLimit {
776 current: libc::RLIM64_INFINITY,
777 maximum: libc::RLIM64_INFINITY,
778 };
779 let mut limits = vec![unlimited; reverie::syscalls::linux_abi::RLIMIT_RTTIME as usize + 1];
780 limits[reverie::syscalls::linux_abi::RLIMIT_STACK as usize] = ResourceLimit {
781 current: 8 * 1024 * 1024,
782 maximum: libc::RLIM64_INFINITY,
783 };
784 limits[reverie::syscalls::linux_abi::RLIMIT_NOFILE as usize] = ResourceLimit {
785 current: 1_048_576,
786 maximum: 1_048_576,
787 };
788 Self { limits }
789 }
790}
791
792pub fn initial_resource_limit(resource: u32) -> Option<(u64, u64)> {
801 ResourceLimits::default()
802 .get(resource)
803 .map(|limit| (limit.current, limit.maximum))
804}
805
806impl ResourceLimits {
807 pub(crate) fn get(&self, resource: u32) -> Option<ResourceLimit> {
809 self.limits.get(resource as usize).copied()
810 }
811
812 pub(crate) fn set(&mut self, resource: u32, limit: ResourceLimit) {
814 self.limits[resource as usize] = limit;
815 }
816}
817
818fn remaining_ns(deadline: Option<LogicalTime>, interval_ns: u64, now: LogicalTime) -> u64 {
822 match deadline {
823 Some(d) if d > now => d.as_nanos() - now.as_nanos(),
824 Some(d) if interval_ns != 0 => {
825 let elapsed = now.as_nanos() - d.as_nanos();
826 interval_ns - (elapsed % interval_ns)
827 }
828 Some(_) => 0,
829 None => 0,
830 }
831}
832
833impl<T> Default for Detcore<T> {
834 fn default() -> Self {
835 panic!("Detcore Default impl should not be called");
839 }
840}
841
842impl<T: RecordOrReplay> AsRef<T> for Detcore<T> {
843 fn as_ref(&self) -> &T {
844 &self.record_or_replay
845 }
846}
847
848impl<T: RecordOrReplay> AsMut<T> for Detcore<T> {
849 fn as_mut(&mut self) -> &mut T {
850 &mut self.record_or_replay
851 }
852}
853
854impl<T: RecordOrReplay> Detcore<T> {
855 pub(crate) async fn record_or_replay_preserving_tool_errors<G, S>(
859 &self,
860 guest: &mut G,
861 syscall: S,
862 ) -> Result<i64, Error>
863 where
864 G: Guest<Self>,
865 S: Into<Syscall>,
866 {
867 preserve_record_or_replay_error(
868 self.translated_record_or_replay(guest, syscall.into())
869 .await,
870 )?
871 .map_err(Error::from)
872 }
873
874 async fn translated_record_or_replay<G: Guest<Self>>(
879 &self,
880 guest: &mut G,
881 syscall: Syscall,
882 ) -> Result<i64, Error> {
883 let translated = if Self::may_translate_procfs_call(guest, &syscall) {
890 Box::pin(Self::procfs_translated_call(guest, &syscall)).await?
891 } else {
892 None
893 };
894 let call = translated
895 .as_ref()
896 .map_or(syscall, |translated| translated.call);
897 crate::reserved_input::before_native_call(guest, call)?;
898 let result = if crate::record_or_replay::injects_only::<T>() {
899 guest.inject(call).await.map_err(Error::from)
903 } else {
904 self.record_or_replay
905 .handle_syscall_event(&mut guest.into_guest(), call)
906 .await
907 };
908 if let Some(translated) = translated {
909 Box::pin(Self::finish_translated_call(guest, translated)).await;
910 }
911 result
912 }
913
914 pub(crate) async fn record_or_replay<G, S>(
931 &self,
932 guest: &mut G,
933 syscall: S,
934 ) -> Result<i64, Errno>
935 where
936 G: Guest<Self>,
937 S: Into<Syscall>,
938 {
939 self.translated_record_or_replay(guest, syscall.into())
940 .await
941 .map_err(|err| err.into_errno().unwrap())
943 }
944}
945
946fn preserve_record_or_replay_error(
947 result: Result<i64, Error>,
948) -> Result<Result<i64, Errno>, Error> {
949 match result {
950 Ok(value) => Ok(Ok(value)),
951 Err(Error::Errno(error)) => Ok(Err(error)),
952 Err(error) => Err(error),
953 }
954}
955
956pub(crate) fn finish_partial_record_or_replay_write(
961 written: i64,
962 error: Error,
963) -> Result<i64, Error> {
964 match error {
965 Error::Errno(_) if written > 0 => Ok(written),
966 error => Err(error),
967 }
968}
969
970#[cfg(test)]
971mod record_or_replay_error_tests {
972 use super::*;
973
974 #[test]
975 fn errno_remains_a_guest_syscall_result() {
976 let result = preserve_record_or_replay_error(Err(Error::Errno(Errno::EBADF)))
977 .expect("guest errno must not abort the tool");
978 assert_eq!(result, Err(Errno::EBADF));
979 }
980
981 #[test]
982 fn tool_failure_remains_an_outer_error() {
983 let failure =
984 preserve_record_or_replay_error(Err(Error::Tool(anyhow::anyhow!("restore failed"))))
985 .expect_err("tool failure must abort replay");
986 assert!(matches!(failure.into_errno(), Err(Error::Tool(_))));
987 }
988
989 #[test]
990 fn partial_write_suppresses_only_guest_errno() {
991 assert_eq!(
992 finish_partial_record_or_replay_write(7, Error::Errno(Errno::EPIPE)).unwrap(),
993 7
994 );
995
996 let io_failure = finish_partial_record_or_replay_write(
997 7,
998 Error::Io(std::io::Error::from_raw_os_error(libc::ENOSPC)),
999 )
1000 .expect_err("host replay-output failure must not become a partial guest write");
1001 assert!(matches!(io_failure.into_errno(), Err(Error::Io(_))));
1002
1003 let tool_failure = finish_partial_record_or_replay_write(
1004 7,
1005 Error::Tool(anyhow::anyhow!("replay endpoint disappeared")),
1006 )
1007 .expect_err("tool failure must not become a partial guest write");
1008 assert!(matches!(tool_failure.into_errno(), Err(Error::Tool(_))));
1009
1010 let guest_failure = finish_partial_record_or_replay_write(0, Error::Errno(Errno::EPIPE))
1011 .expect_err("zero-progress guest failure must remain guest-visible");
1012 assert_eq!(guest_failure.into_errno().unwrap(), Errno::EPIPE);
1013 }
1014}
1015
1016impl FileMetadata {
1017 pub(crate) fn new(owner: DetTid) -> Self {
1019 FileMetadata {
1020 files_id: FilesId::initial(owner),
1021 next_open_file_sequence: 0,
1022 next_socket_open_file_sequence: 0,
1023 next_random_device_sequence: 0,
1024 has_promoted_shared_files: false,
1025 has_shared_procfs_sequences: false,
1026 file_handles: HashMap::new(),
1027 accept_waits: Vec::new(),
1028 next_accept_wait: 0,
1029 cpuinfo_placeholders: None,
1030 pending_cpuinfo_placeholders: Vec::new(),
1031 proc_level: None,
1032 present_unmodelled: BTreeMap::new(),
1033 unaccounted_receipts: false,
1034 descriptor_capacity: default_descriptor_capacity(),
1035 exec_dropped_lock_tracked: false,
1036 released_rights: ReleasedRights::default(),
1037 holders: BTreeMap::from([(owner, 1)]),
1038 exec_replaced: ReplacedTable::default(),
1039 }
1040 }
1041
1042 fn note_installed(&mut self, fd: RawFd) {
1046 self.present_unmodelled.remove(&fd);
1047 self.grow_to_hold(fd);
1048 }
1049
1050 fn grow_to_hold(&mut self, fd: RawFd) {
1056 if let Ok(fd) = u64::try_from(fd)
1057 && fd >= self.descriptor_capacity
1058 {
1059 self.descriptor_capacity = (fd + 1)
1060 .checked_next_power_of_two()
1061 .unwrap_or(u64::MAX / 2)
1062 .max(default_descriptor_capacity());
1063 }
1064 }
1065
1066 fn set_unmodelled_cloexec(&mut self, fd: RawFd, cloexec: bool) -> bool {
1069 match self.present_unmodelled.get_mut(&fd) {
1070 Some(flag) => {
1071 *flag = cloexec;
1072 true
1073 }
1074 None => false,
1075 }
1076 }
1077
1078 fn descriptor_numbers(&self) -> Vec<RawFd> {
1081 let mut numbers: Vec<RawFd> = self
1082 .file_handles
1083 .keys()
1084 .copied()
1085 .chain(self.present_unmodelled.keys().copied())
1086 .collect();
1087 numbers.sort_unstable();
1088 numbers.dedup();
1089 numbers
1090 }
1091
1092 fn forked_descriptor_capacity(&self) -> u64 {
1098 let open = self
1099 .descriptor_numbers()
1100 .last()
1101 .and_then(|fd| u64::try_from(*fd).ok())
1102 .map_or(0, |fd| (fd + 1).next_multiple_of(64));
1103 if open <= default_descriptor_capacity() {
1104 default_descriptor_capacity()
1105 } else {
1106 open.checked_next_power_of_two().unwrap_or(u64::MAX / 2)
1107 }
1108 }
1109
1110 pub(crate) fn note_proc_level(&mut self, level: detcore_model::config::ProcLevel) {
1112 self.proc_level = Some(level);
1113 }
1114
1115 fn within_proc_level(
1119 &self,
1120 found: Option<(&'static std::path::Path, Vec<u8>)>,
1121 ) -> Option<(&'static std::path::Path, Vec<u8>)> {
1122 found.filter(|(path, _)| {
1123 self.proc_level.is_none_or(|level| {
1124 !crate::syscalls::rendered_procfs::named_file_outside_level(path, level)
1125 })
1126 })
1127 }
1128
1129 pub(crate) fn decide_cpuinfo_placeholders(&mut self, rendered: bool) -> Result<(), Errno> {
1138 if self.cpuinfo_placeholders.is_some() {
1139 return Ok(());
1140 }
1141 self.cpuinfo_placeholders = Some(rendered);
1142 let pending = std::mem::take(&mut self.pending_cpuinfo_placeholders);
1143 if !rendered {
1144 return Ok(());
1145 }
1146 for fd in pending {
1147 if !self.file_handles.contains_key(&fd) {
1148 continue;
1149 }
1150 let raw_stat =
1151 stat::fstat(unsafe { BorrowedFd::borrow_raw(fd) }).map_err(|_| Errno::last())?;
1152 let fd_flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
1153 if let Some((path, snapshot)) =
1154 self.within_proc_level(placeholder_behind(fd, crate::procfs::RenderedFiles::ALL))
1155 {
1156 self.adopt_placeholder(fd, &raw_stat, fd_flags, path, snapshot)?;
1157 }
1158 }
1159 Ok(())
1160 }
1161
1162 pub(crate) fn add_accept_wait(&mut self, listener: RawFd) -> u64 {
1165 let operation = self.next_accept_wait;
1166 self.next_accept_wait += 1;
1167 self.accept_waits.push((operation, listener));
1168 operation
1169 }
1170
1171 pub(crate) fn remove_accept_wait(&mut self, operation: u64) {
1173 self.accept_waits
1174 .retain(|(waiting, _)| *waiting != operation);
1175 }
1176
1177 pub(crate) fn waited_listener_in(&self, fds: std::ops::RangeInclusive<u32>) -> Option<RawFd> {
1179 self.accept_waits
1180 .iter()
1181 .map(|(_, listener)| *listener)
1182 .filter(|listener| u32::try_from(*listener).is_ok_and(|fd| fds.contains(&fd)))
1183 .min()
1184 }
1185
1186 fn allocate_open_file_id(&mut self, creator: DetTid, ty: FdType) -> OpenFileId {
1187 if ty == FdType::Socket {
1188 let id = OpenFileId::new_socket(creator, self.next_socket_open_file_sequence);
1189 self.next_socket_open_file_sequence += 1;
1190 id
1191 } else {
1192 let id = OpenFileId::new(creator, self.next_open_file_sequence);
1193 self.next_open_file_sequence += 1;
1194 id
1195 }
1196 }
1197
1198 fn count_open_files_at_paths(&self, paths: &[&Path]) -> usize {
1199 self.file_handles
1200 .values()
1201 .filter(|fd| {
1202 fd.path()
1203 .is_some_and(|path| paths.iter().any(|candidate| path == *candidate))
1204 })
1205 .map(DetFd::open_file_id)
1206 .collect::<BTreeSet<_>>()
1207 .len()
1208 }
1209
1210 fn has_loopback_peer(&self) -> bool {
1211 self.file_handles.values().any(DetFd::is_loopback_peer)
1212 }
1213
1214 fn has_unsafe_vfork_flock_state(&self) -> bool {
1259 self.file_handles.values().any(|detfd| {
1260 matches!(detfd.known_flock_mode(), Some(Some(_)))
1261 || (detfd.flock_mode_may_be_stale() && detfd.flock_may_have_been_granted())
1262 })
1263 }
1264
1265 fn has_unsafe_vfork_ofd_state(&self) -> bool {
1266 self.file_handles
1267 .values()
1268 .any(|detfd| detfd.may_hold_ofd_lock())
1269 }
1270
1271 fn forget_flock_modes(&self) {
1272 for detfd in self.file_handles.values() {
1273 detfd.forget_local_flock_mode_at_fork();
1274 }
1275 }
1276
1277 pub(crate) fn fork_for(&self, child: DetTid) -> Self {
1286 self.forget_flock_modes();
1287 Self {
1288 files_id: FilesId::forked(child),
1289 next_open_file_sequence: self.next_open_file_sequence,
1290 next_socket_open_file_sequence: self.next_socket_open_file_sequence,
1291 next_random_device_sequence: self.next_random_device_sequence,
1292 has_promoted_shared_files: self.has_promoted_shared_files,
1293 has_shared_procfs_sequences: self.has_shared_procfs_sequences,
1294 file_handles: self.file_handles.clone(),
1295 accept_waits: Vec::new(),
1296 next_accept_wait: 0,
1297 cpuinfo_placeholders: self.cpuinfo_placeholders,
1298 pending_cpuinfo_placeholders: self.pending_cpuinfo_placeholders.clone(),
1299 proc_level: self.proc_level,
1300 present_unmodelled: self.present_unmodelled.clone(),
1301 unaccounted_receipts: self.unaccounted_receipts,
1302 descriptor_capacity: self.forked_descriptor_capacity(),
1303 exec_dropped_lock_tracked: false,
1304 released_rights: ReleasedRights::default(),
1305 holders: BTreeMap::from([(child, 1)]),
1306 exec_replaced: ReplacedTable::default(),
1307 }
1308 }
1309
1310 pub(crate) fn for_exec(&self, task: DetTid) -> Self {
1311 Self {
1312 files_id: self.files_id.for_exec(task),
1313 next_open_file_sequence: self.next_open_file_sequence,
1314 next_socket_open_file_sequence: self.next_socket_open_file_sequence,
1315 next_random_device_sequence: self.next_random_device_sequence,
1316 has_promoted_shared_files: self.has_promoted_shared_files,
1317 has_shared_procfs_sequences: self.has_shared_procfs_sequences,
1318 file_handles: self
1319 .file_handles
1320 .iter()
1321 .filter_map(|(&fd, detfd)| (!detfd.is_cloexec()).then_some((fd, detfd.clone())))
1322 .collect(),
1323 accept_waits: Vec::new(),
1324 next_accept_wait: 0,
1325 cpuinfo_placeholders: self.cpuinfo_placeholders,
1326 pending_cpuinfo_placeholders: self.pending_cpuinfo_placeholders.clone(),
1327 proc_level: self.proc_level,
1328 present_unmodelled: self
1329 .present_unmodelled
1330 .iter()
1331 .filter(|(_, cloexec)| !**cloexec)
1332 .map(|(fd, cloexec)| (*fd, *cloexec))
1333 .collect(),
1334 unaccounted_receipts: self.unaccounted_receipts,
1335 descriptor_capacity: self.descriptor_capacity,
1336 exec_dropped_lock_tracked: self
1337 .file_handles
1338 .values()
1339 .any(|detfd| detfd.is_cloexec() && detfd.is_lock_tracked()),
1340 released_rights: ReleasedRights::default(),
1341 holders: BTreeMap::from([(task, 1)]),
1342 exec_replaced: ReplacedTable::default(),
1343 }
1344 }
1345
1346 pub(crate) fn join_holder(&mut self, process: DetPid) {
1349 if !self.holders.is_empty() {
1350 *self.holders.entry(process).or_insert(0) += 1;
1351 }
1352 }
1353
1354 pub(crate) fn hold_alone(&mut self, process: DetPid) {
1358 self.holders = BTreeMap::from([(process, 1)]);
1359 }
1360
1361 pub(crate) fn leave(&mut self, process: DetPid, whole_process: bool) -> bool {
1368 let Some(count) = self.holders.get_mut(&process) else {
1369 return false;
1370 };
1371 if whole_process || *count <= 1 {
1372 self.holders.remove(&process);
1373 } else {
1374 *count -= 1;
1375 }
1376 self.holders.is_empty()
1377 }
1378
1379 pub(crate) fn release_at_death(&mut self, process: DetPid) -> TableAtDeath {
1390 if !self.holders.contains_key(&process) {
1391 return TableAtDeath::Undecided;
1392 }
1393 if !self.leave(process, true) {
1394 return TableAtDeath::Kept;
1395 }
1396 self.release_every_descriptor();
1397 TableAtDeath::Released(self.take_released_rights())
1398 }
1399
1400 pub(crate) fn keep_replaced_table(
1403 &mut self,
1404 replaced: Arc<Mutex<FileMetadata>>,
1405 process: DetPid,
1406 ) {
1407 self.exec_replaced = ReplacedTable(Some((replaced, process)));
1408 }
1409
1410 pub fn native_exec_files_id(&self) -> FilesId {
1412 self.files_id
1413 }
1414
1415 pub(crate) fn validate_native_exec_cut(
1419 &self,
1420 expected: FilesId,
1421 prepared: bool,
1422 ) -> Result<(), crate::native_exec::NativeExecCutError> {
1423 use crate::native_exec::NativeExecCutError;
1424
1425 if self.files_id != expected {
1426 return Err(NativeExecCutError::Identity("files_id"));
1427 }
1428 if !self.accept_waits.is_empty() {
1429 return Err(NativeExecCutError::LiveCapability("accept_waits"));
1430 }
1431 if prepared && self.next_accept_wait != 0 {
1432 return Err(NativeExecCutError::PreparedState("next_accept_wait"));
1433 }
1434 for (index, (&slot, fd)) in self.file_handles.iter().enumerate() {
1439 if slot < 0 || slot != fd.fd {
1440 return Err(NativeExecCutError::Descriptor("slot number"));
1441 }
1442 if prepared && fd.is_cloexec() {
1443 return Err(NativeExecCutError::PreparedState("CLOEXEC slot"));
1444 }
1445 let (id, owner) = fd.validate_native_exec_runtime_state()?;
1446 for (_, previous) in self.file_handles.iter().take(index) {
1447 let (previous_id, previous_owner) =
1448 previous.validate_native_exec_runtime_state()?;
1449 if (id == previous_id) != (owner == previous_owner) {
1450 return Err(NativeExecCutError::Descriptor("OFD alias identity"));
1451 }
1452 }
1453 }
1454 Ok(())
1455 }
1456
1457 pub(crate) fn exec_blocking_overrides(&self) -> ExecFdBlockingOverrides {
1458 self.file_handles
1459 .iter()
1460 .filter_map(|(&fd, detfd)| {
1461 (!detfd.is_cloexec() && detfd.physically_nonblocking() && !detfd.is_nonblocking())
1462 .then_some(fd)
1463 })
1464 .collect()
1465 }
1466
1467 pub(crate) fn apply_exec_blocking_overrides(
1468 &mut self,
1469 owner: DetTid,
1470 overrides: ExecFdBlockingOverrides,
1471 ) {
1472 for fd in overrides {
1473 tracing::trace!(
1474 "[detcore, dtid {}] restoring descriptor {} as logically blocking after exec",
1475 owner,
1476 fd,
1477 );
1478 match self.discover_fd_from_current_process(owner, fd) {
1479 Ok(()) => {
1480 self.with_detfd(fd, |detfd| detfd.set_logical_nonblocking(false))
1481 .expect("a just-discovered descriptor must remain registered");
1482 }
1483 Err(error) => {
1484 tracing::warn!(
1485 "[detcore, dtid {}] unable to restore inherited descriptor {} after exec: {}",
1486 owner,
1487 fd,
1488 error,
1489 );
1490 }
1491 }
1492 }
1493 }
1494
1495 fn inherited_reads_for_exec(&self) -> BTreeMap<RawFd, crate::fd::InheritedExternalRead> {
1496 self.file_handles
1497 .iter()
1498 .filter(|(_, detfd)| !detfd.is_cloexec())
1499 .filter_map(|(&fd, detfd)| {
1500 detfd
1501 .inherited_external_read()
1502 .map(|terminal| (fd, terminal))
1503 })
1504 .collect()
1505 }
1506
1507 fn discover_inherited_terminals(&mut self, descriptors: impl IntoIterator<Item = RawFd>) {
1511 for fd in descriptors {
1512 let Some(detfd) = self.file_handles.get(&fd) else {
1513 continue;
1514 };
1515 let Some(stat) = detfd.stat() else { continue };
1516 if stat.mode & libc::S_IFMT != libc::S_IFCHR {
1517 continue;
1518 }
1519 let mut settings: libc::termios = unsafe { std::mem::zeroed() };
1520 if unsafe { libc::ioctl(fd, libc::TCGETS, &raw mut settings) } != 0 {
1522 continue;
1523 }
1524 detfd.set_inherited_external_read(crate::fd::InheritedExternalRead {
1525 origin: detfd.open_file_id(),
1526 dev: stat.dev,
1527 inode: stat.inode,
1528 rdev: stat.rdev,
1529 file_type: libc::S_IFCHR,
1530 access_mode: detfd.status_flags() & libc::O_ACCMODE,
1531 resource: detfd.resource(),
1532 });
1533 }
1534 }
1535
1536 fn discover_inherited_pipes(&mut self, descriptors: impl IntoIterator<Item = RawFd>) {
1552 for fd in descriptors {
1553 let Some(detfd) = self.file_handles.get(&fd) else {
1554 continue;
1555 };
1556 let access_mode = detfd.status_flags() & libc::O_ACCMODE;
1557 if detfd.ty() != FdType::Pipe
1558 || detfd.is_socketpair_endpoint()
1559 || access_mode == libc::O_WRONLY
1560 || detfd.inherited_external_read().is_some()
1561 {
1562 continue;
1563 }
1564 let Some(stat) = detfd.stat() else { continue };
1565 detfd.set_inherited_external_read(crate::fd::InheritedExternalRead {
1566 origin: detfd.open_file_id(),
1567 dev: stat.dev,
1568 inode: stat.inode,
1569 rdev: stat.rdev,
1570 file_type: libc::S_IFIFO,
1571 access_mode,
1572 resource: detfd.resource(),
1573 });
1574 }
1575 }
1576
1577 fn restore_inherited_reads(
1580 &mut self,
1581 owner: DetTid,
1582 inherited_reads: BTreeMap<RawFd, crate::fd::InheritedExternalRead>,
1583 ) -> Result<(), Errno> {
1584 let mut aliases: HashMap<OpenFileId, DetFd> = HashMap::new();
1585 for (fd, terminal) in inherited_reads {
1586 self.discover_fd_from_current_process(owner, fd)?;
1587 let detfd = self.file_handles.get_mut(&fd).ok_or(Errno::EBADF)?;
1588 let stat =
1589 stat::fstat(unsafe { BorrowedFd::borrow_raw(fd) }).map_err(|_| Errno::last())?;
1590 if stat.st_mode & libc::S_IFMT != terminal.file_type
1591 || (stat.st_dev, stat.st_ino, stat.st_rdev)
1592 != (terminal.dev, terminal.inode, terminal.rdev)
1593 || detfd.status_flags() & libc::O_ACCMODE != terminal.access_mode
1594 {
1595 return Err(Errno::EIO);
1596 }
1597 if let Some(alias) = aliases.get(&terminal.origin) {
1600 if detfd.status_flags() != alias.status_flags() {
1601 return Err(Errno::EIO);
1602 }
1603 detfd.share_open_file_with(alias);
1604 } else {
1605 detfd.set_resource(terminal.resource.clone());
1606 detfd.set_inherited_external_read(terminal.clone());
1607 aliases.insert(terminal.origin, detfd.clone());
1608 }
1609 }
1610 Ok(())
1611 }
1612
1613 fn install_inherited_input(
1614 &mut self,
1615 owner: DetTid,
1616 input: reverie::InheritedInputIdentity,
1617 ) -> Result<(), Errno> {
1618 let fd = input.guest_fd();
1619 self.discover_fd_from_current_process(owner, fd)?;
1620 let stat = stat::fstat(unsafe { BorrowedFd::borrow_raw(fd) }).map_err(|_| Errno::last())?;
1621 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
1622 let detfd = self.file_handles.get(&fd).ok_or(Errno::EBADF)?;
1623 if !input.matches(&stat, flags) || detfd.ty() != FdType::Pipe {
1624 return Err(Errno::EIO);
1625 }
1626 detfd.set_inherited_external_read(crate::fd::InheritedExternalRead {
1627 origin: detfd.open_file_id(),
1628 dev: input.dev(),
1629 inode: input.inode(),
1630 rdev: input.rdev(),
1631 file_type: libc::S_IFIFO,
1632 access_mode: input.access_mode(),
1633 resource: detfd.resource(),
1634 });
1635 Ok(())
1636 }
1637
1638 pub(crate) fn apply_exec_fd_state(
1639 &mut self,
1640 owner: DetTid,
1641 state: ExecFdState,
1642 shared_guest_table: bool,
1643 ) -> Result<(), Errno> {
1644 if shared_guest_table && let Some(maps) = &state.maps {
1645 for (&fd, survivor) in &maps.handles {
1649 if ExecOpenFile::capture(fd, survivor.identity.id)? != survivor.identity {
1650 return Err(Errno::EIO);
1651 }
1652 }
1653 if state
1654 .open_files
1655 .as_ref()
1656 .is_some_and(|files| files.creator != maps.creator)
1657 || state
1658 .open_file_namespace
1659 .is_some_and(|(creator, _)| creator != maps.creator)
1660 {
1661 return Err(Errno::EIO);
1662 }
1663 self.next_open_file_sequence = self.next_open_file_sequence.max(maps.next_sequence);
1664 }
1665 if shared_guest_table && let Some(records) = &state.procfs_records {
1666 for (&fd, survivor) in &records.handles {
1671 if ExecOpenFile::capture(fd, survivor.identity.id)? != survivor.identity {
1672 return Err(Errno::EIO);
1673 }
1674 }
1675 if state
1676 .open_files
1677 .as_ref()
1678 .is_some_and(|files| files.creator != records.creator)
1679 || state
1680 .open_file_namespace
1681 .is_some_and(|(creator, _)| creator != records.creator)
1682 || state
1683 .maps
1684 .as_ref()
1685 .is_some_and(|maps| maps.creator != records.creator)
1686 {
1687 return Err(Errno::EIO);
1688 }
1689 self.next_open_file_sequence = self.next_open_file_sequence.max(records.next_sequence);
1690 self.has_shared_procfs_sequences = true;
1691 }
1692 let owner = if shared_guest_table && let Some(open_files) = &state.open_files {
1696 self.next_open_file_sequence =
1697 self.next_open_file_sequence.max(open_files.next_sequence);
1698 self.next_socket_open_file_sequence = self
1699 .next_socket_open_file_sequence
1700 .max(open_files.next_socket_sequence);
1701 open_files.creator
1702 } else if shared_guest_table
1703 && let Some((creator, next_sequence)) = state.open_file_namespace
1704 {
1705 self.next_open_file_sequence = self.next_open_file_sequence.max(next_sequence);
1708 creator
1709 } else if shared_guest_table && let Some(maps) = &state.maps {
1710 maps.creator
1711 } else if shared_guest_table && let Some(records) = &state.procfs_records {
1712 records.creator
1713 } else {
1714 owner
1715 };
1716 if shared_guest_table {
1717 self.next_random_device_sequence = self
1718 .next_random_device_sequence
1719 .max(state.random_device_sequence);
1720 }
1721 self.apply_exec_blocking_overrides(owner, state.blocking);
1722 if !shared_guest_table {
1723 return Ok(());
1724 }
1725 if let Some(input) = state.initial_input {
1726 self.install_inherited_input(owner, input)?;
1727 }
1728 if state.initialize_inherited_stdio {
1729 self.discover_inherited_terminals(0..=2);
1730 self.discover_inherited_pipes(0..=2);
1731 } else {
1732 self.restore_inherited_reads(owner, state.inherited_reads)?;
1733 }
1734 if let Some(open_files) = state.open_files {
1735 let mut handles = HashMap::new();
1736 for (fd, survivor) in open_files.handles {
1737 if ExecOpenFile::capture(fd, survivor.id)? != survivor {
1738 return Err(Errno::EIO);
1739 }
1740 handles.insert(fd, DetFd::shared_survivor(fd, survivor.id));
1741 }
1742 crate::fd::intern_open_files(&mut handles);
1743 self.file_handles.extend(handles);
1747 self.has_promoted_shared_files = true;
1748 }
1749 for (fd, end) in state.channels {
1753 match self.discover_fd_from_current_process(owner, fd) {
1754 Ok(()) => {
1755 self.with_detfd(fd, |detfd| detfd.set_channel(end))
1756 .expect("a just-discovered descriptor must remain registered");
1757 }
1758 Err(error) => {
1759 tracing::warn!(
1760 "[detcore, dtid {}] descriptor {} carried a pipe channel across exec \
1761 but cannot be rediscovered; it keeps none: {}",
1762 owner,
1763 fd,
1764 error,
1765 );
1766 }
1767 }
1768 }
1769 if let Some(maps) = state.maps {
1770 let mut handles = HashMap::new();
1771 for (fd, survivor) in maps.handles {
1772 let raw_stat = stat::fstat(unsafe { BorrowedFd::borrow_raw(fd) })
1773 .map_err(|error| Errno::new(error as i32))?;
1774 let handle = DetFd::new(
1775 fd,
1776 OFlag::from_bits_truncate(survivor.flags),
1777 FdType::Regular,
1778 survivor.identity.id,
1779 )
1780 .with_stat(DetStat::from(raw_stat));
1781 if let Some(path) = survivor.path {
1782 handle.set_path(path);
1783 }
1784 handle.set_procfs(survivor.procfs);
1785 handle.mark_flock_mode_unobserved();
1786 handles.insert(fd, handle);
1787 }
1788 crate::fd::intern_open_files(&mut handles);
1791 self.file_handles.extend(handles);
1792 }
1793 if let Some(records) = state.procfs_records {
1794 self.restore_procfs_records(records)?;
1795 }
1796 Ok(())
1797 }
1798
1799 fn restore_procfs_records(&mut self, records: ExecProcfsRecords) -> Result<(), Errno> {
1808 let mut handles = HashMap::new();
1809 let mut placeholders = Vec::new();
1810 for (fd, survivor) in records.handles {
1811 let raw_stat = stat::fstat(unsafe { BorrowedFd::borrow_raw(fd) })
1812 .map_err(|error| Errno::new(error as i32))?;
1813 let handle = DetFd::new(
1814 fd,
1815 OFlag::from_bits_truncate(survivor.flags),
1816 FdType::Regular,
1817 survivor.identity.id,
1818 )
1819 .with_stat(DetStat::from(raw_stat));
1820 if let Some(path) = survivor.path {
1821 handle.set_path(path);
1822 }
1823 let mut procfs = survivor.procfs;
1824 procfs.adopt_sequence(survivor.sequence);
1825 if let Some(rendered) = procfs.opened_path() {
1826 placeholders.push((fd, (survivor.identity.dev, survivor.identity.ino), rendered));
1827 }
1828 handle.set_procfs(procfs);
1829 handle.mark_flock_mode_unobserved();
1830 if survivor.sequence_unknown {
1831 handle.mark_record_sequence_unknown();
1832 }
1833 handles.insert(fd, handle);
1834 }
1835 crate::fd::intern_open_files(&mut handles);
1836 self.file_handles.extend(handles);
1837 for (fd, identity, rendered) in placeholders {
1838 crate::syscalls::rendered_procfs::register_placeholder(
1839 identity.0, identity.1, rendered,
1840 );
1841 let handle = &self.file_handles[&fd];
1842 crate::syscalls::rendered_procfs::register_placeholder_open(
1843 identity,
1844 handle.open_file_handle(),
1845 handle.open_file_id(),
1846 Vec::new(),
1847 );
1848 }
1849 Ok(())
1850 }
1851
1852 pub(crate) fn open_files_closed_on_exec(&self, table_is_shared: bool) -> Vec<OpenFileId> {
1853 if table_is_shared {
1854 return Vec::new();
1855 }
1856
1857 let mut open_files = HashMap::new();
1858 for detfd in self.file_handles.values() {
1859 let id = detfd.open_file_id();
1860 let total_aliases = detfd.open_file_alias_count();
1861 let in_flight = detfd.copies_in_flight() > 0;
1864 let entry = open_files
1865 .entry(id)
1866 .or_insert((0, total_aliases, !in_flight));
1867 debug_assert_eq!(entry.1, total_aliases);
1868 entry.0 += 1;
1869 entry.2 &= detfd.is_cloexec();
1870 }
1871
1872 let mut closed: Vec<_> = open_files
1873 .into_iter()
1874 .filter_map(|(id, (table_aliases, total_aliases, all_cloexec))| {
1875 (all_cloexec && table_aliases == total_aliases).then_some(id)
1876 })
1877 .collect();
1878 closed.sort();
1879 closed
1880 }
1881
1882 fn setup_stdio(mut self, _pid: Pid, owner: DetTid, aliases: Option<[u8; 3]>) -> Self {
1889 let mut metadata: libc::stat = unsafe { std::mem::zeroed() };
1894 let status = unsafe {
1896 crate::util::raw_syscall(libc::SYS_fstat, [0, (&raw mut metadata) as u64, 0, 0, 0, 0])
1897 };
1898 if let Err(error) = crate::util::raw_zero_result(status) {
1899 panic!(
1900 "fstat of the guest's stdin failed (errno {})",
1901 error.raw_os_error().unwrap_or(0)
1902 );
1903 }
1904 let stat: DetStat = metadata.into();
1905 let stdin = DetFd::new(
1906 0,
1907 OFlag::empty(),
1908 FdType::Regular,
1909 self.allocate_open_file_id(owner, FdType::Regular),
1910 )
1911 .with_stat(stat)
1912 .with_resource(ResourceID::Device(Device::ContainerStdin));
1913 let stdout = DetFd::new(
1914 1,
1915 OFlag::empty(),
1916 FdType::Regular,
1917 self.allocate_open_file_id(owner, FdType::Regular),
1918 )
1919 .with_stat(stat)
1920 .with_resource(ResourceID::Device(Device::ContainerStdout));
1921 let stderr = DetFd::new(
1922 2,
1923 OFlag::empty(),
1924 FdType::Regular,
1925 self.allocate_open_file_id(owner, FdType::Regular),
1926 )
1927 .with_stat(stat)
1928 .with_resource(ResourceID::Device(Device::ContainerStderr));
1929
1930 stdin.mark_flock_mode_unobserved();
1933 stdout.mark_flock_mode_unobserved();
1934 stderr.mark_flock_mode_unobserved();
1935
1936 if let Some(aliases) = aliases {
1944 let streams = [&stdin, &stdout, &stderr];
1945 for (later, earlier) in aliases.iter().enumerate() {
1946 let earlier = usize::from(*earlier);
1947 if earlier < later {
1948 let group = streams[earlier]
1949 .stdio_alias_group()
1950 .unwrap_or_else(|| streams[earlier].open_file_id());
1951 streams[earlier].set_stdio_alias_group(group);
1952 streams[later].set_stdio_alias_group(group);
1953 }
1954 }
1955 }
1956
1957 self.add_detfd(stdin);
1958 self.add_detfd(stdout);
1959 self.add_detfd(stderr);
1960
1961 self
1962 }
1963
1964 fn discover_fd_from_current_process(&mut self, owner: DetTid, fd: RawFd) -> Result<(), Errno> {
1967 if self.file_handles.contains_key(&fd) {
1968 return Ok(());
1969 }
1970
1971 let fd_flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
1972 let status_flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
1973 if fd_flags == -1 || status_flags == -1 {
1974 return Err(Errno::last());
1975 }
1976 let raw_stat =
1977 stat::fstat(unsafe { BorrowedFd::borrow_raw(fd) }).map_err(|_| Errno::last())?;
1978 let link = (raw_stat.st_mode & libc::S_IFMT == 0)
1980 .then(|| own_fd_link(fd))
1981 .flatten();
1982 self.add_discovered_fd(
1983 owner,
1984 fd,
1985 fd_flags,
1986 status_flags,
1987 &raw_stat,
1988 link.as_deref(),
1989 )?;
1990 let may_be_placeholder = raw_stat.st_mode & libc::S_IFMT == libc::S_IFREG
1997 && raw_stat.st_nlink == 0
1998 && raw_stat.st_size == 0;
1999 let decided = self.cpuinfo_placeholders;
2005 let files = crate::procfs::RenderedFiles {
2008 cpuinfo: decided == Some(true),
2009 mounts: true,
2010 locks: false,
2013 };
2014 let found = self.within_proc_level(
2015 may_be_placeholder
2016 .then(|| placeholder_behind(fd, files))
2017 .flatten(),
2018 );
2019 if found.is_none()
2023 && decided.is_none()
2024 && may_be_placeholder
2025 && self
2026 .within_proc_level(placeholder_behind(fd, crate::procfs::RenderedFiles::ALL))
2027 .is_some()
2028 {
2029 self.pending_cpuinfo_placeholders.push(fd);
2030 }
2031 if let Some((path, snapshot)) = found {
2032 if self.adopt_placeholder(fd, &raw_stat, fd_flags, path, snapshot)? {
2033 return Ok(());
2034 }
2035 }
2036 else if raw_stat.st_mode & libc::S_IFMT == libc::S_IFREG
2042 && raw_stat.st_size == 0
2043 && let Some(procfs) = fixed_procfs_behind(fd)
2044 {
2045 let offset = unsafe {
2047 crate::util::raw_syscall(
2048 libc::SYS_lseek,
2049 [fd as u64, 0, libc::SEEK_CUR as u64, 0, 0, 0],
2050 )
2051 };
2052 let offset = usize::try_from(offset).unwrap_or(0);
2053 let path = procfs
2054 .rendered_path(crate::procfs::RenderedFiles::WITHOUT_CPUINFO)
2055 .expect("a fixed procfs file has a path");
2056 self.with_detfd(fd, |detfd| {
2057 detfd.set_path(path);
2058 detfd.set_procfs(procfs.clone());
2059 detfd.set_procfs_offset(offset);
2060 })?;
2061 }
2062 else if let Some(path) =
2077 own_fd_link(fd).and_then(|link| kernel_record_file_link(&raw_stat, &link))
2078 {
2079 self.with_detfd(fd, |detfd| {
2080 detfd.set_path(path.clone());
2081 detfd.mark_record_sequence_unknown();
2082 })?;
2083 }
2084 if let Some(resource) = stdio_resource(fd) {
2085 self.with_detfd(fd, |detfd| detfd.set_resource(resource.clone()))?;
2086 }
2087 Ok(())
2088 }
2089
2090 fn add_discovered_fd(
2103 &mut self,
2104 owner: DetTid,
2105 fd: RawFd,
2106 fd_flags: libc::c_int,
2107 status_flags: libc::c_int,
2108 raw_stat: &libc::stat,
2109 link: Option<&[u8]>,
2110 ) -> Result<(), Errno> {
2111 let file_type = stat::SFlag::from_bits_truncate(raw_stat.st_mode);
2112 let ty = if raw_stat.st_mode & libc::S_IFMT == 0
2113 && link == Some(crate::syscalls::inotify::INOTIFY_LINK)
2114 {
2115 FdType::Inotify
2117 } else if file_type.contains(stat::SFlag::S_IFIFO) {
2118 FdType::Pipe
2119 } else if file_type.contains(stat::SFlag::S_IFSOCK) {
2120 FdType::Socket
2121 } else if status_flags & libc::O_PATH == 0
2122 && crate::fd::is_random_device(raw_stat.st_mode, raw_stat.st_rdev)
2123 {
2124 FdType::Rng
2127 } else {
2128 FdType::Regular
2129 };
2130 let mut flags = OFlag::from_bits_truncate(status_flags);
2131 let physically_nonblocking = flags.contains(OFlag::O_NONBLOCK);
2132 if fd_flags & libc::FD_CLOEXEC != 0 {
2136 flags.insert(OFlag::O_CLOEXEC);
2137 }
2138 self.add_fd(owner, fd, flags, ty, Some((*raw_stat).into()))?;
2139 self.with_detfd(fd, |detfd| detfd.mark_flock_mode_unobserved())?;
2142 if ty == FdType::Rng {
2143 let stream = crate::fd::RandomDeviceStream::discovered(owner, fd);
2144 self.with_detfd(fd, |detfd| detfd.set_random_device(stream))?;
2145 }
2146 if ty == FdType::Pipe && physically_nonblocking {
2147 self.with_detfd(fd, |detfd| detfd.set_physically_nonblocking())?;
2148 }
2149 Ok(())
2150 }
2151
2152 fn adopt_placeholder(
2177 &mut self,
2178 fd: RawFd,
2179 raw_stat: &libc::stat,
2180 fd_flags: libc::c_int,
2181 path: &'static std::path::Path,
2182 snapshot: Vec<u8>,
2183 ) -> Result<bool, Errno> {
2184 crate::syscalls::rendered_procfs::register_placeholder(
2185 raw_stat.st_dev,
2186 raw_stat.st_ino,
2187 path,
2188 );
2189 if let Some(alias) = self.rendered_alias(fd, raw_stat.st_dev, raw_stat.st_ino) {
2192 let mut alias = alias.with_fd(fd);
2193 alias.set_cloexec(fd_flags & libc::FD_CLOEXEC != 0);
2194 self.add_detfd(alias);
2195 return Ok(true);
2196 }
2197 let offset = unsafe {
2199 crate::util::raw_syscall(
2200 libc::SYS_lseek,
2201 [fd as u64, 0, libc::SEEK_CUR as u64, 0, 0, 0],
2202 )
2203 };
2204 let offset = usize::try_from(offset).unwrap_or(0);
2205 let procfs = crate::procfs::ProcfsFile::from_rendered_path(path)
2206 .expect("a rendered path is a procfs file")
2207 .into_rendered();
2208 let mut procfs = if snapshot.is_empty() {
2209 procfs
2210 } else {
2211 procfs.with_rendered_snapshot(snapshot)
2212 };
2213 let follows = procfs.follows_record_sequence();
2214 let joined = follows
2215 .then(|| {
2216 crate::syscalls::rendered_procfs::placeholder_open_id((
2217 raw_stat.st_dev,
2218 raw_stat.st_ino,
2219 ))
2220 })
2221 .flatten();
2222 if joined.is_some() {
2223 procfs.share_sequence();
2224 self.has_shared_procfs_sequences = true;
2225 }
2226 self.with_detfd(fd, |detfd| {
2227 if let Some(id) = joined {
2228 detfd.rebind_open_file_id(id);
2229 }
2230 detfd.set_path(path);
2231 detfd.set_procfs(procfs.clone());
2232 detfd.set_procfs_offset(offset);
2233 if follows && joined.is_none() {
2234 detfd.mark_record_sequence_unknown();
2235 }
2236 })?;
2237 Ok(false)
2238 }
2239
2240 fn rendered_alias(&self, fd: RawFd, dev: u64, ino: u64) -> Option<DetFd> {
2243 self.file_handles
2244 .iter()
2245 .filter(|(other, detfd)| **other != fd && detfd.procfs_rendered_path().is_some())
2246 .find(|(other, _)| {
2247 stat::fstat(unsafe { BorrowedFd::borrow_raw(**other) })
2248 .is_ok_and(|stat| stat.st_dev == dev && stat.st_ino == ino)
2249 })
2250 .map(|(_, detfd)| detfd.clone())
2251 }
2252
2253 fn with_detfd<F, U>(&mut self, fd: RawFd, mut f: F) -> Result<U, Errno>
2255 where
2256 F: FnMut(&mut DetFd) -> U,
2257 {
2258 let detfd = self.file_handles.get_mut(&fd).ok_or(Errno::EBADF)?;
2259 Ok(f(detfd))
2260 }
2261
2262 fn add_detfd(&mut self, detfd: DetFd) {
2264 let fd = detfd.fd;
2265 self.note_installed(fd);
2266 self.file_handles.insert(fd, detfd);
2267 }
2268
2269 fn add_fd(
2271 &mut self,
2272 creator: DetTid,
2273 fd: RawFd,
2274 flags: OFlag,
2275 ty: FdType,
2276 stat: Option<DetStat>,
2277 ) -> Result<(), Errno> {
2278 let id = self.allocate_open_file_id(creator, ty);
2279 let detfd = DetFd::new(fd, flags, ty, id).with_stat(stat);
2280 self.add_detfd(detfd);
2281 Ok(())
2282 }
2283
2284 fn classify_random_device(&mut self, creator: DetTid, fd: RawFd) -> Result<(), Errno> {
2288 let stream = crate::fd::RandomDeviceStream {
2289 creator,
2290 sequence: self.next_random_device_sequence,
2291 };
2292 self.with_detfd(fd, |detfd| detfd.set_random_device(stream))?;
2293 self.next_random_device_sequence += 1;
2294 Ok(())
2295 }
2296
2297 fn slot_note(&self, fd: RawFd, op: FdNoteOp) -> crate::access_trace::FdNote {
2300 use detcore_model::access_trace::FdOp;
2301 let detfd = self.file_handles.get(&fd);
2302 let op = match op {
2303 FdNoteOp::Open => FdOp::Open {
2304 path: detfd
2305 .and_then(DetFd::path)
2306 .map(|path| path.to_string_lossy().into_owned()),
2307 },
2308 FdNoteOp::Dup(source) => FdOp::Dup { source },
2309 };
2310 crate::access_trace::FdNote {
2311 slot: detcore_model::fd::FdSlot {
2312 files: self.files_id,
2313 fd,
2314 },
2315 open_file: detfd.map(DetFd::open_file_id),
2316 op,
2317 released: None,
2318 }
2319 }
2320
2321 fn close_note(&self, fd: RawFd) -> Option<crate::access_trace::FdNote> {
2324 let detfd = self.file_handles.get(&fd)?;
2325 Some(crate::access_trace::FdNote {
2326 slot: detcore_model::fd::FdSlot {
2327 files: self.files_id,
2328 fd,
2329 },
2330 open_file: Some(detfd.open_file_id()),
2331 op: detcore_model::access_trace::FdOp::Close,
2332 released: Some(crate::access_trace::FdInfo::of(detfd)),
2333 })
2334 }
2335
2336 fn remove_fd(&mut self, fd: RawFd) -> Option<OpenFileId> {
2338 self.present_unmodelled.remove(&fd);
2339 let detfd = self.file_handles.remove(&fd)?;
2340 self.release_detfd(detfd)
2341 }
2342
2343 fn release_detfd(&mut self, detfd: DetFd) -> Option<OpenFileId> {
2353 detfd.is_last_reference().then(|| {
2354 self.released_rights
2355 .0
2356 .extend(detfd.take_all_queued_rights());
2357 detfd.open_file_id()
2358 })
2359 }
2360
2361 fn release_every_descriptor(&mut self) {
2367 let mut fds: Vec<RawFd> = self.file_handles.keys().copied().collect();
2368 fds.sort_unstable();
2369 for fd in fds {
2370 let _ = self.remove_fd(fd);
2371 }
2372 }
2373
2374 fn take_released_rights(&mut self) -> Vec<SentDescriptor> {
2377 std::mem::take(&mut self.released_rights.0)
2378 }
2379
2380 fn remove_fd_range(&mut self, first: u32, last: u32) -> Vec<OpenFileId> {
2382 self.present_unmodelled
2383 .retain(|fd, _| !(*fd >= 0 && first <= *fd as u32 && *fd as u32 <= last));
2384 let mut descriptors: Vec<_> = self
2385 .file_handles
2386 .keys()
2387 .copied()
2388 .filter(|fd| *fd >= 0 && first <= *fd as u32 && *fd as u32 <= last)
2389 .collect();
2390 descriptors.sort_unstable();
2391 descriptors
2392 .into_iter()
2393 .filter_map(|fd| self.remove_fd(fd))
2394 .collect()
2395 }
2396
2397 fn dup_fd(
2399 &mut self,
2400 oldfd: RawFd,
2401 newfd: RawFd,
2402 flags: OFlag,
2403 ) -> Result<Option<OpenFileId>, Errno> {
2404 if oldfd == newfd {
2405 if self.present_unmodelled.contains_key(&oldfd) {
2406 return Ok(None);
2407 }
2408 self.with_detfd(oldfd, |_| ())?;
2409 return Ok(None);
2410 }
2411 if !self.file_handles.contains_key(&oldfd) && self.present_unmodelled.contains_key(&oldfd) {
2414 self.note_installed(newfd);
2415 self.present_unmodelled
2416 .insert(newfd, flags.contains(OFlag::O_CLOEXEC));
2417 let replaced = self.file_handles.remove(&newfd);
2418 return Ok(replaced.and_then(|detfd| self.release_detfd(detfd)));
2419 }
2420
2421 let detfd = self.with_detfd(oldfd, |old_detfd| {
2422 old_detfd.clone().with_fd(newfd).with_fd_flags(flags)
2423 })?;
2424 self.note_installed(newfd);
2425 let replaced = self.file_handles.insert(newfd, detfd);
2426 Ok(replaced.and_then(|detfd| self.release_detfd(detfd)))
2427 }
2428
2429 fn capture_fd(&mut self, fd: RawFd) -> Result<CapturedDetFd, Errno> {
2431 let detfd = self.with_detfd(fd, |detfd| detfd.clone())?;
2432 Ok(CapturedDetFd {
2433 files_id: self.files_id,
2434 detfd,
2435 })
2436 }
2437
2438 fn install_captured_fd(
2444 &mut self,
2445 captured: CapturedDetFd,
2446 newfd: RawFd,
2447 flags: OFlag,
2448 ) -> Result<Option<OpenFileId>, CapturedDetFdInstallError> {
2449 if captured.files_id != self.files_id {
2450 return Err(CapturedDetFdInstallError {
2451 expected_files_id: captured.files_id,
2452 actual_files_id: self.files_id,
2453 returned_fd: newfd,
2454 captured,
2455 });
2456 }
2457
2458 let detfd = captured.detfd.with_fd(newfd).with_fd_flags(flags);
2459 self.note_installed(newfd);
2460 let replaced = self.file_handles.insert(newfd, detfd);
2461 Ok(replaced.and_then(|detfd| self.release_detfd(detfd)))
2462 }
2463
2464 fn abandon_captured_fd(&mut self, captured: CapturedDetFd) -> Option<OpenFileId> {
2467 self.release_detfd(captured.detfd)
2468 }
2469}
2470
2471#[derive(Clone, Copy)]
2473enum FdNoteOp {
2474 Open,
2475 Dup(RawFd),
2476}
2477
2478fn stdio_resource(fd: RawFd) -> Option<ResourceID> {
2479 match fd {
2480 0 => Some(ResourceID::Device(Device::ContainerStdin)),
2481 1 => Some(ResourceID::Device(Device::ContainerStdout)),
2482 2 => Some(ResourceID::Device(Device::ContainerStderr)),
2483 _ => None,
2484 }
2485}
2486
2487#[cfg(test)]
2488mod posix_timers_tests {
2489 use super::*;
2490
2491 fn t(ns: u64) -> LogicalTime {
2492 LogicalTime::from_nanos(ns)
2493 }
2494
2495 #[test]
2496 fn ids_are_deterministic_and_sequential() {
2497 let mut timers = PosixTimers::default();
2498 assert_eq!(timers.create(None, None, None, false), 0);
2499 assert_eq!(timers.create(Some(libc::SIGALRM), None, None, false), 1);
2500 assert_eq!(timers.create(None, None, None, false), 2);
2501 }
2502
2503 #[test]
2504 fn settime_reports_previous_arming_and_remaining_uses_virtual_clock() {
2505 let mut timers = PosixTimers::default();
2506 let id = timers.create(None, None, None, false);
2507
2508 let old = timers.settime(id, 0, Some(t(100)), t(0)).expect("known id");
2511 assert_eq!(old, (0, 0));
2512
2513 assert_eq!(timers.gettime(id, t(40)), Some((60, 0)));
2515 assert_eq!(timers.gettime(id, t(150)), Some((0, 0)));
2517 }
2518
2519 #[test]
2520 fn resetting_reports_old_remaining() {
2521 let mut timers = PosixTimers::default();
2522 let id = timers.create(None, None, None, false);
2523 timers.settime(id, 0, Some(t(100)), t(0));
2524 let old = timers
2526 .settime(id, 50, Some(t(200)), t(30))
2527 .expect("known id");
2528 assert_eq!(old, (70, 0));
2529 assert_eq!(timers.gettime(id, t(30)), Some((170, 50)));
2530 }
2531
2532 #[test]
2535 fn periodic_remaining_advances_past_each_deadline() {
2536 let mut timers = PosixTimers::default();
2537 let id = timers.create(Some(libc::SIGALRM), None, None, false);
2538 timers.settime(id, 50, Some(t(100)), t(0));
2539
2540 assert_eq!(timers.gettime(id, t(100)), Some((50, 50)));
2541 assert_eq!(timers.gettime(id, t(125)), Some((25, 50)));
2542 assert_eq!(timers.gettime(id, t(150)), Some((50, 50)));
2543 assert_eq!(timers.signal(id), Some(Some(libc::SIGALRM)));
2544 }
2545
2546 #[test]
2547 fn disarm_and_unknown_ids() {
2548 let mut timers = PosixTimers::default();
2549 let id = timers.create(None, None, None, false);
2550 timers.settime(id, 0, Some(t(100)), t(0));
2551 timers.settime(id, 0, None, t(10));
2553 assert_eq!(timers.gettime(id, t(10)), Some((0, 0)));
2554
2555 assert_eq!(timers.settime(99, 0, Some(t(1)), t(0)), None);
2557 assert_eq!(timers.gettime(99, t(0)), None);
2558 assert!(!timers.contains(99));
2559 }
2560
2561 #[test]
2562 fn delete_removes_timer() {
2563 let mut timers = PosixTimers::default();
2564 let id = timers.create(None, None, None, false);
2565 assert!(timers.contains(id));
2566 assert!(timers.remove(id));
2567 assert!(!timers.contains(id));
2568 assert!(!timers.remove(id));
2570 }
2571}
2572
2573#[cfg(test)]
2574mod resource_limits_tests {
2575 use super::*;
2576
2577 #[test]
2578 fn defaults_are_fixed_and_cover_linux_resources() {
2579 let limits = ResourceLimits::default();
2580 assert_eq!(
2581 limits.get(reverie::syscalls::linux_abi::RLIMIT_STACK),
2582 Some(ResourceLimit {
2583 current: 8 * 1024 * 1024,
2584 maximum: libc::RLIM64_INFINITY,
2585 })
2586 );
2587 assert_eq!(
2588 limits.get(reverie::syscalls::linux_abi::RLIMIT_NOFILE),
2589 Some(ResourceLimit {
2590 current: 1_048_576,
2591 maximum: 1_048_576,
2592 })
2593 );
2594 assert_eq!(
2595 limits.get(reverie::syscalls::linux_abi::RLIMIT_CORE),
2596 Some(ResourceLimit {
2597 current: libc::RLIM64_INFINITY,
2598 maximum: libc::RLIM64_INFINITY,
2599 })
2600 );
2601 assert_eq!(
2602 limits.get(reverie::syscalls::linux_abi::RLIMIT_RTTIME + 1),
2603 None
2604 );
2605 }
2606
2607 #[test]
2608 fn cloned_process_state_changes_independently() {
2609 let parent = ResourceLimits::default();
2610 let mut child = parent.clone();
2611 let lowered = ResourceLimit {
2612 current: 1024,
2613 maximum: 1_048_576,
2614 };
2615 child.set(reverie::syscalls::linux_abi::RLIMIT_NOFILE, lowered);
2616
2617 assert_eq!(
2618 child.get(reverie::syscalls::linux_abi::RLIMIT_NOFILE),
2619 Some(lowered)
2620 );
2621 assert_eq!(
2622 parent.get(reverie::syscalls::linux_abi::RLIMIT_NOFILE),
2623 Some(ResourceLimit {
2624 current: 1_048_576,
2625 maximum: 1_048_576,
2626 })
2627 );
2628 }
2629}
2630
2631#[cfg(test)]
2632mod file_metadata_tests {
2633 mod inherited_input;
2634 use std::os::fd::AsRawFd;
2635
2636 use super::*;
2637 use crate::pipe_wake::ChannelEnd;
2638 use crate::pipe_wake::ChannelId;
2639
2640 fn isolated_child(case: &str) -> bool {
2649 const CHILD: &str = "HERMIT_FILE_METADATA_CHILD";
2650 if std::env::var_os(CHILD).as_deref() == Some(std::ffi::OsStr::new(case)) {
2651 return false;
2652 }
2653 let output = std::process::Command::new(std::env::current_exe().unwrap())
2654 .args(["--exact", case, "--nocapture", "--test-threads=1"])
2655 .env(CHILD, case)
2656 .output()
2657 .unwrap();
2658 assert!(output.status.success(), "{output:?}");
2659 assert!(
2660 String::from_utf8_lossy(&output.stdout).contains("1 passed"),
2661 "{output:?}"
2662 );
2663 true
2664 }
2665
2666 #[test]
2667 fn maps_exec_survivors_keep_opened_target_aliases_and_cursor() {
2668 use crate::procfs::BoundTask;
2669 use crate::procfs::MapsTarget;
2670 use crate::procfs::ProcfsFile;
2671 use crate::procfs::ProcfsSnapshotContext;
2672 use crate::procfs::TaskIncarnation;
2673
2674 let owner = DetTid::from_raw(9021);
2675 let config = Config::default();
2676 let mut parent = ThreadState::new(owner, &config, ());
2677 let maps = std::fs::File::open("/proc/self/maps").unwrap();
2678 let alias = maps.try_clone().unwrap();
2679 let closed = maps.try_clone().unwrap();
2680 let unread = std::fs::File::open("/proc/self/maps").unwrap();
2681 let fd = maps.as_raw_fd();
2682 let alias_fd = alias.as_raw_fd();
2683 let closed_fd = closed.as_raw_fd();
2684 let unread_fd = unread.as_raw_fd();
2685 let target = MapsTarget {
2686 task: BoundTask {
2687 process: 9021,
2688 thread: None,
2689 },
2690 incarnation: TaskIncarnation {
2691 process: 1,
2692 thread: None,
2693 },
2694 mm: MmId::initial(owner),
2695 private: reverie::RuntimePrivateMemorySnapshot {
2696 owner: [1, 2, 3, 4],
2697 version: 7,
2698 regions: vec![reverie::RuntimePrivateMemoryRegion {
2699 start: 0x1000,
2700 end: 0x2000,
2701 }],
2702 },
2703 };
2704 for number in [fd, unread_fd] {
2705 parent
2706 .add_fd(
2707 number,
2708 OFlag::O_RDONLY | OFlag::O_NONBLOCK,
2709 FdType::Regular,
2710 None,
2711 )
2712 .unwrap();
2713 let mut procfs = ProcfsFile::from_path(Path::new("/proc/self/maps")).unwrap();
2714 procfs.bind_maps_target(target.clone());
2715 parent
2716 .with_detfd(number, |handle| {
2717 handle.set_path("/proc/self/maps");
2718 handle.set_procfs(procfs.clone());
2719 })
2720 .unwrap();
2721 }
2722 parent.dup_fd(fd, alias_fd, OFlag::empty()).unwrap();
2723 parent.dup_fd(fd, closed_fd, OFlag::O_CLOEXEC).unwrap();
2724 parent
2725 .with_detfd(fd, |handle| {
2726 handle.initialize_procfs(
2727 b"3000-4000 rw-p 00000000 00:00 0 \n".to_vec(),
2728 ProcfsSnapshotContext::default(),
2729 );
2730 handle.commit_procfs_read(0, 7);
2731 })
2732 .unwrap();
2733 assert!(
2734 parent.maps_for_exec(None).unwrap().is_none(),
2735 "exec maps carry requires an advertised private-memory provider"
2736 );
2737 let transfer = parent
2738 .maps_for_exec(Some(&reverie::RuntimePrivateMemory::Unavailable))
2739 .unwrap()
2740 .unwrap();
2741 let ready_transfer = parent
2742 .maps_for_exec(Some(&reverie::RuntimePrivateMemory::Ready(
2743 target.private.clone(),
2744 )))
2745 .unwrap()
2746 .unwrap();
2747 assert!(ready_transfer == transfer);
2748 let original_preview = parent
2749 .with_detfd(fd, |h| h.preview_procfs(3))
2750 .unwrap()
2751 .unwrap();
2752 assert!(!transfer.handles.contains_key(&closed_fd));
2753 assert_eq!(
2754 transfer.handles[&fd].identity.id,
2755 transfer.handles[&alias_fd].identity.id
2756 );
2757 assert_ne!(
2758 transfer.handles[&fd].identity.id,
2759 transfer.handles[&unread_fd].identity.id
2760 );
2761 let old_id = transfer.handles[&fd].identity.id;
2762 let old_unread_id = transfer.handles[&unread_fd].identity.id;
2763 let next_sequence = transfer.next_sequence;
2764 let state = ExecFdState {
2765 maps: Some(transfer),
2766 ..ExecFdState::default()
2767 };
2768 assert!(!state.is_empty());
2769 let state: ExecFdState =
2771 serde_json::from_slice(&serde_json::to_vec(&state).unwrap()).unwrap();
2772 drop(state.clone()); assert_eq!(
2774 parent
2775 .with_detfd(fd, |h| h.procfs_position())
2776 .unwrap()
2777 .unwrap()
2778 .0,
2779 7
2780 );
2781 let receiving_tid = DetTid::from_raw(9022);
2782 let mut successor_thread = ThreadState::new(receiving_tid, &config, ());
2783 assert_eq!(state.open_file_creator(), Some(owner));
2785 successor_thread.set_open_file_creator(state.open_file_creator().unwrap());
2786 let mut successor = successor_thread.metadata();
2787 successor
2788 .apply_exec_fd_state(receiving_tid, state.clone(), true)
2789 .unwrap();
2790 assert!(
2791 successor.file_handles.contains_key(&fd),
2792 "exec must retain maps provenance before lazy discovery"
2793 );
2794 assert!(!successor.file_handles.contains_key(&closed_fd));
2795 for number in [fd, alias_fd, unread_fd] {
2796 assert_eq!(
2797 successor.file_handles[&number].procfs_maps_target(),
2798 Some(target.clone())
2799 );
2800 assert!(successor.file_handles[&number].is_nonblocking());
2801 }
2802 assert_eq!(successor.file_handles[&fd].open_file_id(), old_id);
2803 assert_eq!(
2804 successor.file_handles[&unread_fd].open_file_id(),
2805 old_unread_id
2806 );
2807 assert_eq!(successor.file_handles[&fd].procfs_position().unwrap().0, 7);
2808 assert!(successor.file_handles[&unread_fd].procfs_needs_snapshot());
2809 let (offset, bytes) = successor.file_handles[&alias_fd].preview_procfs(3).unwrap();
2810 assert_eq!((offset, bytes.clone()), original_preview);
2811 successor.file_handles[&alias_fd].commit_procfs_read(offset, bytes.len());
2812 assert_eq!(successor.file_handles[&fd].procfs_position().unwrap().0, 10);
2813 assert_eq!(
2814 parent
2815 .with_detfd(fd, |h| h.procfs_position())
2816 .unwrap()
2817 .unwrap()
2818 .0,
2819 7
2820 );
2821 assert!(successor.next_open_file_sequence >= parent.metadata().next_open_file_sequence);
2822 drop(successor);
2823 let next = tempfile::tempfile().unwrap();
2824 successor_thread
2825 .add_fd(next.as_raw_fd(), OFlag::O_RDWR, FdType::Regular, None)
2826 .unwrap();
2827 assert_eq!(
2828 successor_thread
2829 .with_detfd(next.as_raw_fd(), |h| h.open_file_id())
2830 .unwrap(),
2831 OpenFileId::new(owner, next_sequence),
2832 );
2833 let replacement = tempfile::tempfile().unwrap();
2835 assert_eq!(
2836 unsafe { libc::dup2(replacement.as_raw_fd(), alias_fd) },
2837 alias_fd
2838 );
2839 assert_eq!(
2840 FileMetadata::new(owner).apply_exec_fd_state(owner, state, true),
2841 Err(Errno::EIO)
2842 );
2843 }
2844
2845 #[test]
2846 fn shared_exec_survivors_keep_aliases_and_allocator_after_closed_high_ids() {
2847 crate::fd::install_test_shared_open_file_channel();
2848 let creator = DetTid::from_raw(9011);
2849 let leader = DetTid::from_raw(9012);
2850 let config = Config::default();
2851 let mut parent = ThreadState::new(creator, &config, ());
2852 parent.set_open_file_creator(creator);
2853 parent.metadata().file_handles.clear();
2854 let file = std::fs::File::open("/dev/urandom").unwrap();
2855 let alias = nix::unistd::dup(&file).unwrap();
2856 let distinct = std::fs::File::open("/dev/urandom").unwrap();
2857 let cloexec = nix::unistd::dup(&file).unwrap();
2858 let (socket, _peer) = std::os::unix::net::UnixStream::pair().unwrap();
2859 let (fd, alias_fd, distinct_fd, closed_fd, socket_fd) = (
2860 file.as_raw_fd(),
2861 alias.as_raw_fd(),
2862 distinct.as_raw_fd(),
2863 cloexec.as_raw_fd(),
2864 socket.as_raw_fd(),
2865 );
2866 for fd in [fd, alias_fd, distinct_fd, socket_fd] {
2867 assert_eq!(unsafe { libc::fcntl(fd, libc::F_SETFD, 0) }, 0);
2868 }
2869 parent
2870 .add_fd(fd, OFlag::O_RDONLY, FdType::Rng, None)
2871 .unwrap();
2872 parent.classify_random_device(fd).unwrap();
2873 parent.dup_fd(fd, alias_fd, OFlag::empty()).unwrap();
2874 parent.dup_fd(fd, closed_fd, OFlag::O_CLOEXEC).unwrap();
2875 parent
2876 .add_fd(distinct_fd, OFlag::O_RDONLY, FdType::Rng, None)
2877 .unwrap();
2878 parent.classify_random_device(distinct_fd).unwrap();
2879 parent
2880 .add_fd(socket_fd, OFlag::O_RDWR, FdType::Socket, None)
2881 .unwrap();
2882 let first_id = parent.metadata().file_handles[&fd].open_file_id();
2883 let distinct_id = parent.metadata().file_handles[&distinct_fd].open_file_id();
2884 let old_socket_id = parent.metadata().file_handles[&socket_fd].open_file_id();
2885 assert_ne!(
2886 first_id, distinct_id,
2887 "same inode does not identify one OFD"
2888 );
2889 parent.metadata().file_handles[&fd]
2890 .with_random_device_stream::<Errno>(|offset| {
2891 assert_eq!(offset, 0);
2892 Ok(7)
2893 })
2894 .unwrap();
2895 let high = parent
2897 .metadata()
2898 .allocate_open_file_id(creator, FdType::Rng);
2899 let high_socket = parent
2900 .metadata()
2901 .allocate_open_file_id(creator, FdType::Socket);
2902 parent.promote_shared_open_files().unwrap();
2903 assert!(!parent.metadata().file_handles[&socket_fd].is_shared());
2904 let transfer = parent.shared_open_files_for_exec().unwrap().unwrap();
2905 assert_eq!(transfer.creator, creator);
2906 assert!(!transfer.handles.contains_key(&closed_fd));
2907 assert!(!transfer.handles.contains_key(&socket_fd));
2908 assert_eq!(transfer.handles[&fd].id, transfer.handles[&alias_fd].id);
2909 drop(transfer.clone());
2911 parent.metadata().file_handles[&fd]
2912 .with_random_device_stream::<Errno>(|offset| {
2913 assert_eq!(offset, 7);
2914 Ok(0)
2915 })
2916 .unwrap();
2917
2918 let replacement = tempfile::tempfile().unwrap();
2921 assert_eq!(
2922 unsafe { libc::dup2(replacement.as_raw_fd(), alias_fd) },
2923 alias_fd
2924 );
2925 assert_eq!(
2926 FileMetadata::new(leader).apply_exec_fd_state(
2927 leader,
2928 ExecFdState {
2929 open_files: Some(transfer.clone()),
2930 ..ExecFdState::default()
2931 },
2932 true,
2933 ),
2934 Err(Errno::EIO),
2935 );
2936 assert_eq!(unsafe { libc::dup2(fd, alias_fd) }, alias_fd);
2937
2938 let mut successor = ThreadState::new(leader, &config, ());
2939 successor
2942 .add_fd(socket_fd, OFlag::O_RDWR, FdType::Socket, None)
2943 .unwrap();
2944 let new_socket_id = successor.metadata().file_handles[&socket_fd].open_file_id();
2945 assert_ne!(old_socket_id, new_socket_id);
2946 let stdio_ids: Vec<_> = (0..=2)
2947 .map(|fd| successor.metadata().file_handles[&fd].open_file_id())
2948 .collect();
2949 successor.set_open_file_creator(transfer.creator);
2950 successor
2951 .metadata()
2952 .apply_exec_fd_state(
2953 creator,
2954 ExecFdState {
2955 open_files: Some(transfer),
2956 random_device_sequence: parent.random_device_sequence_for_exec(),
2957 ..ExecFdState::default()
2958 },
2959 true,
2960 )
2961 .unwrap();
2962 assert!(!successor.metadata().file_handles.contains_key(&closed_fd));
2963 assert_eq!(
2964 successor.metadata().file_handles[&socket_fd].open_file_id(),
2965 new_socket_id
2966 );
2967 assert!(!successor.metadata().file_handles[&socket_fd].is_shared());
2968 assert_eq!(
2969 (0..=2)
2970 .map(|fd| successor.metadata().file_handles[&fd].open_file_id())
2971 .collect::<Vec<_>>(),
2972 stdio_ids
2973 );
2974 successor.metadata().file_handles[&alias_fd]
2975 .with_random_device_stream::<Errno>(|offset| {
2976 assert_eq!(offset, 7);
2977 Ok(5)
2978 })
2979 .unwrap();
2980 parent.metadata().file_handles[&fd]
2981 .with_random_device_stream::<Errno>(|offset| {
2982 assert_eq!(offset, 12);
2983 Ok(0)
2984 })
2985 .unwrap();
2986 successor.metadata().file_handles[&distinct_fd]
2987 .with_random_device_stream::<Errno>(|offset| {
2988 assert_eq!(offset, 0);
2989 Ok(0)
2990 })
2991 .unwrap();
2992 successor.remove_fd(fd);
2993 successor
2994 .add_fd(fd, OFlag::O_RDONLY, FdType::Rng, None)
2995 .unwrap();
2996 successor.classify_random_device(fd).unwrap();
2997 let reopened = successor.metadata().file_handles[&fd].open_file_id();
2998 assert_ne!(reopened, first_id);
2999 assert_ne!(reopened, high);
3000 assert_eq!(
3001 reopened,
3002 OpenFileId::new(creator, successor.metadata().next_open_file_sequence - 1)
3003 );
3004 let stream_of = |fd| successor.metadata().file_handles[&fd].random_device_stream();
3007 let named = |sequence| crate::fd::RandomDeviceStream { creator, sequence };
3008 assert_eq!(stream_of(alias_fd), named(0));
3009 assert_eq!(stream_of(distinct_fd), named(1));
3010 assert_eq!(stream_of(fd), named(2));
3011 assert_ne!(
3012 successor
3013 .metadata()
3014 .allocate_open_file_id(creator, FdType::Socket),
3015 high_socket
3016 );
3017 successor.promote_shared_open_files().unwrap();
3018 let second = successor.shared_open_files_for_exec().unwrap().unwrap();
3019 assert_eq!(
3020 second.creator, creator,
3021 "nonleader creator survives repeated exec"
3022 );
3023 let mut again = FileMetadata::new(leader);
3024 again
3025 .apply_exec_fd_state(
3026 creator,
3027 ExecFdState {
3028 open_files: Some(second),
3029 ..ExecFdState::default()
3030 },
3031 true,
3032 )
3033 .unwrap();
3034 assert_eq!(again.file_handles[&fd].open_file_id(), reopened);
3035 again.file_handles[&alias_fd]
3036 .with_random_device_stream::<Errno>(|offset| {
3037 assert_eq!(offset, 12);
3038 Ok(0)
3039 })
3040 .unwrap();
3041
3042 for fd in [fd, alias_fd, distinct_fd] {
3044 successor.remove_fd(fd);
3045 }
3046 let empty = successor.shared_open_files_for_exec().unwrap().unwrap();
3047 assert!(empty.handles.is_empty());
3048 let next_sequence = empty.next_sequence;
3049 let mut after_close = ThreadState::new(leader, &config, ());
3050 after_close.set_open_file_creator(empty.creator);
3051 after_close
3052 .metadata()
3053 .apply_exec_fd_state(
3054 creator,
3055 ExecFdState {
3056 open_files: Some(empty),
3057 ..ExecFdState::default()
3058 },
3059 true,
3060 )
3061 .unwrap();
3062 after_close
3063 .add_fd(fd, OFlag::O_RDONLY, FdType::Rng, None)
3064 .unwrap();
3065 assert_eq!(
3066 after_close.metadata().file_handles[&fd].open_file_id(),
3067 OpenFileId::new(creator, next_sequence)
3068 );
3069 after_close.promote_shared_open_files().unwrap();
3071 }
3072
3073 #[test]
3074 fn shared_random_promotion_leaves_nonrandom_tables_local() {
3075 let state = ThreadState::new(DetTid::from_raw(9013), &Config::default(), ());
3076 let regular = tempfile::tempfile().unwrap();
3077 let (socket, _peer) = std::os::unix::net::UnixStream::pair().unwrap();
3078 state
3079 .add_fd(regular.as_raw_fd(), OFlag::O_RDWR, FdType::Regular, None)
3080 .unwrap();
3081 state
3082 .add_fd(socket.as_raw_fd(), OFlag::O_RDWR, FdType::Socket, None)
3083 .unwrap();
3084 let before: HashMap<_, _> = state
3085 .metadata()
3086 .file_handles
3087 .iter()
3088 .map(|(&fd, handle)| (fd, handle.open_file_id()))
3089 .collect();
3090 state.promote_shared_open_files().unwrap();
3091 assert!(state.shared_open_files_for_exec().unwrap().is_none());
3092 for (fd, id) in before {
3093 let metadata = state.metadata();
3094 let handle = &metadata.file_handles[&fd];
3095 assert!(!handle.is_shared());
3096 assert_eq!(handle.open_file_id(), id);
3097 }
3098 }
3099
3100 #[test]
3101 fn shared_exec_restores_allocator_before_legacy_pipe_discovery() {
3102 crate::fd::install_test_shared_open_file_channel();
3103 let owner = DetTid::from_raw(9014);
3104 let config = Config::default();
3105 let parent = ThreadState::new(owner, &config, ());
3106 let random = std::fs::File::open("/dev/random").unwrap();
3107 let fd = random.as_raw_fd();
3108 assert_eq!(unsafe { libc::fcntl(fd, libc::F_SETFD, 0) }, 0);
3109 parent
3110 .add_fd(fd, OFlag::O_RDONLY, FdType::Rng, None)
3111 .unwrap();
3112 parent.promote_shared_open_files().unwrap();
3113 let transfer = parent.shared_open_files_for_exec().unwrap().unwrap();
3114 let random_id = transfer.handles[&fd].id;
3115 let old_next = transfer.next_sequence;
3116 let (pipe, _writer) = nix::unistd::pipe2(OFlag::O_NONBLOCK).unwrap();
3117 let pipe_fd = pipe.as_raw_fd();
3118 let successor = ThreadState::new(owner, &config, ());
3119 successor
3120 .metadata()
3121 .apply_exec_fd_state(
3122 owner,
3123 ExecFdState {
3124 blocking: [pipe_fd].into_iter().collect(),
3125 open_files: Some(transfer),
3126 ..ExecFdState::default()
3127 },
3128 true,
3129 )
3130 .unwrap();
3131 let mut metadata = successor.metadata();
3132 let pipe_id = metadata.file_handles[&pipe_fd].open_file_id();
3133 assert_eq!(pipe_id, OpenFileId::new(owner, old_next));
3134 assert_ne!(pipe_id, random_id);
3135 assert_eq!(metadata.file_handles[&pipe_fd].ty(), FdType::Pipe);
3136 assert!(!metadata.file_handles[&pipe_fd].is_nonblocking());
3137 assert!(metadata.file_handles[&pipe_fd].physically_nonblocking());
3138 assert!(!metadata.file_handles[&pipe_fd].is_shared());
3139 assert_eq!(metadata.file_handles[&fd].open_file_id(), random_id);
3140 assert_ne!(
3141 metadata.allocate_open_file_id(owner, FdType::Regular),
3142 pipe_id
3143 );
3144 crate::fd::intern_open_files(&mut metadata.file_handles);
3146 assert_eq!(metadata.file_handles[&pipe_fd].ty(), FdType::Pipe);
3147 assert_eq!(metadata.file_handles[&fd].ty(), FdType::Rng);
3148 }
3149
3150 fn pipe_with_channel(
3153 state: &ThreadState<()>,
3154 ) -> (std::os::fd::OwnedFd, std::os::fd::OwnedFd, ChannelEnd) {
3155 let (reader, writer) = nix::unistd::pipe2(OFlag::empty()).unwrap();
3156 state
3157 .add_fd(reader.as_raw_fd(), OFlag::O_RDONLY, FdType::Pipe, None)
3158 .unwrap();
3159 let id = state.metadata().file_handles[&reader.as_raw_fd()].open_file_id();
3160 let end = ChannelEnd {
3161 channel: ChannelId::Pipe(id),
3162 reads: true,
3163 writes: false,
3164 };
3165 state
3166 .with_detfd(reader.as_raw_fd(), |detfd| detfd.set_channel(end))
3167 .unwrap();
3168 (reader, writer, end)
3169 }
3170
3171 #[test]
3182 fn the_descriptor_capacity_follows_linuxs_allocation() {
3183 let owner = DetTid::from_raw(9031);
3184 let mut table = FileMetadata::new(owner);
3185 assert_eq!(table.descriptor_capacity, 64);
3186 table
3187 .add_fd(owner, 100, OFlag::empty(), FdType::Regular, None)
3188 .unwrap();
3189 assert_eq!(table.descriptor_capacity, 128);
3190 table.remove_fd(100);
3191 assert_eq!(table.descriptor_capacity, 128);
3192 assert_eq!(
3193 table.fork_for(DetTid::from_raw(9032)).descriptor_capacity,
3194 64
3195 );
3196 table
3197 .add_fd(owner, 70, OFlag::empty(), FdType::Regular, None)
3198 .unwrap();
3199 assert_eq!(
3200 table.fork_for(DetTid::from_raw(9033)).descriptor_capacity,
3201 128
3202 );
3203 assert_eq!(table.for_exec(owner).descriptor_capacity, 128);
3204 }
3205
3206 #[test]
3212 fn a_failed_dup_grows_the_table_for_its_target() {
3213 let owner = DetTid::from_raw(9041);
3214 let mut table = FileMetadata::new(owner);
3215 table.grow_to_hold(10);
3216 assert_eq!(table.descriptor_capacity, 64);
3217 table.grow_to_hold(100);
3218 assert_eq!(table.descriptor_capacity, 128);
3219 assert!(table.descriptor_numbers().is_empty());
3220 table.grow_to_hold(-1);
3221 assert_eq!(table.descriptor_capacity, 128);
3222 }
3223
3224 #[test]
3228 fn an_unmodelled_descriptor_keeps_its_close_on_exec_changes() {
3229 let owner = DetTid::from_raw(9042);
3230 let mut table = FileMetadata::new(owner);
3231 table.present_unmodelled.insert(5, false);
3232 table.present_unmodelled.insert(6, true);
3233 assert!(table.set_unmodelled_cloexec(5, true));
3234 assert!(table.set_unmodelled_cloexec(6, false));
3235 assert!(!table.set_unmodelled_cloexec(7, true));
3236 let after = table.for_exec(owner);
3237 assert!(!after.present_unmodelled.contains_key(&5));
3238 assert_eq!(after.present_unmodelled.get(&6), Some(&false));
3239 }
3240
3241 #[test]
3246 fn an_unmodelled_descriptor_is_listed_until_it_goes() {
3247 let owner = DetTid::from_raw(9034);
3248 let mut table = FileMetadata::new(owner);
3249 table
3250 .add_fd(owner, 0, OFlag::empty(), FdType::Regular, None)
3251 .unwrap();
3252 table.present_unmodelled.insert(5, false);
3253 table.present_unmodelled.insert(6, true);
3254 assert_eq!(table.descriptor_numbers(), vec![0, 5, 6]);
3255 assert_eq!(table.for_exec(owner).descriptor_numbers(), vec![0, 5]);
3256 assert_eq!(table.dup_fd(5, 9, OFlag::O_CLOEXEC).unwrap(), None);
3257 assert_eq!(table.present_unmodelled.get(&9), Some(&true));
3258 table.remove_fd(5);
3259 table
3260 .add_fd(owner, 6, OFlag::empty(), FdType::Regular, None)
3261 .unwrap();
3262 assert_eq!(table.descriptor_numbers(), vec![0, 6, 9]);
3263 assert!(!table.present_unmodelled.contains_key(&6));
3264 table.remove_fd_range(0, 100);
3265 assert!(table.descriptor_numbers().is_empty());
3266 }
3267
3268 #[test]
3274 fn the_last_close_of_a_socket_takes_its_queue() {
3275 let owner = DetTid::from_raw(9043);
3276 let mut table = FileMetadata::new(owner);
3277 for (fd, ty) in [
3278 (3, FdType::Socket),
3279 (4, FdType::Socket),
3280 (5, FdType::Regular),
3281 ] {
3282 table.add_fd(owner, fd, OFlag::empty(), ty, None).unwrap();
3283 }
3284 let left = table.with_detfd(3, |detfd| detfd.clone()).unwrap();
3285 let right = table.with_detfd(4, |detfd| detfd.clone()).unwrap();
3286 left.pair_sockets(&right, false);
3287 let carried = table
3288 .with_detfd(5, |detfd| detfd.in_flight_open_file(true))
3289 .unwrap();
3290 assert!(matches!(
3291 left.queue_rights_to_peer(
3292 vec![SentDescriptor {
3293 file: (1, 5),
3294 open_file: Some(carried),
3295 lock_copy: None,
3296 }],
3297 1
3298 ),
3299 Ok(())
3300 ));
3301 drop((left, right));
3302 table.dup_fd(4, 6, OFlag::empty()).unwrap();
3303 assert_eq!(table.remove_fd(4), None, "another descriptor of it remains");
3304 assert!(table.take_released_rights().is_empty());
3305 let id = table.with_detfd(6, |detfd| detfd.open_file_id()).unwrap();
3306 assert_eq!(table.remove_fd(6), Some(id), "its last descriptor");
3307 let copy = table.clone();
3308 assert_eq!(table.take_released_rights().len(), 1, "its queue, taken");
3309 assert!(
3310 copy.released_rights.0.is_empty(),
3311 "a copy of the table releases nothing"
3312 );
3313 }
3314
3315 #[test]
3322 fn a_table_let_go_at_exit_takes_its_last_sockets_queues() {
3323 let parent_id = DetTid::from_raw(9044);
3324 let mut parent = FileMetadata::new(parent_id);
3325 for fd in [3, 4] {
3326 parent
3327 .add_fd(parent_id, fd, OFlag::empty(), FdType::Socket, None)
3328 .unwrap();
3329 }
3330 let left = parent.with_detfd(3, |detfd| detfd.clone()).unwrap();
3331 let right = parent.with_detfd(4, |detfd| detfd.clone()).unwrap();
3332 left.pair_sockets(&right, false);
3333 drop(right);
3334 let mut child = parent.fork_for(DetTid::from_raw(9045));
3335 assert_eq!(parent.remove_fd(4), None, "the child holds the receiver");
3336 assert_eq!(child.remove_fd(3), None, "the parent holds the sender");
3337 parent
3338 .add_fd(parent_id, 5, OFlag::empty(), FdType::Socket, None)
3339 .unwrap();
3340 let carried = parent
3341 .with_detfd(5, |detfd| detfd.in_flight_open_file(true))
3342 .unwrap();
3343 let sent_id = parent.with_detfd(5, |detfd| detfd.open_file_id()).unwrap();
3344 assert!(matches!(
3345 left.queue_rights_to_peer(
3346 vec![SentDescriptor {
3347 file: (1, 5),
3348 open_file: Some(carried),
3349 lock_copy: None,
3350 }],
3351 1
3352 ),
3353 Ok(())
3354 ));
3355 assert_eq!(parent.remove_fd(5), None, "a copy is in flight");
3356 child.release_every_descriptor();
3357 assert!(child.file_handles.is_empty());
3358 let released: Vec<_> = child
3359 .take_released_rights()
3360 .into_iter()
3361 .filter_map(|sent| {
3362 sent.open_file
3363 .and_then(crate::fd::InFlightOpenFile::release)
3364 })
3365 .map(|(id, ty, _)| (id, ty))
3366 .collect();
3367 assert_eq!(
3368 released,
3369 [(sent_id, FdType::Socket)],
3370 "the exit released the queued socket, whose last reference it was"
3371 );
3372 drop(left);
3373 }
3374
3375 #[test]
3382 fn a_table_goes_with_the_exit_that_leaves_it_no_holder() {
3383 let process = DetTid::from_raw(9050);
3384 let mut table = FileMetadata::new(process);
3385 table.join_holder(process);
3386 assert!(!table.leave(process, false), "one thread of two exits");
3387 assert!(table.leave(process, false), "and then the other");
3388
3389 let mut table = FileMetadata::new(process);
3390 table.join_holder(process);
3391 table.join_holder(process);
3392 assert!(table.leave(process, true), "an exit_group ends all three");
3393
3394 let sharer = DetTid::from_raw(9051);
3395 let mut table = FileMetadata::new(process);
3396 table.join_holder(sharer);
3397 assert!(
3398 !table.leave(process, true),
3399 "a process made with CLONE_FILES"
3400 );
3401 assert!(
3402 !table.leave(process, false),
3403 "a process that left holds none"
3404 );
3405 assert!(table.leave(sharer, false));
3406
3407 let mut copy = FileMetadata::new(process).fork_for(sharer);
3408 assert!(
3409 copy.leave(sharer, false),
3410 "a fork's copy, its child's alone"
3411 );
3412 let mut copy = FileMetadata::new(process).for_exec(sharer);
3413 copy.hold_alone(process);
3414 assert!(!copy.leave(sharer, false));
3415 assert!(copy.leave(process, false), "an exec's copy, its process's");
3416
3417 let mut unknown = FileMetadata::new(process);
3418 unknown.holders.clear();
3419 unknown.join_holder(process);
3420 assert!(!unknown.leave(process, true), "holders not known");
3421 }
3422
3423 #[test]
3433 fn an_exec_leaves_a_table_another_process_still_holds() {
3434 let process = DetTid::from_raw(9052);
3435 let sharer = DetTid::from_raw(9053);
3436 let exec_of = |table: FileMetadata| {
3437 let replaced = Arc::new(Mutex::new(table));
3438 let mut new = replaced.lock().unwrap().for_exec(process);
3439 new.hold_alone(process);
3440 new.keep_replaced_table(Arc::clone(&replaced), process);
3441 let state = ThreadState::new(process, &Config::default(), ());
3442 *state.file_metadata.lock().unwrap() = new;
3443 let released = state.release_exec_dropped_descriptors();
3444 (replaced, released)
3445 };
3446
3447 let mut shared = FileMetadata::new(process);
3448 shared.join_holder(process);
3449 shared.join_holder(sharer);
3450 shared
3451 .add_fd(process, 3, OFlag::O_CLOEXEC, FdType::Regular, None)
3452 .unwrap();
3453 let (shared, released) = exec_of(shared);
3454 assert!(!released, "a process made with CLONE_FILES still holds it");
3455 let mut shared = shared.lock().unwrap();
3456 assert_eq!(shared.file_handles.len(), 1, "it keeps every descriptor");
3457 assert_eq!(shared.holders, BTreeMap::from([(sharer, 1)]));
3458 assert!(shared.leave(sharer, true), "its exit lets the table go");
3459
3460 let mut unknown = FileMetadata::new(process);
3461 unknown.holders.clear();
3462 unknown
3463 .add_fd(process, 3, OFlag::O_CLOEXEC, FdType::Regular, None)
3464 .unwrap();
3465 let (unknown, released) = exec_of(unknown);
3466 assert!(!released);
3467 assert_eq!(
3468 unknown.lock().unwrap().file_handles.len(),
3469 1,
3470 "holders not known"
3471 );
3472 }
3473
3474 #[test]
3485 fn an_exec_releases_what_its_close_on_exec_receiving_socket_held() {
3486 let parent_id = DetTid::from_raw(9054);
3487 let child_id = DetTid::from_raw(9055);
3488 let mut parent = FileMetadata::new(parent_id);
3489 parent
3490 .add_fd(parent_id, 3, OFlag::empty(), FdType::Socket, None)
3491 .unwrap();
3492 parent
3493 .add_fd(parent_id, 4, OFlag::O_CLOEXEC, FdType::Socket, None)
3494 .unwrap();
3495 let left = parent.with_detfd(3, |detfd| detfd.clone()).unwrap();
3496 let right = parent.with_detfd(4, |detfd| detfd.clone()).unwrap();
3497 left.pair_sockets(&right, false);
3498 drop(right);
3499 let mut child = parent.fork_for(child_id);
3500 assert_eq!(parent.remove_fd(4), None, "the child holds the receiver");
3501 assert_eq!(child.remove_fd(3), None, "the parent holds the sender");
3502 parent
3503 .add_fd(parent_id, 5, OFlag::empty(), FdType::Regular, None)
3504 .unwrap();
3505 let carried = parent
3506 .with_detfd(5, |detfd| detfd.in_flight_open_file(true))
3507 .unwrap();
3508 let sent_id = parent.with_detfd(5, |detfd| detfd.open_file_id()).unwrap();
3509 assert!(matches!(
3510 left.queue_rights_to_peer(
3511 vec![SentDescriptor {
3512 file: (1, 5),
3513 open_file: Some(carried),
3514 lock_copy: Some(sent_id),
3515 }],
3516 1
3517 ),
3518 Ok(())
3519 ));
3520 assert_eq!(parent.remove_fd(5), None, "a copy is in flight");
3521
3522 child.join_holder(child_id);
3523 let replaced = Arc::new(Mutex::new(child));
3524 let mut new = replaced.lock().unwrap().for_exec(child_id);
3525 new.hold_alone(child_id);
3526 assert!(
3527 new.file_handles.is_empty(),
3528 "the receiver was close-on-exec"
3529 );
3530 new.keep_replaced_table(Arc::clone(&replaced), child_id);
3531 let state = ThreadState::new(child_id, &Config::default(), ());
3532 *state.file_metadata.lock().unwrap() = new;
3533
3534 assert!(state.release_exec_dropped_descriptors());
3535 let released: Vec<_> = state
3536 .take_released_rights()
3537 .into_iter()
3538 .map(|sent| {
3539 (
3540 sent.lock_copy,
3541 sent.open_file
3542 .and_then(crate::fd::InFlightOpenFile::release)
3543 .map(|(id, ty, _)| (id, ty)),
3544 )
3545 })
3546 .collect();
3547 assert_eq!(
3548 released,
3549 [(Some(sent_id), Some((sent_id, FdType::Regular)))],
3550 "the exec released the copy in flight, its description's last reference"
3551 );
3552 assert!(
3553 replaced.lock().unwrap().file_handles.is_empty(),
3554 "the replaced table names nothing"
3555 );
3556 assert!(!state.release_exec_dropped_descriptors(), "released once");
3557 drop(left);
3558 }
3559
3560 #[test]
3573 fn a_fatal_signals_death_releases_a_table_only_its_process_held() {
3574 let parent_id = DetTid::from_raw(9056);
3575 let child_id = DetTid::from_raw(9057);
3576 let sharer = DetTid::from_raw(9058);
3577 let mut parent = FileMetadata::new(parent_id);
3578 parent
3579 .add_fd(parent_id, 3, OFlag::empty(), FdType::Socket, None)
3580 .unwrap();
3581 parent
3582 .add_fd(parent_id, 4, OFlag::empty(), FdType::Socket, None)
3583 .unwrap();
3584 let left = parent.with_detfd(3, |detfd| detfd.clone()).unwrap();
3585 let right = parent.with_detfd(4, |detfd| detfd.clone()).unwrap();
3586 left.pair_sockets(&right, false);
3587 drop(right);
3588 let mut child = parent.fork_for(child_id);
3589 child.hold_alone(child_id);
3590 assert_eq!(parent.remove_fd(4), None, "the child holds the receiver");
3591 assert_eq!(child.remove_fd(3), None, "the parent holds the sender");
3592 parent
3593 .add_fd(parent_id, 5, OFlag::empty(), FdType::Regular, None)
3594 .unwrap();
3595 let carried = parent
3596 .with_detfd(5, |detfd| detfd.in_flight_open_file(true))
3597 .unwrap();
3598 let sent_id = parent.with_detfd(5, |detfd| detfd.open_file_id()).unwrap();
3599 assert!(matches!(
3600 left.queue_rights_to_peer(
3601 vec![SentDescriptor {
3602 file: (1, 5),
3603 open_file: Some(carried),
3604 lock_copy: Some(sent_id),
3605 }],
3606 1
3607 ),
3608 Ok(())
3609 ));
3610 assert_eq!(parent.remove_fd(5), None, "a copy is in flight");
3611
3612 child.join_holder(child_id);
3614 let TableAtDeath::Released(rights) = child.release_at_death(child_id) else {
3615 panic!("the child alone held its table");
3616 };
3617 assert_eq!(
3618 crate::syscalls::scm_rights::settle(rights),
3619 crate::syscalls::scm_rights::Settled {
3620 lock_copies: vec![sent_id],
3621 ports: Vec::new(),
3622 },
3623 "the death released the copy in flight, its description's last reference"
3624 );
3625 assert!(child.file_handles.is_empty(), "every descriptor went");
3626 assert!(child.holders.is_empty(), "both threads left");
3627 assert!(
3628 matches!(child.release_at_death(child_id), TableAtDeath::Undecided),
3629 "released once"
3630 );
3631
3632 let mut shared = FileMetadata::new(child_id);
3633 shared.join_holder(sharer);
3634 shared
3635 .add_fd(child_id, 3, OFlag::empty(), FdType::Regular, None)
3636 .unwrap();
3637 assert!(matches!(
3638 shared.release_at_death(child_id),
3639 TableAtDeath::Kept
3640 ));
3641 assert_eq!(shared.file_handles.len(), 1, "it keeps every descriptor");
3642 assert_eq!(shared.holders, BTreeMap::from([(sharer, 1)]));
3643
3644 let mut unknown = FileMetadata::new(child_id);
3645 unknown.holders.clear();
3646 unknown
3647 .add_fd(child_id, 3, OFlag::empty(), FdType::Regular, None)
3648 .unwrap();
3649 assert!(matches!(
3650 unknown.release_at_death(child_id),
3651 TableAtDeath::Undecided
3652 ));
3653 assert_eq!(unknown.file_handles.len(), 1, "holders not known");
3654 let mut another = FileMetadata::new(sharer);
3655 another
3656 .add_fd(sharer, 3, OFlag::empty(), FdType::Regular, None)
3657 .unwrap();
3658 assert!(matches!(
3659 another.release_at_death(child_id),
3660 TableAtDeath::Undecided
3661 ));
3662 assert_eq!(another.file_handles.len(), 1, "not the process's table");
3663 drop(left);
3664 }
3665
3666 #[test]
3667 fn a_pipe_channel_follows_its_description_through_dup_and_fork() {
3668 if isolated_child(
3669 "tool_local::file_metadata_tests::a_pipe_channel_follows_its_description_through_dup_and_fork",
3670 ) {
3671 return;
3672 }
3673 let owner = DetTid::from_raw(9018);
3674 let mut parent = FileMetadata::new(owner);
3675 let (reader, _writer) = nix::unistd::pipe2(OFlag::empty()).unwrap();
3676 let fd = reader.as_raw_fd();
3677 parent
3678 .add_fd(owner, fd, OFlag::empty(), FdType::Pipe, None)
3679 .unwrap();
3680 let end = ChannelEnd {
3681 channel: ChannelId::Pipe(parent.with_detfd(fd, |detfd| detfd.open_file_id()).unwrap()),
3682 reads: true,
3683 writes: false,
3684 };
3685 parent
3686 .with_detfd(fd, |detfd| detfd.set_channel(end))
3687 .unwrap();
3688 let alias = fd + 100;
3689 parent.dup_fd(fd, alias, OFlag::empty()).unwrap();
3690 assert_eq!(
3691 parent.with_detfd(alias, |detfd| detfd.channel()).unwrap(),
3692 Some(end)
3693 );
3694 let mut child = parent.fork_for(DetTid::from_raw(9019));
3695 for fd in [fd, alias] {
3696 assert_eq!(
3697 child.with_detfd(fd, |detfd| detfd.channel()).unwrap(),
3698 Some(end)
3699 );
3700 }
3701 let (received, _peer) = nix::unistd::pipe2(OFlag::empty()).unwrap();
3702 parent
3703 .discover_fd_from_current_process(owner, received.as_raw_fd())
3704 .unwrap();
3705 assert_eq!(
3706 parent
3707 .with_detfd(received.as_raw_fd(), |detfd| detfd.channel())
3708 .unwrap(),
3709 None
3710 );
3711 }
3712
3713 #[test]
3719 fn a_received_descriptor_is_recorded_as_the_in_guest_discovery_records_it() {
3720 let owner = DetTid::from_raw(9031);
3721 let facts = |fd: RawFd| {
3722 let fd_flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
3724 let status = unsafe { libc::fcntl(fd, libc::F_GETFL) };
3725 let mut stat: libc::stat = unsafe { std::mem::zeroed() };
3726 assert_eq!(unsafe { libc::fstat(fd, &mut stat) }, 0);
3727 (fd_flags, status, stat)
3728 };
3729 let shape = |metadata: &FileMetadata, fd: RawFd| {
3730 let detfd = &metadata.file_handles[&fd];
3731 (
3732 detfd.ty(),
3733 detfd.status_flags(),
3734 detfd.is_cloexec(),
3735 detfd.physically_nonblocking(),
3736 detfd.is_nonblocking(),
3737 detfd.known_flock_mode(),
3738 detfd.flock_may_have_been_granted(),
3739 detfd.stat().map(|stat| stat.raw_inode()),
3740 )
3741 };
3742 let (pipe_read, _pipe_write) =
3743 nix::unistd::pipe2(OFlag::O_NONBLOCK | OFlag::O_CLOEXEC).unwrap();
3744 let (socket, _peer) = nix::sys::socket::socketpair(
3745 nix::sys::socket::AddressFamily::Unix,
3746 nix::sys::socket::SockType::Stream,
3747 None,
3748 nix::sys::socket::SockFlag::empty(),
3749 )
3750 .unwrap();
3751 let file = std::fs::File::open("/proc/self/exe").unwrap();
3752 let random = std::fs::File::open("/dev/urandom").unwrap();
3753 let inotify = unsafe {
3755 <std::os::fd::OwnedFd as std::os::fd::FromRawFd>::from_raw_fd(libc::inotify_init1(
3756 libc::IN_NONBLOCK | libc::IN_CLOEXEC,
3757 ))
3758 };
3759 let cases = [
3760 (pipe_read.as_raw_fd(), FdType::Pipe),
3761 (socket.as_raw_fd(), FdType::Socket),
3762 (file.as_raw_fd(), FdType::Regular),
3763 (random.as_raw_fd(), FdType::Rng),
3764 (inotify.as_raw_fd(), FdType::Inotify),
3765 ];
3766 for (fd, ty) in cases {
3767 let mut recorded = FileMetadata::new(owner);
3768 let (fd_flags, status, stat) = facts(fd);
3769 let link = (stat.st_mode & libc::S_IFMT == 0)
3771 .then(|| super::own_fd_link(fd))
3772 .flatten();
3773 recorded
3774 .add_discovered_fd(owner, fd, fd_flags, status, &stat, link.as_deref())
3775 .unwrap();
3776 let mut discovered = FileMetadata::new(owner);
3777 discovered
3778 .discover_fd_from_current_process(owner, fd)
3779 .unwrap();
3780 assert_eq!(shape(&recorded, fd), shape(&discovered, fd), "fd {fd}");
3781 assert_eq!(recorded.file_handles[&fd].ty(), ty, "fd {fd}");
3782 assert_eq!(recorded.file_handles[&fd].known_flock_mode(), None);
3784 }
3785 let pipe = pipe_read.as_raw_fd();
3786 let mut recorded = FileMetadata::new(owner);
3787 let (fd_flags, status, stat) = facts(pipe);
3788 recorded
3789 .add_discovered_fd(owner, pipe, fd_flags, status, &stat, None)
3790 .unwrap();
3791 let detfd = &recorded.file_handles[&pipe];
3792 assert!(detfd.is_cloexec() && detfd.is_nonblocking() && detfd.physically_nonblocking());
3795 }
3796
3797 #[test]
3805 fn a_rediscovering_exec_keeps_pipe_channels_apart() {
3806 let leader = DetTid::from_raw(9015);
3807 let config = Config::default();
3808 let before = ThreadState::new(leader, &config, ());
3809 let (a_reader, _a_writer, a_end) = pipe_with_channel(&before);
3810 assert_eq!(
3811 unsafe { libc::fcntl(a_reader.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK) },
3812 0
3813 );
3814 let channels = before.pipe_channels_for_exec();
3815 assert_eq!(channels, BTreeMap::from([(a_reader.as_raw_fd(), a_end)]));
3816 let namespace = before.open_file_namespace_for_exec();
3817 assert_eq!(
3818 namespace,
3819 (leader, before.metadata().next_open_file_sequence)
3820 );
3821
3822 let after = ThreadState::new(leader, &config, ());
3823 after
3824 .metadata()
3825 .apply_exec_fd_state(
3826 leader,
3827 ExecFdState {
3828 channels,
3829 open_file_namespace: Some(namespace),
3830 ..ExecFdState::default()
3831 },
3832 true,
3833 )
3834 .unwrap();
3835 assert_eq!(
3836 after
3837 .with_detfd(a_reader.as_raw_fd(), |detfd| detfd.channel())
3838 .unwrap(),
3839 Some(a_end),
3840 "the carried channel survives rediscovery"
3841 );
3842 let (b_reader, _b_writer, b_end) = pipe_with_channel(&after);
3843 assert_ne!(b_end.channel, a_end.channel);
3844 let metadata = after.metadata();
3845 assert_ne!(
3846 metadata.file_handles[&b_reader.as_raw_fd()].open_file_id(),
3847 metadata.file_handles[&a_reader.as_raw_fd()].open_file_id()
3848 );
3849 }
3850
3851 #[test]
3856 fn an_exec_carrying_no_channel_still_reserves_the_old_numbering() {
3857 let leader = DetTid::from_raw(9016);
3858 let config = Config::default();
3859 let before = ThreadState::new(leader, &config, ());
3860 let (a_reader, _a_writer, a_end) = pipe_with_channel(&before);
3861 let namespace = before.open_file_namespace_for_exec();
3862 drop(a_reader);
3863
3864 let after = ThreadState::new(leader, &config, ());
3865 let state = ExecFdState {
3866 open_file_namespace: Some(namespace),
3867 ..ExecFdState::default()
3868 };
3869 assert!(
3870 !state.is_empty(),
3871 "a namespace-only state survives reconnect"
3872 );
3873 after
3874 .metadata()
3875 .apply_exec_fd_state(leader, state, true)
3876 .unwrap();
3877 let (_b_reader, _b_writer, b_end) = pipe_with_channel(&after);
3878 assert_ne!(b_end.channel, a_end.channel);
3879 }
3880
3881 #[test]
3886 fn an_exec_without_the_pipe_wake_latency_is_unchanged() {
3887 let leader = DetTid::from_raw(9017);
3888 let config = Config::default();
3889 let after = ThreadState::new(leader, &config, ());
3890 let state = ExecFdState::default();
3891 assert!(state.is_empty());
3892 assert_eq!(
3893 format!("{state:?}"),
3894 "ExecFdState { blocking: {}, inherited_reads: {}, \
3895 initialize_inherited_stdio: false, initial_input: None, \
3896 robust_wakes: [], open_files: None }"
3897 );
3898 after
3899 .metadata()
3900 .apply_exec_fd_state(leader, state, true)
3901 .unwrap();
3902 let (reader, _writer, _) = pipe_with_channel(&after);
3903 let fresh = ThreadState::new(leader, &config, ());
3904 let (fresh_reader, _fresh_writer, _) = pipe_with_channel(&fresh);
3905 assert_eq!(
3906 after.metadata().file_handles[&reader.as_raw_fd()].open_file_id(),
3907 fresh.metadata().file_handles[&fresh_reader.as_raw_fd()].open_file_id()
3908 );
3909 let channel_only = ExecFdState {
3910 channels: BTreeMap::from([(
3911 reader.as_raw_fd(),
3912 ChannelEnd {
3913 channel: ChannelId::Pipe(OpenFileId::new(leader, 0)),
3914 reads: true,
3915 writes: false,
3916 },
3917 )]),
3918 ..ExecFdState::default()
3919 };
3920 assert!(!channel_only.is_empty());
3921 assert!(format!("{channel_only:?}").contains("channels: {"));
3922
3923 let (plain, _plain_writer) = nix::unistd::pipe2(OFlag::empty()).unwrap();
3925 after
3926 .add_fd(plain.as_raw_fd(), OFlag::O_RDONLY, FdType::Pipe, None)
3927 .unwrap();
3928 let printed = format!("{:?}", after.metadata().file_handles[&plain.as_raw_fd()]);
3929 assert!(!printed.contains(" channel:"), "{printed}");
3930 assert!(printed.contains("timerfd: None }"), "{printed}");
3931 let printed = format!("{:?}", after.metadata().file_handles[&reader.as_raw_fd()]);
3932 assert!(
3933 printed.contains("timerfd: None, channel: ChannelEnd {"),
3934 "{printed}"
3935 );
3936 }
3937
3938 fn terminal_pair() -> (std::os::fd::OwnedFd, std::os::fd::OwnedFd) {
3939 use std::os::fd::FromRawFd;
3940 let (mut master, mut slave) = (-1, -1);
3941 assert_eq!(
3942 unsafe {
3943 libc::openpty(
3944 &raw mut master,
3945 &raw mut slave,
3946 std::ptr::null_mut(),
3947 std::ptr::null(),
3948 std::ptr::null(),
3949 )
3950 },
3951 0
3952 );
3953 unsafe {
3954 (
3955 std::os::fd::OwnedFd::from_raw_fd(master),
3956 std::os::fd::OwnedFd::from_raw_fd(slave),
3957 )
3958 }
3959 }
3960
3961 #[test]
3962 fn inherited_terminal_discovery_keeps_kernel_flags_and_nonterminal_types() {
3963 let owner = DetTid::from_raw(9);
3964 let (_master, slave) = terminal_pair();
3965 let regular = tempfile::tempfile().unwrap();
3966 let zero = std::fs::File::open("/dev/zero").unwrap();
3967 let null = std::fs::File::open("/dev/null").unwrap();
3968 let (pipe, _writer) = nix::unistd::pipe().unwrap();
3969 let fds = [
3970 slave.as_raw_fd(),
3971 regular.as_raw_fd(),
3972 zero.as_raw_fd(),
3973 null.as_raw_fd(),
3974 pipe.as_raw_fd(),
3975 ];
3976 let before: Vec<_> = fds
3977 .iter()
3978 .map(|fd| unsafe {
3979 (
3980 libc::fcntl(*fd, libc::F_GETFL),
3981 libc::fcntl(*fd, libc::F_GETFD),
3982 )
3983 })
3984 .collect();
3985 let mut metadata = FileMetadata::new(owner);
3986 for fd in fds {
3987 metadata
3988 .discover_fd_from_current_process(owner, fd)
3989 .unwrap();
3990 }
3991 metadata.discover_inherited_terminals(fds);
3992 for (index, fd) in fds.into_iter().enumerate() {
3993 let detfd = &metadata.file_handles[&fd];
3994 assert_eq!(detfd.inherited_external_read().is_some(), index == 0);
3995 assert_eq!(
3996 detfd.ty(),
3997 if index == 4 {
3998 FdType::Pipe
3999 } else {
4000 FdType::Regular
4001 }
4002 );
4003 assert_eq!(
4004 unsafe {
4005 (
4006 libc::fcntl(fd, libc::F_GETFL),
4007 libc::fcntl(fd, libc::F_GETFD),
4008 )
4009 },
4010 before[index]
4011 );
4012 }
4013 }
4014
4015 #[test]
4024 fn a_cpuinfo_placeholder_waits_for_the_sessions_answer() {
4025 let owner = DetTid::from_raw(9);
4026 let forged = || {
4027 let raw = unsafe {
4029 libc::memfd_create(
4030 c"hermit-procfs:/proc/cpuinfo@".as_ptr(),
4031 libc::MFD_ALLOW_SEALING,
4032 )
4033 };
4034 assert!(
4035 raw >= 0,
4036 "memfd_create: {}",
4037 std::io::Error::last_os_error()
4038 );
4039 let fd = unsafe { <std::os::fd::OwnedFd as std::os::fd::FromRawFd>::from_raw_fd(raw) };
4041 let seals = crate::syscalls::rendered_procfs::PLACEHOLDER_SEALS;
4042 assert_eq!(unsafe { libc::fcntl(raw, libc::F_ADD_SEALS, seals) }, 0);
4043 fd
4044 };
4045 let ordinary = |metadata: &FileMetadata, fd: RawFd| {
4046 metadata.file_handles[&fd].procfs_rendered_path().is_none()
4047 };
4048 let fd = forged();
4051 let mut metadata = FileMetadata::new(owner);
4052 metadata
4053 .discover_fd_from_current_process(owner, fd.as_raw_fd())
4054 .unwrap();
4055 assert!(ordinary(&metadata, fd.as_raw_fd()));
4056 metadata.decide_cpuinfo_placeholders(false).unwrap();
4057 assert!(ordinary(&metadata, fd.as_raw_fd()));
4058 let later = forged();
4060 metadata
4061 .discover_fd_from_current_process(owner, later.as_raw_fd())
4062 .unwrap();
4063 assert!(ordinary(&metadata, later.as_raw_fd()));
4064 metadata.decide_cpuinfo_placeholders(true).unwrap();
4066 assert!(ordinary(&metadata, fd.as_raw_fd()));
4067 let mut metadata = FileMetadata::new(owner);
4070 metadata
4071 .discover_fd_from_current_process(owner, fd.as_raw_fd())
4072 .unwrap();
4073 assert!(ordinary(&metadata, fd.as_raw_fd()));
4074 metadata.decide_cpuinfo_placeholders(true).unwrap();
4075 assert_eq!(
4076 metadata.file_handles[&fd.as_raw_fd()].procfs_rendered_path(),
4077 Some(std::path::Path::new("/proc/cpuinfo"))
4078 );
4079 let after = forged();
4080 metadata
4081 .discover_fd_from_current_process(owner, after.as_raw_fd())
4082 .unwrap();
4083 assert_eq!(
4084 metadata.file_handles[&after.as_raw_fd()].procfs_rendered_path(),
4085 Some(std::path::Path::new("/proc/cpuinfo"))
4086 );
4087 }
4088
4089 #[test]
4096 fn an_arch_status_placeholder_waits_for_the_sessions_answer() {
4097 let owner = DetTid::from_raw(9);
4098 let stand_in = || {
4099 let raw = unsafe {
4101 libc::memfd_create(
4102 c"hermit-procfs:/proc/3/arch_status#1@".as_ptr(),
4103 libc::MFD_ALLOW_SEALING,
4104 )
4105 };
4106 assert!(
4107 raw >= 0,
4108 "memfd_create: {}",
4109 std::io::Error::last_os_error()
4110 );
4111 let fd = unsafe { <std::os::fd::OwnedFd as std::os::fd::FromRawFd>::from_raw_fd(raw) };
4113 let seals = crate::syscalls::rendered_procfs::PLACEHOLDER_SEALS;
4114 assert_eq!(unsafe { libc::fcntl(raw, libc::F_ADD_SEALS, seals) }, 0);
4115 fd
4116 };
4117 let fd = stand_in();
4119 let mut metadata = FileMetadata::new(owner);
4120 metadata
4121 .discover_fd_from_current_process(owner, fd.as_raw_fd())
4122 .unwrap();
4123 assert!(
4124 metadata.file_handles[&fd.as_raw_fd()]
4125 .procfs_rendered_path()
4126 .is_none()
4127 );
4128 metadata.decide_cpuinfo_placeholders(false).unwrap();
4129 assert!(
4130 metadata.file_handles[&fd.as_raw_fd()]
4131 .procfs_rendered_path()
4132 .is_none()
4133 );
4134 let mut metadata = FileMetadata::new(owner);
4137 metadata
4138 .discover_fd_from_current_process(owner, fd.as_raw_fd())
4139 .unwrap();
4140 assert!(
4141 metadata.file_handles[&fd.as_raw_fd()]
4142 .procfs_rendered_path()
4143 .is_none()
4144 );
4145 metadata.decide_cpuinfo_placeholders(true).unwrap();
4146 assert_eq!(
4147 metadata.file_handles[&fd.as_raw_fd()].procfs_rendered_path(),
4148 Some(std::path::Path::new("/proc/3/arch_status"))
4149 );
4150 }
4151
4152 #[test]
4158 fn a_memfd_with_a_stand_in_name_outside_the_proc_level_stays_a_memfd() {
4159 use detcore_model::config::ProcLevel;
4160 let owner = DetTid::from_raw(9);
4161 let raw = unsafe {
4163 libc::memfd_create(
4164 c"hermit-procfs:/proc/diskstats@".as_ptr(),
4165 libc::MFD_ALLOW_SEALING,
4166 )
4167 };
4168 assert!(
4169 raw >= 0,
4170 "memfd_create: {}",
4171 std::io::Error::last_os_error()
4172 );
4173 let fd = unsafe { <std::os::fd::OwnedFd as std::os::fd::FromRawFd>::from_raw_fd(raw) };
4175 let seals = crate::syscalls::rendered_procfs::PLACEHOLDER_SEALS;
4176 assert_eq!(unsafe { libc::fcntl(raw, libc::F_ADD_SEALS, seals) }, 0);
4177 let diskstats = std::path::Path::new("/proc/diskstats");
4178 assert!(crate::syscalls::rendered_procfs::named_file_outside_level(
4179 diskstats,
4180 ProcLevel::Minimal
4181 ));
4182 assert!(!crate::syscalls::rendered_procfs::named_file_outside_level(
4183 diskstats,
4184 ProcLevel::Standard
4185 ));
4186 let mut noted = FileMetadata::new(owner);
4187 noted.note_proc_level(ProcLevel::Minimal);
4188 let mut minimal: FileMetadata =
4191 serde_json::from_slice(&serde_json::to_vec(¬ed.for_exec(owner)).unwrap()).unwrap();
4192 minimal
4193 .discover_fd_from_current_process(owner, fd.as_raw_fd())
4194 .unwrap();
4195 assert!(
4196 minimal.file_handles[&fd.as_raw_fd()]
4197 .procfs_rendered_path()
4198 .is_none()
4199 );
4200 let mut standard = FileMetadata::new(owner);
4201 standard.note_proc_level(ProcLevel::Standard);
4202 standard
4203 .discover_fd_from_current_process(owner, fd.as_raw_fd())
4204 .unwrap();
4205 assert_eq!(
4206 standard.file_handles[&fd.as_raw_fd()].procfs_rendered_path(),
4207 Some(diskstats)
4208 );
4209 }
4210
4211 #[test]
4212 fn inherited_terminal_aliases_survive_fork_serialization_and_exec_handoff() {
4213 use std::os::fd::FromRawFd;
4214 let owner = DetTid::from_raw(9);
4215 let (_master, slave) = terminal_pair();
4216 let fd = slave.as_raw_fd();
4217 let duplicate = unsafe { libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 3) };
4218 assert!(duplicate >= 0);
4219 let duplicate = unsafe { std::os::fd::OwnedFd::from_raw_fd(duplicate) };
4220 let alias = duplicate.as_raw_fd();
4221 assert_eq!(unsafe { libc::fcntl(alias, libc::F_SETFD, 0) }, 0);
4223 let mut metadata = FileMetadata::new(owner);
4224 metadata
4225 .discover_fd_from_current_process(owner, fd)
4226 .unwrap();
4227 metadata.file_handles[&fd].set_resource(ResourceID::Device(Device::ContainerStdin));
4228 metadata.discover_inherited_terminals([fd]);
4229 metadata.dup_fd(fd, alias, OFlag::empty()).unwrap();
4230 metadata.dup_fd(fd, 100_000, OFlag::O_CLOEXEC).unwrap();
4231 let child = metadata.fork_for(DetTid::from_raw(10));
4232 let encoded = serde_json::to_vec(&child).unwrap();
4233 let child: FileMetadata = serde_json::from_slice(&encoded).unwrap();
4234 let state = ExecFdState {
4235 inherited_reads: child.inherited_reads_for_exec(),
4236 ..Default::default()
4237 };
4238 assert_eq!(
4239 state.inherited_reads.len(),
4240 2,
4241 "CLOEXEC alias is not transferred"
4242 );
4243 assert_eq!(state.inherited_reads[&fd], state.inherited_reads[&alias]);
4244 let mut restored = FileMetadata::new(owner);
4245 restored
4246 .apply_exec_fd_state(
4247 owner,
4248 serde_json::from_slice(&serde_json::to_vec(&state).unwrap()).unwrap(),
4249 true,
4250 )
4251 .unwrap();
4252 assert_eq!(
4253 restored.file_handles[&fd].open_file_id(),
4254 restored.file_handles[&alias].open_file_id()
4255 );
4256 assert_eq!(
4257 restored.file_handles[&alias].resource(),
4258 Some(ResourceID::Device(Device::ContainerStdin))
4259 );
4260 let flags = unsafe { libc::fcntl(alias, libc::F_GETFL) };
4261 assert_eq!(
4262 unsafe { libc::fcntl(alias, libc::F_SETFL, flags | libc::O_NONBLOCK) },
4263 0
4264 );
4265 restored.file_handles[&alias].set_status_flags(flags | libc::O_NONBLOCK);
4266 assert!(restored.file_handles[&fd].is_nonblocking());
4267 assert!(restored.file_handles[&fd].physically_nonblocking());
4268 assert_eq!(
4269 unsafe { libc::fcntl(fd, libc::F_GETFL) },
4270 flags | libc::O_NONBLOCK
4271 );
4272 let mut byte = 0_u8;
4273 assert_eq!(unsafe { libc::read(fd, (&raw mut byte).cast(), 1) }, -1);
4274 assert_eq!(Errno::last(), Errno::EAGAIN);
4275 }
4276
4277 #[test]
4278 fn guest_created_terminal_and_reused_descriptor_do_not_gain_inherited_provenance() {
4279 let owner = DetTid::from_raw(9);
4280 let (_master, slave) = terminal_pair();
4281 let fd = slave.as_raw_fd();
4282 let mut guest_created = FileMetadata::new(owner);
4283 guest_created
4284 .discover_fd_from_current_process(owner, fd)
4285 .unwrap();
4286 guest_created.dup_fd(fd, 0, OFlag::empty()).unwrap();
4287 assert!(guest_created.inherited_reads_for_exec().is_empty());
4288 let mut fresh = FileMetadata::new(owner);
4289 fresh
4290 .apply_exec_fd_state(owner, ExecFdState::default(), true)
4291 .unwrap();
4292 fresh.discover_fd_from_current_process(owner, fd).unwrap();
4293 assert!(fresh.file_handles[&fd].inherited_external_read().is_none());
4294
4295 guest_created.discover_inherited_terminals([fd]);
4296 let state = ExecFdState {
4297 inherited_reads: guest_created.inherited_reads_for_exec(),
4298 ..Default::default()
4299 };
4300 let state = ExecFdState {
4302 inherited_reads: state
4303 .inherited_reads
4304 .into_iter()
4305 .filter(|(number, _)| *number == fd)
4306 .collect(),
4307 ..Default::default()
4308 };
4309 guest_created.remove_fd(fd);
4310 let zero = std::fs::File::open("/dev/zero").unwrap();
4311 assert_eq!(unsafe { libc::dup2(zero.as_raw_fd(), fd) }, fd);
4312 let mut reused = FileMetadata::new(owner);
4313 assert_eq!(
4314 reused.apply_exec_fd_state(owner, state, true),
4315 Err(Errno::EIO)
4316 );
4317 assert!(reused.file_handles[&fd].inherited_external_read().is_none());
4318 }
4319
4320 #[test]
4321 fn on_demand_discovery_finds_a_live_descriptor() {
4322 let owner = DetTid::from_raw(9);
4323 let file = tempfile::NamedTempFile::new().expect("create test descriptor");
4324 let fd = file.as_raw_fd();
4325 assert_eq!(unsafe { libc::flock(fd, libc::LOCK_SH | libc::LOCK_NB) }, 0);
4326 let mut metadata = FileMetadata::new(owner);
4327
4328 assert_eq!(metadata.with_detfd(fd, |_| ()), Err(Errno::EBADF));
4329 metadata
4330 .discover_fd_from_current_process(owner, fd)
4331 .expect("live descriptor should be discovered");
4332 assert_eq!(
4333 metadata
4334 .with_detfd(fd, |detfd| detfd.ty())
4335 .expect("discovered descriptor should be tracked"),
4336 FdType::Regular
4337 );
4338 assert_eq!(
4339 metadata
4340 .with_detfd(fd, |detfd| detfd.known_flock_mode())
4341 .expect("discovered descriptor should be tracked"),
4342 None,
4343 "a live descriptor may already hold a flock that Detcore did not observe"
4344 );
4345 assert!(
4346 !metadata
4347 .with_detfd(fd, |detfd| detfd.flock_mode_may_be_stale())
4348 .expect("discovered descriptor should be tracked"),
4349 "first discovery must not invent a previously known lock state"
4350 );
4351 assert!(
4352 !metadata.has_unsafe_vfork_flock_state(),
4353 "never-observed history is distinct from a held or invalidated cached claim"
4354 );
4355
4356 let separate = std::fs::OpenOptions::new()
4359 .read(true)
4360 .write(true)
4361 .open(file.path())
4362 .expect("open an independent file description");
4363 assert_eq!(
4364 unsafe { libc::flock(separate.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) },
4365 -1
4366 );
4367 assert_eq!(Errno::last(), Errno::EWOULDBLOCK);
4368
4369 metadata
4370 .with_detfd(fd, |detfd| detfd.set_flock_mode(None))
4371 .expect("discovered descriptor should remain tracked");
4372 assert_eq!(metadata.file_handles[&fd].known_flock_mode(), None);
4373 assert!(!metadata.file_handles[&fd].flock_mode_may_be_stale());
4374 }
4375
4376 #[test]
4377 fn discovery_classifies_random_devices_by_device_number() {
4378 let owner = DetTid::from_raw(9);
4379 let random = std::fs::File::open("/dev/random").unwrap();
4380 let urandom = std::fs::File::open("/dev/urandom").unwrap();
4381 let null = std::fs::File::open("/dev/null").unwrap();
4382 let path_only = nix::fcntl::open(
4383 "/dev/urandom",
4384 OFlag::O_PATH | OFlag::O_CLOEXEC,
4385 nix::sys::stat::Mode::empty(),
4386 )
4387 .unwrap();
4388 let mut metadata = FileMetadata::new(owner);
4389 for (fd, expected) in [
4390 (random.as_raw_fd(), FdType::Rng),
4391 (urandom.as_raw_fd(), FdType::Rng),
4392 (null.as_raw_fd(), FdType::Regular),
4395 (path_only.as_raw_fd(), FdType::Regular),
4396 ] {
4397 metadata
4398 .discover_fd_from_current_process(owner, fd)
4399 .expect("live descriptor should be discovered");
4400 assert_eq!(metadata.file_handles[&fd].ty(), expected, "fd {fd}");
4401 }
4402 for fd in [random.as_raw_fd(), urandom.as_raw_fd()] {
4405 assert_eq!(
4406 metadata.file_handles[&fd].random_device_stream(),
4407 crate::fd::RandomDeviceStream::discovered(owner, fd)
4408 );
4409 }
4410 assert_eq!(metadata.next_random_device_sequence, 0);
4411 }
4412
4413 #[test]
4414 fn rediscovery_preserves_known_and_invalidated_flock_history() {
4415 let owner = DetTid::from_raw(9);
4416 let file = tempfile::tempfile().expect("create test descriptor");
4417 let fd = file.as_raw_fd();
4418 let mut metadata = FileMetadata::new(owner);
4419 metadata
4420 .add_fd(owner, fd, OFlag::O_RDWR, FdType::Regular, None)
4421 .expect("register an observed open");
4422 let description = metadata.file_handles[&fd].open_file_id();
4423 assert_eq!(metadata.file_handles[&fd].known_flock_mode(), Some(None));
4424 metadata.file_handles[&fd].set_flock_mode(Some(libc::LOCK_SH));
4425
4426 metadata
4427 .discover_fd_from_current_process(owner, fd)
4428 .expect("rediscover an existing descriptor");
4429 assert_eq!(metadata.file_handles[&fd].open_file_id(), description);
4430 assert_eq!(
4431 metadata.file_handles[&fd].known_flock_mode(),
4432 Some(Some(libc::LOCK_SH))
4433 );
4434 assert!(metadata.has_unsafe_vfork_flock_state());
4435
4436 metadata.file_handles[&fd].forget_flock_mode();
4437 metadata
4438 .discover_fd_from_current_process(owner, fd)
4439 .expect("rediscover an invalidated descriptor");
4440 assert_eq!(metadata.file_handles[&fd].open_file_id(), description);
4441 assert_eq!(metadata.file_handles[&fd].known_flock_mode(), None);
4442 assert!(metadata.file_handles[&fd].flock_mode_may_be_stale());
4443 assert!(metadata.file_handles[&fd].flock_may_have_been_granted());
4444 assert!(metadata.has_unsafe_vfork_flock_state());
4445 }
4446
4447 #[test]
4452 fn invalidated_description_never_locked_does_not_refuse_vfork() {
4453 let owner = DetTid::from_raw(9);
4454 let mut metadata = FileMetadata::new(owner);
4455 metadata
4456 .add_fd(owner, 3, OFlag::empty(), FdType::Pipe, None)
4457 .expect("register a pipe end");
4458 assert!(!metadata.file_handles[&3].flock_may_have_been_granted());
4459
4460 let child = metadata.fork_for(DetTid::from_raw(10));
4461 for table in [&metadata, &child] {
4462 let detfd = &table.file_handles[&3];
4463 assert_eq!(detfd.known_flock_mode(), None);
4464 assert!(detfd.flock_mode_may_be_stale());
4465 assert!(!detfd.flock_may_have_been_granted());
4466 assert!(
4467 !table.has_unsafe_vfork_flock_state(),
4468 "an invalidated description with no grant through it holds no Detcore claim"
4469 );
4470 }
4471
4472 metadata.file_handles[&3].set_flock_mode(Some(libc::LOCK_EX));
4475 assert_eq!(metadata.file_handles[&3].known_flock_mode(), None);
4476 assert!(metadata.file_handles[&3].flock_may_have_been_granted());
4477 assert!(metadata.has_unsafe_vfork_flock_state());
4478 }
4479
4480 #[test]
4484 fn a_description_serialized_without_the_grant_fact_reads_it_as_granted() {
4485 let owner = DetTid::from_raw(9);
4486 let mut metadata = FileMetadata::new(owner);
4487 metadata
4488 .add_fd(owner, 3, OFlag::empty(), FdType::Pipe, None)
4489 .expect("register a pipe end");
4490 let child = metadata.fork_for(DetTid::from_raw(10));
4491 assert!(!child.has_unsafe_vfork_flock_state());
4492 let encoded = serde_json::to_string(&child).expect("serialize the table");
4493 assert_eq!(encoded.matches("\"flock_ever_granted\":false").count(), 1);
4494 let without = encoded.replace("\"flock_ever_granted\":false,", "");
4495 assert_ne!(without, encoded, "the field must have been removed");
4496 let restored: FileMetadata = serde_json::from_str(&without).expect("deserialize the table");
4497 assert!(restored.file_handles[&3].flock_mode_may_be_stale());
4498 assert!(restored.file_handles[&3].flock_may_have_been_granted());
4499 assert!(restored.has_unsafe_vfork_flock_state());
4500 }
4501
4502 #[test]
4507 fn lock_granted_before_fork_still_refuses_vfork_after_it() {
4508 let owner = DetTid::from_raw(9);
4509 let mut metadata = FileMetadata::new(owner);
4510 metadata
4511 .add_fd(owner, 3, OFlag::empty(), FdType::Regular, None)
4512 .expect("register descriptor");
4513 metadata.file_handles[&3].set_flock_mode(Some(libc::LOCK_SH));
4514 metadata.file_handles[&3].set_flock_mode(None);
4515 assert_eq!(metadata.file_handles[&3].known_flock_mode(), Some(None));
4516 assert!(
4517 !metadata.has_unsafe_vfork_flock_state(),
4518 "a known-unlocked description is safe while its mode is known"
4519 );
4520
4521 let child = metadata.fork_for(DetTid::from_raw(10));
4522 for table in [&metadata, &child] {
4523 assert!(table.file_handles[&3].flock_may_have_been_granted());
4524 assert!(table.has_unsafe_vfork_flock_state());
4525 }
4526 }
4527
4528 #[test]
4533 fn a_deserialized_table_keeps_dup_aliases_as_one_open_file() {
4534 let owner = DetTid::from_raw(9);
4535 let mut metadata = FileMetadata::new(owner);
4536 metadata
4537 .add_fd(owner, 3, OFlag::empty(), FdType::Rng, None)
4538 .expect("register descriptor");
4539 metadata
4540 .dup_fd(3, 4, OFlag::O_CLOEXEC)
4541 .expect("dup the descriptor");
4542 metadata
4543 .add_fd(owner, 5, OFlag::empty(), FdType::Rng, None)
4544 .expect("register a separate open");
4545 let encoded = serde_json::to_string(&metadata).expect("serialize the table");
4546 let restored: FileMetadata = serde_json::from_str(&encoded).expect("deserialize the table");
4547 let three = restored.file_handles[&3].clone();
4548 let four = restored.file_handles[&4].clone();
4549 let five = restored.file_handles[&5].clone();
4550 assert_eq!(three.open_file_id(), four.open_file_id());
4551 assert_ne!(three.open_file_id(), five.open_file_id());
4552 four.advance_random_device_offset(50);
4554 assert_eq!(three.random_device_offset(), 50);
4555 assert_eq!(five.random_device_offset(), 0);
4556 assert_eq!(three.open_file_alias_count(), 4);
4558 assert!(!three.is_cloexec());
4560 assert!(four.is_cloexec());
4561 }
4562
4563 #[test]
4569 fn each_random_device_open_reads_its_own_stream() {
4570 use crate::fd::RandomDeviceStream;
4571 let owner = DetTid::from_raw(9);
4572 let thread = DetTid::from_raw(11);
4573 let stream = |table: &mut FileMetadata, fd| {
4574 table
4575 .with_detfd(fd, |detfd| detfd.random_device_stream())
4576 .expect("registered descriptor")
4577 };
4578 let open = |table: &mut FileMetadata, creator, fd| {
4579 table
4580 .add_fd(creator, fd, OFlag::empty(), FdType::Regular, None)
4581 .expect("register the open");
4582 table
4583 .classify_random_device(creator, fd)
4584 .expect("classify it");
4585 };
4586 let mut metadata = FileMetadata::new(owner);
4587 open(&mut metadata, owner, 3);
4588 metadata.dup_fd(3, 4, OFlag::empty()).expect("dup it");
4589 metadata
4590 .add_fd(owner, 5, OFlag::empty(), FdType::Regular, None)
4591 .expect("open a regular file");
4592 open(&mut metadata, thread, 6);
4593 assert_eq!(
4594 metadata.classify_random_device(thread, 7),
4595 Err(Errno::EBADF)
4596 );
4597 let child = DetTid::from_raw(10);
4598 let mut forked = metadata.fork_for(child);
4599 open(&mut forked, child, 7);
4600 let mut exec = metadata.for_exec(owner);
4601 open(&mut exec, owner, 7);
4602 let mut rebuilt = FileMetadata::new(owner);
4603 rebuilt
4604 .apply_exec_fd_state(
4605 owner,
4606 ExecFdState {
4607 random_device_sequence: 2,
4608 ..ExecFdState::default()
4609 },
4610 true,
4611 )
4612 .expect("apply the exec exchange");
4613 open(&mut rebuilt, owner, 3);
4614
4615 let named = |creator, sequence| RandomDeviceStream { creator, sequence };
4616 assert_eq!(stream(&mut metadata, 3), named(owner, 0));
4617 assert_eq!(stream(&mut metadata, 4), named(owner, 0));
4618 assert_eq!(stream(&mut metadata, 6), named(thread, 1));
4619 assert_eq!(stream(&mut forked, 3), named(owner, 0));
4620 assert_eq!(stream(&mut forked, 7), named(child, 2));
4621 assert_eq!(stream(&mut exec, 7), named(owner, 2));
4622 assert_eq!(stream(&mut rebuilt, 3), named(owner, 2));
4623 }
4624
4625 #[test]
4626 fn fork_makes_inherited_flock_state_unknown_in_parent_and_child() {
4627 let owner = DetTid::from_raw(9);
4628 let child = DetTid::from_raw(10);
4629 let mut metadata = FileMetadata::new(owner);
4630 metadata
4631 .add_fd(owner, 3, OFlag::empty(), FdType::Regular, None)
4632 .expect("register descriptor");
4633 metadata
4634 .with_detfd(3, |detfd| detfd.set_flock_mode(Some(libc::LOCK_SH)))
4635 .expect("registered descriptor");
4636
4637 let mut child_metadata = metadata.fork_for(child);
4638 assert_eq!(
4639 metadata
4640 .with_detfd(3, |detfd| detfd.known_flock_mode())
4641 .expect("parent descriptor should remain tracked"),
4642 None,
4643 "parent cache must not become stale when the child changes the shared open file description"
4644 );
4645 assert_eq!(
4646 child_metadata
4647 .with_detfd(3, |detfd| detfd.known_flock_mode())
4648 .expect("child descriptor should be inherited"),
4649 None,
4650 "child starts with the same deliberately unknown shared state"
4651 );
4652 }
4653
4654 #[test]
4655 fn exec_preserves_known_flock_mode_for_surviving_descriptors() {
4656 let owner = DetTid::from_raw(9);
4657 let mut metadata = FileMetadata::new(owner);
4658 metadata
4659 .add_fd(owner, 3, OFlag::empty(), FdType::Regular, None)
4660 .expect("register descriptor");
4661 metadata
4662 .with_detfd(3, |detfd| detfd.set_flock_mode(Some(libc::LOCK_SH)))
4663 .expect("registered descriptor");
4664
4665 let mut after_exec = metadata.for_exec(DetTid::from_raw(10));
4666 assert_eq!(
4667 after_exec
4668 .with_detfd(3, |detfd| detfd.known_flock_mode())
4669 .expect("non-cloexec descriptor should survive"),
4670 Some(Some(libc::LOCK_SH)),
4671 "exec must preserve known flock state for a surviving open file description"
4672 );
4673 }
4674
4675 #[test]
4676 fn discovered_stdio_uses_container_wide_resources() {
4677 let owner = DetTid::from_raw(9);
4678 let mut metadata = FileMetadata::new(owner);
4679
4680 metadata
4681 .discover_fd_from_current_process(owner, libc::STDOUT_FILENO)
4682 .expect("live stdout should be discovered");
4683
4684 assert_eq!(
4685 metadata
4686 .with_detfd(libc::STDOUT_FILENO, |detfd| detfd.resource())
4687 .expect("discovered stdout should be tracked"),
4688 Some(ResourceID::Device(Device::ContainerStdout))
4689 );
4690
4691 use crate::syscalls::deterministic_stdio_inode_for_resource;
4695
4696 let inherited_stat = metadata
4697 .with_detfd(libc::STDOUT_FILENO, |detfd| detfd.stat().unwrap())
4698 .unwrap();
4699 metadata.dup_fd(1, 7, OFlag::empty()).unwrap();
4700 assert_eq!(
4701 metadata.with_detfd(7, |detfd| detfd.resource()).unwrap(),
4702 Some(ResourceID::Device(Device::ContainerStdout))
4703 );
4704 assert_eq!(
4705 metadata
4706 .with_detfd(7, |detfd| {
4707 deterministic_stdio_inode_for_resource(7, detfd.resource())
4708 })
4709 .unwrap(),
4710 None
4711 );
4712 for fd in 0..=2 {
4713 metadata.remove_fd(fd);
4714 let mut ordinary = inherited_stat;
4715 ordinary.inode = 123 + fd as u64;
4716 metadata
4717 .add_fd(owner, fd, OFlag::empty(), FdType::Regular, Some(ordinary))
4718 .unwrap();
4719 metadata.dup_fd(fd, 8, OFlag::empty()).unwrap();
4720 for ordinary_fd in [fd, 8] {
4721 assert_eq!(
4722 metadata
4723 .with_detfd(ordinary_fd, |detfd| {
4724 deterministic_stdio_inode_for_resource(ordinary_fd, detfd.resource())
4725 })
4726 .unwrap(),
4727 None
4728 );
4729 }
4730 assert_eq!(
4731 metadata
4732 .with_detfd(fd, |detfd| detfd.open_file_id())
4733 .unwrap(),
4734 metadata
4735 .with_detfd(8, |detfd| detfd.open_file_id())
4736 .unwrap()
4737 );
4738 metadata.dup_fd(7, fd, OFlag::empty()).unwrap();
4739 assert_eq!(
4740 metadata
4741 .with_detfd(fd, |detfd| {
4742 deterministic_stdio_inode_for_resource(fd, detfd.resource())
4743 })
4744 .unwrap(),
4745 Some(DetInode::mint(1000 + fd as u64)),
4746 "inherited streams retain the existing numeric-slot outcome"
4747 );
4748 }
4749 metadata.dup_fd(7, 9, OFlag::O_CLOEXEC).unwrap();
4750 let child = metadata.fork_for(DetTid::from_raw(10));
4751 let after_exec = child.for_exec(DetTid::from_raw(10));
4752 assert!(!after_exec.file_handles.contains_key(&9));
4753 assert!(after_exec.file_handles.contains_key(&7));
4754 }
4755
4756 #[test]
4757 fn discovered_pipe_preserves_inherited_nonblocking() {
4758 let owner = DetTid::from_raw(9);
4759 let mut fds = [-1; 2];
4760 assert_eq!(
4761 unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_NONBLOCK) },
4762 0
4763 );
4764 let mut metadata = FileMetadata::new(owner);
4765
4766 metadata
4767 .discover_fd_from_current_process(owner, fds[0])
4768 .expect("live pipe should be discovered");
4769 let flags = metadata
4770 .with_detfd(fds[0], |detfd| {
4771 (detfd.is_nonblocking(), detfd.physically_nonblocking())
4772 })
4773 .expect("discovered pipe should be tracked");
4774
4775 assert_eq!(flags, (true, true));
4776 unsafe {
4777 libc::close(fds[0]);
4778 libc::close(fds[1]);
4779 }
4780 }
4781
4782 #[test]
4783 fn exec_handoff_restores_scheduler_pipe_as_logically_blocking() {
4784 let owner = DetTid::from_raw(9);
4785 let mut fds = [-1; 2];
4786 assert_eq!(
4787 unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_NONBLOCK) },
4788 0
4789 );
4790 let mut before_exec = FileMetadata::new(owner);
4791 before_exec
4792 .add_fd(owner, fds[0], OFlag::empty(), FdType::Pipe, None)
4793 .expect("scheduler pipe should be registered");
4794 before_exec
4795 .with_detfd(fds[0], |detfd| detfd.set_physically_nonblocking())
4796 .expect("scheduler pipe should remain registered");
4797
4798 let overrides = before_exec.exec_blocking_overrides();
4799 assert_eq!(overrides, BTreeSet::from([fds[0]]));
4800
4801 let mut after_exec = FileMetadata::new(owner);
4802 after_exec.apply_exec_blocking_overrides(owner, overrides);
4803 assert_eq!(
4804 after_exec
4805 .with_detfd(fds[0], |detfd| {
4806 (detfd.is_nonblocking(), detfd.physically_nonblocking())
4807 })
4808 .expect("inherited pipe should be rediscovered"),
4809 (false, true)
4810 );
4811
4812 unsafe {
4813 libc::close(fds[0]);
4814 libc::close(fds[1]);
4815 }
4816 }
4817
4818 #[test]
4819 fn fork_copies_slots_but_preserves_open_file_aliases() {
4820 let parent_tid = DetTid::from_raw(10);
4821 let child_tid = DetTid::from_raw(11);
4822 let mut parent = FileMetadata::new(parent_tid);
4823 parent
4824 .add_fd(parent_tid, 3, OFlag::O_NONBLOCK, FdType::Socket, None)
4825 .expect("parent fd should be inserted");
4826 parent
4827 .dup_fd(3, 4, OFlag::O_CLOEXEC)
4828 .expect("dup should succeed");
4829
4830 let parent_open = parent
4831 .with_detfd(3, |fd| fd.open_file_id())
4832 .expect("parent fd should exist");
4833 let duplicate_open = parent
4834 .with_detfd(4, |fd| fd.open_file_id())
4835 .expect("duplicate fd should exist");
4836 assert_eq!(parent_open, duplicate_open);
4837
4838 let initial_timestamp = LogicalTime::from_nanos(1_234_567_890);
4839 parent
4840 .with_detfd(3, |fd| fd.set_socket_receive_timestamp(initial_timestamp))
4841 .expect("parent socket should accept a receive timestamp");
4842
4843 let mut child = parent.fork_for(child_tid);
4844 assert_ne!(parent.files_id, child.files_id);
4845 assert_ne!(
4846 FdSlot {
4847 files: parent.files_id,
4848 fd: 3,
4849 },
4850 FdSlot {
4851 files: child.files_id,
4852 fd: 3,
4853 }
4854 );
4855 assert_eq!(
4856 parent_open,
4857 child
4858 .with_detfd(3, |fd| fd.open_file_id())
4859 .expect("forked fd should retain its open file identity")
4860 );
4861 assert_eq!(
4862 child
4863 .with_detfd(3, |fd| fd.socket_receive_timestamp())
4864 .expect("forked fd should retain its receive timestamp"),
4865 Some(initial_timestamp)
4866 );
4867 let child_timestamp = LogicalTime::from_nanos(2_345_678_901);
4868 child
4869 .with_detfd(3, |fd| fd.set_socket_receive_timestamp(child_timestamp))
4870 .expect("child socket should update the shared receive timestamp");
4871 assert_eq!(
4872 parent
4873 .with_detfd(4, |fd| fd.socket_receive_timestamp())
4874 .expect("parent duplicate should see the child update"),
4875 Some(child_timestamp)
4876 );
4877
4878 parent
4879 .add_fd(parent_tid, 5, OFlag::empty(), FdType::Regular, None)
4880 .expect("new parent fd should be inserted");
4881 child
4882 .add_fd(child_tid, 5, OFlag::empty(), FdType::Regular, None)
4883 .expect("new child fd should be inserted");
4884 assert_ne!(
4885 parent
4886 .with_detfd(5, |fd| fd.open_file_id())
4887 .expect("new parent fd should exist"),
4888 child
4889 .with_detfd(5, |fd| fd.open_file_id())
4890 .expect("new child fd should exist"),
4891 "separate opens after fork must not alias"
4892 );
4893 }
4894
4895 #[test]
4896 fn pidfd_getfd_target_requires_the_calling_leader() {
4897 let leader = DetPid::from_raw(31);
4898 assert!(pidfd_getfd_targets_calling_task(
4899 Some(leader),
4900 leader,
4901 DetTid::from_raw(31),
4902 ));
4903 assert!(!pidfd_getfd_targets_calling_task(
4904 Some(leader),
4905 leader,
4906 DetTid::from_raw(32),
4907 ));
4908 assert!(!pidfd_getfd_targets_calling_task(
4909 Some(DetPid::from_raw(32)),
4910 leader,
4911 DetTid::from_raw(31),
4912 ));
4913 }
4914
4915 #[test]
4916 fn captured_fd_installs_when_the_descriptor_table_is_unchanged() {
4917 let owner = DetTid::from_raw(32);
4918 let mut metadata = FileMetadata::new(owner);
4919 metadata
4920 .add_fd(owner, 3, OFlag::empty(), FdType::Regular, None)
4921 .expect("source should be inserted");
4922 let source_id = metadata
4923 .with_detfd(3, |fd| fd.open_file_id())
4924 .expect("source should exist");
4925 let captured = metadata.capture_fd(3).expect("source should be captured");
4926
4927 assert_eq!(
4928 metadata
4929 .install_captured_fd(captured, 4, OFlag::O_CLOEXEC)
4930 .expect("an unchanged table should accept the captured alias"),
4931 None
4932 );
4933 assert_eq!(
4934 metadata
4935 .with_detfd(4, |fd| (fd.open_file_id(), fd.is_cloexec()))
4936 .expect("captured alias should be installed"),
4937 (source_id, true)
4938 );
4939 }
4940
4941 #[test]
4942 fn failed_captured_fd_install_preserves_cleanup_obligations() {
4943 let owner = DetTid::from_raw(36);
4944 let mut original = FileMetadata::new(owner);
4945 original
4946 .add_fd(owner, 3, OFlag::empty(), FdType::Socket, None)
4947 .expect("source should be inserted");
4948 let source_id = original
4949 .with_detfd(3, |fd| fd.open_file_id())
4950 .expect("source should exist");
4951 let captured = original.capture_fd(3).expect("source should be captured");
4952 assert_eq!(
4953 original.remove_fd(3),
4954 None,
4955 "the capture defers final-OFD cleanup while the syscall is in flight"
4956 );
4957
4958 let mut replacement_table = FileMetadata::new(DetTid::from_raw(37));
4959 let failure = replacement_table
4960 .install_captured_fd(captured, 41, OFlag::O_CLOEXEC)
4961 .expect_err("a capture must not cross descriptor-table identity");
4962 assert_ne!(failure.expected_files_id, failure.actual_files_id);
4963 let cleanup = failure.into_cleanup();
4964 assert_eq!(cleanup.close_fd, 41);
4965 assert_eq!(cleanup.release_open_file, Some(source_id));
4966 }
4967
4968 #[test]
4969 fn equal_fd_dup_preserves_descriptor_flags() {
4970 let owner = DetTid::from_raw(20);
4971 let mut metadata = FileMetadata::new(owner);
4972 metadata
4973 .add_fd(owner, 3, OFlag::O_CLOEXEC, FdType::Regular, None)
4974 .expect("fd should be inserted");
4975
4976 assert_eq!(
4977 metadata
4978 .dup_fd(3, 3, OFlag::empty())
4979 .expect("equal-fd dup should validate the source"),
4980 None
4981 );
4982 assert!(
4983 metadata
4984 .with_detfd(3, |fd| fd.is_cloexec())
4985 .expect("fd should remain present"),
4986 "dup2(fd, fd) must not clear close-on-exec"
4987 );
4988 }
4989
4990 #[test]
4991 fn last_open_file_alias_survives_dup_and_fork() {
4992 let parent_tid = DetTid::from_raw(30);
4993 let child_tid = DetTid::from_raw(31);
4994 let mut parent = FileMetadata::new(parent_tid);
4995 parent
4996 .add_fd(parent_tid, 3, OFlag::empty(), FdType::Socket, None)
4997 .expect("socket should be inserted");
4998 let open_file_id = parent
4999 .with_detfd(3, |fd| fd.open_file_id())
5000 .expect("socket should exist");
5001 assert_eq!(
5002 parent
5003 .dup_fd(3, 4, OFlag::empty())
5004 .expect("dup should succeed"),
5005 None
5006 );
5007 assert_eq!(parent.remove_fd(3), None, "duplicate retains the OFD");
5008
5009 let mut child = parent.fork_for(child_tid);
5010 assert_eq!(parent.remove_fd(4), None, "forked child retains the OFD");
5011 assert_eq!(
5012 child.remove_fd(4),
5013 Some(open_file_id),
5014 "only the final alias releases the OFD"
5015 );
5016
5017 let mut replacement = FileMetadata::new(parent_tid);
5018 replacement
5019 .add_fd(parent_tid, 3, OFlag::empty(), FdType::Socket, None)
5020 .expect("source should be inserted");
5021 replacement
5022 .add_fd(parent_tid, 4, OFlag::empty(), FdType::Socket, None)
5023 .expect("target should be inserted");
5024 let target_id = replacement
5025 .with_detfd(4, |fd| fd.open_file_id())
5026 .expect("target should exist");
5027 assert_eq!(
5028 replacement
5029 .dup_fd(3, 4, OFlag::empty())
5030 .expect("dup replacement should succeed"),
5031 Some(target_id),
5032 "replacing the target must release its last OFD alias"
5033 );
5034 }
5035
5036 #[test]
5037 fn close_range_removes_selected_slots_and_releases_final_aliases() {
5038 let owner = DetTid::from_raw(35);
5039 let mut metadata = FileMetadata::new(owner);
5040 metadata
5041 .add_fd(owner, 3, OFlag::empty(), FdType::Regular, None)
5042 .expect("source should be inserted");
5043 metadata
5044 .dup_fd(3, 4, OFlag::empty())
5045 .expect("duplicate should be inserted");
5046 metadata
5047 .add_fd(owner, 100, OFlag::empty(), FdType::Regular, None)
5048 .expect("high fd should be inserted");
5049 let high_id = metadata
5050 .with_detfd(100, |fd| fd.open_file_id())
5051 .expect("high fd should exist");
5052
5053 assert_eq!(metadata.remove_fd_range(4, 100), [high_id]);
5054 assert!(metadata.with_detfd(3, |_| ()).is_ok());
5055 assert_eq!(metadata.with_detfd(4, |_| ()), Err(Errno::EBADF));
5056 assert_eq!(metadata.with_detfd(100, |_| ()), Err(Errno::EBADF));
5057 }
5058
5059 #[test]
5060 fn exec_reports_only_cloexec_open_files_with_no_other_aliases() {
5061 let owner = DetTid::from_raw(40);
5062 let child_tid = DetTid::from_raw(41);
5063 let mut metadata = FileMetadata::new(owner);
5064 metadata
5065 .add_fd(owner, 3, OFlag::O_CLOEXEC, FdType::Socket, None)
5066 .expect("socket should be inserted");
5067 let open_file_id = metadata
5068 .with_detfd(3, |fd| fd.open_file_id())
5069 .expect("socket should exist");
5070
5071 assert_eq!(metadata.open_files_closed_on_exec(false), [open_file_id]);
5072 assert!(
5073 metadata.open_files_closed_on_exec(true).is_empty(),
5074 "a shared descriptor table retains the original slot"
5075 );
5076
5077 let child = metadata.fork_for(child_tid);
5078 assert!(
5079 metadata.open_files_closed_on_exec(false).is_empty(),
5080 "a copied table retains an OFD alias"
5081 );
5082 drop(child);
5083
5084 metadata
5085 .dup_fd(3, 4, OFlag::empty())
5086 .expect("non-CLOEXEC alias should be created");
5087 assert!(
5088 metadata.open_files_closed_on_exec(false).is_empty(),
5089 "a non-CLOEXEC alias keeps the OFD live across exec"
5090 );
5091 }
5092
5093 fn rendered_record_file(spelling: &str) -> crate::procfs::ProcfsFile {
5096 let mut file = crate::procfs::ProcfsFile::from_path(Path::new(spelling)).unwrap();
5097 file.bind_task(3, 5);
5098 file.set_task_incarnation(crate::procfs::TaskIncarnation {
5099 process: 1,
5100 thread: None,
5101 });
5102 file.into_rendered()
5103 }
5104
5105 #[test]
5119 fn record_sequence_exec_survivors_keep_identity_sequence_and_numbering() {
5120 if isolated_child(
5121 "tool_local::file_metadata_tests::record_sequence_exec_survivors_keep_identity_sequence_and_numbering",
5122 ) {
5123 return;
5124 }
5125 use crate::procfs::ProcfsFile;
5126 use crate::procfs::ProcfsSnapshotContext;
5127
5128 let owner = DetTid::from_raw(9031);
5129 let config = Config::default();
5130 let mut parent = ThreadState::new(owner, &config, ());
5131 let statm = std::fs::File::open("/proc/self/statm").unwrap();
5132 let alias = statm.try_clone().unwrap();
5133 let closed = statm.try_clone().unwrap();
5134 let received = std::fs::File::open("/proc/self/statm").unwrap();
5135 let (fd, alias_fd, closed_fd, received_fd) = (
5136 statm.as_raw_fd(),
5137 alias.as_raw_fd(),
5138 closed.as_raw_fd(),
5139 received.as_raw_fd(),
5140 );
5141 for number in [fd, received_fd] {
5142 parent
5143 .add_fd(number, OFlag::O_RDONLY, FdType::Regular, None)
5144 .unwrap();
5145 parent
5146 .with_detfd(number, |handle| {
5147 handle.set_path("/proc/self/statm");
5148 handle
5149 .set_procfs(ProcfsFile::from_path(Path::new("/proc/self/statm")).unwrap());
5150 })
5151 .unwrap();
5152 }
5153 parent.dup_fd(fd, alias_fd, OFlag::empty()).unwrap();
5154 parent.dup_fd(fd, closed_fd, OFlag::O_CLOEXEC).unwrap();
5155 let record = b"1 2 3 4 5 6 7\n".to_vec();
5156 parent
5157 .with_detfd(fd, |handle| {
5158 handle.initialize_procfs(record.clone(), ProcfsSnapshotContext::default());
5159 handle.commit_procfs_read(0, 4);
5160 handle.procfs_note_read_end(4);
5161 })
5162 .unwrap();
5163 parent
5164 .with_detfd(received_fd, |handle| handle.mark_record_sequence_unknown())
5165 .unwrap();
5166 parent.share_procfs_sequences();
5168 let old_id = parent.with_detfd(fd, |h| h.open_file_id()).unwrap();
5169 let sequence = parent
5170 .with_detfd(fd, |h| h.procfs_sequence())
5171 .unwrap()
5172 .unwrap();
5173 let records = parent.procfs_records_for_exec().unwrap().unwrap();
5174 assert!(!records.handles.contains_key(&closed_fd));
5175 assert_eq!(
5176 records.handles[&fd].identity.id,
5177 records.handles[&alias_fd].identity.id
5178 );
5179 assert!(records.handles[&received_fd].sequence_unknown);
5180 let next_sequence = records.next_sequence;
5181 let state = ExecFdState {
5182 procfs_records: Some(records),
5183 ..ExecFdState::default()
5184 };
5185 assert!(!state.is_empty());
5186 assert_eq!(state.open_file_creator(), Some(owner));
5187 let state: ExecFdState =
5189 serde_json::from_slice(&serde_json::to_vec(&state).unwrap()).unwrap();
5190 let receiving = DetTid::from_raw(9032);
5191 let mut successor_thread = ThreadState::new(receiving, &config, ());
5192 successor_thread.set_open_file_creator(state.open_file_creator().unwrap());
5194 let mut successor = successor_thread.metadata();
5195 successor
5196 .apply_exec_fd_state(receiving, state.clone(), true)
5197 .unwrap();
5198 assert!(!successor.file_handles.contains_key(&closed_fd));
5199 for number in [fd, alias_fd] {
5200 let handle = &successor.file_handles[&number];
5201 assert_eq!(handle.open_file_id(), old_id);
5202 assert_eq!(handle.procfs_shared_sequence(), Some(old_id));
5203 assert_eq!(handle.procfs_sequence(), Some(sequence.clone()));
5204 assert!(!handle.record_sequence_unknown());
5205 }
5206 assert!(successor.file_handles[&received_fd].record_sequence_unknown());
5207 successor.file_handles[&alias_fd].commit_procfs_read(4, 2);
5209 assert_eq!(successor.file_handles[&fd].procfs_position().unwrap().0, 6);
5210 assert!(successor.next_open_file_sequence >= next_sequence);
5211 assert!(successor.has_shared_procfs_sequences);
5212 drop(successor);
5213 let next = tempfile::tempfile().unwrap();
5214 successor_thread
5215 .add_fd(next.as_raw_fd(), OFlag::O_RDWR, FdType::Regular, None)
5216 .unwrap();
5217 assert_eq!(
5218 successor_thread
5219 .with_detfd(next.as_raw_fd(), |h| h.open_file_id())
5220 .unwrap(),
5221 OpenFileId::new(owner, next_sequence),
5222 );
5223 let lone = ThreadState::new(owner, &config, ());
5226 let cloexec = std::fs::File::open("/proc/self/statm").unwrap();
5227 lone.add_fd(cloexec.as_raw_fd(), OFlag::O_CLOEXEC, FdType::Regular, None)
5228 .unwrap();
5229 lone.with_detfd(cloexec.as_raw_fd(), |handle| {
5230 handle.set_procfs(ProcfsFile::from_path(Path::new("/proc/self/statm")).unwrap());
5231 })
5232 .unwrap();
5233 assert!(lone.procfs_records_for_exec().unwrap().is_none());
5234 lone.share_procfs_sequences();
5235 let numbering = lone.procfs_records_for_exec().unwrap().unwrap();
5236 assert!(numbering.handles.is_empty());
5237 assert_eq!(
5238 numbering.next_sequence,
5239 lone.metadata().next_open_file_sequence
5240 );
5241 assert!(numbering.next_sequence > 0);
5242 let replacement = tempfile::tempfile().unwrap();
5244 assert_eq!(
5245 unsafe { libc::dup2(replacement.as_raw_fd(), alias_fd) },
5246 alias_fd
5247 );
5248 assert_eq!(
5249 FileMetadata::new(owner).apply_exec_fd_state(owner, state, true),
5250 Err(Errno::EIO)
5251 );
5252 }
5253
5254 #[test]
5260 fn a_kernel_record_file_link_names_only_a_sequence_file() {
5261 let mut stat: libc::stat = unsafe { std::mem::zeroed() };
5262 stat.st_mode = libc::S_IFREG | 0o444;
5263 stat.st_nlink = 1;
5264 for name in ["stat", "statm", "io", "arch_status", "cgroup"] {
5265 let link = format!("/proc/12/{name}");
5266 assert_eq!(
5267 kernel_record_file_link(&stat, link.as_bytes()),
5268 Some(std::path::PathBuf::from(&link)),
5269 "{name}"
5270 );
5271 }
5272 assert_eq!(
5273 kernel_record_file_link(&stat, b"/proc/12/task/13/statm"),
5274 Some(std::path::PathBuf::from("/proc/12/task/13/statm"))
5275 );
5276 for link in [
5277 "/proc/12/comm",
5278 "/proc/12/status",
5279 "/proc/12/maps",
5280 "/proc/uptime",
5281 "/tmp/statm",
5282 ] {
5283 assert_eq!(
5284 kernel_record_file_link(&stat, link.as_bytes()),
5285 None,
5286 "{link}"
5287 );
5288 }
5289 let mut placeholder = stat;
5290 placeholder.st_nlink = 0;
5291 assert_eq!(
5292 kernel_record_file_link(&placeholder, b"/proc/12/statm"),
5293 None
5294 );
5295 let mut sized = stat;
5296 sized.st_size = 1;
5297 assert_eq!(kernel_record_file_link(&sized, b"/proc/12/statm"), None);
5298 let mut directory = stat;
5299 directory.st_mode = libc::S_IFDIR | 0o555;
5300 assert_eq!(kernel_record_file_link(&directory, b"/proc/12/statm"), None);
5301 }
5302
5303 #[test]
5311 fn a_sent_rendered_record_file_shares_its_sequence() {
5312 let owner = DetTid::from_raw(9033);
5313 let config = Config::default();
5314 let state = ThreadState::new(owner, &config, ());
5315 state.metadata().file_handles.clear();
5316 for (fd, spelling, rendered) in [
5317 (40, "/proc/7/statm", true),
5318 (41, "/proc/7/statm", false),
5319 (42, "/proc/7/statm", true),
5320 (43, "/proc/7/auxv", true),
5321 ] {
5322 state
5323 .add_fd(fd, OFlag::O_RDONLY, FdType::Regular, None)
5324 .unwrap();
5325 let procfs = if rendered {
5326 rendered_record_file(spelling)
5327 } else {
5328 crate::procfs::ProcfsFile::from_path(Path::new(spelling)).unwrap()
5329 };
5330 state
5331 .with_detfd(fd, |handle| handle.set_procfs(procfs.clone()))
5332 .unwrap();
5333 }
5334 state
5335 .with_detfd(42, |handle| handle.mark_record_sequence_unknown())
5336 .unwrap();
5337 let id = state
5338 .with_detfd(40, |handle| handle.open_file_id())
5339 .unwrap();
5340 assert!(!state.metadata().has_shared_procfs_sequences);
5341 assert_eq!(state.share_sent_procfs_sequence(40), Some(id));
5342 assert_eq!(
5343 state
5344 .with_detfd(40, |handle| handle.procfs_shared_sequence())
5345 .unwrap(),
5346 Some(id)
5347 );
5348 assert!(state.metadata().has_shared_procfs_sequences);
5349 assert_eq!(state.share_sent_procfs_sequence(40), Some(id));
5351 for fd in [41, 42, 43, 44] {
5352 assert_eq!(state.share_sent_procfs_sequence(fd), None, "{fd}");
5353 }
5354 assert_eq!(
5355 state
5356 .with_detfd(41, |handle| handle.procfs_shared_sequence())
5357 .unwrap(),
5358 None
5359 );
5360 assert_eq!(state.rendered_descriptors(), vec![40, 42, 43]);
5361 }
5362}
5363
5364#[derive(Debug, Serialize, Deserialize, Clone, Default)]
5367pub struct ThreadStats {
5368 pub syscall_count: u64,
5370
5371 pub syscall_histogram: SyscallHistogram,
5375
5376 pub syscall_refused: bool,
5380
5381 pub hb_occurrences: detcore_model::happens_before::HbThreadCounters,
5388
5389 pub hb_pending_posthook: Vec<String>,
5393
5394 pub regs_sample_index: u64,
5404
5405 pub signal_count: u64,
5407
5408 pub timeslice_syscall_count: u64,
5410
5411 pub timeslice_signal_count: u64,
5413
5414 pub timeslice_count: u64,
5417
5418 pub last_recorded_slice: Option<u64>,
5421
5422 pub timeslice_stats: TimesliceStats,
5426
5427 pub timeslice_start_ns: Option<LogicalTime>,
5431}
5432
5433impl ThreadStats {
5434 pub fn new() -> Self {
5436 Default::default()
5437 }
5438
5439 pub fn count_syscall(&mut self, sysno: Sysno) {
5441 self.syscall_count += 1;
5442 self.timeslice_syscall_count += 1;
5443 self.syscall_histogram.enter(sysno);
5444 self.syscall_refused = false;
5445 }
5446
5447 pub fn count_signal(&mut self) {
5449 self.signal_count += 1;
5450 self.timeslice_signal_count += 1;
5451 }
5452
5453 pub(crate) fn reset_timeslice(&mut self) {
5456 self.timeslice_syscall_count = 0;
5457 self.timeslice_signal_count = 0;
5458 self.timeslice_count += 1;
5459 }
5460
5461 pub fn close_final_timeslice(&mut self, now: LogicalTime) {
5466 if let Some(start) = self.timeslice_start_ns.take()
5467 && now >= start
5468 {
5469 self.timeslice_stats.record((now - start).as_nanos());
5470 }
5471 }
5472}
5473
5474#[derive(Debug, Clone, Serialize, Deserialize)]
5477pub struct PendingVfork {
5478 pub parent_dettid: DetTid,
5479 pub op: ExternalOpId,
5482 pub parent_detpid: DetPid,
5483 pub child_tid_addr: usize,
5484 pub flags: CloneFlags,
5485 pub exit_signal: libc::c_int,
5486 pub child_priority_entropy: Option<u64>,
5487 #[serde(default)]
5493 pub user_namespace_owner_may_differ: bool,
5494}
5495
5496#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
5498pub(crate) struct ProcessCpuSnapshot {
5499 pub user: LogicalTime,
5500 pub system: LogicalTime,
5501 pub children_user: LogicalTime,
5502 pub children_system: LogicalTime,
5503}
5504
5505#[derive(Debug)]
5508pub(crate) struct ChildCpuPublication(Mutex<ChildCpuPublicationState>);
5509
5510#[derive(Debug)]
5511enum ChildCpuPublicationState {
5512 Pending(Vec<std::sync::Weak<futures::task::AtomicWaker>>),
5513 Published,
5514 Invalidated,
5515}
5516
5517impl ChildCpuPublication {
5518 fn pending() -> Arc<Self> {
5519 Arc::new(Self(Mutex::new(ChildCpuPublicationState::Pending(
5520 Vec::new(),
5521 ))))
5522 }
5523
5524 fn finish(&self, published: bool) {
5525 let waiters = {
5526 let mut state = self.0.lock().expect("child CPU publication mutex poisoned");
5527 if matches!(*state, ChildCpuPublicationState::Invalidated)
5528 || published && matches!(*state, ChildCpuPublicationState::Published)
5529 {
5530 return;
5531 }
5532 let next = if published {
5533 ChildCpuPublicationState::Published
5534 } else {
5535 ChildCpuPublicationState::Invalidated
5536 };
5537 match std::mem::replace(&mut *state, next) {
5538 ChildCpuPublicationState::Pending(waiters) => waiters,
5539 _ => Vec::new(),
5540 }
5541 };
5542 for waiter in waiters.into_iter().filter_map(|waiter| waiter.upgrade()) {
5544 waiter.wake();
5545 }
5546 }
5547
5548 async fn wait(&self) -> bool {
5549 let waiter = Arc::new(futures::task::AtomicWaker::new());
5550 {
5551 let mut state = self.0.lock().expect("child CPU publication mutex poisoned");
5552 match &mut *state {
5553 ChildCpuPublicationState::Pending(waiters) => {
5554 waiters.retain(|waiter| waiter.strong_count() != 0);
5555 waiters.push(Arc::downgrade(&waiter));
5556 }
5557 ChildCpuPublicationState::Published => return true,
5558 ChildCpuPublicationState::Invalidated => return false,
5559 }
5560 }
5561 futures::future::poll_fn(|cx| {
5562 waiter.register(cx.waker());
5563 let state = self.0.lock().expect("child CPU publication mutex poisoned");
5564 match *state {
5565 ChildCpuPublicationState::Pending(_) => std::task::Poll::Pending,
5566 ChildCpuPublicationState::Published => std::task::Poll::Ready(true),
5567 ChildCpuPublicationState::Invalidated => std::task::Poll::Ready(false),
5568 }
5569 })
5570 .await
5571 }
5572}
5573
5574#[derive(Debug, Default, Clone, Serialize, Deserialize)]
5575pub(crate) struct ProcessCpuTime {
5576 snapshot: ProcessCpuSnapshot,
5577 exited_children: BTreeMap<DetPid, ProcessCpuSnapshot>,
5578 #[serde(default)]
5584 threads: BTreeMap<DetTid, (LogicalTime, LogicalTime)>,
5585 #[serde(skip)]
5588 child_cpu_publications: BTreeMap<DetPid, Arc<ChildCpuPublication>>,
5589}
5590
5591impl ProcessCpuTime {
5592 fn validate_native_exec_cut(&self) -> Result<(), crate::native_exec::NativeExecCutError> {
5593 if !self.child_cpu_publications.is_empty() {
5594 return Err(crate::native_exec::NativeExecCutError::LiveCapability(
5595 "child_cpu_publications",
5596 ));
5597 }
5598 Ok(())
5599 }
5600
5601 fn add_thread_delta(&mut self, user: LogicalTime, system: LogicalTime) {
5602 self.snapshot.user = self.snapshot.user + user;
5603 self.snapshot.system = self.snapshot.system + system;
5604 }
5605
5606 fn record_thread(&mut self, thread: DetTid, own: (LogicalTime, LogicalTime)) {
5609 self.threads.insert(thread, own);
5610 }
5611
5612 fn thread_time(&self, thread: DetTid) -> (LogicalTime, LogicalTime) {
5615 self.threads
5616 .get(&thread)
5617 .copied()
5618 .unwrap_or((LogicalTime::ZERO, LogicalTime::ZERO))
5619 }
5620
5621 fn forget_thread(&mut self, thread: DetTid) {
5624 self.threads.remove(&thread);
5625 }
5626
5627 fn record_exited_child(&mut self, pid: DetPid, child: ProcessCpuSnapshot) {
5628 self.exited_children
5629 .entry(pid)
5630 .and_modify(|previous| {
5631 previous.user = previous.user.max(child.user);
5632 previous.system = previous.system.max(child.system);
5633 previous.children_user = previous.children_user.max(child.children_user);
5634 previous.children_system = previous.children_system.max(child.children_system);
5635 })
5636 .or_insert(child);
5637 }
5638
5639 fn reap_child(&mut self, pid: DetPid) {
5640 let Some(child) = self.exited_children.remove(&pid) else {
5641 return;
5642 };
5643 self.snapshot.children_user =
5644 self.snapshot.children_user + child.user + child.children_user;
5645 self.snapshot.children_system =
5646 self.snapshot.children_system + child.system + child.children_system;
5647 }
5648
5649 fn prepare_child(&mut self, pid: DetPid) {
5650 self.exited_children.remove(&pid);
5651 }
5652}
5653
5654#[derive(Debug, Default, Serialize, Deserialize)]
5662pub(crate) struct GuestClock {
5663 now: LogicalTime,
5664}
5665
5666#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5669pub(crate) struct RobustListWake {
5670 pub(crate) futex: FutexID,
5671}
5672
5673#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5676pub(crate) enum RobustListExit {
5677 ExitGroup,
5678 Signal(i32),
5679}
5680
5681#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
5684pub(crate) struct PendingRobustListWakes {
5685 pub(crate) reason: RobustListExit,
5686 pub(crate) wakes: Vec<RobustListWake>,
5687}
5688
5689impl PendingRobustListWakes {
5690 fn matches_exit(&self, exit_signal: Option<i32>) -> bool {
5691 match self.reason {
5692 RobustListExit::ExitGroup => true,
5693 RobustListExit::Signal(expected) => exit_signal == Some(expected),
5694 }
5695 }
5696}
5697
5698pub(crate) const EXIT_GROUP_ROBUST_WALK_WAIT: std::time::Duration =
5701 std::time::Duration::from_secs(10);
5702
5703#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5705pub(crate) struct RobustListOwner {
5706 pub(crate) dettid: DetTid,
5707 pub(crate) head: usize,
5708}
5709
5710#[derive(Debug, Clone, Default, Serialize, Deserialize)]
5714pub(crate) struct RobustListProcessState {
5715 heads: BTreeMap<DetTid, usize>,
5716 #[serde(default)]
5719 tool_walked: BTreeSet<DetTid>,
5720 #[serde(default)]
5724 exit_group_filed: bool,
5725 #[serde(default)]
5728 exit_group_filers: BTreeSet<DetTid>,
5729 #[serde(default)]
5732 exit_group_walker: Option<DetTid>,
5733 #[serde(default)]
5736 exit_group_walked: bool,
5737 pending: BTreeMap<DetTid, PendingRobustListWakes>,
5738 matched_physical_exits: BTreeMap<DetTid, DetTime>,
5739 exec_pending: Vec<(DetTid, RobustListWake)>,
5741}
5742
5743impl GuestClock {
5744 fn observe(&mut self, raw: LogicalTime) -> LogicalTime {
5745 self.now = self.now.max(raw);
5746 self.now
5747 }
5748}
5749
5750pub(crate) const DEFAULT_TIMER_SLACK_NS: u64 = 50_000;
5756
5757fn default_timer_slack_ns() -> u64 {
5758 DEFAULT_TIMER_SLACK_NS
5759}
5760
5761pub(crate) const MAX_SIGRETURN_CONTINUATIONS: u8 = 32;
5773
5774#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
5777pub(crate) struct InboundStop {
5778 pub(crate) reported: bool,
5780 pub(crate) grants_at_stop: u64,
5782}
5783
5784#[derive(Clone, Debug, Serialize, Deserialize)]
5787pub(crate) struct InboundTurn {
5788 pub(crate) dettid: DetTid,
5790 pub(crate) mycount: u64,
5792 #[serde(default)]
5794 pub(crate) boundary_token: Option<crate::preemptions::callback_history::Token>,
5795}
5796
5797#[derive(Clone, Debug, Serialize, Deserialize)]
5800pub(crate) struct SignalAnnouncementState {
5801 pub(crate) token: u64,
5803 pub(crate) signal: i32,
5805 pub(crate) turn: InboundTurn,
5807}
5808
5809#[derive(Serialize, Deserialize, Clone)]
5811pub struct ThreadState<T> {
5812 pub dettid: DetTid,
5814 pub detpid: Option<DetTid>,
5816
5817 pub(crate) thread_start_entered: bool,
5820
5821 pub(crate) vfork_origin: Option<ExternalOpId>,
5827
5828 #[serde(default)]
5832 pub physical_tid: Option<i32>,
5833
5834 #[serde(default)]
5836 pub(crate) signal_task_identity: Option<reverie::SignalTaskIdentity>,
5837
5838 #[serde(default)]
5842 pub(crate) open_file_creator: Option<DetTid>,
5843
5844 #[serde(skip)]
5851 pub(crate) receive_geometry: Option<Option<crate::syscalls::scm_rights::ReceiveGeometry>>,
5852
5853 #[serde(skip)]
5861 pub(crate) entry_import: Option<Box<crate::syscalls::entry_import::EntryImport>>,
5862
5863 #[serde(skip)]
5872 pub(crate) send_inspection: Option<crate::syscalls::SendInspection>,
5873 #[serde(skip)]
5881 pub(crate) inotify_queue: Option<crate::syscalls::inotify::QueueCount>,
5882 #[serde(skip)]
5889 pub(crate) call_parts: Option<crate::syscalls::helpers::CallParts>,
5890 #[serde(skip)]
5894 pub(crate) traced_call: crate::access_trace::TracedCall,
5895
5896 pub mm_id: MmId,
5898
5899 #[serde(skip)]
5902 pub(crate) published_runtime_private_memory: Option<(MmId, reverie::RuntimePrivateMemory)>,
5903
5904 pub(crate) memory_metadata: Arc<Mutex<MemoryMetadata>>,
5906
5907 pub pedigree: Pedigree,
5910
5911 pub stats: ThreadStats,
5913
5914 pub preemption_points: Option<ThreadHistoryIterator>,
5916
5917 pub interrupt_at: BTreeSet<u64>,
5919
5920 pub clone_flags: Option<CloneFlags>,
5928
5929 pub pending_vfork: Option<PendingVfork>,
5933
5934 pub(crate) restarted_clone_id: Option<crate::syscalls::chosen_ids::RestartedCloneId>,
5939
5940 #[serde(skip)]
5946 pub(crate) procfs_sequence_taken: Option<OpenFileId>,
5947
5948 pub file_metadata: Arc<Mutex<FileMetadata>>,
5951
5952 #[serde(default)]
5958 pub(crate) tool_shares_guest_descriptor_table: bool,
5959
5960 #[serde(default = "default_timer_slack_ns")]
5967 pub(crate) timer_slack_ns: u64,
5968 #[serde(default = "default_timer_slack_ns")]
5971 pub(crate) default_timer_slack_ns: u64,
5972
5973 #[serde(default)]
5978 pub(crate) nested_pid_namespace: bool,
5979
5980 #[serde(default)]
5986 pub(crate) changed_root: Arc<std::sync::atomic::AtomicBool>,
5987
5988 #[serde(default)]
5997 pub(crate) fs_apart_from_leader: bool,
5998
5999 #[serde(default)]
6006 pub(crate) exe: Option<Arc<crate::exec_record::ExecRecord>>,
6007
6008 #[serde(default)]
6020 pub(crate) process_files: Option<FilesId>,
6021
6022 pub(crate) posix_timers: Arc<Mutex<PosixTimers>>,
6025
6026 pub(crate) resource_limits: Arc<Mutex<ResourceLimits>>,
6028
6029 pub(crate) process_cpu_time: Arc<Mutex<ProcessCpuTime>>,
6031
6032 #[serde(default)]
6034 pub(crate) guest_clock: Arc<Mutex<GuestClock>>,
6035
6036 #[serde(skip)]
6042 pub(crate) unix_connections: Arc<Mutex<crate::syscalls::scm_rights::UnixConnections>>,
6043
6044 pub(crate) parent_process_cpu_time: Option<Arc<Mutex<ProcessCpuTime>>>,
6046
6047 #[serde(skip)]
6049 pub(crate) parent_cpu_publication: Option<Arc<ChildCpuPublication>>,
6050
6051 #[serde(skip)]
6056 pub(crate) restart_block: Option<crate::syscalls::helpers::RestartBlock>,
6057
6058 #[serde(skip)]
6070 pub(crate) syscall_cwd: Option<crate::syscalls::SyscallCwd>,
6071
6072 pub(crate) sigalrm_handled: bool,
6078
6079 #[serde(default)]
6085 pub(crate) signal_shadow: Option<crate::signal_state::SignalShadow>,
6086
6087 #[serde(default)]
6090 pub(crate) signal_shadow_lost: bool,
6091
6092 #[serde(skip)]
6099 pub(crate) delivery_interruption: Option<reverie::Interruption>,
6100
6101 #[serde(skip)]
6108 pub(crate) wait_signal_end: Option<crate::wait_signal_end::WaitSignalEnd>,
6109
6110 #[serde(skip)]
6114 pub(crate) delivery_frame_handler: u64,
6115
6116 #[serde(skip)]
6120 pub(crate) delivery_frame_signal: i32,
6121
6122 #[serde(skip)]
6126 pub(crate) delivery_frame_orig_ax: i64,
6127
6128 #[serde(skip)]
6131 pub(crate) delivery_counters: crate::syscalls::signal_shadow::DeliveryCounters,
6132
6133 #[serde(default)]
6138 pub(crate) published_dispositions:
6139 Arc<Mutex<crate::syscalls::signal_shadow::PublishedDispositions>>,
6140
6141 #[serde(default)]
6146 pub(crate) published_mask: Option<Option<u64>>,
6147
6148 pub(crate) sigalrm_blocked_published: bool,
6153
6154 pub(crate) handler_restore_rt_reported: u64,
6159
6160 #[serde(skip)]
6166 pub(crate) signal_register_projection: Option<libc::user_regs_struct>,
6167
6168 pub(crate) last_accounted_user_time: LogicalTime,
6170 pub(crate) last_accounted_system_time: LogicalTime,
6171
6172 pub(crate) thread_cpu_start_user_time: LogicalTime,
6176 pub(crate) thread_cpu_start_system_time: LogicalTime,
6177
6178 pub prng: Pcg64Mcg,
6180
6181 #[serde(default)]
6185 pub(crate) initialized_random_auxv: Option<(
6189 crate::random::InitialImage,
6190 [u8; 16],
6191 Vec<crate::random::EarlyRequest>,
6192 )>,
6193
6194 pub chaos_prng: Pcg64Mcg,
6196
6197 pub thread_logical_time: DetTime,
6199
6200 pub committed_clock_value: u64,
6202
6203 #[serde(default)]
6207 pub(crate) uncharged_bootstrap_syscalls: u32,
6208
6209 #[serde(default)]
6220 pub(crate) in_uncharged_bootstrap_syscall: bool,
6221
6222 #[serde(default)]
6227 pub(crate) passed_descriptors_out_unreported: bool,
6228
6229 #[serde(default)]
6234 pub(crate) passed_descriptors_out_reported: bool,
6235
6236 #[serde(default)]
6244 pub(crate) nonblocking_result_normalized: bool,
6245
6246 #[serde(default)]
6259 pub(crate) yield_after_futex_wake: bool,
6260 #[serde(default)]
6267 pub(crate) pipe_handoff_due: Option<LogicalTime>,
6268 #[serde(default)]
6273 pub(crate) own_signal_turn_hold: Option<libc::c_int>,
6274 #[serde(default)]
6322 pub(crate) delivered_since_request: bool,
6323 #[serde(default)]
6338 pub(crate) returned_from_sigreturn: bool,
6339 #[serde(default)]
6344 pub(crate) signal_announcements: Vec<SignalAnnouncementState>,
6345 #[serde(default)]
6347 pub(crate) next_signal_announcement: u64,
6348 #[serde(default)]
6357 pub(crate) consecutive_continuations: u8,
6358 #[serde(default)]
6362 pub(crate) scheduler_grants: u64,
6363
6364 pub record_or_replay: T,
6366
6367 pub end_of_timeslice: Option<LogicalTime>,
6376
6377 #[serde(default)]
6382 pub replay_rcb_end: Option<u64>,
6383
6384 #[serde(skip)]
6389 pub last_replay_change_point: Option<LogicalTime>,
6390
6391 #[serde(default = "chaos_epoch_sentinel")]
6398 pub chaos_epoch: u64,
6399
6400 #[serde(default)]
6404 pub chaos_slowdown_factor: RcbTimeMultiplier,
6405
6406 #[serde(default)]
6410 pub chaos_slowdown_active: bool,
6411
6412 #[serde(default)]
6420 pub(crate) claimed_sigchld: bool,
6421
6422 #[serde(default)]
6426 pub pending_chaos_epochs: Vec<ChaosEpochTransition>,
6427
6428 pub max_timeslice_end: Option<LogicalTime>,
6431
6432 pub last_grant_deadline: Option<Duration>,
6437
6438 pub last_rcb_timer: Option<u64>,
6442
6443 #[serde(default)]
6445 pub last_rcb_timer_is_max: bool,
6446
6447 #[serde(default)]
6452 pub lone_grant: bool,
6453
6454 #[serde(default)]
6460 pub lone_slice_end: Option<u64>,
6461
6462 #[serde(default)]
6465 pub replaying_lone_slice_end: Option<u64>,
6466
6467 #[serde(default)]
6471 pub lone_replay_unsound: bool,
6472
6473 pub(crate) past_global_first_execve: bool,
6476
6477 #[serde(default)]
6486 pub(crate) robust_list_head: Option<usize>,
6487
6488 #[serde(default)]
6491 pub(crate) robust_list_process: Arc<Mutex<RobustListProcessState>>,
6492
6493 #[serde(default)]
6496 pub(crate) slice_boundary: Option<crate::slice_boundary::Observations>,
6497 #[serde(default)]
6498 pub(crate) callback_history: crate::preemptions::callback_history::State,
6499
6500 #[serde(default)]
6507 pub(crate) schedule_replay_over: bool,
6508}
6509
6510impl<T> std::fmt::Debug for ThreadState<T> {
6513 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6514 f.debug_struct("ThreadState")
6515 .field("dettid", &self.dettid)
6516 .field("detpid", &self.detpid)
6517 .field("physical_tid", &self.physical_tid)
6518 .field("mm_id", &self.mm_id)
6519 .field("memory_metadata", &self.memory_metadata)
6520 .field("stats", &self.stats)
6521 .field("clone_flags", &self.clone_flags)
6522 .field("file_metadata", &self.file_metadata)
6523 .field("process_files", &self.process_files)
6524 .field("posix_timers", &self.posix_timers)
6525 .field("resource_limits", &self.resource_limits)
6526 .field("process_cpu_time", &self.process_cpu_time)
6527 .field("prng", &self.prng)
6528 .field("chaos_prng", &self.chaos_prng)
6529 .field("thread_logical_time", &self.thread_logical_time)
6530 .field("committed_clock_value", &self.committed_clock_value)
6531 .field("end_of_timeslice", &self.end_of_timeslice)
6532 .field("replay_rcb_end", &self.replay_rcb_end)
6533 .field("chaos_epoch", &self.chaos_epoch)
6534 .field("chaos_slowdown_factor", &self.chaos_slowdown_factor)
6535 .field("chaos_slowdown_active", &self.chaos_slowdown_active)
6536 .field("max_timeslice_end", &self.max_timeslice_end)
6537 .field("last_rcb_timer", &self.last_rcb_timer)
6538 .field("last_rcb_timer_is_max", &self.last_rcb_timer_is_max)
6539 .finish()
6540 }
6541}
6542
6543impl<T> Default for ThreadState<T> {
6544 fn default() -> Self {
6545 unreachable!()
6546 }
6547}
6548
6549pub(crate) fn chaos_epoch_sentinel() -> u64 {
6555 u64::MAX
6556}
6557
6558impl<T> AsRef<T> for ThreadState<T> {
6559 fn as_ref(&self) -> &T {
6560 &self.record_or_replay
6561 }
6562}
6563
6564impl<T> AsMut<T> for ThreadState<T> {
6565 fn as_mut(&mut self) -> &mut T {
6566 &mut self.record_or_replay
6567 }
6568}
6569
6570pub(crate) fn chaos_per_thread_slowdown_factor(
6599 sched_seed: u64,
6600 dettid: DetTid,
6601 epoch: u64,
6602 max_factor: f64,
6603) -> RcbTimeMultiplier {
6604 if max_factor <= 1.0 {
6607 return RcbTimeMultiplier::ONE;
6608 }
6609 const SLOWDOWN_SALT: u64 = 0x736c_6f77_646f_776e; const EPOCH_GOLDEN: u64 = 0xbf58_476d_1ce4_e5b9;
6619 let mixed = sched_seed
6620 ^ SLOWDOWN_SALT
6621 ^ ((dettid.as_raw() as u32 as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15))
6622 ^ epoch.wrapping_mul(EPOCH_GOLDEN);
6623 let mut prng = Pcg64Mcg::seed_from_u64(mixed);
6624 let u: f64 = prng.random::<f64>();
6627 let exponent = 2.0 * u - 1.0;
6628 RcbTimeMultiplier::from_f64(max_factor.powf(exponent))
6629}
6630
6631impl<T> ThreadState<T> {
6632 pub(crate) fn note_scheduler_handoff(&mut self, continues_sigreturn: bool) {
6647 self.scheduler_grants = self.scheduler_grants.wrapping_add(1);
6648 self.delivered_since_request = false;
6649 self.consecutive_continuations = if continues_sigreturn {
6650 self.consecutive_continuations.saturating_add(1)
6651 } else {
6652 0
6653 };
6654 }
6655
6656 pub(crate) fn peek_guest_clock(&self, raw: LogicalTime) -> LogicalTime {
6659 let clock = self.guest_clock.lock().expect("guest clock mutex poisoned");
6660 clock.now.max(raw)
6661 }
6662
6663 pub(crate) fn validate_native_exec_cut(
6667 &self,
6668 binding: &crate::native_exec::NativeExecCutBinding,
6669 config: &Config,
6670 ) -> Result<(), crate::native_exec::NativeExecCutError> {
6671 use crate::native_exec::NativeExecCutError;
6672 use crate::native_exec::try_native_exec_lock;
6673
6674 if self.dettid != binding.dettid
6675 || self.detpid != Some(binding.detpid)
6676 || self.physical_tid != Some(binding.physical_tid)
6677 {
6678 return Err(NativeExecCutError::Identity("ThreadState IDs"));
6679 }
6680 if !self.thread_start_entered || !self.past_global_first_execve {
6681 return Err(NativeExecCutError::PreparedState("initial lifecycle"));
6682 }
6683 if self.tool_shares_guest_descriptor_table
6684 != config.backend.tool_shares_guest_descriptor_table
6685 {
6686 return Err(NativeExecCutError::Identity("descriptor-table capability"));
6687 }
6688 if self.nested_pid_namespace {
6689 return Err(NativeExecCutError::LiveCapability("nested PID namespace"));
6690 }
6691 if self.signal_task_identity.is_some() {
6692 return Err(NativeExecCutError::LiveCapability("signal task identity"));
6693 }
6694 if self.parent_process_cpu_time.is_some() || self.parent_cpu_publication.is_some() {
6695 return Err(NativeExecCutError::LiveCapability("parent CPU ownership"));
6696 }
6697 if self.restart_block.is_some() || self.signal_register_projection.is_some() {
6698 return Err(NativeExecCutError::LiveCapability("restart/projection"));
6699 }
6700 if self.claimed_sigchld {
6701 return Err(NativeExecCutError::LiveCapability("claimed SIGCHLD"));
6702 }
6703 if !self.signal_announcements.is_empty() {
6704 return Err(NativeExecCutError::LiveCapability(
6705 "unsettled signal announcement",
6706 ));
6707 }
6708 if self.clone_flags.is_some() || self.pending_vfork.is_some() {
6709 return Err(NativeExecCutError::LiveCapability("clone/vfork"));
6710 }
6711 if self.own_signal_turn_hold.is_some()
6712 || self.sigalrm_handled
6713 || self.handler_restore_rt_reported != 0
6714 {
6715 return Err(NativeExecCutError::LiveCapability("guest signal handler"));
6716 }
6717 if self.initialized_random_auxv.is_some() {
6718 return Err(NativeExecCutError::LiveCapability("initial random handoff"));
6719 }
6720 if self.uncharged_bootstrap_syscalls != 0
6721 || self.in_uncharged_bootstrap_syscall
6722 || self.yield_after_futex_wake
6723 {
6724 return Err(NativeExecCutError::LiveCapability(
6725 "bootstrap/yield transient",
6726 ));
6727 }
6728 if self.preemption_points.is_some()
6729 || self.slice_boundary.is_some()
6730 || !self.interrupt_at.is_empty()
6731 || self.replay_rcb_end.is_some()
6732 || self.last_replay_change_point.is_some()
6733 || self.last_rcb_timer.is_some()
6734 || self.last_rcb_timer_is_max
6735 || self.max_timeslice_end.is_some()
6736 || self.chaos_slowdown_active
6737 || !self.pending_chaos_epochs.is_empty()
6738 {
6739 return Err(NativeExecCutError::LiveCapability("RCB/replay/chaos state"));
6740 }
6741 let user = self.thread_logical_time.user_cpu_time();
6742 let system = self.thread_logical_time.system_cpu_time();
6743 if self.thread_cpu_start_user_time > self.last_accounted_user_time
6744 || self.last_accounted_user_time > user
6745 || self.thread_cpu_start_system_time > self.last_accounted_system_time
6746 || self.last_accounted_system_time > system
6747 {
6748 return Err(NativeExecCutError::PreparedState(
6749 "CPU accounting checkpoints",
6750 ));
6751 }
6752 try_native_exec_lock(&self.process_cpu_time, "process_cpu_time")?
6753 .validate_native_exec_cut()?;
6754 drop(try_native_exec_lock(&self.guest_clock, "guest_clock")?);
6757 drop(try_native_exec_lock(
6758 &self.resource_limits,
6759 "resource_limits",
6760 )?);
6761 drop(try_native_exec_lock(&self.posix_timers, "posix_timers")?);
6762 Ok(())
6763 }
6764
6765 pub(crate) fn validate_old_native_exec_robust_state(
6766 &self,
6767 ) -> Result<(), crate::native_exec::NativeExecCutError> {
6768 use crate::native_exec::NativeExecCutError;
6769 let process = crate::native_exec::try_native_exec_lock(
6770 &self.robust_list_process,
6771 "robust_list_process",
6772 )?;
6773 if process.heads.len() != usize::from(self.robust_list_head.is_some())
6774 || process.heads.get(&self.dettid).copied() != self.robust_list_head
6775 || !process.pending.is_empty()
6776 || !process.matched_physical_exits.is_empty()
6777 || !process.exec_pending.is_empty()
6778 || !process.tool_walked.is_empty()
6779 || process.exit_group_filed
6780 || !process.exit_group_filers.is_empty()
6781 || process.exit_group_walker.is_some()
6782 || process.exit_group_walked
6783 {
6784 return Err(NativeExecCutError::LiveCapability(
6785 "old robust process state",
6786 ));
6787 }
6788 Ok(())
6789 }
6790
6791 pub(crate) fn validate_prepared_native_exec_cut(
6792 &self,
6793 binding: &crate::native_exec::NativeExecCutBinding,
6794 ) -> Result<(), crate::native_exec::NativeExecCutError> {
6795 use crate::native_exec::NativeExecCutError;
6796 use crate::native_exec::try_native_exec_lock;
6797
6798 if self.mm_id != binding.prepared_mm() {
6799 return Err(NativeExecCutError::Identity("prepared mm"));
6800 }
6801 try_native_exec_lock(&self.file_metadata, "prepared file_metadata")?
6802 .validate_native_exec_cut(binding.prepared_files(), true)?;
6803 if !try_native_exec_lock(&self.memory_metadata, "memory_metadata")?
6804 .is_fresh_for_native_exec()
6805 {
6806 return Err(NativeExecCutError::PreparedState("memory_metadata"));
6807 }
6808 let process = try_native_exec_lock(&self.robust_list_process, "robust_list_process")?;
6809 if self.robust_list_head.is_some()
6810 || !process.heads.is_empty()
6811 || !process.pending.is_empty()
6812 || !process.matched_physical_exits.is_empty()
6813 || !process.tool_walked.is_empty()
6814 || process.exit_group_filed
6815 || !process.exit_group_filers.is_empty()
6816 || process.exit_group_walker.is_some()
6817 || process.exit_group_walked
6818 {
6819 return Err(NativeExecCutError::PreparedState("robust process state"));
6820 }
6821 for (owner, wake) in &process.exec_pending {
6824 if *owner != binding.dettid
6825 || matches!(wake.futex, FutexID::Private { mm, .. } if mm != binding.old_mm)
6826 {
6827 return Err(NativeExecCutError::PreparedState("staged robust wake"));
6828 }
6829 }
6830 Ok(())
6831 }
6832
6833 pub(crate) fn observe_guest_clock(&self, raw: LogicalTime) -> LogicalTime {
6834 self.guest_clock
6835 .lock()
6836 .expect("guest clock mutex poisoned")
6837 .observe(raw)
6838 }
6839
6840 pub fn reseed_child_rngs(&mut self, parent: &Self, entropy: u128) {
6845 self.prng = thread_rng_from_parent_entropy("USER RAND", &parent.prng, entropy);
6846 self.chaos_prng = thread_rng_from_parent_entropy("CHAOSRAND", &parent.chaos_prng, entropy);
6847 }
6848
6849 pub(crate) fn recover_process_mm_id(&mut self, detpid: DetPid) -> bool {
6854 if self.dettid == detpid || self.mm_id != MmId::initial(self.dettid) {
6855 return false;
6856 }
6857
6858 self.mm_id = MmId::initial(detpid);
6859 true
6860 }
6861
6862 pub(crate) fn account_process_cpu_time(&mut self) {
6863 let user = self.thread_logical_time.user_cpu_time();
6864 let system = self.thread_logical_time.system_cpu_time();
6865 let user_delta = user - self.last_accounted_user_time;
6866 let system_delta = system - self.last_accounted_system_time;
6867 let own = self.thread_cpu_time();
6868 let mut process = self
6869 .process_cpu_time
6870 .lock()
6871 .expect("process CPU time mutex poisoned");
6872 process.add_thread_delta(user_delta, system_delta);
6873 process.record_thread(self.dettid, own);
6874 drop(process);
6875 self.last_accounted_user_time = user;
6876 self.last_accounted_system_time = system;
6877 }
6878
6879 pub(crate) fn forget_thread_cpu_time(&self) {
6883 self.process_cpu_time
6884 .lock()
6885 .expect("process CPU time mutex poisoned")
6886 .forget_thread(self.dettid);
6887 }
6888
6889 pub(crate) fn sibling_cpu_time(&self, thread: DetTid) -> (LogicalTime, LogicalTime) {
6893 self.process_cpu_time
6894 .lock()
6895 .expect("process CPU time mutex poisoned")
6896 .thread_time(thread)
6897 }
6898
6899 pub(crate) fn task_cpu_time(&self, thread: DetTid) -> (LogicalTime, LogicalTime) {
6903 if thread == self.dettid {
6904 self.thread_cpu_time()
6905 } else {
6906 self.sibling_cpu_time(thread)
6907 }
6908 }
6909
6910 pub(crate) fn charge_syscall_time(
6925 &mut self,
6926 in_backend_runtime_bootstrap: bool,
6927 sysno: Sysno,
6928 ) -> bool {
6929 if !in_backend_runtime_bootstrap {
6930 if self.uncharged_bootstrap_syscalls != 0 {
6931 debug!(
6932 "[dtid {}] backend runtime bootstrap window left {} syscalls uncharged",
6933 self.dettid, self.uncharged_bootstrap_syscalls
6934 );
6935 self.uncharged_bootstrap_syscalls = 0;
6936 }
6937 return true;
6938 }
6939 if crate::syscall_time::observes_virtual_time(sysno) {
6940 return true;
6941 }
6942 if self.uncharged_bootstrap_syscalls < crate::syscall_time::MAX_UNCHARGED_BOOTSTRAP_SYSCALLS
6943 {
6944 self.uncharged_bootstrap_syscalls += 1;
6945 if self.uncharged_bootstrap_syscalls
6946 == crate::syscall_time::MAX_UNCHARGED_BOOTSTRAP_SYSCALLS
6947 {
6948 info!(
6950 "[dtid {}] backend runtime bootstrap window reached its cap of {} uncharged syscalls; every further syscall in this window is charged",
6951 self.dettid,
6952 crate::syscall_time::MAX_UNCHARGED_BOOTSTRAP_SYSCALLS
6953 );
6954 }
6955 return false;
6956 }
6957 true
6958 }
6959
6960 pub(crate) fn process_cpu_time(&mut self) -> ProcessCpuSnapshot {
6961 self.account_process_cpu_time();
6962 self.process_cpu_time
6963 .lock()
6964 .expect("process CPU time mutex poisoned")
6965 .snapshot
6966 }
6967
6968 pub(crate) fn thread_cpu_time(&self) -> (LogicalTime, LogicalTime) {
6976 (
6977 self.thread_logical_time.user_cpu_time() - self.thread_cpu_start_user_time,
6978 self.thread_logical_time.system_cpu_time() - self.thread_cpu_start_system_time,
6979 )
6980 }
6981
6982 pub(crate) fn record_exited_child_process_cpu_time(&mut self, pid: DetPid) -> bool {
6983 self.account_process_cpu_time();
6984 let Some(parent) = &self.parent_process_cpu_time else {
6985 return true;
6986 };
6987 let child = self
6988 .process_cpu_time
6989 .lock()
6990 .expect("process CPU time mutex poisoned")
6991 .snapshot;
6992 {
6993 let mut parent = parent
6994 .lock()
6995 .expect("parent process CPU time mutex poisoned");
6996 if let Some(publication) = &self.parent_cpu_publication
6997 && !parent
6998 .child_cpu_publications
6999 .get(&pid)
7000 .is_some_and(|current| Arc::ptr_eq(current, publication))
7001 {
7002 return false;
7003 }
7004 parent.record_exited_child(pid, child);
7005 }
7006 if let Some(publication) = &self.parent_cpu_publication {
7007 publication.finish(true);
7008 }
7009 true
7010 }
7011
7012 pub(crate) fn prepare_child_cpu_publication(&self, pid: DetPid) -> Arc<ChildCpuPublication> {
7015 let publication = ChildCpuPublication::pending();
7016 let previous = {
7017 let mut parent = self
7018 .process_cpu_time
7019 .lock()
7020 .expect("process CPU time mutex poisoned");
7021 parent.prepare_child(pid);
7022 parent
7023 .child_cpu_publications
7024 .insert(pid, Arc::clone(&publication))
7025 };
7026 if let Some(previous) = previous {
7027 previous.finish(false);
7028 }
7029 publication
7030 }
7031
7032 pub(crate) async fn wait_for_child_cpu_publication(&self, pid: DetPid) -> bool {
7033 let publication = {
7034 let parent = self
7035 .process_cpu_time
7036 .lock()
7037 .expect("process CPU time mutex poisoned");
7038 let Some(publication) = parent.child_cpu_publications.get(&pid) else {
7039 return false;
7040 };
7041 Arc::clone(publication)
7042 };
7043 if !publication.wait().await {
7047 return false;
7048 }
7049 let parent = self
7050 .process_cpu_time
7051 .lock()
7052 .expect("process CPU time mutex poisoned");
7053 parent
7054 .child_cpu_publications
7055 .get(&pid)
7056 .is_some_and(|current| Arc::ptr_eq(current, &publication))
7057 && parent.exited_children.contains_key(&pid)
7058 }
7059
7060 pub(crate) fn has_exited_child_process_cpu_time(&self, pid: DetPid) -> bool {
7061 self.process_cpu_time
7062 .lock()
7063 .expect("process CPU time mutex poisoned")
7064 .exited_children
7065 .contains_key(&pid)
7066 }
7067
7068 pub(crate) fn reap_child_process_cpu_time(&mut self, pid: DetPid) {
7069 self.account_process_cpu_time();
7070 let publication = {
7071 let mut parent = self
7072 .process_cpu_time
7073 .lock()
7074 .expect("process CPU time mutex poisoned");
7075 let had_snapshot = parent.exited_children.contains_key(&pid);
7078 parent.reap_child(pid);
7079 if had_snapshot {
7080 parent.child_cpu_publications.remove(&pid)
7081 } else {
7082 None
7083 }
7084 };
7085 if let Some(publication) = publication {
7086 publication.finish(false);
7087 }
7088 }
7089
7090 pub(crate) fn prepare_child_process_cpu_time(&self, pid: DetPid) {
7091 let mut parent = self
7092 .process_cpu_time
7093 .lock()
7094 .expect("process CPU time mutex poisoned");
7095 if !parent.child_cpu_publications.contains_key(&pid) {
7099 parent.prepare_child(pid);
7100 }
7101 }
7102
7103 pub fn new(pid: DetPid, cfg: &Config, record_or_replay: T) -> Self {
7106 crate::detlog::root_seed_records(pid.as_raw(), cfg.rng_seed(), cfg.sched_seed());
7107 let thread_logical_time = DetTime::new(cfg);
7108 let last_accounted_user_time = thread_logical_time.user_cpu_time();
7109 let last_accounted_system_time = thread_logical_time.system_cpu_time();
7110 let file_metadata = if cfg.backend.tool_shares_guest_descriptor_table {
7111 let mut metadata = FileMetadata::new(pid);
7112 metadata.note_proc_level(cfg.proc_level);
7113 for fd in 0..=2 {
7114 match metadata.discover_fd_from_current_process(pid, fd) {
7115 Ok(()) | Err(Errno::EBADF) => {}
7116 Err(error) => panic!("unable to discover guest stdio {fd}: {error}"),
7117 }
7118 }
7119 metadata
7120 } else {
7121 let mut metadata =
7122 FileMetadata::new(pid).setup_stdio(pid.into(), pid, cfg.standard_stream_aliases);
7123 metadata.note_proc_level(cfg.proc_level);
7124 metadata
7125 };
7126 let process_files = Some(file_metadata.files_id);
7127 ThreadState {
7128 dettid: pid,
7129 detpid: None, thread_start_entered: false,
7131 vfork_origin: None, physical_tid: None,
7133 signal_task_identity: None,
7134 open_file_creator: None,
7135 receive_geometry: None,
7136 entry_import: None,
7137 send_inspection: None,
7138 inotify_queue: None,
7139 call_parts: None,
7140 traced_call: Default::default(),
7141 mm_id: MmId::initial(pid),
7142 memory_metadata: Arc::new(Mutex::new(MemoryMetadata::new())),
7143 pedigree: Pedigree::new(), stats: ThreadStats::new(),
7145 file_metadata: Arc::new(Mutex::new(file_metadata)),
7146 tool_shares_guest_descriptor_table: cfg.backend.tool_shares_guest_descriptor_table,
7147 timer_slack_ns: DEFAULT_TIMER_SLACK_NS,
7148 default_timer_slack_ns: DEFAULT_TIMER_SLACK_NS,
7149 nested_pid_namespace: false,
7150 changed_root: Default::default(),
7151 fs_apart_from_leader: false,
7152 exe: None,
7153 process_files,
7154 posix_timers: Arc::new(Mutex::new(PosixTimers::default())),
7155 resource_limits: Arc::new(Mutex::new(ResourceLimits::default())),
7156 process_cpu_time: Arc::new(Mutex::new(ProcessCpuTime::default())),
7157 guest_clock: Arc::new(Mutex::new(GuestClock::default())),
7158 unix_connections: Default::default(),
7159 parent_process_cpu_time: None,
7160 parent_cpu_publication: None,
7161 restart_block: None,
7162 syscall_cwd: None,
7163 sigalrm_handled: false,
7164 signal_shadow: None,
7165 signal_shadow_lost: false,
7166 delivery_interruption: None,
7167 wait_signal_end: None,
7168 delivery_frame_handler: 0,
7169 delivery_frame_signal: 0,
7170 delivery_frame_orig_ax: 0,
7171 delivery_counters: Default::default(),
7172 published_mask: Some(None),
7173 published_dispositions: Arc::default(),
7174 sigalrm_blocked_published: false,
7175 handler_restore_rt_reported: 0,
7176 signal_register_projection: None,
7177 last_accounted_user_time,
7178 last_accounted_system_time,
7179 thread_cpu_start_user_time: last_accounted_user_time,
7180 thread_cpu_start_system_time: last_accounted_system_time,
7181 clone_flags: None,
7182 pending_vfork: None,
7183 restarted_clone_id: None,
7184 procfs_sequence_taken: None,
7185 prng: crate::random::root_prng(cfg.rng_seed()),
7187 initialized_random_auxv: None,
7188 chaos_prng: Pcg64Mcg::seed_from_u64(cfg.sched_seed()),
7189 thread_logical_time,
7190 committed_clock_value: 0,
7191 uncharged_bootstrap_syscalls: 0,
7192 in_uncharged_bootstrap_syscall: false,
7193 passed_descriptors_out_unreported: false,
7194 passed_descriptors_out_reported: false,
7195 nonblocking_result_normalized: false,
7196 yield_after_futex_wake: false,
7197 pipe_handoff_due: None,
7198 own_signal_turn_hold: None,
7199 delivered_since_request: false,
7200 returned_from_sigreturn: false,
7201 signal_announcements: Vec::new(),
7202 next_signal_announcement: 0,
7203 consecutive_continuations: 0,
7204 scheduler_grants: 0,
7205 end_of_timeslice: None, replay_rcb_end: None,
7207 last_replay_change_point: None,
7208 chaos_epoch: chaos_epoch_sentinel(),
7211 chaos_slowdown_factor: RcbTimeMultiplier::ONE,
7214 chaos_slowdown_active: false,
7215 claimed_sigchld: false,
7216 pending_chaos_epochs: Vec::new(),
7217 max_timeslice_end: None,
7218 last_grant_deadline: None,
7219 published_runtime_private_memory: None,
7220 last_rcb_timer: None,
7221 last_rcb_timer_is_max: false,
7222 lone_grant: false,
7223 lone_slice_end: None,
7224 replaying_lone_slice_end: None,
7225 lone_replay_unsound: false,
7226 record_or_replay,
7227 preemption_points: None,
7228 past_global_first_execve: false,
7229 interrupt_at: cfg.interrupts_for_thread(pid),
7230 robust_list_head: None,
7231 robust_list_process: Arc::new(Mutex::new(RobustListProcessState::default())),
7232 slice_boundary: None,
7233 callback_history: Default::default(),
7234 schedule_replay_over: false,
7235 }
7236 }
7237
7238 pub(crate) fn bind_initial_current_image(&mut self, tid: DetTid) -> Result<(), Errno> {
7241 if tid != self.dettid
7242 || self.detpid.is_some()
7243 || self.thread_start_entered
7244 || self.past_global_first_execve
7245 || self.mm_id != MmId::initial(tid)
7246 || self.clone_flags.is_some()
7247 || self.pending_vfork.is_some()
7248 || self.pedigree.raw() != Pedigree::new().raw()
7249 || self.published_runtime_private_memory.is_some()
7250 {
7251 return Err(Errno::EPROTO);
7252 }
7253 self.mm_id = self.mm_id.for_exec(tid);
7254 Ok(())
7255 }
7256
7257 pub fn apply_initial_random_state(
7260 &mut self,
7261 bytes: &[u8],
7262 config: &Config,
7263 image: crate::random::InitialImage,
7264 ) -> Result<(), Errno> {
7265 if self.initialized_random_auxv.is_some()
7266 || self.past_global_first_execve
7267 || self.dettid.as_raw() != image.pid
7268 || self.clone_flags.is_some()
7269 || self.pedigree.raw() != Pedigree::new().raw()
7270 {
7271 return Err(Errno::EPROTO);
7272 }
7273 let (prng, at_random_value, early_requests) =
7274 crate::random::decode_initial_state(bytes, config, image)?;
7275 self.prng = prng;
7276 self.initialized_random_auxv = Some((image, at_random_value, early_requests));
7277 Ok(())
7278 }
7279
7280 pub(crate) fn complete_initial_random_auxv(
7284 &mut self,
7285 pointer: Option<usize>,
7286 ) -> Result<Option<crate::random::EarlyRandomRecords>, Errno> {
7287 let Some((image, _, _)) = self.initialized_random_auxv.as_ref() else {
7288 return Ok(None);
7289 };
7290 if pointer != Some(image.at_random) || self.dettid.as_raw() != image.pid {
7291 return Err(Errno::EPROTO);
7292 }
7293 let (_, at_random_value, early_requests) =
7294 self.initialized_random_auxv.take().expect("checked above");
7295 Ok(Some((at_random_value, early_requests)))
7296 }
7297
7298 pub(crate) fn record_robust_list_head(&mut self, head: Option<usize>) {
7299 self.robust_list_head = head;
7300 let mut process = self
7301 .robust_list_process
7302 .lock()
7303 .expect("robust-list process state mutex poisoned");
7304 if let Some(head) = head {
7305 process.heads.insert(self.dettid, head);
7306 } else {
7307 process.heads.remove(&self.dettid);
7308 }
7309 }
7310
7311 pub(crate) fn mark_robust_list_walked_by_tool(&self) {
7315 self.robust_list_process
7316 .lock()
7317 .expect("robust-list process state mutex poisoned")
7318 .tool_walked
7319 .insert(self.dettid);
7320 }
7321
7322 pub(crate) fn file_exit_group_for_robust_lists(&self) {
7338 let mut process = self
7339 .robust_list_process
7340 .lock()
7341 .expect("robust-list process state mutex poisoned");
7342 process.exit_group_filed = true;
7343 process.exit_group_filers.insert(self.dettid);
7344 }
7345
7346 pub(crate) fn begin_exit_group_robust_walk(&self) {
7351 self.robust_list_process
7352 .lock()
7353 .expect("robust-list process state mutex poisoned")
7354 .exit_group_walker = Some(self.dettid);
7355 }
7356
7357 pub(crate) fn cancel_exit_group_filing(&self) {
7362 self.robust_list_process
7363 .lock()
7364 .expect("robust-list process state mutex poisoned")
7365 .exit_group_filers
7366 .remove(&self.dettid);
7367 }
7368
7369 pub(crate) fn finish_exit_group_robust_walk(&self) {
7372 self.robust_list_process
7373 .lock()
7374 .expect("robust-list process state mutex poisoned")
7375 .exit_group_walked = true;
7376 }
7377
7378 pub(crate) fn wait_for_exit_group_robust_walk(&self, bound: std::time::Duration) -> bool {
7399 let started = std::time::Instant::now();
7400 loop {
7401 {
7402 let process = self
7403 .robust_list_process
7404 .lock()
7405 .expect("robust-list process state mutex poisoned");
7406 if !process.exit_group_filed
7407 || process.exit_group_walked
7408 || process.exit_group_walker == Some(self.dettid)
7409 || (process.exit_group_walker.is_none() && process.exit_group_filers.is_empty())
7410 {
7411 return true;
7412 }
7413 }
7414 if started.elapsed() >= bound {
7415 return false;
7416 }
7417 std::thread::sleep(std::time::Duration::from_micros(200));
7418 }
7419 }
7420
7421 pub(crate) fn drop_robust_list_head_at_exit(&mut self) {
7426 self.robust_list_head = None;
7427 let mut process = self
7428 .robust_list_process
7429 .lock()
7430 .expect("robust-list process state mutex poisoned");
7431 if !process.exit_group_filed {
7432 process.heads.remove(&self.dettid);
7433 }
7434 }
7435
7436 pub(crate) fn robust_list_owners_for_exit_group(&self) -> Vec<RobustListOwner> {
7441 let walked = self
7442 .robust_list_process
7443 .lock()
7444 .expect("robust-list process state mutex poisoned")
7445 .tool_walked
7446 .clone();
7447 self.robust_list_owners()
7448 .into_iter()
7449 .filter(|owner| !walked.contains(&owner.dettid))
7450 .collect()
7451 }
7452
7453 pub(crate) fn robust_list_owners(&self) -> Vec<RobustListOwner> {
7455 let process = self
7456 .robust_list_process
7457 .lock()
7458 .expect("robust-list process state mutex poisoned");
7459 process
7460 .heads
7461 .iter()
7462 .map(|(&dettid, &head)| RobustListOwner { dettid, head })
7463 .collect()
7464 }
7465
7466 #[cfg(test)]
7467 pub(crate) fn robust_list_heads(&self) -> Vec<(DetTid, usize)> {
7468 self.robust_list_process
7469 .lock()
7470 .expect("robust-list process state mutex poisoned")
7471 .heads
7472 .iter()
7473 .map(|(&tid, &head)| (tid, head))
7474 .collect()
7475 }
7476
7477 pub(crate) fn stage_robust_list_wakes(
7478 &self,
7479 reason: RobustListExit,
7480 wakes: Vec<(DetTid, Vec<RobustListWake>)>,
7481 ) {
7482 let mut process = self
7483 .robust_list_process
7484 .lock()
7485 .expect("robust-list process state mutex poisoned");
7486 process.pending = wakes
7487 .into_iter()
7488 .map(|(owner, wakes)| (owner, PendingRobustListWakes { reason, wakes }))
7489 .collect();
7490 process.matched_physical_exits.clear();
7491 }
7492
7493 pub(crate) fn has_matching_robust_list_exit(&self, exit_signal: Option<i32>) -> bool {
7494 self.robust_list_process
7495 .lock()
7496 .expect("robust-list process state mutex poisoned")
7497 .pending
7498 .get(&self.dettid)
7499 .is_some_and(|pending| pending.matches_exit(exit_signal))
7500 }
7501
7502 pub(crate) fn take_robust_list_wakes_after_exit(
7503 &self,
7504 exit_signal: Option<i32>,
7505 exit_time: DetTime,
7506 ) -> Option<(DetTime, Vec<(DetTid, RobustListWake)>)> {
7507 let mut process = self
7508 .robust_list_process
7509 .lock()
7510 .expect("robust-list process state mutex poisoned");
7511 if !process.exit_group_filed {
7512 process.heads.remove(&self.dettid);
7513 }
7514 let pending = process.pending.get(&self.dettid)?;
7515 if !pending.matches_exit(exit_signal) {
7516 return None;
7517 }
7518
7519 process
7520 .matched_physical_exits
7521 .insert(self.dettid, exit_time);
7522 if process
7523 .pending
7524 .keys()
7525 .any(|owner| !process.matched_physical_exits.contains_key(owner))
7526 {
7527 return None;
7528 }
7529
7530 let request_time = process
7531 .matched_physical_exits
7532 .values()
7533 .max()
7534 .expect("a completed robust-list exit group has a physical-exit time")
7535 .clone();
7536 process.matched_physical_exits.clear();
7537 let pending = std::mem::take(&mut process.pending);
7538 let mut ready = Vec::new();
7539 for (owner, mut pending) in pending {
7540 pending
7541 .wakes
7542 .sort_by_key(|wake| format!("{:?}", wake.futex));
7543 ready.extend(pending.wakes.into_iter().map(|wake| (owner, wake)));
7544 }
7545 Some((request_time, ready))
7546 }
7547
7548 pub(crate) fn stage_robust_list_wakes_for_exec(
7562 &self,
7563 wakes: Vec<(DetTid, Vec<RobustListWake>)>,
7564 ) {
7565 let mut process = self
7566 .robust_list_process
7567 .lock()
7568 .expect("robust-list process state mutex poisoned");
7569 process.exec_pending = flatten_exec_robust_list_wakes(wakes);
7570 }
7571
7572 pub(crate) fn take_robust_list_wakes_for_exec(&self) -> Vec<(DetTid, RobustListWake)> {
7575 std::mem::take(
7576 &mut self
7577 .robust_list_process
7578 .lock()
7579 .expect("robust-list process state mutex poisoned")
7580 .exec_pending,
7581 )
7582 }
7583
7584 pub(crate) fn take_robust_list_for_exec(&mut self) -> Option<usize> {
7593 let previous = self.robust_list_head.take();
7594 self.robust_list_process
7595 .lock()
7596 .expect("robust-list process state mutex poisoned")
7597 .heads
7598 .remove(&self.dettid);
7599 previous
7600 }
7601
7602 pub(crate) fn restore_robust_list_after_failed_exec(&mut self, previous: Option<usize>) {
7605 self.record_robust_list_head(previous);
7606 }
7607
7608 pub(crate) fn futex_id(&self, address: usize, is_private: bool) -> FutexID {
7610 if is_private {
7611 FutexID::private(self.mm_id, address)
7612 } else {
7613 self.memory_metadata
7614 .lock()
7615 .expect("memory metadata mutex poisoned")
7616 .futex_id(self.mm_id, address)
7617 }
7618 }
7619
7620 pub(crate) fn note_protection_keys(&self) {
7623 self.memory_metadata
7624 .lock()
7625 .expect("memory metadata mutex poisoned")
7626 .protection_keys = true;
7627 }
7628
7629 pub(crate) fn may_use_protection_keys(&self) -> bool {
7631 self.memory_metadata
7632 .lock()
7633 .expect("memory metadata mutex poisoned")
7634 .protection_keys
7635 }
7636
7637 pub(crate) fn map_shared_anonymous(&self, start: usize, len: usize) {
7639 self.memory_metadata
7640 .lock()
7641 .expect("memory metadata mutex poisoned")
7642 .map_anonymous(self.mm_id, start, len);
7643 }
7644
7645 pub(crate) fn map_shared_object(
7647 &self,
7648 start: usize,
7649 len: usize,
7650 object: SharedMemoryObjectId,
7651 object_offset: u64,
7652 ) {
7653 self.memory_metadata
7654 .lock()
7655 .expect("memory metadata mutex poisoned")
7656 .map_object(start, len, object, object_offset);
7657 }
7658
7659 pub(crate) fn unmap_memory(&self, start: usize, len: usize) {
7661 self.memory_metadata
7662 .lock()
7663 .expect("memory metadata mutex poisoned")
7664 .unmap(start, len);
7665 }
7666
7667 pub(crate) fn remap_memory(
7669 &self,
7670 old_start: usize,
7671 old_len: usize,
7672 new_start: usize,
7673 new_len: usize,
7674 keep_source: bool,
7675 ) {
7676 self.memory_metadata
7677 .lock()
7678 .expect("memory metadata mutex poisoned")
7679 .remap(old_start, old_len, new_start, new_len, keep_source);
7680 }
7681
7682 pub fn mk_request(&self, rid: ResourceID, perm: Permission) -> Resources {
7684 let mut resources = HashMap::new();
7685 resources.insert(rid, perm);
7686 Resources {
7687 tid: self.dettid,
7688 resources,
7689 poll_attempt: 0,
7690 fyi: String::new(),
7691 signal_interrupt_errno: None,
7692 backend_runtime_bootstrap: false,
7693 blocked_signal_mask: None,
7694 blocked_signal_mask_unknown: false,
7695 replay_served_from_log: false,
7696 reads_signalfd: false,
7697 read_fd: None,
7698 polls_guest_state_only: false,
7699 polls_a_named_fifo: false,
7700 relies_on_guest_held_marks: false,
7701 passes_descriptors_out: false,
7702 waits_until_nothing_else_can_run: false,
7703 previous_attempt_made_progress: false,
7704 upgraded_from_attempt: 0,
7705 polls_channel: None,
7706 poll_deadline: None,
7707 in_guest_sleep_sigalrm: None,
7708 signal_state_task: None,
7709 decides_signal_interruption_itself: false,
7710 continues_sigreturn: false,
7711 private_sigchld_decides: false,
7712 slice_boundary: None,
7713 guest_signal_mask: None,
7714 callback_checkpoint: None,
7715 priority_row: None,
7716 slice_expired: false,
7717 zero_length_sleep: false,
7718 }
7719 }
7720
7721 pub fn chaos_prng_next_u64(&mut self, msg: &str) -> u64 {
7723 let r = self.chaos_prng.next_u64();
7724 detlog!("[dtid {}] CHAOSRAND({}): u64 => {}", self.dettid, msg, r);
7725 r
7726 }
7727
7728 fn metadata(&self) -> MutexGuard<'_, FileMetadata> {
7730 self.file_metadata.lock().unwrap()
7731 }
7732
7733 pub(crate) fn descriptor_table(&self) -> (Vec<RawFd>, u64, bool) {
7739 let metadata = self.metadata();
7740 (
7741 metadata.descriptor_numbers(),
7742 metadata.descriptor_capacity,
7743 metadata.unaccounted_receipts,
7744 )
7745 }
7746
7747 pub(crate) fn note_unmodelled_descriptor(&self, fd: RawFd, cloexec: bool) {
7751 let mut metadata = self.metadata();
7752 if !metadata.file_handles.contains_key(&fd) {
7753 metadata.note_installed(fd);
7754 metadata.present_unmodelled.insert(fd, cloexec);
7755 }
7756 }
7757
7758 pub(crate) fn note_failed_dup_target(&self, fd: RawFd) {
7763 let limit = self
7764 .resource_limits
7765 .lock()
7766 .unwrap()
7767 .get(reverie::syscalls::linux_abi::RLIMIT_NOFILE)
7768 .map_or(0, |limit| limit.current);
7769 if u64::try_from(fd).is_ok_and(|fd| fd < limit) {
7770 self.metadata().grow_to_hold(fd);
7771 }
7772 }
7773
7774 pub(crate) fn set_unmodelled_cloexec(&self, fd: RawFd, cloexec: bool) -> bool {
7779 self.metadata().set_unmodelled_cloexec(fd, cloexec)
7780 }
7781
7782 pub(crate) fn note_unaccounted_receipt(&self) {
7786 self.metadata().unaccounted_receipts = true;
7787 }
7788
7789 pub(crate) fn files_id(&self) -> detcore_model::fd::FilesId {
7791 self.metadata().files_id
7792 }
7793
7794 pub(crate) fn holds_process_files(&self) -> bool {
7798 self.process_files == Some(self.files_id())
7799 }
7800
7801 pub(crate) fn adopt_process_files(&mut self) {
7805 self.process_files = Some(self.files_id());
7806 }
7807
7808 pub fn add_fd(
7823 &self,
7824 fd: RawFd,
7825 flags: OFlag,
7826 ty: FdType,
7827 stat: Option<DetStat>,
7828 ) -> Result<(), Errno> {
7829 let mut metadata = self.metadata();
7830 metadata.add_fd(
7831 self.open_file_creator.unwrap_or(self.dettid),
7832 fd,
7833 flags,
7834 ty,
7835 stat,
7836 )?;
7837 if self.traced_call.is_active() {
7838 self.traced_call
7839 .note(metadata.slot_note(fd, FdNoteOp::Open));
7840 }
7841 Ok(())
7842 }
7843
7844 pub(crate) fn add_discovered_fd(
7849 &self,
7850 fd: RawFd,
7851 fd_flags: libc::c_int,
7852 status_flags: libc::c_int,
7853 raw_stat: &libc::stat,
7854 link: Option<&[u8]>,
7855 ) -> Result<(), Errno> {
7856 let mut metadata = self.metadata();
7857 metadata.add_discovered_fd(
7858 self.open_file_creator.unwrap_or(self.dettid),
7859 fd,
7860 fd_flags,
7861 status_flags,
7862 raw_stat,
7863 link,
7864 )?;
7865 if self.traced_call.is_active() {
7866 self.traced_call
7867 .note(metadata.slot_note(fd, FdNoteOp::Open));
7868 }
7869 Ok(())
7870 }
7871
7872 pub(crate) fn classify_random_device(&self, fd: RawFd) -> Result<(), Errno> {
7875 self.metadata()
7876 .classify_random_device(self.open_file_creator.unwrap_or(self.dettid), fd)
7877 }
7878
7879 pub(crate) fn random_device_sequence_for_exec(&self) -> u64 {
7882 self.metadata().next_random_device_sequence
7883 }
7884
7885 pub fn set_open_file_creator(&mut self, creator: DetTid) {
7889 self.open_file_creator = Some(creator);
7890 }
7891
7892 pub(crate) fn virtual_timerfds(&self) -> Vec<(RawFd, DetFd)> {
7897 let mut timerfds: Vec<(RawFd, DetFd)> = self
7898 .metadata()
7899 .file_handles
7900 .iter()
7901 .filter(|(_, detfd)| detfd.ty() == FdType::Timerfd && detfd.virtual_timerfd().is_some())
7902 .map(|(&fd, detfd)| (fd, detfd.clone()))
7903 .collect();
7904 timerfds.sort_unstable_by_key(|(fd, _)| *fd);
7905 let mut descriptions = Vec::with_capacity(timerfds.len());
7906 timerfds.retain(|(_, detfd)| {
7907 let id = detfd.open_file_id();
7908 let first = !descriptions.contains(&id);
7909 descriptions.push(id);
7910 first
7911 });
7912 timerfds
7913 }
7914
7915 pub(crate) fn holds_a_signalfd(&self) -> bool {
7918 self.metadata()
7919 .file_handles
7920 .values()
7921 .any(|detfd| matches!(detfd.ty(), FdType::Signalfd))
7922 }
7923
7924 pub(crate) fn holds_a_rendered_procfs_file(&self) -> bool {
7927 self.metadata()
7928 .file_handles
7929 .values()
7930 .any(|detfd| detfd.procfs_rendered_path().is_some())
7931 }
7932
7933 pub fn with_detfd<F, U>(&self, fd: RawFd, f: F) -> Result<U, Errno>
7936 where
7937 F: FnMut(&mut DetFd) -> U,
7938 {
7939 let mut metadata = self.metadata();
7940 if self.tool_shares_guest_descriptor_table {
7941 metadata.discover_fd_from_current_process(
7942 self.open_file_creator.unwrap_or(self.dettid),
7943 fd,
7944 )?;
7945 }
7946 metadata.with_detfd(fd, f)
7947 }
7948
7949 pub fn exec_blocking_overrides(&self) -> ExecFdBlockingOverrides {
7952 self.metadata().exec_blocking_overrides()
7953 }
7954
7955 pub fn promote_shared_open_files(&self) -> Result<(), crate::fd::SharedOpenFileError> {
7966 self.share_procfs_sequences();
7967 let mut metadata = self.metadata();
7968 let mut fds: Vec<_> = metadata.file_handles.keys().copied().collect();
7969 fds.sort_unstable();
7970 for fd in fds {
7971 let handle = &metadata.file_handles[&fd];
7972 if matches!(handle.ty(), FdType::Rng | FdType::Timerfd) {
7973 handle.promote()?;
7974 metadata.has_promoted_shared_files = true;
7976 }
7977 }
7978 Ok(())
7979 }
7980
7981 pub fn share_procfs_sequences(&self) {
7993 let mut metadata = self.metadata();
7994 let mut shared = false;
7995 for handle in metadata.file_handles.values() {
7996 shared |= handle.share_procfs_sequence();
7997 }
7998 metadata.has_shared_procfs_sequences |= shared;
7999 }
8000
8001 pub(crate) fn share_sent_procfs_sequence(&self, fd: RawFd) -> Option<OpenFileId> {
8010 let mut metadata = self.metadata();
8011 let handle = metadata.file_handles.get(&fd)?;
8012 if handle.is_shared()
8013 || handle.record_sequence_unknown()
8014 || handle.procfs_rendered_path().is_none()
8015 || !handle.share_procfs_sequence()
8016 {
8017 return None;
8018 }
8019 let id = handle.procfs_shared_sequence();
8020 metadata.has_shared_procfs_sequences |= id.is_some();
8021 id
8022 }
8023
8024 pub(crate) fn rendered_descriptors(&self) -> Vec<RawFd> {
8029 let metadata = self.metadata();
8030 let mut fds: Vec<RawFd> = metadata
8031 .file_handles
8032 .iter()
8033 .filter(|(_, detfd)| !detfd.is_shared() && detfd.procfs_rendered_path().is_some())
8034 .map(|(&fd, _)| fd)
8035 .collect();
8036 fds.sort_unstable();
8037 fds
8038 }
8039
8040 pub fn reset_copied_open_file_locks(&self) {
8044 let mut metadata = self.metadata();
8045 let mut handles = metadata
8046 .file_handles
8047 .iter()
8048 .map(|(&fd, handle)| (fd, handle.fork_shared_handle()))
8049 .collect();
8050 crate::fd::intern_open_files(&mut handles);
8051 metadata.file_handles = handles;
8052 }
8053
8054 pub(crate) fn pipe_channels_for_exec(&self) -> BTreeMap<RawFd, crate::pipe_wake::ChannelEnd> {
8057 self.metadata()
8058 .file_handles
8059 .iter()
8060 .filter(|(_, detfd)| !detfd.is_cloexec())
8061 .filter_map(|(&fd, detfd)| detfd.channel().map(|end| (fd, end)))
8062 .collect()
8063 }
8064
8065 pub(crate) fn open_file_namespace_for_exec(&self) -> (DetTid, u64) {
8067 (
8068 self.open_file_creator.unwrap_or(self.dettid),
8069 self.metadata().next_open_file_sequence,
8070 )
8071 }
8072
8073 pub(crate) fn shared_open_files_for_exec(&self) -> Result<Option<ExecOpenFiles>, Errno> {
8074 let metadata = self.metadata();
8075 if !metadata.has_promoted_shared_files {
8076 return Ok(None);
8077 }
8078 let mut handles = BTreeMap::new();
8079 for (&fd, handle) in &metadata.file_handles {
8080 if !handle.is_shared() || handle.is_cloexec() {
8081 continue;
8082 }
8083 assert!(
8084 matches!(handle.ty(), FdType::Rng | FdType::Timerfd),
8085 "only random and timerfd OFDs are promoted here"
8086 );
8087 handles.insert(fd, ExecOpenFile::capture(fd, handle.open_file_id())?);
8088 }
8089 Ok(Some(ExecOpenFiles {
8090 creator: self.open_file_creator.unwrap_or(self.dettid),
8091 next_sequence: metadata.next_open_file_sequence,
8092 next_socket_sequence: metadata.next_socket_open_file_sequence,
8093 handles,
8094 }))
8095 }
8096
8097 pub(crate) fn procfs_records_for_exec(&self) -> Result<Option<ExecProcfsRecords>, Errno> {
8114 let metadata = self.metadata();
8115 let mut handles = BTreeMap::new();
8116 for (&fd, handle) in &metadata.file_handles {
8117 if handle.is_cloexec() || handle.is_shared() {
8118 continue;
8119 }
8120 let Some(procfs) = handle.procfs_record_for_exec() else {
8121 continue;
8122 };
8123 let sequence = procfs.sequence();
8124 handles.insert(
8125 fd,
8126 ExecProcfsRecord {
8127 identity: ExecOpenFile::capture(fd, handle.open_file_id())?,
8128 procfs,
8129 sequence,
8130 flags: handle.status_flags(),
8131 path: handle.path(),
8132 sequence_unknown: handle.record_sequence_unknown(),
8133 },
8134 );
8135 }
8136 if handles.is_empty() && !metadata.has_shared_procfs_sequences {
8137 return Ok(None);
8138 }
8139 Ok(Some(ExecProcfsRecords {
8140 creator: self.open_file_creator.unwrap_or(self.dettid),
8141 next_sequence: metadata.next_open_file_sequence,
8142 handles,
8143 }))
8144 }
8145
8146 pub(crate) fn maps_for_exec(
8147 &self,
8148 private_memory: Option<&reverie::RuntimePrivateMemory>,
8149 ) -> Result<Option<ExecMapsFiles>, Errno> {
8150 if private_memory.is_none() {
8153 return Ok(None);
8154 }
8155 let metadata = self.metadata();
8156 let mut handles = BTreeMap::new();
8157 for (&fd, handle) in &metadata.file_handles {
8158 if handle.is_cloexec() {
8159 continue;
8160 }
8161 if let Some(procfs) = handle.maps_for_exec() {
8162 handles.insert(
8163 fd,
8164 ExecMapsFile {
8165 identity: ExecOpenFile::capture(fd, handle.open_file_id())?,
8166 procfs,
8167 flags: handle.status_flags(),
8168 path: handle.path(),
8169 },
8170 );
8171 }
8172 }
8173 Ok((!handles.is_empty()).then_some(ExecMapsFiles {
8174 creator: self.open_file_creator.unwrap_or(self.dettid),
8175 next_sequence: metadata.next_open_file_sequence,
8176 handles,
8177 }))
8178 }
8179
8180 pub(crate) fn inherited_reads_for_exec(
8181 &self,
8182 ) -> BTreeMap<RawFd, crate::fd::InheritedExternalRead> {
8183 self.metadata().inherited_reads_for_exec()
8184 }
8185
8186 pub(crate) fn scheduler_managed_pipe_fds(&self) -> Vec<RawFd> {
8198 let mut fds: Vec<RawFd> = self
8199 .metadata()
8200 .file_handles
8201 .iter()
8202 .filter(|(_, detfd)| {
8203 detfd.ty() == FdType::Pipe
8204 && detfd.inherited_external_read().is_none()
8205 && detfd.physically_nonblocking()
8206 && !detfd.is_nonblocking()
8207 })
8208 .map(|(&fd, _)| fd)
8209 .collect();
8210 fds.sort_unstable();
8211 fds
8212 }
8213
8214 pub(crate) fn names_guest_held_object(&self, fd: RawFd, dev: u64, inode: u64) -> bool {
8220 self.metadata().file_handles.iter().any(|(&other, detfd)| {
8221 other != fd
8222 && detfd.is_guest_held()
8223 && detfd
8224 .stat()
8225 .is_some_and(|stat| stat.dev == dev && stat.inode == inode)
8226 })
8227 }
8228
8229 pub(crate) fn mark_accept_in_turn_listeners_exported(&self) {
8234 for detfd in self.metadata().file_handles.values() {
8235 if detfd.is_accept_in_turn_eligible() {
8236 detfd.set_exported();
8237 }
8238 }
8239 }
8240
8241 pub(crate) fn give_up_every_pair_completeness(&self) {
8245 for detfd in self.metadata().file_handles.values() {
8246 detfd.give_up_pair_completeness();
8247 }
8248 }
8249
8250 pub(crate) fn network_channel_fds(&self) -> Vec<RawFd> {
8253 let mut fds: Vec<RawFd> = self
8254 .metadata()
8255 .file_handles
8256 .iter()
8257 .filter(|(_, detfd)| detfd.is_network_channel())
8258 .map(|(&fd, _)| fd)
8259 .collect();
8260 fds.sort_unstable();
8261 fds
8262 }
8263
8264 pub(crate) fn count_open_files_at_paths(&self, paths: &[&Path]) -> usize {
8265 self.metadata().count_open_files_at_paths(paths)
8266 }
8267
8268 pub(crate) fn has_loopback_peer(&self) -> bool {
8270 self.metadata().has_loopback_peer()
8271 }
8272
8273 pub fn has_unsafe_vfork_flock_state(&self) -> bool {
8277 self.metadata().has_unsafe_vfork_flock_state()
8278 }
8279
8280 pub fn has_unsafe_vfork_ofd_state(&self) -> bool {
8285 self.metadata().has_unsafe_vfork_ofd_state()
8286 }
8287
8288 pub fn forget_flock_modes(&self) {
8292 self.metadata().forget_flock_modes();
8293 }
8294
8295 pub fn remove_fd(&self, fd: RawFd) -> Option<OpenFileId> {
8297 let mut metadata = self.metadata();
8298 if self.traced_call.is_active()
8299 && let Some(note) = metadata.close_note(fd)
8300 {
8301 self.traced_call.note(note);
8302 }
8303 metadata.remove_fd(fd)
8304 }
8305
8306 pub(crate) fn release_table_at_exit(&self, whole_process: bool) -> bool {
8323 let Some(process) = self.detpid else {
8324 return false;
8325 };
8326 let mut metadata = self.metadata();
8327 if !metadata.leave(process, whole_process) {
8328 return false;
8329 }
8330 metadata.release_every_descriptor();
8331 true
8332 }
8333
8334 pub(crate) fn release_exec_dropped_descriptors(&self) -> bool {
8356 let Some((replaced, process)) = self.metadata().exec_replaced.0.take() else {
8357 return false;
8358 };
8359 let released = {
8360 let mut replaced = replaced.lock().expect("file metadata mutex poisoned");
8361 if !replaced.leave(process, true) {
8362 return false;
8363 }
8364 replaced.release_every_descriptor();
8365 replaced.take_released_rights()
8366 };
8367 if released.is_empty() {
8368 return false;
8369 }
8370 self.defer_released_rights(released);
8371 true
8372 }
8373
8374 pub(crate) fn take_released_rights(&self) -> Vec<SentDescriptor> {
8380 self.metadata().take_released_rights()
8381 }
8382
8383 pub(crate) fn holds_released_rights(&self) -> bool {
8386 !self.metadata().released_rights.0.is_empty()
8387 }
8388
8389 pub(crate) fn defer_released_rights(&self, released: impl IntoIterator<Item = SentDescriptor>) {
8393 self.metadata().released_rights.0.extend(released);
8394 }
8395
8396 pub(crate) fn remove_fd_range(&self, first: u32, last: u32) -> Vec<OpenFileId> {
8398 let mut metadata = self.metadata();
8399 if self.traced_call.is_active() {
8400 let mut released: Vec<RawFd> = metadata
8401 .file_handles
8402 .keys()
8403 .copied()
8404 .filter(|fd| *fd >= 0 && first <= *fd as u32 && *fd as u32 <= last)
8405 .collect();
8406 released.sort_unstable();
8407 for note in released
8408 .into_iter()
8409 .filter_map(|fd| metadata.close_note(fd))
8410 {
8411 self.traced_call.note(note);
8412 }
8413 }
8414 metadata.remove_fd_range(first, last)
8415 }
8416
8417 pub(crate) fn slots_naming(&self, open_file: OpenFileId) -> u32 {
8420 let count = self
8421 .metadata()
8422 .file_handles
8423 .values()
8424 .filter(|detfd| detfd.open_file_id() == open_file)
8425 .count();
8426 u32::try_from(count).unwrap_or(u32::MAX)
8427 }
8428
8429 pub(crate) fn exec_dropped_lock_tracked(&self) -> bool {
8432 self.metadata().exec_dropped_lock_tracked
8433 }
8434
8435 pub(crate) fn holds_placeholder(&self) -> bool {
8438 self.metadata()
8439 .file_handles
8440 .values()
8441 .any(|detfd| detfd.placeholder().is_some())
8442 }
8443
8444 pub(crate) fn lowest_slot_naming(&self, open_file: OpenFileId) -> Option<RawFd> {
8447 self.metadata()
8448 .file_handles
8449 .iter()
8450 .filter(|(_, detfd)| detfd.open_file_id() == open_file)
8451 .map(|(&fd, _)| fd)
8452 .min()
8453 }
8454
8455 pub(crate) fn lock_tracked_slots(&self) -> Vec<(OpenFileId, u32)> {
8459 let mut slots: BTreeMap<OpenFileId, u32> = BTreeMap::new();
8460 for detfd in self.metadata().file_handles.values() {
8461 if detfd.is_lock_tracked() {
8462 *slots.entry(detfd.open_file_id()).or_default() += 1;
8463 }
8464 }
8465 slots.into_iter().collect()
8466 }
8467
8468 pub(crate) fn open_fds(&self) -> Vec<RawFd> {
8470 let mut fds: Vec<RawFd> = self.metadata().file_handles.keys().copied().collect();
8471 fds.sort_unstable();
8472 fds
8473 }
8474
8475 pub fn dup_fd(
8477 &mut self,
8478 oldfd: RawFd,
8479 newfd: RawFd,
8480 flags: OFlag,
8481 ) -> Result<Option<OpenFileId>, Errno> {
8482 let mut metadata = self.metadata();
8483 if self.tool_shares_guest_descriptor_table {
8484 metadata.discover_fd_from_current_process(
8485 self.open_file_creator.unwrap_or(self.dettid),
8486 oldfd,
8487 )?;
8488 }
8489 let replaced = (self.traced_call.is_active() && oldfd != newfd)
8490 .then(|| metadata.close_note(newfd))
8491 .flatten();
8492 let released = metadata.dup_fd(oldfd, newfd, flags)?;
8493 if self.traced_call.is_active() && oldfd != newfd {
8494 if let Some(note) = replaced {
8495 self.traced_call.note(note);
8496 }
8497 self.traced_call
8498 .note(metadata.slot_note(newfd, FdNoteOp::Dup(oldfd)));
8499 }
8500 Ok(released)
8501 }
8502
8503 pub(crate) fn capture_pidfd_getfd_source(
8509 &self,
8510 pidfd: RawFd,
8511 targetfd: RawFd,
8512 current_tgid: DetPid,
8513 current_tid: DetTid,
8514 ) -> Result<CapturedDetFd, Errno> {
8515 let mut metadata = self.metadata();
8516 if self.tool_shares_guest_descriptor_table {
8517 metadata.discover_fd_from_current_process(
8518 self.open_file_creator.unwrap_or(self.dettid),
8519 pidfd,
8520 )?;
8521 }
8522 let (is_pidfd, target) = metadata.with_detfd(pidfd, |detfd| {
8523 (matches!(detfd.ty(), FdType::Pidfd), detfd.pidfd_target())
8524 })?;
8525 if !is_pidfd {
8526 return Err(Errno::EBADF);
8527 }
8528 if !pidfd_getfd_targets_calling_task(target, current_tgid, current_tid) {
8529 return Err(Errno::EOPNOTSUPP);
8530 }
8531 if self.tool_shares_guest_descriptor_table {
8532 metadata.discover_fd_from_current_process(
8533 self.open_file_creator.unwrap_or(self.dettid),
8534 targetfd,
8535 )?;
8536 }
8537 metadata.capture_fd(targetfd)
8538 }
8539
8540 pub(crate) fn install_captured_fd(
8543 &mut self,
8544 captured: CapturedDetFd,
8545 newfd: RawFd,
8546 flags: OFlag,
8547 ) -> Result<Option<OpenFileId>, CapturedDetFdInstallError> {
8548 let mut metadata = self.metadata();
8549 let replaced = self
8550 .traced_call
8551 .is_active()
8552 .then(|| metadata.close_note(newfd))
8553 .flatten();
8554 let released = metadata.install_captured_fd(captured, newfd, flags)?;
8555 if self.traced_call.is_active() {
8556 if let Some(note) = replaced {
8557 self.traced_call.note(note);
8558 }
8559 self.traced_call
8560 .note(metadata.slot_note(newfd, FdNoteOp::Open));
8561 }
8562 Ok(released)
8563 }
8564
8565 pub(crate) fn install_alias(&self, detfd: DetFd) {
8571 let fd = detfd.fd;
8572 let mut metadata = self.metadata();
8573 metadata.add_detfd(detfd);
8574 if self.traced_call.is_active() {
8575 self.traced_call
8576 .note(metadata.slot_note(fd, FdNoteOp::Open));
8577 }
8578 }
8579
8580 pub(crate) fn access_trace_fds(
8583 &self,
8584 fds: &[RawFd],
8585 ) -> BTreeMap<RawFd, crate::access_trace::FdInfo> {
8586 let metadata = self.metadata();
8587 fds.iter()
8588 .filter_map(|fd| {
8589 metadata
8590 .file_handles
8591 .get(fd)
8592 .map(|detfd| (*fd, crate::access_trace::FdInfo::of(detfd)))
8593 })
8594 .collect()
8595 }
8596
8597 pub(crate) fn abandon_captured_fd(&self, captured: CapturedDetFd) -> Option<OpenFileId> {
8599 self.metadata().abandon_captured_fd(captured)
8600 }
8601
8602 pub fn thread_prng(&mut self) -> &mut Pcg64Mcg {
8605 &mut self.prng
8606 }
8607
8608 pub(crate) fn follows_replayed_schedule(&self, cfg: &Config) -> bool {
8616 cfg.replay_schedule_from.is_some() && !self.schedule_replay_over
8617 }
8618
8619 #[cfg(target_arch = "x86_64")]
8635 pub(crate) fn instruction_trap_ends_timeslice(&self, cfg: &Config) -> bool {
8636 let replaying = self.preemption_points.is_some() || self.follows_replayed_schedule(cfg);
8637 match self.max_timeslice_end {
8638 Some(max_end) if cfg.target_timeslice_syscalls_only && !replaying => {
8639 self.thread_logical_time.as_nanos() >= max_end
8640 }
8641 _ => self.timeslice_expired(),
8642 }
8643 }
8644
8645 pub(crate) fn cap_timeslice_at_deadline(
8671 &mut self,
8672 remaining: Duration,
8673 clock_multiplier: f64,
8674 min_slice: Duration,
8675 schedule_replay: bool,
8676 ) {
8677 let recorded_slice =
8678 self.preemption_points.is_some() && self.stats.last_recorded_slice.is_none();
8679 if recorded_slice || schedule_replay {
8680 return;
8681 }
8682 let Some(end) = self.end_of_timeslice else {
8683 return;
8684 };
8685 let one_rcb =
8686 (NANOS_PER_RCB * clock_multiplier * self.rcb_time_multiplier().as_f64()).ceil() as u64;
8687 let current = self.thread_logical_time.as_nanos();
8688 let cap = current
8689 + remaining
8690 .max(min_slice)
8691 .max(Duration::from_nanos(one_rcb + 1));
8692 if cap < end {
8693 debug!(
8694 "[dtid {}] end of slice moved from {} to {}, the earliest pending deadline (current {})",
8695 self.dettid, end, cap, current
8696 );
8697 self.end_of_timeslice = Some(cap);
8698 }
8699 }
8700
8701 pub(crate) fn active_priority_row(
8706 &self,
8707 ) -> Option<crate::preemptions::callback_history::ActiveRow> {
8708 use crate::preemptions::callback_history::ActiveRow;
8709 let history = self.preemption_points.as_ref()?;
8710 Some(if self.stats.last_recorded_slice.is_some() {
8711 ActiveRow::Exhausted
8712 } else {
8713 match history.installed_rows() {
8714 0 => ActiveRow::BeforeFirst,
8715 installed => ActiveRow::Row(installed - 1),
8716 }
8717 })
8718 }
8719
8720 pub(crate) fn pending_checkpoint(
8723 &self,
8724 ) -> Option<&crate::preemptions::callback_history::Checkpoint> {
8725 use crate::preemptions::callback_history::ActiveRow;
8726 let ActiveRow::Row(row) = self.active_priority_row()? else {
8727 return None;
8728 };
8729 if !self.callback_history.annotates(row) {
8730 return None;
8731 }
8732 self.callback_history.remaining().next()
8733 }
8734
8735 pub(crate) fn pmu_epsilon(&self, cfg: &Config) -> Duration {
8739 Duration::from_nanos((NANOS_PER_RCB * self.clock_multiplier(cfg)).ceil() as u64)
8740 }
8741
8742 pub(crate) fn clock_multiplier(&self, cfg: &Config) -> f64 {
8745 cfg.clock_multiplier.unwrap_or(1.0) * self.rcb_time_multiplier().as_f64()
8746 }
8747
8748 pub(crate) fn file_replay_change_point(
8765 &mut self,
8766 cfg: &Config,
8767 time: LogicalTime,
8768 ) -> Result<(), LogicalTime> {
8769 if cfg.replay_preemptions_from.is_none() || cfg.replay_edited_preemptions {
8770 return Ok(());
8771 }
8772 if let Some(last) = self.last_replay_change_point.filter(|last| time <= *last) {
8773 return Err(last);
8774 }
8775 self.last_replay_change_point = Some(time);
8776 Ok(())
8777 }
8778
8779 pub(crate) fn abandon_replayed_slices(&mut self, cfg: &Config) {
8785 self.stats.last_recorded_slice = Some(self.stats.timeslice_count);
8786 self.end_of_timeslice = Some(LogicalTime::MAX);
8787 self.replay_rcb_end = None;
8788 let now = self.thread_logical_time.as_nanos();
8789 self.max_timeslice_end = cfg
8790 .max_timeslice
8791 .map(|max_timeslice| now + Duration::from_nanos(u64::from(max_timeslice)));
8792 self.last_rcb_timer = None;
8793 self.last_rcb_timer_is_max = false;
8794 }
8795
8796 pub(crate) fn timeslice_expired(&self) -> bool {
8798 if let Some(replay_rcb_end) = self.replay_rcb_end {
8799 return self.committed_clock_value >= replay_rcb_end;
8800 }
8801 let current_time = self.thread_logical_time.as_nanos();
8802 self.end_of_timeslice
8803 .is_some_and(|end_of_timeslice| current_time >= end_of_timeslice)
8804 }
8805
8806 pub(crate) fn rcb_time_multiplier(&self) -> RcbTimeMultiplier {
8810 if self.chaos_slowdown_active {
8811 self.chaos_slowdown_factor
8812 } else {
8813 RcbTimeMultiplier::ONE
8814 }
8815 }
8816
8817 pub(crate) fn take_pending_chaos_epochs(&mut self) -> Vec<ChaosEpochTransition> {
8820 std::mem::take(&mut self.pending_chaos_epochs)
8821 }
8822
8823 fn install_chaos_epoch(&mut self, transition: ChaosEpochTransition, record: bool) {
8826 self.chaos_epoch = transition.epoch;
8827 self.chaos_slowdown_factor = transition.factor;
8828 self.chaos_slowdown_active = true;
8829 if record {
8830 self.pending_chaos_epochs.push(transition);
8831 }
8832 detlog!(
8833 "[dtid {}] CHAOSEPOCH => epoch = {}, factor = {}, logical_time = {}",
8834 self.dettid,
8835 transition.epoch,
8836 transition.factor.as_f64(),
8837 transition.logical_time
8838 );
8839 }
8840
8841 pub fn next_timeslice(&mut self, cfg: &Config) -> Option<Priority> {
8851 let logical_timeslice = cfg.target_timeslice.or(cfg.max_timeslice);
8852 if let Some(timeout_ns) = logical_timeslice {
8853 let current_ns = self.thread_logical_time.as_nanos();
8854
8855 let replay_has_epochs = self
8861 .preemption_points
8862 .as_ref()
8863 .is_some_and(ThreadHistoryIterator::has_chaos_epochs);
8864 if replay_has_epochs {
8865 let transition_time = self.end_of_timeslice.unwrap_or(current_ns);
8872 let transition = self
8873 .preemption_points
8874 .as_mut()
8875 .and_then(|history| history.advance_chaos_epoch(transition_time));
8876 if let Some(transition) = transition {
8877 self.install_chaos_epoch(transition, false);
8878 }
8879 } else if cfg.chaos && cfg.chaos_per_thread_slowdown {
8880 let elapsed_ns = self.thread_logical_time.without_starting().as_nanos();
8881 let epoch = elapsed_ns
8882 .checked_div(cfg.chaos_epoch_length_ns)
8883 .unwrap_or(0);
8884 let factor = chaos_per_thread_slowdown_factor(
8885 cfg.sched_seed(),
8886 self.dettid,
8887 epoch,
8888 cfg.chaos_slowdown_max_factor,
8889 );
8890 if epoch != self.chaos_epoch {
8891 self.install_chaos_epoch(
8892 ChaosEpochTransition {
8893 logical_time: current_ns,
8894 epoch,
8895 factor,
8896 },
8897 true,
8898 );
8899 } else {
8900 self.chaos_slowdown_factor = factor;
8901 self.chaos_slowdown_active = true;
8902 }
8903 } else {
8904 self.chaos_slowdown_factor = RcbTimeMultiplier::ONE;
8905 self.chaos_slowdown_active = false;
8906 self.pending_chaos_epochs.clear();
8907 }
8908
8909 let mut result = None;
8910 self.replay_rcb_end = None;
8911 let replay_controls_deadline =
8912 self.preemption_points.is_some() || self.follows_replayed_schedule(cfg);
8913
8914 let margin = if cfg.max_timeslice.is_some() {
8920 self.pmu_epsilon(cfg)
8921 } else {
8922 Duration::from_nanos(1)
8923 };
8924 if let Some(thi) = &mut self.preemption_points {
8926 if self.stats.last_recorded_slice.is_none() {
8927 let current_rcbs = self.committed_clock_value;
8929 let next = if cfg.replay_edited_preemptions {
8938 let (next, skipped) =
8939 next_edited_slice(thi, current_ns, current_rcbs, margin);
8940 if skipped > 0 {
8941 debug!(
8942 "[dtid {}] edited record: skipped {} recorded slices that ended before {} (RCB {})",
8943 self.dettid, skipped, current_ns, current_rcbs
8944 );
8945 }
8946 next
8947 } else {
8948 thi.next_with_rcbs()
8949 };
8950 if let Some((end_time, prio, replay_rcb_end)) = next {
8951 debug!(
8952 "[dtid {}] next timeslice (T{}), set by recording to {:?} (current {}), priority {}",
8953 self.dettid,
8954 self.stats.timeslice_count + 1,
8955 end_time,
8956 current_ns,
8957 prio
8958 );
8959 if let Some(target_rcbs) = replay_rcb_end
8960 && target_rcbs < current_rcbs
8961 {
8962 panic!(
8963 "Cannot set RCB end of timeslice to {} for thread {}, when current RCB count is already {}. \
8964 An edited record, not the recording of this run's schedule, needs \
8965 --replay-edited-preemptions.",
8966 target_rcbs, self.dettid, current_rcbs
8967 )
8968 }
8969 let exact_deadline_is_now =
8970 replay_rcb_end.is_some_and(|target| target == current_rcbs);
8971 let rcb_target_governs =
8974 cfg.replay_edited_preemptions && replay_rcb_end.is_some();
8975 if end_time <= current_ns && !exact_deadline_is_now && !rcb_target_governs {
8976 panic!(
8983 "Cannot set end of timeslice to {} for thread {}, when current thread logical time is already {}. \
8984 The replayed preemption record's timeslice ends are absolute virtual times; if it was \
8985 recorded under a different virtual-time epoch, replay it with that --epoch \
8986 (https://github.com/rrnewton/hermit/issues/3413). An edited record, not the \
8987 recording of this run's schedule, needs --replay-edited-preemptions.",
8988 end_time, self.dettid, current_ns
8989 )
8990 }
8991 self.end_of_timeslice = Some(end_time);
8992 self.replay_rcb_end = replay_rcb_end;
8993 result = Some(prio);
8994 } else {
8995 let max = LogicalTime::MAX;
8996 let prio = thi.final_priority();
8997 debug!(
8998 "[dtid {}] next timeslice (T{}) final slice after recorded preemption points... setting end_of_timeslice to max {}, final priority {}",
8999 self.dettid,
9000 self.stats.timeslice_count + 1,
9001 max,
9002 prio
9003 );
9004 self.stats.last_recorded_slice = Some(self.stats.timeslice_count);
9005 self.end_of_timeslice = Some(max);
9006 result = Some(prio)
9007 }
9008 } else {
9009 tracing::warn!(
9010 "[dtid {}] next timeslice: timer expired beyond the last recorded preemption. Not handled yet.",
9011 self.dettid
9012 );
9013 self.end_of_timeslice = Some(LogicalTime::MAX);
9014 result = Some(thi.final_priority())
9015 }
9016 } else if !cfg.chaos {
9017 if self.follows_replayed_schedule(cfg) {
9018 if cfg.no_rcb_time {
9019 let max_timeslice = cfg
9020 .max_timeslice
9021 .expect("schedule replay with PMU requires a maximum");
9022 self.end_of_timeslice =
9023 Some(current_ns + Duration::from_nanos(u64::from(max_timeslice)));
9024 } else {
9025 debug!(
9027 "[dtid {}] next timeslice (T{}), in replay mode setting timeslice to max (current time {})",
9028 self.dettid,
9029 self.stats.timeslice_count + 1,
9030 current_ns
9031 );
9032 self.end_of_timeslice = Some(LogicalTime::MAX);
9033 }
9034 } else {
9035 self.end_of_timeslice =
9039 Some(current_ns + Duration::from_nanos(u64::from(timeout_ns)));
9040 debug!(
9041 "[dtid {}] next timeslice (T{}), end of slice set to {} (current {})",
9042 self.dettid,
9043 self.stats.timeslice_count + 1,
9044 self.end_of_timeslice.unwrap(),
9045 current_ns,
9046 );
9047 }
9048 } else {
9049 let slowdown = self.rcb_time_multiplier().as_f64();
9055 let nanos_per_rcb = NANOS_PER_RCB * cfg.clock_multiplier.unwrap_or(1.0) * slowdown;
9056 let target_timeout_rcbs = u64::from(timeout_ns) as f64 / nanos_per_rcb;
9057 if self.chaos_slowdown_active {
9058 detlog!(
9059 "[dtid {}] CHAOSSLOWDOWN => factor = {}, virtual ns/rcb = {}",
9060 self.dettid,
9061 slowdown,
9062 nanos_per_rcb
9063 );
9064 }
9065 let next_rcbs: u64 = if cfg.chaos {
9066 let target_timeout_rcbs = match cfg.chaos_timeslice_range {
9074 Some(range)
9075 if !replay_controls_deadline && cfg.chaos_timeslice.is_none() =>
9076 {
9077 range.log_uniform(self.chaos_prng.random::<f64>()) / nanos_per_rcb
9078 }
9079 _ => target_timeout_rcbs,
9080 };
9081 let lambda = 1.0 / target_timeout_rcbs;
9083 let exp = Exp::new(lambda).unwrap();
9084 let rcbs = 1 + exp.sample(&mut self.chaos_prng) as u64;
9086 detlog!("[dtid {}] CHAOSRAND => next_rcbs = {}", self.dettid, rcbs);
9087 rcbs
9088 } else {
9089 target_timeout_rcbs as u64
9090 };
9091 assert!(next_rcbs > 0);
9092 self.last_rcb_timer = None;
9093 self.end_of_timeslice = Some(
9094 current_ns
9095 + Duration::from_nanos((next_rcbs as f64 * nanos_per_rcb).ceil() as u64),
9096 );
9097 debug!(
9098 "[dtid {}] next timeslice (T{}) chosen as {} rcbs, end of slice = {} (current {})",
9099 self.dettid,
9100 self.stats.timeslice_count + 1,
9101 next_rcbs,
9102 self.end_of_timeslice.unwrap(),
9103 current_ns
9104 );
9105 }
9106
9107 let past_an_edited_record = cfg.replay_edited_preemptions
9114 && self.preemption_points.is_some()
9115 && self.end_of_timeslice == Some(LogicalTime::MAX);
9116 let configured_max_end = cfg.max_timeslice.map(|max_timeslice| {
9117 let length = Duration::from_nanos(u64::from(max_timeslice));
9118 current_ns
9119 + if past_an_edited_record {
9120 length.max(margin)
9121 } else {
9122 length
9123 }
9124 });
9125 self.max_timeslice_end = if replay_controls_deadline {
9126 if cfg.max_timeslice.is_some() {
9127 self.end_of_timeslice
9132 .filter(|end| *end != LogicalTime::MAX)
9133 .or(configured_max_end)
9134 } else {
9135 None
9136 }
9137 } else if cfg.target_timeslice.is_none() {
9138 match (self.end_of_timeslice, configured_max_end) {
9139 (Some(logical_end), Some(configured_end)) => {
9140 Some(logical_end.min(configured_end))
9141 }
9142 (_, configured_end) => configured_end,
9143 }
9144 } else {
9145 configured_max_end
9146 };
9147
9148 if let (Some(target_end), Some(max_end)) =
9149 (self.end_of_timeslice, self.max_timeslice_end)
9150 && target_end > max_end
9151 {
9152 self.end_of_timeslice = Some(max_end);
9153 }
9154
9155 self.last_rcb_timer = None;
9156 self.last_rcb_timer_is_max = false;
9157 self.reset_timeslice_stats(current_ns);
9158 result
9159 } else {
9160 self.end_of_timeslice = None;
9161 self.replay_rcb_end = None;
9162 self.max_timeslice_end = None;
9163 self.last_rcb_timer = None;
9164 self.last_rcb_timer_is_max = false;
9165 None
9166 }
9167 }
9168
9169 pub fn reset_timeslice_for_explicit_yield(&mut self) {
9173 let current_ns = self.thread_logical_time.as_nanos();
9174 self.reset_timeslice_stats(current_ns);
9175 }
9176
9177 fn reset_timeslice_stats(&mut self, current_ns: LogicalTime) {
9178 if let Some(start) = self.stats.timeslice_start_ns
9179 && current_ns >= start
9180 {
9181 self.stats
9182 .timeslice_stats
9183 .record((current_ns - start).as_nanos());
9184 }
9185 self.stats.timeslice_start_ns = Some(current_ns);
9186 self.stats.reset_timeslice();
9187 }
9188
9189 pub fn guest_past_first_execve(&self) -> bool {
9195 self.past_global_first_execve
9196 }
9197}
9198
9199fn next_edited_slice(
9214 history: &mut ThreadHistoryIterator,
9215 now: LogicalTime,
9216 rcbs: u64,
9217 margin: Duration,
9218) -> (Option<(LogicalTime, Priority, Option<u64>)>, usize) {
9219 let mut skipped = 0;
9220 loop {
9221 match history.next_with_rcbs() {
9222 None => return (None, skipped),
9223 Some((end, prio, Some(target))) if target >= rcbs => {
9224 return (Some((end, prio, Some(target))), skipped);
9225 }
9226 Some((end, prio, _)) if end >= now + margin => {
9227 return (Some((end, prio, None)), skipped);
9228 }
9229 Some(_) => skipped += 1,
9230 }
9231 }
9232}
9233
9234#[cfg(test)]
9235mod traced_call_tests {
9236 use detcore_model::access_trace::FdOp;
9237
9238 use super::*;
9239
9240 #[test]
9246 fn a_traced_call_notes_the_slots_it_changes() {
9247 let mut state = ThreadState::<()>::new(DetTid::from_raw(3), &Config::default(), ());
9248 for fd in [10, 11] {
9249 state.add_fd(fd, OFlag::O_RDWR, FdType::Pipe, None).unwrap();
9250 }
9251 let id = |state: &ThreadState<()>, fd| state.with_detfd(fd, |d| d.open_file_id()).unwrap();
9252 let (ten, eleven) = (id(&state, 10), id(&state, 11));
9253 state.remove_fd(12);
9254 assert!(
9255 state.traced_call.take().is_none(),
9256 "no traced call, no notes"
9257 );
9258
9259 state.traced_call.begin(BTreeMap::new());
9260 state.dup_fd(10, 11, OFlag::empty()).unwrap();
9261 state.remove_fd_range(10, u32::MAX);
9262 let notes = state.traced_call.take().expect("a traced call").notes;
9263 let slot = |fd| detcore_model::fd::FdSlot {
9264 files: state.files_id(),
9265 fd,
9266 };
9267 let summary: Vec<_> = notes
9268 .iter()
9269 .map(|note| {
9270 (
9271 note.slot,
9272 note.open_file,
9273 note.op.clone(),
9274 note.released.as_ref().map(|r| (r.open_file, r.ty)),
9275 )
9276 })
9277 .collect();
9278 assert_eq!(
9279 summary,
9280 vec![
9281 (
9282 slot(11),
9283 Some(eleven),
9284 FdOp::Close,
9285 Some((eleven, FdType::Pipe))
9286 ),
9287 (slot(11), Some(ten), FdOp::Dup { source: 10 }, None),
9288 (slot(10), Some(ten), FdOp::Close, Some((ten, FdType::Pipe))),
9289 (slot(11), Some(ten), FdOp::Close, Some((ten, FdType::Pipe))),
9290 ]
9291 );
9292 }
9293}
9294
9295#[cfg(test)]
9296mod timeslice_tests {
9297 use std::num::NonZeroU64;
9298
9299 use super::*;
9300 use crate::DEFAULT_PRIORITY;
9301 use crate::preemptions::ThreadHistory;
9302
9303 #[test]
9309 fn an_exit_group_walks_the_registrations_current_at_its_grant() {
9310 let owner = |tid: i32, head: usize| RobustListOwner {
9311 dettid: DetTid::from_raw(tid),
9312 head,
9313 };
9314 let mut caller = ThreadState::<()>::new(DetTid::from_raw(3), &Config::default(), ());
9315 let sibling = |tid: i32| {
9316 let mut state = caller.clone();
9317 state.dettid = DetTid::from_raw(tid);
9318 state
9319 };
9320 let (mut killed, mut exited, mut late, mut replaced, mut unregistered) =
9321 (sibling(4), sibling(5), sibling(6), sibling(7), sibling(8));
9322 caller.record_robust_list_head(Some(0x3000));
9323 killed.record_robust_list_head(Some(0x4000));
9324 exited.record_robust_list_head(Some(0x5000));
9325 replaced.record_robust_list_head(Some(0x7000));
9326 unregistered.record_robust_list_head(Some(0x8000));
9327
9328 caller.file_exit_group_for_robust_lists();
9329 exited.mark_robust_list_walked_by_tool();
9333 exited.drop_robust_list_head_at_exit();
9334 late.record_robust_list_head(Some(0x6000));
9335 replaced.record_robust_list_head(Some(0x7700));
9336 unregistered.record_robust_list_head(None);
9337 killed.drop_robust_list_head_at_exit();
9339 assert_eq!(
9340 killed.take_robust_list_wakes_after_exit(None, DetTime::default()),
9341 None
9342 );
9343
9344 assert_eq!(
9345 caller.robust_list_owners_for_exit_group(),
9346 vec![
9347 owner(3, 0x3000),
9348 owner(4, 0x4000),
9349 owner(6, 0x6000),
9350 owner(7, 0x7700)
9351 ]
9352 );
9353 }
9354
9355 #[test]
9360 fn a_killed_thread_waits_for_the_exit_group_walk() {
9361 use std::sync::atomic::AtomicBool;
9362 use std::sync::atomic::Ordering;
9363 const BOUND: std::time::Duration = EXIT_GROUP_ROBUST_WALK_WAIT;
9364
9365 for granted_first_filer in [true, false] {
9366 let first = ThreadState::<()>::new(DetTid::from_raw(3), &Config::default(), ());
9367 let mut second = first.clone();
9368 second.dettid = DetTid::from_raw(4);
9369 assert!(second.wait_for_exit_group_robust_walk(BOUND));
9371
9372 first.file_exit_group_for_robust_lists();
9373 second.file_exit_group_for_robust_lists();
9374 let (walker, killed) = if granted_first_filer {
9375 (first, second)
9376 } else {
9377 (second, first)
9378 };
9379 walker.begin_exit_group_robust_walk();
9380 assert!(walker.wait_for_exit_group_robust_walk(BOUND));
9381
9382 let released = Arc::new(AtomicBool::new(false));
9383 let entered = Arc::new(AtomicBool::new(false));
9384 let waiter = {
9385 let released = Arc::clone(&released);
9386 let entered = Arc::clone(&entered);
9387 std::thread::spawn(move || {
9388 killed.cancel_exit_group_filing();
9389 entered.store(true, Ordering::SeqCst);
9390 let finished = killed.wait_for_exit_group_robust_walk(BOUND);
9391 (finished, released.load(Ordering::SeqCst))
9392 })
9393 };
9394 while !entered.load(Ordering::SeqCst) {
9398 std::thread::yield_now();
9399 }
9400 std::thread::sleep(std::time::Duration::from_millis(50));
9401 assert!(
9402 !waiter.is_finished(),
9403 "the killed filer did not wait for the walk"
9404 );
9405 released.store(true, Ordering::SeqCst);
9406 walker.finish_exit_group_robust_walk();
9407 assert_eq!(
9408 waiter.join().unwrap(),
9409 (true, true),
9410 "the killed filer went on before the walk finished (first filer granted: {granted_first_filer})"
9411 );
9412 }
9413 }
9414
9415 #[test]
9418 fn an_unfinished_walk_is_never_released_by_the_bound() {
9419 let walker = ThreadState::<()>::new(DetTid::from_raw(3), &Config::default(), ());
9420 let mut killed = walker.clone();
9421 killed.dettid = DetTid::from_raw(4);
9422 walker.file_exit_group_for_robust_lists();
9423 walker.begin_exit_group_robust_walk();
9424 killed.cancel_exit_group_filing();
9425 assert!(!killed.wait_for_exit_group_robust_walk(std::time::Duration::from_millis(20)));
9426 }
9427
9428 #[test]
9432 fn a_wait_ends_when_every_filing_is_cancelled() {
9433 let filer = ThreadState::<()>::new(DetTid::from_raw(3), &Config::default(), ());
9434 let mut other = filer.clone();
9435 other.dettid = DetTid::from_raw(4);
9436 filer.file_exit_group_for_robust_lists();
9437 other.cancel_exit_group_filing();
9438 assert!(!other.wait_for_exit_group_robust_walk(std::time::Duration::from_millis(20)));
9439 filer.cancel_exit_group_filing();
9440 assert!(other.wait_for_exit_group_robust_walk(std::time::Duration::from_millis(20)));
9441 }
9442
9443 #[test]
9445 fn an_exit_drops_its_head_when_no_exit_group_is_filed() {
9446 let mut state = ThreadState::<()>::new(DetTid::from_raw(4), &Config::default(), ());
9447 state.record_robust_list_head(Some(0x4000));
9448 state.drop_robust_list_head_at_exit();
9449 assert!(state.robust_list_heads().is_empty());
9450 assert_eq!(state.robust_list_head, None);
9451 }
9452
9453 #[test]
9459 fn a_successful_exec_clears_the_robust_list_head() {
9460 let mut state = ThreadState::<()>::new(DetTid::from_raw(3), &Config::default(), ());
9461 assert_eq!(state.robust_list_head, None, "a fresh thread has no list");
9462
9463 state.record_robust_list_head(Some(0x7ffff7bff920));
9464 assert_eq!(
9465 state.take_robust_list_for_exec(),
9466 Some(0x7ffff7bff920),
9467 "the previous head is handed back for the failure path"
9468 );
9469 assert_eq!(
9470 state.robust_list_head, None,
9471 "the candidate exec image starts with no robust list, as after copy_process"
9472 );
9473 assert!(
9474 state.robust_list_heads().is_empty(),
9475 "the old address-space registration must also leave the shared index"
9476 );
9477 }
9478
9479 #[test]
9482 fn a_failed_exec_restores_the_robust_list_head() {
9483 let mut state = ThreadState::<()>::new(DetTid::from_raw(3), &Config::default(), ());
9484 state.record_robust_list_head(Some(0x404100));
9485
9486 let saved = state.take_robust_list_for_exec();
9487 state.restore_robust_list_after_failed_exec(saved);
9488
9489 assert_eq!(state.robust_list_head, Some(0x404100));
9490 assert_eq!(
9491 state.robust_list_heads(),
9492 vec![(DetTid::from_raw(3), 0x404100)],
9493 "a failed exec keeps the old address-space registration indexed"
9494 );
9495 }
9496
9497 fn two_owner_robust_list_state() -> (
9498 ThreadState<()>,
9499 ThreadState<()>,
9500 RobustListWake,
9501 RobustListWake,
9502 RobustListWake,
9503 ) {
9504 let first_owner = DetTid::from_raw(3);
9505 let second_owner = DetTid::from_raw(4);
9506 let mm = MmId::initial(first_owner);
9507 let lower = RobustListWake {
9508 futex: FutexID::private(mm, 0x4040),
9509 };
9510 let middle = RobustListWake {
9511 futex: FutexID::private(mm, 0x5050),
9512 };
9513 let higher = RobustListWake {
9514 futex: FutexID::private(mm, 0x6060),
9515 };
9516 let mut first = ThreadState::<()>::new(first_owner, &Config::default(), ());
9517 let mut second = first.clone();
9518 second.dettid = second_owner;
9519 first.record_robust_list_head(Some(0x404100));
9520 second.record_robust_list_head(Some(0x404200));
9521 first.stage_robust_list_wakes(
9522 RobustListExit::Signal(libc::SIGTERM),
9523 vec![
9524 (second_owner, vec![higher]),
9525 (first_owner, vec![middle, lower]),
9526 ],
9527 );
9528 (first, second, lower, middle, higher)
9529 }
9530
9531 #[test]
9532 fn group_robust_list_wakes_wait_for_every_owner_and_sort_the_request() {
9533 let (first, second, lower, middle, higher) = two_owner_robust_list_state();
9534 let mut first_time = DetTime::default();
9535 first_time.add_syscall();
9536 let mut second_time = first_time.clone();
9537 second_time.add_syscall();
9538
9539 assert_eq!(
9540 second.take_robust_list_wakes_after_exit(Some(libc::SIGTERM), second_time.clone()),
9541 None,
9542 "the first physical exit must not release part of the group"
9543 );
9544 assert_eq!(
9545 first.take_robust_list_wakes_after_exit(Some(libc::SIGTERM), first_time),
9546 Some((
9547 second_time,
9548 vec![
9549 (DetTid::from_raw(3), lower),
9550 (DetTid::from_raw(3), middle),
9551 (DetTid::from_raw(4), higher),
9552 ],
9553 )),
9554 "the final matching exit releases one owner/futex-sorted request"
9555 );
9556 }
9557
9558 #[test]
9559 fn group_robust_list_wake_request_is_independent_of_exit_arrival_order() {
9560 let release = |reverse: bool| {
9561 let (first, second, _, _, _) = two_owner_robust_list_state();
9562 let first_time = DetTime::default();
9563 let mut second_time = first_time.clone();
9564 second_time.add_syscall();
9565 if reverse {
9566 assert_eq!(
9567 second.take_robust_list_wakes_after_exit(
9568 Some(libc::SIGTERM),
9569 second_time.clone(),
9570 ),
9571 None
9572 );
9573 first.take_robust_list_wakes_after_exit(Some(libc::SIGTERM), first_time)
9574 } else {
9575 assert_eq!(
9576 first.take_robust_list_wakes_after_exit(Some(libc::SIGTERM), first_time),
9577 None
9578 );
9579 second.take_robust_list_wakes_after_exit(Some(libc::SIGTERM), second_time)
9580 }
9581 };
9582
9583 assert_eq!(release(false), release(true));
9584 }
9585
9586 #[test]
9587 fn mismatched_or_nonfatal_exit_does_not_release_group_robust_list_wakes() {
9588 let (first, second, _, _, _) = two_owner_robust_list_state();
9589 assert_eq!(
9590 first.take_robust_list_wakes_after_exit(Some(libc::SIGKILL), DetTime::default()),
9591 None
9592 );
9593 assert_eq!(
9594 second.take_robust_list_wakes_after_exit(Some(libc::SIGTERM), DetTime::default()),
9595 None,
9596 "one matching owner cannot release a group whose peer exited for another reason"
9597 );
9598
9599 let (first, second, _, _, _) = two_owner_robust_list_state();
9600 assert_eq!(
9601 first.take_robust_list_wakes_after_exit(None, DetTime::default()),
9602 None
9603 );
9604 assert_eq!(
9605 second.take_robust_list_wakes_after_exit(Some(libc::SIGTERM), DetTime::default()),
9606 None,
9607 "a normal exit must not satisfy a staged fatal-signal exit"
9608 );
9609 }
9610
9611 #[test]
9612 fn regular_file_opens_do_not_shift_socket_cookie_identity() {
9613 let owner = DetTid::from_raw(3);
9614 let mut files = FileMetadata::new(owner);
9615 let first = files.allocate_open_file_id(owner, FdType::Socket);
9616 for _ in 0..4 {
9617 files.allocate_open_file_id(owner, FdType::Regular);
9618 }
9619 let second = files.allocate_open_file_id(owner, FdType::Socket);
9620
9621 assert_eq!(first.deterministic_socket_cookie(), 3_u64 << 32);
9622 assert_eq!(second.deterministic_socket_cookie(), (3_u64 << 32) | 1);
9623 }
9624
9625 #[test]
9626 fn guest_clock_tracks_raw_logical_time_without_lag() {
9627 let epoch = LogicalTime::from_secs(1_000);
9628 let mut clock = GuestClock::default();
9629
9630 assert_eq!(
9631 clock.observe(epoch + Duration::from_nanos(41_000_000)),
9632 epoch + Duration::from_nanos(41_000_000)
9633 );
9634 assert_eq!(
9635 clock.observe(epoch + Duration::from_nanos(41_025_000)),
9636 epoch + Duration::from_nanos(41_025_000)
9637 );
9638 assert_eq!(
9640 clock.observe(epoch + Duration::from_nanos(41_010_000)),
9641 epoch + Duration::from_nanos(41_025_000)
9642 );
9643 }
9644
9645 #[test]
9646 fn guest_clock_absolute_deadline_stays_ahead_of_committed_time() {
9647 let committed_time = LogicalTime::from_secs(1_000) + Duration::from_millis(250);
9648 let mut clock = GuestClock::default();
9649 let guest_now = clock.observe(committed_time);
9650 let deadline = guest_now + Duration::from_millis(100);
9651
9652 assert_eq!(guest_now, committed_time);
9653 assert!(deadline > committed_time);
9654 }
9655
9656 #[test]
9657 fn guest_clock_process_tree_shares_one_monotonic_domain() {
9658 let epoch = LogicalTime::from_secs(1_000);
9659 let root = Arc::new(Mutex::new(GuestClock::default()));
9660 let forked_child = Arc::clone(&root);
9661
9662 assert!(Arc::ptr_eq(&root, &forked_child));
9663 assert_eq!(
9664 root.lock().unwrap().observe(epoch + Duration::from_secs(1)),
9665 epoch + Duration::from_secs(1)
9666 );
9667 assert_eq!(
9668 forked_child
9669 .lock()
9670 .unwrap()
9671 .observe(epoch + Duration::from_secs(2)),
9672 epoch + Duration::from_secs(2)
9673 );
9674
9675 let execed_child = Arc::clone(&forked_child);
9677 assert!(Arc::ptr_eq(&root, &execed_child));
9678 assert_eq!(
9679 execed_child
9680 .lock()
9681 .unwrap()
9682 .observe(epoch + Duration::from_secs(9)),
9683 epoch + Duration::from_secs(9)
9684 );
9685 }
9686
9687 #[test]
9688 fn initial_current_image_is_selected_once_before_registration() {
9689 let tid = DetTid::from_raw(3);
9690 let mut state = ThreadState::new(tid, &Config::default(), ());
9691 assert_eq!(state.mm_id, MmId::initial(tid)); state.physical_tid = Some(tid.as_raw());
9693 let physical_tid = state.physical_tid;
9694 state.bind_initial_current_image(tid).unwrap();
9695 let current = MmId::initial(tid).for_exec(tid);
9696 assert_eq!(state.mm_id, current);
9697 assert_eq!(state.physical_tid, physical_tid);
9698 assert!(!state.thread_start_entered);
9699 assert!(state.detpid.is_none());
9700 assert_eq!(state.bind_initial_current_image(tid), Err(Errno::EPROTO));
9701 assert_eq!(state.mm_id, current);
9702 }
9703
9704 #[test]
9705 fn initial_current_image_rejects_nonfresh_or_other_thread_state() {
9706 let tid = DetTid::from_raw(3);
9707 for case in 0..7 {
9708 let mut state = ThreadState::new(tid, &Config::default(), ());
9709 let requested = if case == 0 { DetTid::from_raw(4) } else { tid };
9710 match case {
9711 1 => state.detpid = Some(tid),
9712 2 => state.thread_start_entered = true,
9713 3 => state.past_global_first_execve = true,
9714 4 => state.mm_id = state.mm_id.for_exec(tid),
9715 5 => state.clone_flags = Some(CloneFlags::empty()),
9716 6 => state.mm_id = MmId::initial(DetTid::from_raw(4)),
9717 _ => {}
9718 }
9719 let before = state.mm_id;
9720 assert_eq!(
9721 state.bind_initial_current_image(requested),
9722 Err(Errno::EPROTO)
9723 );
9724 assert_eq!(state.mm_id, before);
9725 }
9726 }
9727
9728 #[test]
9729 fn unparented_thread_recovers_process_memory_identity() {
9730 let detpid = DetPid::from_raw(4);
9731 let dettid = DetTid::from_raw(7);
9732 let mut state = ThreadState::new(dettid, &Config::default(), ());
9733
9734 assert!(state.recover_process_mm_id(detpid));
9735 assert_eq!(state.mm_id, MmId::initial(detpid));
9736 assert!(!state.recover_process_mm_id(detpid));
9737 }
9738
9739 #[test]
9740 fn inherited_thread_keeps_existing_memory_identity() {
9741 let detpid = DetPid::from_raw(4);
9742 let dettid = DetTid::from_raw(7);
9743 let inherited_mm = MmId::initial(detpid).for_exec(detpid);
9744 let mut state = ThreadState::new(dettid, &Config::default(), ());
9745 state.mm_id = inherited_mm;
9746
9747 assert!(!state.recover_process_mm_id(detpid));
9748 assert_eq!(state.mm_id, inherited_mm);
9749 }
9750
9751 #[test]
9752 fn backend_can_override_open_file_creator_identity() {
9753 let host_tid = DetTid::from_raw(10_003);
9754 let virtual_tid = DetTid::from_raw(3);
9755 let mut state = ThreadState::new(host_tid, &Config::default(), ());
9756
9757 assert_eq!(state.open_file_creator, None);
9758 state.set_open_file_creator(virtual_tid);
9759 assert_eq!(state.open_file_creator, Some(virtual_tid));
9760 }
9761
9762 #[test]
9765 fn chaos_per_thread_slowdown_factor_is_stable_and_deterministic() {
9766 let seed = 0xdead_beef_u64;
9767 let max_factor: f64 = 10.0;
9768 for raw in 1..=64 {
9770 let tid = DetTid::from_raw(raw);
9771 let a = chaos_per_thread_slowdown_factor(seed, tid, 0, max_factor);
9772 let b = chaos_per_thread_slowdown_factor(seed, tid, 0, max_factor);
9773 assert_eq!(
9774 a, b,
9775 "factor must be a pure function of (seed, dettid, epoch)"
9776 );
9777 let a = a.as_f64();
9779 assert!(
9780 a >= 1.0 / max_factor - 1e-9 && a <= max_factor + 1e-9,
9781 "factor {} out of [1/{max_factor}, {max_factor}] for tid {raw}",
9782 a
9783 );
9784 }
9785 }
9786
9787 #[test]
9790 fn chaos_per_thread_slowdown_factor_varies_across_threads_and_seeds() {
9791 let max_factor = 10.0;
9792 let factors: Vec<f64> = (1..=32)
9794 .map(|raw| {
9795 chaos_per_thread_slowdown_factor(1234, DetTid::from_raw(raw), 0, max_factor)
9796 .as_f64()
9797 })
9798 .collect();
9799 let first = factors[0];
9800 assert!(
9801 factors.iter().any(|&f| (f - first).abs() > 1e-6),
9802 "per-thread factors should differ across threads"
9803 );
9804 let tid = DetTid::from_raw(7);
9806 let f_a = chaos_per_thread_slowdown_factor(1, tid, 0, max_factor).as_f64();
9807 let f_b = chaos_per_thread_slowdown_factor(2, tid, 0, max_factor).as_f64();
9808 assert!(
9809 (f_a - f_b).abs() > 1e-12,
9810 "different seeds should yield different factors for the same thread"
9811 );
9812 }
9813
9814 #[test]
9817 fn chaos_per_thread_slowdown_factor_disabled_when_max_factor_at_most_one() {
9818 for raw in 1..=16 {
9820 let tid = DetTid::from_raw(raw);
9821 assert_eq!(
9822 chaos_per_thread_slowdown_factor(99, tid, 0, 1.0),
9823 RcbTimeMultiplier::ONE
9824 );
9825 assert_eq!(
9826 chaos_per_thread_slowdown_factor(99, tid, 0, 0.5),
9827 RcbTimeMultiplier::ONE
9828 );
9829 }
9830 }
9831
9832 #[test]
9835 fn chaos_epoch_zero_reproduces_epochless_factor() {
9836 use rand::RngExt as _;
9840 use rand::SeedableRng as _;
9841 let max_factor: f64 = 10.0;
9842 for raw in 1..=64 {
9843 let tid = DetTid::from_raw(raw);
9844 for &seed in &[0u64, 1, 7, 0xdead_beef, u64::MAX] {
9845 let epochless = {
9846 const SLOWDOWN_SALT: u64 = 0x736c_6f77_646f_776e;
9849 let mixed = seed
9850 ^ SLOWDOWN_SALT
9851 ^ ((tid.as_raw() as u32 as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15));
9852 let mut prng = Pcg64Mcg::seed_from_u64(mixed);
9853 let u: f64 = prng.random::<f64>();
9854 max_factor.powf(2.0 * u - 1.0)
9855 };
9856 assert_eq!(
9857 chaos_per_thread_slowdown_factor(seed, tid, 0, max_factor),
9858 RcbTimeMultiplier::from_f64(epochless),
9859 "epoch 0 must reproduce the epoch-less factor for seed {seed}, tid {raw}"
9860 );
9861 }
9862 }
9863 }
9864
9865 #[test]
9868 fn chaos_epoch_factor_varies_deterministically_across_epochs() {
9869 let max_factor = 10.0;
9870 let seed = 0x1234_5678_u64;
9871 let tid = DetTid::from_raw(3);
9872 let factors: Vec<f64> = (0..16)
9875 .map(|epoch| chaos_per_thread_slowdown_factor(seed, tid, epoch, max_factor).as_f64())
9876 .collect();
9877 for (epoch, &f) in factors.iter().enumerate() {
9879 assert_eq!(
9880 chaos_per_thread_slowdown_factor(seed, tid, epoch as u64, max_factor).as_f64(),
9881 f,
9882 "factor must be pure in epoch"
9883 );
9884 assert!(f >= 1.0 / max_factor - 1e-9 && f <= max_factor + 1e-9);
9886 }
9887 let first = factors[0];
9889 assert!(
9890 factors.iter().any(|&f| (f - first).abs() > 1e-6),
9891 "per-epoch factors should differ across epochs"
9892 );
9893 }
9894
9895 fn cpu_snapshot(
9896 user: u64,
9897 system: u64,
9898 children_user: u64,
9899 children_system: u64,
9900 ) -> ProcessCpuSnapshot {
9901 ProcessCpuSnapshot {
9902 user: LogicalTime::from_nanos(user),
9903 system: LogicalTime::from_nanos(system),
9904 children_user: LogicalTime::from_nanos(children_user),
9905 children_system: LogicalTime::from_nanos(children_system),
9906 }
9907 }
9908
9909 #[test]
9910 fn child_cpu_time_is_hidden_until_reap() {
9911 let pid = DetPid::from_raw(2);
9912 let mut parent = ProcessCpuTime::default();
9913 parent.record_exited_child(pid, cpu_snapshot(10, 20, 3, 4));
9914
9915 assert_eq!(parent.snapshot.children_user, LogicalTime::ZERO);
9916 assert_eq!(parent.snapshot.children_system, LogicalTime::ZERO);
9917
9918 parent.reap_child(pid);
9919 assert_eq!(parent.snapshot.children_user, LogicalTime::from_nanos(13));
9920 assert_eq!(parent.snapshot.children_system, LogicalTime::from_nanos(24));
9921 }
9922
9923 #[test]
9924 fn reaping_nonexited_child_does_not_change_accounting() {
9925 let pid = DetPid::from_raw(2);
9926 let mut parent = ProcessCpuTime::default();
9927
9928 parent.reap_child(pid);
9929 assert_eq!(parent.snapshot.children_user, LogicalTime::ZERO);
9930 assert_eq!(parent.snapshot.children_system, LogicalTime::ZERO);
9931 }
9932
9933 #[test]
9934 fn child_cpu_time_uses_final_thread_snapshot_and_drops_reaped_state() {
9935 let pid = DetPid::from_raw(2);
9936 let mut parent = ProcessCpuTime::default();
9937
9938 parent.record_exited_child(pid, cpu_snapshot(10, 20, 3, 4));
9939 parent.record_exited_child(pid, cpu_snapshot(12, 25, 4, 5));
9940
9941 parent.reap_child(pid);
9942 assert_eq!(parent.snapshot.children_user, LogicalTime::from_nanos(16));
9943 assert_eq!(parent.snapshot.children_system, LogicalTime::from_nanos(30));
9944 assert!(parent.exited_children.is_empty());
9945
9946 parent.reap_child(pid);
9947 assert_eq!(parent.snapshot.children_user, LogicalTime::from_nanos(16));
9948 assert_eq!(parent.snapshot.children_system, LogicalTime::from_nanos(30));
9949 }
9950
9951 #[tokio::test]
9952 async fn child_cpu_publication_replacement_rejects_stale_owner_and_snapshot() {
9953 use std::future::Future;
9954 use std::task::Context;
9955
9956 let cfg = Config::default();
9957 let child = DetPid::from_raw(7);
9958 let mut parent = ThreadState::new(DetPid::from_raw(3), &cfg, ());
9959 assert!(!parent.wait_for_child_cpu_publication(child).await);
9960 let before = serde_json::to_value(&*parent.process_cpu_time.lock().unwrap()).unwrap();
9961 let first = parent.prepare_child_cpu_publication(child);
9962 let mut old_child = ThreadState::new(child, &cfg, ());
9963 old_child.parent_process_cpu_time = Some(Arc::clone(&parent.process_cpu_time));
9964 old_child.parent_cpu_publication = Some(Arc::clone(&first));
9965 let mut old_wait = Box::pin(parent.wait_for_child_cpu_publication(child));
9966 let mut cx = Context::from_waker(futures::task::noop_waker_ref());
9967 assert!(old_wait.as_mut().poll(&mut cx).is_pending());
9968 let second = parent.prepare_child_cpu_publication(child);
9969 assert!(!Arc::ptr_eq(&first, &second));
9970 assert!(!old_wait.await);
9971 assert!(!old_child.record_exited_child_process_cpu_time(child));
9972 assert!(!parent.has_exited_child_process_cpu_time(child));
9973 {
9975 let cpu = parent.process_cpu_time.lock().unwrap();
9976 assert_eq!(serde_json::to_value(&*cpu).unwrap(), before);
9977 let clone = cpu.clone();
9978 assert!(Arc::ptr_eq(
9979 clone.child_cpu_publications.get(&child).unwrap(),
9980 &second
9981 ));
9982 let restored: ProcessCpuTime = serde_json::from_value(before).unwrap();
9983 assert!(restored.child_cpu_publications.is_empty());
9984 }
9985
9986 let mut current_child = ThreadState::new(child, &cfg, ());
9987 current_child.parent_process_cpu_time = Some(Arc::clone(&parent.process_cpu_time));
9988 current_child.parent_cpu_publication = Some(Arc::clone(&second));
9989 assert!(current_child.record_exited_child_process_cpu_time(child));
9990 parent.prepare_child_process_cpu_time(child);
9991 assert!(parent.has_exited_child_process_cpu_time(child));
9992 assert!(parent.wait_for_child_cpu_publication(child).await);
9993 assert!(Arc::ptr_eq(
9995 current_child
9996 .clone()
9997 .parent_cpu_publication
9998 .as_ref()
9999 .unwrap(),
10000 &second
10001 ));
10002 parent.reap_child_process_cpu_time(child);
10003 assert!(!parent.wait_for_child_cpu_publication(child).await);
10004 assert!(!current_child.record_exited_child_process_cpu_time(child));
10005 assert!(!parent.has_exited_child_process_cpu_time(child));
10006 }
10007
10008 fn nz(value: u64) -> Option<NonZeroU64> {
10009 NonZeroU64::new(value)
10010 }
10011
10012 #[test]
10016 fn a_deadline_cap_lowers_the_slice_end_and_keeps_one_rcb() {
10017 let config = Config {
10018 max_timeslice: nz(200_000_000),
10019 ..Default::default()
10020 };
10021 let mut state = ThreadState::new(DetPid::from_raw(3), &config, ());
10022 state.next_timeslice(&config);
10023 let now = state.thread_logical_time.as_nanos();
10024 let end = state.end_of_timeslice.unwrap();
10025 assert_eq!(end, now + Duration::from_nanos(200_000_000));
10026 assert_eq!(state.max_timeslice_end, Some(end));
10027
10028 state.cap_timeslice_at_deadline(
10029 Duration::from_nanos(5_000_000),
10030 1.0,
10031 Duration::ZERO,
10032 false,
10033 );
10034 let capped = now + Duration::from_nanos(5_000_000);
10035 assert_eq!(state.end_of_timeslice, Some(capped));
10036 assert_eq!(state.max_timeslice_end, Some(end));
10037
10038 state.cap_timeslice_at_deadline(
10040 Duration::from_nanos(9_000_000),
10041 1.0,
10042 Duration::ZERO,
10043 false,
10044 );
10045 assert_eq!(state.end_of_timeslice, Some(capped));
10046
10047 state.cap_timeslice_at_deadline(Duration::ZERO, 1.0, Duration::ZERO, false);
10049 assert_eq!(state.end_of_timeslice, Some(now + Duration::from_nanos(11)));
10050 assert_eq!(state.max_timeslice_end, Some(end));
10051 }
10052
10053 #[test]
10057 fn a_deadline_cap_keeps_at_least_one_turn_charge() {
10058 let config = Config {
10059 max_timeslice: nz(200_000_000),
10060 ..Default::default()
10061 };
10062 let mut state = ThreadState::new(DetPid::from_raw(3), &config, ());
10063 state.next_timeslice(&config);
10064 let now = state.thread_logical_time.as_nanos();
10065 let turn = Duration::from_nanos(500_000);
10066 state.cap_timeslice_at_deadline(Duration::from_nanos(1_000), 1.0, turn, false);
10067 assert_eq!(state.end_of_timeslice, Some(now + turn));
10068 state.cap_timeslice_at_deadline(Duration::ZERO, 1.0, turn, false);
10069 assert_eq!(state.end_of_timeslice, Some(now + turn));
10070 state.cap_timeslice_at_deadline(Duration::from_nanos(700_000), 1.0, turn, false);
10071 assert_eq!(state.end_of_timeslice, Some(now + turn));
10072 }
10073
10074 #[test]
10077 fn schedule_replay_takes_no_deadline_cap() {
10078 let config = Config {
10079 max_timeslice: nz(200_000_000),
10080 ..Default::default()
10081 };
10082 let mut state = ThreadState::new(DetPid::from_raw(3), &config, ());
10083 state.next_timeslice(&config);
10084 let end = state.end_of_timeslice;
10085 state.cap_timeslice_at_deadline(Duration::from_nanos(5_000), 1.0, Duration::ZERO, true);
10086 assert_eq!(state.end_of_timeslice, end);
10087 }
10088
10089 #[test]
10092 fn constant_slowdown_is_the_single_epoch_case() {
10093 let config = Config {
10094 chaos: true,
10095 chaos_per_thread_slowdown: true,
10096 chaos_epoch_length_ns: 0,
10097 target_timeslice: nz(10_000),
10098 max_timeslice: nz(100_000),
10099 ..Default::default()
10100 };
10101 let mut state = ThreadState::new(DetPid::from_raw(3), &config, ());
10102 state.next_timeslice(&config);
10103 let first = state.take_pending_chaos_epochs().pop().unwrap();
10104 assert_eq!(first.epoch, 0);
10105 assert_eq!(state.chaos_epoch, 0);
10106
10107 state
10108 .thread_logical_time
10109 .add_rcbs_with_multiplier(10_000, first.factor);
10110 state.next_timeslice(&config);
10111 assert_eq!(state.chaos_epoch, 0);
10112 assert_eq!(state.chaos_slowdown_factor, first.factor);
10113 assert!(state.take_pending_chaos_epochs().is_empty());
10114 }
10115
10116 #[test]
10119 fn epoch_redraw_uses_elapsed_logical_time_at_commit_boundaries() {
10120 let config = Config {
10121 chaos: true,
10122 chaos_per_thread_slowdown: true,
10123 chaos_epoch_length_ns: 100,
10124 target_timeslice: nz(10_000),
10125 max_timeslice: nz(100_000),
10126 ..Default::default()
10127 };
10128 let tid = DetPid::from_raw(5);
10129 let mut state = ThreadState::new(tid, &config, ());
10130 state.next_timeslice(&config);
10131 let first = state.pending_chaos_epochs[0];
10132
10133 state
10134 .thread_logical_time
10135 .add_rcbs_with_multiplier(1_000, first.factor);
10136 let expected_epoch =
10137 state.thread_logical_time.without_starting().as_nanos() / config.chaos_epoch_length_ns;
10138 assert!(expected_epoch > 0);
10139
10140 state.next_timeslice(&config);
10141 let transitions = state.take_pending_chaos_epochs();
10142 assert_eq!(transitions.len(), 2);
10143 assert_eq!(transitions[0], first);
10144 let redraw = transitions[1];
10145 assert_eq!(redraw.epoch, expected_epoch);
10146 assert_eq!(
10147 redraw.factor,
10148 chaos_per_thread_slowdown_factor(
10149 config.sched_seed(),
10150 tid,
10151 expected_epoch,
10152 config.chaos_slowdown_max_factor,
10153 )
10154 );
10155 assert!(redraw.logical_time > first.logical_time);
10156 }
10157
10158 #[test]
10161 fn replay_installs_recorded_epoch_without_ambient_chaos_flags() {
10162 let config = Config {
10163 target_timeslice: nz(10_000),
10164 max_timeslice: nz(100_000),
10165 ..Default::default()
10166 };
10167 let transition = ChaosEpochTransition {
10168 logical_time: LogicalTime::ZERO,
10169 epoch: 7,
10170 factor: RcbTimeMultiplier::from_f64(3.25),
10171 };
10172 let mut state = ThreadState::new(DetPid::from_raw(5), &config, ());
10173 state.preemption_points = Some(
10174 ThreadHistory::new()
10175 .with_chaos_epochs(vec![transition])
10176 .into_iter(),
10177 );
10178
10179 state.next_timeslice(&config);
10180
10181 assert!(state.chaos_slowdown_active);
10182 assert_eq!(state.chaos_epoch, transition.epoch);
10183 assert_eq!(state.chaos_slowdown_factor, transition.factor);
10184 assert!(state.take_pending_chaos_epochs().is_empty());
10185 }
10186
10187 #[test]
10190 fn replay_installs_boundary_epoch_when_pmu_checks_in_early() {
10191 let config = Config {
10192 target_timeslice: nz(10_000),
10193 max_timeslice: nz(100_000),
10194 ..Default::default()
10195 };
10196 let mut state = ThreadState::new(DetPid::from_raw(5), &config, ());
10197 let now = state.thread_logical_time.as_nanos();
10198 let first = ChaosEpochTransition {
10199 logical_time: now,
10200 epoch: 0,
10201 factor: RcbTimeMultiplier::from_f64(2.0),
10202 };
10203 let second = ChaosEpochTransition {
10204 logical_time: now + Duration::from_nanos(100),
10205 epoch: 1,
10206 factor: RcbTimeMultiplier::from_f64(3.0),
10207 };
10208 let history = ThreadHistory::new()
10209 .with_prio_changes(vec![
10210 (now + Duration::from_nanos(100), DEFAULT_PRIORITY),
10211 (now + Duration::from_nanos(200), DEFAULT_PRIORITY),
10212 ])
10213 .with_preemption_rcbs(vec![4, 8])
10214 .with_chaos_epochs(vec![first, second]);
10215 state.preemption_points = Some(history.into_iter());
10216
10217 state.next_timeslice(&config);
10218 assert_eq!(state.chaos_slowdown_factor, first.factor);
10219 assert_eq!(
10220 state.end_of_timeslice,
10221 Some(now + Duration::from_nanos(100))
10222 );
10223
10224 state
10227 .thread_logical_time
10228 .add_rcbs_with_multiplier(4, first.factor);
10229 state.committed_clock_value = 4;
10230 assert!(state.timeslice_expired());
10231 state.next_timeslice(&config);
10232
10233 assert_eq!(
10234 state.thread_logical_time.as_nanos(),
10235 now + Duration::from_nanos(80)
10236 );
10237 assert_eq!(state.chaos_epoch, second.epoch);
10238 assert_eq!(state.chaos_slowdown_factor, second.factor);
10239 assert_eq!(
10240 state.end_of_timeslice,
10241 Some(now + Duration::from_nanos(200))
10242 );
10243 }
10244
10245 fn replay_from_500ns(
10250 config: &Config,
10251 history: ThreadHistory,
10252 ) -> (ThreadState<()>, Option<Priority>, LogicalTime) {
10253 let mut state = ThreadState::new(DetPid::from_raw(5), config, ());
10254 state.preemption_points = Some(history.into_iter());
10255 state
10256 .thread_logical_time
10257 .add_rcbs_with_multiplier(50, RcbTimeMultiplier::ONE);
10258 state.committed_clock_value = 10;
10259 let now = state.thread_logical_time.as_nanos();
10260 let prio = state.next_timeslice(config);
10261 (state, prio, now)
10262 }
10263
10264 fn edited_replay_config() -> Config {
10265 Config {
10266 target_timeslice: nz(10_000),
10267 max_timeslice: nz(100_000),
10268 replay_edited_preemptions: true,
10269 ..Default::default()
10270 }
10271 }
10272
10273 #[test]
10279 fn an_edited_record_skips_the_slices_that_already_ended() {
10280 let config = edited_replay_config();
10281 let start = ThreadState::new(DetPid::from_raw(5), &config, ())
10282 .thread_logical_time
10283 .as_nanos();
10284 let at = |ns: u64| start + Duration::from_nanos(ns);
10285 let history = ThreadHistory::new()
10286 .with_prio_changes(vec![
10287 (at(100), DEFAULT_PRIORITY + 1),
10288 (at(505), DEFAULT_PRIORITY + 2),
10290 (at(900), DEFAULT_PRIORITY + 3),
10291 (at(1300), DEFAULT_PRIORITY + 4),
10292 ])
10293 .with_preemption_rcbs(vec![4, 9, 90, 130]);
10294 let (mut state, prio, _) = replay_from_500ns(&config, history);
10295 assert_eq!(prio, Some(DEFAULT_PRIORITY + 3));
10296 assert_eq!(state.end_of_timeslice, Some(at(900)));
10297 assert_eq!(state.replay_rcb_end, Some(90));
10298
10299 state
10300 .thread_logical_time
10301 .add_rcbs_with_multiplier(40, RcbTimeMultiplier::ONE);
10302 state.committed_clock_value = 90;
10303 assert!(state.timeslice_expired());
10304 assert_eq!(state.next_timeslice(&config), Some(DEFAULT_PRIORITY + 4));
10305 assert_eq!(state.end_of_timeslice, Some(at(1300)));
10306 assert_eq!(state.replay_rcb_end, Some(130));
10307 }
10308
10309 #[test]
10313 fn an_edited_record_keeps_a_slice_one_of_whose_deadlines_is_ahead() {
10314 let config = edited_replay_config();
10315 let start = ThreadState::new(DetPid::from_raw(5), &config, ())
10316 .thread_logical_time
10317 .as_nanos();
10318 let at = |ns: u64| start + Duration::from_nanos(ns);
10319 let one_slice = |end: u64, rcbs: Option<u64>| {
10320 let history = ThreadHistory::new().with_prio_changes(vec![
10321 (at(end), DEFAULT_PRIORITY + 7),
10322 (at(5000), DEFAULT_PRIORITY),
10323 ]);
10324 match rcbs {
10325 Some(rcbs) => history.with_preemption_rcbs(vec![rcbs, 500]),
10326 None => history,
10327 }
10328 };
10329 let (state, prio, _) = replay_from_500ns(&config, one_slice(100, Some(20)));
10331 assert_eq!(prio, Some(DEFAULT_PRIORITY + 7));
10332 assert_eq!(state.replay_rcb_end, Some(20));
10333 assert!(!state.timeslice_expired());
10334 let (state, prio, _) = replay_from_500ns(&config, one_slice(800, Some(4)));
10336 assert_eq!(prio, Some(DEFAULT_PRIORITY + 7));
10337 assert_eq!(
10338 (state.end_of_timeslice, state.replay_rcb_end),
10339 (Some(at(800)), None)
10340 );
10341 let (state, prio, _) = replay_from_500ns(&config, one_slice(100, Some(10)));
10344 assert_eq!(prio, Some(DEFAULT_PRIORITY + 7));
10345 assert_eq!(state.replay_rcb_end, Some(10));
10346 assert!(state.timeslice_expired());
10347 }
10348
10349 #[test]
10352 fn an_edited_record_whose_slices_all_ended_goes_on_at_its_final_priority() {
10353 let config = edited_replay_config();
10354 let start = ThreadState::new(DetPid::from_raw(5), &config, ())
10355 .thread_logical_time
10356 .as_nanos();
10357 let history = ThreadHistory::new().with_prio_changes(vec![
10358 (start + Duration::from_nanos(100), DEFAULT_PRIORITY + 1),
10359 (start + Duration::from_nanos(200), DEFAULT_PRIORITY + 2),
10360 ]);
10361 let final_priority = history.clone().into_iter().final_priority();
10362 let (state, prio, now) = replay_from_500ns(&config, history);
10363 assert_eq!(prio, Some(final_priority));
10364 assert_eq!(
10367 state.end_of_timeslice,
10368 Some(now + Duration::from_nanos(100_000))
10369 );
10370 assert!(state.stats.last_recorded_slice.is_some());
10371 }
10372
10373 #[test]
10378 fn the_skipping_margin_is_one_rcb_at_the_thread_clock() {
10379 let config = Config {
10380 clock_multiplier: Some(0.3),
10381 ..edited_replay_config()
10382 };
10383 let state = ThreadState::new(DetPid::from_raw(5), &config, ());
10384 assert_eq!(state.pmu_epsilon(&config), Duration::from_nanos(3));
10385 let mut slowed = ThreadState::new(DetPid::from_raw(5), &config, ());
10386 slowed.chaos_slowdown_factor = RcbTimeMultiplier::from_f64(2.0);
10387 slowed.chaos_slowdown_active = true;
10388 assert_eq!(slowed.pmu_epsilon(&config), Duration::from_nanos(6));
10389
10390 let start = state.thread_logical_time.as_nanos();
10391 for (ahead, skipped) in [(2, true), (3, false)] {
10392 let mut state = ThreadState::new(DetPid::from_raw(5), &config, ());
10395 state
10396 .thread_logical_time
10397 .add_rcbs_with_multiplier(50, RcbTimeMultiplier::ONE);
10398 let now = state.thread_logical_time.as_nanos();
10399 state.preemption_points = Some(
10400 ThreadHistory::new()
10401 .with_prio_changes(vec![
10402 (now + Duration::from_nanos(ahead), DEFAULT_PRIORITY + 1),
10403 (start + Duration::from_nanos(9000), DEFAULT_PRIORITY + 2),
10404 ])
10405 .into_iter(),
10406 );
10407 let prio = state.next_timeslice(&config);
10408 let expected = if skipped {
10409 DEFAULT_PRIORITY + 2
10410 } else {
10411 DEFAULT_PRIORITY + 1
10412 };
10413 assert_eq!(prio, Some(expected), "{ahead} ns ahead");
10414 }
10415
10416 for (ahead, skipped) in [(5, true), (6, false)] {
10419 let mut state = ThreadState::new(DetPid::from_raw(5), &config, ());
10420 let begin = state.thread_logical_time.as_nanos();
10421 state.preemption_points = Some(
10422 ThreadHistory::new()
10423 .with_prio_changes(vec![
10424 (begin + Duration::from_nanos(ahead), DEFAULT_PRIORITY + 1),
10425 (begin + Duration::from_nanos(9000), DEFAULT_PRIORITY + 2),
10426 ])
10427 .with_chaos_epochs(vec![ChaosEpochTransition {
10428 logical_time: begin,
10429 epoch: 0,
10430 factor: RcbTimeMultiplier::from_f64(2.0),
10431 }])
10432 .into_iter(),
10433 );
10434 let prio = state.next_timeslice(&config);
10435 assert_eq!(state.pmu_epsilon(&config), Duration::from_nanos(6));
10436 let expected = if skipped {
10437 DEFAULT_PRIORITY + 2
10438 } else {
10439 DEFAULT_PRIORITY + 1
10440 };
10441 assert_eq!(prio, Some(expected), "{ahead} ns ahead at 2 times");
10442 }
10443
10444 let unbounded = Config {
10447 max_timeslice: None,
10448 ..edited_replay_config()
10449 };
10450 for (end, expected) in [(501, DEFAULT_PRIORITY + 1), (500, DEFAULT_PRIORITY + 2)] {
10451 let history = ThreadHistory::new().with_prio_changes(vec![
10452 (start + Duration::from_nanos(end), DEFAULT_PRIORITY + 1),
10453 (start + Duration::from_nanos(9000), DEFAULT_PRIORITY + 2),
10454 ]);
10455 let (_, prio, _) = replay_from_500ns(&unbounded, history);
10456 assert_eq!(
10457 prio,
10458 Some(expected),
10459 "an end at {end} ns, without a maximum"
10460 );
10461 }
10462 }
10463
10464 #[test]
10469 fn an_edited_replay_keeps_one_rcb_slices_as_a_faithful_one_does() {
10470 let edited = edited_replay_config();
10471 let faithful = Config {
10472 replay_edited_preemptions: false,
10473 ..edited_replay_config()
10474 };
10475 let start = ThreadState::new(DetPid::from_raw(5), &edited, ())
10476 .thread_logical_time
10477 .as_nanos();
10478 let at = |ns: u64| start + Duration::from_nanos(ns);
10479 let history = ThreadHistory::new().with_prio_changes(vec![
10481 (at(510), DEFAULT_PRIORITY + 1),
10482 (at(520), DEFAULT_PRIORITY + 2),
10483 (at(530), DEFAULT_PRIORITY + 3),
10484 (at(900), DEFAULT_PRIORITY + 4),
10485 ]);
10486 let slices = |config: &Config| {
10487 let (mut state, prio, _) = replay_from_500ns(config, history.clone());
10488 let mut slices = vec![(prio, state.end_of_timeslice)];
10489 for _ in 0..3 {
10490 state.thread_logical_time.add_rcbs(1);
10491 let prio = state.next_timeslice(config);
10492 slices.push((prio, state.end_of_timeslice));
10493 }
10494 slices
10495 };
10496 assert_eq!(slices(&edited), slices(&faithful));
10497 assert_eq!(
10498 slices(&edited)[..3],
10499 [
10500 (Some(DEFAULT_PRIORITY + 1), Some(at(510))),
10501 (Some(DEFAULT_PRIORITY + 2), Some(at(520))),
10502 (Some(DEFAULT_PRIORITY + 3), Some(at(530))),
10503 ]
10504 );
10505 }
10506
10507 #[test]
10513 fn past_an_edited_record_the_maximum_keeps_one_rcb_at_a_restored_slowdown() {
10514 let config = |edited: bool| Config {
10515 target_timeslice: None,
10516 max_timeslice: nz(10),
10517 replay_edited_preemptions: edited,
10518 ..Default::default()
10519 };
10520 for (edited, expected) in [(true, 20), (false, 10)] {
10521 let config = config(edited);
10522 let mut state = ThreadState::new(DetPid::from_raw(5), &config, ());
10523 let now = state.thread_logical_time.as_nanos();
10524 let doubled = ChaosEpochTransition {
10525 logical_time: now,
10526 epoch: 1,
10527 factor: RcbTimeMultiplier::from_f64(2.0),
10528 };
10529 state.preemption_points = Some(
10530 ThreadHistory::new()
10531 .with_chaos_epochs(vec![doubled])
10532 .into_iter(),
10533 );
10534 state.next_timeslice(&config);
10535 assert_eq!(state.chaos_slowdown_factor, doubled.factor);
10536 assert_eq!(state.pmu_epsilon(&config), Duration::from_nanos(20));
10537 assert!(state.stats.last_recorded_slice.is_some());
10538 assert_eq!(
10539 state.max_timeslice_end,
10540 Some(now + Duration::from_nanos(expected)),
10541 "edited: {edited}"
10542 );
10543 }
10544 }
10545
10546 #[test]
10550 #[should_panic(expected = "needs --replay-edited-preemptions")]
10551 fn a_faithful_replay_past_a_recorded_end_stops() {
10552 let config = Config {
10553 replay_edited_preemptions: false,
10554 ..edited_replay_config()
10555 };
10556 let start = ThreadState::new(DetPid::from_raw(5), &config, ())
10557 .thread_logical_time
10558 .as_nanos();
10559 let history = ThreadHistory::new().with_prio_changes(vec![
10560 (start + Duration::from_nanos(100), DEFAULT_PRIORITY + 1),
10561 (start + Duration::from_nanos(900), DEFAULT_PRIORITY),
10562 ]);
10563 replay_from_500ns(&config, history);
10564 }
10565
10566 #[test]
10568 #[should_panic(expected = "count is already 10. An edited record")]
10569 fn a_faithful_replay_past_a_recorded_rcb_end_stops() {
10570 let config = Config {
10571 replay_edited_preemptions: false,
10572 ..edited_replay_config()
10573 };
10574 let start = ThreadState::new(DetPid::from_raw(5), &config, ())
10575 .thread_logical_time
10576 .as_nanos();
10577 let history = ThreadHistory::new()
10578 .with_prio_changes(vec![
10579 (start + Duration::from_nanos(100), DEFAULT_PRIORITY + 1),
10580 (start + Duration::from_nanos(900), DEFAULT_PRIORITY),
10581 ])
10582 .with_preemption_rcbs(vec![4, 90]);
10583 replay_from_500ns(&config, history);
10584 }
10585
10586 fn assert_faithful_rcb_ends_stop(panic_on_rcb_overshoot: bool) {
10590 for counts in [
10591 vec![40, 80],
10592 vec![40, 45, 80],
10593 vec![40, 51, 80],
10594 vec![40, 45],
10595 ] {
10596 let config = Config {
10597 target_timeslice: nz(10_000),
10598 max_timeslice: nz(100_000),
10599 panic_on_rcb_overshoot,
10600 ..Default::default()
10601 };
10602 let mut state = ThreadState::new(DetPid::from_raw(5), &config, ());
10603 let now = state.thread_logical_time.as_nanos();
10604 let history = ThreadHistory::new()
10605 .with_prio_changes(
10606 (0..counts.len())
10607 .map(|i| {
10608 (
10609 now + Duration::from_nanos(100 * (i as u64 + 1)),
10610 DEFAULT_PRIORITY,
10611 )
10612 })
10613 .collect(),
10614 )
10615 .with_preemption_rcbs(counts.clone());
10616 state.preemption_points = Some(history.into_iter());
10617 state.committed_clock_value = 51;
10618 let panic = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
10619 state.next_timeslice(&config)
10620 }))
10621 .expect_err("faithful replay must not pass a recorded RCB end");
10622 let message = panic.downcast_ref::<String>().unwrap();
10623 assert!(
10624 message.contains("Cannot set RCB end of timeslice to 40"),
10625 "{counts:?}: {message}"
10626 );
10627 assert!(
10628 message.contains("count is already 51. An edited record"),
10629 "{counts:?}: {message}"
10630 );
10631 assert!(
10632 message.contains("needs --replay-edited-preemptions"),
10633 "{message}"
10634 );
10635 }
10636 }
10637
10638 #[test]
10639 fn a_faithful_replay_past_larger_rcb_ends_stops_by_default() {
10640 assert_faithful_rcb_ends_stop(false);
10641 }
10642
10643 #[test]
10644 fn a_faithful_replay_past_larger_rcb_ends_stops_with_skid_panic_enabled() {
10645 assert_faithful_rcb_ends_stop(true);
10646 }
10647
10648 #[test]
10653 fn an_edited_record_reports_no_departure() {
10654 let config = Config {
10655 panic_on_rcb_overshoot: true,
10656 replay_preemptions_from: Some(std::path::PathBuf::from("history.json")),
10657 ..edited_replay_config()
10658 };
10659 let start = ThreadState::new(DetPid::from_raw(5), &config, ())
10660 .thread_logical_time
10661 .as_nanos();
10662 let at = |ns: u64| start + Duration::from_nanos(ns);
10663 let history = |end: u64, rcbs: u64| {
10664 ThreadHistory::new()
10665 .with_prio_changes(vec![
10666 (at(end), DEFAULT_PRIORITY + 7),
10667 (at(5000), DEFAULT_PRIORITY),
10668 ])
10669 .with_preemption_rcbs(vec![rcbs, 500])
10670 };
10671 let (mut state, prio, now) = replay_from_500ns(&config, history(100, 10));
10672 assert_eq!(prio, Some(DEFAULT_PRIORITY + 7));
10673 assert_eq!(
10674 (state.end_of_timeslice, state.replay_rcb_end),
10675 (Some(at(100)), Some(10))
10676 );
10677 assert!(state.timeslice_expired());
10678 assert_eq!(state.file_replay_change_point(&config, now), Ok(()));
10679 assert_eq!(state.file_replay_change_point(&config, now), Ok(()));
10680 assert_eq!(state.file_replay_change_point(&config, at(100)), Ok(()));
10681 let (state, prio, _) = replay_from_500ns(&config, history(800, 4));
10682 assert_eq!(prio, Some(DEFAULT_PRIORITY + 7));
10683 assert_eq!(
10684 (state.end_of_timeslice, state.replay_rcb_end),
10685 (Some(at(800)), None)
10686 );
10687 let faithful = Config {
10690 replay_edited_preemptions: false,
10691 ..config
10692 };
10693 let (mut state, _, now) = replay_from_500ns(&faithful, history(100, 10));
10694 assert_eq!(state.replay_rcb_end, Some(10));
10695 assert_eq!(state.file_replay_change_point(&faithful, now), Ok(()));
10696 assert_eq!(state.file_replay_change_point(&faithful, now), Err(now));
10697 }
10698
10699 #[test]
10703 fn a_replay_keeps_an_equal_rcb_end_and_refuses_repeated_filing() {
10704 let config = Config {
10705 target_timeslice: nz(10_000),
10706 max_timeslice: nz(100_000),
10707 replay_preemptions_from: Some("history.json".into()),
10708 ..Default::default()
10709 };
10710 let mut state = ThreadState::new(DetPid::from_raw(31754), &config, ());
10711 state.thread_logical_time.add_rcbs(863_958);
10712 state.committed_clock_value = 863_958;
10713 let now = state.thread_logical_time.as_nanos();
10714 let history = ThreadHistory::new()
10715 .with_prio_changes(vec![
10716 (
10717 LogicalTime::from_nanos(now.as_nanos() - 1_802_680),
10718 DEFAULT_PRIORITY + 1,
10719 ),
10720 (
10721 LogicalTime::from_nanos(now.as_nanos() - 1_802_670),
10722 DEFAULT_PRIORITY + 2,
10723 ),
10724 (now + Duration::from_nanos(10_780), DEFAULT_PRIORITY + 3),
10725 ])
10726 .with_preemption_rcbs(vec![863_958, 863_959, 864_037]);
10727 state.preemption_points = Some(history.into_iter());
10728
10729 assert_eq!(state.file_replay_change_point(&config, now), Ok(()));
10730 let prio = state.next_timeslice(&config);
10731 assert_eq!(prio, Some(DEFAULT_PRIORITY + 1));
10732 assert_eq!(state.replay_rcb_end, Some(863_958));
10733 assert_eq!(
10734 state.end_of_timeslice,
10735 Some(LogicalTime::from_nanos(now.as_nanos() - 1_802_680))
10736 );
10737 assert!(state.timeslice_expired());
10738 assert_eq!(state.file_replay_change_point(&config, now), Err(now));
10739 state.abandon_replayed_slices(&config);
10740 assert!(!state.timeslice_expired());
10741 assert_eq!(state.end_of_timeslice, Some(LogicalTime::MAX));
10742 }
10743
10744 #[test]
10748 fn a_replay_follows_an_end_at_its_count_whose_time_is_ahead() {
10749 let config = Config {
10750 target_timeslice: nz(10_000),
10751 max_timeslice: nz(100_000),
10752 ..Default::default()
10753 };
10754 let mut state = ThreadState::new(DetPid::from_raw(5), &config, ());
10755 let now = state.thread_logical_time.as_nanos();
10756 let history = ThreadHistory::new()
10757 .with_prio_changes(vec![(
10758 now + Duration::from_nanos(25_000),
10759 DEFAULT_PRIORITY + 1,
10760 )])
10761 .with_preemption_rcbs(vec![0]);
10762 state.preemption_points = Some(history.into_iter());
10763
10764 assert_eq!(state.next_timeslice(&config), Some(DEFAULT_PRIORITY + 1));
10765 assert_eq!(state.replay_rcb_end, Some(0));
10766 assert_eq!(
10767 state.end_of_timeslice,
10768 Some(now + Duration::from_nanos(25_000))
10769 );
10770
10771 let mut state = ThreadState::new(DetPid::from_raw(6), &config, ());
10773 let now = state.thread_logical_time.as_nanos();
10774 let history = ThreadHistory::new()
10775 .with_prio_changes(vec![(now, DEFAULT_PRIORITY + 1)])
10776 .with_preemption_rcbs(vec![0]);
10777 state.preemption_points = Some(history.into_iter());
10778 assert_eq!(state.next_timeslice(&config), Some(DEFAULT_PRIORITY + 1));
10779 assert_eq!(state.replay_rcb_end, Some(0));
10780 assert_eq!(state.end_of_timeslice, Some(now));
10781 }
10782
10783 #[test]
10791 fn a_replay_change_point_at_the_last_ones_instant_is_caught_before_filing() {
10792 let config = Config {
10793 target_timeslice: nz(10_000),
10794 max_timeslice: nz(100_000),
10795 replay_preemptions_from: Some(std::path::PathBuf::from("history.json")),
10796 ..Default::default()
10797 };
10798 let mut state = ThreadState::new(DetPid::from_raw(31744), &config, ());
10799 let start = state.thread_logical_time.as_nanos();
10800 let history = ThreadHistory::new()
10801 .with_prio_changes(vec![
10802 (start + Duration::from_nanos(70), DEFAULT_PRIORITY + 1),
10803 (start + Duration::from_nanos(80), DEFAULT_PRIORITY + 2),
10804 (start + Duration::from_nanos(500), DEFAULT_PRIORITY + 3),
10805 ])
10806 .with_preemption_rcbs(vec![4, 4, 40]);
10807 state.preemption_points = Some(history.into_iter());
10808
10809 state.next_timeslice(&config);
10810 state.thread_logical_time.add_rcbs(4);
10811 state.committed_clock_value = 4;
10812 assert!(state.timeslice_expired());
10813 let now = state.thread_logical_time.as_nanos();
10814 assert_eq!(state.file_replay_change_point(&config, now), Ok(()));
10815
10816 state.next_timeslice(&config);
10820 assert_eq!(state.replay_rcb_end, Some(4));
10821 assert!(state.timeslice_expired());
10822 assert_eq!(state.file_replay_change_point(&config, now), Err(now));
10823
10824 state.abandon_replayed_slices(&config);
10825 assert!(!state.timeslice_expired());
10826 assert_eq!(state.replay_rcb_end, None);
10827 assert_eq!(state.end_of_timeslice, Some(LogicalTime::MAX));
10828 assert_eq!(
10829 state.max_timeslice_end,
10830 Some(now + Duration::from_nanos(100_000))
10831 );
10832
10833 let later = now + Duration::from_nanos(1);
10836 assert_eq!(state.file_replay_change_point(&config, later), Ok(()));
10837 let recording = Config {
10838 replay_preemptions_from: None,
10839 ..config
10840 };
10841 assert_eq!(state.file_replay_change_point(&recording, later), Ok(()));
10842 }
10843
10844 #[test]
10847 fn replay_preserves_adjacent_zero_rcb_slices() {
10848 let config = Config {
10849 target_timeslice: nz(10_000),
10850 max_timeslice: nz(100_000),
10851 ..Default::default()
10852 };
10853 let mut state = ThreadState::new(DetPid::from_raw(5), &config, ());
10854 let now = state.thread_logical_time.as_nanos();
10855 let history = ThreadHistory::new()
10856 .with_prio_changes(vec![
10857 (now + Duration::from_nanos(70), DEFAULT_PRIORITY),
10858 (now + Duration::from_nanos(80), DEFAULT_PRIORITY),
10859 ])
10860 .with_preemption_rcbs(vec![4, 4]);
10861 state.preemption_points = Some(history.into_iter());
10862
10863 state.next_timeslice(&config);
10864 state.thread_logical_time.add_rcbs(4);
10865 state.committed_clock_value = 4;
10866 assert!(state.timeslice_expired());
10867
10868 state.next_timeslice(&config);
10869 assert_eq!(state.replay_rcb_end, Some(4));
10870 assert!(state.timeslice_expired());
10871 }
10872
10873 #[test]
10874 fn target_and_pmu_deadlines_are_independent() {
10875 let config = Config {
10876 target_timeslice: nz(20_000),
10877 max_timeslice: nz(100_000),
10878 ..Default::default()
10879 };
10880 let mut state = ThreadState::new(DetPid::from_raw(1), &config, ());
10881 let now = state.thread_logical_time.as_nanos();
10882
10883 state.next_timeslice(&config);
10884
10885 assert_eq!(
10886 state.end_of_timeslice,
10887 Some(now + Duration::from_nanos(20_000))
10888 );
10889 assert_eq!(
10890 state.max_timeslice_end,
10891 Some(now + Duration::from_nanos(100_000))
10892 );
10893 }
10894
10895 #[test]
10896 fn target_only_mode_does_not_create_a_pmu_deadline() {
10897 let config = Config {
10898 target_timeslice: nz(20_000),
10899 max_timeslice: None,
10900 ..Default::default()
10901 };
10902 let mut state = ThreadState::new(DetPid::from_raw(1), &config, ());
10903 let now = state.thread_logical_time.as_nanos();
10904
10905 state.next_timeslice(&config);
10906
10907 assert_eq!(
10908 state.end_of_timeslice,
10909 Some(now + Duration::from_nanos(20_000))
10910 );
10911 assert_eq!(state.max_timeslice_end, None);
10912 }
10913
10914 #[test]
10915 fn max_timeslice_caps_a_larger_target() {
10916 let config = Config {
10917 target_timeslice: nz(100_000),
10918 max_timeslice: nz(20_000),
10919 ..Default::default()
10920 };
10921 let mut state = ThreadState::new(DetPid::from_raw(1), &config, ());
10922 let now = state.thread_logical_time.as_nanos();
10923
10924 state.next_timeslice(&config);
10925
10926 let max_end = now + Duration::from_nanos(20_000);
10927 assert_eq!(state.end_of_timeslice, Some(max_end));
10928 assert_eq!(state.max_timeslice_end, Some(max_end));
10929 }
10930
10931 #[test]
10932 fn chaos_without_target_caps_randomized_deadline_at_maximum() {
10933 let config = Config {
10934 chaos: true,
10935 target_timeslice: None,
10936 max_timeslice: nz(100_000),
10937 clock_multiplier: Some(1.05),
10938 ..Default::default()
10939 };
10940 let mut state = ThreadState::new(DetPid::from_raw(1), &config, ());
10941 let now = state.thread_logical_time.as_nanos();
10942 let configured_max = now + Duration::from_nanos(100_000);
10943 let minimum_progress = now + Duration::from_nanos(11);
10944
10945 state.next_timeslice(&config);
10946
10947 assert_eq!(state.max_timeslice_end, state.end_of_timeslice);
10948 assert!(state.max_timeslice_end.unwrap() <= configured_max);
10949 assert!(state.max_timeslice_end.unwrap() >= minimum_progress);
10950 }
10951
10952 #[test]
10955 fn a_chaos_timeslice_range_draws_slices_of_every_scale_up_to_its_max() {
10956 let config = Config {
10957 chaos: true,
10958 max_timeslice: nz(100_000_000),
10959 chaos_timeslice_range: Some("1000..100000000".parse().unwrap()),
10960 ..Default::default()
10961 };
10962 let mut state = ThreadState::new(DetPid::from_raw(1), &config, ());
10963 let now = state.thread_logical_time.as_nanos();
10964 let lengths: Vec<u64> = (0..3000)
10965 .map(|_| {
10966 state.next_timeslice(&config);
10967 let end = state.end_of_timeslice.unwrap();
10968 assert_eq!(state.max_timeslice_end, Some(end));
10969 (end - now).as_nanos()
10970 })
10971 .collect();
10972 assert!(
10973 lengths
10974 .iter()
10975 .all(|length| (10..=100_000_000).contains(length)),
10976 "{:?}",
10977 lengths.iter().max()
10978 );
10979 assert!(lengths.iter().filter(|length| **length < 10_000).count() > 100);
10980 assert!(
10981 lengths
10982 .iter()
10983 .filter(|length| **length > 10_000_000)
10984 .count()
10985 > 100
10986 );
10987 }
10988
10989 #[test]
10992 fn a_schedule_replay_draws_nothing_from_the_chaos_timeslice_range() {
10993 let plain = Config {
10994 chaos: true,
10995 max_timeslice: nz(100_000),
10996 replay_schedule_from: Some(std::path::PathBuf::from("schedule.json")),
10997 ..Default::default()
10998 };
10999 let ranged = Config {
11000 chaos_timeslice_range: Some("1000..100000".parse().unwrap()),
11001 ..plain.clone()
11002 };
11003 let mut without = ThreadState::new(DetPid::from_raw(1), &plain, ());
11004 let mut with = ThreadState::new(DetPid::from_raw(1), &ranged, ());
11005 for _ in 0..16 {
11006 without.next_timeslice(&plain);
11007 with.next_timeslice(&ranged);
11008 assert_eq!(with.end_of_timeslice, without.end_of_timeslice);
11009 assert_eq!(with.max_timeslice_end, without.max_timeslice_end);
11010 }
11011 assert!(with.chaos_prng == without.chaos_prng);
11012 }
11013
11014 #[test]
11015 fn schedule_replay_without_rcb_time_arms_pmu_maximum() {
11016 let config = Config {
11017 no_rcb_time: true,
11018 max_timeslice: nz(100_000),
11019 replay_schedule_from: Some(std::path::PathBuf::from("schedule.json")),
11020 ..Default::default()
11021 };
11022 let mut state = ThreadState::new(DetPid::from_raw(1), &config, ());
11023 let now = state.thread_logical_time.as_nanos();
11024
11025 state.next_timeslice(&config);
11026
11027 let expected = now + Duration::from_nanos(100_000);
11028 assert_eq!(state.end_of_timeslice, Some(expected));
11029 assert_eq!(state.max_timeslice_end, Some(expected));
11030 }
11031
11032 #[test]
11033 fn exhausted_preemption_replay_uses_bounded_pmu_maximum() {
11034 let config = Config {
11035 max_timeslice: nz(100_000),
11036 ..Default::default()
11037 };
11038 let mut state = ThreadState::new(DetPid::from_raw(3), &config, ());
11039 state.preemption_points = Some(ThreadHistory::new().into_iter());
11040 let now = state.thread_logical_time.as_nanos();
11041
11042 state.next_timeslice(&config);
11043
11044 let bounded_end = Some(now + Duration::from_nanos(100_000));
11045 assert_eq!(state.end_of_timeslice, bounded_end);
11046 assert_eq!(state.max_timeslice_end, bounded_end);
11047 }
11048
11049 #[test]
11053 fn preemption_replay_caps_only_past_its_recording() {
11054 let config = Config {
11055 max_timeslice: nz(100_000),
11056 ..Default::default()
11057 };
11058 let mut state = ThreadState::new(DetPid::from_raw(3), &config, ());
11059 let now = state.thread_logical_time.as_nanos();
11060 let recorded_end = now + Duration::from_nanos(50_000);
11061 let history = ThreadHistory::new()
11062 .with_prio_changes(vec![(recorded_end, DEFAULT_PRIORITY)])
11063 .with_preemption_rcbs(vec![5_000]);
11064 state.preemption_points = Some(history.into_iter());
11065
11066 state.next_timeslice(&config);
11067 assert_eq!(state.end_of_timeslice, Some(recorded_end));
11068 state.cap_timeslice_at_deadline(Duration::from_nanos(1_000), 1.0, Duration::ZERO, false);
11069 assert_eq!(state.end_of_timeslice, Some(recorded_end));
11070
11071 let mut exhausted = ThreadState::new(DetPid::from_raw(3), &config, ());
11072 exhausted.preemption_points = Some(ThreadHistory::new().into_iter());
11073 exhausted.next_timeslice(&config);
11074 exhausted.cap_timeslice_at_deadline(
11075 Duration::from_nanos(1_000),
11076 1.0,
11077 Duration::ZERO,
11078 false,
11079 );
11080 let capped = Some(now + Duration::from_nanos(1_000));
11081 assert_eq!(exhausted.end_of_timeslice, capped);
11082 assert_eq!(
11083 exhausted.max_timeslice_end,
11084 Some(now + Duration::from_nanos(100_000))
11085 );
11086 }
11087
11088 #[test]
11089 fn timeslice_expiry_is_inclusive() {
11090 let config = Config::default();
11091 let mut state = ThreadState::new(DetPid::from_raw(1), &config, ());
11092 let now = state.thread_logical_time.as_nanos();
11093
11094 state.end_of_timeslice = Some(now + Duration::from_nanos(1));
11095 assert!(!state.timeslice_expired());
11096 state.end_of_timeslice = Some(now);
11097 assert!(state.timeslice_expired());
11098 }
11099
11100 #[test]
11101 fn instruction_trap_ends_an_expired_target_only_at_the_pmu_maximum() {
11102 let config = Config {
11103 target_timeslice: nz(1_000),
11104 target_timeslice_syscalls_only: true,
11105 max_timeslice: nz(20_000),
11106 ..Default::default()
11107 };
11108 let mut state = ThreadState::new(DetPid::from_raw(1), &config, ());
11109 state.next_timeslice(&config);
11110
11111 state.thread_logical_time.add_rcbs(500);
11113 assert!(state.timeslice_expired());
11114 assert!(!state.instruction_trap_ends_timeslice(&config));
11115
11116 state.thread_logical_time.add_rcbs(1_500);
11118 assert!(state.instruction_trap_ends_timeslice(&config));
11119 }
11120
11121 #[test]
11122 fn instruction_trap_ends_an_expired_target_by_default() {
11123 let config = Config {
11124 target_timeslice: nz(1_000),
11125 max_timeslice: nz(20_000),
11126 ..Default::default()
11127 };
11128 assert!(!config.target_timeslice_syscalls_only);
11129 let mut state = ThreadState::new(DetPid::from_raw(1), &config, ());
11130 state.next_timeslice(&config);
11131 state.thread_logical_time.add_rcbs(99);
11132 assert!(!state.instruction_trap_ends_timeslice(&config));
11133 state.thread_logical_time.add_rcbs(1);
11134 assert!(state.instruction_trap_ends_timeslice(&config));
11135 }
11136
11137 #[test]
11138 fn instruction_trap_ends_an_expired_target_without_a_pmu_maximum() {
11139 let config = Config {
11142 target_timeslice: nz(1_000),
11143 target_timeslice_syscalls_only: true,
11144 max_timeslice: None,
11145 ..Default::default()
11146 };
11147 let mut state = ThreadState::new(DetPid::from_raw(1), &config, ());
11148 state.next_timeslice(&config);
11149 assert_eq!(state.max_timeslice_end, None);
11150
11151 state.thread_logical_time.add_rcbs(99);
11153 assert!(!state.instruction_trap_ends_timeslice(&config));
11154 state.thread_logical_time.add_rcbs(1);
11155 assert!(state.instruction_trap_ends_timeslice(&config));
11156 }
11157
11158 #[test]
11159 fn instruction_trap_keeps_replayed_and_untargeted_boundaries() {
11160 let untargeted = Config {
11164 max_timeslice: nz(20_000),
11165 ..Default::default()
11166 };
11167 let mut state = ThreadState::new(DetPid::from_raw(1), &untargeted, ());
11168 state.next_timeslice(&untargeted);
11169 state.thread_logical_time.add_rcbs(1_999);
11170 assert_eq!(
11171 state.instruction_trap_ends_timeslice(&untargeted),
11172 state.timeslice_expired()
11173 );
11174 state.thread_logical_time.add_rcbs(1);
11175 assert!(state.instruction_trap_ends_timeslice(&untargeted));
11176
11177 let targeted = Config {
11178 target_timeslice: nz(1_000),
11179 target_timeslice_syscalls_only: true,
11180 max_timeslice: nz(20_000),
11181 ..Default::default()
11182 };
11183 let mut state = ThreadState::new(DetPid::from_raw(1), &targeted, ());
11184 state.next_timeslice(&targeted);
11185 state.preemption_points = Some(ThreadHistory::new().into_iter());
11186 state.thread_logical_time.add_rcbs(500);
11187 assert!(state.timeslice_expired());
11188 assert!(state.instruction_trap_ends_timeslice(&targeted));
11189 }
11190
11191 #[test]
11192 fn child_rng_distinguishes_adjacent_thread_ids() {
11193 let parent = Pcg64Mcg::seed_from_u64(0);
11194 let mut even = thread_rng_from_parent("test", &parent, DetTid::from_raw(8));
11195 let mut odd = thread_rng_from_parent("test", &parent, DetTid::from_raw(9));
11196
11197 let even_values: [u64; 4] = std::array::from_fn(|_| even.next_u64());
11198 let odd_values: [u64; 4] = std::array::from_fn(|_| odd.next_u64());
11199 assert_ne!(even_values, odd_values);
11200 }
11201
11202 #[test]
11203 fn child_rng_uses_high_entropy_bits() {
11204 let parent = Pcg64Mcg::seed_from_u64(0);
11205 let mut low = thread_rng_from_parent_entropy("test", &parent, 1);
11206 let mut high = thread_rng_from_parent_entropy("test", &parent, (1_u128 << 64) | 1);
11207
11208 let low_values: [u64; 4] = std::array::from_fn(|_| low.next_u64());
11209 let high_values: [u64; 4] = std::array::from_fn(|_| high.next_u64());
11210 assert_ne!(low_values, high_values);
11211 }
11212
11213 #[test]
11214 fn child_rng_pedigree_uses_the_full_unbounded_path() {
11215 let parent_rng = Pcg64Mcg::seed_from_u64(0);
11216 let mut long_path = Pedigree::new();
11217 for index in 0..2_048 {
11218 let (parent, child) = long_path.fork();
11219 long_path = if index % 2 == 0 { child } else { parent };
11220 }
11221 let (left, right) = long_path.fork();
11222 let mut left_rng =
11223 thread_rng_from_parent_pedigree("test", &parent_rng, &left, ChildRngStream::User);
11224 let mut right_rng =
11225 thread_rng_from_parent_pedigree("test", &parent_rng, &right, ChildRngStream::User);
11226
11227 let left_values: [u64; 4] = std::array::from_fn(|_| left_rng.next_u64());
11228 let right_values: [u64; 4] = std::array::from_fn(|_| right_rng.next_u64());
11229 assert_ne!(left_values, right_values);
11230 }
11231
11232 #[test]
11233 fn child_rng_pedigree_separates_user_and_chaos_streams() {
11234 let parent_rng = Pcg64Mcg::seed_from_u64(0);
11235 let (_, child) = Pedigree::new().fork();
11236 let mut user_rng = thread_rng_from_parent_pedigree(
11237 "same logging label",
11238 &parent_rng,
11239 &child,
11240 ChildRngStream::User,
11241 );
11242 let mut chaos_rng = thread_rng_from_parent_pedigree(
11243 "same logging label",
11244 &parent_rng,
11245 &child,
11246 ChildRngStream::Chaos,
11247 );
11248
11249 let user_values: [u64; 4] = std::array::from_fn(|_| user_rng.next_u64());
11250 let chaos_values: [u64; 4] = std::array::from_fn(|_| chaos_rng.next_u64());
11251 assert_ne!(user_values, chaos_values);
11252 }
11253}
11254
11255pub fn thread_rng_from_parent(msg: &str, parent: &Pcg64Mcg, child: DetTid) -> Pcg64Mcg {
11260 thread_rng_from_parent_entropy_labeled(msg, parent, child.as_raw() as u32 as u128, "tid")
11261}
11262
11263#[derive(Clone, Copy, Debug)]
11264pub(crate) enum ChildRngStream {
11265 User,
11266 Chaos,
11267}
11268
11269impl ChildRngStream {
11270 fn domain(self) -> &'static [u8] {
11271 match self {
11272 Self::User => b"user",
11273 Self::Chaos => b"chaos",
11274 }
11275 }
11276}
11277
11278pub(crate) fn thread_rng_from_parent_pedigree(
11280 msg: &str,
11281 parent: &Pcg64Mcg,
11282 child: &Pedigree,
11283 stream: ChildRngStream,
11284) -> Pcg64Mcg {
11285 let bits = child.raw();
11286 let mut packed = Vec::with_capacity(bits.len().div_ceil(8));
11287 let mut byte = 0_u8;
11288 for (index, bit) in bits.iter().enumerate() {
11289 if *bit {
11290 byte |= 1 << (index % 8);
11291 }
11292 if index % 8 == 7 {
11293 packed.push(byte);
11294 byte = 0;
11295 }
11296 }
11297 if !bits.len().is_multiple_of(8) {
11298 packed.push(byte);
11299 }
11300
11301 let mut hasher = Sha256::new();
11308 hasher.update(b"hermit-child-rng-pedigree-v1\0");
11309 hasher.update(stream.domain());
11310 hasher.update([0]);
11311 hasher.update((bits.len() as u64).to_le_bytes());
11312 hasher.update(&packed);
11313 let digest = hasher.finalize();
11314
11315 let mut seed = <Pcg64Mcg as SeedableRng>::Seed::default();
11316 parent.clone().fill_bytes(seed.as_mut());
11317 for (seed_byte, pedigree_byte) in seed.iter_mut().zip(digest) {
11318 *seed_byte ^= pedigree_byte;
11319 }
11320 detlog!(
11321 "RNG {} seeding child {:?} pedigree {}: {:?} from parent {:?}",
11322 msg,
11323 stream,
11324 child,
11325 seed,
11326 parent
11327 );
11328 let mut rng = Pcg64Mcg::from_seed(seed);
11329 rng.next_u64();
11330 rng.next_u64();
11331 rng.next_u64();
11332 rng.next_u64();
11333 rng
11334}
11335
11336#[cfg(test)]
11337mod native_exec_cut_tests {
11338 use super::*;
11339 use crate::native_exec::NativeExecCutError;
11340 use crate::native_exec::capture_prepared_native_exec;
11341 use crate::native_exec::tests::fixture;
11342 use crate::native_exec::tests::prepare;
11343 use crate::native_exec::tests::state_bytes;
11344 use crate::native_exec::validate_old_native_exec_cut;
11345
11346 #[test]
11347 fn native_exec_child_publications_refuse_every_runtime_token_state() {
11348 for completion in [None, Some(true), Some(false)] {
11349 let (tool, thread, binding) = fixture();
11350 let before = state_bytes(&thread);
11351 let child = DetPid::from_raw(43);
11352 let token = thread.prepare_child_cpu_publication(child);
11353 if let Some(published) = completion {
11354 token.finish(published);
11355 }
11356 assert_eq!(state_bytes(&thread), before);
11358 assert!(matches!(
11359 validate_old_native_exec_cut(&tool, &thread, &binding),
11360 Err(NativeExecCutError::LiveCapability("child_cpu_publications"))
11361 ));
11362 assert!(Arc::ptr_eq(
11363 thread
11364 .process_cpu_time
11365 .lock()
11366 .unwrap()
11367 .child_cpu_publications
11368 .get(&child)
11369 .unwrap(),
11370 &token
11371 ));
11372 }
11373 }
11374
11375 #[test]
11376 fn native_exec_historical_cpu_accounting_survives_without_live_publications() {
11377 let (tool, mut thread, binding) = fixture();
11378 {
11379 let mut cpu = thread.process_cpu_time.lock().unwrap();
11380 cpu.add_thread_delta(LogicalTime::from_nanos(17), LogicalTime::from_nanos(31));
11381 cpu.record_exited_child(
11382 DetPid::from_raw(43),
11383 ProcessCpuSnapshot {
11384 user: LogicalTime::from_nanos(19),
11385 system: LogicalTime::from_nanos(23),
11386 children_user: LogicalTime::from_nanos(29),
11387 children_system: LogicalTime::from_nanos(37),
11388 },
11389 );
11390 cpu.reap_child(DetPid::from_raw(43));
11391 cpu.record_exited_child(
11392 DetPid::from_raw(44),
11393 ProcessCpuSnapshot {
11394 user: LogicalTime::from_nanos(41),
11395 system: LogicalTime::from_nanos(47),
11396 ..ProcessCpuSnapshot::default()
11397 },
11398 );
11399 }
11400 let before = serde_json::to_value(&*thread.process_cpu_time.lock().unwrap()).unwrap();
11401 let receipt = validate_old_native_exec_cut(&tool, &thread, &binding).unwrap();
11402 prepare(&mut thread, &binding);
11403 let bytes = bincode::serde::encode_to_vec(
11404 capture_prepared_native_exec(&tool, &thread, &binding, &receipt).unwrap(),
11405 bincode::config::legacy(),
11406 )
11407 .unwrap();
11408 let (payload, used): (crate::native_exec::DetcoreExecSnapshotV2, _) =
11409 bincode::serde::decode_from_slice(&bytes, bincode::config::legacy()).unwrap();
11410 assert_eq!(used, bytes.len());
11411 let (_, restored) = payload.into_validated_parts(&binding, &tool.cfg).unwrap();
11412 assert_eq!(
11413 serde_json::to_value(&*restored.process_cpu_time.lock().unwrap()).unwrap(),
11414 before
11415 );
11416 let cpu = restored.process_cpu_time.lock().unwrap();
11417 assert_eq!(cpu.snapshot.children_user, LogicalTime::from_nanos(48));
11418 assert_eq!(cpu.snapshot.children_system, LogicalTime::from_nanos(60));
11419 assert_eq!(cpu.exited_children.len(), 1);
11420 assert!(cpu.child_cpu_publications.is_empty());
11421 }
11422
11423 #[test]
11424 fn native_exec_old_accept_waits_and_invalid_table_slots_refuse() {
11425 let (tool, thread, binding) = fixture();
11426 let operation = {
11427 let mut table = thread.file_metadata.lock().unwrap();
11428 table
11429 .add_fd(binding.dettid, 33, OFlag::O_CLOEXEC, FdType::Socket, None)
11430 .unwrap();
11431 table.add_accept_wait(33)
11432 };
11433 assert!(matches!(
11434 validate_old_native_exec_cut(&tool, &thread, &binding),
11435 Err(NativeExecCutError::LiveCapability("accept_waits"))
11436 ));
11437 let mut table = thread.file_metadata.lock().unwrap();
11438 assert!(table.for_exec(binding.dettid).accept_waits.is_empty());
11439 assert_eq!(table.accept_waits, [(operation, 33)]);
11440 table.remove_accept_wait(operation);
11441 assert_eq!(table.next_accept_wait, operation + 1);
11444 table
11445 .validate_native_exec_cut(binding.old_files, false)
11446 .unwrap();
11447 let mut prepared = table.for_exec(binding.dettid);
11448 prepared
11449 .validate_native_exec_cut(binding.prepared_files(), true)
11450 .unwrap();
11451 prepared.next_accept_wait = 1;
11452 assert_eq!(
11453 prepared.validate_native_exec_cut(binding.prepared_files(), true),
11454 Err(NativeExecCutError::PreparedState("next_accept_wait"))
11455 );
11456 let only = table.file_handles.remove(&33).unwrap();
11457 table.file_handles.insert(34, only);
11458 assert_eq!(
11459 table.validate_native_exec_cut(binding.old_files, false),
11460 Err(NativeExecCutError::Descriptor("slot number"))
11461 );
11462 table.file_handles.clear();
11463 table.file_handles.insert(
11464 -1,
11465 DetFd::new(
11466 -1,
11467 OFlag::empty(),
11468 FdType::Rng,
11469 OpenFileId::new(binding.dettid, 1),
11470 ),
11471 );
11472 assert_eq!(
11473 table.validate_native_exec_cut(binding.old_files, false),
11474 Err(NativeExecCutError::Descriptor("slot number"))
11475 );
11476 }
11477
11478 #[test]
11479 fn native_exec_each_skipped_or_live_thread_transient_refuses() {
11480 for field in 0..26 {
11481 let (tool, mut thread, binding) = fixture();
11482 match field {
11483 0 => {
11484 thread.parent_process_cpu_time =
11485 Some(Arc::new(Mutex::new(ProcessCpuTime::default())))
11486 }
11487 1 => thread.parent_cpu_publication = Some(ChildCpuPublication::pending()),
11488 2 => {
11489 thread.restart_block = Some(crate::syscalls::helpers::RestartBlock {
11490 rip: 0x1234,
11491 deadline: None,
11492 call: crate::syscalls::helpers::RestartCall::Poll(
11493 reverie::syscalls::Poll::new(),
11494 ),
11495 })
11496 }
11497 3 => thread.signal_register_projection = Some(unsafe { std::mem::zeroed() }),
11498 4 => thread.clone_flags = Some(CloneFlags::empty()),
11499 5 => {
11500 thread.pending_vfork = Some(PendingVfork {
11501 parent_dettid: binding.dettid,
11502 op: ExternalOpId::new(binding.dettid, 0),
11503 parent_detpid: binding.detpid,
11504 child_tid_addr: 0,
11505 flags: CloneFlags::CLONE_VFORK,
11506 exit_signal: libc::SIGCHLD,
11507 child_priority_entropy: None,
11508 user_namespace_owner_may_differ: false,
11509 })
11510 }
11511 6 => thread.own_signal_turn_hold = Some(libc::SIGALRM),
11512 7 => thread.sigalrm_handled = true,
11513 8 => thread.handler_restore_rt_reported = 1,
11514 9 => {
11515 thread.initialized_random_auxv = Some((
11516 crate::random::InitialImage {
11517 pid: binding.physical_tid,
11518 start_time_ticks: 1,
11519 at_random: 0x234000,
11520 },
11521 [7; 16],
11522 Vec::new(),
11523 ))
11524 }
11525 10 => thread.uncharged_bootstrap_syscalls = 1,
11526 11 => thread.in_uncharged_bootstrap_syscall = true,
11527 12 => thread.yield_after_futex_wake = true,
11528 13 => {
11529 thread.interrupt_at.insert(1);
11530 }
11531 14 => thread.replay_rcb_end = Some(1),
11532 15 => thread.last_rcb_timer = Some(1),
11533 16 => thread.last_rcb_timer_is_max = true,
11534 17 => thread.max_timeslice_end = Some(LogicalTime::from_nanos(1)),
11535 18 => thread.chaos_slowdown_active = true,
11536 19 => thread.nested_pid_namespace = true,
11537 20 => thread.thread_start_entered = false,
11538 21 => thread.past_global_first_execve = false,
11539 22 => {
11540 thread.last_accounted_system_time =
11541 thread.thread_logical_time.system_cpu_time() + Duration::from_nanos(1)
11542 }
11543 23 => {
11544 thread.preemption_points =
11545 Some(crate::preemptions::ThreadHistory::new().into_iter())
11546 }
11547 24 => thread.pending_chaos_epochs.push(ChaosEpochTransition {
11548 logical_time: LogicalTime::from_nanos(1),
11549 epoch: 1,
11550 factor: RcbTimeMultiplier::from_f64(1.5),
11551 }),
11552 25 => {
11553 thread.signal_task_identity = Some(reverie::SignalTaskIdentity {
11554 process: reverie::SignalProcessId {
11555 tgid: reverie::Pid::from_raw(binding.physical_tid),
11556 generation: 0,
11557 },
11558 tid: reverie::Pid::from_raw(binding.physical_tid),
11559 task_generation: 0,
11560 })
11561 }
11562 _ => unreachable!(),
11563 }
11564 assert!(
11565 validate_old_native_exec_cut(&tool, &thread, &binding).is_err(),
11566 "field {field}"
11567 );
11568 }
11569 }
11570
11571 #[test]
11572 fn native_exec_robust_and_prepared_memory_must_match_the_actual_cut() {
11573 for field in 0..4 {
11574 let (tool, mut thread, binding) = fixture();
11575 thread.record_robust_list_head(Some(0x1000));
11576 {
11577 let mut robust = thread.robust_list_process.lock().unwrap();
11578 match field {
11579 0 => {
11580 robust.heads.insert(DetTid::from_raw(43), 0x2000);
11581 }
11582 1 => {
11583 robust.pending.insert(
11584 binding.dettid,
11585 PendingRobustListWakes {
11586 reason: RobustListExit::ExitGroup,
11587 wakes: Vec::new(),
11588 },
11589 );
11590 }
11591 2 => {
11592 robust
11593 .matched_physical_exits
11594 .insert(binding.dettid, thread.thread_logical_time.clone());
11595 }
11596 3 => robust.exec_pending.push((
11597 binding.dettid,
11598 RobustListWake {
11599 futex: FutexID::private(binding.old_mm, 0x3000),
11600 },
11601 )),
11602 _ => unreachable!(),
11603 }
11604 }
11605 assert!(
11606 validate_old_native_exec_cut(&tool, &thread, &binding).is_err(),
11607 "old field {field}"
11608 );
11609 }
11610 for field in 0..8 {
11611 let (tool, mut thread, binding) = fixture();
11612 let receipt = validate_old_native_exec_cut(&tool, &thread, &binding).unwrap();
11613 prepare(&mut thread, &binding);
11614 match field {
11615 0 => thread.memory_metadata.lock().unwrap().observe_brk(0x4000),
11616 1 => thread.memory_metadata.lock().unwrap().map_anonymous(
11617 binding.prepared_mm(),
11618 0x5000,
11619 0x1000,
11620 ),
11621 2 => thread.record_robust_list_head(Some(0x1000)),
11622 3 => thread.stage_robust_list_wakes_for_exec(vec![(
11623 binding.dettid,
11624 vec![RobustListWake {
11625 futex: FutexID::private(binding.prepared_mm(), 0x3000),
11626 }],
11627 )]),
11628 4 => thread.stage_robust_list_wakes_for_exec(vec![(
11629 DetTid::from_raw(43),
11630 vec![RobustListWake {
11631 futex: FutexID::private(binding.old_mm, 0x3000),
11632 }],
11633 )]),
11634 5 => thread.file_metadata.lock().unwrap().files_id = binding.old_files,
11635 6 => {
11636 thread.file_metadata.lock().unwrap().file_handles.insert(
11637 3,
11638 DetFd::new(
11639 3,
11640 OFlag::O_CLOEXEC,
11641 FdType::Rng,
11642 OpenFileId::new(binding.dettid, 1),
11643 ),
11644 );
11645 }
11646 7 => thread.mm_id = binding.old_mm,
11647 _ => unreachable!(),
11648 }
11649 assert!(
11650 capture_prepared_native_exec(&tool, &thread, &binding, &receipt).is_err(),
11651 "prepared field {field}"
11652 );
11653 }
11654 }
11655
11656 #[test]
11657 fn native_exec_model_lock_contention_is_a_refusal_without_mutation() {
11658 let (tool, thread, binding) = fixture();
11659 let before = state_bytes(&thread);
11660 macro_rules! locked {
11661 ($field:ident) => {{
11662 let guard = thread.$field.lock().unwrap();
11663 assert!(matches!(
11664 validate_old_native_exec_cut(&tool, &thread, &binding),
11665 Err(NativeExecCutError::LockUnavailable(_))
11666 ));
11667 drop(guard);
11668 assert_eq!(state_bytes(&thread), before);
11669 }};
11670 }
11671 locked!(file_metadata);
11672 locked!(process_cpu_time);
11673 locked!(guest_clock);
11674 locked!(resource_limits);
11675 locked!(posix_timers);
11676 locked!(robust_list_process);
11677 validate_old_native_exec_cut(&tool, &thread, &binding).unwrap();
11678 }
11679}
11680
11681fn thread_rng_from_parent_entropy(msg: &str, parent: &Pcg64Mcg, entropy: u128) -> Pcg64Mcg {
11682 thread_rng_from_parent_entropy_labeled(msg, parent, entropy, "entropy")
11683}
11684
11685fn thread_rng_from_parent_entropy_labeled(
11686 msg: &str,
11687 parent: &Pcg64Mcg,
11688 entropy: u128,
11689 identity_kind: &str,
11690) -> Pcg64Mcg {
11691 let mut seed = <Pcg64Mcg as SeedableRng>::Seed::default();
11693 parent.clone().fill_bytes(seed.as_mut());
11695 detlog!("RNG {} Generated new seed {:?}", msg, seed);
11696 let entropy_bytes = entropy.to_le_bytes();
11700 for (seed_byte, entropy_byte) in seed[4..].iter_mut().zip(entropy_bytes) {
11701 *seed_byte ^= entropy_byte;
11702 }
11703 detlog!(
11704 "RNG {} seeding child {} {}: {:?} from parent {:?}",
11705 msg,
11706 identity_kind,
11707 entropy,
11708 seed,
11709 parent
11710 );
11711 let mut rng = Pcg64Mcg::from_seed(seed);
11712 rng.next_u64();
11715 rng.next_u64();
11716 rng.next_u64();
11717 rng.next_u64();
11718 rng
11719}
11720
11721fn own_fd_link(fd: RawFd) -> Option<Vec<u8>> {
11731 let link = std::ffi::CString::new(format!("/proc/thread-self/fd/{fd}")).ok()?;
11733 let mut target = [0_u8; 256];
11734 let length = unsafe {
11736 crate::util::raw_syscall(
11737 libc::SYS_readlinkat,
11738 [
11739 libc::AT_FDCWD as u64,
11740 link.as_ptr() as u64,
11741 target.as_mut_ptr() as u64,
11742 target.len() as u64,
11743 0,
11744 0,
11745 ],
11746 )
11747 };
11748 let length = usize::try_from(length)
11749 .ok()
11750 .filter(|length| *length < target.len())?;
11751 Some(target[..length].to_vec())
11752}
11753
11754pub(crate) fn kernel_record_file_link(
11764 raw_stat: &libc::stat,
11765 link: &[u8],
11766) -> Option<std::path::PathBuf> {
11767 if raw_stat.st_mode & libc::S_IFMT != libc::S_IFREG
11768 || raw_stat.st_size != 0
11769 || raw_stat.st_nlink == 0
11770 {
11771 return None;
11772 }
11773 let path =
11774 std::path::Path::new(<std::ffi::OsStr as std::os::unix::ffi::OsStrExt>::from_bytes(link));
11775 crate::procfs::ProcfsFile::from_path(path)
11776 .filter(|file| file.follows_record_sequence() && !file.is_task_comm())
11777 .map(|_| path.to_path_buf())
11778}
11779
11780fn fixed_procfs_behind(fd: RawFd) -> Option<crate::procfs::ProcfsFile> {
11783 let target = own_fd_link(fd)?;
11784 let path = std::path::Path::new(
11785 <std::ffi::OsStr as std::os::unix::ffi::OsStrExt>::from_bytes(&target),
11786 );
11787 crate::procfs::ProcfsFile::from_path(path).filter(crate::procfs::ProcfsFile::snapshot_is_fixed)
11788}
11789
11790fn placeholder_behind(
11791 fd: RawFd,
11792 files: crate::procfs::RenderedFiles,
11793) -> Option<(&'static std::path::Path, Vec<u8>)> {
11794 let seals = unsafe {
11796 crate::util::raw_syscall(
11797 libc::SYS_fcntl,
11798 [fd as u64, libc::F_GET_SEALS as u64, 0, 0, 0, 0],
11799 )
11800 };
11801 let wanted = i64::from(crate::syscalls::rendered_procfs::PLACEHOLDER_SEALS);
11803 if seals != -i64::from(libc::EBADF) && (seals < 0 || seals & wanted != wanted) {
11804 return None;
11805 }
11806 let link = std::ffi::CString::new(format!("/proc/thread-self/fd/{fd}")).ok()?;
11808 let mut target = [0_u8; crate::procfs::PLACEHOLDER_LINK_CAPACITY];
11809 let length = unsafe {
11811 crate::util::raw_syscall(
11812 libc::SYS_readlinkat,
11813 [
11814 libc::AT_FDCWD as u64,
11815 link.as_ptr() as u64,
11816 target.as_mut_ptr() as u64,
11817 target.len() as u64,
11818 0,
11819 0,
11820 ],
11821 )
11822 };
11823 let length = usize::try_from(length).ok()?.min(target.len());
11824 crate::procfs::placeholder_link(&target[..length], files)
11825}
11826
11827#[cfg(test)]
11828mod native_main_state_tests {
11829 use super::*;
11830 use crate::native_exec::capture_prepared_native_exec;
11831 use crate::native_exec::tests::fixture;
11832 use crate::native_exec::tests::prepare;
11833 use crate::native_exec::tests::state_bytes;
11834 use crate::native_exec::validate_old_native_exec_cut;
11835 fn snapshot(
11836 tool: &crate::Detcore<crate::native_exec::NoopTool>,
11837 thread: &ThreadState<()>,
11838 binding: &crate::native_exec::NativeExecCutBinding,
11839 receipt: &crate::native_exec::NativeExecPreparationReceipt,
11840 ) -> crate::native_exec::DetcoreExecSnapshotV2 {
11841 let bytes = bincode::serde::encode_to_vec(
11842 capture_prepared_native_exec(tool, thread, binding, receipt).unwrap(),
11843 bincode::config::legacy(),
11844 )
11845 .unwrap();
11846 crate::native_exec::tests::decoded(&bytes)
11847 }
11848
11849 #[test]
11850 fn native_main_claimed_sigchld_refuses_live_and_decoded_without_reset() {
11851 let (tool, mut thread, binding) = fixture();
11852 thread.claimed_sigchld = true;
11853 let before = state_bytes(&thread);
11854 assert!(matches!(
11855 validate_old_native_exec_cut(&tool, &thread, &binding),
11856 Err(crate::native_exec::NativeExecCutError::LiveCapability(
11857 "claimed SIGCHLD"
11858 ))
11859 ));
11860 assert_eq!(state_bytes(&thread), before);
11861 thread.claimed_sigchld = false;
11862 let receipt = validate_old_native_exec_cut(&tool, &thread, &binding).unwrap();
11863 prepare(&mut thread, &binding);
11864 let mut payload = snapshot(&tool, &thread, &binding, &receipt);
11865 crate::native_exec::tests::thread_for_test(&mut payload).claimed_sigchld = true;
11866 assert!(matches!(
11867 payload.validate(&binding, &tool.cfg),
11868 Err(crate::native_exec::NativeExecCutError::LiveCapability(
11869 "claimed SIGCHLD"
11870 ))
11871 ));
11872 assert!(crate::native_exec::tests::thread_for_test(&mut payload).claimed_sigchld);
11873 }
11874
11875 fn mark_barrier(process: &mut RobustListProcessState, field: usize, owner: DetTid) {
11876 match field {
11877 0 => {
11878 process.tool_walked.insert(owner);
11879 }
11880 1 => process.exit_group_filed = true,
11881 2 => {
11882 process.exit_group_filers.insert(owner);
11883 }
11884 3 => process.exit_group_walker = Some(owner),
11885 4 => process.exit_group_walked = true,
11886 _ => unreachable!(),
11887 }
11888 }
11889
11890 #[test]
11891 fn native_main_robust_barrier_state_refuses_old_prepared_and_decoded() {
11892 for field in 0..5 {
11893 let (tool, thread, binding) = fixture();
11894 mark_barrier(
11895 &mut thread.robust_list_process.lock().unwrap(),
11896 field,
11897 binding.dettid,
11898 );
11899 let before = state_bytes(&thread);
11900 assert!(
11901 validate_old_native_exec_cut(&tool, &thread, &binding).is_err(),
11902 "old {field}"
11903 );
11904 assert_eq!(state_bytes(&thread), before);
11905
11906 let (tool, mut thread, binding) = fixture();
11907 let receipt = validate_old_native_exec_cut(&tool, &thread, &binding).unwrap();
11908 prepare(&mut thread, &binding);
11909 let mut payload = snapshot(&tool, &thread, &binding, &receipt);
11910 mark_barrier(
11911 &mut thread.robust_list_process.lock().unwrap(),
11912 field,
11913 binding.dettid,
11914 );
11915 let before = state_bytes(&thread);
11916 assert!(
11917 capture_prepared_native_exec(&tool, &thread, &binding, &receipt).is_err(),
11918 "prepared {field}"
11919 );
11920 assert_eq!(state_bytes(&thread), before);
11921 let decoded = crate::native_exec::tests::thread_for_test(&mut payload);
11922 mark_barrier(
11923 &mut decoded.robust_list_process.lock().unwrap(),
11924 field,
11925 binding.dettid,
11926 );
11927 let before = state_bytes(decoded);
11928 assert!(
11929 payload.validate(&binding, &tool.cfg).is_err(),
11930 "decoded {field}"
11931 );
11932 assert_eq!(
11933 state_bytes(crate::native_exec::tests::thread_for_test(&mut payload)),
11934 before
11935 );
11936 }
11937 }
11938
11939 #[test]
11940 fn native_main_nanosleep_restart_refuses_even_when_serde_skips_it() {
11941 for deadline in [None, Some(crate::types::LogicalTime::from_nanos(4567))] {
11942 let (tool, mut thread, binding) = fixture();
11943 let before = state_bytes(&thread);
11944 thread.restart_block = Some(crate::syscalls::helpers::RestartBlock {
11945 rip: 0x1234,
11946 deadline,
11947 call: crate::syscalls::helpers::RestartCall::Nanosleep(
11948 reverie::syscalls::Nanosleep::new().into(),
11949 ),
11950 });
11951 assert_eq!(state_bytes(&thread), before);
11952 assert!(matches!(
11953 validate_old_native_exec_cut(&tool, &thread, &binding),
11954 Err(crate::native_exec::NativeExecCutError::LiveCapability(
11955 "restart/projection"
11956 ))
11957 ));
11958 assert!(thread.restart_block.is_some());
11959 }
11960 }
11961
11962 #[test]
11963 fn native_main_rng_promotion_history_survives_snapshot_and_some_empty() {
11964 use std::os::fd::AsRawFd;
11965 crate::fd::install_test_shared_open_file_channel();
11966 {
11967 let (tool, mut thread, binding) = fixture();
11968 let local = std::fs::File::open("/dev/urandom").unwrap();
11969 thread
11970 .add_fd(local.as_raw_fd(), OFlag::O_RDONLY, FdType::Rng, None)
11971 .unwrap();
11972 assert!(thread.shared_open_files_for_exec().unwrap().is_none());
11973 let receipt = validate_old_native_exec_cut(&tool, &thread, &binding).unwrap();
11974 prepare(&mut thread, &binding);
11975 let (_, restored) = snapshot(&tool, &thread, &binding, &receipt)
11976 .into_validated_parts(&binding, &tool.cfg)
11977 .unwrap();
11978 assert!(restored.shared_open_files_for_exec().unwrap().is_none());
11979 }
11980 let (tool, mut thread, binding) = fixture();
11981 let creator = DetTid::from_raw(930001);
11982 thread.set_open_file_creator(creator);
11983 let file = std::fs::File::open("/dev/urandom").unwrap();
11984 let alias = nix::unistd::dup(&file).unwrap();
11985 let cloexec = nix::unistd::dup(&file).unwrap();
11986 let distinct = std::fs::File::open("/dev/urandom").unwrap();
11987 let (fd, alias_fd, cloexec_fd, distinct_fd) = (
11988 file.as_raw_fd(),
11989 alias.as_raw_fd(),
11990 cloexec.as_raw_fd(),
11991 distinct.as_raw_fd(),
11992 );
11993 thread
11994 .add_fd(fd, OFlag::O_RDONLY, FdType::Rng, None)
11995 .unwrap();
11996 thread.dup_fd(fd, alias_fd, OFlag::empty()).unwrap();
11997 thread.dup_fd(fd, cloexec_fd, OFlag::O_CLOEXEC).unwrap();
11998 thread
11999 .add_fd(distinct_fd, OFlag::O_RDONLY, FdType::Rng, None)
12000 .unwrap();
12001 assert!(
12002 thread.shared_open_files_for_exec().unwrap().is_none(),
12003 "local RNG is not promotion"
12004 );
12005 let high = thread
12006 .metadata()
12007 .allocate_open_file_id(creator, FdType::Rng);
12008 let high_socket = thread
12009 .metadata()
12010 .allocate_open_file_id(creator, FdType::Socket);
12011 thread.promote_shared_open_files().unwrap();
12012 let expected = thread.shared_open_files_for_exec().unwrap().unwrap();
12013 assert_eq!(expected.creator, creator);
12014 assert_eq!(expected.handles[&fd].id, expected.handles[&alias_fd].id);
12015 assert_ne!(expected.handles[&fd].id, expected.handles[&distinct_fd].id);
12016 assert!(!expected.handles.contains_key(&cloexec_fd));
12017 let old_table = Arc::clone(&thread.file_metadata);
12018 let receipt = validate_old_native_exec_cut(&tool, &thread, &binding).unwrap();
12019 prepare(&mut thread, &binding);
12020 assert!(
12021 old_table
12022 .lock()
12023 .unwrap()
12024 .file_handles
12025 .contains_key(&cloexec_fd)
12026 );
12027 let (_, restored) = snapshot(&tool, &thread, &binding, &receipt)
12028 .into_validated_parts(&binding, &tool.cfg)
12029 .unwrap();
12030 assert_eq!(
12031 restored.shared_open_files_for_exec().unwrap(),
12032 Some(expected)
12033 );
12034 assert_eq!(restored.open_file_creator, Some(creator));
12035 assert_eq!(
12036 restored.metadata().next_open_file_sequence,
12037 thread.metadata().next_open_file_sequence
12038 );
12039 assert_eq!(
12040 restored.metadata().next_socket_open_file_sequence,
12041 thread.metadata().next_socket_open_file_sequence
12042 );
12043 restored.metadata().file_handles[&alias_fd]
12044 .with_random_device_stream::<Errno>(|offset| {
12045 assert_eq!(offset, 0);
12046 Ok(11)
12047 })
12048 .unwrap();
12049 thread.metadata().file_handles[&fd]
12050 .with_random_device_stream::<Errno>(|offset| {
12051 assert_eq!(offset, 11);
12052 Ok(0)
12053 })
12054 .unwrap();
12055 for slot in [fd, alias_fd, distinct_fd] {
12057 thread.remove_fd(slot);
12058 }
12059 let empty = thread.shared_open_files_for_exec().unwrap().unwrap();
12060 assert!(empty.handles.is_empty());
12061 assert_eq!(empty.creator, creator);
12062 assert_ne!(OpenFileId::new(creator, empty.next_sequence), high);
12063 assert_ne!(
12064 OpenFileId::new_socket(creator, empty.next_socket_sequence),
12065 high_socket
12066 );
12067 let old_files = thread.metadata().files_id;
12069 let binding2 = crate::native_exec::NativeExecCutBinding {
12070 old_mm: thread.mm_id,
12071 old_files,
12072 ..binding
12073 };
12074 let receipt2 = validate_old_native_exec_cut(&tool, &thread, &binding2).unwrap();
12075 prepare(&mut thread, &binding2);
12076 let (_, again) = snapshot(&tool, &thread, &binding2, &receipt2)
12077 .into_validated_parts(&binding2, &tool.cfg)
12078 .unwrap();
12079 assert_eq!(again.shared_open_files_for_exec().unwrap(), Some(empty));
12080 }
12081}
12082pub(crate) const EDITED_RECORD_HINT: &str = "An edited record, not the recording of this run's \
12089 schedule, needs --replay-edited-preemptions.";
12090
12091#[cold]
12097pub(crate) fn report_stale_replay_filing(
12098 panic_on_rcb_overshoot: bool,
12099 dettid: DetTid,
12100 slice: u64,
12101 time: LogicalTime,
12102 last: LogicalTime,
12103) {
12104 let message = format!(
12105 "{} preemption replay: thread {} would end slice T{} with a change point at {}, not \
12106 after its last change point at {}: the slice closed with no time in it, which the \
12107 replayed record never does. The thread files no change point here and runs on as \
12108 its final slice; the rest of this run does not replay the record exactly. {}",
12109 "HERMIT_REPLAY_DEPARTURE:", dettid, slice, time, last, EDITED_RECORD_HINT,
12110 );
12111 if panic_on_rcb_overshoot {
12112 panic!("{message}");
12113 }
12114 tracing::error!("{message}");
12115}