Skip to content

Commit d4c2afa

Browse files
committed
guestagent: support setting guest time
Automatically set guest time on GA connect and VM resume. Resolves #3218
1 parent 7377b3a commit d4c2afa

File tree

4 files changed

+58
-0
lines changed

4 files changed

+58
-0
lines changed

Managers/UTMQemuGuestAgent.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ NS_ASSUME_NONNULL_BEGIN
3030
/// - Parameter completion: Callback to run on completion
3131
- (void)synchronizeWithCompletion:(void (^ _Nullable)(NSError * _Nullable))completion;
3232

33+
/// Set guest time
34+
/// - Parameters:
35+
/// - time: time in seconds, relative to the Epoch of 1970-01-01 in UTC.
36+
/// - completion: Callback to run on completion
37+
- (void)guestSetTime:(NSTimeInterval)time withCompletion:(void (^ _Nullable)(NSError * _Nullable))completion;
38+
3339
@end
3440

3541
NS_ASSUME_NONNULL_END

Managers/UTMQemuGuestAgent.m

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,17 @@ - (void)_withSynchronizeBlock:(NSError * _Nullable (^)(void))block withCompletio
103103
});
104104
}
105105

106+
- (void)guestSetTime:(NSTimeInterval)time withCompletion:(void (^ _Nullable)(NSError * _Nullable))completion {
107+
int64_t timeNanoseconds = (int64_t)(time * NSEC_PER_SEC);
108+
[self _withSynchronizeBlock:^{
109+
Error *qerr = NULL;
110+
qmp_guest_set_time(true, timeNanoseconds, &qerr, (__bridge void *)self);
111+
if (qerr) {
112+
return [self errorForQerror:qerr];
113+
} else {
114+
return (NSError *)nil;
115+
}
116+
} withCompletion:completion];
117+
}
118+
106119
@end

Managers/UTMQemuVirtualMachine.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ NS_ASSUME_NONNULL_BEGIN
2525

2626
@property (nonatomic, weak, nullable) id<UTMSpiceIODelegate> ioDelegate;
2727

28+
/// If non-null, provides access to the QEMU guest agent interface
29+
@property (nonatomic, readonly, nullable) UTMQemuGuestAgent *guestAgent;
30+
2831
/// Set to true to request guest tools install.
2932
///
3033
/// This property is observable and must only be accessed on the main thread.

Managers/UTMQemuVirtualMachine.m

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#import "UTMQemuVirtualMachine.h"
2323
#import "UTMQemuVirtualMachine+SPICE.h"
2424
#import "UTMQemuMonitor.h"
25+
#import "UTMQemuGuestAgent.h"
2526
#import "UTMQemuSystem.h"
2627
#import "UTMSpiceIO.h"
2728
#import "UTMLogging.h"
@@ -33,9 +34,12 @@
3334
extern NSString *const kUTMBundleConfigFilename;
3435
NSString *const kSuspendSnapshotName = @"suspend";
3536

37+
static void *SpiceIoServiceGuestAgentContext = &SpiceIoServiceGuestAgentContext;
38+
3639
@interface UTMQemuVirtualMachine () <UTMLoggingDelegate, UTMQemuMonitorDelegate>
3740

3841
@property (nonatomic, readwrite, nullable) UTMQemuMonitor *qemu;
42+
@property (nonatomic, readwrite, nullable) UTMQemuGuestAgent *guestAgent;
3943
@property (nonatomic, readwrite, nullable) UTMQemuSystem *system;
4044
@property (nonatomic, readwrite, nullable) UTMSpiceIO *ioService;
4145
@property (nonatomic, weak) id<UTMSpiceIODelegate> ioServiceDelegate;
@@ -188,6 +192,10 @@ - (void)_vmStartWithCompletion:(void (^)(NSError * _Nullable))completion {
188192
self.ioService = [[UTMSpiceIO alloc] initWithConfiguration:self.config];
189193
self.ioService.delegate = self.ioServiceDelegate;
190194
self.ioServiceDelegate = nil;
195+
[self.ioService addObserver:self
196+
forKeyPath:@"qemuGuestAgent"
197+
options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionInitial
198+
context:SpiceIoServiceGuestAgentContext];
191199

192200
NSError *spiceError;
193201
if (![self.ioService startWithError:&spiceError]) {
@@ -336,6 +344,7 @@ - (void)_vmStopForce:(BOOL)force completion:(void (^)(NSError * _Nullable))compl
336344
}
337345
self.qemu.delegate = nil;
338346
self.qemu = nil;
347+
[self.ioService removeObserver:self forKeyPath:@"qemuGuestAgent" context:SpiceIoServiceGuestAgentContext];
339348
self.ioService = nil;
340349

341350
if (force || dispatch_semaphore_wait(self.qemuDidExitEvent, dispatch_time(DISPATCH_TIME_NOW, kStopTimeout)) != 0) {
@@ -586,6 +595,32 @@ - (void)vmGuestPowerDownWithCompletion:(void (^)(NSError * _Nullable))completion
586595
});
587596
}
588597

598+
#pragma mark - QEMU Guest agent
599+
600+
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
601+
if (context == SpiceIoServiceGuestAgentContext) {
602+
UTMQemuGuestAgent *guestAgent = ((UTMSpiceIO *)object).qemuGuestAgent;
603+
if (guestAgent == nil && self.guestAgent != nil) {
604+
[self _didDisconnectGuestAgent:self.guestAgent];
605+
}
606+
if (guestAgent != nil) {
607+
[self _didConnectGuestAgent:guestAgent];
608+
}
609+
self.guestAgent = guestAgent;
610+
} else {
611+
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
612+
}
613+
}
614+
615+
- (void)_didConnectGuestAgent:(UTMQemuGuestAgent *)guestAgent {
616+
UTMLog(@"QEMU guest agent has connected.");
617+
[guestAgent guestSetTime:NSDate.now.timeIntervalSince1970 withCompletion:nil];
618+
}
619+
620+
- (void)_didDisconnectGuestAgent:(UTMQemuGuestAgent *)guestAgent {
621+
UTMLog(@"QEMU guest agent has disconnected.");
622+
}
623+
589624
#pragma mark - Qemu manager delegate
590625

591626
- (void)qemuHasWakeup:(UTMQemuMonitor *)monitor {
@@ -594,6 +629,7 @@ - (void)qemuHasWakeup:(UTMQemuMonitor *)monitor {
594629

595630
- (void)qemuHasResumed:(UTMQemuMonitor *)monitor {
596631
UTMLog(@"qemuHasResumed");
632+
[self.guestAgent guestSetTime:NSDate.now.timeIntervalSince1970 withCompletion:nil];
597633
}
598634

599635
- (void)qemuHasStopped:(UTMQemuMonitor *)monitor {

0 commit comments

Comments
 (0)