1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 */
26
27 #include <assert.h>
28 #include <dirent.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <pthread.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <strings.h>
35 #include <string.h>
36 #include <syslog.h>
37 #include <sys/msg.h>
38 #include <sys/stat.h>
39 #include <sys/types.h>
40 #include <unistd.h>
41
42 #include "libnwam_impl.h"
43 #include <libnwam_priv.h>
44 #include <libnwam.h>
45
46 /*
47 * Implementation of event notification mechanism used by the GUI and
48 * nwamadm. Clients register for events via nwam_events_init() and
49 * unregister via nwam_events_fini(). nwamd sends events via nwam_event_send()
50 * and applications block waiting for a new event to be delivered in
51 * nwam_event_wait(). Events are implemented as System V message queues,
52 * one per event client. The event mechanism has to be resilient to
53 * nwamd restarts so that clients do not lose the event connection.
54 */
55
56 #define NWAM_EVENT_MSG_DIR "/etc/svc/volatile/nwam/"
57 #define NWAM_EVENT_MSG_FILE "nwam_event_msgs"
58 #define NWAM_EVENT_MSG_FILE_PREFIX NWAM_EVENT_MSG_DIR NWAM_EVENT_MSG_FILE
59 #define NWAM_EVENT_MAX_SIZE (sizeof (struct nwam_event) + \
60 (NWAMD_MAX_NUM_WLANS * sizeof (nwam_wlan_t)))
61 #define NWAM_EVENT_WAIT_TIME 10
62 #define NWAM_EVENT_MAX_NUM_PENDING 25
63
64 /*
65 * This is protecting simultaneous access to the msqid and its configuration.
66 */
67 static pthread_mutex_t event_mutex = PTHREAD_MUTEX_INITIALIZER;
68 static int event_msqid = -1;
69
70 static nwam_error_t
nwam_event_alloc(nwam_event_t * eventp)71 nwam_event_alloc(nwam_event_t *eventp)
72 {
73 assert(eventp != NULL);
74
75 *eventp = calloc(1, NWAM_EVENT_MAX_SIZE);
76 if (*eventp == NULL)
77 return (NWAM_NO_MEMORY);
78 return (NWAM_SUCCESS);
79 }
80
81 void
nwam_event_free(nwam_event_t event)82 nwam_event_free(nwam_event_t event)
83 {
84 if (event != NULL)
85 free(event);
86 }
87
88 /*
89 * Get next event in queue.
90 */
91 nwam_error_t
nwam_event_wait(nwam_event_t * eventp)92 nwam_event_wait(nwam_event_t *eventp)
93 {
94 nwam_error_t err;
95 nwam_event_t event;
96
97 assert(eventp != NULL);
98
99 if ((err = nwam_event_alloc(&event)) != NWAM_SUCCESS)
100 return (err);
101 while (msgrcv(event_msqid, (struct msgbuf *)event, NWAM_EVENT_MAX_SIZE,
102 0, 0) == -1) {
103 switch (errno) {
104 case EAGAIN:
105 case EBUSY:
106 /*
107 * We see this errno eventhough it isn't
108 * documented. Try again. If this causes
109 * a busy loop then grab a trace otherwise
110 * it's a brace 'til we can figure out why it
111 * happens.
112 */
113 continue;
114
115 default:
116 nwam_event_free(event);
117 return (nwam_errno_to_nwam_error(errno));
118 }
119 }
120
121 /* Resize event down from maximum size */
122 if ((*eventp = realloc(event, event->nwe_size)) == NULL)
123 return (NWAM_NO_MEMORY);
124
125 return (NWAM_SUCCESS);
126 }
127
128 /*
129 * Register for receipt of events from nwamd. Event delivery is
130 * done via a System V message queue.
131 */
132 nwam_error_t
nwam_events_init(void)133 nwam_events_init(void)
134 {
135 char eventmsgfile[MAXPATHLEN];
136 nwam_error_t err;
137 nwam_error_t rc = NWAM_SUCCESS;
138 key_t key;
139
140 (void) snprintf(eventmsgfile, sizeof (eventmsgfile), "%s.%d",
141 NWAM_EVENT_MSG_FILE_PREFIX, getpid());
142
143 (void) pthread_mutex_lock(&event_mutex);
144
145 if (event_msqid != -1) {
146 rc = NWAM_ENTITY_IN_USE;
147 goto exit;
148 }
149
150 if ((err = nwam_request_register_unregister
151 (NWAM_REQUEST_TYPE_EVENT_REGISTER, eventmsgfile)) != NWAM_SUCCESS) {
152 rc = err;
153 goto exit;
154 }
155
156 if ((key = ftok(eventmsgfile, 0)) == -1) {
157 rc = nwam_errno_to_nwam_error(errno);
158 goto exit;
159 }
160
161 /* Get system-wide message queue ID */
162 if ((event_msqid = msgget(key, 0444)) == -1) {
163 rc = nwam_errno_to_nwam_error(errno);
164 goto exit;
165 }
166
167 exit:
168 (void) pthread_mutex_unlock(&event_mutex);
169
170 return (rc);
171 }
172
173 /*
174 * Un-register for receipt of events from nwamd. Make a request to nwamd
175 * to destroy the message queue.
176 */
177 void
nwam_events_fini(void)178 nwam_events_fini(void)
179 {
180 char eventmsgfile[MAXPATHLEN];
181
182 (void) snprintf(eventmsgfile, sizeof (eventmsgfile), "%s.%d",
183 NWAM_EVENT_MSG_FILE_PREFIX, getpid());
184
185 (void) pthread_mutex_lock(&event_mutex);
186
187 (void) nwam_request_register_unregister
188 (NWAM_REQUEST_TYPE_EVENT_UNREGISTER, eventmsgfile);
189
190 event_msqid = -1;
191
192 (void) pthread_mutex_unlock(&event_mutex);
193 }
194
195 /*
196 * Create an event queue. Called by nwamd to create System V message queues
197 * for clients to listen for events.
198 */
199 nwam_error_t
nwam_event_queue_init(const char * eventmsgfile)200 nwam_event_queue_init(const char *eventmsgfile)
201 {
202 int fd;
203 key_t key;
204
205 if ((fd = open(eventmsgfile, O_RDWR | O_CREAT | O_TRUNC, 0644)) == -1)
206 return (nwam_errno_to_nwam_error(errno));
207 (void) close(fd);
208
209 if ((key = ftok(eventmsgfile, 0)) == -1)
210 return (nwam_errno_to_nwam_error(errno));
211
212 if (msgget(key, 0644 | IPC_CREAT) == -1)
213 return (nwam_errno_to_nwam_error(errno));
214
215 return (NWAM_SUCCESS);
216 }
217
218 /*
219 * Send event to registered listeners via the set of registered System V
220 * message queues.
221 */
222 nwam_error_t
nwam_event_send(nwam_event_t event)223 nwam_event_send(nwam_event_t event)
224 {
225 DIR *dirp;
226 struct dirent *dp;
227 struct msqid_ds buf;
228 key_t key;
229 int msqid;
230 char eventmsgfile[MAXPATHLEN];
231 nwam_error_t err = NWAM_SUCCESS;
232
233 if ((dirp = opendir(NWAM_EVENT_MSG_DIR)) == NULL) {
234 return (nwam_errno_to_nwam_error(errno));
235 }
236
237 /*
238 * For each file matching our event message queue file prefix,
239 * check the queue is still being read, and if so send the message.
240 */
241 while ((dp = readdir(dirp)) != NULL) {
242 if (strncmp(dp->d_name, NWAM_EVENT_MSG_FILE,
243 strlen(NWAM_EVENT_MSG_FILE)) != 0)
244 continue;
245
246 (void) snprintf(eventmsgfile, sizeof (eventmsgfile), "%s/%s",
247 NWAM_EVENT_MSG_DIR, dp->d_name);
248
249 if ((key = ftok(eventmsgfile, 0)) == -1) {
250 int errno_save = errno;
251 syslog(LOG_INFO, "nwam_event_send: ftok: %s",
252 strerror(errno_save));
253 err = nwam_errno_to_nwam_error(errno_save);
254 continue;
255 }
256
257 if ((msqid = msgget(key, 0644)) == -1) {
258 int errno_save = errno;
259 syslog(LOG_INFO, "nwam_event_send: msgget: %s",
260 strerror(errno_save));
261 err = nwam_errno_to_nwam_error(errno_save);
262 continue;
263 }
264
265 /* Retrieve stats to analyse queue activity */
266 if (msgctl(msqid, IPC_STAT, &buf) == -1) {
267 int errno_save = errno;
268 syslog(LOG_INFO, "nwam_event_send: msgctl: %s",
269 strerror(errno_save));
270 err = nwam_errno_to_nwam_error(errno_save);
271 continue;
272 }
273 /*
274 * If buf.msg_qnum > NWAM_EVENT_MAX_NUM_PENDING
275 * _and_ msg_stime is more than 10s after msg_rtime -
276 * indicating message(s) have been hanging around unclaimed -
277 * we destroy the queue as the client has most likely gone
278 * away. This can happen if a registered client hits Ctrl^C.
279 */
280 if (buf.msg_qnum > NWAM_EVENT_MAX_NUM_PENDING &&
281 ((buf.msg_stime + NWAM_EVENT_WAIT_TIME) > buf.msg_rtime)) {
282 nwam_event_queue_fini(eventmsgfile);
283 continue;
284 }
285
286 /*
287 * This shouldn't ever block. If it does then log an error and
288 * clean up the queue.
289 */
290 if (msgsnd(msqid, (struct msgbuf *)event, event->nwe_size,
291 IPC_NOWAIT) == -1) {
292 int errno_save = errno;
293 syslog(LOG_ERR, "nwam_event_send: msgsnd: %s, "
294 "destroying message queue %s", strerror(errno_save),
295 eventmsgfile);
296 nwam_event_queue_fini(eventmsgfile);
297 err = nwam_errno_to_nwam_error(errno_save);
298 continue;
299 }
300
301 }
302 (void) closedir(dirp);
303
304 return (err);
305 }
306
307 /*
308 * Destroy an event queue. Called by nwamd to destroy the associated message
309 * queue.
310 */
311 void
nwam_event_queue_fini(const char * eventmsgfile)312 nwam_event_queue_fini(const char *eventmsgfile)
313 {
314 key_t key;
315 int msqid;
316
317 if ((key = ftok(eventmsgfile, 0)) != -1 &&
318 (msqid = msgget(key, 0644)) != -1 &&
319 msgctl(msqid, IPC_RMID, NULL) != -1)
320 (void) unlink(eventmsgfile);
321 }
322
323 /*
324 * Stop sending events. Called by nwamd to destroy each System V message queue
325 * registered.
326 */
327 void
nwam_event_send_fini(void)328 nwam_event_send_fini(void)
329 {
330 DIR *dirp;
331 struct dirent *dp;
332 char eventmsgfile[MAXPATHLEN];
333
334 (void) pthread_mutex_lock(&event_mutex);
335
336 if ((dirp = opendir(NWAM_EVENT_MSG_DIR)) == NULL) {
337 (void) pthread_mutex_unlock(&event_mutex);
338 return;
339 }
340
341 /*
342 * For each file matching our event message queue file prefix,
343 * destroy the queue and message file.
344 */
345 while ((dp = readdir(dirp)) != NULL) {
346 if (strncmp(dp->d_name, NWAM_EVENT_MSG_FILE,
347 strlen(NWAM_EVENT_MSG_FILE)) != 0)
348 continue;
349
350 (void) snprintf(eventmsgfile, sizeof (eventmsgfile), "%s/%s",
351 NWAM_EVENT_MSG_DIR, dp->d_name);
352
353 nwam_event_queue_fini(eventmsgfile);
354 }
355 (void) pthread_mutex_unlock(&event_mutex);
356 }
357