1use procfs::process::Process;
10use reverie::Error;
11use reverie::Guest;
12use reverie::syscalls;
13use reverie::syscalls::Errno;
14use reverie::syscalls::MemoryAccess;
15use reverie::syscalls::Sysno;
16
17use crate::Detcore;
18use crate::RecordOrReplay;
19use crate::tool_global::thread_observe_time;
20use crate::tool_local::ResourceLimit;
21
22const CLOCK_TICKS_PER_SECOND: u64 = 100;
24const NANOS_PER_CLOCK_TICK: u64 = 1_000_000_000 / CLOCK_TICKS_PER_SECOND;
25
26pub(crate) fn clock_ticks(duration: crate::types::LogicalTime) -> u64 {
30 duration.as_nanos() / NANOS_PER_CLOCK_TICK
31}
32
33fn clock_t_from_ticks(ticks: u64) -> libc::clock_t {
34 ticks as libc::clock_t
35}
36
37const NANOS_PER_SECOND: u64 = 1_000_000_000;
38const NANOS_PER_MICROSECOND: u64 = 1_000;
39
40fn timeval_from_logical(duration: crate::types::LogicalTime) -> libc::timeval {
48 let nanos = duration.as_nanos();
49 libc::timeval {
50 tv_sec: (nanos / NANOS_PER_SECOND) as libc::time_t,
51 tv_usec: ((nanos % NANOS_PER_SECOND) / NANOS_PER_MICROSECOND) as libc::suseconds_t,
52 }
53}
54
55fn logical_clock_ticks(
56 now: crate::types::LogicalTime,
57 boot: crate::types::LogicalTime,
58 uptime_offset_seconds: u64,
59) -> libc::clock_t {
60 let ticks = uptime_offset_seconds
61 .wrapping_mul(CLOCK_TICKS_PER_SECOND)
62 .wrapping_add(clock_ticks(now - boot));
63 clock_t_from_ticks(ticks)
64}
65
66fn sysinfo_uptime_seconds(
72 now: crate::types::LogicalTime,
73 epoch: crate::types::LogicalTime,
74 uptime_offset_seconds: u64,
75) -> Result<u64, Error> {
76 let now_ns = now.as_nanos();
77 let epoch_ns = epoch.as_nanos();
78 let elapsed_ns = now_ns.checked_sub(epoch_ns).ok_or_else(|| {
79 Error::Tool(anyhow::anyhow!(
80 "sysinfo observed logical time {now_ns} ns before epoch {epoch_ns} ns"
81 ))
82 })?;
83 let seconds =
86 elapsed_ns / NANOS_PER_SECOND + u64::from(!elapsed_ns.is_multiple_of(NANOS_PER_SECOND));
87 Ok(uptime_offset_seconds.wrapping_add(seconds))
91}
92
93fn procfs_uptime_seconds(
101 now: crate::types::LogicalTime,
102 boot: crate::types::LogicalTime,
103 uptime_offset_seconds: u64,
104) -> u64 {
105 uptime_offset_seconds + (now - boot).as_secs()
106}
107
108fn procfs_boot_time_seconds(
121 boot: crate::types::LogicalTime,
122 uptime_offset_seconds: u64,
123) -> Option<i64> {
124 i64::try_from(i128::from(boot.as_secs()) - i128::from(uptime_offset_seconds)).ok()
125}
126
127fn prlimit_targets_current_process(
128 target_pid: i32,
129 deterministic_pid: Option<i32>,
130 physical_pid: i32,
131) -> bool {
132 target_pid == 0 || target_pid == deterministic_pid.unwrap_or(physical_pid)
133}
134
135fn validate_resource_limit_mutation(
136 resource: u32,
137 previous: ResourceLimit,
138 requested: ResourceLimit,
139) -> Result<(), Errno> {
140 if requested.current > requested.maximum {
141 return Err(Errno::EINVAL);
142 }
143 if requested == previous {
147 return Ok(());
148 }
149 if resource != reverie::syscalls::linux_abi::RLIMIT_STACK
152 && resource != reverie::syscalls::linux_abi::RLIMIT_NOFILE
153 && resource != reverie::syscalls::linux_abi::RLIMIT_CORE
154 {
155 return Err(Errno::EPERM);
156 }
157 if requested.maximum > previous.maximum {
158 return Err(Errno::EPERM);
159 }
160 Ok(())
161}
162
163fn warn_refused_cpu_limit<G: Guest<Detcore<T>>, T: RecordOrReplay>(
172 guest: &G,
173 call: &str,
174 resource: u32,
175 errno: Errno,
176) {
177 if resource == reverie::syscalls::linux_abi::RLIMIT_CPU
178 && errno == Errno::EPERM
179 && guest.config().in_guest_refuses_host_timed_producers
180 {
181 tracing::warn!(
182 "{}",
183 crate::host_timed_producers::rlimit_cpu_refused_warning(
184 call,
185 guest.thread_state().dettid.as_raw(),
186 )
187 );
188 }
189}
190
191pub(crate) fn record_early_limit_read(
196 call: crate::random::LimitReadCall,
197 resource: u32,
198 current: u64,
199 maximum: u64,
200) {
201 if let crate::random::LimitReadCall::Prlimit64 { pid } = call {
202 crate::detlog!(
203 "prlimit64: pid={pid}, resource={resource}, mutation=false, old={current}:{maximum}"
204 );
205 }
206}
207
208impl<T: RecordOrReplay> Detcore<T> {
209 pub async fn handle_getrlimit<G: Guest<Self>>(
213 &self,
214 guest: &mut G,
215 call: syscalls::Getrlimit,
216 ) -> Result<i64, Error> {
217 let resource = u32::try_from(call.resource()).map_err(|_| Errno::EINVAL)?;
218 let address = call.rlim().ok_or(Errno::EFAULT)?;
219 let limit = guest
220 .thread_state()
221 .resource_limits
222 .lock()
223 .expect("resource limits mutex poisoned")
224 .get(resource)
225 .ok_or(Errno::EINVAL)?;
226 let result = libc::rlimit {
227 rlim_cur: limit.current,
228 rlim_max: limit.maximum,
229 };
230 guest.memory().write_value(address, &result)?;
231 Ok(0)
232 }
233
234 pub async fn handle_setrlimit<G: Guest<Self>>(
238 &self,
239 guest: &mut G,
240 call: syscalls::Setrlimit,
241 ) -> Result<i64, Error> {
242 let resource = u32::try_from(call.resource()).map_err(|_| Errno::EINVAL)?;
243 let address = call.rlim().ok_or(Errno::EFAULT)?;
244 let requested: libc::rlimit = guest.memory().read_value(address)?;
245 let requested = ResourceLimit {
246 current: requested.rlim_cur,
247 maximum: requested.rlim_max,
248 };
249 let resource_limits = guest.thread_state().resource_limits.clone();
250 let previous = {
251 let mut limits = resource_limits
252 .lock()
253 .expect("resource limits mutex poisoned");
254 let previous = limits.get(resource).ok_or(Errno::EINVAL)?;
255 validate_resource_limit_mutation(resource, previous, requested).inspect_err(
256 |errno| warn_refused_cpu_limit(guest, "setrlimit", resource, *errno),
257 )?;
258 if requested != previous {
259 limits.set(resource, requested);
260 }
261 previous
262 };
263 self.publish_rss_limit(guest, resource, previous, requested)
264 .await;
265 Ok(0)
266 }
267
268 #[allow(clippy::unnecessary_cast)]
275 async fn publish_rss_limit<G: Guest<Self>>(
276 &self,
277 guest: &mut G,
278 resource: u32,
279 previous: ResourceLimit,
280 requested: ResourceLimit,
281 ) {
282 if resource == libc::RLIMIT_RSS as u32 && requested.current != previous.current {
283 let process = self.own_detpid(guest);
284 crate::tool_global::set_rss_limit(guest, process, requested.current).await;
285 }
286 }
287
288 pub async fn handle_prlimit64<G: Guest<Self>>(
299 &self,
300 guest: &mut G,
301 call: syscalls::Prlimit64,
302 ) -> Result<i64, Error> {
303 let resource = call.resource();
304 let resource_limits = guest.thread_state().resource_limits.clone();
305 if resource_limits
306 .lock()
307 .expect("resource limits mutex poisoned")
308 .get(resource)
309 .is_none()
310 {
311 return Err(Errno::EINVAL.into());
312 }
313
314 let requested = if let Some(address) = call.new_rlim() {
315 let limit: libc::rlimit64 = guest.memory().read_value(address)?;
316 Some(ResourceLimit {
317 current: limit.rlim_cur,
318 maximum: limit.rlim_max,
319 })
320 } else {
321 None
322 };
323
324 let pid = if call.pid() > 0 && guest.thread_state().nested_pid_namespace {
328 match crate::tool_global::guest_pid(guest, call.pid()).await {
329 crate::scheduler::pid_namespaces::GuestPidAnswer::Task { task, .. } => {
330 task.as_raw()
331 }
332 crate::scheduler::pid_namespaces::GuestPidAnswer::NoSuchPid => {
333 return Err(Errno::ESRCH.into());
334 }
335 crate::scheduler::pid_namespaces::GuestPidAnswer::Untranslatable(reason) => {
336 tracing::error!(
337 "[detcore, dtid {}] prlimit64 from a nested PID namespace is not supported: {} (https://github.com/rrnewton/hermit/issues/4155)",
338 guest.thread_state().dettid,
339 reason,
340 );
341 return self
342 .refuse_unserviceable_operation(guest, Sysno::prlimit64, Errno::ENOSYS)
343 .await;
344 }
345 }
346 } else {
347 call.pid()
348 };
349 let deterministic_pid = guest.thread_state().detpid.map(|detpid| detpid.as_raw());
350 if !prlimit_targets_current_process(pid, deterministic_pid, guest.pid().as_raw()) {
351 return Err(Errno::EPERM.into());
352 }
353
354 let previous = {
355 let mut limits = resource_limits
356 .lock()
357 .expect("resource limits mutex poisoned");
358 let previous = limits
359 .get(resource)
360 .expect("resource validity changed while handling prlimit64");
361
362 if let Some(requested) = requested {
363 validate_resource_limit_mutation(resource, previous, requested).inspect_err(
364 |errno| warn_refused_cpu_limit(guest, "prlimit64", resource, *errno),
365 )?;
366 if requested != previous {
367 limits.set(resource, requested);
368 }
369 }
370
371 previous
372 };
373
374 if let Some(requested) = requested {
375 self.publish_rss_limit(guest, resource, previous, requested)
376 .await;
377 }
378
379 if let Some(address) = call.old_rlim() {
380 let previous = libc::rlimit64 {
381 rlim_cur: previous.current,
382 rlim_max: previous.maximum,
383 };
384 guest.memory().write_value(address, &previous)?;
385 }
386
387 crate::detlog!(
388 "prlimit64: pid={pid}, resource={resource}, mutation={}, old={}:{}",
389 requested.is_some(),
390 previous.current,
391 previous.maximum
392 );
393 Ok(0)
394 }
395 pub async fn handle_getrusage<G: Guest<Self>>(
421 &self,
422 guest: &mut G,
423 call: syscalls::Getrusage,
424 ) -> Result<i64, Error> {
425 let who = call.who();
426 match who {
427 libc::RUSAGE_SELF | libc::RUSAGE_CHILDREN | libc::RUSAGE_THREAD => {}
428 _ => return Err(Errno::EINVAL.into()),
429 }
430
431 let usage_addr = call.usage().ok_or(Errno::EFAULT)?;
432
433 let mut usage: libc::rusage = unsafe { std::mem::zeroed() };
435
436 let (user, system) = match who {
437 libc::RUSAGE_THREAD => guest.thread_state_mut().thread_cpu_time(),
438 libc::RUSAGE_CHILDREN => {
439 let cpu = guest.thread_state_mut().process_cpu_time();
440 (cpu.children_user, cpu.children_system)
441 }
442 _ => {
444 let cpu = guest.thread_state_mut().process_cpu_time();
445 (cpu.user, cpu.system)
446 }
447 };
448 usage.ru_utime = timeval_from_logical(user);
449 usage.ru_stime = timeval_from_logical(system);
450
451 if matches!(who, libc::RUSAGE_SELF | libc::RUSAGE_THREAD) {
455 usage.ru_maxrss = self.guest_peak_rss_kb(guest) as libc::c_long;
456 }
457
458 guest.memory().write_value(usage_addr, &usage)?;
459 Ok(0)
460 }
461
462 pub async fn handle_times<G: Guest<Self>>(
471 &self,
472 guest: &mut G,
473 call: syscalls::Times,
474 ) -> Result<i64, Error> {
475 let now = thread_observe_time(guest).await;
476 let boot = crate::types::DetTime::new(&self.cfg).as_nanos();
477 let ticks = logical_clock_ticks(now, boot, self.cfg.sysinfo_uptime_offset);
478 let cpu = guest.thread_state_mut().process_cpu_time();
479
480 if let Some(address) = call.buf() {
481 let usage = libc::tms {
482 tms_utime: clock_t_from_ticks(clock_ticks(cpu.user)),
483 tms_stime: clock_t_from_ticks(clock_ticks(cpu.system)),
484 tms_cutime: clock_t_from_ticks(clock_ticks(cpu.children_user)),
485 tms_cstime: clock_t_from_ticks(clock_ticks(cpu.children_system)),
486 };
487 guest.memory().write_value(address, &usage)?;
488 }
489
490 Ok(ticks as i64)
491 }
492
493 fn guest_peak_rss_kb<G: Guest<Self>>(&self, guest: &G) -> u64 {
498 Process::new(guest.pid().as_raw())
499 .and_then(|process| process.status())
500 .ok()
501 .and_then(|status| status.vmhwm.or(status.vmrss))
502 .unwrap_or(0)
503 .max(1)
504 }
505
506 pub async fn handle_sysinfo<G: Guest<Self>>(
508 &self,
509 guest: &mut G,
510 call: syscalls::Sysinfo,
511 ) -> Result<i64, Error> {
512 let info_addr = call.info().ok_or(Errno::EFAULT)?;
513 let sys_info = self.collect_sysinfo(guest).await?;
514 let mut memory = guest.memory();
515
516 let bytes = sys_info.to_kernel_bytes();
519 memory.write_exact(info_addr.cast::<u8>(), &bytes)?;
520 Ok(0)
521 }
522
523 pub(super) async fn calculate_procfs_uptime<G: Guest<Self>>(
525 &self,
526 guest: &mut G,
527 ) -> Result<u64, Error> {
528 let global_time = thread_observe_time(guest).await;
529 Ok(procfs_uptime_seconds(
530 global_time,
531 crate::types::DetTime::new(&self.cfg).as_nanos(),
532 self.cfg.sysinfo_uptime_offset,
533 ))
534 }
535
536 pub(super) fn calculate_procfs_boot_time(&self) -> Result<i64, Error> {
542 procfs_boot_time_seconds(
543 crate::types::DetTime::new(&self.cfg).as_nanos(),
544 self.cfg.sysinfo_uptime_offset,
545 )
546 .ok_or_else(|| Errno::EOVERFLOW.into())
547 }
548
549 async fn collect_sysinfo<G: Guest<Self>>(
550 &self,
551 guest: &mut G,
552 ) -> Result<syscalls::SysInfo, Error> {
553 let memory = configured_memory(crate::machine::MachineDescription::of(&self.cfg).memory);
554 let now = thread_observe_time(guest).await;
555 let epoch = crate::types::DetTime::new(&self.cfg).as_nanos();
556 Ok(syscalls::SysInfo {
557 uptime: sysinfo_uptime_seconds(now, epoch, self.cfg.sysinfo_uptime_offset)?,
558 loads_1: 1,
559 loads_5: 1,
560 loads_15: 1,
561 total_ram: memory.total_ram,
562 free_ram: memory.free_ram,
563 buffer_ram: memory.buffer_ram,
564 shared_ram: memory.shared_ram,
565 total_swap: memory.total_swap,
566 free_swap: memory.free_swap,
567 procs: 1,
568 total_high: memory.total_high,
569 free_high: memory.free_high,
570 mem_unit: memory.mem_unit,
571 })
572 }
573}
574
575#[derive(Debug, PartialEq, Eq)]
578struct ConfiguredMemory {
579 total_ram: u64,
580 free_ram: u64,
581 buffer_ram: u64,
582 shared_ram: u64,
583 total_swap: u64,
584 free_swap: u64,
585 total_high: u64,
586 free_high: u64,
587 mem_unit: u32,
588}
589
590fn configured_memory(memory: u64) -> ConfiguredMemory {
597 ConfiguredMemory {
598 total_ram: memory,
599 free_ram: memory,
600 buffer_ram: 0,
601 shared_ram: 0,
602 total_swap: 0,
603 free_swap: 0,
604 total_high: 0,
605 free_high: 0,
606 mem_unit: 1,
607 }
608}
609
610#[cfg(test)]
611mod tests {
612 use super::*;
613 use crate::types::LogicalTime;
614
615 #[test]
616 fn logical_clock_ticks_include_boot_offset_and_fractional_seconds() {
617 let boot = LogicalTime::from_secs(1_000);
618 let now = boot + LogicalTime::from_millis(25);
619
620 assert_eq!(logical_clock_ticks(now, boot, 120), 12_002);
621 }
622
623 #[test]
624 fn sysinfo_uptime_rounds_positive_elapsed_up() {
625 let epoch = LogicalTime::from_nanos(1_000_000_000_000);
626 for (elapsed_ns, expected_zero, expected_offset) in [
627 (0, 0, 120),
628 (1, 1, 121),
629 (999_999_999, 1, 121),
630 (1_000_000_000, 1, 121),
631 (1_000_000_001, 2, 122),
632 (1_200_000_000, 2, 122),
633 ] {
634 let now = epoch + LogicalTime::from_nanos(elapsed_ns);
635 assert_eq!(
636 sysinfo_uptime_seconds(now, epoch, 0).unwrap(),
637 expected_zero,
638 "elapsed {elapsed_ns} ns without boot offset"
639 );
640 assert_eq!(
641 sysinfo_uptime_seconds(now, epoch, 120).unwrap(),
642 expected_offset,
643 "elapsed {elapsed_ns} ns with boot offset"
644 );
645 }
646 }
647
648 #[test]
649 fn sysinfo_uptime_ignores_epoch_fraction() {
650 for fraction_ns in [0, 1, 1_000, 999_999_000, 999_999_999] {
651 let epoch = LogicalTime::from_nanos(1_000_000_000_000 + fraction_ns);
652 for (elapsed_ns, expected) in [
653 (0, 120),
654 (1, 121),
655 (999_999_999, 121),
656 (1_000_000_000, 121),
657 (1_000_000_001, 122),
658 (1_200_000_000, 122),
659 ] {
660 let now = epoch + LogicalTime::from_nanos(elapsed_ns);
661 assert_eq!(
662 sysinfo_uptime_seconds(now, epoch, 120).unwrap(),
663 expected,
664 "epoch fraction {fraction_ns} ns, elapsed {elapsed_ns} ns"
665 );
666 }
667 }
668 }
669
670 #[test]
671 fn sysinfo_uptime_rounds_maximum_duration_without_overflow() {
672 let epoch = LogicalTime::from_nanos(0);
673 assert_eq!(
674 sysinfo_uptime_seconds(LogicalTime::MAX, epoch, 0).unwrap(),
675 18_446_744_074
676 );
677 assert_eq!(
678 sysinfo_uptime_seconds(
679 LogicalTime::from_nanos(18_446_744_073_000_000_000),
680 epoch,
681 120,
682 )
683 .unwrap(),
684 18_446_744_193
685 );
686 assert_eq!(
689 sysinfo_uptime_seconds(LogicalTime::MAX, LogicalTime::from_nanos(u64::MAX - 1), 120,)
690 .unwrap(),
691 121
692 );
693 }
694
695 #[test]
696 fn sysinfo_uptime_preserves_wrapping_offset_extension() {
697 let epoch = LogicalTime::from_nanos(0);
698 for (elapsed_ns, offset, expected) in [
699 (0, u64::MAX, u64::MAX),
700 (1, u64::MAX, 0),
701 (1_000_000_000, u64::MAX, 0),
702 (1_000_000_001, u64::MAX, 1),
703 (1, u64::MAX - 1, u64::MAX),
704 (1_000_000_001, u64::MAX - 1, 0),
705 ] {
706 assert_eq!(
707 sysinfo_uptime_seconds(LogicalTime::from_nanos(elapsed_ns), epoch, offset).unwrap(),
708 expected,
709 "elapsed {elapsed_ns} ns, offset {offset}"
710 );
711 }
712 }
713
714 #[test]
715 fn sysinfo_uptime_preserves_signed_abi_conversion() {
716 let epoch = LogicalTime::from_nanos(0);
717 for (elapsed_ns, offset, expected) in [
718 (0, i64::MAX as u64, i64::MAX),
719 (1, i64::MAX as u64, i64::MIN),
720 (0, u64::MAX, -1),
721 (1, u64::MAX, 0),
722 ] {
723 let uptime =
724 sysinfo_uptime_seconds(LogicalTime::from_nanos(elapsed_ns), epoch, offset).unwrap();
725 let info: reverie::syscalls::linux_abi::Sysinfo = syscalls::SysInfo {
726 uptime,
727 loads_1: 0,
728 loads_5: 0,
729 loads_15: 0,
730 total_ram: 0,
731 free_ram: 0,
732 shared_ram: 0,
733 buffer_ram: 0,
734 total_swap: 0,
735 free_swap: 0,
736 procs: 0,
737 total_high: 0,
738 free_high: 0,
739 mem_unit: 1,
740 }
741 .into();
742 assert_eq!(info.uptime, expected);
743 }
744 }
745
746 #[test]
747 fn sysinfo_uptime_rejects_time_before_epoch() {
748 let error = sysinfo_uptime_seconds(
749 LogicalTime::from_nanos(999),
750 LogicalTime::from_nanos(1_000),
751 120,
752 )
753 .unwrap_err();
754 let Error::Tool(error) = error else {
755 panic!("an impossible clock must be a tool failure, got {error:?}");
756 };
757 assert_eq!(
758 error.to_string(),
759 "sysinfo observed logical time 999 ns before epoch 1000 ns"
760 );
761 }
762
763 #[test]
764 fn procfs_uptime_subtracts_fractional_boot_before_truncating() {
765 let boot = LogicalTime::from_nanos(1_000_999_999_999);
766
767 assert_eq!(
768 procfs_uptime_seconds(boot + LogicalTime::from_nanos(1), boot, 120),
769 120
770 );
771 assert_eq!(
772 procfs_uptime_seconds(boot + LogicalTime::from_secs(1), boot, 120),
773 121
774 );
775 }
776
777 #[test]
778 fn procfs_boot_time_is_the_boot_instant_not_now_minus_uptime() {
779 let boot = LogicalTime::from_nanos(1_000_750_000_000);
782 assert_eq!(procfs_boot_time_seconds(boot, 120), Some(880));
783
784 let old_btime =
788 |now: LogicalTime| now.as_secs() as i64 - procfs_uptime_seconds(now, boot, 120) as i64;
789 let samples = [100, 400, 1_100].map(|millis| boot + LogicalTime::from_millis(millis));
790 assert_eq!(samples.map(old_btime), [880, 881, 880]);
791
792 let integral_boot = LogicalTime::from_secs(1_000);
795 let integral_now = integral_boot + LogicalTime::from_millis(1_400);
796 assert_eq!(
797 procfs_boot_time_seconds(integral_boot, 120),
798 Some(
799 integral_now.as_secs() as i64
800 - procfs_uptime_seconds(integral_now, integral_boot, 120) as i64
801 )
802 );
803
804 assert_eq!(procfs_boot_time_seconds(boot, u64::MAX), None);
805 }
806
807 #[test]
808 fn procfs_boot_time_is_exact_for_every_offset_whose_result_fits() {
809 let boot = LogicalTime::from_secs(1_767_225_600);
813 assert_eq!(procfs_boot_time_seconds(boot, 0), Some(1_767_225_600));
814 assert_eq!(
815 procfs_boot_time_seconds(boot, 1 << 63),
816 Some(-9_223_372_035_087_550_208)
817 );
818 assert_eq!(
821 procfs_boot_time_seconds(boot, 1_767_225_600 + (1 << 63)),
822 Some(i64::MIN)
823 );
824 assert_eq!(
825 procfs_boot_time_seconds(boot, 1_767_225_600 + (1 << 63) + 1),
826 None
827 );
828 assert_eq!(procfs_boot_time_seconds(boot, u64::MAX), None);
829 }
830
831 #[test]
832 fn sysinfo_uptime_rounds_fractional_elapsed_up_like_linux() {
833 let boot = LogicalTime::from_nanos(1_000_999_999_999);
836
837 assert_eq!(sysinfo_uptime_seconds(boot, boot, 120).unwrap(), 120);
838 assert_eq!(
839 sysinfo_uptime_seconds(boot + LogicalTime::from_nanos(1), boot, 120).unwrap(),
840 121
841 );
842 assert_eq!(
843 sysinfo_uptime_seconds(boot + LogicalTime::from_millis(999), boot, 120).unwrap(),
844 121
845 );
846 assert_eq!(
847 sysinfo_uptime_seconds(boot + LogicalTime::from_secs(1), boot, 120).unwrap(),
848 121
849 );
850 assert_eq!(
851 sysinfo_uptime_seconds(
852 boot + LogicalTime::from_secs(1) + LogicalTime::from_nanos(1),
853 boot,
854 120
855 )
856 .unwrap(),
857 122
858 );
859 let fractional = boot + LogicalTime::from_millis(1_500);
861 assert_eq!(procfs_uptime_seconds(fractional, boot, 120), 121);
862 assert_eq!(sysinfo_uptime_seconds(fractional, boot, 120).unwrap(), 122);
863 }
864
865 #[test]
866 fn sysinfo_memory_matches_configured_memory() {
867 assert_eq!(
868 configured_memory(1_000_000_000),
869 ConfiguredMemory {
870 total_ram: 1_000_000_000,
871 free_ram: 1_000_000_000,
872 buffer_ram: 0,
873 shared_ram: 0,
874 total_swap: 0,
875 free_swap: 0,
876 total_high: 0,
877 free_high: 0,
878 mem_unit: 1,
879 },
880 );
881 }
882
883 #[test]
884 fn prlimit_self_target_prefers_deterministic_process_identity() {
885 assert!(prlimit_targets_current_process(3, Some(3), 10_003));
886 assert!(prlimit_targets_current_process(0, Some(3), 10_003));
887 assert!(!prlimit_targets_current_process(10_003, Some(3), 10_003));
888 assert!(!prlimit_targets_current_process(4, Some(3), 10_003));
889 }
890
891 #[test]
892 fn prlimit_self_target_falls_back_to_physical_identity_before_init() {
893 assert!(prlimit_targets_current_process(10_003, None, 10_003));
894 assert!(!prlimit_targets_current_process(3, None, 10_003));
895 }
896
897 #[test]
898 fn prlimit_accepts_exact_noop_for_restricted_resource() {
899 let limit = ResourceLimit {
900 current: 0,
901 maximum: 0,
902 };
903 assert_eq!(
904 validate_resource_limit_mutation(
905 reverie::syscalls::linux_abi::RLIMIT_CPU,
906 limit,
907 limit
908 ),
909 Ok(())
910 );
911 }
912
913 #[test]
914 fn prlimit_accepts_core_soft_limit_change() {
915 let previous = ResourceLimit {
916 current: 1,
917 maximum: 1,
918 };
919 let requested = ResourceLimit {
920 current: 0,
921 maximum: 1,
922 };
923 assert_eq!(
924 validate_resource_limit_mutation(
925 reverie::syscalls::linux_abi::RLIMIT_CORE,
926 previous,
927 requested
928 ),
929 Ok(())
930 );
931 }
932
933 #[test]
934 fn prlimit_rejects_actual_change_to_restricted_resource() {
935 let previous = ResourceLimit {
936 current: 1,
937 maximum: 1,
938 };
939 let requested = ResourceLimit {
940 current: 0,
941 maximum: 1,
942 };
943 assert_eq!(
944 validate_resource_limit_mutation(
945 reverie::syscalls::linux_abi::RLIMIT_CPU,
946 previous,
947 requested
948 ),
949 Err(Errno::EPERM)
950 );
951 }
952
953 #[test]
954 fn prlimit_rejects_invalid_soft_limit_before_noop_policy() {
955 let previous = ResourceLimit {
956 current: 1,
957 maximum: 1,
958 };
959 let requested = ResourceLimit {
960 current: 2,
961 maximum: 1,
962 };
963 assert_eq!(
964 validate_resource_limit_mutation(
965 reverie::syscalls::linux_abi::RLIMIT_CORE,
966 previous,
967 requested
968 ),
969 Err(Errno::EINVAL)
970 );
971 }
972
973 #[test]
974 fn prlimit_rejects_core_hard_limit_raise() {
975 let previous = ResourceLimit {
976 current: 1,
977 maximum: 1,
978 };
979 let requested = ResourceLimit {
980 current: 1,
981 maximum: 2,
982 };
983 assert_eq!(
984 validate_resource_limit_mutation(
985 reverie::syscalls::linux_abi::RLIMIT_CORE,
986 previous,
987 requested
988 ),
989 Err(Errno::EPERM)
990 );
991 }
992
993 #[test]
994 fn logical_cpu_ticks_exclude_boot_epoch() {
995 assert_eq!(clock_ticks(LogicalTime::from_millis(25)), 2);
996 }
997
998 #[test]
999 fn rusage_timeval_splits_seconds_and_microseconds() {
1000 let tv = timeval_from_logical(LogicalTime::from_millis(2_500));
1001 assert_eq!(tv.tv_sec, 2);
1002 assert_eq!(tv.tv_usec, 500_000);
1003 }
1004
1005 #[test]
1006 fn rusage_timeval_truncates_sub_microsecond_rather_than_rounding() {
1007 let tv = timeval_from_logical(LogicalTime::from_nanos(1_999));
1011 assert_eq!(tv.tv_sec, 0);
1012 assert_eq!(tv.tv_usec, 1);
1013 }
1014
1015 #[test]
1016 fn rusage_timeval_is_monotonic_in_the_logical_duration() {
1017 let mut previous = (0_i64, 0_i64);
1021 for nanos in (0..3_000_000u64).step_by(997) {
1022 let tv = timeval_from_logical(LogicalTime::from_nanos(nanos));
1023 let current = (tv.tv_sec, tv.tv_usec);
1024 assert!(
1025 current >= previous,
1026 "rusage timeval went backwards at {nanos}ns: {previous:?} -> {current:?}"
1027 );
1028 previous = current;
1029 }
1030 }
1031
1032 #[test]
1033 fn rusage_zero_cpu_time_renders_as_zero() {
1034 let tv = timeval_from_logical(LogicalTime::ZERO);
1035 assert_eq!(tv.tv_sec, 0);
1036 assert_eq!(tv.tv_usec, 0);
1037 }
1038
1039 #[test]
1040 fn rusage_and_times_agree_within_one_clock_tick() {
1041 for nanos in [0u64, 1_000_000, 300_484_000, 7_000_000_000, 12_345_678_901] {
1044 let duration = LogicalTime::from_nanos(nanos);
1045 let tv = timeval_from_logical(duration);
1046 let rusage_micros = tv.tv_sec as u64 * 1_000_000 + tv.tv_usec as u64;
1047 let times_micros = clock_ticks(duration) * (NANOS_PER_CLOCK_TICK / 1_000);
1048
1049 assert!(rusage_micros >= times_micros);
1050 assert!(rusage_micros - times_micros < NANOS_PER_CLOCK_TICK / 1_000);
1051
1052 if nanos % NANOS_PER_CLOCK_TICK == 0 {
1057 assert_eq!(rusage_micros, times_micros);
1058 }
1059 }
1060 }
1061
1062 #[test]
1063 fn logical_clock_ticks_wrap_configured_offset_like_linux_clock_t() {
1064 let boot = LogicalTime::from_secs(1_000);
1065 let before = logical_clock_ticks(boot, boot, u64::MAX);
1066 let after = logical_clock_ticks(boot + LogicalTime::from_millis(10), boot, u64::MAX);
1067
1068 assert_eq!(before, -100);
1069 assert_eq!(after, -99);
1070 }
1071}