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