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 */
67static pthread_mutex_t event_mutex = PTHREAD_MUTEX_INITIALIZER;
68static int event_msqid = -1;
69
70static nwam_error_t
71nwam_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
81void
82nwam_event_free(nwam_event_t event)
83{
84	if (event != NULL)
85		free(event);
86}
87
88/*
89 * Get next event in queue.
90 */
91nwam_error_t
92nwam_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 */
132nwam_error_t
133nwam_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
167exit:
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 */
177void
178nwam_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 */
199nwam_error_t
200nwam_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 */
222nwam_error_t
223nwam_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 */
311void
312nwam_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 */
327void
328nwam_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