summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--SConstruct3
-rw-r--r--gps.h3
-rw-r--r--ntpshm.h15
-rw-r--r--ntpshmmon.c8
-rw-r--r--ntpshmread.c4
-rw-r--r--ntpshmwrite.c450
-rw-r--r--timehint.c440
7 files changed, 480 insertions, 443 deletions
diff --git a/SConstruct b/SConstruct
index 3fe09c80..6ec1727d 100644
--- a/SConstruct
+++ b/SConstruct
@@ -838,6 +838,7 @@ libgps_sources = [
"libgps_sock.c",
"netlib.c",
"ntpshmread.c",
+ "ntpshmwrite.c",
"rtcm2_json.c",
"rtcm3_json.c",
"shared_json.c",
@@ -1023,7 +1024,7 @@ gpsdlibs = ["-lgpsd"] + usblibs + bluezlibs + gpslibs
# Source groups
-gpsd_sources = ['gpsd.c','ntpshmwrite.c','shmexport.c','dbusexport.c']
+gpsd_sources = ['gpsd.c','timehint.c', 'shmexport.c','dbusexport.c']
if env['systemd']:
gpsd_sources.append("sd_socket.c")
diff --git a/gps.h b/gps.h
index 0b480522..84cf9e5d 100644
--- a/gps.h
+++ b/gps.h
@@ -1883,10 +1883,13 @@ struct policy_t {
char remote[GPS_PATH_MAX]; /* ...if this was passthrough */
};
+#ifndef HAVE_TIMEDELTA
struct timedelta_t {
struct timespec real;
struct timespec clock;
};
+#define HAVE_TIMEDELTA
+#endif /* HAVE_TIMEDELTA */
/* difference between timespecs in nanoseconds */
/* int is too small, avoid floats */
diff --git a/ntpshm.h b/ntpshm.h
index 7c8624ad..c634e947 100644
--- a/ntpshm.h
+++ b/ntpshm.h
@@ -66,10 +66,21 @@ struct shm_stat_t {
int leap;
};
+#ifndef HAVE_TIMEDELTA
+
+struct timedelta_t {
+ struct timespec real;
+ struct timespec clock;
+};
+
+#define HAVE_TIMEDELTA
+#endif /* HAVE_TIMEDELTA */
+
struct shmTime /*@null@*/ *shm_get(int, bool, bool);
-extern char *shm_name(const int);
-enum segstat_t shm_query(/*@null@*/struct shmTime *,
+extern char *ntp_name(const int);
+enum segstat_t ntp_read(/*@null@*/struct shmTime *,
/*@out@*/struct shm_stat_t *,
const bool);
+void ntp_write(volatile struct shmTime *, struct timedelta_t *, int, int);
/* end */
diff --git a/ntpshmmon.c b/ntpshmmon.c
index f272ebd2..38447960 100644
--- a/ntpshmmon.c
+++ b/ntpshmmon.c
@@ -84,7 +84,7 @@ int main(int argc, char **argv)
struct shm_stat_t shm_stat;
for (i = 0; i < NTPSEGMENTS; i++) {
- enum segstat_t status = shm_query(segments[i], &shm_stat, false);
+ enum segstat_t status = ntp_read(segments[i], &shm_stat, false);
if (verbose)
fprintf(stderr, "unit %d status %d\n", i, status);
switch(status)
@@ -94,7 +94,7 @@ int main(int argc, char **argv)
/*@-type@*//* splint is confused about struct timespec */
if (shm_stat.tvc.tv_sec != tick[i].tv_sec || shm_stat.tvc.tv_nsec != tick[i].tv_nsec) {
printf("sample %s %ld.%09ld %ld.%09ld %ld.%09ld %d %3d\n",
- shm_name(i),
+ ntp_name(i),
shm_stat.tvc.tv_sec, shm_stat.tvc.tv_nsec,
shm_stat.tvr.tv_sec, shm_stat.tvr.tv_nsec,
shm_stat.tvt.tv_sec, shm_stat.tvt.tv_nsec,
@@ -113,7 +113,7 @@ int main(int argc, char **argv)
case BAD_MODE:
/*@-mustfreefresh@*/
fprintf(stderr, "ntpshmmon: unknown mode %d on segment %s\n",
- shm_stat.status, shm_name(i));
+ shm_stat.status, ntp_name(i));
/*@+mustfreefresh@*/
break;
case CLASH:
@@ -122,7 +122,7 @@ int main(int argc, char **argv)
default:
/*@-mustfreefresh@*/
fprintf(stderr, "ntpshmmon: unknown status %d on segment %s\n",
- status, shm_name(i));
+ status, ntp_name(i));
/*@+mustfreefresh@*/
break;
}
diff --git a/ntpshmread.c b/ntpshmread.c
index d230f663..1282f779 100644
--- a/ntpshmread.c
+++ b/ntpshmread.c
@@ -47,7 +47,7 @@ struct shmTime /*@null@*/ *shm_get(const int unit, const bool create, const bool
}
/*@-statictrans@*/
-char *shm_name(const int unit)
+char *ntp_name(const int unit)
/* return the name of a specified segment */
{
static char name[5] = "NTP\0";
@@ -58,7 +58,7 @@ char *shm_name(const int unit)
}
/*@+statictrans@*/
-enum segstat_t shm_query(/*@null@*/struct shmTime *shm_in, /*@out@*/struct shm_stat_t *shm_stat, const bool consume)
+enum segstat_t ntp_read(/*@null@*/struct shmTime *shm_in, /*@out@*/struct shm_stat_t *shm_stat, const bool consume)
/* try to grab a sample from the specified SHM segment */
{
volatile struct shmTime shmcopy, *shm = shm_in;
diff --git a/ntpshmwrite.c b/ntpshmwrite.c
index f03b152a..f001d9ea 100644
--- a/ntpshmwrite.c
+++ b/ntpshmwrite.c
@@ -1,11 +1,5 @@
/*
- * ntpshmwrite.c - put time information in SHM segment for xntpd, or to chrony
- *
- * struct shmTime and getShmTime from file in the xntp distribution:
- * sht.c - Testprogram for shared memory refclock
- *
- * Note that for easy debugging all logging from this file is prefixed
- * with PPS or NTP.
+ * ntpshmwrite.c - put time information in SHM segment for ntpd
*
* This file is Copyright (c) 2010 by the GPSD project BSD terms apply:
* see the file COPYING in the distribution root for details.
@@ -24,235 +18,13 @@
#include <unistd.h>
#endif /* S_SPLINT_S */
-#include "gpsd.h"
-
-#ifdef NTPSHM_ENABLE
#include "ntpshm.h"
+#include "compiler.h"
-/* Note: you can start gpsd as non-root, and have it work with ntpd.
- * However, it will then only use the ntpshm segments 2 3, and higher.
- *
- * Ntpd always runs as root (to be able to control the system clock).
- * After that it often (depending on its host configuration) drops to run as
- * user ntpd and group ntpd.
- *
- * As of February 2015 its rules for the creation of ntpshm segments are:
- *
- * Segments 0 and 1: permissions 0600, i.e. other programs can only
- * read and write as root.
- *
- * Segments 2, 3, and higher:
- * permissions 0666, i.e. other programs can read
- * and write as any user. I.e.: if ntpd has been
- * configured to use these segments, any
- * unprivileged user is allowed to provide data
- * for synchronisation.
- *
- * By default ntpd creates 0 segments (though the documentation is
- * written in such a way as to suggest it creates 4). It can be
- * configured to create up to 217. gpsd creates two segments for each
- * device it can drive; by default this is 8 segments for 4
- * devices,but can be higher if it was compiled with a larger value of
- * MAX_DEVICES.
- *
- * Started as root, gpsd does as ntpd when attaching (creating) the
- * segments. In contrast to ntpd, which only attaches (creates)
- * configured segments, gpsd creates all segments. Thus a gpsd will
- * by default create eight segments 0-7 that an ntpd with default
- * configuration does not watch.
- *
- * Started as non-root, gpsd will only attach (create) segments 2 and
- * above, with permissions 0666. As the permissions are for any user,
- * the creator does not matter.
- *
- * For each GPS module gpsd controls, it will use the attached ntpshm
- * segments in pairs (for coarse clock and pps source, respectively)
- * starting from the first found segments. I.e. started as root, one
- * GPS will deliver data on all segments including 0 and 1; started as
- * non-root, gpsd will be deliver data only on segments 2 and higher.
- *
- * Segments are allocated to activated devices on a first-come-first-served
- * basis. A device's segment is marked unused when the device is closed and
- * may be re-used by devices connected later.
- *
- * To debug, try looking at the live segments this way:
- *
- * ipcs -m
- *
- * results should look like this:
- * ------ Shared Memory Segments --------
- * key shmid owner perms bytes nattch status
- * 0x4e545030 0 root 700 96 2
- * 0x4e545031 32769 root 700 96 2
- * 0x4e545032 163842 root 666 96 1
- * 0x4e545033 196611 root 666 96 1
- * 0x4e545034 253555 root 666 96 1
- * 0x4e545035 367311 root 666 96 1
- *
- * For a bit more data try this:
- * cat /proc/sysvipc/shm
- *
- * If gpsd can not open the segments be sure you are not running SELinux
- * or apparmor.
- *
- * if you see the shared segments (keys 1314148400 -- 1314148405), and
- * no gpsd or ntpd is running, you can remove them like this:
- *
- * ipcrm -M 0x4e545030
- * ipcrm -M 0x4e545031
- * ipcrm -M 0x4e545032
- * ipcrm -M 0x4e545033
- * ipcrm -M 0x4e545034
- * ipcrm -M 0x4e545035
- *
- * Removing these segments is usually not necessary, as the operating system
- * garbage-collects them when they have no attached processes.
- */
-
-#define PPS_MIN_FIXES 3 /* # fixes to wait for before shipping PPS */
-
-static /*@null@*/ volatile struct shmTime *getShmTime(struct gps_context_t *context, int unit)
-{
- int shmid;
- unsigned int perms;
- volatile struct shmTime *p;
- // set the SHM perms the way ntpd does
- if (unit < 2) {
- // we are root, be careful
- perms = 0600;
- } else {
- // we are not root, try to work anyway
- perms = 0666;
- }
-
- /*
- * Note: this call requires root under BSD, and possibly on
- * well-secured Linux systems. This is why ntpshmwrite.context_init() has to be
- * called before privilege-dropping.
- */
- shmid = shmget((key_t) (NTPD_BASE + unit),
- sizeof(struct shmTime), (int)(IPC_CREAT | perms));
- if (shmid == -1) {
- gpsd_report(&context->errout, LOG_ERROR,
- "NTPD shmget(%ld, %zd, %o) fail: %s\n",
- (long int)(NTPD_BASE + unit), sizeof(struct shmTime),
- (int)perms, strerror(errno));
- return NULL;
- }
- p = (struct shmTime *)shmat(shmid, 0, 0);
- /*@ -mustfreefresh */
- if ((int)(long)p == -1) {
- gpsd_report(&context->errout, LOG_ERROR,
- "NTPD shmat failed: %s\n",
- strerror(errno));
- return NULL;
- }
- gpsd_report(&context->errout, LOG_PROG,
- "NTPD shmat(%d,0,0) succeeded, segment %d\n",
- shmid, unit);
- return p;
- /*@ +mustfreefresh */
-}
-
-void ntpshm_context_init(struct gps_context_t *context)
-/* Attach all NTP SHM segments. Called once at startup, while still root. */
-{
- int i;
-
- for (i = 0; i < NTPSHMSEGS; i++) {
- // Only grab the first two when running as root.
- if (2 <= i || 0 == getuid()) {
- context->shmTime[i] = getShmTime(context, i);
- }
- }
- memset(context->shmTimeInuse, 0, sizeof(context->shmTimeInuse));
-}
-
-/*@-unqualifiedtrans@*/
-static /*@null@*/ volatile struct shmTime *ntpshm_alloc(struct gps_context_t *context)
-/* allocate NTP SHM segment. return its segment number, or -1 */
-{
- int i;
-
- for (i = 0; i < NTPSHMSEGS; i++)
- if (context->shmTime[i] != NULL && !context->shmTimeInuse[i]) {
- context->shmTimeInuse[i] = true;
-
- /*
- * In case this segment gets sent to ntpd before an
- * ephemeris is available, the LEAP_NOTINSYNC value will
- * tell ntpd that this source is in a "clock alarm" state
- * and should be ignored. The goal is to prevent ntpd
- * from declaring the GPS a falseticker before it gets
- * all its marbles together.
- */
- memset((void *)context->shmTime[i], 0, sizeof(struct shmTime));
- context->shmTime[i]->mode = 1;
- context->shmTime[i]->leap = LEAP_NOTINSYNC;
- context->shmTime[i]->precision = -1; /* initially 0.5 sec */
- context->shmTime[i]->nsamples = 3; /* stages of median filter */
-
- return context->shmTime[i];
- }
-
- return NULL;
-}
-/*@+unqualifiedtrans@*/
-
-static bool ntpshm_free(struct gps_context_t * context, volatile struct shmTime *s)
-/* free NTP SHM segment */
-{
- int i;
-
- for (i = 0; i < NTPSHMSEGS; i++)
- if (s == context->shmTime[i]) {
- context->shmTimeInuse[i] = false;
- return true;
- }
-
- return false;
-}
-
-void ntpshm_session_init(struct gps_device_t *session)
-{
- /*@-mustfreeonly@*/
-#ifdef NTPSHM_ENABLE
- /* mark NTPD shared memory segments as unused */
- session->shm_clock = NULL;
-#endif /* NTPSHM_ENABLE */
-#ifdef PPS_ENABLE
- session->shm_pps = NULL;
-#endif /* PPS_ENABLE */
- /*@+mustfreeonly@*/
-}
-
-int ntpshm_put(struct gps_device_t *session, volatile struct shmTime *shmseg, struct timedelta_t *td)
+void ntp_write(volatile struct shmTime *shmseg,
+ struct timedelta_t *td, int precision, int leap_notify)
/* put a received fix time into shared memory for NTP */
{
- char real_str[TIMESPEC_LEN];
- char clock_str[TIMESPEC_LEN];
- /*
- * shmTime is volatile to try to prevent C compiler from reordering
- * writes, or optimizing some 'dead code'. but CPU cache may still
- * write out of order if memory_barrier() is a no-op (our implementation
- * isn't portable).
- */
- volatile struct shmTime *shmTime = shmseg;
- /* Any NMEA will be about -1 or -2. Garmin GPS-18/USB is around -6 or -7. */
- int precision = -1; /* default precision */
-
- if (shmTime == NULL) {
- gpsd_report(&session->context->errout, LOG_RAW, "NTPD missing shm\n");
- return 0;
- }
-
-#ifdef PPS_ENABLE
- /* ntpd sets -20 for PPS refclocks, thus -20 precision */
- /* TODO: if PPS over USB 1.1, then precision = -10 */
- if (shmTime == session->shm_pps)
- precision = -20;
-#endif /* PPS_ENABLE */
-
/* we use the shmTime mode 1 protocol
*
* ntpd does this:
@@ -268,214 +40,24 @@ int ntpshm_put(struct gps_device_t *session, volatile struct shmTime *shmseg, st
*
*/
- shmTime->valid = 0;
- shmTime->count++;
+ shmseg->valid = 0;
+ shmseg->count++;
/* We need a memory barrier here to prevent write reordering by
* the compiler or CPU cache */
memory_barrier();
/*@-type@*/ /* splint is confused about struct timespec */
- shmTime->clockTimeStampSec = (time_t)td->real.tv_sec;
- shmTime->clockTimeStampUSec = (int)(td->real.tv_nsec/1000);
- shmTime->clockTimeStampNSec = (unsigned)td->real.tv_nsec;
- shmTime->receiveTimeStampSec = (time_t)td->clock.tv_sec;
- shmTime->receiveTimeStampUSec = (int)(td->clock.tv_nsec/1000);
- shmTime->receiveTimeStampNSec = (unsigned)td->clock.tv_nsec;
+ shmseg->clockTimeStampSec = (time_t)td->real.tv_sec;
+ shmseg->clockTimeStampUSec = (int)(td->real.tv_nsec/1000);
+ shmseg->clockTimeStampNSec = (unsigned)td->real.tv_nsec;
+ shmseg->receiveTimeStampSec = (time_t)td->clock.tv_sec;
+ shmseg->receiveTimeStampUSec = (int)(td->clock.tv_nsec/1000);
+ shmseg->receiveTimeStampNSec = (unsigned)td->clock.tv_nsec;
/*@+type@*/
- shmTime->leap = session->context->leap_notify;
- shmTime->precision = precision;
+ shmseg->leap = leap_notify;
+ shmseg->precision = precision;
memory_barrier();
- shmTime->count++;
- shmTime->valid = 1;
-
- /*@-type@*/ /* splint is confused about struct timespec */
- timespec_str( &td->real, real_str, sizeof(real_str) );
- timespec_str( &td->clock, clock_str, sizeof(clock_str) );
- gpsd_report(&session->context->errout, LOG_RAW,
- "NTP ntpshm_put(%s %s) %s @ %s\n",
- session->gpsdata.dev.path,
- (precision == -20) ? "pps" : "clock",
- real_str, clock_str);
- /*@+type@*/
-
- return 1;
-}
-
-#ifdef PPS_ENABLE
-#define SOCK_MAGIC 0x534f434b
-struct sock_sample {
- struct timeval tv;
- double offset;
- int pulse;
- int leap;
- // cppcheck-suppress unusedStructMember
- int _pad;
- int magic; /* must be SOCK_MAGIC */
-};
-
-/*@-mustfreefresh@*/
-static void init_hook(struct gps_device_t *session)
-/* for chrony SOCK interface, which allows nSec timekeeping */
-{
- /* open the chrony socket */
- char chrony_path[GPS_PATH_MAX];
-
- session->chronyfd = -1;
- if ( 0 == getuid() ) {
- /* this case will fire on command-line devices;
- * they're opened before priv-dropping. Matters because
- * only root can use /var/run.
- */
- (void)snprintf(chrony_path, sizeof (chrony_path),
- "/var/run/chrony.%s.sock", basename(session->gpsdata.dev.path));
- } else {
- (void)snprintf(chrony_path, sizeof (chrony_path),
- "/tmp/chrony.%s.sock", basename(session->gpsdata.dev.path));
- }
-
- if (access(chrony_path, F_OK) != 0) {
- gpsd_report(&session->context->errout, LOG_PROG,
- "PPS chrony socket %s doesn't exist\n", chrony_path);
- } else {
- session->chronyfd = netlib_localsocket(chrony_path, SOCK_DGRAM);
- if (session->chronyfd < 0)
- gpsd_report(&session->context->errout, LOG_PROG,
- "PPS connect chrony socket failed: %s, error: %d, errno: %d/%s\n",
- chrony_path, session->chronyfd, errno, strerror(errno));
- else
- gpsd_report(&session->context->errout, LOG_RAW,
- "PPS using chrony socket: %s\n", chrony_path);
- }
-}
-/*@+mustfreefresh@*/
-
-
-/* td is the real time and clock time of the edge */
-/* offset is actual_ts - clock_ts */
-static void chrony_send(struct gps_device_t *session, struct timedelta_t *td)
-{
- char real_str[TIMESPEC_LEN];
- char clock_str[TIMESPEC_LEN];
- struct timespec offset;
- struct sock_sample sample;
-
- /* chrony expects tv-sec since Jan 1970 */
- sample.pulse = 0;
- sample.leap = session->context->leap_notify;
- sample.magic = SOCK_MAGIC;
- /*@-compdef@*/
- /*@-type@*//* splint is confused about struct timespec */
- /* chronyd wants a timeval, not a timspec, not to worry, it is
- * just the top of the second */
- TSTOTV(&sample.tv, &td->clock);
- /* calculate the offset as a timespec to not lose precision */
- TS_SUB( &offset, &td->real, &td->clock);
- /* if tv_sec greater than 2 then tv_nsec loses precision, but
- * not a big deal as slewing will bbe required */
- sample.offset = TSTONS( &offset );
- /*@+compdef@*/
- sample._pad = 0;
- /*@+type@*/
-
- /*@-type@*/ /* splint is confused about struct timespec */
- timespec_str( &td->real, real_str, sizeof(real_str) );
- timespec_str( &td->clock, clock_str, sizeof(clock_str) );
- gpsd_report(&session->context->errout, LOG_RAW,
- "PPS chrony_send %s @ %s Offset: %0.9f\n",
- real_str, clock_str, sample.offset);
- /*@+type@*/
- (void)send(session->chronyfd, &sample, sizeof (sample), 0);
-}
-
-static void wrap_hook(struct gps_device_t *session)
-{
- if (session->chronyfd != -1)
- (void)close(session->chronyfd);
-}
-
-static /*@observer@*/ char *report_hook(struct gps_device_t *session,
- struct timedelta_t *td)
-/* ship the time of a PPS event to ntpd and/or chrony */
-{
- char *log1;
-
- if (!session->ship_to_ntpd)
- return "skipped ship_to_ntp=0";
-
- /*
- * Only listen to PPS after several consecutive fixes,
- * otherwise time may be inaccurate. (We know this is
- * required on all Garmin and u-blox; safest to do it
- * for all cases as we have no other general way to know
- * if PPS is good.
- *
- * Not sure yet how to handle u-blox UBX_MODE_TMONLY
- */
- if (session->fixcnt <= PPS_MIN_FIXES)
- return "no fix";
-
- log1 = "accepted";
- if ( 0 <= session->chronyfd ) {
- log1 = "accepted chrony sock";
- chrony_send(session, td);
- }
- if (session->shm_pps != NULL)
- (void)ntpshm_put(session, session->shm_pps, td);
-
- return log1;
-}
-#endif /* PPS_ENABLE */
-
-/*@-mustfreeonly@*/
-void ntpshm_link_deactivate(struct gps_device_t *session)
-/* release ntpshm storage for a session */
-{
- if (session->shm_clock != NULL) {
- (void)ntpshm_free(session->context, session->shm_clock);
- session->shm_clock = NULL;
- }
-#if defined(PPS_ENABLE)
- if (session->shm_pps != NULL) {
- pps_thread_deactivate(session);
- (void)ntpshm_free(session->context, session->shm_pps);
- session->shm_pps = NULL;
- }
-#endif /* PPS_ENABLE */
-}
-/*@+mustfreeonly@*/
-
-/*@-mustfreeonly@*/
-void ntpshm_link_activate(struct gps_device_t *session)
-/* set up ntpshm storage for a session */
-{
- /* don't talk to NTP when we're running inside the test harness */
- if (session->sourcetype == source_pty)
- return;
-
- /* allocate a shared-memory segment for "NMEA" time data */
- session->shm_clock = ntpshm_alloc(session->context);
-
- if (session->shm_clock == NULL) {
- gpsd_report(&session->context->errout, LOG_INF,
- "NTPD ntpshm_alloc() failed\n");
-#if defined(PPS_ENABLE)
- } else if (session->sourcetype == source_usb || session->sourcetype == source_rs232) {
- /* We also have the 1pps capability, allocate a shared-memory segment
- * for the 1pps time data and launch a thread to capture the 1pps
- * transitions
- */
- if ((session->shm_pps = ntpshm_alloc(session->context)) == NULL) {
- gpsd_report(&session->context->errout, LOG_INF,
- "NTPD ntpshm_alloc(1) failed\n");
- } else {
- init_hook(session);
- session->thread_report_hook = report_hook;
- session->thread_wrap_hook = wrap_hook;
- pps_thread_activate(session);
- }
-#endif /* PPS_ENABLE */
- }
+ shmseg->count++;
+ shmseg->valid = 1;
}
-/*@+mustfreeonly@*/
-#endif /* NTPSHM_ENABLE */
/* end */
diff --git a/timehint.c b/timehint.c
new file mode 100644
index 00000000..afc98637
--- /dev/null
+++ b/timehint.c
@@ -0,0 +1,440 @@
+/*
+ * timehint.c - put time information in SHM segment for ntpd, or to chrony
+ *
+ * Note that for easy debugging all logging from this file is prefixed
+ * with PPS or NTP.
+ *
+ * This file is Copyright (c) 2010 by the GPSD project BSD terms apply:
+ * see the file COPYING in the distribution root for details.
+ */
+
+#include <string.h>
+#include <libgen.h>
+#include <stdbool.h>
+#include <math.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifndef S_SPLINT_S
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#endif /* S_SPLINT_S */
+
+#include "gpsd.h"
+
+#ifdef NTPSHM_ENABLE
+#include "ntpshm.h"
+
+/* Note: you can start gpsd as non-root, and have it work with ntpd.
+ * However, it will then only use the ntpshm segments 2 3, and higher.
+ *
+ * Ntpd always runs as root (to be able to control the system clock).
+ * After that it often (depending on its host configuration) drops to run as
+ * user ntpd and group ntpd.
+ *
+ * As of February 2015 its rules for the creation of ntpshm segments are:
+ *
+ * Segments 0 and 1: permissions 0600, i.e. other programs can only
+ * read and write as root.
+ *
+ * Segments 2, 3, and higher:
+ * permissions 0666, i.e. other programs can read
+ * and write as any user. I.e.: if ntpd has been
+ * configured to use these segments, any
+ * unprivileged user is allowed to provide data
+ * for synchronisation.
+ *
+ * By default ntpd creates 0 segments (though the documentation is
+ * written in such a way as to suggest it creates 4). It can be
+ * configured to create up to 217. gpsd creates two segments for each
+ * device it can drive; by default this is 8 segments for 4
+ * devices,but can be higher if it was compiled with a larger value of
+ * MAX_DEVICES.
+ *
+ * Started as root, gpsd does as ntpd when attaching (creating) the
+ * segments. In contrast to ntpd, which only attaches (creates)
+ * configured segments, gpsd creates all segments. Thus a gpsd will
+ * by default create eight segments 0-7 that an ntpd with default
+ * configuration does not watch.
+ *
+ * Started as non-root, gpsd will only attach (create) segments 2 and
+ * above, with permissions 0666. As the permissions are for any user,
+ * the creator does not matter.
+ *
+ * For each GPS module gpsd controls, it will use the attached ntpshm
+ * segments in pairs (for coarse clock and pps source, respectively)
+ * starting from the first found segments. I.e. started as root, one
+ * GPS will deliver data on all segments including 0 and 1; started as
+ * non-root, gpsd will be deliver data only on segments 2 and higher.
+ *
+ * Segments are allocated to activated devices on a first-come-first-served
+ * basis. A device's segment is marked unused when the device is closed and
+ * may be re-used by devices connected later.
+ *
+ * To debug, try looking at the live segments this way:
+ *
+ * ipcs -m
+ *
+ * results should look like this:
+ * ------ Shared Memory Segments --------
+ * key shmid owner perms bytes nattch status
+ * 0x4e545030 0 root 700 96 2
+ * 0x4e545031 32769 root 700 96 2
+ * 0x4e545032 163842 root 666 96 1
+ * 0x4e545033 196611 root 666 96 1
+ * 0x4e545034 253555 root 666 96 1
+ * 0x4e545035 367311 root 666 96 1
+ *
+ * For a bit more data try this:
+ * cat /proc/sysvipc/shm
+ *
+ * If gpsd can not open the segments be sure you are not running SELinux
+ * or apparmor.
+ *
+ * if you see the shared segments (keys 1314148400 -- 1314148405), and
+ * no gpsd or ntpd is running, you can remove them like this:
+ *
+ * ipcrm -M 0x4e545030
+ * ipcrm -M 0x4e545031
+ * ipcrm -M 0x4e545032
+ * ipcrm -M 0x4e545033
+ * ipcrm -M 0x4e545034
+ * ipcrm -M 0x4e545035
+ *
+ * Removing these segments is usually not necessary, as the operating system
+ * garbage-collects them when they have no attached processes.
+ */
+
+#define PPS_MIN_FIXES 3 /* # fixes to wait for before shipping PPS */
+
+static /*@null@*/ volatile struct shmTime *getShmTime(struct gps_context_t *context, int unit)
+{
+ int shmid;
+ unsigned int perms;
+ volatile struct shmTime *p;
+ // set the SHM perms the way ntpd does
+ if (unit < 2) {
+ // we are root, be careful
+ perms = 0600;
+ } else {
+ // we are not root, try to work anyway
+ perms = 0666;
+ }
+
+ /*
+ * Note: this call requires root under BSD, and possibly on
+ * well-secured Linux systems. This is why ntpshmwrite.context_init() has to be
+ * called before privilege-dropping.
+ */
+ shmid = shmget((key_t) (NTPD_BASE + unit),
+ sizeof(struct shmTime), (int)(IPC_CREAT | perms));
+ if (shmid == -1) {
+ gpsd_report(&context->errout, LOG_ERROR,
+ "NTPD shmget(%ld, %zd, %o) fail: %s\n",
+ (long int)(NTPD_BASE + unit), sizeof(struct shmTime),
+ (int)perms, strerror(errno));
+ return NULL;
+ }
+ p = (struct shmTime *)shmat(shmid, 0, 0);
+ /*@ -mustfreefresh */
+ if ((int)(long)p == -1) {
+ gpsd_report(&context->errout, LOG_ERROR,
+ "NTPD shmat failed: %s\n",
+ strerror(errno));
+ return NULL;
+ }
+ gpsd_report(&context->errout, LOG_PROG,
+ "NTPD shmat(%d,0,0) succeeded, segment %d\n",
+ shmid, unit);
+ return p;
+ /*@ +mustfreefresh */
+}
+
+void ntpshm_context_init(struct gps_context_t *context)
+/* Attach all NTP SHM segments. Called once at startup, while still root. */
+{
+ int i;
+
+ for (i = 0; i < NTPSHMSEGS; i++) {
+ // Only grab the first two when running as root.
+ if (2 <= i || 0 == getuid()) {
+ context->shmTime[i] = getShmTime(context, i);
+ }
+ }
+ memset(context->shmTimeInuse, 0, sizeof(context->shmTimeInuse));
+}
+
+/*@-unqualifiedtrans@*/
+static /*@null@*/ volatile struct shmTime *ntpshm_alloc(struct gps_context_t *context)
+/* allocate NTP SHM segment. return its segment number, or -1 */
+{
+ int i;
+
+ for (i = 0; i < NTPSHMSEGS; i++)
+ if (context->shmTime[i] != NULL && !context->shmTimeInuse[i]) {
+ context->shmTimeInuse[i] = true;
+
+ /*
+ * In case this segment gets sent to ntpd before an
+ * ephemeris is available, the LEAP_NOTINSYNC value will
+ * tell ntpd that this source is in a "clock alarm" state
+ * and should be ignored. The goal is to prevent ntpd
+ * from declaring the GPS a falseticker before it gets
+ * all its marbles together.
+ */
+ memset((void *)context->shmTime[i], 0, sizeof(struct shmTime));
+ context->shmTime[i]->mode = 1;
+ context->shmTime[i]->leap = LEAP_NOTINSYNC;
+ context->shmTime[i]->precision = -1; /* initially 0.5 sec */
+ context->shmTime[i]->nsamples = 3; /* stages of median filter */
+
+ return context->shmTime[i];
+ }
+
+ return NULL;
+}
+/*@+unqualifiedtrans@*/
+
+static bool ntpshm_free(struct gps_context_t * context, volatile struct shmTime *s)
+/* free NTP SHM segment */
+{
+ int i;
+
+ for (i = 0; i < NTPSHMSEGS; i++)
+ if (s == context->shmTime[i]) {
+ context->shmTimeInuse[i] = false;
+ return true;
+ }
+
+ return false;
+}
+
+void ntpshm_session_init(struct gps_device_t *session)
+{
+ /*@-mustfreeonly@*/
+#ifdef NTPSHM_ENABLE
+ /* mark NTPD shared memory segments as unused */
+ session->shm_clock = NULL;
+#endif /* NTPSHM_ENABLE */
+#ifdef PPS_ENABLE
+ session->shm_pps = NULL;
+#endif /* PPS_ENABLE */
+ /*@+mustfreeonly@*/
+}
+
+int ntpshm_put(struct gps_device_t *session, volatile struct shmTime *shmseg, struct timedelta_t *td)
+/* put a received fix time into shared memory for NTP */
+{
+ char real_str[TIMESPEC_LEN];
+ char clock_str[TIMESPEC_LEN];
+
+ /* Any NMEA will be about -1 or -2. Garmin GPS-18/USB is around -6 or -7. */
+ int precision = -1; /* default precision */
+
+ if (shmseg == NULL) {
+ gpsd_report(&session->context->errout, LOG_RAW, "NTPD missing shm\n");
+ return 0;
+ }
+
+#ifdef PPS_ENABLE
+ /* ntpd sets -20 for PPS refclocks, thus -20 precision */
+ /* TODO: if PPS over USB 1.1, then precision = -10 */
+ if (shmseg == session->shm_pps)
+ precision = -20;
+#endif /* PPS_ENABLE */
+
+ ntp_write(shmseg, td, precision, session->context->leap_notify);
+
+ /*@-type@*/ /* splint is confused about struct timespec */
+ timespec_str( &td->real, real_str, sizeof(real_str) );
+ timespec_str( &td->clock, clock_str, sizeof(clock_str) );
+ gpsd_report(&session->context->errout, LOG_RAW,
+ "NTP ntpshm_put(%s %s) %s @ %s\n",
+ session->gpsdata.dev.path,
+ (precision == -20) ? "pps" : "clock",
+ real_str, clock_str);
+ /*@+type@*/
+
+ return 1;
+}
+
+#ifdef PPS_ENABLE
+#define SOCK_MAGIC 0x534f434b
+struct sock_sample {
+ struct timeval tv;
+ double offset;
+ int pulse;
+ int leap;
+ // cppcheck-suppress unusedStructMember
+ int _pad;
+ int magic; /* must be SOCK_MAGIC */
+};
+
+/*@-mustfreefresh@*/
+static void init_hook(struct gps_device_t *session)
+/* for chrony SOCK interface, which allows nSec timekeeping */
+{
+ /* open the chrony socket */
+ char chrony_path[GPS_PATH_MAX];
+
+ session->chronyfd = -1;
+ if ( 0 == getuid() ) {
+ /* this case will fire on command-line devices;
+ * they're opened before priv-dropping. Matters because
+ * only root can use /var/run.
+ */
+ (void)snprintf(chrony_path, sizeof (chrony_path),
+ "/var/run/chrony.%s.sock", basename(session->gpsdata.dev.path));
+ } else {
+ (void)snprintf(chrony_path, sizeof (chrony_path),
+ "/tmp/chrony.%s.sock", basename(session->gpsdata.dev.path));
+ }
+
+ if (access(chrony_path, F_OK) != 0) {
+ gpsd_report(&session->context->errout, LOG_PROG,
+ "PPS chrony socket %s doesn't exist\n", chrony_path);
+ } else {
+ session->chronyfd = netlib_localsocket(chrony_path, SOCK_DGRAM);
+ if (session->chronyfd < 0)
+ gpsd_report(&session->context->errout, LOG_PROG,
+ "PPS connect chrony socket failed: %s, error: %d, errno: %d/%s\n",
+ chrony_path, session->chronyfd, errno, strerror(errno));
+ else
+ gpsd_report(&session->context->errout, LOG_RAW,
+ "PPS using chrony socket: %s\n", chrony_path);
+ }
+}
+/*@+mustfreefresh@*/
+
+
+/* td is the real time and clock time of the edge */
+/* offset is actual_ts - clock_ts */
+static void chrony_send(struct gps_device_t *session, struct timedelta_t *td)
+{
+ char real_str[TIMESPEC_LEN];
+ char clock_str[TIMESPEC_LEN];
+ struct timespec offset;
+ struct sock_sample sample;
+
+ /* chrony expects tv-sec since Jan 1970 */
+ sample.pulse = 0;
+ sample.leap = session->context->leap_notify;
+ sample.magic = SOCK_MAGIC;
+ /*@-compdef@*/
+ /*@-type@*//* splint is confused about struct timespec */
+ /* chronyd wants a timeval, not a timspec, not to worry, it is
+ * just the top of the second */
+ TSTOTV(&sample.tv, &td->clock);
+ /* calculate the offset as a timespec to not lose precision */
+ TS_SUB( &offset, &td->real, &td->clock);
+ /* if tv_sec greater than 2 then tv_nsec loses precision, but
+ * not a big deal as slewing will bbe required */
+ sample.offset = TSTONS( &offset );
+ /*@+compdef@*/
+ sample._pad = 0;
+ /*@+type@*/
+
+ /*@-type@*/ /* splint is confused about struct timespec */
+ timespec_str( &td->real, real_str, sizeof(real_str) );
+ timespec_str( &td->clock, clock_str, sizeof(clock_str) );
+ gpsd_report(&session->context->errout, LOG_RAW,
+ "PPS chrony_send %s @ %s Offset: %0.9f\n",
+ real_str, clock_str, sample.offset);
+ /*@+type@*/
+ (void)send(session->chronyfd, &sample, sizeof (sample), 0);
+}
+
+static void wrap_hook(struct gps_device_t *session)
+{
+ if (session->chronyfd != -1)
+ (void)close(session->chronyfd);
+}
+
+static /*@observer@*/ char *report_hook(struct gps_device_t *session,
+ struct timedelta_t *td)
+/* ship the time of a PPS event to ntpd and/or chrony */
+{
+ char *log1;
+
+ if (!session->ship_to_ntpd)
+ return "skipped ship_to_ntp=0";
+
+ /*
+ * Only listen to PPS after several consecutive fixes,
+ * otherwise time may be inaccurate. (We know this is
+ * required on all Garmin and u-blox; safest to do it
+ * for all cases as we have no other general way to know
+ * if PPS is good.
+ *
+ * Not sure yet how to handle u-blox UBX_MODE_TMONLY
+ */
+ if (session->fixcnt <= PPS_MIN_FIXES)
+ return "no fix";
+
+ log1 = "accepted";
+ if ( 0 <= session->chronyfd ) {
+ log1 = "accepted chrony sock";
+ chrony_send(session, td);
+ }
+ if (session->shm_pps != NULL)
+ (void)ntpshm_put(session, session->shm_pps, td);
+
+ return log1;
+}
+#endif /* PPS_ENABLE */
+
+/*@-mustfreeonly@*/
+void ntpshm_link_deactivate(struct gps_device_t *session)
+/* release ntpshm storage for a session */
+{
+ if (session->shm_clock != NULL) {
+ (void)ntpshm_free(session->context, session->shm_clock);
+ session->shm_clock = NULL;
+ }
+#if defined(PPS_ENABLE)
+ if (session->shm_pps != NULL) {
+ pps_thread_deactivate(session);
+ (void)ntpshm_free(session->context, session->shm_pps);
+ session->shm_pps = NULL;
+ }
+#endif /* PPS_ENABLE */
+}
+/*@+mustfreeonly@*/
+
+/*@-mustfreeonly@*/
+void ntpshm_link_activate(struct gps_device_t *session)
+/* set up ntpshm storage for a session */
+{
+ /* don't talk to NTP when we're running inside the test harness */
+ if (session->sourcetype == source_pty)
+ return;
+
+ /* allocate a shared-memory segment for "NMEA" time data */
+ session->shm_clock = ntpshm_alloc(session->context);
+
+ if (session->shm_clock == NULL) {
+ gpsd_report(&session->context->errout, LOG_INF,
+ "NTPD ntpshm_alloc() failed\n");
+#if defined(PPS_ENABLE)
+ } else if (session->sourcetype == source_usb || session->sourcetype == source_rs232) {
+ /* We also have the 1pps capability, allocate a shared-memory segment
+ * for the 1pps time data and launch a thread to capture the 1pps
+ * transitions
+ */
+ if ((session->shm_pps = ntpshm_alloc(session->context)) == NULL) {
+ gpsd_report(&session->context->errout, LOG_INF,
+ "NTPD ntpshm_alloc(1) failed\n");
+ } else {
+ init_hook(session);
+ session->thread_report_hook = report_hook;
+ session->thread_wrap_hook = wrap_hook;
+ pps_thread_activate(session);
+ }
+#endif /* PPS_ENABLE */
+ }
+}
+/*@+mustfreeonly@*/
+
+#endif /* NTPSHM_ENABLE */
+/* end */