/*
 * nbs_time - set system clock to National Bureau of Standards Time.
 *
 * This program sets the system clock to the time maintained by the
 * National Bureau of Standards (WWV Boulder CO).
 *
 * Modified for Linux by Kayvan Sylvan (kayvan@satyr.Sylvan.COM).
 */

static char rcs_id[] = "$Id: nbs_time.c,v 1.6 1994/01/31 08:43:30 kayvan Exp $";

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <string.h>

#include "config.h"

void main()
{
  int call_nbs();

  /* Start log for this run */
  (void)freopen(LOG, "a", stdout);
  setbuf(stdout, NULL);		/* unbuffered output to log    */

  if(call_nbs() < 0)		/* Call the National Bureau of Standards */
    {
      exit(1);
    }
  set_time();			/* Set the system clock on this machine. */
  disc_nbs();			/* Disconnect the call to NBS            */
  exit(0);
}

int call_nbs()
{
  int	p1[2];		/* pipe from cu process */
  int	p2[2];		/* pipe to cu process   */
  int pid;		/* process id of child  */

  if(pipe(p1) < 0 || pipe(p2) < 0)	/* create the pipes */
    {
      time_stamp((time_t) 0);
      printf("Cannot create pipes.\n");
      return(-1);	/* pipe create failed */
    }
  if((pid = fork()) < 0)
    {
      time_stamp((time_t) 0);
      printf("Cannot fork for child process.\n");
      return(-1);	/* fork failed */
    }
  if (pid != 0)	/* parent process */
    {
      (void)close(p1[1]);		/* close the unused end of pipe 1  */
      (void)close(p2[0]);		/* close the unused end of pipe 2  */
      Nbs_rd = fdopen(p1[0], "r");	/* save the pipe end for NBS read  */
      Nbs_wt = fdopen(p2[1], "w");	/* save the pipe end for NBS write */
      setbuf(Nbs_rd, NULL);		/* set unbuffered mode */
      setbuf(Nbs_wt, NULL);		/* set unbuffered mode */
      return(0);
    }

  /* this is the child process - set up stdin, stdout, stderr and exec cu */

  (void)close(p1[0]);		/* close the unused end of pipe 1  */
  (void)close(p2[1]);		/* close the unused end of pipe 2  */
  (void)dup2(p2[0], 0);		/* set up pipe 2 as stdin  */
  (void)dup2(p1[1], 1);		/* set up pipe 1 as stdout */
  (void)dup2(p1[1], 2);		/* set up pipe 1 as stderr */
  (void)execl(CU_PROG, "cu", CU_ARGS, 0);
  return(-1);
}

void disc_nbs()
{
  (void)fprintf(Nbs_wt, "~.\n");	/* write the cu disconnect command */
  return;
}

/*
 *  The NBS output includes some header information (several lines) followed
 *  followed by a clock tick every second.  The clock tick contains all of
 *  the information needed to set the unix clock.  The additional fields are
 *  ignored.
 *
 *	00000000001111111111222222222233333333334444444444
 *	01234567890123456789012345678901234567890123456789
 *	jdate yr-mo-dd hh:mm:ss ds l cor msadv tz        v
 *
 *	47588 89-03-03 20:33:45 81 0 -.2 045.0 UTC(NIST) *
 */

void set_time()
{
  time_t new_time;	/* the converted time       */
  time_t cur_time;	/* current system time      */
  short got_tick;	/* how many ticks seen?     */
  char buf[80];		/* buffer for NBS tick data */
  time_t delta;		/* time delta               */
  char *dir;		/* is clock slow or fast    */
  int rc;		/* return code from stime() */
  time_t convert_tick(char *);

  got_tick = 0;	/* don't have one yet */
  while(fgets(buf, 80, Nbs_rd) != NULL)
    {
      if (strncmp(&buf[39], "UTC", 3) == 0)
        {
	  if(++got_tick >= 3)
	    {
	      break;	/* take the third valid tick to insure sync */
	    }
        }
    }
  if(got_tick == 0)
    {
      /* print last cu error line */
      time_stamp((time_t) 0);
      printf("%s\n", buf);
      return;
    }

  /* got a tick, so convert it to seconds since 1/1/70 00:00:00 GMT */

  new_time = convert_tick(buf);

  /* now re-sync with the source */

  got_tick = 0;	/* don't have a tick yet */
  while(fgets(buf, 80, Nbs_rd) != NULL)
    {
      if(strncmp(&buf[39], "UTC", 3) == 0)
        {
	  new_time++;		/* add a second for each tick we read */
	  if(++got_tick >= 3)
	    {
	      break;	/* take the third valid tick to insure sync */
	    }
        }
    }

  (void)time(&cur_time);	/* get current time */
  rc = stime(&new_time);	/* set the time */

  delta = new_time - cur_time;	/* get delta */
  if(delta != 0L)
    {
      dir = "slow";
      if(delta < 0L)
	{
	  dir = "fast";
	  delta = -delta;
	}
      time_stamp(cur_time);
      printf("Clock is %ld seconds %s.\n", delta, dir);
      if(rc < 0)
	{
	  time_stamp(cur_time);
	  printf("Clock not set; permission denied.\n");
	}
      else
	{
	  time_stamp(new_time);
	  printf("New time set.\n");
	}
    }
  else
    {
      time_stamp((time_t) 0);
      printf("Clock is correct.\n");
    }
  set_rtc();	/* sync the hardware clock */
  return;
}

time_t convert_tick(char *buf)
{
  time_t ret;
  int year, mon, day, hour, min, sec;

  struct tm the_time;
  sscanf(buf,
	 "%*d %d-%d-%d %d:%d:%d", &year, &mon, &day, &hour, &min, &sec);
  the_time.tm_year = year;
  the_time.tm_mon = mon - 1;
  the_time.tm_mday = day;
  the_time.tm_hour = hour;
  the_time.tm_min = min;
  the_time.tm_sec = sec;
  the_time.tm_isdst = 0;
  ret = mktime(&the_time) - timezone;
  return ret;
}

void set_rtc()
{
  execl(CLOCKPATH, "clock", CLOCKARGS, 0);
}

void time_stamp(time_t tim)
{
  struct tm *tm;	/* pointer to local time structure */

  if(tim == 0L)
    {
      (void)time(&tim);	/* get current time */
    }
  tm = localtime(&tim);	/* convert tim to local time */

  /* generate time stamp */
  (void)printf("%.2d/%.2d/%.2d", tm->tm_mon + 1, tm->tm_mday, tm->tm_year); 
  (void)printf("-%.2d:%.2d:%.2d  ", tm->tm_hour, tm->tm_min, tm->tm_sec);
  return;
}
