/*
 * refclock_shm - clock driver for utc via shared memory 
 * - under construction -
 * To add new modes: Extend or union the shmTime-struct. Do not
 * extend/shrink size, because otherwise existing implementations
 * will specify wrong size of shared memory-segment
 * PB 18.3.97
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#if defined(REFCLOCK) && defined(CLOCK_SHM)

#undef fileno   
#include <ctype.h>
#undef fileno   
#include <sys/time.h>
#undef fileno   

#include "ntpd.h"
#undef fileno   
#include "ntp_io.h"
#undef fileno   
#include "ntp_refclock.h"
#undef fileno   
#include "ntp_unixtime.h"
#undef fileno   
#include "ntp_stdlib.h"

#ifndef SYS_WINNT
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <assert.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#endif

/*
 * This driver supports a reference clock attached thru shared memory
 */ 

/*
 * SHM interface definitions
 */
#define PRECISION       (-1)    /* precision assumed (0.5 s) */
#define REFID           "SHM"   /* reference ID */
#define DESCRIPTION     "SHM/Shared memory interface"

#define NSAMPLES        3       /* stages of median filter */

/*
 * Function prototypes
 */
static  int     shm_start       (int, struct peer *);
static  void    shm_shutdown    (int, struct peer *);
static  void    shm_poll        (int unit, struct peer *);

/*
 * Transfer vector
 */
struct  refclock refclock_shm = {
	shm_start,              /* start up driver */
	shm_shutdown,           /* shut down driver */
	shm_poll,               /* transmit poll message */
	noentry,                /* not used */
	noentry,                /* initialize driver (not used) */
	noentry,                /* not used */
	NOFLAGS                 /* not used */
};
struct shmTime {
	int    mode; /* 0 - if valid set
		      *       use values, 
		      *       clear valid
		      * 1 - if valid set 
		      *       if count before and after read of values is equal,
		      *         use values 
		      *       clear valid
		      */
	int    count;
	time_t clockTimeStampSec;
	int    clockTimeStampUSec;
	time_t receiveTimeStampSec;
	int    receiveTimeStampUSec;
	int    leap;
	int    precision;
	int    nsamples;
	int    valid;
	int    dummy[10]; 
};
struct shmTime *getShmTime (int unit) {
#ifndef SYS_WINNT
	extern char *sys_errlist[ ];
	extern int sys_nerr;
	extern int errno;
	int shmid=0;

	assert (unit<10); /* MAXUNIT is 4, so should never happen */
	shmid=shmget (0x4e545030+unit, sizeof (struct shmTime), 
		      IPC_CREAT|(unit<2?0700:0777));
	if (shmid==-1) { /*error */
		char buf[20];
		char *pe=buf;
		if (errno<sys_nerr)
		    pe=sys_errlist[errno];
		else {
			sprintf (buf,"errno=%d",errno);
		}
		msyslog(LOG_ERR,"SHM shmget (unit %d): %s",unit,pe);
		return 0;
	}
	else { /* no error  */
		struct shmTime *p=(struct shmTime *)shmat (shmid, 0, 0);
		if ((int)(long)p==-1) { /* error */
			char buf[20];
			char *pe=buf;
			if (errno<sys_nerr)
			    pe=sys_errlist[errno];
			else {
				sprintf (buf,"errno=%d",errno);
			}
			msyslog(LOG_ERR,"SHM shmat (unit %d): %s",unit,pe);
			return 0;
		}
		return p;
	}
#else
	char buf[10];
	LPSECURITY_ATTRIBUTES psec=0;
	HANDLE shmid=0;
	SECURITY_DESCRIPTOR sd;
	SECURITY_ATTRIBUTES sa;
	sprintf (buf,"NTP%d",unit);
	if (unit>=2) { /* world access */
		if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
			msyslog(LOG_ERR,"SHM InitializeSecurityDescriptor (unit %d): %m",unit);
			return 0;
		}
		if (!SetSecurityDescriptorDacl(&sd,1,0,0)) {
			msyslog(LOG_ERR,"SHM SetSecurityDescriptorDacl (unit %d): %m",unit);
			return 0;
		}
		sa.nLength=sizeof (SECURITY_ATTRIBUTES);
		sa.lpSecurityDescriptor=&sd;
		sa.bInheritHandle=0;
		psec=&sa;
	}
	shmid=CreateFileMapping ((HANDLE)0xffffffff, psec, PAGE_READWRITE,
				 0, sizeof (struct shmTime),buf);
	if (!shmid) { /*error*/
		char buf[1000];
		FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM,
			       0, GetLastError (), 0, buf, sizeof (buf), 0);
		msyslog(LOG_ERR,"SHM CreateFileMapping (unit %d): %s",unit,buf);
		return 0;
	}
	else {
		struct shmTime *p=(struct shmTime *) MapViewOfFile (shmid, 
								    FILE_MAP_WRITE, 0, 0, sizeof (struct shmTime));
		if (p==0) { /*error*/
			char buf[1000];
			FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM,
				       0, GetLastError (), 0, buf, sizeof (buf), 0);
			msyslog(LOG_ERR,"SHM MapViewOfFile (unit %d): %s",unit,buf);
			return 0;
		}
		return p;
	}
