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_NUM_WLANS	32
60 #define	NWAM_EVENT_MAX_SIZE		(sizeof (struct nwam_event) + \
61 	(NWAM_EVENT_MAX_NUM_WLANS * sizeof (nwam_wlan_t)))
62 #define	NWAM_EVENT_WAIT_TIME		10
63 #define	NWAM_EVENT_MAX_NUM_PENDING	25
64 
65 /*
66  * This is protecting simultaneous access to the msqid and its configuration.
67  */
68 static pthread_mutex_t event_mutex = PTHREAD_MUTEX_INITIALIZER;
69 static int event_msqid = -1;
70 
71 static nwam_error_t
72 nwam_event_alloc(nwam_event_t *eventp)
73 {
74 	assert(eventp != NULL);
75 
76 	*eventp = calloc(1, NWAM_EVENT_MAX_SIZE);
77 	if (*eventp == NULL)
78 		return (NWAM_NO_MEMORY);
79 	return (NWAM_SUCCESS);
80 }
81 
82 void
83 nwam_event_free(nwam_event_t event)
84 {
85 	if (event != NULL)
86 		free(event);
87 }
88 
89 /*
90  * Get next event in queue.
91  */
92 nwam_error_t
93 nwam_event_wait(nwam_event_t *eventp)
94 {
95 	nwam_error_t err;
96 	nwam_event_t event;
97 
98 	assert(eventp != NULL);
99 
100 	if ((err = nwam_event_alloc(&event)) != NWAM_SUCCESS)
101 		return (err);
102 	while (msgrcv(event_msqid, (struct msgbuf *)event, NWAM_EVENT_MAX_SIZE,
103 	    0, 0) == -1) {
104 		switch (errno) {
105 			case EAGAIN:
106 			case EBUSY:
107 				/*
108 				 * We see this errno eventhough it isn't
109 				 * documented.  Try again.  If this causes
110 				 * a busy loop then grab a trace otherwise
111 				 * it's a brace 'til we can figure out why it
112 				 * happens.
113 				 */
114 				continue;
115 
116 			default:
117 				nwam_event_free(event);
118 				return (nwam_errno_to_nwam_error(errno));
119 		}
120 	}
121 
122 	/* Resize event down from maximum size */
123 	if ((*eventp = realloc(event, event->nwe_size)) == NULL)
124 		return (NWAM_NO_MEMORY);
125 
126 	return (NWAM_SUCCESS);
127 }
128 
129 /*
130  * Register for receipt of events from nwamd.  Event delivery is
131  * done via a System V message queue.
132  */
133 nwam_error_t
134 nwam_events_init(void)
135 {
136 	char eventmsgfile[MAXPATHLEN];
137 	nwam_error_t err;
138 	nwam_error_t rc = NWAM_SUCCESS;
139 	key_t key;
140 
141 	(void) snprintf(eventmsgfile, sizeof (eventmsgfile), "%s.%d",
142 	    NWAM_EVENT_MSG_FILE_PREFIX, getpid());
143 
144 	(void) pthread_mutex_lock(&event_mutex);
145 
146 	if (event_msqid != -1) {
147 		rc = NWAM_ENTITY_IN_USE;
148 		goto exit;
149 	}
150 
151 	if ((err = nwam_request_register_unregister
152 	    (NWAM_REQUEST_TYPE_EVENT_REGISTER, eventmsgfile)) != NWAM_SUCCESS) {
153 		rc = err;
154 		goto exit;
155 	}
156 
157 	if ((key = ftok(eventmsgfile, 0)) == -1) {
158 		rc = nwam_errno_to_nwam_error(errno);
159 		goto exit;
160 	}
161 
162 	/* Get system-wide message queue ID */
163 	if ((event_msqid = msgget(key, 0444)) == -1) {
164 		rc = nwam_errno_to_nwam_error(errno);
165 		goto exit;
166 	}
167 
168 exit:
169 	(void) pthread_mutex_unlock(&event_mutex);
170 
171 	return (rc);
172 }
173 
174 /*
175  * Un-register for receipt of events from nwamd.  Make a request to nwamd
176  * to destroy the message queue.
177  */
178 void
179 nwam_events_fini(void)
180 {
181 	char eventmsgfile[MAXPATHLEN];
182 
183 	(void) snprintf(eventmsgfile, sizeof (eventmsgfile), "%s.%d",
184 	    NWAM_EVENT_MSG_FILE_PREFIX, getpid());
185 
186 	(void) pthread_mutex_lock(&event_mutex);
187 
188 	(void) nwam_request_register_unregister
189 	    (NWAM_REQUEST_TYPE_EVENT_UNREGISTER, eventmsgfile);
190 
191 	event_msqid = -1;
192 
193 	(void) pthread_mutex_unlock(&event_mutex);
194 }
195 
196 /*
197  * Create an event queue.  Called by nwamd to create System V message queues
198  * for clients to listen for events.
199  */
200 nwam_error_t
201 nwam_event_queue_init(const char *eventmsgfile)
202 {
203 	int fd;
204 	key_t key;
205 
206 	if ((fd = open(eventmsgfile, O_RDWR | O_CREAT | O_TRUNC, 0644)) == -1)
207 		return (nwam_errno_to_nwam_error(errno));
208 	(void) close(fd);
209 
210 	if ((key = ftok(eventmsgfile, 0)) == -1)
211 		return (nwam_errno_to_nwam_error(errno));
212 
213 	if (msgget(key, 0644 | IPC_CREAT) == -1)
214 		return (nwam_errno_to_nwam_error(errno));
215 
216 	return (NWAM_SUCCESS);
217 }
218 
219 /*
220  * Send event to registered listeners via the set of registered System V
221  * message queues.
222  */
223 nwam_error_t
224 nwam_event_send(nwam_event_t event)
225 {
226 	DIR *dirp;
227 	struct dirent *dp;
228 	struct msqid_ds buf;
229 	key_t key;
230 	int msqid;
231 	char eventmsgfile[MAXPATHLEN];
232 	nwam_error_t err = NWAM_SUCCESS;
233 
234 	if ((dirp = opendir(NWAM_EVENT_MSG_DIR)) == NULL) {
235 		return (nwam_errno_to_nwam_error(errno));
236 	}
237 
238 	/*
239 	 * For each file matching our event message queue file prefix,
240 	 * check the queue is still being read, and if so send the message.
241 	 */
242 	while ((dp = readdir(dirp)) != NULL) {
243 		if (strncmp(dp->d_name, NWAM_EVENT_MSG_FILE,
244 		    strlen(NWAM_EVENT_MSG_FILE)) != 0)
245 			continue;
246 
247 		(void) snprintf(eventmsgfile, sizeof (eventmsgfile), "%s/%s",
248 		    NWAM_EVENT_MSG_DIR, dp->d_name);
249 
250 		if ((key = ftok(eventmsgfile, 0)) == -1) {
251 			int errno_save = errno;
252 			syslog(LOG_INFO, "nwam_event_send: ftok: %s",
253 			    strerror(errno_save));
254 			err = nwam_errno_to_nwam_error(errno_save);
255 			continue;
256 		}
257 
258 		if ((msqid = msgget(key, 0644)) == -1) {
259 			int errno_save = errno;
260 			syslog(LOG_INFO, "nwam_event_send: msgget: %s",
261 			    strerror(errno_save));
262 			err = nwam_errno_to_nwam_error(errno_save);
263 			continue;
264 		}
265 
266 		/* Retrieve stats to analyse queue activity */
267 		if (msgctl(msqid, IPC_STAT, &buf) == -1) {
268 			int errno_save = errno;
269 			syslog(LOG_INFO, "nwam_event_send: msgctl: %s",
270 			    strerror(errno_save));
271 			err = nwam_errno_to_nwam_error(errno_save);
272 			continue;
273 		}
274 		/*
275 		 * If buf.msg_qnum > NWAM_EVENT_MAX_NUM_PENDING
276 		 * _and_ msg_stime is more than 10s after msg_rtime -
277 		 * indicating message(s) have been hanging around unclaimed -
278 		 * we destroy the queue as the client has most likely gone
279 		 * away. This can happen if a registered client hits Ctrl^C.
280 		 */
281 		if (buf.msg_qnum > NWAM_EVENT_MAX_NUM_PENDING &&
282 		    ((buf.msg_stime + NWAM_EVENT_WAIT_TIME) > buf.msg_rtime)) {
283 			nwam_event_queue_fini(eventmsgfile);
284 			continue;
285 		}
286 
287 		/*
288 		 * This shouldn't ever block.  If it does then log an error and
289 		 * clean up the queue.
290 		 */
291 		if (msgsnd(msqid, (struct msgbuf *)event, event->nwe_size,
292 		    IPC_NOWAIT) == -1) {
293 			int errno_save = errno;
294 			syslog(LOG_ERR, "nwam_event_send: msgsnd: %s, "
295 			    "destroying message queue %s", strerror(errno_save),
296 			    eventmsgfile);
297 			nwam_event_queue_fini(eventmsgfile);
298 			err = nwam_errno_to_nwam_error(errno_save);
299 			continue;
300 		}
301 
302 	}
303 	(void) closedir(dirp);
304 
305 	return (err);
306 }
307 
308 /*
309  * Destroy an event queue.  Called by nwamd to destroy the associated message
310  * queue.
311  */
312 void
313 nwam_event_queue_fini(const char *eventmsgfile)
314 {
315 	key_t key;
316 	int msqid;
317 
318 	if ((key = ftok(eventmsgfile, 0)) != -1 &&
319 	    (msqid = msgget(key, 0644)) != -1 &&
320 	    msgctl(msqid, IPC_RMID, NULL) != -1)
321 		(void) unlink(eventmsgfile);
322 }
323 
324 /*
325  * Stop sending events.  Called by nwamd to destroy each System V message queue
326  * registered.
327  */
328 void
329 nwam_event_send_fini(void)
330 {
331 	DIR *dirp;
332 	struct dirent *dp;
333 	char eventmsgfile[MAXPATHLEN];
334 
335 	(void) pthread_mutex_lock(&event_mutex);
336 
337 	if ((dirp = opendir(NWAM_EVENT_MSG_DIR)) == NULL) {
338 		(void) pthread_mutex_unlock(&event_mutex);
339 		return;
340 	}
341 
342 	/*
343 	 * For each file matching our event message queue file prefix,
344 	 * destroy the queue and message file.
345 	 */
346 	while ((dp = readdir(dirp)) != NULL) {
347 		if (strncmp(dp->d_name, NWAM_EVENT_MSG_FILE,
348 		    strlen(NWAM_EVENT_MSG_FILE)) != 0)
349 			continue;
350 
351 		(void) snprintf(eventmsgfile, sizeof (eventmsgfile), "%s/%s",
352 		    NWAM_EVENT_MSG_DIR, dp->d_name);
353 
354 		nwam_event_queue_fini(eventmsgfile);
355 	}
356 	(void) pthread_mutex_unlock(&event_mutex);
357 }
358