summaryrefslogtreecommitdiff
path: root/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp
blob: 4b45638e7cd2706044e4feba10662201bac9e1bb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
/* $Id$ */
/** @file
 * VBoxService - Guest Additions TimeSync Service.
 */

/*
 * Copyright (C) 2007-2023 Oracle and/or its affiliates.
 *
 * This file is part of VirtualBox base platform packages, as
 * available from https://www.virtualbox.org.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, in version 3 of the
 * License.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses>.
 *
 * SPDX-License-Identifier: GPL-3.0-only
 */


/** @page pg_vgsvc_timesync     VBoxService - The Time Sync Service
 *
 * The time sync subservice synchronizes the guest OS walltime with the host.
 *
 * The time sync service plays along with the Time Manager (TM) in the VMM
 * to keep the guest time accurate using the host machine as a reference.
 * Communication is facilitated by VMMDev.  TM will try its best to make sure
 * all timer ticks get delivered so that there isn't normally any need to
 * adjust the guest time.
 *
 * There are three normal (= acceptable) cases:
 *      -# When the service starts up. This is because ticks and such might
 *         be lost during VM and OS startup. (Need to figure out exactly why!)
 *      -# When the TM is unable to deliver all the ticks and swallows a
 *         backlog of ticks. The threshold for this is configurable with
 *         a default of 60 seconds.
 *      -# The time is adjusted on the host. This can be caused manually by
 *         the user or by some time sync daemon (NTP, LAN server, etc.).
 *
 * There are a number of very odd case where adjusting is needed. Here
 * are some of them:
 *      -# Timer device emulation inaccuracies (like rounding).
 *      -# Inaccuracies in time source VirtualBox uses.
 *      -# The Guest and/or Host OS doesn't perform proper time keeping. This
 *         can come about as a result of OS and/or hardware issues.
 *
 * The TM is our source for the host time and will make adjustments for
 * current timer delivery lag. The simplistic approach taken by TM is to
 * adjust the host time by the current guest timer delivery lag, meaning that
 * if the guest is behind 1 second with PIT/RTC/++ ticks, this should be
 * reflected in the guest wall time as well.
 *
 * Now, there is any amount of trouble we can cause by changing the time.
 * Most applications probably use the wall time when they need to measure
 * things. A walltime that is being juggled about every so often, even if just
 * a little bit, could occasionally upset these measurements by for instance
 * yielding negative results.
 *
 * This bottom line here is that the time sync service isn't really supposed
 * to do anything and will try avoid having to do anything when possible.
 *
 * The implementation uses the latency it takes to query host time as the
 * absolute maximum precision to avoid messing up under timer tick catchup
 * and/or heavy host/guest load. (Rationale is that a *lot* of stuff may
 * happen on our way back from ring-3 and TM/VMMDev since we're taking the
 * route thru the inner EM loop with its force flag processing.)
 *
 * But this latency has to be measured from our perspective, which means it
 * could just as easily come out as 0. (OS/2 and Windows guests only update
 * the current time when the timer ticks for instance.) The good thing is
 * that this isn't really a problem since we won't ever do anything unless
 * the drift is noticeable.
 *
 * It now boils down to these three (configuration) factors:
 *  -# g_cMsTimeSyncMinAdjust - The minimum drift we will ever bother with.
 *  -# g_TimeSyncLatencyFactor - The factor we multiply the latency by to
 *     calculate the dynamic minimum adjust factor.
 *  -# g_cMsTimeSyncMaxLatency - When to start discarding the data as utterly
 *     useless and take a rest (someone is too busy to give us good data).
 *  -# g_TimeSyncSetThreshold - The threshold at which we will just set the time
 *     instead of trying to adjust it (milliseconds).
 */


/*********************************************************************************************************************************
*   Header Files                                                                                                                 *
*********************************************************************************************************************************/
#ifdef RT_OS_WINDOWS
# include <iprt/win/windows.h>
#else
# include <unistd.h>
# include <errno.h>
# include <time.h>
# include <sys/time.h>
#endif

