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