|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | + |
| 3 | +use crate::virtio::VsockConfig; |
| 4 | +use std::io::{BufRead, BufReader, Write}; |
| 5 | +use std::os::unix::net::UnixStream; |
| 6 | +use std::path::PathBuf; |
| 7 | +use std::time::SystemTime; |
| 8 | +use std::time::UNIX_EPOCH; |
| 9 | + |
| 10 | +use core_foundation::runloop::{ |
| 11 | + kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetCurrent, CFRunLoopRun, __CFRunLoopSource, |
| 12 | +}; |
| 13 | +use objc2_io_kit::{ |
| 14 | + io_object_t, io_service_t, kIOMessageSystemHasPoweredOn, kIOMessageSystemWillPowerOn, |
| 15 | + kIOMessageSystemWillSleep, IONotificationPort, IORegisterForSystemPower, |
| 16 | +}; |
| 17 | +use std::{ |
| 18 | + ffi::c_void, |
| 19 | + sync::mpsc::{channel, Receiver, Sender}, |
| 20 | +}; |
| 21 | + |
| 22 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 23 | +pub enum Activity { |
| 24 | + Sleep, |
| 25 | + Wake, |
| 26 | +} |
| 27 | + |
| 28 | +#[allow(non_upper_case_globals)] |
| 29 | +extern "C-unwind" fn power_callback( |
| 30 | + refcon: *mut c_void, |
| 31 | + _service: io_service_t, |
| 32 | + message_type: u32, |
| 33 | + _message_argument: *mut c_void, |
| 34 | +) { |
| 35 | + let tx = unsafe { &*(refcon as *mut Sender<Activity>) }; |
| 36 | + log::debug!("Power callback called: {:X?}", message_type); |
| 37 | + let activity = match message_type { |
| 38 | + kIOMessageSystemWillSleep => Some(Activity::Sleep), |
| 39 | + kIOMessageSystemWillPowerOn | kIOMessageSystemHasPoweredOn => Some(Activity::Wake), |
| 40 | + _ => { |
| 41 | + log::debug!("Unknown message type: {:X?}", message_type); |
| 42 | + None |
| 43 | + } |
| 44 | + }; |
| 45 | + if let Some(activity) = activity { |
| 46 | + if let Err(e) = tx.send(activity) { |
| 47 | + log::error!("Failed to send activity: {e}"); |
| 48 | + } |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +pub fn start_power_monitor() -> Receiver<Activity> { |
| 53 | + let (tx, rx) = channel::<Activity>(); |
| 54 | + std::thread::spawn(move || unsafe { |
| 55 | + let tx_ptr = Box::into_raw(Box::new(tx)); |
| 56 | + let mut notifier_port: *mut IONotificationPort = std::ptr::null_mut(); |
| 57 | + let mut notifier_object: io_object_t = 0; |
| 58 | + |
| 59 | + let root_port = IORegisterForSystemPower( |
| 60 | + tx_ptr as *mut c_void, |
| 61 | + &mut notifier_port, |
| 62 | + Some(power_callback), |
| 63 | + &mut notifier_object, |
| 64 | + ); |
| 65 | + if root_port == 0 { |
| 66 | + log::error!("Failed to register for system power notifications"); |
| 67 | + return; |
| 68 | + } |
| 69 | + let run_loop_source = IONotificationPort::run_loop_source(notifier_port).unwrap(); |
| 70 | + CFRunLoopAddSource( |
| 71 | + CFRunLoopGetCurrent(), |
| 72 | + std::ptr::from_ref(&*run_loop_source) as *mut __CFRunLoopSource, |
| 73 | + kCFRunLoopCommonModes, |
| 74 | + ); |
| 75 | + CFRunLoopRun(); |
| 76 | + }); |
| 77 | + rx |
| 78 | +} |
| 79 | + |
| 80 | +fn sync_time(socket_url: PathBuf) { |
| 81 | + let mut stream = match UnixStream::connect(&socket_url) { |
| 82 | + Ok(stream) => stream, |
| 83 | + Err(e) => { |
| 84 | + log::error!( |
| 85 | + "Failed to connect to timesync socket {:?}: {}", |
| 86 | + socket_url, |
| 87 | + e |
| 88 | + ); |
| 89 | + return; |
| 90 | + } |
| 91 | + }; |
| 92 | + |
| 93 | + let time_ns = SystemTime::now() |
| 94 | + .duration_since(UNIX_EPOCH) |
| 95 | + .unwrap() |
| 96 | + .as_nanos(); |
| 97 | + let cmd = |
| 98 | + format!("{{\"execute\": \"guest-set-time\", \"arguments\":{{\"time\": {time_ns}}}}}\n"); |
| 99 | + |
| 100 | + if let Err(e) = stream.write_all(cmd.as_bytes()) { |
| 101 | + log::error!("Failed to write to timesync socket: {}", e); |
| 102 | + return; |
| 103 | + } |
| 104 | + |
| 105 | + let mut reader = BufReader::new(&stream); |
| 106 | + let mut response = String::new(); |
| 107 | + match reader.read_line(&mut response) { |
| 108 | + Ok(_) => { |
| 109 | + log::info!("Time synced to {time_ns}"); |
| 110 | + } |
| 111 | + Err(e) => { |
| 112 | + log::error!("Failed to read qemu-guest-agent response: {e}"); |
| 113 | + } |
| 114 | + } |
| 115 | +} |
| 116 | + |
| 117 | +pub fn timesync_listener(vsock_config: VsockConfig) { |
| 118 | + let rx = start_power_monitor(); |
| 119 | + for activity in rx { |
| 120 | + match activity { |
| 121 | + Activity::Sleep => { |
| 122 | + log::debug!("System is going to sleep"); |
| 123 | + } |
| 124 | + Activity::Wake => { |
| 125 | + log::debug!("System is waking up. Syncing time..."); |
| 126 | + sync_time(vsock_config.socket_url.clone()); |
| 127 | + } |
| 128 | + } |
| 129 | + } |
| 130 | +} |
0 commit comments