#include <iprt/assert.h>
#include <iprt/string.h>
#include <iprt/semaphore.h>
#include <iprt/time.h>
#include <iprt/thread.h>
#include <VBox/err.h>
#include <VBox/VBoxGuestLib.h>
#include "VBoxServiceInternal.h"
#include "VBoxServiceUtils.h"


/*********************************************************************************************************************************
*   Global Variables                                                                                                             *
*********************************************************************************************************************************/
/** The timesync interval (milliseconds). */
static uint32_t         g_TimeSyncInterval = 0;
/**
 * @see pg_vgsvc_timesync
 *
 * @remark  OS/2: There is either a 1 second resolution on the DosSetDateTime
 *                API or a bug in my settimeofday implementation.  Thus, don't
 *                bother unless there is at least a 1 second drift.
 */
#ifdef RT_OS_OS2
static uint32_t         g_cMsTimeSyncMinAdjust = 1000;
#else
static uint32_t         g_cMsTimeSyncMinAdjust = 100;
#endif
/** @see pg_vgsvc_timesync */
static uint32_t         g_TimeSyncLatencyFactor = 8;
/** @see pg_vgsvc_timesync */
static uint32_t         g_cMsTimeSyncMaxLatency = 250;
/** @see pg_vgsvc_timesync */
static uint32_t         g_TimeSyncSetThreshold = 20*60*1000;
/** Whether the next adjustment should just set the time instead of trying to
 * adjust it. This is used to implement --timesync-set-start.
 * For purposes of setting the kernel timezone, OS/2 always starts with this. */
#ifdef RT_OS_OS2
static bool volatile    g_fTimeSyncSetOnStart = true;
#else
static bool volatile    g_fTimeSyncSetOnStart = false;
#endif
/** Whether to set the time when the VM was restored. */
static bool             g_fTimeSyncSetOnRestore = true;
/** The logging verbosity level.
 *  This uses the global verbosity level by default. */
static uint32_t         g_cTimeSyncVerbosity = 0;

/** Current error count. Used to decide when to bitch and when not to. */
static uint32_t         g_cTimeSyncErrors = 0;

/** The semaphore we're blocking on. */
static RTSEMEVENTMULTI  g_TimeSyncEvent = NIL_RTSEMEVENTMULTI;

/** The VM session ID. Changes whenever the VM is restored or reset. */
static uint64_t         g_idTimeSyncSession;

#ifdef RT_OS_WINDOWS
/** Process token. */
static HANDLE           g_hTokenProcess = NULL;
/** Old token privileges. */
static TOKEN_PRIVILEGES g_TkOldPrivileges;
/** Backup values for time adjustment. */
static DWORD            g_dwWinTimeAdjustment;
static DWORD            g_dwWinTimeIncrement;
static BOOL             g_bWinTimeAdjustmentDisabled;
#endif


/**
 * @interface_method_impl{VBOXSERVICE,pfnPreInit}
 */
