gpsd.c
来自「gpsd, a popular GPS daemon.」· C语言 代码 · 共 1,824 行 · 第 1/4 页
C
1,824 行
/* $Id: gpsd.c 4661 2008-01-19 22:54:23Z garyemiller $ */#include <sys/types.h>#include <sys/socket.h>#include <sys/un.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <stdlib.h>#include <syslog.h>#include <signal.h>#include <errno.h>#include <ctype.h>#include <fcntl.h>#include <string.h>#include <netdb.h>#include <stdarg.h>#include <setjmp.h>#include <stdio.h>#include <assert.h>#include <pwd.h>#include <stdbool.h>#include <math.h>#include "gpsd_config.h"#if defined (HAVE_PATH_H)#include <paths.h>#else#if !defined (_PATH_DEVNULL)#define _PATH_DEVNULL "/dev/null"#endif#endif#if defined (HAVE_SYS_SELECT_H)#include <sys/select.h>#endif#if defined (HAVE_SYS_STAT_H)#include <sys/stat.h>#endif#if defined(HAVE_SYS_TIME_H)#include <sys/time.h>#endif#ifdef HAVE_SETLOCALE#include <locale.h>#endif#ifdef DBUS_ENABLE#include <gpsd_dbus.h>#endif#include "gpsd.h"#include "timebase.h"/* * The name of a tty device from which to pick up whatever the local * owning group for tty devices is. Used when we drop privileges. */#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)#define PROTO_TTY "/dev/tty00" /* correct for *BSD */#else#define PROTO_TTY "/dev/ttyS0" /* correct for Linux */#endif/* Name of (unprivileged) user to change to when we drop privileges. */#ifndef GPSD_USER#define GPSD_USER "nobody"#endif/* * Timeout policy. We can't rely on clients closing connections * correctly, so we need timeouts to tell us when it's OK to * reclaim client fds. The assignment timeout fends off programs * that open connections and just sit there, not issuing a W or * doing anything else that triggers a device assignment. Clients * in watcher or raw mode that don't read their data will get dropped * when throttled_write() fills up the outbound buffers and the * NOREAD_TIMEOUT expires. Clients in the original polling mode have * to be timed out as well. */#define ASSIGNMENT_TIMEOUT 60#define POLLER_TIMEOUT 60*15#define NOREAD_TIMEOUT 60*3#define QLEN 5#define sub_index(s) (s - subscribers)static fd_set all_fds;static int maxfd;static int debuglevel;static bool in_background = false;static bool nowait = false;static jmp_buf restartbuf;/*@ -initallelements -nullassign -nullderef @*/static struct gps_context_t context = { .valid = 0, .readonly = false, .sentdgps = false, .dgnss_service = dgnss_none, .fixcnt = 0, .dsock = -1, .dgnss_privdata = NULL, .rtcmbytes = 0, .rtcmbuf = {'\0'}, .rtcmtime = 0, .leap_seconds = LEAP_SECONDS, .century = CENTURY_BASE,#ifdef NTPSHM_ENABLE .enable_ntpshm = false, .shmTime = {0}, .shmTimeInuse = {0},# ifdef PPS_ENABLE .shmTimePPS = false,# endif /* PPS_ENABLE */#endif /* NTPSHM_ENABLE */};/*@ +initallelements +nullassign +nullderef @*/static volatile sig_atomic_t signalled;static void onsig(int sig){ /* just set a variable, and deal with it in the main loop */ signalled = (sig_atomic_t)sig;}static int daemonize(void){ int fd; pid_t pid; switch (pid = fork()) { case -1: return -1; case 0: /* child side */ break; default: /* parent side */ exit(0); } if (setsid() == -1) return -1; (void)chdir("/"); /*@ -nullpass @*/ if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { (void)dup2(fd, STDIN_FILENO); (void)dup2(fd, STDOUT_FILENO); (void)dup2(fd, STDERR_FILENO); if (fd > 2) (void)close(fd); } /*@ +nullpass @*/ in_background = true; return 0;}#if defined(PPS_ENABLE)static pthread_mutex_t report_mutex;#endif /* PPS_ENABLE */void gpsd_report(int errlevel, const char *fmt, ... )/* assemble command in printf(3) style, use stderr or syslog */{#ifndef SQUELCH_ENABLE if (errlevel <= debuglevel) { char buf[BUFSIZ], buf2[BUFSIZ], *sp; va_list ap;#if defined(PPS_ENABLE) /*@ -unrecog (splint has no pthread declarations as yet) @*/ (void)pthread_mutex_lock(&report_mutex); /* +unrecog */#endif /* PPS_ENABLE */ (void)strlcpy(buf, "gpsd: ", BUFSIZ); va_start(ap, fmt) ; (void)vsnprintf(buf + strlen(buf), sizeof(buf)-strlen(buf), fmt, ap); va_end(ap); buf2[0] = '\0'; for (sp = buf; *sp != '\0'; sp++) if (isprint(*sp) || (isspace(*sp) && (sp[1]=='\0' || sp[2]=='\0'))) (void)snprintf(buf2+strlen(buf2), 2, "%c", *sp); else (void)snprintf(buf2+strlen(buf2), 6, "\\x%02x", (unsigned)*sp); if (in_background) syslog((errlevel == 0) ? LOG_ERR : LOG_NOTICE, "%s", buf2); else (void)fputs(buf2, stderr);#if defined(PPS_ENABLE) /*@ -unrecog (splint has no pthread declarations as yet) @*/ (void)pthread_mutex_unlock(&report_mutex); /* +unrecog */#endif /* PPS_ENABLE */ }#endif /* !SQUELCH_ENABLE */}static void usage(void){ struct gps_type_t **dp; (void)printf("usage: gpsd [-b] [-n] [-N] [-D n] [-F sockfile] [-P pidfile] [-S port] [-h] device...\n\ Options include: \n\ -b = bluetooth-safe: open data sources read-only\n\ -n = don't wait for client connects to poll GPS\n\ -N = don't go into background\n\ -F sockfile = specify control socket location\n\ -P pidfile = set file to record process ID \n\ -D integer (default 0) = set debug level \n\ -S integer (default %s) = set port for daemon \n\ -h = help message \n\ -V = emit version and exit.\n\A device may be a local serial device for GPS input, or a URL of the form:\n\ [{dgpsip|ntrip}://][user:passwd@]host[:port][/stream]\n\in which case it specifies an input source for DGPS or ntrip data.\n\\n\The following driver types are compiled into this gpsd instance:\n", DEFAULT_GPSD_PORT); for (dp = gpsd_drivers; *dp; dp++) { (void)printf(" %s\n", (*dp)->type_name); }}static int passivesock(char *service, char *protocol, int qlen){ struct servent *pse; struct protoent *ppe ; /* splint has a bug here */ struct sockaddr_in sin; int s, type, proto, one = 1; /*@ -mustfreefresh @*/ memset((char *) &sin, 0, sizeof(sin)); /*@i1@*/sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; if ((pse = getservbyname(service, protocol))) sin.sin_port = htons(ntohs((in_port_t)pse->s_port)); else if ((sin.sin_port = htons((in_port_t)atoi(service))) == 0) { gpsd_report(LOG_ERROR, "Can't get \"%s\" service entry.\n", service); return -1; } ppe = getprotobyname(protocol); if (strcmp(protocol, "udp") == 0) { type = SOCK_DGRAM; /*@i@*/proto = (ppe) ? ppe->p_proto : IPPROTO_UDP; } else { type = SOCK_STREAM; /*@i@*/proto = (ppe) ? ppe->p_proto : IPPROTO_TCP; } if ((s = socket(PF_INET, type, /*@i1@*/proto)) < 0) { gpsd_report(LOG_ERROR, "Can't create socket\n"); return -1; } if (setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char *)&one,(int)sizeof(one)) == -1) { gpsd_report(LOG_ERROR, "Error: SETSOCKOPT SO_REUSEADDR\n"); return -1; } if (bind(s, (struct sockaddr *) &sin, (int)sizeof(sin)) < 0) { gpsd_report(LOG_ERROR, "Can't bind to port %s\n", service); if (errno == EADDRINUSE) { gpsd_report(LOG_ERROR, "Maybe gpsd is already running!\n"); } return -1; } if (type == SOCK_STREAM && listen(s, qlen) < 0) { gpsd_report(LOG_ERROR, "Can't listen on %s port%s\n", service); return -1; } return s; /*@ +mustfreefresh @*/}static int filesock(char *filename){ struct sockaddr_un addr; int sock; /*@ -mayaliasunique @*/ if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { gpsd_report(LOG_ERROR, "Can't create device-control socket\n"); return -1; } (void)strlcpy(addr.sun_path, filename, 104); /* from sys/un.h */ /*@i1@*/addr.sun_family = AF_UNIX; (void)bind(sock, (struct sockaddr *) &addr, (int)sizeof(addr)); if (listen(sock, QLEN) < 0) { gpsd_report(LOG_ERROR, "can't listen on local socket %s\n", filename); return -1; } /*@ +mayaliasunique @*/ return sock;}/* * This hackery is intended to support SBCs that are resource-limited * and only need to support one or a few devices each. It avoids the * space overhead of allocating thousands of unused device structures. * This array fills from the bottom, so as an extreme case you could * reduce LIMITED_MAX_DEVICES to 1. */#ifdef LIMITED_MAX_DEVICES#define MAXDEVICES LIMITED_MAX_DEVICES#else/* we used to make this FD_SETSIZE, but that cost 14MB of wasted core! */#define MAXDEVICES 4#endif#ifdef LIMITED_MAX_CLIENTS#define MAXSUBSCRIBERS LIMITED_MAX_CLIENTS#else/* subscriber structure is small enough that there's no need to limit this */#define MAXSUBSCRIBERS FD_SETSIZE#endif/* * Multi-session support requires us to have two arrays, one of GPS * devices currently available and one of client sessions. The number * of slots in each array is limited by the maximum number of client * sessions we can have open. */static struct gps_device_t channels[MAXDEVICES];#define allocated_channel(chp) ((chp)->gpsdata.gps_device[0] != '\0')#define free_channel(chp) (chp)->gpsdata.gps_device[0] = '\0'#define syncing(chp) (chp->gpsdata.gps_fd>-1&& chp->packet_type==BAD_PACKET)static struct subscriber_t { int fd; /* client file descriptor. -1 if unused */ double active; /* when subscriber last polled for data */ bool tied; /* client set device with F */ bool watcher; /* is client in watcher mode? */ int raw; /* is client in raw mode? */ enum {GPS,RTCM104,ANY} requires; /* type of device requested */ struct gps_fix_t fixbuffer; /* info to report to the client */ struct gps_fix_t oldfix; /* previous fix for error modeling */ enum {casoc=0, nocasoc=1} buffer_policy; /* buffering policy */ /*@relnull@*/struct gps_device_t *device; /* device subscriber listens to */} subscribers[MAXSUBSCRIBERS]; /* indexed by client file descriptor */static void adjust_max_fd(int fd, bool on)/* track the largest fd currently in use */{ if (on) { if (fd > maxfd) maxfd = fd; }#if !defined(LIMITED_MAX_DEVICES) && !defined(LIMITED_MAX_CLIENT_FD) /* * I suspect there could be some weird interactions here if * either of these were set lower than FD_SETSIZE. We'll avoid * potential bugs by not scavenging in this case at all -- should * be OK, as the use case for limiting is SBCs where the limits * will be very low (typically 1) and the maximum size of fd * set to scan through correspondingly small. */ else { if (fd == maxfd) { int tfd; for (maxfd = tfd = 0; tfd < FD_SETSIZE; tfd++) if (FD_ISSET(tfd, &all_fds)) maxfd = tfd; } }#endif /* !defined(LIMITED_MAX_DEVICES) && !defined(LIMITED_MAX_CLIENT_FD) */}static bool have_fix(struct subscriber_t *whoami){ if (!whoami->device) { gpsd_report(LOG_PROG, "Client has no device\n"); return false; }#define VALIDATION_COMPLAINT(level, legend) \ gpsd_report(level, legend " (status=%d, mode=%d).\n", \ whoami->device->gpsdata.status, whoami->fixbuffer.mode) if ((whoami->device->gpsdata.status == STATUS_NO_FIX) != (whoami->fixbuffer.mode == MODE_NO_FIX)) { VALIDATION_COMPLAINT(3, "GPS is confused about whether it has a fix"); return false; } else if (whoami->device->gpsdata.status > STATUS_NO_FIX && whoami->fixbuffer.mode > MODE_NO_FIX) { VALIDATION_COMPLAINT(3, "GPS has a fix"); return true; } VALIDATION_COMPLAINT(3, "GPS has no fix"); return false;#undef VALIDATION_COMPLAINT}static /*@null@*/ /*@observer@*/ struct subscriber_t* allocate_client(void){ int cfd; for (cfd = 0; cfd < MAXSUBSCRIBERS; cfd++) { if (subscribers[cfd].fd <= 0 ) { subscribers[cfd].fd = cfd; /* mark subscriber as allocated */ return &subscribers[cfd]; } } return NULL;}static void detach_client(struct subscriber_t *sub){ char *c_ip = sock2ip(sub->fd); (void)shutdown(sub->fd, SHUT_RDWR); (void)close(sub->fd); gpsd_report(LOG_INF, "detaching %s (sub%d, fd %d) in detach_client\n", c_ip, sub_index(sub), sub->fd); FD_CLR(sub->fd, &all_fds); adjust_max_fd(sub->fd, false); sub->raw = 0; sub->watcher = false; sub->active = 0; /*@i1@*/sub->device = NULL; sub->buffer_policy = casoc; sub->fd = -1;}static ssize_t throttled_write(struct subscriber_t *sub, char *buf, ssize_t len)/* write to client -- throttle if it's gone or we're close to buffer overrun */{ ssize_t status; if (debuglevel >= 3) { if (isprint(buf[0])) gpsd_report(LOG_IO, "=> client(%d): %s", sub_index(sub), buf); else { char *cp, buf2[MAX_PACKET_LENGTH*3]; buf2[0] = '\0'; for (cp = buf; cp < buf + len; cp++) (void)snprintf(buf2 + strlen(buf2), sizeof(buf2)-strlen(buf2), "%02x", (unsigned int)(*cp & 0xff)); gpsd_report(LOG_IO, "=> client(%d): =%s\r\n", sub_index(sub), buf2); } } status = write(sub->fd, buf, (size_t)len); if (status == len ) return status; else if (status > -1) { gpsd_report(LOG_INF, "short write disconnecting client(%d)\n", sub_index(sub)); detach_client(sub); return 0; } else if (errno == EAGAIN || errno == EINTR) return 0; /* no data written, and errno says to retry */ else if (errno == EBADF) gpsd_report(LOG_WARN, "client(%d) has vanished.\n", sub_index(sub)); else if (errno == EWOULDBLOCK && timestamp() - sub->active > NOREAD_TIMEOUT) gpsd_report(LOG_INF, "client(%d) timed out.\n", sub_index(sub)); else gpsd_report(LOG_INF, "client(%d) write: %s\n", sub_index(sub), strerror(errno)); detach_client(sub); return status;}
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?