1#![expect(clippy::cast_ptr_alignment, reason = "TODO COME BACK TO THIS")]
2
3use std::ffi::c_void;
4use std::fs::File;
5use std::io::Read;
6use std::mem::size_of;
7use std::os::fd::AsRawFd;
8use std::ptr;
9use std::sync::atomic;
10use tracing::{debug, error};
11
12use crate::shm::ShmError;
13use crate::syserror;
14use crate::vmclock::shm::{VMClockShmBody, VMClockShmHeader};
15
16const VMCLOCK_SUPPORTED_VERSION: u16 = 1;
17
18struct MmapGuard {
23 segment: *mut c_void,
25
26 segsize: usize,
28
29 _file: File,
31}
32
33impl MmapGuard {
34 fn new(path: &str) -> Result<Self, ShmError> {
38 let mut file = match File::open(path) {
39 Ok(f) => f,
40 Err(e) => {
41 error!(error = ?e, "Failed to open VMClock SHM segment.");
42 return Err(ShmError::SegmentNotInitialized(format!(
43 "Failed to open SHM segment at {path}"
44 )));
45 }
46 };
47
48 let mut buffer = vec![];
49
50 let Ok(bytes_read) = file.read_to_end(&mut buffer) else {
51 return syserror!(String::from("Failed to read SHM segment"));
52 };
53
54 if bytes_read == 0_usize {
55 let msg = String::from("Read zero bytes.");
56 error!(msg);
57 return Err(ShmError::SegmentNotInitialized(msg));
58 } else if bytes_read < size_of::<VMClockShmHeader>() {
59 let msg = format!(
60 "Number of bytes read ({:?}) is less than the size of VMClockShmHeader ({:?}).",
61 bytes_read,
62 size_of::<VMClockShmHeader>()
63 );
64 error!(msg);
65 return Err(ShmError::SegmentMalformed(msg));
66 }
67
68 debug!("Reading the VMClockShmHeader.");
69
70 let header = VMClockShmHeader::read(&buffer)?;
72
73 let segsize = header.size.into_inner() as usize;
76
77 debug!("Read a segment size of: {:?}", segsize);
78
79 let segment: *mut c_void = unsafe {
81 libc::mmap(
82 ptr::null_mut(),
83 segsize,
84 libc::PROT_READ,
85 libc::MAP_SHARED,
86 file.as_raw_fd(),
87 0,
88 )
89 };
90
91 if segment == libc::MAP_FAILED {
92 return syserror!(String::from("Failed to mmap the SHM segment"));
93 }
94
95 Ok(MmapGuard {
96 segment,
97 segsize,
98 _file: file,
99 })
100 }
101}
102
103impl Drop for MmapGuard {
104 fn drop(&mut self) {
106 unsafe {
110 let ret = libc::munmap(self.segment, self.segsize);
111 assert_eq!(0, ret);
112 }
113 }
114}
115
116pub struct VMClockShmReader {
130 _marker: std::marker::PhantomData<*const ()>,
133
134 _guard: MmapGuard,
136
137 version_ptr: *const atomic::AtomicU16,
141
142 seq_count_ptr: *const atomic::AtomicU32,
148
149 vmclock_shm_body_ptr: *const VMClockShmBody,
153
154 vmclock_shm_body_snapshot: VMClockShmBody,
159
160 seq_count_snapshot: u32,
162}
163
164impl VMClockShmReader {
165 #[expect(clippy::missing_errors_doc, reason = "todo")]
171 pub fn new(path: &str) -> Result<VMClockShmReader, ShmError> {
172 debug!(?path, "Creating a MmapGuard.");
173 let mmap_guard = MmapGuard::new(path)?;
174
175 let mut cursor: *const u8 = mmap_guard.segment.cast();
178 debug!("Created the cursor.");
179
180 let version_ptr = unsafe { ptr::addr_of!((*cursor.cast::<VMClockShmHeader>()).version) };
185 let counter_id_ptr =
186 unsafe { ptr::addr_of!((*cursor.cast::<VMClockShmHeader>()).counter_id) };
187 let time_type_ptr =
188 unsafe { ptr::addr_of!((*cursor.cast::<VMClockShmHeader>()).time_type) };
189 let seq_count_ptr =
190 unsafe { ptr::addr_of!((*cursor.cast::<VMClockShmHeader>()).seq_count) };
191
192 let version = unsafe { &*version_ptr };
197 let version_number = version.load(atomic::Ordering::Acquire);
198 if version_number != VMCLOCK_SUPPORTED_VERSION {
199 let msg = format!(
200 "VMClock shared memory segment has version {version_number} which is not supported by this version of the VMClockShmReader."
201 );
202 error!(msg);
203 return Err(ShmError::SegmentVersionNotSupported(msg));
204 }
205
206 let counter_id = unsafe { &*counter_id_ptr };
210 let counter_id_value = counter_id.load(atomic::Ordering::Acquire);
211 debug!(?counter_id_value, "Read counter_id.");
212
213 let time_type = unsafe { &*time_type_ptr };
217 let time_type_value = time_type.load(atomic::Ordering::Acquire);
218 debug!(?time_type_value, "Read time_type.");
219
220 if mmap_guard.segsize < (size_of::<VMClockShmHeader>() + size_of::<VMClockShmBody>()) {
223 let msg = format!(
224 "Segment size is smaller than expected [{} < {}].",
225 mmap_guard.segsize,
226 size_of::<VMClockShmHeader>() + size_of::<VMClockShmBody>()
227 );
228 error!(msg);
229 return Err(ShmError::SegmentMalformed(msg));
230 }
231 cursor = unsafe { cursor.add(size_of::<VMClockShmHeader>()) };
233 let vmclock_shm_body_ptr = ptr::addr_of!(*cursor.cast::<VMClockShmBody>());
234
235 Ok(VMClockShmReader {
236 _marker: std::marker::PhantomData,
237 _guard: mmap_guard,
238 version_ptr,
239 seq_count_ptr,
240 vmclock_shm_body_ptr,
241 vmclock_shm_body_snapshot: VMClockShmBody::default(),
242 seq_count_snapshot: 0,
243 })
244 }
245
246 #[expect(clippy::missing_errors_doc, reason = "todo")]
258 pub fn snapshot(&mut self) -> Result<&VMClockShmBody, ShmError> {
259 let version = unsafe { &*self.version_ptr };
262 let version = version.load(atomic::Ordering::Acquire);
263
264 if version != VMCLOCK_SUPPORTED_VERSION {
269 let msg = format!(
270 "VMClock shared memory segment has version {version} which is not supported by this version of the VMClockShmReader."
271 );
272 error!(msg);
273 return Err(ShmError::SegmentVersionNotSupported(msg));
274 }
275
276 let seq_count = unsafe { &*self.seq_count_ptr };
279 let mut seq_count_first = seq_count.load(atomic::Ordering::Acquire);
280
281 if seq_count_first == self.seq_count_snapshot {
294 return Ok(&self.vmclock_shm_body_snapshot);
295 }
296
297 let mut retries = u32::MAX;
305 while retries > 0 {
306 let snapshot = unsafe { self.vmclock_shm_body_ptr.read_volatile() };
309
310 let seq_count_second = seq_count.load(atomic::Ordering::Acquire);
312
313 if seq_count_second & 0x0001 == 0 {
315 if seq_count_first == seq_count_second {
316 self.seq_count_snapshot = seq_count_first;
317 self.vmclock_shm_body_snapshot = snapshot;
318 return Ok(&self.vmclock_shm_body_snapshot);
319 }
320 seq_count_first = seq_count_second;
321 }
322 retries -= 1;
323 }
324
325 Err(ShmError::SegmentNotInitialized(String::from(
327 "Failed to read the SHM segment after all attempts.",
328 )))
329 }
330}
331
332#[cfg(test)]
333mod t_reader {
334 use super::*;
335 use crate::vmclock::shm::VMClockClockStatus;
336 use std::fs::{File, OpenOptions};
337 use std::io::Write;
338 use std::path::Path;
339 use tempfile::NamedTempFile;
343
344 #[repr(C)]
346 #[derive(Debug, Copy, Clone, PartialEq)]
347 struct VMClockContent {
348 magic: u32,
349 size: u32,
350 version: u16,
351 counter_id: u8,
352 time_type: u8,
353 seq_count: u32,
354 disruption_marker: u64,
355 flags: u64,
356 _padding: [u8; 2],
357 clock_status: VMClockClockStatus,
358 leap_second_smearing_hint: u8,
359 tai_offset_sec: i16,
360 leap_indicator: u8,
361 counter_period_shift: u8,
362 counter_value: u64,
363 counter_period_frac_sec: u64,
364 counter_period_esterror_rate_frac_sec: u64,
365 counter_period_maxerror_rate_frac_sec: u64,
366 time_sec: u64,
367 time_frac_sec: u64,
368 time_esterror_nanosec: u64,
369 time_maxerror_nanosec: u64,
370 }
371
372 fn write_vmclock_content(file: &mut File, vmclock_content: &VMClockContent) {
373 let slice = unsafe {
376 ::core::slice::from_raw_parts(
377 (vmclock_content as *const VMClockContent) as *const u8,
378 ::core::mem::size_of::<VMClockContent>(),
379 )
380 };
381
382 file.write_all(slice).expect("Write failed VMClockContent");
383 file.sync_all().expect("Sync to disk failed");
384 }
385
386 fn remove_path_if_exists(path_shm: &str) {
387 let path = Path::new(path_shm);
388 if path.exists() {
389 if path.is_dir() {
390 std::fs::remove_dir_all(path_shm).expect("failed to remove file");
391 } else {
392 std::fs::remove_file(path_shm).expect("failed to remove file");
393 }
394 }
395 }
396
397 #[test]
399 fn test_reader_new() {
400 let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
401 let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
402 let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
403 let mut vmclock_shm_file = OpenOptions::new()
404 .write(true)
405 .open(vmclock_shm_path)
406 .expect("open vmclock file failed");
407 let vmclock_content = VMClockContent {
408 magic: 0x4B4C4356,
409 size: 104_u32,
410 version: 1_u16,
411 counter_id: 1_u8,
412 time_type: 0_u8,
413 seq_count: 10_u32,
414 disruption_marker: 888888_u64,
415 flags: 0_u64,
416 _padding: [0x00, 0x00],
417 clock_status: VMClockClockStatus::Synchronized,
418 leap_second_smearing_hint: 0_u8,
419 tai_offset_sec: 0_i16,
420 leap_indicator: 0_u8,
421 counter_period_shift: 0_u8,
422 counter_value: 123456_u64,
423 counter_period_frac_sec: 0_u64,
424 counter_period_esterror_rate_frac_sec: 0_u64,
425 counter_period_maxerror_rate_frac_sec: 0_u64,
426 time_sec: 0_u64,
427 time_frac_sec: 0_u64,
428 time_esterror_nanosec: 0_u64,
429 time_maxerror_nanosec: 0_u64,
430 };
431 write_vmclock_content(&mut vmclock_shm_file, &vmclock_content);
432
433 let reader =
434 VMClockShmReader::new(&vmclock_shm_path).expect("Failed to create VMClockShmReader");
435
436 let version = unsafe { &*reader.version_ptr };
437 let seq_count = unsafe { &*reader.seq_count_ptr };
438 let vmclock_shm_body = unsafe { *reader.vmclock_shm_body_ptr };
439
440 assert_eq!(version.load(atomic::Ordering::Relaxed), 1_u16);
441 assert_eq!(seq_count.load(atomic::Ordering::Relaxed), 10_u32);
442 assert_eq!(vmclock_shm_body.counter_value, 123456_u64);
443 assert_eq!(
444 vmclock_shm_body.clock_status,
445 VMClockClockStatus::Synchronized
446 );
447 assert_eq!(vmclock_shm_body.disruption_marker, 888888_u64);
448 }
449
450 #[test]
452 fn test_reader_file_does_not_exist() {
453 let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
454 let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
455 let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
456 remove_path_if_exists(vmclock_shm_path);
457
458 assert!(VMClockShmReader::new(&vmclock_shm_path).is_err());
459 }
460
461 #[test]
463 fn test_reader_file_is_empty() {
464 let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
465 let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
466 let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
467
468 assert!(VMClockShmReader::new(&vmclock_shm_path).is_err());
469 }
470
471 #[test]
474 fn test_reader_version_not_supported() {
475 let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
476 let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
477 let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
478 let mut vmclock_shm_file = OpenOptions::new()
479 .write(true)
480 .open(vmclock_shm_path)
481 .expect("open vmclock file failed");
482 let vmclock_content = VMClockContent {
483 magic: 0x4B4C4356,
484 size: 104_u32,
485 version: 999_u16,
486 counter_id: 1_u8,
487 time_type: 0_u8,
488 seq_count: 10_u32,
489 disruption_marker: 888888_u64,
490 flags: 0_u64,
491 _padding: [0x00, 0x00],
492 clock_status: VMClockClockStatus::Synchronized,
493 leap_second_smearing_hint: 0_u8,
494 tai_offset_sec: 0_i16,
495 leap_indicator: 0_u8,
496 counter_period_shift: 0_u8,
497 counter_value: 123456_u64,
498 counter_period_frac_sec: 0_u64,
499 counter_period_esterror_rate_frac_sec: 0_u64,
500 counter_period_maxerror_rate_frac_sec: 0_u64,
501 time_sec: 0_u64,
502 time_frac_sec: 0_u64,
503 time_esterror_nanosec: 0_u64,
504 time_maxerror_nanosec: 0_u64,
505 };
506 write_vmclock_content(&mut vmclock_shm_file, &vmclock_content);
507
508 assert!(VMClockShmReader::new(&vmclock_shm_path).is_err());
509 }
510
511 #[test]
514 fn test_reader_segment_size_smaller_than_header() {
515 let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
516 let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
517 let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
518 let mut vmclock_shm_file = OpenOptions::new()
519 .write(true)
520 .open(vmclock_shm_path)
521 .expect("open vmclock file failed");
522 let vmclock_content = VMClockContent {
523 magic: 0x4B4C4356,
524 size: 8_u32, version: 1_u16,
526 counter_id: 1_u8,
527 time_type: 0_u8,
528 seq_count: 10_u32,
529 disruption_marker: 888888_u64,
530 flags: 0_u64,
531 _padding: [0x00, 0x00],
532 clock_status: VMClockClockStatus::Synchronized,
533 leap_second_smearing_hint: 0_u8,
534 tai_offset_sec: 0_i16,
535 leap_indicator: 0_u8,
536 counter_period_shift: 0_u8,
537 counter_value: 123456_u64,
538 counter_period_frac_sec: 0_u64,
539 counter_period_esterror_rate_frac_sec: 0_u64,
540 counter_period_maxerror_rate_frac_sec: 0_u64,
541 time_sec: 0_u64,
542 time_frac_sec: 0_u64,
543 time_esterror_nanosec: 0_u64,
544 time_maxerror_nanosec: 0_u64,
545 };
546 write_vmclock_content(&mut vmclock_shm_file, &vmclock_content);
547
548 assert!(VMClockShmReader::new(&vmclock_shm_path).is_err());
549 }
550
551 #[test]
555 fn test_reader_segment_size_smaller_than_header_and_body() {
556 let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
557 let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
558 let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
559 let mut vmclock_shm_file = OpenOptions::new()
560 .write(true)
561 .open(vmclock_shm_path)
562 .expect("open vmclock file failed");
563 let vmclock_content = VMClockContent {
564 magic: 0x4B4C4356,
565 size: 26_u32, version: 1_u16,
567 counter_id: 1_u8,
568 time_type: 0_u8,
569 seq_count: 10_u32,
570 disruption_marker: 888888_u64,
571 flags: 0_u64,
572 _padding: [0x00, 0x00],
573 clock_status: VMClockClockStatus::Synchronized,
574 leap_second_smearing_hint: 0_u8,
575 tai_offset_sec: 0_i16,
576 leap_indicator: 0_u8,
577 counter_period_shift: 0_u8,
578 counter_value: 123456_u64,
579 counter_period_frac_sec: 0_u64,
580 counter_period_esterror_rate_frac_sec: 0_u64,
581 counter_period_maxerror_rate_frac_sec: 0_u64,
582 time_sec: 0_u64,
583 time_frac_sec: 0_u64,
584 time_esterror_nanosec: 0_u64,
585 time_maxerror_nanosec: 0_u64,
586 };
587 write_vmclock_content(&mut vmclock_shm_file, &vmclock_content);
588
589 assert!(VMClockShmReader::new(&vmclock_shm_path).is_err());
590 }
591}