static DECLCALLBACK(int) vgsvcTimeSyncPreInit(void)
{
    /* Use global verbosity as default. */
    g_cTimeSyncVerbosity = g_cVerbosity;

#ifdef VBOX_WITH_GUEST_PROPS
    /** @todo Merge this function with vgsvcTimeSyncOption() to generalize
     *        the "command line args override guest property values" behavior. */

    /*
     * Read the service options from the VM's guest properties.
     * Note that these options can be overridden by the command line options later.
     */
    uint32_t uGuestPropSvcClientID;
    int rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID);
    if (RT_FAILURE(rc))
    {
        if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
        {
            VGSvcVerbose(0, "VMInfo: Guest property service is not available, skipping\n");
            rc = VINF_SUCCESS;
        }
        else
            VGSvcError("Failed to connect to the guest property service! Error: %Rrc\n", rc);
    }
    else
    {
        rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-interval",
                                 &g_TimeSyncInterval, 50, UINT32_MAX - 1);
        if (   RT_SUCCESS(rc)
            || rc == VERR_NOT_FOUND)
            rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-min-adjust",
                                     &g_cMsTimeSyncMinAdjust, 0, 3600000);
        if (   RT_SUCCESS(rc)
            || rc == VERR_NOT_FOUND)
            rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-latency-factor",
                                     &g_TimeSyncLatencyFactor, 1, 1024);
        if (   RT_SUCCESS(rc)
            || rc == VERR_NOT_FOUND)
            rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-max-latency",
                                     &g_cMsTimeSyncMaxLatency, 1, 3600000);
        if (   RT_SUCCESS(rc)
            || rc == VERR_NOT_FOUND)
            rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-threshold",
                                     &g_TimeSyncSetThreshold, 0, 7*24*60*60*1000 /* a week */);

        if (VbglR3GuestPropExist(uGuestPropSvcClientID,
                                 "/VirtualBox/GuestAdd/VBoxService/--timesync-set-start"))
            g_fTimeSyncSetOnStart = true;

        if (VbglR3GuestPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-no-set-start"))
            g_fTimeSyncSetOnStart = false;


        if (VbglR3GuestPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-on-restore"))
            g_fTimeSyncSetOnRestore = true;

        if (VbglR3GuestPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-no-set-on-restore"))
            g_fTimeSyncSetOnRestore = false;

        uint32_t uValue;
        rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-verbosity",
                                 &uValue, 0 /*uMin*/, 255 /*uMax*/);
        if (RT_SUCCESS(rc))
            g_cTimeSyncVerbosity = uValue;

        VbglR3GuestPropDisconnect(uGuestPropSvcClientID);
    }

    if (rc == VERR_NOT_FOUND) /* If a value is not found, don't be sad! */
        rc = VINF_SUCCESS;
    return rc;
#else
    /* Nothing to do here yet. */
    return VINF_SUCCESS;
#endif
}


/**
 * Displays a verbose message based on the currently
 * set timesync verbosity level.
 *
 * @param   iLevel      Minimum log level required to display this message.
 * @param   pszFormat   The message text.
 * @param   ...         Format arguments.
 */
static void vgsvcTimeSyncLog(unsigned iLevel, const char *pszFormat, ...)
{
    if (iLevel <= g_cTimeSyncVerbosity)
    {
        va_list va;
        va_start(va, pszFormat);
        VGSvcLogV(pszFormat, va);
        va_end(va);
    }
}


/**
 * @interface_method_impl{VBOXSERVICE,pfnOption}
 */
static DECLCALLBACK(int) vgsvcTimeSyncOption(const char **ppszShort, int argc, char **argv, int *pi)
{
    int rc = VINF_SUCCESS;
    if (ppszShort)
        rc = -1 ;/* no short options */
    else if (!strcmp(argv[*pi], "--timesync-interval"))
        rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncInterval, 50, UINT32_MAX - 1);
    else if (!strcmp(argv[*pi], "--timesync-min-adjust"))
        rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cMsTimeSyncMinAdjust, 0, 3600000);
    else if (!strcmp(argv[*pi], "--timesync-latency-factor"))
        rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncLatencyFactor, 1, 1024);
    else if (!strcmp(argv[*pi], "--timesync-max-latency"))
        rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cMsTimeSyncMaxLatency, 1, 3600000);
    else if (!strcmp(argv[*pi], "--timesync-set-threshold"))
        rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncSetThreshold, 0, 7*24*60*60*1000); /* a week */
    else if (!strcmp(argv[*pi], "--timesync-set-start"))
        g_fTimeSyncSetOnStart = true;
    else if (!strcmp(argv[*pi], "--timesync-no-set-start"))
        g_fTimeSyncSetOnStart = false;
    else if (!strcmp(argv[*pi], "--timesync-set-on-restore"))
        g_fTimeSyncSetOnRestore = true;
    else if (!strcmp(argv[*pi], "--timesync-no-set-on-restore"))
        g_fTimeSyncSetOnRestore = false;
    else if (!strcmp(argv[*pi], "--timesync-verbosity"))
        rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cTimeSyncVerbosity, 0 /*uMin*/, 255 /*uMax*/);
    else
        rc = -1;

    return rc;
}


/**
 * @interface_method_impl{VBOXSERVICE,pfnInit}
 */
