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