1 /*
2  * gpspipe
3  *
4  * a simple program to connect to a gpsd daemon and dump the received data
5  * to stdout
6  *
7  * This will dump the raw NMEA from gpsd to stdout
8  *      gpspipe -r
9  *
10  * This will dump the super-raw data (gps binary) from gpsd to stdout
11  *      gpspipe -R
12  *
13  * This will dump the GPSD sentences from gpsd to stdout
14  *      gpspipe -w
15  *
16  * This will dump the GPSD and the NMEA sentences from gpsd to stdout
17  *      gpspipe -wr
18  *
19  * Original code by: Gary E. Miller <gem@rellim.com>.  Cleanup by ESR.
20  *
21  * This file is Copyright (c) 2010-2018 by the GPSD project
22  * SPDX-License-Identifier: BSD-2-clause
23  *
24  */
25 
26 #include "gpsd_config.h"  /* must be before all includes */
27 
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <stdbool.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <strings.h>
35 #include <sys/select.h>
36 #include <sys/stat.h>
37 #include <sys/time.h>
38 #include <sys/types.h>
39 #include <time.h>               /* for time_t */
40 #include <unistd.h>
41 
42 #ifdef HAVE_SYS_SOCKET_H
43 #include <sys/socket.h>
44 #endif /* HAVE_SYS_SOCKET_H */
45 #include <termios.h>            /* for speed_t, and cfmakeraw() on some OS */
46 #ifdef HAVE_WINSOCK2_H
47 #include <winsock2.h>
48 #endif /* HAVE_WINSOCK2_H */
49 
50 #include "gpsd.h"
51 
52 #include "gpsdclient.h"
53 #include "revision.h"
54 
55 static struct gps_data_t gpsdata;
56 static void spinner(unsigned int, unsigned int);
57 
58 /* NMEA-0183 standard baud rate */
59 #define BAUDRATE B4800
60 
61 /* Serial port variables */
62 static struct termios oldtio, newtio;
63 static int fd_out = 1;		/* output initially goes to standard output */
64 static char serbuf[255];
65 static int debug;
66 
open_serial(char * device)67 static void open_serial(char *device)
68 /* open the serial port and set it up */
69 {
70     /*
71      * Open modem device for reading and writing and not as controlling
72      * tty.
73      */
74     if ((fd_out = open(device, O_RDWR | O_NOCTTY)) == -1) {
75 	(void)fprintf(stderr, "gpspipe: error opening serial port\n");
76 	exit(EXIT_FAILURE);
77     }
78 
79     /* Save current serial port settings for later */
80     if (tcgetattr(fd_out, &oldtio) != 0) {
81 	(void)fprintf(stderr, "gpspipe: error reading serial port settings\n");
82 	exit(EXIT_FAILURE);
83     }
84 
85     /* Clear struct for new port settings. */
86     memset(&newtio, 0, sizeof(newtio));
87 
88     /* make it raw */
89     (void)cfmakeraw(&newtio);
90     /* set speed */
91     (void)cfsetospeed(&newtio, BAUDRATE);
92 
93     /* Clear the modem line and activate the settings for the port. */
94     (void)tcflush(fd_out, TCIFLUSH);
95     if (tcsetattr(fd_out, TCSANOW, &newtio) != 0) {
96 	(void)fprintf(stderr, "gpspipe: error configuring serial port\n");
97 	exit(EXIT_FAILURE);
98     }
99 }
100 
usage(void)101 static void usage(void)
102 {
103     (void)fprintf(stderr,
104 		  "Usage: gpspipe [OPTIONS] [server[:port[:device]]]\n\n"
105 		  "-2 Set the split24 flag.\n"
106 		  "-d Run as a daemon.\n"
107 		  "-h Show this help.\n"
108 		  "-l Sleep for ten seconds before connecting to gpsd.\n"
109 		  "-n [count] exit after count packets.\n"
110 		  "-o [file] Write output to file.\n"
111 		  "-P Include PPS JSON in NMEA or raw mode.\n"
112 		  "-p Include profiling info in the JSON.\n"
113 		  "-r Dump raw NMEA.\n"
114 		  "-R Dump super-raw mode (GPS binary).\n"
115 		  "-s [serial dev] emulate a 4800bps NMEA GPS on serial port (use with '-r').\n"
116 		  "-S Set scaled flag. For AIS and subframe data.\n"
117 		  "-T [format] set the timestamp format (strftime(3)-like; implies '-t')\n"
118 		  "-t Time stamp the data.\n"
119 		  "-u usec time stamp, implies -t. Use -uu to output sec.usec\n"
120 		  "-v Print a little spinner.\n"
121 		  "-V Print version and exit.\n"
122 		  "-w Dump gpsd native data.\n"
123 		  "-x [seconds] Exit after given delay.\n"
124 		  "-Z sets the timestamp format iso8601: implies '-t'\n"
125 		  "You must specify one, or more, of -r, -R, or -w\n"
126 		  "You must use -o if you use -d.\n");
127 }
128 
main(int argc,char ** argv)129 int main(int argc, char **argv)
130 {
131     char buf[4096];
132     bool timestamp = false;
133     bool iso8601 = false;
134     char *format = "%F %T";
135     char *zulu_format = "%FT%T";
136     char tmstr[200];
137     bool daemonize = false;
138     bool binary = false;
139     bool sleepy = false;
140     bool new_line = true;
141     bool raw = false;
142     bool watch = false;
143     bool profile = false;
144     int option_u = 0;                   // option to show uSeconds
145     long count = -1;
146     time_t exit_timer = 0;
147     int option;
148     unsigned int vflag = 0, l = 0;
149     FILE *fp;
150     unsigned int flags;
151     fd_set fds;
152 
153     struct fixsource_t source;
154     char *serialport = NULL;
155     char *outfile = NULL;
156 
157     flags = WATCH_ENABLE;
158     while ((option = getopt(argc, argv,
159                             "2?dD:hln:o:pPrRwSs:tT:uvVx:Z")) != -1) {
160 	switch (option) {
161 	case '2':
162 	    flags |= WATCH_SPLIT24;
163 	    break;
164 	case 'D':
165 	    debug = atoi(optarg);
166 #ifdef CLIENTDEBUG_ENABLE
167 	    gps_enable_debug(debug, stderr);
168 #endif /* CLIENTDEBUG_ENABLE */
169 	    break;
170 	case 'd':
171 	    daemonize = true;
172 	    break;
173 	case 'l':
174 	    sleepy = true;
175 	    break;
176 	case 'n':
177 	    count = strtol(optarg, 0, 0);
178 	    break;
179 	case 'o':
180 	    outfile = optarg;
181 	    break;
182 	case 'P':
183 	    flags |= WATCH_PPS;
184 	    break;
185 	case 'p':
186 	    profile = true;
187 	    break;
188 	case 'R':
189 	    flags |= WATCH_RAW;
190 	    binary = true;
191 	    break;
192 	case 'r':
193 	    raw = true;
194 	    /*
195 	     * Yes, -r invokes NMEA mode rather than proper raw mode.
196 	     * This emulates the behavior under the old protocol.
197 	     */
198 	    flags |= WATCH_NMEA;
199 	    break;
200 	case 'S':
201 	    flags |= WATCH_SCALED;
202 	    break;
203 	case 's':
204 	    serialport = optarg;
205 	    break;
206 	case 'T':
207 	    timestamp = true;
208 	    format = optarg;
209 	    break;
210 	case 't':
211 	    timestamp = true;
212 	    break;
213 	case 'u':
214 	    timestamp = true;
215 	    option_u++;
216 	    break;
217 	case 'V':
218 	    (void)fprintf(stderr, "%s: %s (revision %s)\n",
219 			  argv[0], VERSION, REVISION);
220 	    exit(EXIT_SUCCESS);
221 	case 'v':
222 	    vflag++;
223 	    break;
224 	case 'w':
225 	    flags |= WATCH_JSON;
226 	    watch = true;
227 	    break;
228 	case 'x':
229 	    exit_timer = time(NULL) + strtol(optarg, 0, 0);
230 	    break;
231 	case 'Z':
232 	    timestamp = true;
233 	    format = zulu_format;
234 	    iso8601 = true;
235 	    break;
236 	case '?':
237 	case 'h':
238 	default:
239 	    usage();
240 	    exit(EXIT_FAILURE);
241 	}
242     }
243 
244     /* Grok the server, port, and device. */
245     if (optind < argc) {
246 	gpsd_source_spec(argv[optind], &source);
247     } else
248 	gpsd_source_spec(NULL, &source);
249 
250     if (serialport != NULL && !raw) {
251 	(void)fprintf(stderr, "gpspipe: use of '-s' requires '-r'.\n");
252 	exit(EXIT_FAILURE);
253     }
254 
255     if (outfile == NULL && daemonize) {
256 	(void)fprintf(stderr, "gpspipe: use of '-d' requires '-o'.\n");
257 	exit(EXIT_FAILURE);
258     }
259 
260     if (!raw && !watch && !binary) {
261 	(void)fprintf(stderr,
262 		      "gpspipe: one of '-R', '-r', or '-w' is required.\n");
263 	exit(EXIT_FAILURE);
264     }
265 
266     /* Daemonize if the user requested it. */
267     if (daemonize)
268 	if (os_daemon(0, 0) != 0)
269 	    (void)fprintf(stderr,
270 			  "gpspipe: daemonization failed: %s\n",
271 			  strerror(errno));
272 
273     /* Sleep for ten seconds if the user requested it. */
274     if (sleepy)
275 	(void)sleep(10);
276 
277     /* Open the output file if the user requested it.  If the user
278      * requested '-R', we use the 'b' flag in fopen() to "do the right
279      * thing" in non-linux/unix OSes. */
280     if (outfile == NULL) {
281 	fp = stdout;
282     } else {
283 	if (binary)
284 	    fp = fopen(outfile, "wb");
285 	else
286 	    fp = fopen(outfile, "w");
287 
288 	if (fp == NULL) {
289 	    (void)fprintf(stderr,
290 			  "gpspipe: unable to open output file:  %s\n",
291 			  outfile);
292 	    exit(EXIT_FAILURE);
293 	}
294     }
295 
296     /* Open the serial port and set it up. */
297     if (serialport)
298 	open_serial(serialport);
299 
300     if (gps_open(source.server, source.port, &gpsdata) != 0) {
301 	(void)fprintf(stderr,
302 		      "gpspipe: could not connect to gpsd %s:%s, %s(%d)\n",
303 		      source.server, source.port, gps_errstr(errno), errno);
304 	exit(EXIT_FAILURE);
305     }
306 
307     if (profile)
308 	flags |= WATCH_TIMING;
309     if (source.device != NULL)
310 	flags |= WATCH_DEVICE;
311     (void)gps_stream(&gpsdata, flags, source.device);
312 
313     if ((isatty(STDERR_FILENO) == 0) || daemonize)
314 	vflag = 0;
315 
316     for (;;) {
317 	int r = 0;
318 	struct timespec tv;
319 
320 	tv.tv_sec = 0;
321 	tv.tv_nsec = 100000000;
322 	FD_ZERO(&fds);
323 	FD_SET(gpsdata.gps_fd, &fds);
324 	errno = 0;
325 	r = pselect(gpsdata.gps_fd+1, &fds, NULL, NULL, &tv, NULL);
326 	if (r >= 0 && exit_timer && time(NULL) >= exit_timer)
327 		break;
328 	if (r == -1 && errno != EINTR) {
329 	    (void)fprintf(stderr, "gpspipe: select error %s(%d)\n",
330 			  strerror(errno), errno);
331 	    exit(EXIT_FAILURE);
332 	} else if (r == 0)
333 		continue;
334 
335 	if (vflag)
336 	    spinner(vflag, l++);
337 
338 	/* reading directly from the socket avoids decode overhead */
339 	errno = 0;
340 	r = (int)recv(gpsdata.gps_fd, buf, sizeof(buf), 0);
341 	if (r > 0) {
342 	    int i = 0;
343 	    int j = 0;
344 	    for (i = 0; i < r; i++) {
345 		char c = buf[i];
346 		if (j < (int)(sizeof(serbuf) - 1)) {
347 		    serbuf[j++] = buf[i];
348 		}
349 		if (new_line && timestamp) {
350 		    char tmstr_u[40];            // time with "usec" resolution
351 		    struct timespec now;
352 		    struct tm tmp_now;
353                     int written;
354 
355 		    (void)clock_gettime(CLOCK_REALTIME, &now);
356 		    (void)gmtime_r((time_t *)&(now.tv_sec), &tmp_now);
357 		    (void)strftime(tmstr, sizeof(tmstr), format, &tmp_now);
358 		    new_line = 0;
359 
360 		    switch( option_u ) {
361 		    case 2:
362 			if(iso8601){
363 			    written = strlen(tmstr);
364 			    tmstr[written] = 'Z';
365 			    tmstr[written+1] = '\0';
366 			}
367 			(void)snprintf(tmstr_u, sizeof(tmstr_u),
368 				       " %lld.%06ld",
369 				       (long long)now.tv_sec,
370 				       (long)now.tv_nsec/1000);
371 			break;
372 		    case 1:
373                         written = snprintf(tmstr_u, sizeof(tmstr_u),
374                                            ".%06ld", (long)now.tv_nsec/1000);
375 
376 			if((0 < written) && (40 > written) && iso8601){
377 			    tmstr_u[written-1] = 'Z';
378 			    tmstr_u[written] = '\0';
379 			}
380 			break;
381 		    default:
382 			*tmstr_u = '\0';
383 			break;
384 		    }
385 
386 		    if (fprintf(fp, "%.24s%s: ", tmstr, tmstr_u) <= 0) {
387 			(void)fprintf(stderr,
388 				      "gpspipe: write error, %s(%d)\n",
389 				      strerror(errno), errno);
390 			exit(EXIT_FAILURE);
391 		    }
392 		}
393 		if (fputc(c, fp) == EOF) {
394 		    (void)fprintf(stderr, "gpspipe: write error, %s(%d)\n",
395 		                  strerror(errno), errno);
396 		    exit(EXIT_FAILURE);
397 		}
398 
399 		if (c == '\n') {
400 		    if (serialport != NULL) {
401 			if (write(fd_out, serbuf, (size_t) j) == -1) {
402 			    (void)fprintf(stderr,
403 			                  "gpspipe: serial port write error,"
404 			                  " %s(%d)\n",
405 			                  strerror(errno), errno);
406 			    exit(EXIT_FAILURE);
407 			}
408 			j = 0;
409 		    }
410 
411 		    new_line = true;
412 		    /* flush after every good line */
413 		    if (fflush(fp)) {
414 			(void)fprintf(stderr,
415 				      "gpspipe: fflush error, %s(%d)\n",
416 				      strerror(errno), errno);
417 			exit(EXIT_FAILURE);
418 		    }
419 		    if (count > 0) {
420 			if (0 >= --count) {
421 			    /* completed count */
422 			    exit(EXIT_SUCCESS);
423 			}
424 		    }
425 		}
426 	    }
427 	} else {
428 	    if (r == -1) {
429 		if (errno == EAGAIN)
430 		    continue;
431 		else
432 		    (void)fprintf(stderr, "gpspipe: read error %s(%d)\n",
433 			      strerror(errno), errno);
434 		exit(EXIT_FAILURE);
435 	    } else {
436 		exit(EXIT_SUCCESS);
437 	    }
438 	}
439     }
440 
441 #ifdef __UNUSED__
442     if (serialport != NULL) {
443 	/* Restore the old serial port settings. */
444 	if (tcsetattr(fd_out, TCSANOW, &oldtio) != 0) {
445 	    (void)fprintf(stderr, "gpsipe: error restoring serial port settings\n");
446 	    exit(EXIT_FAILURE);
447 	}
448     }
449 #endif /* __UNUSED__ */
450 
451     exit(EXIT_SUCCESS);
452 }
453 
454 
spinner(unsigned int v,unsigned int num)455 static void spinner(unsigned int v, unsigned int num)
456 {
457     char *spin = "|/-\\";
458 
459     (void)fprintf(stderr, "\010%c", spin[(num / (1 << (v - 1))) % 4]);
460     (void)fflush(stderr);
461     return;
462 }
463