static DECLCALLBACK(int) vgsvcTimeSyncInit(void)
{
    /*
     * If not specified, find the right interval default.
     * Then create the event sem to block on.
     */
    if (!g_TimeSyncInterval)
        g_TimeSyncInterval = g_DefaultInterval * 1000;
    if (!g_TimeSyncInterval)
        g_TimeSyncInterval = 10 * 1000;

    VbglR3GetSessionId(&g_idTimeSyncSession);
    /* The status code is ignored as this information is not available with VBox < 3.2.10. */

    int rc = RTSemEventMultiCreate(&g_TimeSyncEvent);
    AssertRC(rc);
#ifdef RT_OS_WINDOWS
    if (RT_SUCCESS(rc))
    {
        /*
         * Adjust privileges of this process so we can make system time adjustments.
         */
        if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &g_hTokenProcess))
        {
            TOKEN_PRIVILEGES tkPriv;
            RT_ZERO(tkPriv);
            tkPriv.PrivilegeCount = 1;
            tkPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
            if (LookupPrivilegeValue(NULL, SE_SYSTEMTIME_NAME, &tkPriv.Privileges[0].Luid))
            {
                DWORD cbRet = sizeof(g_TkOldPrivileges);
                if (AdjustTokenPrivileges(g_hTokenProcess, FALSE, &tkPriv, sizeof(TOKEN_PRIVILEGES), &g_TkOldPrivileges, &cbRet))
                    rc = VINF_SUCCESS;
                else
                {
                    DWORD dwErr = GetLastError();
                    rc = RTErrConvertFromWin32(dwErr);
                    VGSvcError("vgsvcTimeSyncInit: Adjusting token privileges (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n",
                               dwErr, rc);
                }
            }
            else
            {
                DWORD dwErr = GetLastError();
                rc = RTErrConvertFromWin32(dwErr);
                VGSvcError("vgsvcTimeSyncInit: Looking up token privileges (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n",
                           dwErr, rc);
            }
            if (RT_FAILURE(rc))
            {
                CloseHandle(g_hTokenProcess);
                g_hTokenProcess = NULL;
            }
        }
        else
        {
            DWORD dwErr = GetLastError();
            rc = RTErrConvertFromWin32(dwErr);
            VGSvcError("vgsvcTimeSyncInit: Opening process token (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n",
                       dwErr, rc);
            g_hTokenProcess = NULL;
        }
    }

    if (g_pfnGetSystemTimeAdjustment)
    {
        if (g_pfnGetSystemTimeAdjustment(&g_dwWinTimeAdjustment, &g_dwWinTimeIncrement, &g_bWinTimeAdjustmentDisabled))
            vgsvcTimeSyncLog(0, "vgsvcTimeSyncInit: Initially %ld (100ns) units per %ld (100 ns) units interval, disabled=%d\n",
                             g_dwWinTimeAdjustment, g_dwWinTimeIncrement, g_bWinTimeAdjustmentDisabled ? 1 : 0);
        else
        {
            DWORD dwErr = GetLastError();
            rc = RTErrConvertFromWin32(dwErr);
            VGSvcError("vgsvcTimeSyncInit: Could not get time adjustment values! Last error: %ld!\n", dwErr);
        }
    }
#endif /* RT_OS_WINDOWS */

    return rc;
}


/**
 * Try adjusting the time using adjtime or similar.
 *
 * @returns true on success, false on failure.
 *
 * @param   pDrift          The time adjustment.
 */