#endif
	return 0;
}
/*
 * shm_start - attach to shared memory
 */
static int
shm_start(
	int unit,
	struct peer *peer
	)
{
	struct refclockproc *pp;
	pp = peer->procptr;
	pp->io.clock_recv = noentry;
	pp->io.srcclock = (caddr_t)peer;
	pp->io.datalen = 0;
	pp->io.fd = -1;
	pp->unitptr = (caddr_t)getShmTime(unit);

	/*
	 * Initialize miscellaneous peer variables
	 */
	memcpy((char *)&pp->refid, REFID, 4);
	if (pp->unitptr!=0) {
		((struct shmTime*)pp->unitptr)->precision=PRECISION;
		peer->precision = ((struct shmTime*)pp->unitptr)->precision;
		((struct shmTime*)pp->unitptr)->valid=0;
		((struct shmTime*)pp->unitptr)->nsamples=NSAMPLES;
		pp->clockdesc = DESCRIPTION;
		return (1);
	}
	else {
		return 0;
	}
}


/*
 * shm_shutdown - shut down the clock
 */
static void
shm_shutdown(
	int unit,
	struct peer *peer
	)
{
	register struct shmTime *up;
	struct refclockproc *pp;

	pp = peer->procptr;
	up = (struct shmTime *)pp->unitptr;
#ifndef SYS_WINNT
	shmdt (up);
#else
	UnmapViewOfFile (up);
#endif
}


/*
 * shm_poll - called by the transmit procedure
 */
static void
shm_poll(
	int unit,
	struct peer *peer
	)
{
	register struct shmTime *up;
	struct refclockproc *pp;

	/*
	 * This is the main routine. It snatches the time from the shm
	 * board and tacks on a local timestamp.
	 */
	pp = peer->procptr;
	up = (struct shmTime*)pp->unitptr;
	if (up==0) { /* try to map again - this may succeed if meanwhile some-
			body has ipcrm'ed the old (unaccessible) shared mem
			segment  */
		pp->unitptr = (caddr_t)getShmTime(unit);
		up = (struct shmTime*)pp->unitptr;
	}
	if (up==0) {
		refclock_report(peer, CEVNT_FAULT);
		return;
	}
	if (up->valid) {
		struct timeval tvr;
		struct timeval tvt;
		struct tm *t;
		int ok=1;
		switch (up->mode) {
		    case 0: {
			    tvr.tv_sec=up->receiveTimeStampSec-172800;
			    tvr.tv_usec=up->receiveTimeStampUSec;
			    tvt.tv_sec=up->clockTimeStampSec;
			    tvt.tv_usec=up->clockTimeStampUSec;
		    }
		    break;
		    case 1: {
			    int cnt=up->count;
			    tvr.tv_sec=up->receiveTimeStampSec-172800;
			    tvr.tv_usec=up->receiveTimeStampUSec;
			    tvt.tv_sec=up->clockTimeStampSec;
			    tvt.tv_usec=up->clockTimeStampUSec;
			    ok=(cnt==up->count);
		    }
		    break;
		    default:
			msyslog (LOG_ERR, "SHM: bad mode found in shared memory: %d",up->mode);
		}
		/*msyslog(LOG_NOTICE,"poll2a tvr.s %d tvr.u %d tvt.s %d tvt.u %d",tvr.tv_sec,tvr.tv_usec,tvt.tv_sec,tvt.tv_usec);*/
		up->valid=0;
		if (ok) {
			TVTOTS(&tvr,&pp->lastrec);
			pp->lasttime = current_time;
			pp->polls++;
			t=gmtime (&tvt.tv_sec);
			pp->day=t->tm_yday;/*+2; */
			pp->hour=t->tm_hour;
			pp->minute=t->tm_min;
			pp->second=t->tm_sec;
			pp->usec=tvt.tv_usec;
			peer->precision=up->precision;
			pp->leap=up->leap;
		} 
		else {
			refclock_report(peer, CEVNT_FAULT);
			msyslog (LOG_NOTICE, "SHM: access clash in shared memory");
			return;
		}
	}
	else {
		refclock_report(peer, CEVNT_TIMEOUT);
		msyslog (LOG_NOTICE, "SHM: no new value found in shared memory");
		return;
	}
	if (!refclock_process(pp)) {
		refclock_report(peer, CEVNT_BADTIME);
		return;
	}
	refclock_receive(peer);
}

#else
int refclock_shm_bs;
#endif /* REFCLOCK */
