Skip to main content

clock_bound/vmclock/
shm_reader.rs

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
18/// A guard tracking an memory mapped file.
19///
20/// Creating the `MmapGuard` maps an open file descriptor.
21/// The file is unmap'ed when the guard is dropped.
22struct MmapGuard {
23    /// A pointer to the head of the segment
24    segment: *mut c_void,
25
26    /// The size of the segment mapped into memory
27    segsize: usize,
28
29    /// Memory mapped file.
30    _file: File,
31}
32
33impl MmapGuard {
34    /// Create a new `MmapGuard`.
35    ///
36    /// Memory map the provided open File.
37    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        // Read the header so we know how much to map in memory.
71        let header = VMClockShmHeader::read(&buffer)?;
72
73        // This consumes the segsize, but we only needed the header for validation and extracting
74        // the segment size. So the move is fine here.
75        let segsize = header.size.into_inner() as usize;
76
77        debug!("Read a segment size of: {:?}", segsize);
78
79        // SAFETY: We're calling into a C function, but this particular call is always safe.
80        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    /// Drop the `MmapGuard` and unmap the file it tracks.
105    fn drop(&mut self) {
106        // SAFETY: `segment` was previously returned from `mmap`, and therefore
107        // when this destructor runs there are no more live references into
108        // it.
109        unsafe {
110            let ret = libc::munmap(self.segment, self.segsize);
111            assert_eq!(0, ret);
112        }
113    }
114}
115
116/// Reader for VMClock shared memory segment.
117///
118/// The VMClock shared memory segment consists of a `VMClockShmHeader` followed by a
119/// `VMClockShmBody` struct. The segment is updated by a single producer (the Hypervisor),
120/// but may be read by many clients.  The shared memory segment does not implement a semaphore or
121/// equivalent to synchronize the single-producer / many-consumers processes. Instead, the
122/// mechanism is lock-free and relies on a `seq_count` number to ensure consistent reads (over
123/// retries).
124///
125/// The writer increments the `seq_count` field from even to odd before each update. It also
126/// increment it again, from odd to even, after finishing the update. Readers must check the
127/// `seq_count` field before and after each read, and verify that they obtain the same, even,
128/// value. Otherwise, the read was dirty and must be retried.
129pub struct VMClockShmReader {
130    // Explicitly make the VMClockShmReader be !Send and !Sync, since it is not thread safe. A bit ugly to
131    // use a phantom raw pointer, but effective and free at runtime.
132    _marker: std::marker::PhantomData<*const ()>,
133
134    // Drop guard to unmap the shared memory segment
135    _guard: MmapGuard,
136
137    // A raw pointer into the shared memory segment, pointing to the version member of the VMClockShmHeader
138    // section. The version number defines the shared memory segment content and layout. This is a
139    // bit less flexible than a series of TLV but simpler (and not mutually exclusive).
140    version_ptr: *const atomic::AtomicU16,
141
142    // A raw pointer into the shared memory segment, pointing to the seq_count member of the
143    // VMClockShmHeader section. The seq_count is used to read consistent snapshots of the shared
144    // memory segment (that is outside of an update event by the writer). This is expected to roll
145    // over as a function of the rate of update from the writer (eg. every ~9 hours if updating
146    // once a second).
147    seq_count_ptr: *const atomic::AtomicU32,
148
149    // A raw pointer into the shared memory segment, pointing to the VMClockShmBody section. Note
150    // that the structured reference by this pointer may not be consistent, and reading it requires
151    // to assert the seq_count value.
152    vmclock_shm_body_ptr: *const VMClockShmBody,
153
154    // The last snapshot of VMClockShmBody taken. This acts as a cache to avoid waiting for the
155    // writer to complete an update and allow to share a reference to this memory location
156    // (avoiding some memory copy). Keeping a state here and sharing it with the caller makes the
157    // VMClockShmReader not thread safe.
158    vmclock_shm_body_snapshot: VMClockShmBody,
159
160    // The value of seq_count when the VMClockShmBody snapshot was taken.
161    seq_count_snapshot: u32,
162}
163
164impl VMClockShmReader {
165    /// Open a VMClock shared memory segment for reading.
166    ///
167    /// On error, returns an appropriate `Errno`. If the content of the segment
168    /// is uninitialized, unparseable, or otherwise malformed, EPROTO will be
169    /// returned.
170    #[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        // Create a cursor to pick the addresses of the various elements of interest in the shared
176        // memory segment.
177        let mut cursor: *const u8 = mmap_guard.segment.cast();
178        debug!("Created the cursor.");
179
180        // Pick fields from the VMClockShmHeader
181        // SAFETY: `cursor` is aligned to the start of the memory segment and the MmapGuard has
182        // validated the memory segment is large enough to contain the header.
183
184        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        // Validate that the VMClock shared memory segment has a version number that is supported
193        // by this reader.
194        // SAFETY: MmapGuard has validated the memory segment of the header. `version_ptr` points
195        // to a part of the memory segment in this header.
196        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        // Log the counter_id in the shared memory segment.
207        // SAFETY: MmapGuard has validated the memory segment of the header. `counter_id_ptr` points
208        // to a part of the memory segment in this header.
209        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        // Log the time_type in the shared memory segment.
214        // SAFETY: MmapGuard has validated the memory segment of the header. `time_type_ptr` points
215        // to a part of the memory segment in this header.
216        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        // Move to the end of the header and map the VMClockShmBody data, but only if the segment
221        // size allows it and matches our expectation.
222        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        // SAFETY: segment size has been checked to ensure `cursor` move leads to a valid cast
232        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    /// Return a consistent snapshot of the shared memory segment.
247    ///
248    /// Taking a snapshot consists in reading the memory segment while confirming the `seq_count`
249    /// number in the header has not changed (which would indicate an update from the writer
250    /// occurred while reading). If an update is detected, the read is retried.
251    ///
252    /// This function returns a reference to the `VMClockShmBody` snapshot stored by the reader, and
253    /// not an owned value. This make the `VMClockShmReader` NOT thread-safe: the data pointed to could be
254    /// updated without one of the thread knowing, leading to a incorrect clock error bond. The
255    /// advantage are in terms of performance: less data copied, but also no locking, yielding or
256    /// excessive retries.
257    #[expect(clippy::missing_errors_doc, reason = "todo")]
258    pub fn snapshot(&mut self) -> Result<&VMClockShmBody, ShmError> {
259        // Atomically read the current version in the shared memory segment
260        // SAFETY: `self.version` has been validated when creating the reader
261        let version = unsafe { &*self.version_ptr };
262        let version = version.load(atomic::Ordering::Acquire);
263
264        // Validate version number.
265        //
266        // We are validating the version prior to each snapshot to protect
267        // against a Hypervisor which has implemented an unsupported VMClock version.
268        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        // Atomically read the current seq_count in the shared memory segment
277        // SAFETY: `self.seq_count_ptr` has been validated when creating the reader
278        let seq_count = unsafe { &*self.seq_count_ptr };
279        let mut seq_count_first = seq_count.load(atomic::Ordering::Acquire);
280
281        // Quick optimization, if the seq_count number matches the last one recorded, the shared
282        // memory segment has not been updated since last read. No need to read more of the memory
283        // segment, instead return the reference to the snapshot. This is useful in cases where the
284        // rate of clockbound read is much higher than the rate of write to the shared memory
285        // segment.
286        //
287        // Although the seq_count number could theoretically roll over, it is unlikely to do so
288        // over the lifespan of the instance running.
289        // Assuming an update on every jiffy with the largest Linux HZ value of 1000,
290        // updates would occur every 1 millisecond.  Therefore a roll-over would occur
291        // approximately after: 1 millisecond * ((2 ^ 64) - 1) = 584542046.0906265 years.
292        //
293        if seq_count_first == self.seq_count_snapshot {
294            return Ok(&self.vmclock_shm_body_snapshot);
295        }
296
297        // The seq_count number has changed since the last snapshot. Loop
298        // until we obtain a consistent read of the clock error bound data. This relies on reading
299        // the seq_count value twice, making sure they are identical and an even number.
300        //
301        // The writer of the VMClock in the production environment is expected to be the
302        // Hypervisor.  It is not expected to die, but we do cap the number of retries in case
303        // there is an unexpected bug in the Hypervisor.
304        let mut retries = u32::MAX;
305        while retries > 0 {
306            // Read the VMClockShmBody data from the shared memory
307            // SAFETY: `VMClockShmBody` has been checked to be valid while creating the VMClockShmReader
308            let snapshot = unsafe { self.vmclock_shm_body_ptr.read_volatile() };
309
310            // Confirm no update occurred during the read
311            let seq_count_second = seq_count.load(atomic::Ordering::Acquire);
312
313            // Only track complete updates indicated by an even seq_count number.
314            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        // Attempts to read the snapshot have failed.
326        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    /// We make use of tempfile::NamedTempFile to ensure that
340    /// local files that are created during a test get removed
341    /// afterwards.
342    use tempfile::NamedTempFile;
343
344    /// Test struct used to hold the expected fields in the VMClock shared memory segment.
345    #[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        // Convert the VMClockShmBody struct into a slice so we can write it all out, fairly magic.
374        // Definitely needs the #[repr(C)] layout.
375        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    /// Assert that the reader can map a file.
398    #[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    /// Assert that the reader will return an error when it tries to open a file that does not exist.
451    #[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    /// Assert that the reader will return an error when it tries to open a file that is empty.
462    #[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    /// Assert that the reader will return an error when it tries to read a file
472    /// that has an unsupported VMClock version.
473    #[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    /// Assert that the reader will return an error when it tries to read a file
512    /// that has a segement size that is too small to fit the VMClockShmHeader.
513    #[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, // Writing a size smaller than the header size of 16.
525            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    /// Assert that the reader will return an error when it tries to read a file
552    /// that has a segement size that is large enough for the VMClockShmHeader,
553    /// but not large enough for both the VMClockShmHeader and VMClockShmBody.
554    #[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, // Writing a size slightly larger than the header size of 16.
566            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}