static bool vgsvcTimeSyncAdjust(PCRTTIMESPEC pDrift)
{
#ifdef RT_OS_WINDOWS
/** @todo r=bird: g_hTokenProcess cannot be NULL here.
 *        vgsvcTimeSyncInit will fail and the service will not be started with
 *        it being NULL.  vgsvcTimeSyncInit OTOH will *NOT* be called until the
 *        service thread has terminated.  If anything
 *        else is the case, there is buggy code somewhere.*/
    if (g_hTokenProcess == NULL) /* Is the token already closed when shutting down? */
        return false;

    /* The API appeared in NT 3.50. */
    if (   !g_pfnSetSystemTimeAdjustment
        || !g_pfnGetSystemTimeAdjustment)
        return false;

    DWORD dwWinTimeAdjustment, dwWinNewTimeAdjustment, dwWinTimeIncrement;
    BOOL  fWinTimeAdjustmentDisabled;
    if (g_pfnGetSystemTimeAdjustment(&dwWinTimeAdjustment, &dwWinTimeIncrement, &fWinTimeAdjustmentDisabled))
    {
        DWORD dwDiffMax = g_dwWinTimeAdjustment * 0.50;
        DWORD dwDiffNew =   dwWinTimeAdjustment * 0.10;

        if (RTTimeSpecGetMilli(pDrift) > 0)
        {
            dwWinNewTimeAdjustment = dwWinTimeAdjustment + dwDiffNew;
            if (dwWinNewTimeAdjustment > (g_dwWinTimeAdjustment + dwDiffMax))
            {
                dwWinNewTimeAdjustment = g_dwWinTimeAdjustment + dwDiffMax;
                dwDiffNew = dwDiffMax;
            }
        }
        else
        {
            dwWinNewTimeAdjustment = dwWinTimeAdjustment - dwDiffNew;
            if (dwWinNewTimeAdjustment < (g_dwWinTimeAdjustment - dwDiffMax))
            {
                dwWinNewTimeAdjustment = g_dwWinTimeAdjustment - dwDiffMax;
                dwDiffNew = dwDiffMax;
            }
        }

        vgsvcTimeSyncLog(3, "vgsvcTimeSyncAdjust: Drift=%lldms\n", RTTimeSpecGetMilli(pDrift));
        vgsvcTimeSyncLog(3, "vgsvcTimeSyncAdjust: OrgTA=%ld, CurTA=%ld, NewTA=%ld, DiffNew=%ld, DiffMax=%ld\n",
                         g_dwWinTimeAdjustment, dwWinTimeAdjustment, dwWinNewTimeAdjustment, dwDiffNew, dwDiffMax);
        if (g_pfnSetSystemTimeAdjustment(dwWinNewTimeAdjustment, FALSE /* Periodic adjustments enabled. */))
        {
            g_cTimeSyncErrors = 0;
            return true;
        }

        if (g_cTimeSyncErrors++ < 10)
             VGSvcError("vgsvcTimeSyncAdjust: SetSystemTimeAdjustment failed, error=%u\n", GetLastError());
    }
    else if (g_cTimeSyncErrors++ < 10)
        VGSvcError("vgsvcTimeSyncAdjust: GetSystemTimeAdjustment failed, error=%ld\n", GetLastError());

#elif defined(RT_OS_OS2) || defined(RT_OS_HAIKU)
    /* No API for doing gradual time adjustments. */

#else /* PORTME */
    /*
     * Try using adjtime(), most unix-like systems have this.
     */
    struct timeval tv;
    RTTimeSpecGetTimeval(pDrift, &tv);
    if (adjtime(&tv, NULL) == 0)
    {
        vgsvcTimeSyncLog(1, "vgsvcTimeSyncAdjust: adjtime by %RDtimespec\n", pDrift);
        g_cTimeSyncErrors = 0;
        return true;
    }
#endif

    /* failed */
    return false;
}


/**
 * Cancels any pending time adjustment.
 *
 * Called when we've caught up and before calls to vgsvcTimeSyncSet.
 */
static void vgsvcTimeSyncCancelAdjust(void)
{
#ifdef RT_OS_WINDOWS
/** @todo r=bird: g_hTokenProcess cannot be NULL here.  See argumentation in
 *        vgsvcTimeSyncAdjust.  */
    if (g_hTokenProcess == NULL) /* No process token (anymore)? */
        return;
    if (!g_pfnSetSystemTimeAdjustment)
        return;
    if (g_pfnSetSystemTimeAdjustment(0, TRUE /* Periodic adjustments disabled. */))
        vgsvcTimeSyncLog(5, "vgsvcTimeSyncCancelAdjust: Windows Time Adjustment is now disabled.\n");
    else if (g_cTimeSyncErrors++ < 10)
        VGSvcError("vgsvcTimeSyncCancelAdjust: SetSystemTimeAdjustment(,disable) failed, error=%u\n", GetLastError());
#endif /* !RT_OS_WINDOWS */
}


/**
 * Set the wall clock to compensate for drift.
 *
 * @returns true on success, false on failure.
 *
 * @param   pDrift              The time adjustment.
 */
