Skip to main content

clock_bound/vmclock/
shm.rs

1//! VMClock Shared Memory
2//!
3//! This crate implements the low-level implementation to share VMClock data
4//! over a shared memory segment. This crate is meant to be used by the C and Rust versions
5//! of the ClockBound client library.
6
7// TODO: prevent clippy from checking for dead code. The writer module is only re-exported publicly
8// if the write feature is selected. There may be a better way to do that and re-enable the lint.
9#![allow(dead_code)]
10
11use std::mem::size_of;
12use std::str::FromStr;
13use std::sync::atomic;
14
15use crate::shm::ShmError;
16use crate::syserror;
17use tracing::{debug, error};
18
19pub const VMCLOCK_SHM_DEFAULT_PATH: &str = "/dev/vmclock0";
20
21/// The magic number that identifies a VMClock shared memory segment.
22pub const VMCLOCK_SHM_MAGIC: u32 = 0x4B4C_4356;
23
24/// Header structure to the Shared Memory segment where the VMClock data is kept.
25///
26/// Most members are atomic types as they are subject to be updated by the Hypervisor.
27#[repr(C)]
28#[derive(Debug)]
29pub struct VMClockShmHeader {
30    /// Magic number to uniquely identify the content of the memory segment.
31    pub magic: atomic::AtomicU32,
32
33    /// Size of the memory segment.
34    pub size: atomic::AtomicU32,
35
36    /// Version identifying the layout of data written to the shared memory segment.
37    pub version: atomic::AtomicU16,
38
39    /// Counter ID.
40    pub counter_id: atomic::AtomicU8,
41
42    /// Time type.
43    ///
44    /// Possible values are:
45    ///
46    /// `VMCLOCK_TIME_UTC`                        0   // Since 1970-01-01 00:00:00z
47    /// `VMCLOCK_TIME_TAI`                        1   // Since 1970-01-01 00:00:00z
48    /// `VMCLOCK_TIME_MONOTONIC`                  2   // Since undefined epoch
49    /// `VMCLOCK_TIME_INVALID_SMEARED`            3   // Not supported
50    /// `VMCLOCK_TIME_INVALID_MAYBE_SMEARED`      4   // Not supported
51    ///
52    pub time_type: atomic::AtomicU8,
53
54    // Sequence count that is a generation number incremented by the
55    // VMClock on every update of the shared memory segment.
56    //
57    // Odd numbers mean that an update is in progress.
58    pub seq_count: atomic::AtomicU32,
59}
60
61impl VMClockShmHeader {
62    /// Initialize a `VMClockShmHeader` from a vector of bytes.
63    ///
64    /// It is assumed that the vecxtor has already been validated to have enough bytes to hold.
65    #[expect(clippy::missing_errors_doc, reason = "todo")]
66    #[expect(clippy::missing_panics_doc, reason = "slices appropriately sized")]
67    pub fn read(vector: &Vec<u8>) -> Result<Self, ShmError> {
68        if vector.len() < size_of::<VMClockShmHeader>() {
69            return syserror!(format!(
70                "VMClockShmHeader is shorter than expected size [{} < {}].",
71                vector.len(),
72                size_of::<VMClockShmHeader>()
73            ));
74        }
75
76        let slice = vector.as_slice();
77
78        let magic = u32::from_le_bytes(slice[0..4].try_into().unwrap());
79        let size = u32::from_le_bytes(slice[4..8].try_into().unwrap());
80        let version = u16::from_le_bytes(slice[8..10].try_into().unwrap());
81        let counter_id = u8::from_le_bytes(slice[10..11].try_into().unwrap());
82        let time_type = u8::from_le_bytes(slice[11..12].try_into().unwrap());
83        let seq_count = u32::from_le_bytes(slice[12..16].try_into().unwrap());
84
85        let header = VMClockShmHeader {
86            magic: atomic::AtomicU32::new(magic),
87            size: atomic::AtomicU32::new(size),
88            version: atomic::AtomicU16::new(version),
89            counter_id: atomic::AtomicU8::new(counter_id),
90            time_type: atomic::AtomicU8::new(time_type),
91            seq_count: atomic::AtomicU32::new(seq_count),
92        };
93
94        header.is_valid()?;
95        Ok(header)
96    }
97
98    /// Check whether the magic number matches the expected one.
99    fn matches_magic(&self) -> bool {
100        let magic = self.magic.load(atomic::Ordering::Relaxed);
101        debug!("VMClockShmHeader has magic: {:?}", magic);
102        magic == VMCLOCK_SHM_MAGIC
103    }
104
105    /// Check whether the header is marked with a valid version
106    fn has_valid_version(&self) -> bool {
107        let version = self.version.load(atomic::Ordering::Relaxed);
108        debug!("VMClockShmHeader has version: {:?}", version);
109        version > 0
110    }
111
112    /// Check whether the header is complete
113    fn is_well_formed(&self) -> bool {
114        let size = self.size.load(atomic::Ordering::Relaxed);
115        debug!("VMClockShmHeader has size: {:?}", size);
116        size as usize >= size_of::<Self>()
117    }
118
119    /// Check whether a `VMClockShmHeader` is valid
120    fn is_valid(&self) -> Result<(), ShmError> {
121        if !self.matches_magic() {
122            let msg = String::from("VMClockShmHeader does not have a matching magic number.");
123            error!(msg);
124            return Err(ShmError::SegmentMalformed(msg));
125        }
126
127        if !self.has_valid_version() {
128            let msg = String::from("VMClockShmHeader does not have a valid version number.");
129            error!(msg);
130            return Err(ShmError::SegmentVersionNotSupported(msg));
131        }
132
133        if !self.is_well_formed() {
134            let msg = String::from("VMClockShmHeader is not well formed.");
135            error!(msg);
136            return Err(ShmError::SegmentMalformed(msg));
137        }
138        Ok(())
139    }
140}
141
142/// Definition of mutually exclusive clock status exposed to the reader.
143#[repr(u8)]
144#[derive(Debug, Copy, Clone, PartialEq)]
145pub enum VMClockClockStatus {
146    /// The status of the clock is unknown.
147    Unknown = 0,
148
149    /// The clock is being initialized.
150    Initializing = 1,
151
152    /// The clock is kept accurate by the synchronization daemon.
153    Synchronized = 2,
154
155    /// The clock is free running and not updated by the synchronization daemon.
156    FreeRunning = 3,
157
158    /// The clock is unreliable and should not be trusted.
159    /// The reason is unspecified, but it could be due to the hardware counter being broken.
160    Unreliable = 4,
161}
162
163/// Custom struct used for indicating a parsing error when parsing a
164/// `VMClockClockStatus` from str.
165#[derive(Clone, PartialEq, Eq, Hash, Debug)]
166pub struct ParseError;
167
168impl FromStr for VMClockClockStatus {
169    type Err = ParseError;
170    fn from_str(input: &str) -> Result<VMClockClockStatus, Self::Err> {
171        match input {
172            "Unknown" => Ok(VMClockClockStatus::Unknown),
173            "Initializing" => Ok(VMClockClockStatus::Initializing),
174            "Synchronized" => Ok(VMClockClockStatus::Synchronized),
175            "FreeRunning" => Ok(VMClockClockStatus::FreeRunning),
176            "Unreliable" => Ok(VMClockClockStatus::Unreliable),
177            _ => Err(ParseError),
178        }
179    }
180}
181
182/// Structure that holds the VMClock data captured at a specific point in time.
183///
184/// The structure is shared across the Shared Memory segment and has a C representation to enforce
185/// this specific layout.
186#[repr(C)]
187#[derive(Debug, Copy, Clone, PartialEq)]
188#[expect(clippy::pub_underscore_fields, reason = "C FFI struct")]
189pub struct VMClockShmBody {
190    /// Disruption Marker.
191    ///
192    /// This value is incremented (by an unspecified delta) each time the clock is disrupted.
193    /// This value is specific to a particular VM/EC2 instance.
194    pub disruption_marker: u64,
195
196    /// Flags.
197    ///
198    /// Bit flags representing the following:
199    ///
200    /// Bit (1 << 0): `VMCLOCK_FLAG_TAI_OFFSET_VALID`: Indicates that the `tai_offset_sec` field is valid.
201    ///
202    /// The below bits are optionally used to notify guests of pending
203    /// maintenance events. A guest which provides latency-sensitive
204    /// services may wish to remove itself from service if an event is coming up.
205    /// Two flags indicate the approximate imminence of the event.
206    ///
207    /// Bit (1 << 1): `VMCLOCK_FLAG_DISRUPTION_SOON`: About a day.
208    /// Bit (1 << 2): `VMCLOCK_FLAG_DISRUPTION_IMMINENT`: About an hour.
209    /// Bit (1 << 3): `VMCLOCK_FLAG_PERIOD_ESTERROR_VALID`
210    /// Bit (1 << 4): `VMCLOCK_FLAG_PERIOD_MAXERROR_VALID`
211    /// Bit (1 << 5): `VMCLOCK_FLAG_TIME_ESTERROR_VALID`
212    /// Bit (1 << 6): `VMCLOCK_FLAG_TIME_MAXERROR_VALID`
213    ///
214    /// The below bit is the MONOTONIC flag.
215    /// If the MONOTONIC flag is set then (other than leap seconds) it is
216    /// guaranteed that the time calculated according this structure at
217    /// any given moment shall never appear to be later than the time
218    /// calculated via the structure at any *later* moment.
219    ///
220    /// In particular, a timestamp based on a counter reading taken
221    /// immediately after setting the low bit of `seq_count` (and the
222    /// associated memory barrier), using the previously-valid time and
223    /// period fields, shall never be later than a timestamp based on
224    /// a counter reading taken immediately before *clearing* the low
225    /// bit again after the update, using the about-to-be-valid fields.
226    ///
227    /// Bit (1 << 7): `VMCLOCK_FLAG_TIME_MONOTONIC`
228    ///
229    pub flags: u64,
230
231    /// Padding.
232    pub _padding: [u8; 2],
233
234    /// Clock Status.
235    ///
236    /// The clock status indicates whether the clock is synchronized,
237    /// free-running, etc.
238    pub clock_status: VMClockClockStatus,
239
240    // Leap Second smearing hint.
241    //
242    /// The time exposed through this device is never smeared. This field
243    /// corresponds to the 'subtype' field in virtio-rtc, which indicates
244    /// the smearing method. However in this case it provides a *hint* to
245    /// the guest operating system, such that *if* the guest OS wants to
246    /// provide its users with an alternative clock which does not follow
247    /// UTC, it may do so in a fashion consistent with the other systems
248    /// in the nearby environment.
249    ///
250    /// Possible values are:
251    ///
252    /// `VMCLOCK_SMEARING_STRICT`:      0
253    /// `VMCLOCK_SMEARING_NOON_LINEAR`: 1
254    /// `VMCLOCK_SMEARING_UTC_SLS`:     2
255    ///
256    pub leap_second_smearing_hint: u8,
257
258    /// Offset between TAI and UTC, in seconds.
259    pub tai_offset_sec: i16,
260
261    /// Leap indicator.
262    ///
263    /// This field is based on the the `VIRTIO_RTC_LEAP_xxx` values as
264    /// defined in the current draft of virtio-rtc, but since smearing
265    /// cannot be used with the shared memory device, some values are
266    /// not used.
267    ///
268    /// The _`POST_POS` and _`POST_NEG` values allow the guest to perform
269    /// its own smearing during the day or so after a leap second when
270    /// such smearing may need to continue being applied for a leap
271    /// second which is now theoretically "historical".
272    ///
273    /// Possible values are:
274    /// `VMCLOCK_LEAP_NONE`       0x00  // No known nearby leap second
275    /// `VMCLOCK_LEAP_PRE_POS`    0x01  // Positive leap second at EOM
276    /// `VMCLOCK_LEAP_PRE_NEG`    0x02  // Negative leap second at EOM
277    /// `VMCLOCK_LEAP_POS`        0x03  // Set during 23:59:60 second
278    /// `VMCLOCK_LEAP_POST_POS`   0x04
279    /// `VMCLOCK_LEAP_POST_NEG`   0x05
280    ///
281    pub leap_indicator: u8,
282
283    /// Counter period shift.
284    ///
285    /// Bit shift for the `counter_period_frac_sec` and its error rate.
286    pub counter_period_shift: u8,
287
288    /// Counter value.
289    pub counter_value: u64,
290
291    /// Counter period.
292    ///
293    /// This is the estimated period of the counter, in binary fractional seconds.
294    /// The unit of this field is: 1 / (2 ^ (64 + `counter_period_shift`)) of a second.
295    pub counter_period_frac_sec: u64,
296
297    /// Counter period estimated error rate.
298    ///
299    /// This is the estimated error rate of the counter period, in binary fractional seconds per second.
300    /// The unit of this field is: 1 / (2 ^ (64 + `counter_period_shift`)) of a second per second.
301    pub counter_period_esterror_rate_frac_sec: u64,
302
303    /// Counter period maximum error rate.
304    ///
305    /// This is the maximum error rate of the counter period, in binary fractional seconds per second.
306    /// The unit of this field is: 1 / (2 ^ (64 + `counter_period_shift`)) of a second per second.
307    pub counter_period_maxerror_rate_frac_sec: u64,
308
309    /// Time according to the `time_type` field.
310
311    /// Time: Seconds since `time_type` epoch.
312    pub time_sec: u64,
313
314    /// Time: Fractional seconds, in units of 1 / (2 ^ 64) of a second.
315    pub time_frac_sec: u64,
316
317    /// Time: Estimated error.
318    pub time_esterror_nanosec: u64,
319
320    /// Time: Maximum error.
321    pub time_maxerror_nanosec: u64,
322}
323
324impl Default for VMClockShmBody {
325    /// Get a default `VMClockShmBody` struct
326    /// Equivalent to zero'ing this bit of memory
327    fn default() -> Self {
328        VMClockShmBody {
329            disruption_marker: 0,
330            flags: 0,
331            _padding: [0x00, 0x00],
332            clock_status: VMClockClockStatus::Unknown,
333            leap_second_smearing_hint: 0,
334            tai_offset_sec: 0,
335            leap_indicator: 0,
336            counter_period_shift: 0,
337            counter_value: 0,
338            counter_period_frac_sec: 0,
339            counter_period_esterror_rate_frac_sec: 0,
340            counter_period_maxerror_rate_frac_sec: 0,
341            time_sec: 0,
342            time_frac_sec: 0,
343            time_esterror_nanosec: 0,
344            time_maxerror_nanosec: 0,
345        }
346    }
347}
348
349#[cfg(test)]
350mod t_vmclock_shm_header {
351    use super::*;
352    use byteorder::{LittleEndian, WriteBytesExt};
353    use std::fs::{File, OpenOptions};
354    use std::io::Read;
355    use std::path::Path;
356    /// We make use of tempfile::NamedTempFile to ensure that
357    /// local files that are created during a test get removed
358    /// afterwards.
359    use tempfile::NamedTempFile;
360
361    macro_rules! write_vmclock_shm_header {
362        ($file:ident,
363         $magic:literal,
364         $size:literal,
365         $version:literal,
366         $counter_id:literal,
367         $time_type:literal,
368         $seq_count:literal) => {
369            $file
370                .write_u32::<LittleEndian>($magic)
371                .expect("Write failed magic");
372            $file
373                .write_u32::<LittleEndian>($size)
374                .expect("Write failed size");
375            $file
376                .write_u16::<LittleEndian>($version)
377                .expect("Write failed version");
378            $file
379                .write_u8($counter_id)
380                .expect("Write failed counter_id");
381            $file.write_u8($time_type).expect("Write failed time_type");
382            $file
383                .write_u32::<LittleEndian>($seq_count)
384                .expect("Write failed seq_count");
385            $file.sync_all().expect("Sync to disk failed");
386        };
387    }
388
389    fn remove_path_if_exists(path_shm: &str) {
390        let path = Path::new(path_shm);
391        if path.exists() {
392            if path.is_dir() {
393                std::fs::remove_dir_all(path_shm).expect("failed to remove file");
394            } else {
395                std::fs::remove_file(path_shm).expect("failed to remove file");
396            }
397        }
398    }
399
400    /// Assert that a file containing a valid header produces a valid VMClockShmHeader
401    #[test]
402    fn test_header_valid() {
403        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
404        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
405        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
406        let mut vmclock_shm_file = OpenOptions::new()
407            .write(true)
408            .open(vmclock_shm_path)
409            .expect("open vmclock file failed");
410        write_vmclock_shm_header!(
411            vmclock_shm_file,
412            0x4B4C4356,
413            104_u32,
414            1_u16,
415            0_u8,
416            0_u8,
417            99_u32
418        );
419
420        let mut file = File::open(vmclock_shm_path).expect("failed to open file");
421        let mut buffer = vec![];
422        file.read_to_end(&mut buffer)
423            .expect("failed to read to end of the file");
424        let header = VMClockShmHeader::read(&buffer).expect("SHM Reader read");
425
426        assert_eq!(header.magic.into_inner(), 0x4B4C4356);
427        assert_eq!(header.size.into_inner(), 104_u32);
428        assert_eq!(header.version.into_inner(), 1_u16);
429        assert_eq!(header.counter_id.into_inner(), 0_u8);
430        assert_eq!(header.time_type.into_inner(), 0_u8);
431        assert_eq!(header.seq_count.into_inner(), 99_u32);
432    }
433
434    /// Assert that a file with a bad magic returns an error
435    #[test]
436    fn test_header_bad_magic() {
437        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
438        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
439        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
440        let mut vmclock_shm_file = OpenOptions::new()
441            .write(true)
442            .open(vmclock_shm_path)
443            .expect("open vmclock file failed");
444        // magic numbers are bogus
445        write_vmclock_shm_header!(
446            vmclock_shm_file,
447            0xdeadbeef,
448            16_u32,
449            1_u16,
450            0_u8,
451            0_u8,
452            99_u32
453        );
454
455        let mut file = File::open(vmclock_shm_path).expect("failed to open file");
456        let mut buffer = vec![];
457        file.read_to_end(&mut buffer)
458            .expect("failed to read to end of the file");
459        assert!(VMClockShmHeader::read(&buffer).is_err());
460    }
461
462    /// Assert that a file with a wrongly truncated header returns an error
463    #[test]
464    fn test_header_bad_size() {
465        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
466        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
467        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
468        let mut vmclock_shm_file = OpenOptions::new()
469            .write(true)
470            .open(vmclock_shm_path)
471            .expect("open vmclock file failed");
472        write_vmclock_shm_header!(
473            vmclock_shm_file,
474            0x4B4C4356,
475            4_u32,
476            1_u16,
477            0_u8,
478            0_u8,
479            99_u32
480        );
481
482        let mut file = File::open(vmclock_shm_path).expect("failed to open file");
483        let mut buffer = vec![];
484        file.read_to_end(&mut buffer)
485            .expect("failed to read to end of the file");
486        assert!(VMClockShmHeader::read(&buffer).is_err());
487    }
488
489    /// Assert that a file with a version number of 0 returns an error
490    #[test]
491    fn test_header_bad_version() {
492        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
493        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
494        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
495        let mut vmclock_shm_file = OpenOptions::new()
496            .write(true)
497            .open(vmclock_shm_path)
498            .expect("open vmclock file failed");
499        // layout version is 0
500        write_vmclock_shm_header!(
501            vmclock_shm_file,
502            0x4B4C4356,
503            16_u32,
504            0_u16,
505            0_u8,
506            0_u8,
507            99_u32
508        );
509
510        let mut file = File::open(vmclock_shm_path).expect("failed to open file");
511        let mut buffer = vec![];
512        file.read_to_end(&mut buffer)
513            .expect("failed to read to end of the file");
514        assert!(VMClockShmHeader::read(&buffer).is_err());
515    }
516}