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}