/*
  chronyd/chronyc - Programs for keeping computer clocks accurate.

 **********************************************************************
 * Copyright (C) Richard P. Curnow  1997-2001
 * Copyright (C) J. Hannken-Illjes  2001
 * Copyright (C) Miroslav Lichvar  2015
 * 
 * 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 NetBSD and FreeBSD operating system.
  */

#include "config.h"

#include "sysincl.h"

#include "sys_netbsd.h"
#include "sys_timex.h"
#include "logging.h"
#include "privops.h"
#include "util.h"

/* Maximum frequency offset accepted by the kernel (in ppm) */
#define MAX_FREQ 500.0

/* Minimum assumed rate at which the kernel updates the clock frequency */
#define MIN_TICK_RATE 100

/* Interval between kernel updates of the adjtime() offset */
#define ADJTIME_UPDATE_INTERVAL 1.0

/* Maximum adjtime() slew rate (in ppm) */
#define MAX_ADJTIME_SLEWRATE 5000.0

/* Minimum offset adjtime() slews faster than MAX_FREQ */
#define MIN_FASTSLEW_OFFSET 1.0

/* ================================================== */

/* Positive offset means system clock is fast of true time, therefore
   slew backwards */

static void
accrue_offset(double offset, double corr_rate)
{
  struct timeval newadj, oldadj;
  double doldadj;

  UTI_DoubleToTimeval(-offset, &newadj);

  if (PRV_AdjustTime(&newadj, &oldadj) < 0)
    LOG_FATAL("adjtime() failed");

  /* Add the old remaining adjustment if not zero */
  doldadj = UTI_TimevalToDouble(&oldadj);
  if (doldadj != 0.0) {
    UTI_DoubleToTimeval(-offset + doldadj, &newadj);
    if (PRV_AdjustTime(&newadj, NULL) < 0)
      LOG_FATAL("adjtime() failed");
  }
}

/* ================================================== */

static void
get_offset_correction(struct timespec *raw,
                      double *corr, double *err)
{
  struct timeval remadj;
  double adjustment_remaining;
#ifdef MACOSX
  struct timeval tv = {0, 0};

  if (PRV_AdjustTime(&tv, &remadj) < 0)
    LOG_FATAL("adjtime() failed");

  if (PRV_AdjustTime(&remadj, NULL) < 0)
    LOG_FATAL("adjtime() failed");
#else
  if (PRV_AdjustTime(NULL, &remadj) < 0)
    LOG_FATAL("adjtime() failed");
#endif

  adjustment_remaining = UTI_TimevalToDouble(&remadj);

  *corr = adjustment_remaining;
  if (err) {
    if (*corr != 0.0)
      *err = 1.0e-6 * MAX_ADJTIME_SLEWRATE / ADJTIME_UPDATE_INTERVAL;
    else
      *err = 0.0;
  }
}

/* ================================================== */

void
SYS_NetBSD_Initialise(void)
{
  SYS_Timex_InitialiseWithFunctions(MAX_FREQ, 1.0 / MIN_TICK_RATE,
                                    NULL, NULL, NULL,
                                    MIN_FASTSLEW_OFFSET, MAX_ADJTIME_SLEWRATE,
                                    accrue_offset, get_offset_correction);
}

/* ================================================== */

void
SYS_NetBSD_Finalise(void)
{
  SYS_Timex_Finalise();
}

/* ================================================== */

#ifdef FEAT_PRIVDROP
void
SYS_NetBSD_DropRoot(uid_t uid, gid_t gid, SYS_ProcessContext context, int clock_control)
{
#ifdef NETBSD
  int fd;
#endif

  /* On NetBSD the helper is used only for socket binding, but on FreeBSD
     it's used also for setting and adjusting the system clock */
  if (context == SYS_MAIN_PROCESS)
    PRV_StartHelper();

  UTI_DropRoot(uid, gid);

#ifdef NETBSD
  if (!clock_control)
    return;

  /* Check if we have write access to /dev/clockctl */
  fd = open("/dev/clockctl", O_WRONLY);
  if (fd < 0)
    LOG_FATAL("Can't write to /dev/clockctl");
  close(fd);
#endif
}
#endif
