1 /*
2  * This file is Copyright (c) 2010-2018 by the GPSD project
3  * SPDX-License-Identifier: BSD-2-clause
4  */
5 
6 #include "gpsd_config.h"  /* must be before all includes */
7 
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <stdbool.h>
11 #include <string.h>
12 #include <math.h>
13 #include <time.h>
14 #include <errno.h>
15 #include <libgen.h>
16 #include <signal.h>
17 #include <assert.h>
18 #include <unistd.h>
19 
20 #include "gps.h"
21 #include "gpsdclient.h"
22 #include "revision.h"
23 #include "os_compat.h"
24 #include "timespec.h"
25 
26 static char *progname;
27 static struct fixsource_t source;
28 
29 /**************************************************************************
30  *
31  * Transport-layer-independent functions
32  *
33  **************************************************************************/
34 
35 static struct gps_data_t gpsdata;
36 static FILE *logfile;
37 static bool intrack = false;
38 static time_t timeout = 5;	/* seconds */
39 static double minmove = 0;	/* meters */
40 #ifdef CLIENTDEBUG_ENABLE
41 static int debug;
42 #endif /* CLIENTDEBUG_ENABLE */
43 
print_gpx_header(void)44 static void print_gpx_header(void)
45 {
46     char tbuf[CLIENT_DATE_MAX+1];
47 
48     (void)fprintf(logfile, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
49     (void)fprintf(logfile, "<gpx version=\"1.1\" creator=\"GPSD %s - %s\"\n",
50                   VERSION, GPSD_URL);
51     (void)fprintf(logfile,
52          "        xmlns:xsi=\"https://www.w3.org/2001/XMLSchema-instance\"\n");
53     (void)fprintf(logfile,
54          "        xmlns=\"http://www.topografix.com/GPX/1/1\"\n");
55     (void)fprintf(logfile
56          ,"        xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1\n");
57     (void)fprintf(logfile
58          ,"        http://www.topografix.com/GPX/1/1/gpx.xsd\">\n");
59     (void)fprintf(logfile, " <metadata>\n");
60     (void)fprintf(logfile, "  <time>%s</time>\n",
61          now_to_iso8601(tbuf, sizeof(tbuf)));
62     (void)fprintf(logfile," </metadata>\n");
63     (void)fflush(logfile);
64 }
65 
print_gpx_trk_end(void)66 static void print_gpx_trk_end(void)
67 {
68     (void)fprintf(logfile,"  </trkseg>\n");
69     (void)fprintf(logfile," </trk>\n");
70     (void)fflush(logfile);
71 }
72 
print_gpx_footer(void)73 static void print_gpx_footer(void)
74 {
75     if (intrack)
76 	print_gpx_trk_end();
77     (void)fprintf(logfile,"</gpx>\n");
78     (void)fclose(logfile);
79 }
80 
print_gpx_trk_start(void)81 static void print_gpx_trk_start(void)
82 {
83     (void)fprintf(logfile," <trk>\n");
84     (void)fprintf(logfile,"  <src>GPSD %s</src>\n", VERSION);
85     (void)fprintf(logfile,"  <trkseg>\n");
86     (void)fflush(logfile);
87 }
88 
print_fix(struct gps_data_t * gpsdata,timespec_t ts_time)89 static void print_fix(struct gps_data_t *gpsdata, timespec_t ts_time)
90 {
91     char tbuf[CLIENT_DATE_MAX+1];
92 
93     /* lat/lon/ele are all WGS84, no altMSL */
94     (void)fprintf(logfile,"   <trkpt lat=\"%f\" lon=\"%f\">\n",
95 		 gpsdata->fix.latitude, gpsdata->fix.longitude);
96     if ((isfinite(gpsdata->fix.altHAE) != 0))
97 	(void)fprintf(logfile,"    <ele>%f</ele>\n", gpsdata->fix.altHAE);
98     (void)fprintf(logfile,"    <time>%s</time>\n",
99 		 timespec_to_iso8601(ts_time, tbuf, sizeof(tbuf)));
100     if (gpsdata->status == STATUS_DGPS_FIX)
101 	(void)fprintf(logfile,"    <fix>dgps</fix>\n");
102     else
103 	switch (gpsdata->fix.mode) {
104 	case MODE_3D:
105 	    (void)fprintf(logfile,"    <fix>3d</fix>\n");
106 	    break;
107 	case MODE_2D:
108 	    (void)fprintf(logfile,"    <fix>2d</fix>\n");
109 	    break;
110 	case MODE_NO_FIX:
111 	    (void)fprintf(logfile,"    <fix>none</fix>\n");
112 	    break;
113 	default:
114 	    /* don't print anything if no fix indicator */
115 	    break;
116 	}
117 
118     if ((gpsdata->fix.mode > MODE_NO_FIX) && (gpsdata->satellites_used > 0))
119 	(void)fprintf(logfile,"    <sat>%d</sat>\n", gpsdata->satellites_used);
120     if (isfinite(gpsdata->dop.hdop) != 0)
121 	(void)fprintf(logfile,"    <hdop>%.1f</hdop>\n", gpsdata->dop.hdop);
122     if (isfinite(gpsdata->dop.vdop) != 0)
123 	(void)fprintf(logfile,"    <vdop>%.1f</vdop>\n", gpsdata->dop.vdop);
124     if (isfinite(gpsdata->dop.pdop) != 0)
125 	(void)fprintf(logfile,"    <pdop>%.1f</pdop>\n", gpsdata->dop.pdop);
126 
127     (void)fprintf(logfile,"   </trkpt>\n");
128     (void)fflush(logfile);
129 }
130 
conditionally_log_fix(struct gps_data_t * gpsdata)131 static void conditionally_log_fix(struct gps_data_t *gpsdata)
132 {
133     static timespec_t ts_time, old_ts_time, ts_diff;
134     static double old_lat, old_lon;
135     static bool first = true;
136 
137     ts_time = gpsdata->fix.time;
138     if (TS_EQ(&ts_time, &old_ts_time) || gpsdata->fix.mode < MODE_2D)
139 	return;
140 
141     /* may not be worth logging if we've moved only a very short distance */
142     if (0 < minmove && !first && earth_distance(
143 					gpsdata->fix.latitude,
144 					gpsdata->fix.longitude,
145 					old_lat, old_lon) < minmove)
146 	return;
147 
148     /*
149      * Make new track if the jump in time is above
150      * timeout.  Handle jumps both forward and
151      * backwards in time.  The clock sometimes jumps
152      * backward when gpsd is submitting junk on the
153      * dbus.
154      */
155     TS_SUB(&ts_diff, &ts_time, &old_ts_time);
156     if (labs((long)ts_diff.tv_sec) > timeout && !first) {
157 	print_gpx_trk_end();
158 	intrack = false;
159     }
160 
161     if (!intrack) {
162 	print_gpx_trk_start();
163 	intrack = true;
164 	if (first)
165 	    first = false;
166     }
167 
168     old_ts_time = ts_time;
169     if (0 < minmove) {
170 	old_lat = gpsdata->fix.latitude;
171 	old_lon = gpsdata->fix.longitude;
172     }
173     print_fix(gpsdata, ts_time);
174 }
175 
quit_handler(int signum)176 static void quit_handler(int signum)
177 {
178     /* don't clutter the logs on Ctrl-C */
179     if (signum != SIGINT)
180 	syslog(LOG_INFO, "exiting, signal %d received", signum);
181     print_gpx_footer();
182     (void)gps_close(&gpsdata);
183     exit(EXIT_SUCCESS);
184 }
185 
186 /**************************************************************************
187  *
188  * Main sequence
189  *
190  **************************************************************************/
191 
usage(void)192 static void usage(void)
193 {
194     (void)fprintf(stderr,
195                   "Usage: %s [-V] [-h] [-l] [-d] [-D debuglevel]"
196                   " [-i timeout] [-f filename] [-m minmove]\n"
197                   "\t[-r] [-e exportmethod] [server[:port:[device]]]\n\n"
198                   "defaults to '%s -i 5 -e %s localhost:2947'\n",
199                   progname, progname, export_default()->name);
200     exit(EXIT_FAILURE);
201 }
202 
main(int argc,char ** argv)203 int main(int argc, char **argv)
204 {
205     int ch;
206     bool daemonize = false;
207     bool reconnect = false;
208     unsigned int flags = WATCH_ENABLE;
209     struct exportmethod_t *method = NULL;
210 
211     progname = argv[0];
212 
213     method = export_default();
214     if (method == NULL) {
215 	(void)fprintf(stderr, "%s: no export methods.\n", progname);
216 	exit(EXIT_FAILURE);
217     }
218 
219     logfile = stdout;
220     while ((ch = getopt(argc, argv, "dD:e:f:hi:lm:rV")) != -1) {
221 	switch (ch) {
222 	case 'd':
223 	    openlog(basename(progname), LOG_PID | LOG_PERROR, LOG_DAEMON);
224 	    daemonize = true;
225 	    break;
226 #ifdef CLIENTDEBUG_ENABLE
227 	case 'D':
228 	    debug = atoi(optarg);
229 	    gps_enable_debug(debug, logfile);
230 	    break;
231 #endif /* CLIENTDEBUG_ENABLE */
232 	case 'e':
233 	    method = export_lookup(optarg);
234 	    if (method == NULL) {
235 		(void)fprintf(stderr,
236 			      "%s: %s is not a known export method.\n",
237 			      progname, optarg);
238 		exit(EXIT_FAILURE);
239 	    }
240 	    break;
241        case 'f':       /* Output file name. */
242             {
243                 char   *fname = NULL;
244                 time_t  t;
245                 size_t  s = 0;
246                 size_t fnamesize = strlen(optarg) + 1;
247 
248                 t = time(NULL);
249                 while (s == 0) {
250 		    char *newfname = realloc(fname, fnamesize);
251 		    if (newfname == NULL) {
252 			syslog(LOG_ERR, "realloc failed.");
253 			goto bailout;
254 		    } else {
255 			fname = newfname;
256 		    }
257 		    s = strftime(fname, fnamesize-1, optarg, localtime(&t));
258 		    if (!s) {
259                         /* expanded filename did not fit in string, try
260                          * a bigger string */
261 			fnamesize += 1024;
262                     }
263                 }
264                 fname[s] = '\0';;
265                 logfile = fopen(fname, "w");
266                 if (logfile == NULL) {
267 		    syslog(LOG_ERR,
268 			   "Failed to open %s: %s, logging to stdout.",
269 			   fname, strerror(errno));
270 		    logfile = stdout;
271 		}
272 	    bailout:
273                 free(fname);
274                 break;
275             }
276 	case 'i':		/* set polling interval */
277 	    timeout = (time_t) atoi(optarg);
278 	    if (timeout < 1)
279 		timeout = 1;
280 	    if (timeout >= 3600)
281 		(void)fprintf(stderr,
282 		              "WARNING: track timeout is an hour or more!\n");
283 	    break;
284 	case 'l':
285 	    export_list(stderr);
286 	    exit(EXIT_SUCCESS);
287         case 'm':
288 	    minmove = (double )atoi(optarg);
289 	    break;
290         case 'r':
291 	    reconnect = true;
292 	    break;
293 	case 'V':
294 	    (void)fprintf(stderr, "%s: version %s (revision %s)\n",
295 			  progname, VERSION, REVISION);
296 	    exit(EXIT_SUCCESS);
297 	default:
298 	    usage();
299 	    /* NOTREACHED */
300 	}
301     }
302 
303     if (daemonize && logfile == stdout) {
304 	syslog(LOG_ERR, "Daemon mode with no valid logfile name - exiting.");
305 	exit(EXIT_FAILURE);
306     }
307 
308     if (method->magic != NULL) {
309 	source.server = (char *)method->magic;
310 	source.port = NULL;
311 	source.device = NULL;
312     } else {
313 	source.server = (char *)"localhost";
314 	source.port = (char *)DEFAULT_GPSD_PORT;
315 	source.device = NULL;
316     }
317 
318     if (optind < argc) {
319 	/* in this case, switch to the method "socket" always */
320 	gpsd_source_spec(argv[optind], &source);
321     }
322 #if 0
323     (void)fprintf(logfile,"<!-- server: %s port: %s  device: %s -->\n",
324 		 source.server, source.port, source.device);
325 #endif
326 
327     /* catch all interesting signals */
328     (void)signal(SIGTERM, quit_handler);
329     (void)signal(SIGQUIT, quit_handler);
330     (void)signal(SIGINT, quit_handler);
331 
332     /* might be time to daemonize */
333     if (daemonize) {
334 	/* not SuS/POSIX portable, but we have our own fallback version */
335 	if (os_daemon(0, 0) != 0)
336 	    (void) fprintf(stderr,"daemonization failed: %s\n", strerror(errno));
337     }
338 
339     //syslog (LOG_INFO, "---------- STARTED ----------");
340 
341     if (gps_open(source.server, source.port, &gpsdata) != 0) {
342 	(void)fprintf(stderr,
343 		      "%s: no gpsd running or network error: %d, %s\n",
344 		      progname, errno, gps_errstr(errno));
345 	exit(EXIT_FAILURE);
346     }
347 
348     if (source.device != NULL)
349 	flags |= WATCH_DEVICE;
350     (void)gps_stream(&gpsdata, flags, source.device);
351 
352     print_gpx_header();
353 
354     while (gps_mainloop(&gpsdata, timeout * 1000000, conditionally_log_fix) < 0 &&
355 	   reconnect) {
356 	/* avoid busy-calling gps_mainloop() */
357 	(void)sleep(timeout);
358 	syslog(LOG_INFO, "timeout; about to reconnect");
359     }
360 
361     print_gpx_footer();
362     (void)gps_close(&gpsdata);
363 
364     exit(EXIT_SUCCESS);
365 }
366