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