sys_macosx: add support for ntp_adjtime() on macOS 10.13+

macOS 10.13 will implement the ntp_adjtime() system call, allowing
better control over the system clock than is possible with the existing
adjtime() system call. chronyd will support both the older and newer
calls, enabling binary code to run without recompilation on macOS 10.9
through macOS 10.13.

Early releases of macOS 10.13 have a very buggy adjtime() call. The
macOS driver tests adjtime() to see if the bug has been fixed. If the
bug persists then the timex driver is invoked otherwise the netbsd
driver.
This commit is contained in:
Bryan Christianson 2017-07-13 12:18:02 +12:00 committed by Miroslav Lichvar
parent 778fce4039
commit ccb94ac5fb
5 changed files with 109 additions and 22 deletions

9
configure vendored
View file

@ -428,6 +428,15 @@ case $OPERATINGSYSTEM in
add_def FEAT_PRIVDROP add_def FEAT_PRIVDROP
priv_ops="ADJUSTTIME SETTIME BINDSOCKET" priv_ops="ADJUSTTIME SETTIME BINDSOCKET"
fi fi
major=`echo $VERSION | cut -d. -f1`
# ntp_adjtime is not available in macOS 10.12 (Darwin 16.x.x) and earlier
if [ $major -gt "16" ]; then
add_def HAVE_MACOS_SYS_TIMEX
EXTRA_OBJECTS="$EXTRA_OBJECTS sys_generic.o sys_netbsd.o sys_timex.o"
if [ $feat_droproot = "1" ]; then
priv_ops="$priv_ops ADJUSTTIMEX"
fi
fi
echo "Configuring for macOS (" $SYSTEM "macOS version" $VERSION ")" echo "Configuring for macOS (" $SYSTEM "macOS version" $VERSION ")"
;; ;;
SunOS) SunOS)

View file