static void vgsvcTimeSyncSet(PCRTTIMESPEC pDrift)
{
    /*
     * Query the current time, adjust it by adding the drift and set it.
     */
    RTTIMESPEC NewGuestTime;
    int rc = RTTimeSet(RTTimeSpecAdd(RTTimeNow(&NewGuestTime), pDrift));
    if (RT_SUCCESS(rc))
    {
        /* Succeeded - reset the error count and log the change. */
        g_cTimeSyncErrors = 0;

        if (g_cTimeSyncVerbosity >= 1)
        {
            char        sz[64];
            RTTIME      Time;
            vgsvcTimeSyncLog(1, "time set to %s\n", RTTimeToString(RTTimeExplode(&Time, &NewGuestTime), sz, sizeof(sz)));
#ifdef DEBUG
            RTTIMESPEC  Tmp;
            vgsvcTimeSyncLog(3, "        now %s\n", RTTimeToString(RTTimeExplode(&Time, RTTimeNow(&Tmp)), sz, sizeof(sz)));
#endif
        }
    }
    else if (g_cTimeSyncErrors++ < 10)
        VGSvcError("vgsvcTimeSyncSet: RTTimeSet(%RDtimespec) failed: %Rrc\n", &NewGuestTime, rc);
}


/**
 * @interface_method_impl{VBOXSERVICE,pfnWorker}
 */
