xref: /illumos-gate/usr/src/lib/libsysevent/libevchannel.c (revision 88f8b78a88cbdc6d8c1af5c3e54bc49d25095c98)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <stdio.h>
30 #include <ctype.h>
31 #include <fcntl.h>
32 #include <errno.h>
33 #include <door.h>
34 #include <unistd.h>
35 #include <stddef.h>
36 #include <stdlib.h>
37 #include <strings.h>
38 #include <sys/types.h>
39 #include <sys/varargs.h>
40 #include <sys/sysevent.h>
41 #include <sys/sysevent_impl.h>
42 
43 #include "libsysevent.h"
44 #include "libsysevent_impl.h"
45 
46 /*
47  * The functions below deal with the General Purpose Event Handling framework
48  *
49  * sysevent_evc_bind	    - create/bind application to named channel
50  * sysevent_evc_unbind	    - unbind from previously bound/created channel
51  * sysevent_evc_subscribe   - subscribe to existing event channel
52  * sysevent_evc_unsubscribe - unsubscribe from existing event channel
53  * sysevent_evc_publish	    - generate a system event via an event channel
54  * sysevent_evc_control	    - various channel based control operation
55  */
56 
57 #define	misaligned(p)	((uintptr_t)(p) & 3)	/* 4-byte alignment required */
58 
59 /*
60  * Check syntax of a channel name
61  */
62 static int
63 sysevent_is_chan_name(const char *str)
64 {
65 	for (; *str != '\0'; str++) {
66 		if (!EVCH_ISCHANCHAR(*str))
67 			return (0);
68 	}
69 
70 	return (1);
71 }
72 
73 /*
74  * Check for printable characters
75  */
76 static int
77 strisprint(const char *s)
78 {
79 	for (; *s != '\0'; s++) {
80 		if (*s < ' ' || *s > '~')
81 			return (0);
82 	}
83 
84 	return (1);
85 }
86 
87 /*
88  * sysevent_evc_bind - Create/bind application to named channel
89  */
90 int
91 sysevent_evc_bind(const char *channel, evchan_t **scpp, uint32_t flags)
92 {
93 	int chanlen;
94 	evchan_t *scp;
95 	sev_bind_args_t uargs;
96 	int ec;
97 
98 	if (scpp == NULL || misaligned(scpp)) {
99 		return (errno = EINVAL);
100 	}
101 
102 	/* Provide useful value in error case */
103 	*scpp = NULL;
104 
105 	if (channel == NULL ||
106 	    (chanlen = strlen(channel) + 1) > MAX_CHNAME_LEN) {
107 		return (errno = EINVAL);
108 	}
109 
110 	/* Check channel syntax */
111 	if (!sysevent_is_chan_name(channel)) {
112 		return (errno = EINVAL);
113 	}
114 
115 	if (flags & ~EVCH_B_FLAGS) {
116 		return (errno = EINVAL);
117 	}
118 
119 	scp = calloc(1, sizeof (evchan_impl_hdl_t));
120 	if (scp == NULL) {
121 		return (errno = ENOMEM);
122 	}
123 
124 	/*
125 	 * Enable sysevent driver.  Fallback if the device link doesn't exist;
126 	 * this situation can arise if a channel is bound early in system
127 	 * startup, prior to devfsadm(1M) being invoked.
128 	 */
129 	EV_FD(scp) = open(DEVSYSEVENT, O_RDWR);
130 	if (EV_FD(scp) == -1) {
131 		if (errno != ENOENT) {
132 			ec = errno == EACCES ? EPERM : errno;
133 			free(scp);
134 			return (errno = ec);
135 		}
136 
137 		EV_FD(scp) = open(DEVICESYSEVENT, O_RDWR);
138 		if (EV_FD(scp) == -1) {
139 			ec = errno == EACCES ? EPERM : errno;
140 			free(scp);
141 			return (errno = ec);
142 		}
143 	}
144 
145 	/*
146 	 * Force to close the fd's when process is doing exec.
147 	 * The driver will then release stale binding handles.
148 	 * The driver will release also the associated subscriptions
149 	 * if EVCH_SUB_KEEP flag was not set.
150 	 */
151 	(void) fcntl(EV_FD(scp), F_SETFD, FD_CLOEXEC);
152 
153 	uargs.chan_name.name = (uintptr_t)channel;
154 	uargs.chan_name.len = chanlen;
155 	uargs.flags = flags;
156 
157 	if (ioctl(EV_FD(scp), SEV_CHAN_OPEN, &uargs) != 0) {
158 		ec = errno;
159 		(void) close(EV_FD(scp));
160 		free(scp);
161 		return (errno = ec);
162 	}
163 
164 	/* Needed to detect a fork() */
165 	EV_PID(scp) = getpid();
166 	(void) mutex_init(EV_LOCK(scp), USYNC_THREAD, NULL);
167 
168 	*scpp = scp;
169 
170 	return (0);
171 }
172 
173 /*
174  * sysevent_evc_unbind - Unbind from previously bound/created channel
175  */
176 void
177 sysevent_evc_unbind(evchan_t *scp)
178 {
179 	sev_unsubscribe_args_t uargs;
180 	evchan_subscr_t *subp, *tofree;
181 
182 	if (scp == NULL || misaligned(scp))
183 		return;
184 
185 	(void) mutex_lock(EV_LOCK(scp));
186 
187 	/*
188 	 * Unsubscribe, if we are in the process which did the bind.
189 	 */
190 	if (EV_PID(scp) == getpid()) {
191 		uargs.sid.name = NULL;
192 		uargs.sid.len = 0;
193 		/*
194 		 * The unsubscribe ioctl will block until all door upcalls have
195 		 * drained.
196 		 */
197 		if (ioctl(EV_FD(scp), SEV_UNSUBSCRIBE, (intptr_t)&uargs) != 0) {
198 			(void) mutex_unlock(EV_LOCK(scp));
199 			return;
200 		}
201 	}
202 
203 	subp =  (evchan_subscr_t *)(void*)EV_SUB(scp);
204 	while (subp->evsub_next != NULL) {
205 		tofree = subp->evsub_next;
206 		subp->evsub_next = tofree->evsub_next;
207 		if (door_revoke(tofree->evsub_door_desc) != 0 && errno == EPERM)
208 			(void) close(tofree->evsub_door_desc);
209 		free(tofree->evsub_sid);
210 		free(tofree);
211 	}
212 
213 	(void) mutex_unlock(EV_LOCK(scp));
214 
215 	/*
216 	 * The close of the driver will do the unsubscribe if a) it is the last
217 	 * close and b) we are in a child which inherited subscriptions.
218 	 */
219 	(void) close(EV_FD(scp));
220 	(void) mutex_destroy(EV_LOCK(scp));
221 	free(scp);
222 }
223 
224 /*
225  * sysevent_evc_publish - Generate a system event via an event channel
226  */
227 int
228 sysevent_evc_publish(evchan_t *scp, const char *class,
229     const char *subclass, const char *vendor,
230     const char *pub_name, nvlist_t *attr_list,
231     uint32_t flags)
232 {
233 	sysevent_t *ev;
234 	sev_publish_args_t uargs;
235 	int rc;
236 	int ec;
237 
238 	if (scp == NULL || misaligned(scp)) {
239 		return (errno = EINVAL);
240 	}
241 
242 	/* No inheritance of binding handles via fork() */
243 	if (EV_PID(scp) != getpid()) {
244 		return (errno = EINVAL);
245 	}
246 
247 	ev = sysevent_alloc_event((char *)class, (char *)subclass,
248 	    (char *)vendor, (char *)pub_name, attr_list);
249 	if (ev == NULL) {
250 		return (errno);
251 	}
252 
253 	uargs.ev.name = (uintptr_t)ev;
254 	uargs.ev.len = SE_SIZE(ev);
255 	uargs.flags = flags;
256 
257 	(void) mutex_lock(EV_LOCK(scp));
258 
259 	rc = ioctl(EV_FD(scp), SEV_PUBLISH, (intptr_t)&uargs);
260 	ec = errno;
261 
262 	(void) mutex_unlock(EV_LOCK(scp));
263 
264 	sysevent_free(ev);
265 
266 	if (rc != 0) {
267 		return (ec);
268 	}
269 	return (0);
270 }
271 
272 /*
273  * Generic callback which catches events from the kernel and calls
274  * subscribers call back routine.
275  *
276  * Kernel guarantees that door_upcalls are disabled when unsubscription
277  * was issued that's why cookie points always to a valid evchan_subscr_t *.
278  *
279  * Furthermore it's not necessary to lock subp because the sysevent
280  * framework guarantees no unsubscription until door_return.
281  */
282 /*ARGSUSED3*/
283 static void
284 door_upcall(void *cookie, char *args, size_t alen,
285     door_desc_t *ddp, uint_t ndid)
286 {
287 	evchan_subscr_t *subp = EVCHAN_SUBSCR(cookie);
288 	int rval = 0;
289 
290 	if (args == NULL || alen <= (size_t)0) {
291 		/* Skip callback execution */
292 		rval = EINVAL;
293 	} else {
294 		rval = subp->evsub_func((sysevent_t *)(void *)args,
295 		    subp->evsub_cookie);
296 	}
297 
298 	/*
299 	 * Fill in return values for door_return
300 	 */
301 	alen = sizeof (rval);
302 	bcopy(&rval, args, alen);
303 
304 	(void) door_return(args, alen, NULL, 0);
305 }
306 
307 /*
308  * sysevent_evc_subscribe - Subscribe to an existing event channel
309  */
310 int
311 sysevent_evc_subscribe(evchan_t *scp, const char *sid, const char *class,
312     int (*event_handler)(sysevent_t *ev, void *cookie),
313     void *cookie, uint32_t flags)
314 {
315 	evchan_subscr_t *subp;
316 	int upcall_door;
317 	sev_subscribe_args_t uargs;
318 	uint32_t sid_len;
319 	uint32_t class_len;
320 	int ec;
321 
322 	if (scp == NULL || misaligned(scp) || sid == NULL || class == NULL) {
323 		return (errno = EINVAL);
324 	}
325 
326 	/* No inheritance of binding handles via fork() */
327 	if (EV_PID(scp) != getpid()) {
328 		return (errno = EINVAL);
329 	}
330 
331 	if ((sid_len = strlen(sid) + 1) > MAX_SUBID_LEN || sid_len == 1 ||
332 	    (class_len = strlen(class) + 1) > MAX_CLASS_LEN) {
333 		return (errno = EINVAL);
334 	}
335 
336 	/* Check for printable characters */
337 	if (!strisprint(sid)) {
338 		return (errno = EINVAL);
339 	}
340 
341 	if (event_handler == NULL) {
342 		return (errno = EINVAL);
343 	}
344 
345 	/* Create subscriber data */
346 	if ((subp = calloc(1, sizeof (evchan_subscr_t))) == NULL) {
347 		return (errno);
348 	}
349 
350 	if ((subp->evsub_sid = strdup(sid)) == NULL) {
351 		ec = errno;
352 		free(subp);
353 		return (ec);
354 	}
355 
356 	/*
357 	 * EC_ALL string will not be copied to kernel - NULL is assumed
358 	 */
359 	if (strcmp(class, EC_ALL) == 0) {
360 		class = NULL;
361 		class_len = 0;
362 	}
363 
364 	upcall_door = door_create(door_upcall, (void *)subp,
365 	    DOOR_REFUSE_DESC | DOOR_NO_CANCEL);
366 	if (upcall_door == -1) {
367 		ec = errno;
368 		free(subp->evsub_sid);
369 		free(subp);
370 		return (ec);
371 	}
372 
373 	/* Complete subscriber information */
374 	subp->evsub_door_desc = upcall_door;
375 	subp->evsub_func = event_handler;
376 	subp->evsub_cookie = cookie;
377 
378 	(void) mutex_lock(EV_LOCK(scp));
379 
380 	subp->ev_subhead = EVCHAN_IMPL_HNDL(scp);
381 
382 	uargs.sid.name = (uintptr_t)sid;
383 	uargs.sid.len = sid_len;
384 	uargs.class_info.name = (uintptr_t)class;
385 	uargs.class_info.len = class_len;
386 	uargs.door_desc = subp->evsub_door_desc;
387 	uargs.flags = flags;
388 	if (ioctl(EV_FD(scp), SEV_SUBSCRIBE, (intptr_t)&uargs) != 0) {
389 		ec = errno;
390 		(void) mutex_unlock(EV_LOCK(scp));
391 		(void) door_revoke(upcall_door);
392 		free(subp->evsub_sid);
393 		free(subp);
394 		return (ec);
395 	}
396 
397 	/* Attach to subscriber list */
398 	subp->evsub_next = EV_SUB_NEXT(scp);
399 	EV_SUB_NEXT(scp) = subp;
400 
401 	(void) mutex_unlock(EV_LOCK(scp));
402 
403 	return (0);
404 }
405 
406 /*
407  * sysevent_evc_unsubscribe - Unsubscribe from an existing event channel
408  */
409 void
410 sysevent_evc_unsubscribe(evchan_t *scp, const char *sid)
411 {
412 	int all_subscribers = 0;
413 	sev_unsubscribe_args_t uargs;
414 	evchan_subscr_t *subp, *tofree;
415 	int rc;
416 
417 	if (scp == NULL || misaligned(scp))
418 		return;
419 
420 	if (sid == NULL || strlen(sid) == 0 ||
421 	    (strlen(sid) >= MAX_SUBID_LEN))
422 		return;
423 
424 	/* No inheritance of binding handles via fork() */
425 	if (EV_PID(scp) != getpid()) {
426 		return;
427 	}
428 
429 	if (strcmp(sid, EVCH_ALLSUB) == 0) {
430 		all_subscribers++;
431 		/* Indicates all subscriber id's for this channel */
432 		uargs.sid.name = NULL;
433 		uargs.sid.len = 0;
434 	} else {
435 		uargs.sid.name = (uintptr_t)sid;
436 		uargs.sid.len = strlen(sid) + 1;
437 	}
438 
439 	(void) mutex_lock(EV_LOCK(scp));
440 
441 	/*
442 	 * The unsubscribe ioctl will block until all door upcalls have drained.
443 	 */
444 	rc = ioctl(EV_FD(scp), SEV_UNSUBSCRIBE, (intptr_t)&uargs);
445 
446 	if (rc != 0) {
447 		(void) mutex_unlock(EV_LOCK(scp));
448 		return;
449 	}
450 
451 	/* Search for the matching subscriber */
452 	subp =  (evchan_subscr_t *)(void*)EV_SUB(scp);
453 	while (subp->evsub_next != NULL) {
454 
455 		if (all_subscribers ||
456 		    (strcmp(subp->evsub_next->evsub_sid, sid) == 0)) {
457 
458 			tofree = subp->evsub_next;
459 			subp->evsub_next = tofree->evsub_next;
460 			(void) door_revoke(tofree->evsub_door_desc);
461 			free(tofree->evsub_sid);
462 			free(tofree);
463 			/* Freed single subscriber already */
464 			if (all_subscribers == 0) {
465 				break;
466 			}
467 		} else
468 			subp = subp->evsub_next;
469 	}
470 
471 	(void) mutex_unlock(EV_LOCK(scp));
472 }
473 
474 /*
475  * sysevent_evc_control - Various channel based control operation
476  */
477 int
478 sysevent_evc_control(evchan_t *scp, int cmd, /* arg */ ...)
479 {
480 	va_list ap;
481 	uint32_t *chlenp;
482 	sev_control_args_t uargs;
483 	int rc = 0;
484 
485 	if (scp == NULL || misaligned(scp)) {
486 		return (errno = EINVAL);
487 	}
488 
489 	/* No inheritance of binding handles via fork() */
490 	if (EV_PID(scp) != getpid()) {
491 		return (errno = EINVAL);
492 	}
493 
494 	va_start(ap, cmd);
495 
496 	uargs.cmd = cmd;
497 
498 	(void) mutex_lock(EV_LOCK(scp));
499 
500 	switch (cmd) {
501 	case EVCH_GET_CHAN_LEN:
502 	case EVCH_GET_CHAN_LEN_MAX:
503 		chlenp = va_arg(ap, uint32_t *);
504 		if (chlenp == NULL || misaligned(chlenp)) {
505 			rc = EINVAL;
506 			break;
507 		}
508 		rc = ioctl(EV_FD(scp), SEV_CHAN_CONTROL, (intptr_t)&uargs);
509 		*chlenp = uargs.value;
510 		break;
511 	case EVCH_SET_CHAN_LEN:
512 		/* Range change will be handled in framework */
513 		uargs.value = va_arg(ap, uint32_t);
514 		rc = ioctl(EV_FD(scp), SEV_CHAN_CONTROL, (intptr_t)&uargs);
515 		break;
516 	default:
517 		rc = EINVAL;
518 	}
519 
520 	(void) mutex_unlock(EV_LOCK(scp));
521 
522 	if (rc == -1) {
523 		rc = errno;
524 	}
525 
526 	va_end(ap);
527 
528 	return (errno = rc);
529 }
530