@ -587,11 +587,12 @@ can be specified.
To compute the rate of gain or loss of time, *chronyd* has to store a To compute the rate of gain or loss of time, *chronyd* has to store a
measurement history for each of the time sources it uses. measurement history for each of the time sources it uses.
+ +
Certain systems (Linux, FreeBSD, NetBSD, Solaris) have operating system support All supported systems, with the exception of macOS 10.12 and earlier, have
for setting the rate of gain or loss to compensate for known errors. (On Mac OS operating system support for setting the rate of gain or loss to compensate for
X, *chronyd* must simulate such a capability by periodically slewing the system known errors.
clock forwards or backwards by a suitable amount to compensate for the error (On macOS 10.12 and earlier, *chronyd* must simulate such a capability by
built up since the previous slew.) periodically slewing the system clock forwards or backwards by a suitable amount
to compensate for the error built up since the previous slew.)
+ +
For such systems, it is possible to save the measurement history across For such systems, it is possible to save the measurement history across
restarts of *chronyd* (assuming no changes are made to the system clock restarts of *chronyd* (assuming no changes are made to the system clock
@ -690,8 +691,9 @@ distances are in milliseconds.
[[corrtimeratio]]*corrtimeratio* _ratio_:: [[corrtimeratio]]*corrtimeratio* _ratio_::
When *chronyd* is slewing the system clock to correct an offset, the rate at When *chronyd* is slewing the system clock to correct an offset, the rate at
which it is slewing adds to the frequency error of the clock. On Linux, which it is slewing adds to the frequency error of the clock. On all supported
FreeBSD, NetBSD and Solaris this rate can be controlled. systems, with the exception of macOS 12 and earlier, this rate can be
controlled.
+ +
The *corrtimeratio* directive sets the ratio between the duration in which the The *corrtimeratio* directive sets the ratio between the duration in which the
clock is slewed for an average correction according to the source history and clock is slewed for an average correction according to the source history and
@ -774,8 +776,8 @@ that error is corrected. There are four options:
When inserting a leap second, the kernel steps the system clock backwards by When inserting a leap second, the kernel steps the system clock backwards by
one second when the clock gets to 00:00:00 UTC. When deleting a leap second, it one second when the clock gets to 00:00:00 UTC. When deleting a leap second, it
steps forward by one second when the clock gets to 23:59:59 UTC. This is the steps forward by one second when the clock gets to 23:59:59 UTC. This is the
default mode when the system driver supports leap seconds (i.e. on Linux, default mode when the system driver supports leap seconds (i.e. all supported
FreeBSD, NetBSD and Solaris). systems with the exception of macOS 12 and earlier).
*step*::: *step*:::
This is similar to the *system* mode, except the clock is stepped by This is similar to the *system* mode, except the clock is stepped by
*chronyd* instead of the kernel. It can be useful to avoid bugs in the kernel *chronyd* instead of the kernel. It can be useful to avoid bugs in the kernel
@ -918,7 +920,7 @@ This directive specifies the maximum assumed drift (frequency error) of the
system clock. It limits the frequency adjustment that *chronyd* is allowed to system clock. It limits the frequency adjustment that *chronyd* is allowed to
use to correct the measured drift. It is an additional limit to the maximum use to correct the measured drift. It is an additional limit to the maximum
adjustment that can be set by the system driver (100000 ppm on Linux, 500 ppm adjustment that can be set by the system driver (100000 ppm on Linux, 500 ppm
on FreeBSD and NetBSD, 32500 ppm on Solaris). on FreeBSD, NetBSD, and macOS 10.13+, 32500 ppm on Solaris).
+ +
By default, the maximum assumed drift is 500000 ppm, i.e. the adjustment is By default, the maximum assumed drift is 500000 ppm, i.e. the adjustment is
limited by the system driver rather than this directive. limited by the system driver rather than this directive.
@ -953,14 +955,18 @@ The *maxslewrate* directive sets the maximum rate at which *chronyd* is allowed
to slew the time. It limits the slew rate controlled by the correction time to slew the time. It limits the slew rate controlled by the correction time
ratio (which can be set by the <<corrtimeratio,*corrtimeratio*>> directive) and ratio (which can be set by the <<corrtimeratio,*corrtimeratio*>> directive) and
is effective only on systems where *chronyd* is able to control the rate (i.e. is effective only on systems where *chronyd* is able to control the rate (i.e.
Linux, FreeBSD, NetBSD, Solaris). all supported systems with the exception of macOS 12 or earlier).
+ +
For each system there is a maximum frequency offset of the clock that For each system there is a maximum frequency offset of the clock that can be set
can be set by the driver. On Linux it is 100000 ppm, on FreeBSD and NetBSD by the driver. On Linux it is 100000 ppm, on FreeBSD, NetBSD and macOS 10.13+ it
it is 5000 ppm, and on Solaris it is 32500 ppm. Also, due to a kernel is 5000 ppm, and on Solaris it is 32500 ppm. Also, due to a kernel limitation,
limitation, setting *maxslewrate* on FreeBSD and NetBSD to a value between 500 setting *maxslewrate* on FreeBSD, NetBSD, macOS 10.13+ to a value between 500
ppm and 5000 ppm will effectively set it to 500 ppm. ppm and 5000 ppm will effectively set it to 500 ppm.
+ +
In early beta releases of macOS 13 this capability is disabled because of a
system kernel bug. When the kernel bug is fixed, chronyd will detect this and
re-enable the capability (see above limitations) with no recompilation required.
+
By default, the maximum slew rate is set to 83333.333 ppm (one twelfth). By default, the maximum slew rate is set to 83333.333 ppm (one twelfth).
[[tempcomp]] [[tempcomp]]

View file

@ -87,8 +87,8 @@ histories are created by using the <<chronyc.adoc#dump,*dump*>> command in
directive in the configuration file. This option is useful if you want to stop directive in the configuration file. This option is useful if you want to stop
and restart *chronyd* briefly for any reason, e.g. to install a new version. and restart *chronyd* briefly for any reason, e.g. to install a new version.
However, it should be used only on systems where the kernel can maintain clock However, it should be used only on systems where the kernel can maintain clock
compensation whilst not under *chronyd*'s control (i.e. Linux, FreeBSD, NetBSD compensation whilst not under *chronyd*'s control (i.e. Linux, FreeBSD, NetBSD,
and Solaris). Solaris, and macOS 10.13 or later).
*-R*:: *-R*::
When this option is used, the <<chrony.conf.adoc#initstepslew,*initstepslew*>> When this option is used, the <<chrony.conf.adoc#initstepslew,*initstepslew*>>

View file

@ -46,6 +46,15 @@
#include "privops.h" #include "privops.h"
#include "util.h" #include "util.h"
#ifdef HAVE_MACOS_SYS_TIMEX
#include <dlfcn.h>
#include "sys_netbsd.h"
#include "sys_timex.h"
static int have_ntp_adjtime = 0;
static int have_bad_adjtime = 0;
#endif
/* ================================================== */ /* ================================================== */
/* This register contains the number of seconds by which the local /* This register contains the number of seconds by which the local
@ -417,8 +426,8 @@ void SYS_MacOSX_DropRoot(uid_t uid, gid_t gid)
/* ================================================== */ /* ================================================== */
void static void
SYS_MacOSX_Initialise(void) legacy_MacOSX_Initialise(void)
{ {
clock_initialise(); clock_initialise();
@ -434,8 +443,8 @@ SYS_MacOSX_Initialise(void)
/* ================================================== */ /* ================================================== */
void static void
SYS_MacOSX_Finalise(void) legacy_MacOSX_Finalise(void)
{ {
SCH_RemoveTimeout(drift_removal_id); SCH_RemoveTimeout(drift_removal_id);
@ -444,4 +453,67 @@ SYS_MacOSX_Finalise(void)
/* ================================================== */ /* ================================================== */
#ifdef HAVE_MACOS_SYS_TIMEX
/*
Test adjtime() to see if Apple have fixed the signed/unsigned bug
*/
static int
test_adjtime()
{
struct timeval tv1 = {-1, 0};
struct timeval tv2 = {0, 0};
struct timeval tv;
if (PRV_AdjustTime(&tv1, &tv) != 0) {
return 0;
}
if (PRV_AdjustTime(&tv2, &tv) != 0) {
return 0;
}
if (tv.tv_sec < -1 || tv.tv_sec > 1) {
return 0;
}
return 1;
}
#endif
/* ================================================== */
void
SYS_MacOSX_Initialise(void)
{
#ifdef HAVE_MACOS_SYS_TIMEX
have_ntp_adjtime = (dlsym(RTLD_NEXT, "ntp_adjtime") != NULL);
if (have_ntp_adjtime) {
have_bad_adjtime = !test_adjtime();
if (have_bad_adjtime) {
LOG(LOGS_WARN, "adjtime() is buggy - using timex driver");
SYS_Timex_Initialise();
} else {
SYS_NetBSD_Initialise();
}
return;
}
#endif
legacy_MacOSX_Initialise();
}
/* ================================================== */
void
SYS_MacOSX_Finalise(void)
{
#ifdef HAVE_MACOS_SYS_TIMEX
if (have_ntp_adjtime) {
if (have_bad_adjtime) {
SYS_Timex_Finalise();
} else {
SYS_NetBSD_Finalise();
}
return;
}
#endif
legacy_MacOSX_Finalise();
}
#endif #endif

View file

@ -59,7 +59,7 @@
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
#if defined(LINUX) || defined(FREEBSD) || defined(NETBSD) || defined(SOLARIS) #if defined(LINUX) || defined(FREEBSD) || defined(NETBSD) || defined(SOLARIS) || defined(HAVE_MACOS_SYS_TIMEX)
#include <sys/timex.h> #include <sys/timex.h>
#endif #endif