DECLCALLBACK(int) vgsvcTimeSyncWorker(bool volatile *pfShutdown)
{
    RTTIME Time;
    int rc = VINF_SUCCESS;

    /*
     * Tell the control thread that it can continue spawning services.
     */
    RTThreadUserSignal(RTThreadSelf());

    /*
     * Initialize the last host and guest times to prevent log message.
     * We also track whether we set the time in the previous loop.
     */
    RTTIMESPEC HostLast;
    if (RT_FAILURE(VbglR3GetHostTime(&HostLast)))
        RTTimeSpecSetNano(&HostLast, 0);
    RTTIMESPEC GuestLast;
    RTTimeNow(&GuestLast);
    bool fSetTimeLastLoop = false;

    /*
     * The Work Loop.
     */
    for (;;)
    {
        /*
         * Try to get a reliable time reading.
         */
        int cTries = 3;
        do
        {
            /*
             * Query the session id (first to keep lantency low) and the time.
             */
            uint64_t idNewSession = g_idTimeSyncSession;
            if (g_fTimeSyncSetOnRestore)
                VbglR3GetSessionId(&idNewSession);

            RTTIMESPEC GuestNow0;
            RTTimeNow(&GuestNow0);

            RTTIMESPEC HostNow;
            int rc2 = VbglR3GetHostTime(&HostNow);
            if (RT_FAILURE(rc2))
            {
                if (g_cTimeSyncErrors++ < 10)
                    VGSvcError("vgsvcTimeSyncWorker: VbglR3GetHostTime failed; rc2=%Rrc\n", rc2);
                break;
            }

            RTTIMESPEC GuestNow;
            RTTimeNow(&GuestNow);

            /*
             * Calc latency and check if it's ok.
             */
            RTTIMESPEC GuestElapsed = GuestNow;
            RTTimeSpecSub(&GuestElapsed, &GuestNow0);
            if ((uint32_t)RTTimeSpecGetMilli(&GuestElapsed) < g_cMsTimeSyncMaxLatency)
            {
                /*
                 * If we were just restored, set the adjustment threshold to zero to force a resync.
                 */
                uint32_t TimeSyncSetThreshold = g_TimeSyncSetThreshold;
                if (   g_fTimeSyncSetOnRestore
                    && idNewSession != g_idTimeSyncSession)
                {
                    vgsvcTimeSyncLog(2, "vgsvcTimeSyncWorker: The VM session ID changed, forcing resync.\n");
                    g_idTimeSyncSession  = idNewSession;
                    TimeSyncSetThreshold = 0;
                }

                /*
                 * Calculate the adjustment threshold and the current drift.
                 */
                uint32_t MinAdjust = RTTimeSpecGetMilli(&GuestElapsed) * g_TimeSyncLatencyFactor;
                if (MinAdjust < g_cMsTimeSyncMinAdjust)
                    MinAdjust = g_cMsTimeSyncMinAdjust;

                RTTIMESPEC Drift = HostNow;
                RTTimeSpecSub(&Drift, &GuestNow);
                if (RTTimeSpecGetMilli(&Drift) < 0)
                    MinAdjust += g_cMsTimeSyncMinAdjust; /* extra buffer against moving time backwards. */

                RTTIMESPEC AbsDrift = Drift;
                RTTimeSpecAbsolute(&AbsDrift);

                if (g_cTimeSyncVerbosity >= 4)
                {
                    char sz1[64];
                    char sz2[64];
                    vgsvcTimeSyncLog(4, "vgsvcTimeSyncWorker: Host: %s (MinAdjust: %RU32 ms), Guest: %s => %RDtimespec drift\n",
                                     RTTimeToString(RTTimeExplode(&Time, &HostNow), sz1, sizeof(sz1)), MinAdjust,
                                     RTTimeToString(RTTimeExplode(&Time, &GuestNow), sz2, sizeof(sz2)), &Drift);
                }

                bool fSetTimeInThisLoop = false;
                uint64_t AbsDriftMilli = RTTimeSpecGetMilli(&AbsDrift);
                if (   AbsDriftMilli > MinAdjust
                    || g_fTimeSyncSetOnStart)
                {
                    /*
                     * Ok, the drift is above the threshold.
                     *
                     * Try a gradual adjustment first, if that fails or the drift is
                     * too big, fall back on just setting the time.
                     */
                    if (   AbsDriftMilli > TimeSyncSetThreshold
                        || g_fTimeSyncSetOnStart
                        || !vgsvcTimeSyncAdjust(&Drift))
                    {
                        vgsvcTimeSyncCancelAdjust();
                        vgsvcTimeSyncSet(&Drift);
                        fSetTimeInThisLoop = true;
                    }

                    /*
                     * Log radical host time changes.
                     */
                    int64_t cNsHostDelta = RTTimeSpecGetNano(&HostNow) - RTTimeSpecGetNano(&HostLast);
                    if ((uint64_t)RT_ABS(cNsHostDelta) > RT_NS_1HOUR / 2)
                        vgsvcTimeSyncLog(0, "vgsvcTimeSyncWorker: Radical host time change: %'RI64ns (HostNow=%RDtimespec HostLast=%RDtimespec)\n",
                                         cNsHostDelta, &HostNow, &HostLast);
                }
                else
                    vgsvcTimeSyncCancelAdjust();
                HostLast = HostNow;

                /*
                 * Log radical guest time changes (we could be the cause of these, mind).
                 * Note! Right now we don't care about an extra log line after we called
                 *       vgsvcTimeSyncSet.  fSetTimeLastLoop helps show it though.
                 */
                int64_t cNsGuestDelta = RTTimeSpecGetNano(&GuestNow) - RTTimeSpecGetNano(&GuestLast);
                if ((uint64_t)RT_ABS(cNsGuestDelta) > RT_NS_1HOUR / 2)
                    vgsvcTimeSyncLog(0, "vgsvcTimeSyncWorker: Radical guest time change: %'RI64ns (GuestNow=%RDtimespec GuestLast=%RDtimespec fSetTimeLastLoop=%RTbool)\n",
                                     cNsGuestDelta, &GuestNow, &GuestLast, fSetTimeLastLoop);
                GuestLast = GuestNow;
                fSetTimeLastLoop = fSetTimeInThisLoop;
                break;
            }
            vgsvcTimeSyncLog(3, "vgsvcTimeSyncWorker: %RDtimespec: latency too high (%RDtimespec, max %ums) sleeping 1s\n",
                             &GuestNow, &GuestElapsed, g_cMsTimeSyncMaxLatency);
            RTThreadSleep(1000);
        } while (--cTries > 0);

        /* Clear the set-next/set-start flag. */
        g_fTimeSyncSetOnStart = false;

        /*
         * Block for a while.
         *
         * The event semaphore takes care of ignoring interruptions and it
         * allows us to implement service wakeup later.
         */
        if (*pfShutdown)
            break;
        int rc2 = RTSemEventMultiWait(g_TimeSyncEvent, g_TimeSyncInterval);
        if (*pfShutdown)
            break;
        if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2))
        {
            VGSvcError("vgsvcTimeSyncWorker: RTSemEventMultiWait failed; rc2=%Rrc\n", rc2);
            rc = rc2;
            break;
        }
    }

    vgsvcTimeSyncCancelAdjust();
    return rc;
}


