2 * The generic GPS packet monitor.
4 * This file is Copyright (c) 2010 by the GPSD project
5 * BSD terms apply: see the file COPYING in the distribution root for details.
15 #endif /* S_SPLINT_S */
19 /* Cygwin has only _timezone and not timezone unless the following is set */
20 #if defined(__CYGWIN__)
22 #endif /* defined(__CYGWIN__) */
25 #include <fcntl.h> /* for O_RDWR */
29 #include <sys/ioctl.h> /* for O_RDWR */
32 #include "gpsd_config.h"
35 #include <bluetooth/bluetooth.h>
42 #endif /* HAVE_NCURSES_H */
47 #if defined(HAVE_SYS_TIME_H)
50 #if defined (HAVE_SYS_SELECT_H)
51 #include <sys/select.h>
54 #include "gpsdclient.h"
59 extern struct tm *localtime_r(const time_t *, /*@out@*/ struct tm *tp);
60 #endif /* S_SPLINT_S */
64 /* external capability tables */
65 extern struct monitor_object_t nmea_mmt, sirf_mmt, garmin_mmt, ashtech_mmt;
66 extern struct monitor_object_t italk_mmt, ubx_mmt, superstar2_mmt;
67 extern struct monitor_object_t fv18_mmt, gpsclock_mmt, mtk3301_mmt;
68 extern struct monitor_object_t oncore_mmt, tnt_mmt;
70 /* These are public */
71 struct gps_device_t session;
75 /* These are private */
76 static struct gps_context_t context;
77 static int controlfd = -1;
78 static bool serial, curses_active;
79 static int debuglevel = 0;
80 static WINDOW *statwin, *cmdwin;
81 /*@null@*/ static WINDOW *packetwin;
83 static char *type_name;
85 static const struct monitor_object_t *monitor_objects[] = {
88 #if defined(GARMIN_ENABLE) && defined(NMEA_ENABLE)
90 #endif /* GARMIN_ENABLE && NMEA_ENABLE */
93 #endif /* ASHTECH_ENABLE */
96 #endif /* FV18_ENABLE */
97 #ifdef GPSCLOCK_ENABLE
99 #endif /* GPSCLOCK_ENABLE */
100 #ifdef MTK3301_ENABLE
102 #endif /* MTK3301_ENABLE */
103 #endif /* NMEA_ENABLE */
104 #if defined(SIRF_ENABLE) && defined(BINARY_ENABLE)
106 #endif /* defined(SIRF_ENABLE) && defined(BINARY_ENABLE) */
107 #if defined(UBX_ENABLE) && defined(BINARY_ENABLE)
109 #endif /* defined(UBX_ENABLE) && defined(BINARY_ENABLE) */
110 #if defined(ITRAX_ENABLE) && defined(BINARY_ENABLE)
112 #endif /* defined(ITALK_ENABLE) && defined(BINARY_ENABLE) */
113 #if defined(SUPERSTAR2_ENABLE) && defined(BINARY_ENABLE)
115 #endif /* defined(SUPERSTAR2_ENABLE) && defined(BINARY_ENABLE) */
116 #if defined(ONCORE_ENABLE) && defined(BINARY_ENABLE)
118 #endif /* defined(ONCORE_ENABLE) && defined(BINARY_ENABLE) */
121 #endif /* TNT_ENABLE */
125 static const struct monitor_object_t **active;
128 static jmp_buf terminate;
130 #define display (void)mvwprintw
132 /* ternination codes */
133 #define TERM_SELECT_FAILED 1
134 #define TERM_DRIVER_SWITCH 2
135 #define TERM_EMPTY_READ 3
136 #define TERM_READ_ERROR 4
138 void monitor_fixframe(WINDOW * win)
140 int ymax, xmax, ycur, xcur;
143 getyx(win, ycur, xcur);
144 getmaxyx(win, ymax, xmax);
145 (void)mvwaddch(win, ycur, xmax - 1, ACS_VLINE);
148 /******************************************************************************
150 * Device-independent I/O routines
152 ******************************************************************************/
154 void gpsd_report(int errlevel UNUSED, const char *fmt, ...)
155 /* our version of the logger */
157 if (errlevel <= debuglevel && packetwin != NULL) {
161 (void)vprintf(fmt, ap);
163 (void)wprintw(packetwin, (char *)fmt, ap);
169 static ssize_t readpkt(void)
171 /*@ -type -shiftnegative -compdef -nullpass @*/
172 struct timeval timeval;
176 FD_ZERO(&select_set);
177 FD_SET(session.gpsdata.gps_fd, &select_set);
179 FD_SET(controlfd, &select_set);
182 if (select(session.gpsdata.gps_fd + 1, &select_set, NULL, NULL, &timeval)
184 longjmp(terminate, TERM_SELECT_FAILED);
186 if (!FD_ISSET(session.gpsdata.gps_fd, &select_set))
187 longjmp(terminate, TERM_SELECT_FAILED);
189 changed = gpsd_poll(&session);
191 longjmp(terminate, TERM_EMPTY_READ);
193 if ((changed & ERROR_IS) != 0)
194 longjmp(terminate, TERM_READ_ERROR);
196 if (logfile != NULL) {
197 /*@ -shiftimplementation -sefparams +charint @*/
199 (session.packet.outbuffer, sizeof(char),
200 session.packet.outbuflen, logfile) >= 1);
201 /*@ +shiftimplementation +sefparams -charint @*/
203 return session.packet.outbuflen;
204 /*@ +type +shiftnegative +compdef +nullpass @*/
209 static void packet_dump(char *buf, size_t buflen)
211 if (packetwin != NULL) {
213 bool printable = true;
214 for (i = 0; i < buflen; i++)
215 if (!isprint(buf[i]) && !isspace(buf[i]))
218 for (i = 0; i < buflen; i++)
220 (void)waddch(packetwin, (chtype) buf[i]);
222 (void)wprintw(packetwin, "\\x%02x",
223 (unsigned char)buf[i]);
225 for (i = 0; i < buflen; i++)
226 (void)wprintw(packetwin, "%02x", (unsigned char)buf[i]);
228 (void)wprintw(packetwin, "\n");
232 #if defined(ALLOW_CONTROLSEND) || defined(ALLOW_RECONFIGURE)
233 static void monitor_dump_send(void)
235 if (packetwin != NULL) {
236 (void)wattrset(packetwin, A_BOLD);
237 (void)wprintw(packetwin, ">>>");
238 packet_dump(session.msgbuf, session.msgbuflen);
239 (void)wattrset(packetwin, A_NORMAL);
242 #endif /* defined(ALLOW_CONTROLSEND) || defined(ALLOW_RECONFIGURE) */
244 #ifdef ALLOW_CONTROLSEND
245 bool monitor_control_send( /*@in@*/ unsigned char *buf, size_t len)
255 assert(write(controlfd, "!", 1) != -1);
257 (controlfd, session.gpsdata.dev.path,
258 strlen(session.gpsdata.dev.path)) != -1);
259 assert(write(controlfd, "=", 1) != -1);
262 * Ugh...temporarily con the libgpsd layer into using the
265 savefd = session.gpsdata.gps_fd;
266 session.gpsdata.gps_fd = controlfd;
269 st = (*active)->driver->control_send(&session, (char *)buf, len);
272 /* stop pretending now */
273 session.gpsdata.gps_fd = controlfd;
274 /* enough room for "ERROR\r\n\0" */
276 assert(read(controlfd, buf, 8) != -1);
284 static bool monitor_raw_send( /*@in@*/ unsigned char *buf, size_t len)
293 assert(write(controlfd, "!", 1) != -1);
294 assert(write(controlfd, session.gpsdata.dev.path,
295 strlen(session.gpsdata.dev.path)) != -1);
296 assert(write(controlfd, "=", 1) != -1);
300 st = write(controlfd, (char *)buf, len);
303 /* enough room for "ERROR\r\n\0" */
305 assert(read(controlfd, buf, 8) != -1);
308 (void)memcpy(session.msgbuf, buf, len);
309 session.msgbuflen = len;
311 return (st > 0 && (size_t) st == len);
314 #endif /* ALLOW_CONTROLSEND */
316 /*****************************************************************************
318 * Main sequence and display machinery
320 *****************************************************************************/
322 static long tzoffset(void)
324 time_t now = time(NULL);
332 res = localtime_r(&now, &tm)->tm_gmtoff;
335 if (daylight != 0 && localtime_r(&now, &tm)->tm_isdst != 0)
338 if (localtime_r(&now, &tm)->tm_isdst)
344 void monitor_complain(const char *fmt, ...)
347 (void)wmove(cmdwin, 0, (int)strlen(type_name) + 2);
348 (void)wclrtoeol(cmdwin);
349 (void)wattrset(cmdwin, A_BOLD | A_BLINK);
351 (void)vwprintw(cmdwin, (char *)fmt, ap);
353 (void)wattrset(cmdwin, A_NORMAL);
354 (void)wrefresh(cmdwin);
355 (void)wgetch(cmdwin);
359 void monitor_log(const char *fmt, ...)
361 if (packetwin != NULL) {
364 (void)vwprintw(packetwin, (char *)fmt, ap);
369 static bool switch_type(const struct gps_type_t *devtype)
371 const struct monitor_object_t **trial, **newobject;
373 for (trial = monitor_objects; *trial; trial++)
374 if ((*trial)->driver == devtype)
378 if (LINES < (*newobject)->min_y + 1 || COLS < (*newobject)->min_x) {
379 monitor_complain("New type requires %dx%d screen",
380 (*newobject)->min_x, (*newobject)->min_y + 1);
382 if (active != NULL) {
384 (void)delwin(devicewin);
387 devicewin = newwin((*active)->min_y, (*active)->min_x, 1, 0);
388 if ((devicewin == NULL) || !(*active)->initialize()) {
389 monitor_complain("Internal initialization failure - screen "
390 "must be at least 80x24. aborting.");
395 leftover = LINES - 1 - (*active)->min_y;
397 if (packetwin != NULL)
398 (void)delwin(packetwin);
400 } else if (packetwin == NULL) {
401 packetwin = newwin(leftover, COLS, (*active)->min_y + 1, 0);
402 (void)scrollok(packetwin, true);
403 (void)wsetscrreg(packetwin, 0, leftover - 1);
405 (void)wresize(packetwin, leftover, COLS);
406 (void)mvwin(packetwin, (*active)->min_y + 1, 0);
407 (void)wsetscrreg(packetwin, 0, leftover - 1);
414 monitor_complain("No matching monitor type.");
418 static jmp_buf assertbuf;
420 static void onsig(int sig UNUSED)
422 longjmp(assertbuf, 1);
425 int main(int argc, char **argv)
427 #if defined(ALLOW_CONTROLSEND) || defined(ALLOW_RECONFIGURE)
429 #endif /* defined(ALLOW_CONTROLSEND) || defined(ALLOW_RECONFIGURE) */
430 int option, status, last_type = BAD_PACKET;
432 struct fixsource_t source;
433 char *p, *controlsock = "/var/run/gpsd.sock";
435 unsigned char buf[BUFLEN];
436 char line[80], *explanation;
439 gmt_offset = (int)tzoffset();
440 /*@ -observertrans @*/
441 (void)putenv("TZ=GMT"); // for ctime()
442 /*@ +observertrans @*/
444 while ((option = getopt(argc, argv, "D:F:LVhl:")) != -1) {
447 debuglevel = atoi(optarg);
450 controlsock = optarg;
453 (void)printf("gpsmon: %s (revision %s)\n", VERSION, REVISION);
455 case 'L': /* list known device types */
458 ("General commands available per type. '+' means there are private commands.\n",
460 for (active = monitor_objects; *active; active++) {
461 (void)fputs("i l q ^S ^Q", stdout);
462 (void)fputc(' ', stdout);
463 #ifdef ALLOW_RECONFIGURE
464 if ((*active)->driver->mode_switcher != NULL)
465 (void)fputc('n', stdout);
467 (void)fputc(' ', stdout);
468 (void)fputc(' ', stdout);
469 if ((*active)->driver->speed_switcher != NULL)
470 (void)fputc('s', stdout);
472 (void)fputc(' ', stdout);
473 (void)fputc(' ', stdout);
474 if ((*active)->driver->rate_switcher != NULL)
475 (void)fputc('x', stdout);
477 (void)fputc(' ', stdout);
478 (void)fputc(' ', stdout);
479 #endif /* ALLOW_RECONFIGURE */
480 #ifdef ALLOW_CONTROLSEND
481 if ((*active)->driver->control_send != NULL)
482 (void)fputc('x', stdout);
484 (void)fputc(' ', stdout);
485 #endif /* ALLOW_CONTROLSEND */
486 (void)fputc(' ', stdout);
487 if ((*active)->command != NULL)
488 (void)fputc('+', stdout);
490 (void)fputc(' ', stdout);
491 (void)fputs("\t", stdout);
492 (void)fputs((*active)->driver->type_name, stdout);
493 (void)fputc('\n', stdout);
496 case 'l': /* enable logging at startup */
497 logfile = fopen(optarg, "w");
498 if (logfile == NULL) {
499 (void)fprintf(stderr, "Couldn't open logfile for writing.\n");
508 ("usage: gpsmon [-?hVl] [-D debuglevel] [-F controlsock] [server[:port:[device]]]\n",
516 gpsd_source_spec(argv[optind], &source);
518 gpsd_source_spec(NULL, &source);
520 gpsd_init(&session, &context, NULL);
523 if ((optind >= argc || source.device == NULL
524 || strchr(argv[optind], ':') != NULL)
526 && bachk(argv[optind])) {
530 (void)gps_open_r(source.server, source.port, &session.gpsdata);
531 if (session.gpsdata.gps_fd < 0) {
532 (void)fprintf(stderr,
533 "%s: connection failure on %s:%s, error %d = %s.\n",
534 argv[0], source.server, source.port,
535 session.gpsdata.gps_fd,
536 netlib_errstr(session.gpsdata.gps_fd));
539 controlfd = open(controlsock, O_RDWR);
540 if (source.device != NULL)
541 (void)gps_send(&session.gpsdata,
542 "?WATCH={\"raw\":2,\"device\":\"%s\"}\r\n",
545 (void)gps_send(&session.gpsdata, "?WATCH={\"raw\":2}\r\n");
548 (void)strlcpy(session.gpsdata.dev.path, argv[optind],
549 sizeof(session.gpsdata.dev.path));
550 if (gpsd_activate(&session) == -1) {
551 gpsd_report(LOG_ERROR,
552 "activation of device %s failed, errno=%d\n",
553 session.gpsdata.dev.path, errno);
557 controlfd = session.gpsdata.gps_fd;
561 /*@ +nullpass +branchstate @*/
564 * This is a monitoring utility. Disable autoprobing, because
565 * in some cases (e.g. SiRFs) there is no way to probe a chip
566 * type without flipping it to native mode.
568 context.readonly = true;
570 /* quit cleanly if an assertion fails */
571 (void)signal(SIGABRT, onsig);
572 if (setjmp(assertbuf) > 0) {
574 (void)fclose(logfile);
576 (void)fputs("gpsmon: assertion failure, probable I/O error\n",
584 (void)intrflush(stdscr, FALSE);
585 (void)keypad(stdscr, true);
586 curses_active = true;
588 #define CMDWINHEIGHT 1
591 statwin = newwin(CMDWINHEIGHT, 30, 0, 0);
592 cmdwin = newwin(CMDWINHEIGHT, 0, 0, 30);
593 packetwin = newwin(0, 0, CMDWINHEIGHT, 0);
594 if (statwin == NULL || cmdwin == NULL || packetwin == NULL)
596 (void)scrollok(packetwin, true);
597 (void)wsetscrreg(packetwin, 0, LINES - CMDWINHEIGHT);
600 (void)wmove(packetwin, 0, 0);
602 FD_ZERO(&select_set);
605 if ((bailout = setjmp(terminate)) == 0) {
606 /*@ -observertrans @*/
610 session.device_type ? session.device_type->type_name : "Unknown device";
612 (void)wattrset(statwin, A_BOLD);
614 display(statwin, 0, 0, "%s %4d %c %d",
615 session.gpsdata.dev.path,
616 gpsd_get_speed(&session.ttyset),
617 session.gpsdata.dev.parity,
618 session.gpsdata.dev.stopbits);
621 display(statwin, 0, 0, "%s:%s:%s",
622 source.server, source.port, session.gpsdata.dev.path);
624 (void)wattrset(statwin, A_NORMAL);
625 (void)wmove(cmdwin, 0, 0);
627 /* get a packet -- calls gpsd_poll() */
628 if ((len = readpkt()) > 0 && session.packet.outbuflen > 0) {
629 /* switch types on packet receipt */
631 if (session.packet.type != last_type) {
632 last_type = session.packet.type;
633 if (!switch_type(session.device_type))
634 longjmp(terminate, TERM_DRIVER_SWITCH);
638 /* refresh all windows */
639 (void)wprintw(cmdwin, type_name);
640 (void)wprintw(cmdwin, "> ");
641 (void)wclrtoeol(cmdwin);
642 if (active != NULL && len > 0 && session.packet.outbuflen > 0)
644 (void)wprintw(packetwin, "(%d) ", session.packet.outbuflen);
645 packet_dump((char *)session.packet.outbuffer,
646 session.packet.outbuflen);
647 (void)wnoutrefresh(statwin);
648 (void)wnoutrefresh(cmdwin);
649 if (devicewin != NULL)
650 (void)wnoutrefresh(devicewin);
651 if (packetwin != NULL)
652 (void)wnoutrefresh(packetwin);
656 /* rest of this invoked only if user has pressed a key */
657 FD_SET(0, &select_set);
658 FD_SET(session.gpsdata.gps_fd, &select_set);
660 if (select(FD_SETSIZE, &select_set, NULL, NULL, NULL) == -1)
663 if (FD_ISSET(0, &select_set)) {
665 (void)wmove(cmdwin, 0, (int)strlen(type_name) + 2);
666 (void)wrefresh(cmdwin);
668 /*@ -usedef -compdef @*/
669 (void)wgetnstr(cmdwin, line, 80);
671 if (packetwin != NULL)
672 (void)wrefresh(packetwin);
673 (void)wrefresh(cmdwin);
675 if ((p = strchr(line, '\r')) != NULL)
680 /*@ +usedef +compdef @*/
683 if (isspace(line[1])) {
684 for (arg = line + 2; *arg != '\0' && isspace(*arg); arg++)
690 if (active != NULL && (*active)->command != NULL) {
691 status = (*active)->command(line);
692 if (status == COMMAND_TERMINATE)
694 else if (status == COMMAND_MATCH)
696 assert(status == COMMAND_UNKNOWN);
699 #ifdef ALLOW_RECONFIGURE
700 case 'c': /* change cycle time */
702 monitor_complain("No device defined yet");
704 double rate = strtod(arg, NULL);
705 /* Ugh...should have a controlfd slot
706 * in the session structure, really
708 if ((*active)->driver->rate_switcher) {
709 int dfd = session.gpsdata.gps_fd;
710 session.gpsdata.gps_fd = controlfd;
712 if ((*active)->driver->rate_switcher(&session, rate)) {
715 monitor_complain("Rate not supported.");
717 session.gpsdata.gps_fd = dfd;
720 ("Device type has no rate switcher");
725 (session.gpsdata.gps_fd, line,
726 strlen(line)) != -1);
727 /* discard response */
728 assert(read(session.gpsdata.gps_fd, buf, sizeof(buf))
733 #endif /* ALLOW_RECONFIGURE */
735 case 'i': /* start probing for subtype */
737 monitor_complain("No GPS type detected.");
739 if (strcspn(line, "01") == strlen(line))
740 context.readonly = !context.readonly;
742 context.readonly = (atoi(line + 1) == 0);
744 (void)gpsd_switch_driver(&session,
745 (*active)->driver->type_name);
750 case 'l': /* open logfile */
751 if (logfile != NULL) {
752 if (packetwin != NULL)
753 (void)wprintw(packetwin,
754 ">>> Logging to %s off", logfile);
755 (void)fclose(logfile);
758 if ((logfile = fopen(line + 1, "a")) != NULL)
759 if (packetwin != NULL)
760 (void)wprintw(packetwin,
761 ">>> Logging to %s on", logfile);
764 #ifdef ALLOW_RECONFIGURE
765 case 'n': /* change mode */
766 /* if argument not specified, toggle */
767 if (strcspn(line, "01") == strlen(line)) {
769 v = (unsigned int)TEXTUAL_PACKET_TYPE(
770 session.packet.type);
773 v = (unsigned)atoi(line + 1);
775 monitor_complain("No device defined yet");
777 /* Ugh...should have a controlfd slot
778 * in the session structure, really
780 if ((*active)->driver->mode_switcher) {
781 int dfd = session.gpsdata.gps_fd;
782 session.gpsdata.gps_fd = controlfd;
783 (*active)->driver->mode_switcher(&session,
786 (void)tcdrain(session.gpsdata.gps_fd);
788 session.gpsdata.gps_fd = dfd;
791 ("Device type has no mode switcher");
798 (session.gpsdata.gps_fd, line,
799 strlen(line)) != -1);
800 /* discard response */
801 assert(read(session.gpsdata.gps_fd, buf, sizeof(buf))
806 #endif /* ALLOW_RECONFIGURE */
811 #ifdef ALLOW_RECONFIGURE
812 case 's': /* change speed */
814 monitor_complain("No device defined yet");
817 char parity = session.gpsdata.dev.parity;
818 unsigned int stopbits =
819 (unsigned int)session.gpsdata.dev.stopbits;
822 modespec = strchr(arg, ':');
824 if (modespec != NULL) {
825 if (strchr("78", *++modespec) == NULL) {
827 ("No support for that word length.");
830 parity = *++modespec;
831 if (strchr("NOE", parity) == NULL) {
832 monitor_complain("What parity is '%c'?.",
836 stopbits = (unsigned int)*++modespec;
837 if (strchr("12", (char)stopbits) == NULL) {
838 monitor_complain("Stop bits must be 1 or 2.");
841 stopbits = (unsigned int)(stopbits - '0');
844 speed = (unsigned)atoi(arg);
845 /* Ugh...should have a controlfd slot
846 * in the session structure, really
849 if ((*active)->driver->speed_switcher) {
850 int dfd = session.gpsdata.gps_fd;
851 session.gpsdata.gps_fd = controlfd;
853 driver->speed_switcher(&session, speed,
858 * See the comment attached to the 'B'
859 * command in gpsd. Allow the control
860 * string time to register at the GPS
861 * before we do the baud rate switch,
862 * which effectively trashes the UART's
865 (void)tcdrain(session.gpsdata.gps_fd);
867 (void)gpsd_set_speed(&session, speed,
871 ("Speed/mode combination not supported.");
872 session.gpsdata.gps_fd = dfd;
875 ("Device type has no speed switcher");
881 (session.gpsdata.gps_fd, line,
882 strlen(line)) != -1);
883 /* discard response */
884 assert(read(session.gpsdata.gps_fd, buf, sizeof(buf))
889 #endif /* ALLOW_RECONFIGURE */
891 case 't': /* force device type */
892 if (strlen(arg) > 0) {
894 const struct gps_type_t **dp, *forcetype = NULL;
895 for (dp = gpsd_drivers; *dp; dp++) {
896 if (strstr((*dp)->type_name, arg) != NULL) {
901 if (matchcount == 0) {
903 ("No driver type matches '%s'.", arg);
904 } else if (matchcount == 1) {
905 assert(forcetype != NULL);
907 if (switch_type(forcetype))
908 (void)gpsd_switch_driver(&session,
909 forcetype->type_name);
913 ("Multiple driver type names match '%s'.",
919 #ifdef ALLOW_CONTROLSEND
920 case 'x': /* send control packet */
922 monitor_complain("No device defined yet");
925 int st = gpsd_hexpack(arg, (char *)buf, strlen(arg));
928 ("Invalid hex string (error %d)", st);
929 else if ((*active)->driver->control_send == NULL)
931 ("Device type has no control-send method.");
932 else if (!monitor_control_send(buf, (size_t) st))
933 monitor_complain("Control send failed.");
938 case 'X': /* send raw packet */
941 (ssize_t) gpsd_hexpack(arg, (char *)buf, strlen(arg));
943 monitor_complain("Invalid hex string (error %d)",
945 else if (!monitor_raw_send(buf, (size_t) len))
946 monitor_complain("Raw send failed.");
949 #endif /* ALLOW_CONTROLSEND */
952 monitor_complain("Unknown command");
958 /*@ +observertrans @*/
962 /* we'll fall through to here on longjmp() */
963 gpsd_close(&session);
965 (void)fclose(logfile);
970 case TERM_SELECT_FAILED:
971 explanation = "select(2) failed\n";
973 case TERM_DRIVER_SWITCH:
974 explanation = "Driver type switch failed\n";
976 case TERM_EMPTY_READ:
977 explanation = "Device went offline\n";
979 case TERM_READ_ERROR:
980 explanation = "Read error from device\n";
984 if (explanation != NULL)
985 (void)fputs(explanation, stderr);
989 /* gpsmon.c ends here */