1 /****************************************************************************
2 
3 NAME
4    libgps_shm.c - reader access to shared-memory export
5 
6 DESCRIPTION
7    This is a very lightweight alternative to JSON-over-sockets.  Clients
8 won't be able to filter by device, and won't get device activation/deactivation
9 notifications.  But both client and daemon will avoid all the marshalling and
10 unmarshalling overhead.
11 
12 PERMISSIONS
13    This file is Copyright (c) 2010-2018 by the GPSD project
14    SPDX-License-Identifier: BSD-2-clause
15 
16 ***************************************************************************/
17 
18 #include "gpsd_config.h"
19 
20 #ifdef SHM_EXPORT_ENABLE
21 
22 #include <stddef.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <sys/time.h>
27 #include <sys/ipc.h>
28 #include <sys/shm.h>
29 
30 #include "gpsd.h"
31 #include "libgps.h"
32 
33 struct privdata_t
34 {
35     void *shmseg;
36     int tick;
37 };
38 
39 
gps_shm_open(struct gps_data_t * gpsdata)40 int gps_shm_open(struct gps_data_t *gpsdata)
41 /* open a shared-memory connection to the daemon */
42 {
43     int shmid;
44 
45     long shmkey = getenv("GPSD_SHM_KEY") ? strtol(getenv("GPSD_SHM_KEY"), NULL, 0) : GPSD_SHM_KEY;
46 
47     libgps_debug_trace((DEBUG_CALLS, "gps_shm_open()\n"));
48 
49     gpsdata->privdata = NULL;
50     shmid = shmget((key_t)shmkey, sizeof(struct gps_data_t), 0);
51     if (shmid == -1) {
52 	/* daemon isn't running or failed to create shared segment */
53 	return -1;
54     }
55     gpsdata->privdata = (void *)malloc(sizeof(struct privdata_t));
56     if (gpsdata->privdata == NULL)
57 	return -1;
58 
59     PRIVATE(gpsdata)->tick = 0;
60     PRIVATE(gpsdata)->shmseg = shmat(shmid, 0, 0);
61     if (PRIVATE(gpsdata)->shmseg == (void *) -1) {
62 	/* attach failed for sume unknown reason */
63 	free(gpsdata->privdata);
64 	gpsdata->privdata = NULL;
65 	return -2;
66     }
67 #ifndef USE_QT
68     gpsdata->gps_fd = SHM_PSEUDO_FD;
69 #else
70     gpsdata->gps_fd = (void *)(intptr_t)SHM_PSEUDO_FD;
71 #endif /* USE_QT */
72     return 0;
73 }
74 
75 /* check to see if new data has been written */
76 /* timeout is in uSec */
gps_shm_waiting(const struct gps_data_t * gpsdata,int timeout)77 bool gps_shm_waiting(const struct gps_data_t *gpsdata, int timeout)
78 {
79     volatile struct shmexport_t *shared =
80             (struct shmexport_t *)PRIVATE(gpsdata)->shmseg;
81     volatile bool newdata = false;
82     timespec_t endtime;
83 
84     (void)clock_gettime(CLOCK_REALTIME, &endtime);
85     endtime.tv_sec += timeout / 1000000;
86     endtime.tv_nsec += (timeout % 1000000) * 1000;
87     TS_NORM(&endtime);
88 
89     /* busy-waiting sucks, but there's not really an alternative */
90     for (;;) {
91 	volatile int bookend1, bookend2;
92 	timespec_t now;
93 
94 	memory_barrier();
95 	bookend1 = shared->bookend1;
96 	memory_barrier();
97 	bookend2 = shared->bookend2;
98 	memory_barrier();
99 	if (bookend1 == bookend2 && bookend1 > PRIVATE(gpsdata)->tick) {
100 	    newdata = true;
101             break;
102         }
103 	(void)clock_gettime(CLOCK_REALTIME, &now);
104 	if (TS_GT(&now, &endtime)) {
105 	    break;
106         }
107     }
108 
109     return newdata;
110 }
111 
gps_shm_read(struct gps_data_t * gpsdata)112 int gps_shm_read(struct gps_data_t *gpsdata)
113 /* read an update from the shared-memory segment */
114 {
115     if (gpsdata->privdata == NULL)
116 	return -1;
117     else
118     {
119 	int before, after;
120 	void *private_save = gpsdata->privdata;
121 	volatile struct shmexport_t *shared = (struct shmexport_t *)PRIVATE(gpsdata)->shmseg;
122 	struct gps_data_t noclobber;
123 
124 	/*
125 	 * Following block of instructions must not be reordered,
126 	 * otherwise havoc will ensue.  The memory_barrier() call
127 	 * should prevent reordering of the data accesses.
128 	 *
129 	 * This is a simple optimistic-concurrency technique.  We wrote
130 	 * the second bookend first, then the data, then the first bookend.
131 	 * Reader copies what it sees in normal order; that way, if we
132 	 * start to write the segment during the read, the second bookend will
133 	 * get clobbered first and the data can be detected as bad.
134 	 */
135 	before = shared->bookend1;
136 	memory_barrier();
137 	(void)memcpy((void *)&noclobber,
138 		     (void *)&shared->gpsdata,
139 		     sizeof(struct gps_data_t));
140 	memory_barrier();
141 	after = shared->bookend2;
142 
143 	if (before != after)
144 	    return 0;
145 	else {
146 	    (void)memcpy((void *)gpsdata,
147 			 (void *)&noclobber,
148 			 sizeof(struct gps_data_t));
149 	    gpsdata->privdata = private_save;
150 #ifndef USE_QT
151 	    gpsdata->gps_fd = SHM_PSEUDO_FD;
152 #else
153 	    gpsdata->gps_fd = (void *)(intptr_t)SHM_PSEUDO_FD;
154 #endif /* USE_QT */
155 	    PRIVATE(gpsdata)->tick = after;
156 	    if ((gpsdata->set & REPORT_IS)!=0) {
157 		gpsdata->set = STATUS_SET;
158 	    }
159 	    return (int)sizeof(struct gps_data_t);
160 	}
161     }
162 }
163 
gps_shm_close(struct gps_data_t * gpsdata)164 void gps_shm_close(struct gps_data_t *gpsdata)
165 {
166     if (PRIVATE(gpsdata)) {
167         if (PRIVATE(gpsdata)->shmseg != NULL)
168 	    (void)shmdt((const void *)PRIVATE(gpsdata)->shmseg);
169         free(PRIVATE(gpsdata));
170         gpsdata->privdata = NULL;
171     }
172 }
173 
gps_shm_mainloop(struct gps_data_t * gpsdata,int timeout,void (* hook)(struct gps_data_t * gpsdata))174 int gps_shm_mainloop(struct gps_data_t *gpsdata, int timeout,
175 			 void (*hook)(struct gps_data_t *gpsdata))
176 /* run a shm main loop with a specified handler */
177 {
178     for (;;) {
179 	if (!gps_shm_waiting(gpsdata, timeout)) {
180 	    return -1;
181 	} else {
182 	    int status = gps_shm_read(gpsdata);
183 
184 	    if (status == -1)
185 		return -1;
186 	    if (status > 0)
187 		(*hook)(gpsdata);
188 	}
189     }
190     //return 0;
191 }
192 
193 #endif /* SHM_EXPORT_ENABLE */
194 
195 /* end */
196