summaryrefslogtreecommitdiff
path: root/ntpshmmon.c
blob: 21550ef82b102bbed369955b9e684f0f107b839d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/* ntpshmmon.c -- monitor the inner end of an ntpshmwrite.connection
 *
 * This file is Copyright (c) 2010-2018 by the GPSD project
 * SPDX-License-Identifier: BSD-2-clause
 *
 */

/* sys/ipc.h needs _XOPEN_SOURCE, 500 means X/Open 1995 */
#define _XOPEN_SOURCE 500

#include <stdio.h>
#include <string.h>     /* for memset() */
#include <stdlib.h>
#include <getopt.h>
#include <limits.h>
#include <unistd.h>

#include "gps.h"	/* for safe_atof() */
#include "gpsd_config.h"
#include "ntpshm.h"
#include "revision.h"
#include "timespec.h"

#define NTPSEGMENTS	256	/* NTPx for x any byte */

static struct shmTime *segments[NTPSEGMENTS + 1];

int main(int argc, char **argv)
{
    int option;
    int	i;
    bool killall = false;
    bool offset = false;            /* show offset, not seen */
    bool verbose = false;
    int nsamples = INT_MAX;
    time_t timeout = (time_t)0, starttime = time(NULL);
    /* a copy of all old segments */
    struct shm_stat_t	shm_stat_old[NTPSEGMENTS + 1];
    char *whoami;

    /* strip path from program name */
    (whoami = strrchr(argv[0], '/')) ? ++whoami : (whoami = argv[0]);

    memset( shm_stat_old, 0 ,sizeof( shm_stat_old));

    while ((option = getopt(argc, argv, "?hn:ost:vV")) != -1) {
	switch (option) {
	case '?':
	case 'h':
	    (void)fprintf(
	        stderr,
                "usage: ntpshmmon [-?] [-h] [-n nsamples] [-o] [-s] [-t nseconds] [-v] [-V]\n"
                "  -?           print this help\n"
                "  -h           print this help\n"
                "  -n nsamples  exit after nsamples\n"
                "  -o           replace Seen@ with Offset\n"
                "  -s           remove SHMs and exit\n"
                "  -t nseconds  exit after nseconds\n"
                "  -v           be verbose\n"
                "  -V           print version and exit\n"
		);
	    exit(EXIT_SUCCESS);
	case 'n':
	    nsamples = atoi(optarg);
	    break;
	case 'o':
	    offset = true;
	    break;
	case 's':
	    killall = true;
	    break;
	case 't':
	    timeout = (time_t)atoi(optarg);
	    break;
	case 'v':
	    verbose = true;
	    break;
	case 'V':
	    (void)fprintf(stderr, "%s: version %s (revision %s)\n",
			  whoami, VERSION, REVISION);
	    exit(EXIT_SUCCESS);
	default:
	    /* no option, just go and do it */
	    break;
	}
    }

    /* grab all segments, keep the non-null ones */
    for (i = 0; i < NTPSEGMENTS; i++) {
	segments[i] = shm_get(i, false, true);
	if (verbose && segments[i] != NULL)
	    (void)fprintf(stderr, "unit %d opened\n", i);
    }

    if (killall) {
	struct shmTime **pp;

	for (pp = segments; pp < segments + NTPSEGMENTS; pp++)
	    if (*pp != NULL)
		(void)shmdt((void *)(*pp));
	exit(EXIT_SUCCESS);
    }

    /*
     * We want line buffering even if stdout is going to a file.  This
     * is a (possibly futile) attempt to avoid writing an incomplete
     * line on interrupt.
     */
    setvbuf(stdout, NULL, _IOLBF, 0);

    (void)printf("%s: version %s\n", whoami, VERSION);
    if (offset) {
	(void)printf("#      Name     Offset           Clock                Real                 L Prc\n");
    } else {
	(void)printf("#      Name Seen@                Clock                Real                 L Prc\n");
    }

    do {
	/* the current segment */
	struct shm_stat_t	shm_stat;
	struct timespec delay;

	for (i = 0; i < NTPSEGMENTS; i++) {
	    long long diff;  /* 32 bit long is too short for a timespec */
	    enum segstat_t status = ntp_read(segments[i], &shm_stat, false);
	    if (verbose)
		(void)fprintf(stderr, "unit %d status %d\n", i, status);
	    switch(status) {
	    case OK:
		/* ntpd can slew the clock at 120% real time
                 * so do not lock out slightly short cycles
		 * use 50% of cycle time as lock out limit.
                 * ignore that system time may jump. */
		diff = timespec_diff_ns(shm_stat.tvr, shm_stat_old[i].tvr);
		if ( 0 == diff) {
		    /* no change in tvr */
		    break;
		}
		diff = timespec_diff_ns(shm_stat.tvt, shm_stat_old[i].tvt);
		if ( 0 == diff) {
		    /* no change in tvt */
		    break;
		}
		/* time stamp it */
		clock_gettime(CLOCK_REALTIME, &shm_stat.tvc);
                if (offset) {
		    diff = timespec_diff_ns(shm_stat.tvr, shm_stat.tvt);
		    printf("sample %s %20.9f %ld.%09ld %ld.%09ld %d %3d\n",
			   ntp_name(i),
			   (double)diff * 1e-9,
			   (long)shm_stat.tvr.tv_sec, shm_stat.tvr.tv_nsec,
			   (long)shm_stat.tvt.tv_sec, shm_stat.tvt.tv_nsec,
			   shm_stat.leap, shm_stat.precision);
                } else {
		    printf("sample %s %ld.%09ld %ld.%09ld %ld.%09ld %d %3d\n",
			   ntp_name(i),
			   (long)shm_stat.tvc.tv_sec, shm_stat.tvc.tv_nsec,
			   (long)shm_stat.tvr.tv_sec, shm_stat.tvr.tv_nsec,
			   (long)shm_stat.tvt.tv_sec, shm_stat.tvt.tv_nsec,
			   shm_stat.leap, shm_stat.precision);
                }
		--nsamples;
		/* save the new time stamp */
		shm_stat_old[i] = shm_stat; /* structure copy */

		break;
	    case NO_SEGMENT:
		break;
	    case NOT_READY:
		/* do nothing, data not ready, wait another cycle */
		break;
	    case BAD_MODE:
		(void)fprintf(stderr,
		              "ntpshmmon: unknown mode %d on segment %s\n",
		              shm_stat.status, ntp_name(i));
		break;
	    case CLASH:
		/* do nothing, data is corrupt, wait another cycle */
		break;
	    default:
		(void)fprintf(stderr,
		              "ntpshmmon: unknown status %d on segment %s\n",
		              status, ntp_name(i));
		break;
	    }
	}
	/* all segments now checked */

	/*
	 * Even on a 1 Hz PPS, a sleep(1) may end up
         * being sleep(1.1) and missing a beat.  Since
	 * we're ignoring duplicates via timestamp, polling
	 * at fast intervals should not be a problem
	 *
	 * PPS is not always one pulse per second.
	 * the Garmin GPS 18x-5Hz outputs 5 pulses per second.
         * That is a 200 millSec cycle, minimum 20 milliSec duration
         * we will wait 1 milliSec out of caution
         *
         * and, of course, nanosleep() may sleep a lot longer than we ask...
	 */
	if ( timeout ) {
	    /* do not read time unless it matters */
	    if ( time(NULL) > (starttime + timeout ) ) {
		/* time to exit */
		break;
	    }
	}

        /* wait 1,000 uSec */
	delay.tv_sec = 0;
	delay.tv_nsec = 1000000L;
	nanosleep(&delay, NULL);
    } while ( 0 < nsamples );

    exit(EXIT_SUCCESS);
}

/* end */