/src/gpsd/gpsd-3.26.2~dev/libgps/libgps_shm.c
Line | Count | Source (jump to first uncovered line) |
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 2010 by the GPSD project |
14 | | SPDX-License-Identifier: BSD-2-clause |
15 | | |
16 | | ***************************************************************************/ |
17 | | |
18 | | #include "../include/gpsd_config.h" |
19 | | |
20 | | #ifdef SHM_EXPORT_ENABLE |
21 | | |
22 | | #include <errno.h> |
23 | | #include <stddef.h> |
24 | | #include <stdlib.h> |
25 | | #include <string.h> |
26 | | #include <sys/ipc.h> |
27 | | #include <sys/shm.h> |
28 | | #include <sys/time.h> |
29 | | |
30 | | #include "../include/gpsd.h" |
31 | | #include "../include/libgps.h" |
32 | | |
33 | | |
34 | | /* open a shared-memory connection to the daemon |
35 | | * |
36 | | * Return: 0 == OK |
37 | | * less than zero is an error |
38 | | * -1 shmget() error |
39 | | * -2 shmat() error |
40 | | * -3 calloc() error |
41 | | */ |
42 | | int gps_shm_open(struct gps_data_t *gpsdata) |
43 | 0 | { |
44 | 0 | int shmid; |
45 | |
|
46 | 0 | long shmkey = getenv("GPSD_SHM_KEY") ? |
47 | 0 | strtol(getenv("GPSD_SHM_KEY"), NULL, 0) : GPSD_SHM_KEY; |
48 | |
|
49 | 0 | libgps_debug_trace((DEBUG_CALLS, "gps_shm_open()\n")); |
50 | |
|
51 | 0 | gpsdata->privdata = NULL; |
52 | 0 | shmid = shmget((key_t)shmkey, sizeof(struct gps_data_t), 0); |
53 | 0 | if (-1 == shmid) { |
54 | | // daemon isn't running or failed to create shared segment |
55 | 0 | libgps_debug_trace((DEBUG_CALLS, "gps_shm_open(x%lx) %s(%d)\n", |
56 | 0 | (unsigned long)shmkey, strerror(errno), errno)); |
57 | 0 | return -1; |
58 | 0 | } |
59 | 0 | gpsdata->privdata = |
60 | 0 | (struct privdata_t *)calloc(1, sizeof(struct privdata_t)); |
61 | 0 | if (NULL == gpsdata->privdata) { |
62 | 0 | libgps_debug_trace((DEBUG_CALLS, "calloc() %s(%d)\n", |
63 | 0 | strerror(errno), errno)); |
64 | 0 | return -3; |
65 | 0 | } |
66 | | |
67 | 0 | PRIVATE(gpsdata)->shmseg = shmat(shmid, 0, 0); |
68 | 0 | if ((void *)-1 == PRIVATE(gpsdata)->shmseg) { |
69 | | // attach failed for sume unknown reason |
70 | 0 | libgps_debug_trace((DEBUG_CALLS, "shmat() %s(%d)\n", |
71 | 0 | strerror(errno), errno)); |
72 | 0 | free(gpsdata->privdata); |
73 | 0 | gpsdata->privdata = NULL; |
74 | 0 | return -2; |
75 | 0 | } |
76 | 0 | gpsdata->gps_fd = (gps_fd_t)SHM_PSEUDO_FD; |
77 | 0 | return 0; |
78 | 0 | } |
79 | | |
80 | | /* check to see if new data has been written |
81 | | * timeout is in uSec */ |
82 | | bool gps_shm_waiting(const struct gps_data_t *gpsdata, int timeout) |
83 | 0 | { |
84 | 0 | volatile struct shmexport_t *shared = |
85 | 0 | (struct shmexport_t *)PRIVATE(gpsdata)->shmseg; |
86 | 0 | volatile bool newdata = false; |
87 | 0 | timespec_t endtime; |
88 | |
|
89 | 0 | (void)clock_gettime(CLOCK_REALTIME, &endtime); |
90 | 0 | endtime.tv_sec += timeout / 1000000; |
91 | 0 | endtime.tv_nsec += (timeout % 1000000) * 1000; |
92 | 0 | TS_NORM(&endtime); |
93 | | |
94 | | // busy-waiting sucks, but there's not really an alternative |
95 | 0 | for (;;) { |
96 | 0 | volatile int bookend1, bookend2; |
97 | 0 | timespec_t now; |
98 | |
|
99 | 0 | memory_barrier(); |
100 | 0 | bookend1 = shared->bookend1; |
101 | 0 | memory_barrier(); |
102 | 0 | bookend2 = shared->bookend2; |
103 | 0 | memory_barrier(); |
104 | 0 | if (bookend1 == bookend2 && bookend1 > PRIVATE(gpsdata)->tick) { |
105 | 0 | newdata = true; |
106 | 0 | break; |
107 | 0 | } |
108 | 0 | (void)clock_gettime(CLOCK_REALTIME, &now); |
109 | 0 | if (TS_GT(&now, &endtime)) { |
110 | 0 | break; |
111 | 0 | } |
112 | 0 | } |
113 | |
|
114 | 0 | return newdata; |
115 | 0 | } |
116 | | |
117 | | // read an update from the shared-memory segment |
118 | | int gps_shm_read(struct gps_data_t *gpsdata) |
119 | 0 | { |
120 | 0 | if (NULL == gpsdata->privdata) { |
121 | 0 | return -1; |
122 | 0 | } else { |
123 | 0 | int before1, before2, after1, after2; |
124 | 0 | struct privdata_t *private_save = gpsdata->privdata; |
125 | 0 | struct shmexport_t *shared = |
126 | 0 | (struct shmexport_t *)PRIVATE(gpsdata)->shmseg; |
127 | 0 | struct gps_data_t noclobber; |
128 | | |
129 | | /* |
130 | | * Following block of instructions must not be reordered, |
131 | | * otherwise havoc will ensue. The memory_barrier() call |
132 | | * should prevent reordering of the data accesses. |
133 | | * for those lucky enough to have a working memory_barrier() |
134 | | * |
135 | | * bookends are volatile, so that should force |
136 | | * them to be read in order. |
137 | | * |
138 | | * This is a simple optimistic-concurrency technique. We wrote |
139 | | * the second bookend first, then the data, then the first bookend. |
140 | | * Reader copies what it sees in normal order; that way, if we |
141 | | * start to write the segment during the read, the second bookend will |
142 | | * get clobbered first and the data can be detected as bad. |
143 | | * |
144 | | * Excwpt with mutil-treading and CPU caches, order is iffy... |
145 | | */ |
146 | 0 | before1 = shared->bookend1; |
147 | 0 | before2 = shared->bookend2; |
148 | 0 | memory_barrier(); |
149 | | // memcpy() and (volatile) don't play well together. |
150 | 0 | (void)memcpy((void *)&noclobber, |
151 | 0 | (void *)&shared->gpsdata, |
152 | 0 | sizeof(struct gps_data_t)); |
153 | 0 | memory_barrier(); |
154 | 0 | after1 = shared->bookend1; |
155 | 0 | after2 = shared->bookend2; |
156 | |
|
157 | 0 | if (before1 != after1 || |
158 | 0 | before1 != after2 || |
159 | 0 | before1 != before2) { |
160 | | // bookend mismatch, throw away the data |
161 | | // FIXME: retry? |
162 | 0 | return 0; |
163 | 0 | } else { |
164 | 0 | (void)memcpy((void *)gpsdata, |
165 | 0 | (void *)&noclobber, |
166 | 0 | sizeof(struct gps_data_t)); |
167 | 0 | gpsdata->privdata = private_save; |
168 | 0 | gpsdata->gps_fd = (gps_fd_t)SHM_PSEUDO_FD; |
169 | 0 | PRIVATE(gpsdata)->tick = after2; |
170 | 0 | if (0 != (gpsdata->set & REPORT_IS)) { |
171 | 0 | gpsdata->set = STATUS_SET; |
172 | 0 | } |
173 | 0 | return (int)sizeof(struct gps_data_t); |
174 | 0 | } |
175 | 0 | } |
176 | 0 | } |
177 | | |
178 | | void gps_shm_close(struct gps_data_t *gpsdata) |
179 | 0 | { |
180 | 0 | if (PRIVATE(gpsdata)) { |
181 | 0 | if (NULL != PRIVATE(gpsdata)->shmseg) { |
182 | 0 | (void)shmdt((const void *)PRIVATE(gpsdata)->shmseg); |
183 | 0 | } |
184 | 0 | free(PRIVATE(gpsdata)); |
185 | 0 | gpsdata->privdata = NULL; |
186 | 0 | } |
187 | 0 | } |
188 | | |
189 | | /* run a shm main loop with a specified handler |
190 | | * |
191 | | * Returns: -1 on timeout |
192 | | * -2 on error |
193 | | * FIXME: read error should return different than timeout |
194 | | */ |
195 | | int gps_shm_mainloop(struct gps_data_t *gpsdata, int timeout, |
196 | | void (*hook)(struct gps_data_t *gpsdata)) |
197 | 0 | { |
198 | |
|
199 | 0 | for (;;) { |
200 | 0 | int status; |
201 | |
|
202 | 0 | if (!gps_shm_waiting(gpsdata, timeout)) { |
203 | 0 | return -1; |
204 | 0 | } |
205 | 0 | status = gps_shm_read(gpsdata); |
206 | |
|
207 | 0 | if (-1 == status) { |
208 | 0 | break; |
209 | 0 | } |
210 | 0 | if (0 < status) { |
211 | 0 | (*hook)(gpsdata); |
212 | 0 | } |
213 | 0 | } |
214 | 0 | return -2; |
215 | 0 | } |
216 | | |
217 | | #endif // SHM_EXPORT_ENABLE |
218 | | |
219 | | // vim: set expandtab shiftwidth=4 |