xref: /illumos-gate/usr/src/uts/common/io/sysevent.c (revision 7c478bd9)
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 2004 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 /*
30  * Sysevent Driver for GPEC
31  */
32 
33 #include <sys/types.h>
34 #include <sys/param.h>
35 #include <sys/cred.h>
36 #include <sys/file.h>
37 #include <sys/stat.h>
38 #include <sys/conf.h>
39 #include <sys/ddi.h>
40 #include <sys/sunddi.h>
41 #include <sys/modctl.h>
42 #include <sys/open.h>		/* OTYP_CHR definition */
43 #include <sys/sysmacros.h>	/* L_BITSMINOR definition */
44 #include <sys/bitmap.h>
45 #include <sys/sysevent.h>
46 #include <sys/sysevent_impl.h>
47 
48 static dev_info_t *sysevent_devi;
49 
50 /* Definitions for binding handle array */
51 static ulong_t sysevent_bitmap_initial = 1;	/* index 0 indicates error */
52 static ulong_t *sysevent_minor_bitmap = &sysevent_bitmap_initial;
53 static size_t sysevent_minor_bits = BT_NBIPUL;
54 static kmutex_t sysevent_minor_mutex;
55 
56 /*
57  * evchan_ctl acts as a container for the binding handle
58  */
59 typedef struct evchan_ctl {
60 	evchan_t *chp;
61 } evchan_ctl_t;
62 
63 static void *evchan_ctlp;
64 
65 /*
66  * Check if it's a null terminated array - to avoid DoS attack
67  * It is supposed that string points to an array with
68  * a minimum length of len. len must be strlen + 1.
69  * Checks for printable characters are already done in library.
70  */
71 static int
72 sysevent_isstrend(char *string, size_t len)
73 {
74 	/* Return 0 if string has length of zero */
75 	if (len > 0) {
76 		return (string[len - 1] == '\0' ? 1 : 0);
77 	} else {
78 		return (0);
79 	}
80 }
81 
82 /*
83  * Following sysevent_minor_* routines map
84  * a binding handle (evchan_t *) to a minor number
85  * Has to be called w/ locks held.
86  */
87 static ulong_t *
88 sysevent_minor_alloc(void)
89 {
90 	ulong_t *bhst = sysevent_minor_bitmap;
91 
92 	/* Increase bitmap by one BT_NBIPUL */
93 	if (sysevent_minor_bits + BT_NBIPUL > SYSEVENT_MINOR_MAX) {
94 		return ((ulong_t *)NULL);
95 	}
96 	sysevent_minor_bitmap = kmem_zalloc(
97 	    BT_SIZEOFMAP(sysevent_minor_bits + BT_NBIPUL), KM_SLEEP);
98 	bcopy(bhst, sysevent_minor_bitmap, BT_SIZEOFMAP(sysevent_minor_bits));
99 	if (bhst != &sysevent_bitmap_initial)
100 		kmem_free(bhst, BT_SIZEOFMAP(sysevent_minor_bits));
101 	sysevent_minor_bits += BT_NBIPUL;
102 
103 	return (sysevent_minor_bitmap);
104 }
105 
106 static void
107 sysevent_minor_free(ulong_t *bitmap)
108 {
109 	if (bitmap != &sysevent_bitmap_initial)
110 		kmem_free(bitmap, BT_SIZEOFMAP(sysevent_minor_bits));
111 }
112 
113 static index_t
114 sysevent_minor_get(void)
115 {
116 	index_t idx;
117 	ulong_t *bhst;
118 
119 	/* Search for an available index */
120 	mutex_enter(&sysevent_minor_mutex);
121 	if ((idx = bt_availbit(sysevent_minor_bitmap,
122 	    sysevent_minor_bits)) == -1) {
123 		/* All busy - allocate additional binding handle bitmap space */
124 		if ((bhst = sysevent_minor_alloc()) == NULL) {
125 			/* Reached our maximum of id's == SHRT_MAX */
126 			mutex_exit(&sysevent_minor_mutex);
127 			return (0);
128 		} else {
129 			sysevent_minor_bitmap = bhst;
130 		}
131 		idx = bt_availbit(sysevent_minor_bitmap, sysevent_minor_bits);
132 	}
133 	BT_SET(sysevent_minor_bitmap, idx);
134 	mutex_exit(&sysevent_minor_mutex);
135 	return (idx);
136 }
137 
138 static void
139 sysevent_minor_rele(index_t idx)
140 {
141 	mutex_enter(&sysevent_minor_mutex);
142 	ASSERT(BT_TEST(sysevent_minor_bitmap, idx) == 1);
143 	BT_CLEAR(sysevent_minor_bitmap, idx);
144 	mutex_exit(&sysevent_minor_mutex);
145 }
146 
147 static void
148 sysevent_minor_init(void)
149 {
150 	mutex_init(&sysevent_minor_mutex, NULL, MUTEX_DEFAULT, NULL);
151 }
152 
153 /* ARGSUSED */
154 static int
155 sysevent_publish(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr)
156 {
157 	int km_flags;
158 	sev_publish_args_t uargs;
159 	sysevent_impl_t *ev;
160 	evchan_ctl_t *ctl;
161 
162 	ctl = ddi_get_soft_state(evchan_ctlp, getminor(dev));
163 	if (ctl == NULL || ctl->chp == NULL)
164 		return (ENXIO);
165 
166 	if (copyin(arg, &uargs, sizeof (sev_publish_args_t)) != 0)
167 		return (EFAULT);
168 
169 	/*
170 	 * This limits the size of an event
171 	 */
172 	if (uargs.ev.len > MAX_EV_SIZE_LEN)
173 		return (EOVERFLOW);
174 
175 	/*
176 	 * Check for valid uargs.flags
177 	 */
178 	if (uargs.flags & ~(EVCH_NOSLEEP | EVCH_SLEEP | EVCH_QWAIT))
179 		return (EINVAL);
180 
181 	/*
182 	 * Check that at least one of EVCH_NOSLEEP or EVCH_SLEEP is
183 	 * specified
184 	 */
185 	km_flags = uargs.flags & (EVCH_NOSLEEP | EVCH_SLEEP);
186 	if (km_flags != EVCH_NOSLEEP && km_flags != EVCH_SLEEP)
187 		return (EINVAL);
188 
189 	ev = evch_usrallocev(uargs.ev.len, uargs.flags);
190 
191 	if (copyin((void *)(uintptr_t)uargs.ev.name, ev, uargs.ev.len) != 0) {
192 		evch_usrfreeev(ev);
193 		return (EFAULT);
194 	}
195 
196 	return (evch_usrpostevent(ctl->chp, ev, uargs.flags));
197 
198 	/* Event will be freed internally */
199 }
200 
201 /*
202  * sysevent_chan_open - used to open a channel in the GPEC channel layer
203  */
204 
205 /* ARGSUSED */
206 static int
207 sysevent_chan_open(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr)
208 {
209 	sev_bind_args_t uargs;
210 	evchan_ctl_t *ctl;
211 	char *chan_name;
212 	int ec;
213 
214 	ctl = ddi_get_soft_state(evchan_ctlp, getminor(dev));
215 	if (ctl == NULL) {
216 		return (ENXIO);
217 	}
218 
219 	if (copyin(arg, &uargs, sizeof (sev_bind_args_t)) != 0)
220 		return (EFAULT);
221 
222 	if (uargs.chan_name.len > MAX_CHNAME_LEN)
223 		return (EINVAL);
224 
225 	chan_name = kmem_alloc(uargs.chan_name.len, KM_SLEEP);
226 
227 	if (copyin((void *)(uintptr_t)uargs.chan_name.name, chan_name,
228 	    uargs.chan_name.len) != 0) {
229 		kmem_free(chan_name, uargs.chan_name.len);
230 		return (EFAULT);
231 	}
232 
233 	if (!sysevent_isstrend(chan_name, uargs.chan_name.len)) {
234 		kmem_free(chan_name, uargs.chan_name.len);
235 		return (EINVAL);
236 	}
237 
238 	/*
239 	 * Check of uargs.flags and uargs.perms just to avoid DoS attacks.
240 	 * libsysevent does this carefully
241 	 */
242 	ctl->chp = evch_usrchanopen((const char *)chan_name,
243 	    uargs.flags & EVCH_B_FLAGS, &ec);
244 
245 	kmem_free(chan_name, uargs.chan_name.len);
246 
247 	if (ec != 0) {
248 		return (ec);
249 	}
250 
251 	return (0);
252 }
253 
254 /* ARGSUSED */
255 static int
256 sysevent_chan_control(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr)
257 {
258 	sev_control_args_t uargs;
259 	evchan_ctl_t *ctl;
260 	int rc;
261 
262 	ctl = ddi_get_soft_state(evchan_ctlp, getminor(dev));
263 	if (ctl == NULL || ctl->chp == NULL)
264 		return (ENXIO);
265 
266 	if (copyin(arg, &uargs, sizeof (sev_control_args_t)) != 0)
267 		return (EFAULT);
268 
269 	switch (uargs.cmd) {
270 	case EVCH_GET_CHAN_LEN:
271 	case EVCH_GET_CHAN_LEN_MAX:
272 		rc = evch_usrcontrol_get(ctl->chp, uargs.cmd, &uargs.value);
273 		if (rc == 0) {
274 			if (copyout((void *)&uargs, arg,
275 			    sizeof (sev_control_args_t)) != 0) {
276 				rc = EFAULT;
277 			}
278 		}
279 		break;
280 	case EVCH_SET_CHAN_LEN:
281 		rc = evch_usrcontrol_set(ctl->chp, uargs.cmd, uargs.value);
282 		break;
283 	default:
284 		rc = EINVAL;
285 	}
286 	return (rc);
287 }
288 
289 /* ARGSUSED */
290 static int
291 sysevent_subscribe(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr)
292 {
293 	sev_subscribe_args_t uargs;
294 	char *sid;
295 	char *class_info = NULL;
296 	evchan_ctl_t *ctl;
297 	int rc;
298 
299 	ctl = ddi_get_soft_state(evchan_ctlp, getminor(dev));
300 	if (ctl == NULL || ctl->chp == NULL)
301 		return (ENXIO);
302 
303 	if (copyin(arg, &uargs, sizeof (sev_subscribe_args_t)) != 0)
304 		return (EFAULT);
305 
306 	if (uargs.sid.len > MAX_SUBID_LEN ||
307 	    uargs.class_info.len > MAX_CLASS_LEN)
308 		return (EINVAL);
309 
310 	sid = kmem_alloc(uargs.sid.len, KM_SLEEP);
311 	if (copyin((void *)(uintptr_t)uargs.sid.name,
312 	    sid, uargs.sid.len) != 0) {
313 		kmem_free(sid, uargs.sid.len);
314 		return (EFAULT);
315 	}
316 	if (!sysevent_isstrend(sid, uargs.sid.len)) {
317 		kmem_free(sid, uargs.sid.len);
318 		return (EINVAL);
319 	}
320 
321 	/* If class string empty then class EC_ALL is assumed */
322 	if (uargs.class_info.len != 0) {
323 		class_info = kmem_alloc(uargs.class_info.len, KM_SLEEP);
324 		if (copyin((void *)(uintptr_t)uargs.class_info.name, class_info,
325 		    uargs.class_info.len) != 0) {
326 			kmem_free(class_info, uargs.class_info.len);
327 			kmem_free(sid, uargs.sid.len);
328 			return (EFAULT);
329 		}
330 		if (!sysevent_isstrend(class_info, uargs.class_info.len)) {
331 			kmem_free(class_info, uargs.class_info.len);
332 			kmem_free(sid, uargs.sid.len);
333 			return (EINVAL);
334 		}
335 	}
336 
337 	/*
338 	 * Check of uargs.flags just to avoid DoS attacks
339 	 * libsysevent does this carefully.
340 	 */
341 	rc = evch_usrsubscribe(ctl->chp, sid, class_info,
342 	    (int)uargs.door_desc, uargs.flags);
343 
344 	kmem_free(class_info, uargs.class_info.len);
345 	kmem_free(sid, uargs.sid.len);
346 
347 	return (rc);
348 }
349 
350 /* ARGSUSED */
351 static int
352 sysevent_unsubscribe(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr)
353 {
354 	sev_unsubscribe_args_t uargs;
355 	char *sid;
356 	evchan_ctl_t *ctl;
357 
358 	if (copyin(arg, &uargs, sizeof (sev_unsubscribe_args_t)) != 0)
359 		return (EFAULT);
360 
361 	ctl = ddi_get_soft_state(evchan_ctlp, getminor(dev));
362 	if (ctl == NULL || ctl->chp == NULL)
363 		return (ENXIO);
364 
365 	if (uargs.sid.len > MAX_SUBID_LEN)
366 		return (EINVAL);
367 
368 	/* Unsubscribe for all */
369 	if (uargs.sid.len == 0) {
370 		evch_usrunsubscribe(ctl->chp, NULL, 0);
371 		return (0);
372 	}
373 
374 	sid = kmem_alloc(uargs.sid.len, KM_SLEEP);
375 
376 	if (copyin((void *)(uintptr_t)uargs.sid.name,
377 	    sid, uargs.sid.len) != 0) {
378 		kmem_free(sid, uargs.sid.len);
379 		return (EFAULT);
380 	}
381 
382 	evch_usrunsubscribe(ctl->chp, sid, 0);
383 
384 	kmem_free(sid, uargs.sid.len);
385 
386 	return (0);
387 }
388 
389 /* ARGSUSED */
390 static int
391 sysevent_channames(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr)
392 {
393 	sev_chandata_args_t uargs;
394 	char *buf;
395 	int len;
396 	int rc = 0;
397 
398 	if (copyin(arg, &uargs, sizeof (sev_chandata_args_t)) != 0)
399 		return (EFAULT);
400 
401 	if (uargs.out_data.len == 0 || uargs.out_data.len > EVCH_MAX_DATA_SIZE)
402 		return (EINVAL);
403 
404 	buf = kmem_alloc(uargs.out_data.len, KM_SLEEP);
405 
406 	if ((len = evch_usrgetchnames(buf, uargs.out_data.len)) == -1) {
407 		rc = EOVERFLOW;
408 	}
409 
410 	if (rc == 0) {
411 		ASSERT(len <= uargs.out_data.len);
412 		if (copyout(buf,
413 		    (void *)(uintptr_t)uargs.out_data.name, len) != 0) {
414 			rc = EFAULT;
415 		}
416 	}
417 
418 	kmem_free(buf, uargs.out_data.len);
419 
420 	return (rc);
421 }
422 
423 /* ARGSUSED */
424 static int
425 sysevent_chandata(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr)
426 {
427 	sev_chandata_args_t uargs;
428 	char *channel;
429 	char *buf;
430 	int len;
431 	int rc = 0;
432 
433 	if (copyin(arg, &uargs, sizeof (sev_chandata_args_t)) != 0)
434 		return (EFAULT);
435 
436 	if (uargs.in_data.len > MAX_CHNAME_LEN ||
437 	    uargs.out_data.len > EVCH_MAX_DATA_SIZE)
438 		return (EINVAL);
439 
440 	channel = kmem_alloc(uargs.in_data.len, KM_SLEEP);
441 
442 	if (copyin((void *)(uintptr_t)uargs.in_data.name, channel,
443 	    uargs.in_data.len) != 0) {
444 		kmem_free(channel, uargs.in_data.len);
445 		return (EFAULT);
446 	}
447 
448 	if (!sysevent_isstrend(channel, uargs.in_data.len)) {
449 		kmem_free(channel, uargs.in_data.len);
450 		return (EINVAL);
451 	}
452 
453 	buf = kmem_alloc(uargs.out_data.len, KM_SLEEP);
454 
455 	len = evch_usrgetchdata(channel, buf, uargs.out_data.len);
456 	if (len == 0) {
457 		rc = EOVERFLOW;
458 	} else if (len == -1) {
459 		rc = ENOENT;
460 	}
461 
462 	if (rc == 0) {
463 		ASSERT(len <= uargs.out_data.len);
464 		if (copyout(buf,
465 		    (void *)(uintptr_t)uargs.out_data.name, len) != 0) {
466 			rc = EFAULT;
467 		}
468 	}
469 
470 	kmem_free(buf, uargs.out_data.len);
471 	kmem_free(channel, uargs.in_data.len);
472 
473 	return (rc);
474 }
475 
476 /*ARGSUSED*/
477 static int
478 sysevent_ioctl(dev_t dev, int cmd, intptr_t arg,
479     int flag, cred_t *cr, int *rvalp)
480 {
481 	int rc;
482 
483 	switch (cmd) {
484 	case SEV_PUBLISH:
485 		rc = sysevent_publish(dev, rvalp, (void *)arg, flag, cr);
486 		break;
487 	case SEV_CHAN_OPEN:
488 		rc = sysevent_chan_open(dev, rvalp, (void *)arg, flag, cr);
489 		break;
490 	case SEV_CHAN_CONTROL:
491 		rc = sysevent_chan_control(dev, rvalp, (void *)arg, flag, cr);
492 		break;
493 	case SEV_SUBSCRIBE:
494 		rc = sysevent_subscribe(dev, rvalp, (void *)arg, flag, cr);
495 		break;
496 	case SEV_UNSUBSCRIBE:
497 		rc = sysevent_unsubscribe(dev, rvalp, (void *)arg, flag, cr);
498 		break;
499 	case SEV_CHANNAMES:
500 		rc = sysevent_channames(dev, rvalp, (void *)arg, flag, cr);
501 		break;
502 	case SEV_CHANDATA:
503 		rc = sysevent_chandata(dev, rvalp, (void *)arg, flag, cr);
504 		break;
505 	default:
506 		rc = EINVAL;
507 	}
508 
509 	return (rc);
510 }
511 
512 /*ARGSUSED*/
513 static int
514 sysevent_open(dev_t *devp, int flag, int otyp, cred_t *cr)
515 {
516 	int minor;
517 
518 	if (otyp != OTYP_CHR)
519 		return (EINVAL);
520 
521 	if (getminor(*devp) != 0)
522 		return (ENXIO);
523 
524 	minor = sysevent_minor_get();
525 	if (minor == 0)
526 		/* All minors are busy */
527 		return (EBUSY);
528 
529 	if (ddi_soft_state_zalloc(evchan_ctlp, minor)
530 	    != DDI_SUCCESS) {
531 		sysevent_minor_rele(minor);
532 		return (ENOMEM);
533 	}
534 
535 	*devp = makedevice(getmajor(*devp), minor);
536 
537 	return (0);
538 }
539 
540 /*ARGSUSED*/
541 static int
542 sysevent_close(dev_t dev, int flag, int otyp, cred_t *cr)
543 {
544 	int minor = (int)getminor(dev);
545 	evchan_ctl_t *ctl;
546 
547 	if (otyp != OTYP_CHR)
548 		return (EINVAL);
549 
550 	ctl = ddi_get_soft_state(evchan_ctlp, minor);
551 	if (ctl == NULL) {
552 		return (ENXIO);
553 	}
554 
555 	if (ctl->chp) {
556 		/* Release all non-persistant subscriptions */
557 		evch_usrunsubscribe(ctl->chp, NULL, EVCH_SUB_KEEP);
558 		evch_usrchanclose(ctl->chp);
559 	}
560 
561 	ddi_soft_state_free(evchan_ctlp, minor);
562 	sysevent_minor_rele(minor);
563 
564 	return (0);
565 }
566 
567 /* ARGSUSED */
568 static int
569 sysevent_info(dev_info_t *dip, ddi_info_cmd_t infocmd,
570     void *arg, void **result)
571 {
572 	switch (infocmd) {
573 	case DDI_INFO_DEVT2DEVINFO:
574 		*result = sysevent_devi;
575 		return (DDI_SUCCESS);
576 	case DDI_INFO_DEVT2INSTANCE:
577 		*result = 0;
578 		return (DDI_SUCCESS);
579 	}
580 	return (DDI_FAILURE);
581 }
582 
583 /* ARGSUSED */
584 static int
585 sysevent_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
586 {
587 
588 	if (cmd != DDI_ATTACH) {
589 		return (DDI_FAILURE);
590 	}
591 
592 	if (ddi_create_minor_node(devi, "sysevent", S_IFCHR,
593 	    0, DDI_PSEUDO, NULL) == DDI_FAILURE) {
594 		ddi_remove_minor_node(devi, NULL);
595 		return (DDI_FAILURE);
596 	}
597 	sysevent_devi = devi;
598 
599 	sysevent_minor_init();
600 
601 	return (DDI_SUCCESS);
602 }
603 
604 static int
605 sysevent_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
606 {
607 	if (cmd != DDI_DETACH) {
608 		return (DDI_FAILURE);
609 	}
610 
611 	sysevent_minor_free(sysevent_minor_bitmap);
612 	ddi_remove_minor_node(devi, NULL);
613 	return (DDI_SUCCESS);
614 }
615 
616 static struct cb_ops sysevent_cb_ops = {
617 	sysevent_open,		/* open */
618 	sysevent_close,		/* close */
619 	nodev,			/* strategy */
620 	nodev,			/* print */
621 	nodev,			/* dump */
622 	nodev,			/* read */
623 	nodev,			/* write */
624 	sysevent_ioctl,		/* ioctl */
625 	nodev,			/* devmap */
626 	nodev,			/* mmap */
627 	nodev,			/* segmap */
628 	nochpoll,		/* poll */
629 	ddi_prop_op,		/* prop_op */
630 	0,			/* streamtab  */
631 	D_NEW|D_MP,		/* flag */
632 	NULL,			/* aread */
633 	NULL			/* awrite */
634 };
635 
636 static struct dev_ops sysevent_ops = {
637 	DEVO_REV,		/* devo_rev */
638 	0,			/* refcnt  */
639 	sysevent_info,		/* info */
640 	nulldev,		/* identify */
641 	nulldev,		/* probe */
642 	sysevent_attach,	/* attach */
643 	sysevent_detach,	/* detach */
644 	nodev,			/* reset */
645 	&sysevent_cb_ops,	/* driver operations */
646 	(struct bus_ops *)0,	/* no bus operations */
647 	nulldev			/* power */
648 };
649 
650 static struct modldrv modldrv = {
651 	&mod_driverops, "sysevent driver %I%", &sysevent_ops
652 };
653 
654 static struct modlinkage modlinkage = {
655 	MODREV_1, &modldrv, NULL
656 };
657 
658 int
659 _init(void)
660 {
661 	int s;
662 
663 	s = ddi_soft_state_init(&evchan_ctlp, sizeof (evchan_ctl_t), 1);
664 	if (s != 0)
665 		return (s);
666 
667 	if ((s = mod_install(&modlinkage)) != 0)
668 		ddi_soft_state_fini(&evchan_ctlp);
669 	return (s);
670 }
671 
672 int
673 _fini(void)
674 {
675 	int s;
676 
677 	if ((s = mod_remove(&modlinkage)) != 0)
678 		return (s);
679 
680 	ddi_soft_state_fini(&evchan_ctlp);
681 	return (s);
682 }
683 
684 int
685 _info(struct modinfo *modinfop)
686 {
687 	return (mod_info(&modlinkage, modinfop));
688 }
689