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 + -
显示快捷键?