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 #include <stdio.h> 28 #include <fcntl.h> 29 #include <errno.h> 30 #include <door.h> 31 #include <unistd.h> 32 #include <stddef.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <strings.h> 36 #include <synch.h> 37 #include <sys/stat.h> 38 #include <librcm_impl.h> 39 40 #include "librcm_event.h" 41 42 #define dprint if (debug) (void) printf 43 static int debug = 1; 44 45 #define BUF_THRESHOLD 1024 /* larger bufs require a free */ 46 47 /* 48 * Lookup seq_num. We can not use the standard nvlist_lookup functions since 49 * the nvlist is not allocated with NV_UNIQUE_NAME or NV_UNIQUE_NAME_TYPE. 50 */ 51 static int 52 lookup_seq_num(nvlist_t *nvl, uint64_t *seq_num) 53 { 54 nvpair_t *nvp = NULL; 55 56 while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) { 57 if (strcmp(nvpair_name(nvp), RCM_SEQ_NUM) == 0 && 58 nvpair_type(nvp) == DATA_TYPE_UINT64) 59 return (nvpair_value_uint64(nvp, seq_num)); 60 } 61 62 return (ENOENT); 63 } 64 65 /* 66 * Get event service from a named door. 67 * 68 * This is similar to sysevent_post_event(), except that it deals with 69 * the "return buffer problem": 70 * Typically, the door service places the return buffer on the stack 71 * when calling door_return(). This places an artificial limit on the 72 * size of the return buffer. 73 * This problem is solved by placing large buffers on the heap, referenced 74 * through door_info. When client detects a large buffer, it will make a 75 * second door_call() to free the buffer. The client and the server agrees 76 * on a size, which is defined as BUF_THRESHOLD. 77 * 78 * Returns -1 if message not delivered. With errno set to cause of error. 79 * Returns 0 for success with the results returned in posting buffer. 80 */ 81 int 82 get_event_service(char *door_name, void *data, size_t datalen, 83 void **result, size_t *rlen) 84 { 85 int service_door, error; 86 door_arg_t door_arg; 87 88 /* 89 * Open the service door 90 */ 91 if ((service_door = open(door_name, O_RDONLY, 0)) == -1) { 92 errno = ESRCH; 93 return (-1); 94 } 95 96 retry1: 97 door_arg.rbuf = NULL; /* doorfs will provide return buf */ 98 door_arg.rsize = 0; 99 door_arg.data_ptr = data; 100 door_arg.data_size = datalen; 101 door_arg.desc_ptr = NULL; 102 door_arg.desc_num = 0; 103 104 /* 105 * Make door call 106 * EAGAIN is returned when the door server is temporarily 107 * out of threads to service the door call. So retry. 108 */ 109 if ((error = door_call(service_door, &door_arg)) == -1 && 110 errno == EAGAIN) { 111 (void) sleep(1); 112 goto retry1; 113 } 114 115 if ((error == 0) && result) { 116 117 uint64_t seq_num = 0; 118 119 *result = NULL; 120 *rlen = 0; 121 if (door_arg.rbuf == NULL || door_arg.rsize == 0) { 122 dprint("bad return from door call\n"); 123 (void) close(service_door); 124 errno = EFAULT; 125 return (-1); 126 } 127 128 (void) nvlist_unpack(door_arg.rbuf, door_arg.rsize, 129 (nvlist_t **)result, 0); 130 (void) munmap(door_arg.rbuf, door_arg.rsize); 131 132 /* 133 * If requiring a buf free, make another door call. There is 134 * no need to call munmap() after this door call, though. 135 */ 136 if (lookup_seq_num((nvlist_t *)*result, &seq_num) == 0) { 137 retry2: 138 door_arg.rbuf = NULL; 139 door_arg.rsize = 0; 140 door_arg.data_ptr = (char *)&seq_num; 141 door_arg.data_size = sizeof (seq_num); 142 door_arg.desc_ptr = NULL; 143 door_arg.desc_num = 0; 144 if (door_call(service_door, &door_arg) == -1) { 145 if (errno == EAGAIN) { 146 (void) sleep(1); 147 goto retry2; 148 } 149 dprint("fail to free event buf in server\n"); 150 } 151 } 152 } 153 154 (void) close(service_door); 155 return (error); 156 } 157 158 /* 159 * Export an event service door 160 */ 161 struct door_result { 162 struct door_result *next; 163 void *data; 164 uint64_t seq_num; 165 }; 166 167 typedef struct door_cookie { 168 uint64_t seq_num; 169 mutex_t door_lock; 170 void (*door_func)(void **, size_t *); 171 struct door_result *results; 172 } door_cookie_t; 173 174 /* 175 * add result to cookie, this is only invoked if result size > BUF_THRESHOLD 176 */ 177 static void 178 add_door_result(door_cookie_t *cook, void *data, uint64_t seq_num) 179 { 180 struct door_result *result; 181 182 /* 183 * Need a better way to handle memory here 184 */ 185 result = malloc(sizeof (*result)); 186 while (result == NULL) { 187 (void) sleep(1); 188 result = malloc(sizeof (*result)); 189 } 190 result->next = NULL; 191 result->data = data; 192 result->seq_num = seq_num; 193 194 /* 195 * Attach current door result to the door cookie 196 */ 197 (void) mutex_lock(&cook->door_lock); 198 if (cook->results == NULL) { 199 cook->results = result; 200 } else { 201 struct door_result *tmp = cook->results; 202 while (tmp->next) { 203 tmp = tmp->next; 204 } 205 tmp->next = result; 206 } 207 (void) mutex_unlock(&cook->door_lock); 208 } 209 210 /* 211 * free a previous door result as described by number. 212 */ 213 static void 214 free_door_result(door_cookie_t *cook, uint64_t num) 215 { 216 struct door_result *prev = NULL, *tmp; 217 218 (void) mutex_lock(&cook->door_lock); 219 tmp = cook->results; 220 while (tmp && tmp->seq_num != num) { 221 prev = tmp; 222 tmp = tmp->next; 223 } 224 225 if (tmp == NULL) { 226 dprint("attempting to free nonexistent buf: %llu\n", 227 (unsigned long long)num); 228 (void) mutex_unlock(&cook->door_lock); 229 return; 230 } 231 232 if (prev) { 233 prev->next = tmp->next; 234 } else { 235 cook->results = tmp->next; 236 } 237 (void) mutex_unlock(&cook->door_lock); 238 239 free(tmp->data); 240 free(tmp); 241 } 242 243 /*ARGSUSED*/ 244 static void 245 door_service(void *cookie, char *args, size_t alen, 246 door_desc_t *ddp, uint_t ndid) 247 { 248 nvlist_t *nvl; 249 size_t nvl_size = 0; 250 char rbuf[BUF_THRESHOLD]; 251 door_cookie_t *cook = (door_cookie_t *)cookie; 252 uint64_t seq_num = 0; 253 254 /* 255 * Special case for asking to free buffer 256 */ 257 if (alen == sizeof (uint64_t)) { 258 free_door_result(cookie, *(uint64_t *)(void *)args); 259 (void) door_return(NULL, 0, NULL, 0); 260 } 261 262 /* 263 * door_func update args to point to return results. 264 * memory for results are dynamically allocated. 265 */ 266 (*cook->door_func)((void **)&args, &alen); 267 268 /* 269 * If no results, just return 270 */ 271 if (args == NULL) { 272 dprint("null results returned from door_func().\n"); 273 (void) door_return(NULL, 0, NULL, 0); 274 } 275 276 /* Determine the size of the packed nvlist */ 277 nvl = (nvlist_t *)(void *)args; 278 args = NULL; 279 alen = 0; 280 if (errno = nvlist_size(nvl, &nvl_size, NV_ENCODE_NATIVE)) { 281 nvlist_free(nvl); 282 dprint("failure to sizeup door results: %s\n", strerror(errno)); 283 (void) door_return(NULL, 0, NULL, 0); 284 } 285 286 /* 287 * If the size of the packed nvlist would exceed the buffer threshold 288 * then get a sequence number and add it to the nvlist. 289 */ 290 if (nvl_size > BUF_THRESHOLD) { 291 (void) mutex_lock(&cook->door_lock); 292 cook->seq_num++; 293 seq_num = cook->seq_num; 294 (void) mutex_unlock(&cook->door_lock); 295 (void) nvlist_add_uint64(nvl, RCM_SEQ_NUM, seq_num); 296 } 297 298 /* Refill the args with a packed version of the nvlist */ 299 if (errno = nvlist_pack(nvl, &args, &alen, NV_ENCODE_NATIVE, 0)) { 300 nvlist_free(nvl); 301 dprint("failure to pack door results: %s\n", strerror(errno)); 302 (void) door_return(NULL, 0, NULL, 0); 303 } 304 nvlist_free(nvl); 305 306 /* 307 * Based on the size of the packed nvlist, either use the local buffer 308 * or add it to the results list. 309 */ 310 if (alen <= BUF_THRESHOLD) { 311 bcopy(args, rbuf, alen); 312 (void) free(args); 313 args = rbuf; 314 } else { 315 /* 316 * for long data, append results to end of queue in cook 317 * and set ndid, ask client to do another door_call 318 * to free the buffer. 319 */ 320 add_door_result(cook, args, seq_num); 321 } 322 323 (void) door_return(args, alen, NULL, 0); 324 } 325 326 int 327 create_event_service(char *door_name, 328 void (*func)(void **data, size_t *datalen)) 329 { 330 int service_door, fd; 331 door_cookie_t *cookie; 332 333 /* create an fs file */ 334 fd = open(door_name, O_EXCL|O_CREAT, S_IREAD|S_IWRITE); 335 if ((fd == -1) && (errno != EEXIST)) { 336 return (-1); 337 } 338 (void) close(fd); 339 340 /* allocate space for door cookie */ 341 if ((cookie = calloc(1, sizeof (*cookie))) == NULL) { 342 return (-1); 343 } 344 345 cookie->door_func = func; 346 if ((service_door = door_create(door_service, (void *)cookie, 347 DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) { 348 dprint("door create failed: %s\n", strerror(errno)); 349 free(cookie); 350 return (-1); 351 } 352 353 retry: 354 (void) fdetach(door_name); 355 if (fattach(service_door, door_name) != 0) { 356 if (errno == EBUSY) { 357 /* 358 * EBUSY error may occur if anyone references the door 359 * file while we are fattach'ing. Since librcm, in the 360 * the process context of a DR initiator program, may 361 * reference the door file (via open/close/stat/ 362 * door_call etc.) while we are still fattach'ing, 363 * retry on EBUSY. 364 */ 365 goto retry; 366 } 367 dprint("door attaching failed: %s\n", strerror(errno)); 368 free(cookie); 369 (void) close(service_door); 370 return (-1); 371 } 372 373 return (service_door); 374 } 375 376 int 377 revoke_event_service(int fd) 378 { 379 struct door_info info; 380 door_cookie_t *cookie; 381 382 if (door_info(fd, &info) == -1) { 383 return (-1); 384 } 385 386 if (door_revoke(fd) != 0) { 387 return (-1); 388 } 389 390 /* wait for existing door calls to finish */ 391 (void) sleep(1); 392 393 if ((cookie = (door_cookie_t *)(uintptr_t)info.di_data) != NULL) { 394 struct door_result *tmp = cookie->results; 395 while (tmp) { 396 cookie->results = tmp->next; 397 free(tmp->data); 398 free(tmp); 399 tmp = cookie->results; 400 } 401 free(cookie); 402 } 403 return (0); 404 } 405