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