diff options
Diffstat (limited to 'sys_macosx.c')
-rw-r--r-- | sys_macosx.c | 519 |
1 files changed, 519 insertions, 0 deletions
diff --git a/sys_macosx.c b/sys_macosx.c new file mode 100644 index 0000000..00ce302 --- /dev/null +++ b/sys_macosx.c @@ -0,0 +1,519 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Richard P. Curnow 1997-2001 + * Copyright (C) J. Hannken-Illjes 2001 + * Copyright (C) Bryan Christianson 2015, 2017 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + + ======================================================================= + + Driver file for the macOS operating system. + + */ + +#include "config.h" + +#ifdef MACOSX + +#include "sysincl.h" + +#include <mach/mach.h> +#include <mach/mach_time.h> +#include <pthread.h> + +#include "sys_macosx.h" +#include "conf.h" +#include "local.h" +#include "localp.h" +#include "logging.h" +#include "sched.h" +#include "privops.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 + clock was estimated to be fast of reference time at the epoch when + LCL_ReadRawTime() returned T0 */ + +static double offset_register; + +/* This register contains the epoch to which the offset is referenced */ + +static struct timespec T0; + +/* This register contains the current estimate of the system + frequency, in absolute (NOT ppm) */ + +static double current_freq; + +/* This register contains the number of seconds of adjustment that + were passed to adjtime last time it was called. */ + +static double adjustment_requested; + +/* Interval in seconds between adjustments to cancel systematic drift */ + +#define DRIFT_REMOVAL_INTERVAL (4.0) +#define DRIFT_REMOVAL_INTERVAL_MIN (0.5) + +/* If current_drift_removal_interval / drift_removal_interval exceeds this + ratio, then restart the drift removal timer */ + +#define DRIFT_REMOVAL_RESTART_RATIO (8.0) + +static double drift_removal_interval; +static double current_drift_removal_interval; +static struct timespec Tdrift; + +/* weighting applied to error in calculating drift_removal_interval */ +#define ERROR_WEIGHT (0.5) + +/* minimum resolution of current_frequency */ +#define FREQUENCY_RES (1.0e-9) + +#define NANOS_PER_MSEC (1000000ULL) + +/* RTC synchronisation - once an hour */ + +static struct timespec last_rtc_sync; +#define RTC_SYNC_INTERVAL (60 * 60.0) + +/* ================================================== */ + +static void +clock_initialise(void) +{ + struct timeval newadj, oldadj; + + offset_register = 0.0; + adjustment_requested = 0.0; + current_freq = 0.0; + drift_removal_interval = DRIFT_REMOVAL_INTERVAL; + current_drift_removal_interval = DRIFT_REMOVAL_INTERVAL; + + LCL_ReadRawTime(&T0); + Tdrift = T0; + last_rtc_sync = T0; + + newadj.tv_sec = 0; + newadj.tv_usec = 0; + + if (PRV_AdjustTime(&newadj, &oldadj) < 0) { + LOG_FATAL("adjtime() failed"); + } +} + +/* ================================================== */ + +static void +clock_finalise(void) +{ + /* Nothing to do yet */ +} + +/* ================================================== */ + +static void +start_adjust(void) +{ + struct timeval newadj, oldadj; + struct timespec T1; + double elapsed, accrued_error, predicted_error, drift_removal_elapsed; + double adjust_required; + double rounding_error; + double old_adjust_remaining; + + /* Determine the amount of error built up since the last adjustment */ + LCL_ReadRawTime(&T1); + + elapsed = UTI_DiffTimespecsToDouble(&T1, &T0); + accrued_error = elapsed * current_freq; + + drift_removal_elapsed = UTI_DiffTimespecsToDouble(&T1, &Tdrift); + + /* To allow for the clock being stepped either forward or backwards, clamp + the elapsed time to bounds [ 0.0, current_drift_removal_interval ] */ + drift_removal_elapsed = MIN(MAX(0.0, drift_removal_elapsed), current_drift_removal_interval); + + predicted_error = (current_drift_removal_interval - drift_removal_elapsed) / 2.0 * current_freq; + + DEBUG_LOG("drift_removal_elapsed: %.3f current_drift_removal_interval: %.3f predicted_error: %.3f", + 1.0e6 * drift_removal_elapsed, 1.0e6 * current_drift_removal_interval, + 1.0e6 * predicted_error); + + adjust_required = - (accrued_error + offset_register + predicted_error); + + UTI_DoubleToTimeval(adjust_required, &newadj); + adjustment_requested = UTI_TimevalToDouble(&newadj); + rounding_error = adjust_required - adjustment_requested; + + if (PRV_AdjustTime(&newadj, &oldadj) < 0) { + LOG_FATAL("adjtime() failed"); + } + + old_adjust_remaining = UTI_TimevalToDouble(&oldadj); + + offset_register = rounding_error - old_adjust_remaining - predicted_error; + + T0 = T1; +} + +/* ================================================== */ + +static void +stop_adjust(void) +{ + struct timespec T1; + struct timeval zeroadj, remadj; + double adjustment_remaining, adjustment_achieved; + double elapsed, elapsed_plus_adjust; + + zeroadj.tv_sec = 0; + zeroadj.tv_usec = 0; + + if (PRV_AdjustTime(&zeroadj, &remadj) < 0) { + LOG_FATAL("adjtime() failed"); + } + + LCL_ReadRawTime(&T1); + + elapsed = UTI_DiffTimespecsToDouble(&T1, &T0); + adjustment_remaining = UTI_TimevalToDouble(&remadj); + + adjustment_achieved = adjustment_requested - adjustment_remaining; + elapsed_plus_adjust = elapsed - adjustment_achieved; + + offset_register += current_freq * elapsed_plus_adjust - adjustment_remaining; + + adjustment_requested = 0.0; + T0 = T1; +} + +/* ================================================== */ + +/* Positive offset means system clock is fast of true time, therefore + slew backwards */ + +static void +accrue_offset(double offset, double corr_rate) +{ + stop_adjust(); + offset_register += offset; + start_adjust(); +} + +/* ================================================== */ + +/* Positive offset means system clock is fast of true time, therefore + step backwards */ + +static int +apply_step_offset(double offset) +{ + struct timespec old_time, new_time, T1; + struct timeval new_time_tv; + + stop_adjust(); + + LCL_ReadRawTime(&old_time); + + UTI_AddDoubleToTimespec(&old_time, -offset, &new_time); + UTI_TimespecToTimeval(&new_time, &new_time_tv); + + if (PRV_SetTime(&new_time_tv, NULL) < 0) { + DEBUG_LOG("settimeofday() failed"); + return 0; + } + + UTI_AddDoubleToTimespec(&T0, -offset, &T1); + T0 = T1; + + start_adjust(); + + return 1; +} + +/* ================================================== */ + +static double +set_frequency(double new_freq_ppm) +{ + stop_adjust(); + current_freq = new_freq_ppm * 1.0e-6; + start_adjust(); + + return current_freq * 1.0e6; +} + +/* ================================================== */ + +static double +read_frequency(void) +{ + return current_freq * 1.0e6; +} + +/* ================================================== */ + +static void +get_offset_correction(struct timespec *raw, + double *corr, double *err) +{ + stop_adjust(); + *corr = -offset_register; + start_adjust(); + if (err) + *err = 0.0; +} + +/* ================================================== */ + +/* Cancel systematic drift */ + +static SCH_TimeoutID drift_removal_id; + +/* ================================================== */ +/* This is the timer callback routine which is called periodically to + invoke a time adjustment to take out the machine's drift. + Otherwise, times reported through this software (e.g. by running + ntpdate from another machine) show the machine being correct (since + they correct for drift build-up), but any program on this machine + that reads the system time will be given an erroneous value, the + degree of error depending on how long it is since + get_offset_correction was last called. */ + +static void +drift_removal_timeout(SCH_ArbitraryArgument not_used) +{ + + stop_adjust(); + + LCL_ReadRawTime(&Tdrift); + + current_drift_removal_interval = drift_removal_interval; + + start_adjust(); + + drift_removal_id = SCH_AddTimeoutByDelay(drift_removal_interval, drift_removal_timeout, NULL); +} + +/* ================================================== */ + +/* use est_error to calculate the drift_removal_interval and + update the RTC */ + +static void +set_sync_status(int synchronised, double est_error, double max_error) +{ + double interval; + + if (!synchronised) { + drift_removal_interval = MAX(drift_removal_interval, DRIFT_REMOVAL_INTERVAL); + } else { + if (CNF_GetRtcSync()) { + struct timespec now; + double rtc_sync_elapsed; + + SCH_GetLastEventTime(NULL, NULL, &now); + rtc_sync_elapsed = UTI_DiffTimespecsToDouble(&now, &last_rtc_sync); + if (fabs(rtc_sync_elapsed) >= RTC_SYNC_INTERVAL) { + /* update the RTC by applying a step of 0.0 secs */ + apply_step_offset(0.0); + last_rtc_sync = now; + DEBUG_LOG("rtc synchronised"); + } + } + + interval = ERROR_WEIGHT * est_error / (fabs(current_freq) + FREQUENCY_RES); + drift_removal_interval = MAX(interval, DRIFT_REMOVAL_INTERVAL_MIN); + + DEBUG_LOG("est_error: %.3f current_freq: %.3f est drift_removal_interval: %.3f act drift_removal_interval: %.3f", + est_error * 1.0e6, current_freq * 1.0e6, interval, drift_removal_interval); + } + + if (current_drift_removal_interval / drift_removal_interval > DRIFT_REMOVAL_RESTART_RATIO) { + /* recover from a large est_error by resetting the timer */ + SCH_ArbitraryArgument unused; + SCH_RemoveTimeout(drift_removal_id); + unused = NULL; + drift_removal_timeout(unused); + } +} + +/* ================================================== */ +/* + Give chronyd real time priority so that time critical calculations + are not pre-empted by the kernel. +*/ + +static int +set_realtime(void) +{ + /* https://developer.apple.com/library/ios/technotes/tn2169/_index.html */ + + mach_timebase_info_data_t timebase_info; + double clock2abs; + thread_time_constraint_policy_data_t policy; + int kr; + + mach_timebase_info(&timebase_info); + clock2abs = ((double)timebase_info.denom / (double)timebase_info.numer) * NANOS_PER_MSEC; + + policy.period = 0; + policy.computation = (uint32_t)(5 * clock2abs); /* 5 ms of work */ + policy.constraint = (uint32_t)(10 * clock2abs); + policy.preemptible = 0; + + kr = thread_policy_set( + pthread_mach_thread_np(pthread_self()), + THREAD_TIME_CONSTRAINT_POLICY, + (thread_policy_t)&policy, + THREAD_TIME_CONSTRAINT_POLICY_COUNT); + + if (kr != KERN_SUCCESS) { + LOG(LOGS_WARN, "Cannot set real-time priority: %d", kr); + return -1; + } + return 0; +} + +/* ================================================== */ + +void +SYS_MacOSX_SetScheduler(int SchedPriority) +{ + if (SchedPriority) { + set_realtime(); + } +} + +/* ================================================== */ + +#ifdef FEAT_PRIVDROP +void SYS_MacOSX_DropRoot(uid_t uid, gid_t gid) +{ + PRV_StartHelper(); + + UTI_DropRoot(uid, gid); +} +#endif + +/* ================================================== */ + +static void +legacy_MacOSX_Initialise(void) +{ + clock_initialise(); + + lcl_RegisterSystemDrivers(read_frequency, set_frequency, + accrue_offset, apply_step_offset, + get_offset_correction, + NULL /* set_leap */, + set_sync_status); + + + drift_removal_id = SCH_AddTimeoutByDelay(drift_removal_interval, drift_removal_timeout, NULL); +} + +/* ================================================== */ + +static void +legacy_MacOSX_Finalise(void) +{ + SCH_RemoveTimeout(drift_removal_id); + + clock_finalise(); +} + +/* ================================================== */ + +#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 |