gpxlogger.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. /*
  2. * This file is Copyright 2010 by the GPSD project
  3. * SPDX-License-Identifier: BSD-2-clause
  4. */
  5. #include "../include/gpsd_config.h" // must be before all includes
  6. #include <assert.h>
  7. #include <errno.h>
  8. #include <libgen.h>
  9. #include <limits.h> // for PATH_MAX
  10. #include <math.h>
  11. #ifdef HAVE_GETOPT_LONG
  12. #include <getopt.h> // for getopt_long()
  13. #endif
  14. #include <signal.h>
  15. #include <stdbool.h>
  16. #include <stdio.h>
  17. #include <stdlib.h> // for atexit()
  18. #include <string.h>
  19. #include <time.h>
  20. #include <unistd.h> // for _exit()
  21. #include "../include/gps.h"
  22. #include "../include/gpsdclient.h"
  23. #include "../include/os_compat.h"
  24. #include "../include/timespec.h"
  25. static char *progname;
  26. static struct fixsource_t source;
  27. /**************************************************************************
  28. *
  29. * Transport-layer-independent functions
  30. *
  31. **************************************************************************/
  32. static struct gps_data_t gpsdata;
  33. static FILE *logfile;
  34. static bool intrack = false;
  35. static time_t timeout = 5; // seconds
  36. static double minmove = 0; // meters
  37. static int debug;
  38. static int sig_flag = 0;
  39. static void print_gpx_header(void)
  40. {
  41. char tbuf[CLIENT_DATE_MAX+1];
  42. (void)fprintf(logfile,
  43. "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
  44. "<gpx version=\"1.1\" creator=\"GPSD %s - %s\"\n"
  45. " xmlns:xsi=\"https://www.w3.org/2001/XMLSchema-instance\"\n"
  46. " xmlns=\"http://www.topografix.com/GPX/1/1\"\n"
  47. " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1\n"
  48. " http://www.topografix.com/GPX/1/1/gpx.xsd\">\n"
  49. " <metadata>\n"
  50. " <time>%s</time>\n"
  51. " </metadata>\n",
  52. VERSION, GPSD_URL,
  53. now_to_iso8601(tbuf, sizeof(tbuf)));
  54. (void)fflush(logfile);
  55. }
  56. static void print_gpx_trk_end(void)
  57. {
  58. (void)fputs(" </trkseg>\n"
  59. " </trk>\n", logfile);
  60. (void)fflush(logfile);
  61. }
  62. static void print_gpx_footer(void)
  63. {
  64. if (intrack) {
  65. print_gpx_trk_end();
  66. }
  67. (void)fputs("</gpx>\n", logfile);
  68. (void)fclose(logfile);
  69. }
  70. static void print_gpx_trk_start(void)
  71. {
  72. (void)fputs(" <trk>\n"
  73. " <src>GPSD " VERSION "</src>\n"
  74. " <trkseg>\n", logfile);
  75. (void)fflush(logfile);
  76. }
  77. static void print_fix(struct gps_data_t *gpsdata, timespec_t ts_time)
  78. {
  79. char tbuf[CLIENT_DATE_MAX + 1];
  80. (void)fprintf(logfile, " <trkpt lat=\"%.9f\" lon=\"%.9f\">\n",
  81. gpsdata->fix.latitude, gpsdata->fix.longitude);
  82. /*
  83. * From the specification at https://www.topografix.com/GPX/1/1/gpx.xsd
  84. * the <ele> tag is defined as "Elevation (in meters) of the point."
  85. * This is ambiguous between HAE and orthometric height (above geoid,
  86. * aka MSL).
  87. * gpsd has historically used HAE and MSL randomly for altitude.
  88. * gpsd now explicitly supports distinct HAE and MSL.
  89. */
  90. if (0 != isfinite(gpsdata->fix.altHAE)) {
  91. (void)fprintf(logfile, " <ele>%.4f</ele>\n", gpsdata->fix.altHAE);
  92. }
  93. (void)fprintf(logfile, " <time>%s</time>\n",
  94. timespec_to_iso8601(ts_time, tbuf, sizeof(tbuf)));
  95. if (STATUS_DGPS == gpsdata->fix.status) {
  96. // FIXME: other status values?
  97. (void)fputs(" <fix>dgps</fix>\n", logfile);
  98. } else {
  99. switch (gpsdata->fix.mode) {
  100. case MODE_3D:
  101. (void)fputs(" <fix>3d</fix>\n", logfile);
  102. break;
  103. case MODE_2D:
  104. (void)fputs(" <fix>2d</fix>\n", logfile);
  105. break;
  106. case MODE_NO_FIX:
  107. (void)fputs(" <fix>none</fix>\n", logfile);
  108. break;
  109. default:
  110. // don't print anything if no fix indicator
  111. break;
  112. }
  113. }
  114. if (MODE_NO_FIX < gpsdata->fix.mode &&
  115. 0 < gpsdata->satellites_used) {
  116. (void)fprintf(logfile, " <sat>%d</sat>\n", gpsdata->satellites_used);
  117. }
  118. if (0 != isfinite(gpsdata->dop.hdop)) {
  119. (void)fprintf(logfile, " <hdop>%.1f</hdop>\n", gpsdata->dop.hdop);
  120. }
  121. if (0 != isfinite(gpsdata->dop.vdop)) {
  122. (void)fprintf(logfile, " <vdop>%.1f</vdop>\n", gpsdata->dop.vdop);
  123. }
  124. if (0 != isfinite(gpsdata->dop.pdop)) {
  125. (void)fprintf(logfile, " <pdop>%.1f</pdop>\n", gpsdata->dop.pdop);
  126. }
  127. (void)fputs(" </trkpt>\n", logfile);
  128. (void)fflush(logfile);
  129. }
  130. // cleanup as an atexit() handler
  131. static void cleanup(void)
  132. {
  133. print_gpx_footer();
  134. (void)gps_close(&gpsdata);
  135. // don't clutter the logs on Ctrl-C
  136. if (0 != sig_flag &&
  137. SIGINT != sig_flag) {
  138. syslog(LOG_INFO, "exiting, signal %d received", sig_flag);
  139. }
  140. }
  141. static void conditionally_log_fix(struct gps_data_t *gpsdata)
  142. {
  143. static timespec_t ts_time, old_ts_time, ts_diff;
  144. static double old_lat, old_lon;
  145. static bool first = true;
  146. if (0 != sig_flag) {
  147. if (SIGINT != sig_flag) {
  148. exit(EXIT_FAILURE);
  149. }
  150. exit(EXIT_SUCCESS);
  151. }
  152. // FIXME: check for good time?
  153. ts_time = gpsdata->fix.time;
  154. if (TS_EQ(&ts_time, &old_ts_time) ||
  155. MODE_2D > gpsdata->fix.mode) {
  156. return;
  157. }
  158. // may not be worth logging if we've moved only a very short distance
  159. if (0 < minmove &&
  160. !first &&
  161. earth_distance(gpsdata->fix.latitude, gpsdata->fix.longitude,
  162. old_lat, old_lon) < minmove) {
  163. return;
  164. }
  165. /*
  166. * Make new track if the jump in time is above
  167. * timeout. Handle jumps both forward and
  168. * backwards in time. The clock sometimes jumps
  169. * backward when gpsd is submitting junk on the
  170. * dbus.
  171. */
  172. TS_SUB(&ts_diff, &ts_time, &old_ts_time);
  173. if (labs((long)ts_diff.tv_sec) > timeout &&
  174. !first) {
  175. print_gpx_trk_end();
  176. intrack = false;
  177. }
  178. if (!intrack) {
  179. print_gpx_trk_start();
  180. intrack = true;
  181. if (first) {
  182. first = false;
  183. }
  184. }
  185. old_ts_time = ts_time;
  186. if (0 < minmove) {
  187. old_lat = gpsdata->fix.latitude;
  188. old_lon = gpsdata->fix.longitude;
  189. }
  190. print_fix(gpsdata, ts_time);
  191. }
  192. static void quit_handler(int signum)
  193. {
  194. // CWE-479: Signal Handler Use of a Non-reentrant Function
  195. // See: The C Standard, 7.14.1.1, paragraph 5 [ISO/IEC 9899:2011]
  196. // Can't log in a signal handler. Can't even call exit().
  197. sig_flag = signum;
  198. return;
  199. }
  200. /**************************************************************************
  201. *
  202. * Main sequence
  203. *
  204. **************************************************************************/
  205. static void usage(void)
  206. {
  207. (void)fprintf(stderr,
  208. "Usage: %s [OPTIONS] [server[:port:[device]]]\n\n"
  209. " -? Show this help, then exit\n"
  210. #ifdef HAVE_GETOPT_LONG
  211. " --daemonize Daemonize\n"
  212. " --debug LVL Set debug level.\n"
  213. " --export EXPORTMETHOD Default %s\n"
  214. " --exports List available exports, then exit\n"
  215. " --help Show this help, then exit\n"
  216. " --interval TIMEOUT Create new track after TIMEOUT seconds. "
  217. "Default 5\n"
  218. " --minmove MINMOVE Minimum move in meters to log\n"
  219. " --output FILNAME Send output to file FILENAME\n"
  220. " --reconnect Retry when gpsd loses the fix.\n"
  221. " --version Show version, then exit\n"
  222. #endif
  223. " -D LVL Set debug level.\n"
  224. " -d Daemonize\n"
  225. " -e EXPORTMETHOD Default %s \n"
  226. " -f FILENAME Send output to file FILENAME\n"
  227. " -h Show this help, then exit\n"
  228. " -i TIMEOUT Create new track after TIMEOUT seconds. "
  229. "Default 5\n"
  230. " -l List available exports, then exit\n"
  231. " -m MINMOVE Minimum move in meters to log\n"
  232. " -r Retry when gpsd loses the fix.\n"
  233. " -V Show version and exit\n",
  234. progname,
  235. #ifdef HAVE_GETOPT_LONG
  236. export_default()->name,
  237. #endif
  238. export_default()->name);
  239. exit(EXIT_FAILURE);
  240. }
  241. int main(int argc, char **argv)
  242. {
  243. int ch;
  244. bool daemonize = false;
  245. bool reconnect = false;
  246. unsigned int flags = WATCH_ENABLE;
  247. struct exportmethod_t *method = NULL;
  248. const char *optstring = "?dD:e:f:hi:lm:rV";
  249. #ifdef HAVE_GETOPT_LONG
  250. int option_index = 0;
  251. static struct option long_options[] = {
  252. {"daemonize", no_argument, NULL, 'd'},
  253. {"debug", required_argument, NULL, 'D'},
  254. {"export", required_argument, NULL, 'e'},
  255. {"exports", no_argument, NULL, 'l'},
  256. {"help", no_argument, NULL, 'h'},
  257. {"interval", required_argument, NULL, 'i'},
  258. {"minmove", required_argument, NULL, 'm'},
  259. {"output", required_argument, NULL, 'f'},
  260. {"reconnect", no_argument, NULL, 'r' },
  261. {"version", no_argument, NULL, 'V' },
  262. {NULL, 0, NULL, 0},
  263. };
  264. #endif
  265. progname = argv[0];
  266. method = export_default();
  267. if (NULL == method) {
  268. (void)fprintf(stderr, "%s: no export methods.\n", progname);
  269. exit(EXIT_FAILURE);
  270. }
  271. logfile = stdout;
  272. while (1) {
  273. #ifdef HAVE_GETOPT_LONG
  274. ch = getopt_long(argc, argv, optstring, long_options, &option_index);
  275. #else
  276. ch = getopt(argc, argv, optstring);
  277. #endif
  278. if (-1 == ch) {
  279. break;
  280. }
  281. switch (ch) {
  282. case 'd':
  283. openlog(basename(progname), LOG_PID | LOG_PERROR, LOG_DAEMON);
  284. daemonize = true;
  285. break;
  286. case 'D':
  287. debug = atoi(optarg);
  288. gps_enable_debug(debug, logfile);
  289. break;
  290. case 'e':
  291. method = export_lookup(optarg);
  292. if (NULL == method) {
  293. (void)fprintf(stderr,
  294. "%s: %s is not a known export method.\n",
  295. progname, optarg);
  296. exit(EXIT_FAILURE);
  297. }
  298. break;
  299. case 'f': // Output file name.
  300. {
  301. char *fname = NULL;
  302. time_t t;
  303. size_t s = 0;
  304. size_t fnamesize = strnlen(optarg, PATH_MAX) + 128;
  305. t = time(NULL);
  306. while (0 == s) {
  307. char *newfname = realloc(fname, fnamesize);
  308. if (NULL == newfname) {
  309. syslog(LOG_ERR, "realloc failed.");
  310. goto bailout;
  311. } else {
  312. fname = newfname;
  313. }
  314. s = strftime(fname, fnamesize-1, optarg, localtime(&t));
  315. if (!s) {
  316. /* expanded filename did not fit in string, try
  317. * a bigger string */
  318. fnamesize += 1024;
  319. }
  320. }
  321. fname[s] = '\0';;
  322. logfile = fopen(fname, "w");
  323. if (NULL == logfile) {
  324. syslog(LOG_ERR,
  325. "Failed to open %s: %s, logging to stdout.",
  326. fname, strerror(errno));
  327. logfile = stdout;
  328. }
  329. bailout:
  330. free(fname);
  331. break;
  332. }
  333. case 'i': // set polling interval
  334. timeout = (time_t)atoi(optarg);
  335. if (1 > timeout) {
  336. timeout = 1;
  337. } else if (3600 <= timeout) {
  338. (void)fputs("WARNING: track timeout is an hour or more!\n",
  339. stderr);
  340. }
  341. break;
  342. case 'l':
  343. export_list(stderr);
  344. exit(EXIT_SUCCESS);
  345. case 'm':
  346. minmove = (double )atoi(optarg);
  347. break;
  348. case 'r':
  349. reconnect = true;
  350. break;
  351. case 'V':
  352. (void)fprintf(stderr, "%s: version %s (revision %s)\n",
  353. progname, VERSION, REVISION);
  354. exit(EXIT_SUCCESS);
  355. case '?':
  356. case 'h':
  357. default:
  358. usage();
  359. // NOTREACHED
  360. }
  361. }
  362. if (daemonize &&
  363. stdout == logfile) {
  364. syslog(LOG_ERR, "Daemon mode with no valid logfile name - exiting.");
  365. exit(EXIT_FAILURE);
  366. }
  367. if (NULL != method->magic) {
  368. source.server = (char *)method->magic;
  369. source.port = NULL;
  370. source.device = NULL;
  371. } else {
  372. source.server = (char *)"localhost";
  373. source.port = (char *)DEFAULT_GPSD_PORT;
  374. source.device = NULL;
  375. }
  376. if (optind < argc) {
  377. // in this case, switch to the method "socket" always
  378. gpsd_source_spec(argv[optind], &source);
  379. }
  380. #if 0
  381. (void)fprintf(logfile, "<!-- server: %s port: %s device: %s -->\n",
  382. source.server, source.port, source.device);
  383. #endif
  384. // catch all interesting signals
  385. (void)signal(SIGTERM, quit_handler);
  386. (void)signal(SIGQUIT, quit_handler);
  387. (void)signal(SIGINT, quit_handler);
  388. // might be time to daemonize
  389. if (daemonize) {
  390. errno = 0;
  391. // not SuS/POSIX portable, but we have our own fallback version
  392. if (0 != os_daemon(0, 0)) {
  393. (void)fprintf(stderr, "daemonization failed: %s(%d)\n",
  394. strerror(errno), errno);
  395. }
  396. }
  397. //syslog (LOG_INFO, "---------- STARTED ----------");
  398. if (0 != gps_open(source.server, source.port, &gpsdata)) {
  399. (void)fprintf(stderr,
  400. "%s: no gpsd running or network error: %d, %s\n",
  401. progname, errno, gps_errstr(errno));
  402. exit(EXIT_FAILURE);
  403. }
  404. if (NULL != source.device) {
  405. flags |= WATCH_DEVICE;
  406. }
  407. if (NULL != source.port) {
  408. // only to sockets, not shared memory or dbus
  409. if (0 > gps_stream(&gpsdata, flags, source.device)) {
  410. syslog(LOG_ERR, "gps_stream() failed");
  411. exit(EXIT_FAILURE);
  412. }
  413. }
  414. print_gpx_header();
  415. // make sure footer added on exit
  416. if (0 != atexit(cleanup)) {
  417. syslog(LOG_ERR, "atexit() failed");
  418. exit(EXIT_FAILURE);
  419. }
  420. while (0 > gps_mainloop(&gpsdata, timeout * 1000000,
  421. conditionally_log_fix)) {
  422. // fell out of mainloop, some sort of error, or just a timeout
  423. if (!reconnect || 0 != sig_flag) {
  424. // give up
  425. break;
  426. }
  427. // avoid banging on reconnect
  428. (void)sleep(timeout);
  429. syslog(LOG_INFO, "timeout; about to reconnect");
  430. }
  431. if (0 != sig_flag &&
  432. SIGINT != sig_flag) {
  433. exit(EXIT_FAILURE);
  434. }
  435. exit(EXIT_SUCCESS);
  436. }
  437. // vim: set expandtab shiftwidth=4