/**
 * @interface_method_impl{VBOXSERVICE,pfnStop}
 */
static DECLCALLBACK(void) vgsvcTimeSyncStop(void)
{
    if (g_TimeSyncEvent != NIL_RTSEMEVENTMULTI)
        RTSemEventMultiSignal(g_TimeSyncEvent);
}


/**
 * @interface_method_impl{VBOXSERVICE,pfnTerm}
 */
static DECLCALLBACK(void) vgsvcTimeSyncTerm(void)
{
#ifdef RT_OS_WINDOWS
    /*
     * Restore the SE_SYSTEMTIME_NAME token privileges (if init succeeded).
     */
    if (g_hTokenProcess)
    {
        if (!AdjustTokenPrivileges(g_hTokenProcess, FALSE, &g_TkOldPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL))
        {
            DWORD dwErr = GetLastError();
            VGSvcError("vgsvcTimeSyncTerm: Restoring token privileges (SE_SYSTEMTIME_NAME) failed with code %u!\n", dwErr);
        }
        CloseHandle(g_hTokenProcess);
        g_hTokenProcess = NULL;
    }
#endif /* !RT_OS_WINDOWS */

    if (g_TimeSyncEvent != NIL_RTSEMEVENTMULTI)
    {
        RTSemEventMultiDestroy(g_TimeSyncEvent);
        g_TimeSyncEvent = NIL_RTSEMEVENTMULTI;
    }
}


/**
 * The 'timesync' service description.
 */
VBOXSERVICE g_TimeSync =
{
    /* pszName. */
    "timesync",
    /* pszDescription. */
    "Time synchronization",
    /* pszUsage. */
    "           [--timesync-interval <ms>] [--timesync-min-adjust <ms>]\n"
    "           [--timesync-latency-factor <x>] [--timesync-max-latency <ms>]\n"
    "           [--timesync-set-threshold <ms>]\n"
    "           [--timesync-set-start|--timesync-no-set-start]\n"
    "           [--timesync-set-on-restore|--timesync-no-set-on-restore]\n"
    "           [--timesync-verbosity <level>]"
    ,
    /* pszOptions. */
    "    --timesync-interval     Specifies the interval at which to synchronize the\n"
    "                            time with the host. The default is 10000 ms.\n"
    "    --timesync-min-adjust   The minimum absolute drift value measured in\n"
    "                            milliseconds to make adjustments for.\n"
    "                            The default is 1000 ms on OS/2 and 100 ms elsewhere.\n"
    "    --timesync-latency-factor\n"
    "                            The factor to multiply the time query latency with\n"
    "                            to calculate the dynamic minimum adjust time.\n"
    "                            The default is 8 times.\n"
    "    --timesync-max-latency  The max host timer query latency to accept.\n"
    "                            The default is 250 ms.\n"
    "    --timesync-set-threshold\n"
    "                            The absolute drift threshold, given as milliseconds,\n"
    "                            where to start setting the time instead of trying to\n"
    "                            adjust it. The default is 20 min.\n"
    "    --timesync-set-start, --timesync-no-set-start    \n"
    "                            Set the time when starting the time sync service.\n"
#ifdef RT_OS_OS2
    "                            Default: --timesync-set-start\n"
#else
    "                            Default: --timesync-no-set-start\n"
#endif
    "    --timesync-set-on-restore, --timesync-no-set-on-restore\n"
    "                            Whether to immediately set the time when the VM is\n"
    "                            restored or not.  Default: --timesync-set-on-restore\n"
    "    --timesync-verbosity    Sets the verbosity level.  Defaults to service wide\n"
    "                            verbosity level.\n"
    ,
    /* methods */
    vgsvcTimeSyncPreInit,
    vgsvcTimeSyncOption,
    vgsvcTimeSyncInit,
    vgsvcTimeSyncWorker,
    vgsvcTimeSyncStop,
    vgsvcTimeSyncTerm
};