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 /* 23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 * Copyright 2019 Peter Tribble. 26 */ 27 28 /* 29 * The snmp library helps to prepare the PDUs and communicate with 30 * the snmp agent on the SP side via the ds_snmp driver. 31 */ 32 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <unistd.h> 37 #include <thread.h> 38 #include <synch.h> 39 #include <errno.h> 40 #include <sys/time.h> 41 #include <sys/types.h> 42 #include <sys/stat.h> 43 #include <fcntl.h> 44 #include <libnvpair.h> 45 #include <sys/ds_snmp.h> 46 47 #include "libpiclsnmp.h" 48 #include "snmplib.h" 49 #include "asn1.h" 50 #include "pdu.h" 51 52 #pragma init(libpiclsnmp_init) /* need this in .init */ 53 54 /* 55 * Data from the MIB is fetched based on the hints about object 56 * groups received from (possibly many threads in) the application. 57 * However, the fetched data is kept in a common cache for use across 58 * all threads, so even a GETBULK is issued only when absolutely 59 * necessary. 60 * 61 * Note that locking is not fine grained (there's no locking per row) 62 * since we don't expect too many MT consumers right away. 63 * 64 */ 65 static mutex_t mibcache_lock; 66 static nvlist_t **mibcache = NULL; 67 static uint_t n_mibcache_rows = 0; 68 69 static mutex_t snmp_reqid_lock; 70 static int snmp_reqid = 1; 71 72 #ifdef USE_SOCKETS 73 #define SNMP_DEFAULT_PORT 161 74 #define SNMP_MAX_RECV_PKTSZ (64 * 1024) 75 #endif 76 77 /* 78 * We need a reliably monotonic and stable source of time values to age 79 * entries in the mibcache toward expiration. The code originally used 80 * gettimeofday(), but since that is subject to time-of-day changes made by 81 * the administrator, the values it returns do not satisfy our needs. 82 * Instead, we use gethrtime(), which is immune to time-of-day changes. 83 * However, since gethrtime() returns a signed 64-bit value in units of 84 * nanoseconds and we are using signed 32-bit timestamps, we always divide 85 * the result by (HRTIME_SCALE * NANOSEC) to scale it down into units of 10 86 * seconds. 87 * 88 * Note that the scaling factor means that the value of MAX_INCACHE_TIME 89 * from snmplib.h should also be in units of 10 seconds. 90 */ 91 #define GET_SCALED_HRTIME() (int)(gethrtime() / (HRTIME_SCALE * NANOSEC)) 92 93 /* 94 * The mibcache code originally cached values for 300 seconds after fetching 95 * data via SNMP. Subsequent reads within that 300 second window would come 96 * from the cache - which is quite a bit faster than an SNMP query - but the 97 * first request that came in more than 300 seconds after the previous SNMP 98 * query would trigger a new SNMP query. This worked well as an 99 * optimization for frequent queries, but when data was only queried less 100 * frequently than every 300 seconds (as proved to be the case at multiple 101 * customer sites), the cache didn't help at all. 102 * 103 * To improve the performance of infrequent queries, code was added to the 104 * library to allow a client (i.e. a thread in the picl plugin) to proactively 105 * refresh cache entries without waiting for them to expire, thereby ensuring 106 * that all volatile entries in the cache at any given time are less than 300 107 * seconds old. Whenever an SNMP query is generated to retrieve volatile data 108 * that will be cached, an entry is added in a refresh queue that tracks the 109 * parameters of the query and the time that it was made. A client can query 110 * the age of the oldest item in the refresh queue and - at its discretion - can 111 * then force that query to be repeated in a manner that will update the 112 * mibcache entry even though it hasn't expired. 113 */ 114 typedef struct { 115 struct picl_snmphdl *smd; 116 char *oidstrs; 117 int n_oids; 118 int row; 119 int last_fetch_time; /* in scaled hrtime */ 120 } refreshq_job_t; 121 122 static mutex_t refreshq_lock; 123 static refreshq_job_t *refreshq = NULL; 124 static uint_t n_refreshq_slots = 0; /* # of alloc'ed job slots */ 125 static uint_t n_refreshq_jobs = 0; /* # of unprocessed jobs */ 126 static uint_t refreshq_next_job = 0; /* oldest unprocessed job */ 127 static uint_t refreshq_next_slot = 0; /* next available job slot */ 128 129 130 /* 131 * Static function declarations 132 */ 133 static void libpiclsnmp_init(void); 134 135 static int lookup_int(char *, int, int *, int); 136 static int lookup_str(char *, int, char **, int); 137 static int lookup_bitstr(char *, int, uchar_t **, uint_t *, int); 138 139 static oidgroup_t *locate_oid_group(struct picl_snmphdl *, char *); 140 static int search_oid_in_group(char *, char *, int); 141 142 static snmp_pdu_t *fetch_single(struct picl_snmphdl *, char *, int, int *); 143 static snmp_pdu_t *fetch_next(struct picl_snmphdl *, char *, int, int *); 144 static void fetch_bulk(struct picl_snmphdl *, char *, int, int, int, int *); 145 static int fetch_single_str(struct picl_snmphdl *, char *, int, 146 char **, int *); 147 static int fetch_single_int(struct picl_snmphdl *, char *, int, 148 int *, int *); 149 static int fetch_single_bitstr(struct picl_snmphdl *, char *, int, 150 uchar_t **, uint_t *, int *); 151 152 static int snmp_send_request(struct picl_snmphdl *, snmp_pdu_t *, int *); 153 static int snmp_recv_reply(struct picl_snmphdl *, snmp_pdu_t *, int *); 154 155 static int mibcache_realloc(int); 156 static void mibcache_populate(snmp_pdu_t *, int); 157 static char *oid_to_oidstr(oid *, size_t); 158 159 static int refreshq_realloc(int); 160 static int refreshq_add_job(struct picl_snmphdl *, char *, int, int); 161 162 163 static void 164 libpiclsnmp_init(void) 165 { 166 (void) mutex_init(&mibcache_lock, USYNC_THREAD, NULL); 167 if (mibcache_realloc(0) < 0) 168 (void) mutex_destroy(&mibcache_lock); 169 170 (void) mutex_init(&refreshq_lock, USYNC_THREAD, NULL); 171 (void) mutex_init(&snmp_reqid_lock, USYNC_THREAD, NULL); 172 } 173 174 picl_snmphdl_t 175 snmp_init() 176 { 177 struct picl_snmphdl *smd; 178 #ifdef USE_SOCKETS 179 int sbuf = (1 << 15); /* 16K */ 180 int rbuf = (1 << 17); /* 64K */ 181 char *snmp_agent_addr; 182 #endif 183 184 smd = (struct picl_snmphdl *)calloc(1, sizeof (struct picl_snmphdl)); 185 if (smd == NULL) 186 return (NULL); 187 188 #ifdef USE_SOCKETS 189 if ((snmp_agent_addr = getenv("SNMP_AGENT_IPADDR")) == NULL) 190 return (NULL); 191 192 if ((smd->fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) 193 return (NULL); 194 195 (void) setsockopt(smd->fd, SOL_SOCKET, SO_SNDBUF, &sbuf, sizeof (int)); 196 (void) setsockopt(smd->fd, SOL_SOCKET, SO_RCVBUF, &rbuf, sizeof (int)); 197 198 memset(&smd->agent_addr, 0, sizeof (struct sockaddr_in)); 199 smd->agent_addr.sin_family = AF_INET; 200 smd->agent_addr.sin_port = htons(SNMP_DEFAULT_PORT); 201 smd->agent_addr.sin_addr.s_addr = inet_addr(snmp_agent_addr); 202 #else 203 smd->fd = open(DS_SNMP_DRIVER, O_RDWR); 204 if (smd->fd < 0) { 205 free(smd); 206 return (NULL); 207 } 208 #endif 209 210 return ((picl_snmphdl_t)smd); 211 } 212 213 void 214 snmp_fini(picl_snmphdl_t hdl) 215 { 216 struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl; 217 218 if (smd) { 219 if (smd->fd >= 0) { 220 (void) close(smd->fd); 221 } 222 free(smd); 223 } 224 } 225 226 int 227 snmp_reinit(picl_snmphdl_t hdl, int clr_linkreset) 228 { 229 struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl; 230 nvlist_t *nvl; 231 int i; 232 233 (void) mutex_lock(&mibcache_lock); 234 235 for (i = 0; i < n_mibcache_rows; i++) { 236 if ((nvl = mibcache[i]) != NULL) 237 nvlist_free(nvl); 238 } 239 240 n_mibcache_rows = 0; 241 if (mibcache) { 242 free(mibcache); 243 mibcache = NULL; 244 } 245 246 (void) mutex_unlock(&mibcache_lock); 247 248 if (clr_linkreset) { 249 if (smd == NULL || smd->fd < 0) 250 return (-1); 251 else 252 return (ioctl(smd->fd, DSSNMP_CLRLNKRESET, NULL)); 253 } 254 255 return (0); 256 } 257 258 void 259 snmp_register_group(picl_snmphdl_t hdl, char *oidstrs, int n_oids, int is_vol) 260 { 261 struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl; 262 oidgroup_t *oidg; 263 oidgroup_t *curr, *prev; 264 char *p; 265 int i, sz; 266 267 /* 268 * Allocate a new oidgroup_t 269 */ 270 oidg = (oidgroup_t *)calloc(1, sizeof (struct oidgroup)); 271 if (oidg == NULL) 272 return; 273 274 /* 275 * Determine how much space is required to register this group 276 */ 277 sz = 0; 278 p = oidstrs; 279 for (i = 0; i < n_oids; i++) { 280 sz += strlen(p) + 1; 281 p = oidstrs + sz; 282 } 283 284 /* 285 * Create this oid group 286 */ 287 if ((p = (char *)malloc(sz)) == NULL) { 288 free((void *) oidg); 289 return; 290 } 291 292 (void) memcpy(p, oidstrs, sz); 293 294 oidg->next = NULL; 295 oidg->oidstrs = p; 296 oidg->n_oids = n_oids; 297 oidg->is_volatile = is_vol; 298 299 /* 300 * Link it to the tail of the list of oid groups 301 */ 302 for (prev = NULL, curr = smd->group; curr; curr = curr->next) 303 prev = curr; 304 305 if (prev == NULL) 306 smd->group = oidg; 307 else 308 prev->next = oidg; 309 } 310 311 /* 312 * snmp_get_int() takes in an OID and returns the integer value 313 * of the object referenced in the passed arg. It returns 0 on 314 * success and -1 on failure. 315 */ 316 int 317 snmp_get_int(picl_snmphdl_t hdl, char *prefix, int row, int *val, 318 int *snmp_syserr) 319 { 320 struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl; 321 oidgroup_t *grp; 322 int ret; 323 int err = 0; 324 325 if (smd == NULL || prefix == NULL || val == NULL) 326 return (-1); 327 328 /* 329 * If this item should not be cached, fetch it directly from 330 * the agent using fetch_single_xxx() 331 */ 332 if ((grp = locate_oid_group(smd, prefix)) == NULL) { 333 ret = fetch_single_int(smd, prefix, row, val, &err); 334 335 if (snmp_syserr) 336 *snmp_syserr = err; 337 338 return (ret); 339 } 340 341 /* 342 * is it in the cache ? 343 */ 344 if (lookup_int(prefix, row, val, grp->is_volatile) == 0) 345 return (0); 346 347 /* 348 * fetch it from the agent and populate the cache 349 */ 350 fetch_bulk(smd, grp->oidstrs, grp->n_oids, row, grp->is_volatile, &err); 351 if (snmp_syserr) 352 *snmp_syserr = err; 353 354 /* 355 * look it up again and return it 356 */ 357 if (lookup_int(prefix, row, val, grp->is_volatile) < 0) 358 return (-1); 359 360 return (0); 361 } 362 363 /* 364 * snmp_get_str() takes in an OID and returns the string value 365 * of the object referenced in the passed arg. Memory for the string 366 * is allocated within snmp_get_str() and is expected to be freed by 367 * the caller when it is no longer needed. The function returns 0 368 * on success and -1 on failure. 369 */ 370 int 371 snmp_get_str(picl_snmphdl_t hdl, char *prefix, int row, char **strp, 372 int *snmp_syserr) 373 { 374 struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl; 375 oidgroup_t *grp; 376 char *val; 377 int ret; 378 int err = 0; 379 380 if (smd == NULL || prefix == NULL || strp == NULL) 381 return (-1); 382 383 *strp = NULL; 384 /* 385 * Check if this item is cacheable or not. If not, call 386 * fetch_single_* to get it directly from the agent 387 */ 388 if ((grp = locate_oid_group(smd, prefix)) == NULL) { 389 ret = fetch_single_str(smd, prefix, row, strp, &err); 390 391 if (snmp_syserr) 392 *snmp_syserr = err; 393 394 return (ret); 395 } 396 397 /* 398 * See if it's in the cache already 399 */ 400 if (lookup_str(prefix, row, &val, grp->is_volatile) == 0) { 401 if ((*strp = strdup(val)) == NULL) 402 return (-1); 403 else 404 return (0); 405 } 406 407 /* 408 * Fetch it from the agent and populate cache 409 */ 410 fetch_bulk(smd, grp->oidstrs, grp->n_oids, row, grp->is_volatile, &err); 411 if (snmp_syserr) 412 *snmp_syserr = err; 413 414 /* 415 * Retry lookup 416 */ 417 if (lookup_str(prefix, row, &val, grp->is_volatile) < 0) 418 return (-1); 419 420 421 if ((*strp = strdup(val)) == NULL) 422 return (-1); 423 else 424 return (0); 425 } 426 427 /* 428 * snmp_get_bitstr() takes in an OID and returns the bit string value 429 * of the object referenced in the passed args. Memory for the bitstring 430 * is allocated within the function and is expected to be freed by 431 * the caller when it is no longer needed. The function returns 0 432 * on success and -1 on failure. 433 */ 434 int 435 snmp_get_bitstr(picl_snmphdl_t hdl, char *prefix, int row, uchar_t **bitstrp, 436 uint_t *nbytes, int *snmp_syserr) 437 { 438 struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl; 439 oidgroup_t *grp; 440 uchar_t *val; 441 int ret; 442 int err = 0; 443 444 if (smd == NULL || prefix == NULL || bitstrp == NULL || nbytes == NULL) 445 return (-1); 446 447 *bitstrp = NULL; 448 /* 449 * Check if this item is cacheable or not. If not, call 450 * fetch_single_* to get it directly from the agent 451 */ 452 if ((grp = locate_oid_group(smd, prefix)) == NULL) { 453 ret = fetch_single_bitstr(smd, prefix, row, bitstrp, 454 nbytes, &err); 455 456 if (snmp_syserr) 457 *snmp_syserr = err; 458 459 return (ret); 460 } 461 462 /* 463 * See if it's in the cache already 464 */ 465 if (lookup_bitstr(prefix, row, &val, nbytes, grp->is_volatile) == 0) { 466 if ((*bitstrp = (uchar_t *)calloc(*nbytes, 1)) == NULL) 467 return (-1); 468 (void) memcpy(*bitstrp, (const void *)val, *nbytes); 469 return (0); 470 } 471 472 /* 473 * Fetch it from the agent and populate cache 474 */ 475 fetch_bulk(smd, grp->oidstrs, grp->n_oids, row, grp->is_volatile, &err); 476 if (snmp_syserr) 477 *snmp_syserr = err; 478 479 /* 480 * Retry lookup 481 */ 482 if (lookup_bitstr(prefix, row, &val, nbytes, grp->is_volatile) < 0) 483 return (-1); 484 485 if ((*bitstrp = (uchar_t *)calloc(*nbytes, 1)) == NULL) 486 return (-1); 487 (void) memcpy(*bitstrp, (const void *)val, *nbytes); 488 489 return (0); 490 } 491 492 /* 493 * snmp_get_nextrow() is similar in operation to SNMP_GETNEXT, but 494 * only just. In particular, this is only expected to return the next 495 * valid row number for the same object, not its value. Since we don't 496 * have any other means, we use this to determine the number of rows 497 * in the table (and the valid ones). This function returns 0 on success 498 * and -1 on failure. 499 */ 500 int 501 snmp_get_nextrow(picl_snmphdl_t hdl, char *prefix, int row, int *nextrow, 502 int *snmp_syserr) 503 { 504 struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl; 505 snmp_pdu_t *reply_pdu; 506 pdu_varlist_t *vp; 507 char *nxt_oidstr; 508 int err = 0; 509 510 if (smd == NULL || prefix == NULL || nextrow == NULL) { 511 if (snmp_syserr) 512 *snmp_syserr = EINVAL; 513 return (-1); 514 } 515 516 /* 517 * The get_nextrow results should *never* go into any cache, 518 * since these relationships are dynamically discovered each time. 519 */ 520 if ((reply_pdu = fetch_next(smd, prefix, row, &err)) == NULL) { 521 if (snmp_syserr) 522 *snmp_syserr = err; 523 return (-1); 524 } 525 526 /* 527 * We are not concerned about the "value" of the lexicographically 528 * next object; we only care about the name of that object and 529 * its row number (and whether such an object exists or not). 530 */ 531 vp = reply_pdu->vars; 532 533 /* 534 * This indicates that we're at the end of the MIB view. 535 */ 536 if (vp == NULL || vp->name == NULL || vp->type == SNMP_NOSUCHOBJECT || 537 vp->type == SNMP_NOSUCHINSTANCE || vp->type == SNMP_ENDOFMIBVIEW) { 538 snmp_free_pdu(reply_pdu); 539 if (snmp_syserr) 540 *snmp_syserr = ENOSPC; 541 return (-1); 542 } 543 544 /* 545 * need to be able to convert the OID 546 */ 547 if ((nxt_oidstr = oid_to_oidstr(vp->name, vp->name_len - 1)) == NULL) { 548 snmp_free_pdu(reply_pdu); 549 if (snmp_syserr) 550 *snmp_syserr = ENOMEM; 551 return (-1); 552 } 553 554 /* 555 * We're on to the next table. 556 */ 557 if (strcmp(nxt_oidstr, prefix) != 0) { 558 free(nxt_oidstr); 559 snmp_free_pdu(reply_pdu); 560 if (snmp_syserr) 561 *snmp_syserr = ENOENT; 562 return (-1); 563 } 564 565 /* 566 * Ok, so we've got an oid that's simply the next valid row of the 567 * passed on object, return this row number. 568 */ 569 *nextrow = (vp->name)[vp->name_len-1]; 570 571 free(nxt_oidstr); 572 snmp_free_pdu(reply_pdu); 573 574 return (0); 575 } 576 577 /* 578 * Request ids for snmp messages to the agent are sequenced here. 579 */ 580 int 581 snmp_get_reqid(void) 582 { 583 int ret; 584 585 (void) mutex_lock(&snmp_reqid_lock); 586 587 ret = snmp_reqid++; 588 589 (void) mutex_unlock(&snmp_reqid_lock); 590 591 return (ret); 592 } 593 594 static int 595 lookup_int(char *prefix, int row, int *valp, int is_vol) 596 { 597 int32_t *val_arr; 598 uint_t nelem; 599 int now; 600 int elapsed; 601 602 (void) mutex_lock(&mibcache_lock); 603 604 if (row >= n_mibcache_rows) { 605 (void) mutex_unlock(&mibcache_lock); 606 return (-1); 607 } 608 609 if (mibcache[row] == NULL) { 610 (void) mutex_unlock(&mibcache_lock); 611 return (-1); 612 } 613 614 /* 615 * If this is a volatile property, we should be searching 616 * for an integer-timestamp pair 617 */ 618 if (is_vol) { 619 if (nvlist_lookup_int32_array(mibcache[row], prefix, 620 &val_arr, &nelem) != 0) { 621 (void) mutex_unlock(&mibcache_lock); 622 return (-1); 623 } 624 if (nelem != 2 || val_arr[1] < 0) { 625 (void) mutex_unlock(&mibcache_lock); 626 return (-1); 627 } 628 now = GET_SCALED_HRTIME(); 629 elapsed = now - val_arr[1]; 630 if (elapsed < 0 || elapsed > MAX_INCACHE_TIME) { 631 (void) mutex_unlock(&mibcache_lock); 632 return (-1); 633 } 634 635 *valp = (int)val_arr[0]; 636 } else { 637 if (nvlist_lookup_int32(mibcache[row], prefix, valp) != 0) { 638 (void) mutex_unlock(&mibcache_lock); 639 return (-1); 640 } 641 } 642 643 (void) mutex_unlock(&mibcache_lock); 644 645 return (0); 646 } 647 648 static int 649 lookup_str(char *prefix, int row, char **valp, int is_vol) 650 { 651 char **val_arr; 652 uint_t nelem; 653 int now; 654 int elapsed; 655 656 (void) mutex_lock(&mibcache_lock); 657 658 if (row >= n_mibcache_rows) { 659 (void) mutex_unlock(&mibcache_lock); 660 return (-1); 661 } 662 663 if (mibcache[row] == NULL) { 664 (void) mutex_unlock(&mibcache_lock); 665 return (-1); 666 } 667 668 /* 669 * If this is a volatile property, we should be searching 670 * for a string-timestamp pair 671 */ 672 if (is_vol) { 673 if (nvlist_lookup_string_array(mibcache[row], prefix, 674 &val_arr, &nelem) != 0) { 675 (void) mutex_unlock(&mibcache_lock); 676 return (-1); 677 } 678 if (nelem != 2 || atoi(val_arr[1]) <= 0) { 679 (void) mutex_unlock(&mibcache_lock); 680 return (-1); 681 } 682 now = GET_SCALED_HRTIME(); 683 elapsed = now - atoi(val_arr[1]); 684 if (elapsed < 0 || elapsed > MAX_INCACHE_TIME) { 685 (void) mutex_unlock(&mibcache_lock); 686 return (-1); 687 } 688 689 *valp = val_arr[0]; 690 } else { 691 if (nvlist_lookup_string(mibcache[row], prefix, valp) != 0) { 692 (void) mutex_unlock(&mibcache_lock); 693 return (-1); 694 } 695 } 696 697 (void) mutex_unlock(&mibcache_lock); 698 699 return (0); 700 } 701 702 static int 703 lookup_bitstr(char *prefix, int row, uchar_t **valp, uint_t *nelem, int is_vol) 704 { 705 (void) mutex_lock(&mibcache_lock); 706 707 if (row >= n_mibcache_rows) { 708 (void) mutex_unlock(&mibcache_lock); 709 return (-1); 710 } 711 712 if (mibcache[row] == NULL) { 713 (void) mutex_unlock(&mibcache_lock); 714 return (-1); 715 } 716 717 /* 718 * We don't support volatile bit string values yet. The nvlist 719 * functions don't support bitstring arrays like they do charstring 720 * arrays, so we would need to do things in a convoluted way, 721 * probably by attaching the timestamp as part of the byte array 722 * itself. However, the need for volatile bitstrings isn't there 723 * yet, to justify the effort. 724 */ 725 if (is_vol) { 726 (void) mutex_unlock(&mibcache_lock); 727 return (-1); 728 } 729 730 if (nvlist_lookup_byte_array(mibcache[row], prefix, valp, nelem) != 0) { 731 (void) mutex_unlock(&mibcache_lock); 732 return (-1); 733 } 734 735 (void) mutex_unlock(&mibcache_lock); 736 737 return (0); 738 } 739 740 static int 741 search_oid_in_group(char *prefix, char *oidstrs, int n_oids) 742 { 743 char *p; 744 int i; 745 746 p = oidstrs; 747 for (i = 0; i < n_oids; i++) { 748 if (strcmp(p, prefix) == 0) 749 return (0); 750 751 p += strlen(p) + 1; 752 } 753 754 return (-1); 755 } 756 757 static oidgroup_t * 758 locate_oid_group(struct picl_snmphdl *smd, char *prefix) 759 { 760 oidgroup_t *grp; 761 762 if (smd == NULL) 763 return (NULL); 764 765 if (smd->group == NULL) 766 return (NULL); 767 768 for (grp = smd->group; grp; grp = grp->next) { 769 if (search_oid_in_group(prefix, grp->oidstrs, 770 grp->n_oids) == 0) { 771 return (grp); 772 } 773 } 774 775 return (NULL); 776 } 777 778 static int 779 fetch_single_int(struct picl_snmphdl *smd, char *prefix, int row, int *ival, 780 int *snmp_syserr) 781 { 782 snmp_pdu_t *reply_pdu; 783 pdu_varlist_t *vp; 784 785 if ((reply_pdu = fetch_single(smd, prefix, row, snmp_syserr)) == NULL) 786 return (-1); 787 788 /* 789 * Note that we don't make any distinction between unsigned int 790 * value and signed int value at this point, since we provide 791 * only snmp_get_int() at the higher level. While it is possible 792 * to provide an entirely separate interface such as snmp_get_uint(), 793 * that's quite unnecessary, because we don't do any interpretation 794 * of the received value. Besides, the sizes of int and uint are 795 * the same and the sizes of all pointers are the same (so val.iptr 796 * would be the same as val.uiptr in pdu_varlist_t). If/when we 797 * violate any of these assumptions, it will be time to add 798 * snmp_get_uint(). 799 */ 800 vp = reply_pdu->vars; 801 if (vp == NULL || vp->val.iptr == NULL) { 802 snmp_free_pdu(reply_pdu); 803 return (-1); 804 } 805 806 *ival = *(vp->val.iptr); 807 808 snmp_free_pdu(reply_pdu); 809 810 return (0); 811 } 812 813 static int 814 fetch_single_str(struct picl_snmphdl *smd, char *prefix, int row, char **valp, 815 int *snmp_syserr) 816 { 817 snmp_pdu_t *reply_pdu; 818 pdu_varlist_t *vp; 819 820 if ((reply_pdu = fetch_single(smd, prefix, row, snmp_syserr)) == NULL) 821 return (-1); 822 823 vp = reply_pdu->vars; 824 if (vp == NULL || vp->val.str == NULL) { 825 snmp_free_pdu(reply_pdu); 826 return (-1); 827 } 828 829 *valp = strdup((const char *)(vp->val.str)); 830 831 snmp_free_pdu(reply_pdu); 832 833 return (0); 834 } 835 836 static int 837 fetch_single_bitstr(struct picl_snmphdl *smd, char *prefix, int row, 838 uchar_t **valp, uint_t *nelem, int *snmp_syserr) 839 { 840 snmp_pdu_t *reply_pdu; 841 pdu_varlist_t *vp; 842 843 if ((reply_pdu = fetch_single(smd, prefix, row, snmp_syserr)) == NULL) 844 return (-1); 845 846 vp = reply_pdu->vars; 847 if (vp == NULL || vp->val.str == NULL) { 848 snmp_free_pdu(reply_pdu); 849 return (-1); 850 } 851 852 if ((*valp = (uchar_t *)calloc(vp->val_len, 1)) == NULL) { 853 snmp_free_pdu(reply_pdu); 854 return (-1); 855 } 856 857 *nelem = vp->val_len; 858 (void) memcpy(*valp, (const void *)(vp->val.str), 859 (size_t)(vp->val_len)); 860 861 snmp_free_pdu(reply_pdu); 862 863 return (0); 864 } 865 866 static snmp_pdu_t * 867 fetch_single(struct picl_snmphdl *smd, char *prefix, int row, int *snmp_syserr) 868 { 869 snmp_pdu_t *pdu, *reply_pdu; 870 871 if ((pdu = snmp_create_pdu(SNMP_MSG_GET, 0, prefix, 1, row)) == NULL) 872 return (NULL); 873 874 if (snmp_make_packet(pdu) < 0) { 875 snmp_free_pdu(pdu); 876 return (NULL); 877 } 878 879 if (snmp_send_request(smd, pdu, snmp_syserr) < 0) { 880 snmp_free_pdu(pdu); 881 return (NULL); 882 } 883 884 if (snmp_recv_reply(smd, pdu, snmp_syserr) < 0) { 885 snmp_free_pdu(pdu); 886 return (NULL); 887 } 888 889 reply_pdu = snmp_parse_reply(pdu->reqid, pdu->reply_pkt, 890 pdu->reply_pktsz); 891 892 snmp_free_pdu(pdu); 893 894 return (reply_pdu); 895 } 896 897 static void 898 fetch_bulk(struct picl_snmphdl *smd, char *oidstrs, int n_oids, 899 int row, int is_vol, int *snmp_syserr) 900 { 901 snmp_pdu_t *pdu, *reply_pdu; 902 int max_reps; 903 904 /* 905 * If we're fetching volatile properties using BULKGET, don't 906 * venture to get multiple rows (passing max_reps=0 will make 907 * snmp_create_pdu() fetch SNMP_DEF_MAX_REPETITIONS rows) 908 */ 909 max_reps = is_vol ? 1 : 0; 910 911 pdu = snmp_create_pdu(SNMP_MSG_GETBULK, max_reps, oidstrs, n_oids, row); 912 if (pdu == NULL) 913 return; 914 915 /* 916 * Make an ASN.1 encoded packet from the PDU information 917 */ 918 if (snmp_make_packet(pdu) < 0) { 919 snmp_free_pdu(pdu); 920 return; 921 } 922 923 /* 924 * Send the request packet to the agent 925 */ 926 if (snmp_send_request(smd, pdu, snmp_syserr) < 0) { 927 snmp_free_pdu(pdu); 928 return; 929 } 930 931 /* 932 * Receive response from the agent into the reply packet buffer 933 * in the request PDU 934 */ 935 if (snmp_recv_reply(smd, pdu, snmp_syserr) < 0) { 936 snmp_free_pdu(pdu); 937 return; 938 } 939 940 /* 941 * Parse the reply, validate the response and create a 942 * reply-PDU out of the information. Populate the mibcache 943 * with the received values. 944 */ 945 reply_pdu = snmp_parse_reply(pdu->reqid, pdu->reply_pkt, 946 pdu->reply_pktsz); 947 if (reply_pdu) { 948 if (reply_pdu->errstat == SNMP_ERR_NOERROR) { 949 if (is_vol) { 950 /* Add a job to the cache refresh work queue */ 951 (void) refreshq_add_job(smd, oidstrs, n_oids, 952 row); 953 } 954 955 mibcache_populate(reply_pdu, is_vol); 956 } 957 958 snmp_free_pdu(reply_pdu); 959 } 960 961 snmp_free_pdu(pdu); 962 } 963 964 static snmp_pdu_t * 965 fetch_next(struct picl_snmphdl *smd, char *prefix, int row, int *snmp_syserr) 966 { 967 snmp_pdu_t *pdu, *reply_pdu; 968 969 pdu = snmp_create_pdu(SNMP_MSG_GETNEXT, 0, prefix, 1, row); 970 if (pdu == NULL) 971 return (NULL); 972 973 if (snmp_make_packet(pdu) < 0) { 974 snmp_free_pdu(pdu); 975 return (NULL); 976 } 977 978 if (snmp_send_request(smd, pdu, snmp_syserr) < 0) { 979 snmp_free_pdu(pdu); 980 return (NULL); 981 } 982 983 if (snmp_recv_reply(smd, pdu, snmp_syserr) < 0) { 984 snmp_free_pdu(pdu); 985 return (NULL); 986 } 987 988 reply_pdu = snmp_parse_reply(pdu->reqid, pdu->reply_pkt, 989 pdu->reply_pktsz); 990 991 snmp_free_pdu(pdu); 992 993 return (reply_pdu); 994 } 995 996 static int 997 snmp_send_request(struct picl_snmphdl *smd, snmp_pdu_t *pdu, int *snmp_syserr) 998 { 999 extern int errno; 1000 #ifdef USE_SOCKETS 1001 int ret; 1002 #endif 1003 1004 if (smd->fd < 0) 1005 return (-1); 1006 1007 if (pdu == NULL || pdu->req_pkt == NULL) 1008 return (-1); 1009 1010 #ifdef USE_SOCKETS 1011 ret = -1; 1012 while (ret < 0) { 1013 ret = sendto(smd->fd, pdu->req_pkt, pdu->req_pktsz, 0, 1014 (struct sockaddr *)&smd->agent_addr, 1015 sizeof (struct sockaddr)); 1016 if (ret < 0 && errno != EINTR) { 1017 return (-1); 1018 } 1019 } 1020 #else 1021 if (write(smd->fd, pdu->req_pkt, pdu->req_pktsz) < 0) { 1022 if (snmp_syserr) 1023 *snmp_syserr = errno; 1024 return (-1); 1025 } 1026 #endif 1027 1028 return (0); 1029 } 1030 1031 static int 1032 snmp_recv_reply(struct picl_snmphdl *smd, snmp_pdu_t *pdu, int *snmp_syserr) 1033 { 1034 struct dssnmp_info snmp_info; 1035 size_t pktsz; 1036 uchar_t *pkt; 1037 extern int errno; 1038 #ifdef USE_SOCKETS 1039 struct sockaddr_in from; 1040 int fromlen; 1041 ssize_t msgsz; 1042 #endif 1043 1044 if (smd->fd < 0 || pdu == NULL) 1045 return (-1); 1046 1047 #ifdef USE_SOCKETS 1048 if ((pkt = (uchar_t *)calloc(1, SNMP_MAX_RECV_PKTSZ)) == NULL) 1049 return (-1); 1050 1051 fromlen = sizeof (struct sockaddr_in); 1052 1053 msgsz = recvfrom(smd->fd, pkt, SNMP_MAX_RECV_PKTSZ, 0, 1054 (struct sockaddr *)&from, &fromlen); 1055 if (msgsz < 0 || msgsz >= SNMP_MAX_RECV_PKTSZ) { 1056 free(pkt); 1057 return (-1); 1058 } 1059 1060 pktsz = (size_t)msgsz; 1061 #else 1062 /* 1063 * The ioctl will block until we have snmp data available 1064 */ 1065 if (ioctl(smd->fd, DSSNMP_GETINFO, &snmp_info) < 0) { 1066 if (snmp_syserr) 1067 *snmp_syserr = errno; 1068 return (-1); 1069 } 1070 1071 pktsz = snmp_info.size; 1072 if ((pkt = (uchar_t *)calloc(1, pktsz)) == NULL) 1073 return (-1); 1074 1075 if (read(smd->fd, pkt, pktsz) < 0) { 1076 free(pkt); 1077 if (snmp_syserr) 1078 *snmp_syserr = errno; 1079 return (-1); 1080 } 1081 #endif 1082 1083 pdu->reply_pkt = pkt; 1084 pdu->reply_pktsz = pktsz; 1085 1086 return (0); 1087 } 1088 1089 static int 1090 mibcache_realloc(int hint) 1091 { 1092 uint_t count = (uint_t)hint; 1093 nvlist_t **p; 1094 1095 if (hint < 0) 1096 return (-1); 1097 1098 (void) mutex_lock(&mibcache_lock); 1099 1100 if (hint < n_mibcache_rows) { 1101 (void) mutex_unlock(&mibcache_lock); 1102 return (0); 1103 } 1104 1105 count = ((count >> MIBCACHE_BLK_SHIFT) + 1) << MIBCACHE_BLK_SHIFT; 1106 1107 p = (nvlist_t **)calloc(count, sizeof (nvlist_t *)); 1108 if (p == NULL) { 1109 (void) mutex_unlock(&mibcache_lock); 1110 return (-1); 1111 } 1112 1113 if (mibcache) { 1114 (void) memcpy((void *) p, (void *) mibcache, 1115 n_mibcache_rows * sizeof (nvlist_t *)); 1116 free((void *) mibcache); 1117 } 1118 1119 mibcache = p; 1120 n_mibcache_rows = count; 1121 1122 (void) mutex_unlock(&mibcache_lock); 1123 1124 return (0); 1125 } 1126 1127 1128 /* 1129 * Scan each variable in the returned PDU's bindings and populate 1130 * the cache appropriately 1131 */ 1132 static void 1133 mibcache_populate(snmp_pdu_t *pdu, int is_vol) 1134 { 1135 pdu_varlist_t *vp; 1136 int row, ret; 1137 char *oidstr; 1138 int tod; /* in secs */ 1139 char tod_str[MAX_INT_LEN]; 1140 int ival_arr[2]; 1141 char *sval_arr[2]; 1142 1143 /* 1144 * If we're populating volatile properties, we also store a 1145 * timestamp with each property value. When we lookup, we check the 1146 * current time against this timestamp to determine if we need to 1147 * refetch the value or not (refetch if it has been in for far too 1148 * long). 1149 */ 1150 1151 if (is_vol) { 1152 tod = GET_SCALED_HRTIME(); 1153 1154 tod_str[0] = 0; 1155 (void) snprintf(tod_str, MAX_INT_LEN, "%d", tod); 1156 1157 ival_arr[1] = tod; 1158 sval_arr[1] = (char *)tod_str; 1159 } 1160 1161 for (vp = pdu->vars; vp; vp = vp->nextvar) { 1162 if (vp->type != ASN_INTEGER && vp->type != ASN_OCTET_STR && 1163 vp->type != ASN_BIT_STR) { 1164 continue; 1165 } 1166 1167 if (vp->name == NULL || vp->val.str == NULL) 1168 continue; 1169 1170 row = (vp->name)[vp->name_len-1]; 1171 1172 (void) mutex_lock(&mibcache_lock); 1173 1174 if (row >= n_mibcache_rows) { 1175 (void) mutex_unlock(&mibcache_lock); 1176 if (mibcache_realloc(row) < 0) 1177 continue; 1178 (void) mutex_lock(&mibcache_lock); 1179 } 1180 ret = 0; 1181 if (mibcache[row] == NULL) 1182 ret = nvlist_alloc(&mibcache[row], NV_UNIQUE_NAME, 0); 1183 1184 (void) mutex_unlock(&mibcache_lock); 1185 1186 if (ret != 0) 1187 continue; 1188 1189 /* 1190 * Convert the standard OID form into an oid string that 1191 * we can use as the key to lookup. Since we only search 1192 * by the prefix (mibcache is really an array of nvlist_t 1193 * pointers), ignore the leaf subid. 1194 */ 1195 oidstr = oid_to_oidstr(vp->name, vp->name_len - 1); 1196 if (oidstr == NULL) 1197 continue; 1198 1199 (void) mutex_lock(&mibcache_lock); 1200 1201 if (vp->type == ASN_INTEGER) { 1202 if (is_vol) { 1203 ival_arr[0] = *(vp->val.iptr); 1204 (void) nvlist_add_int32_array(mibcache[row], 1205 oidstr, ival_arr, 2); 1206 } else { 1207 (void) nvlist_add_int32(mibcache[row], 1208 oidstr, *(vp->val.iptr)); 1209 } 1210 1211 } else if (vp->type == ASN_OCTET_STR) { 1212 if (is_vol) { 1213 sval_arr[0] = (char *)vp->val.str; 1214 (void) nvlist_add_string_array(mibcache[row], 1215 oidstr, sval_arr, 2); 1216 } else { 1217 (void) nvlist_add_string(mibcache[row], 1218 oidstr, (const char *)(vp->val.str)); 1219 } 1220 } else if (vp->type == ASN_BIT_STR) { 1221 /* 1222 * We don't support yet bit string objects that are 1223 * volatile values. 1224 */ 1225 if (!is_vol) { 1226 (void) nvlist_add_byte_array(mibcache[row], 1227 oidstr, (uchar_t *)(vp->val.str), 1228 (uint_t)vp->val_len); 1229 } 1230 } 1231 (void) mutex_unlock(&mibcache_lock); 1232 1233 free(oidstr); 1234 } 1235 } 1236 1237 static char * 1238 oid_to_oidstr(oid *objid, size_t n_subids) 1239 { 1240 char *oidstr; 1241 char subid_str[MAX_INT_LEN]; 1242 int i, isize; 1243 size_t oidstr_sz; 1244 1245 /* 1246 * ugly, but for now this will have to do. 1247 */ 1248 oidstr_sz = sizeof (subid_str) * n_subids; 1249 oidstr = calloc(1, oidstr_sz); 1250 1251 for (i = 0; i < n_subids; i++) { 1252 (void) memset(subid_str, 0, sizeof (subid_str)); 1253 isize = snprintf(subid_str, sizeof (subid_str), "%d", 1254 objid[i]); 1255 if (isize >= sizeof (subid_str)) 1256 return (NULL); 1257 1258 (void) strlcat(oidstr, subid_str, oidstr_sz); 1259 if (i < (n_subids - 1)) 1260 (void) strlcat(oidstr, ".", oidstr_sz); 1261 } 1262 1263 return (oidstr); 1264 } 1265 1266 /* 1267 * Expand the refreshq to hold more cache refresh jobs. Caller must already 1268 * hold refreshq_lock mutex. Every expansion of the refreshq will add 1269 * REFRESH_BLK_SZ job slots, rather than expanding by one slot every time more 1270 * space is needed. 1271 */ 1272 static int 1273 refreshq_realloc(int hint) 1274 { 1275 uint_t count = (uint_t)hint; 1276 refreshq_job_t *p; 1277 1278 if (hint < 0) 1279 return (-1); 1280 1281 if (hint < n_refreshq_slots) { 1282 return (0); 1283 } 1284 1285 /* Round count up to next multiple of REFRESHQ_BLK_SHIFT */ 1286 count = ((count >> REFRESHQ_BLK_SHIFT) + 1) << REFRESHQ_BLK_SHIFT; 1287 1288 p = (refreshq_job_t *)calloc(count, sizeof (refreshq_job_t)); 1289 if (p == NULL) { 1290 return (-1); 1291 } 1292 1293 if (refreshq) { 1294 if (n_refreshq_jobs == 0) { 1295 /* Simple case, nothing to copy */ 1296 refreshq_next_job = 0; 1297 refreshq_next_slot = 0; 1298 } else if (refreshq_next_slot > refreshq_next_job) { 1299 /* Simple case, single copy preserves everything */ 1300 (void) memcpy((void *) p, 1301 (void *) &(refreshq[refreshq_next_job]), 1302 n_refreshq_jobs * sizeof (refreshq_job_t)); 1303 } else { 1304 /* 1305 * Complex case. The jobs in the refresh queue wrap 1306 * around the end of the array in which they are stored. 1307 * To preserve chronological order in the new allocated 1308 * array, we need to copy the jobs at the end of the old 1309 * array to the beginning of the new one and place the 1310 * jobs from the beginning of the old array after them. 1311 */ 1312 uint_t tail_jobs, head_jobs; 1313 1314 tail_jobs = n_refreshq_slots - refreshq_next_job; 1315 head_jobs = n_refreshq_jobs - tail_jobs; 1316 1317 /* Copy the jobs from the end of the old array */ 1318 (void) memcpy((void *) p, 1319 (void *) &(refreshq[refreshq_next_job]), 1320 tail_jobs * sizeof (refreshq_job_t)); 1321 1322 /* Copy the jobs from the beginning of the old array */ 1323 (void) memcpy((void *) &(p[tail_jobs]), 1324 (void *) &(refreshq[0]), 1325 head_jobs * sizeof (refreshq_job_t)); 1326 1327 /* update the job and slot indices to match */ 1328 refreshq_next_job = 0; 1329 refreshq_next_slot = n_refreshq_jobs; 1330 } 1331 free((void *) refreshq); 1332 } else { 1333 /* First initialization */ 1334 refreshq_next_job = 0; 1335 refreshq_next_slot = 0; 1336 n_refreshq_jobs = 0; 1337 } 1338 1339 refreshq = p; 1340 n_refreshq_slots = count; 1341 1342 return (0); 1343 } 1344 1345 /* 1346 * Add a new job to the refreshq. If there aren't any open slots, attempt to 1347 * expand the queue first. Return -1 if unable to add the job to the work 1348 * queue, or 0 if the job was added OR if an existing job with the same 1349 * parameters is already pending. 1350 */ 1351 static int 1352 refreshq_add_job(struct picl_snmphdl *smd, char *oidstrs, int n_oids, int row) 1353 { 1354 int i; 1355 int job; 1356 1357 (void) mutex_lock(&refreshq_lock); 1358 1359 /* 1360 * Can't do anything without a queue. Either the client never 1361 * initialized the refresh queue or the initial memory allocation 1362 * failed. 1363 */ 1364 if (refreshq == NULL) { 1365 (void) mutex_unlock(&refreshq_lock); 1366 return (-1); 1367 } 1368 1369 /* 1370 * If there is already a job pending with the same parameters as the job 1371 * we have been asked to add, we apparently let an entry expire and it 1372 * is now being reloaded. Rather than add another job for the same 1373 * entry, we skip adding the new job and let the existing job address 1374 * it. 1375 */ 1376 for (i = 0, job = refreshq_next_job; i < n_refreshq_jobs; i++, 1377 job = (job + 1) % n_refreshq_slots) { 1378 if ((refreshq[job].row == row) && 1379 (refreshq[job].n_oids == n_oids) && 1380 (refreshq[job].oidstrs == oidstrs)) { 1381 (void) mutex_unlock(&refreshq_lock); 1382 return (0); 1383 } 1384 } 1385 1386 1387 /* 1388 * If the queue is full, we need to expand it 1389 */ 1390 if (n_refreshq_jobs == n_refreshq_slots) { 1391 if (refreshq_realloc(n_refreshq_slots + 1) < 0) { 1392 /* 1393 * Can't expand the job queue, so we drop this job on 1394 * the floor. No data is lost... we just allow some 1395 * data in the mibcache to expire. 1396 */ 1397 (void) mutex_unlock(&refreshq_lock); 1398 return (-1); 1399 } 1400 } 1401 1402 /* 1403 * There is room in the queue, so add the new job. We are actually 1404 * taking a timestamp for this job that is slightly earlier than when 1405 * the mibcache entry will be updated, but since we're trying to update 1406 * the mibcache entry before it expires anyway, the earlier timestamp 1407 * here is acceptable. 1408 */ 1409 refreshq[refreshq_next_slot].smd = smd; 1410 refreshq[refreshq_next_slot].oidstrs = oidstrs; 1411 refreshq[refreshq_next_slot].n_oids = n_oids; 1412 refreshq[refreshq_next_slot].row = row; 1413 refreshq[refreshq_next_slot].last_fetch_time = GET_SCALED_HRTIME(); 1414 1415 /* 1416 * Update queue management variables 1417 */ 1418 n_refreshq_jobs += 1; 1419 refreshq_next_slot = (refreshq_next_slot + 1) % n_refreshq_slots; 1420 1421 (void) mutex_unlock(&refreshq_lock); 1422 1423 return (0); 1424 } 1425 1426 /* 1427 * Almost all of the refresh code remains dormant unless specifically 1428 * initialized by a client (the exception being that fetch_bulk() will still 1429 * call refreshq_add_job(), but the latter will return without doing anything). 1430 */ 1431 int 1432 snmp_refresh_init(void) 1433 { 1434 int ret; 1435 1436 (void) mutex_lock(&refreshq_lock); 1437 1438 ret = refreshq_realloc(0); 1439 1440 (void) mutex_unlock(&refreshq_lock); 1441 1442 return (ret); 1443 } 1444 1445 /* 1446 * If the client is going away, we don't want to keep doing refresh work, so 1447 * clean everything up. 1448 */ 1449 void 1450 snmp_refresh_fini(void) 1451 { 1452 (void) mutex_lock(&refreshq_lock); 1453 1454 n_refreshq_jobs = 0; 1455 n_refreshq_slots = 0; 1456 refreshq_next_job = 0; 1457 refreshq_next_slot = 0; 1458 free(refreshq); 1459 refreshq = NULL; 1460 1461 (void) mutex_unlock(&refreshq_lock); 1462 } 1463 1464 /* 1465 * Return the number of seconds remaining before the mibcache entry associated 1466 * with the next job in the queue will expire. Note that this requires 1467 * reversing the scaling normally done on hrtime values. (The need for scaling 1468 * is purely internal, and should be hidden from clients.) If there are no jobs 1469 * in the queue, return -1. If the next job has already expired, return 0. 1470 */ 1471 int 1472 snmp_refresh_get_next_expiration(void) 1473 { 1474 int ret; 1475 int elapsed; 1476 1477 (void) mutex_lock(&refreshq_lock); 1478 1479 if (n_refreshq_jobs == 0) { 1480 ret = -1; 1481 } else { 1482 elapsed = GET_SCALED_HRTIME() - 1483 refreshq[refreshq_next_job].last_fetch_time; 1484 1485 if (elapsed >= MAX_INCACHE_TIME) { 1486 ret = 0; 1487 } else { 1488 ret = (MAX_INCACHE_TIME - elapsed) * HRTIME_SCALE; 1489 } 1490 } 1491 1492 (void) mutex_unlock(&refreshq_lock); 1493 1494 return (ret); 1495 } 1496 1497 /* 1498 * Given the number of seconds the client wants to spend on each cyle of 1499 * processing jobs and then sleeping, return a suggestion for the number of jobs 1500 * the client should process, calculated by dividing the client's cycle duration 1501 * by MAX_INCACHE_TIME and multiplying the result by the total number of jobs in 1502 * the queue. (Note that the actual implementation of that calculation is done 1503 * in a different order to avoid losing fractional values during integer 1504 * arithmetic.) 1505 */ 1506 int 1507 snmp_refresh_get_cycle_hint(int secs) 1508 { 1509 int jobs; 1510 1511 (void) mutex_lock(&refreshq_lock); 1512 1513 /* 1514 * First, we need to scale the client's cycle time to get it into the 1515 * same units we use internally (i.e. tens of seconds). We round up, as 1516 * it makes more sense for the client to process extra jobs than 1517 * insufficient jobs. If the client's desired cycle time is greater 1518 * than MAX_INCACHE_TIME, we just return the current total number of 1519 * jobs. 1520 */ 1521 secs = (secs + HRTIME_SCALE - 1) / HRTIME_SCALE; 1522 1523 jobs = (n_refreshq_jobs * secs) / MAX_INCACHE_TIME; 1524 if (jobs > n_refreshq_jobs) { 1525 jobs = n_refreshq_jobs; 1526 } 1527 1528 (void) mutex_unlock(&refreshq_lock); 1529 1530 return (jobs); 1531 } 1532 1533 /* 1534 * Process the next job on the refresh queue by invoking fetch_bulk() with the 1535 * recorded parameters. Return -1 if no job was processed (e.g. because there 1536 * aren't any available), or 0 if a job was processed. We don't actually care 1537 * if fetch_bulk() fails, since we're just working on cache entry refreshing and 1538 * the worst case result of failing here is a longer delay getting that data the 1539 * next time it is requested. 1540 */ 1541 int 1542 snmp_refresh_process_job(void) 1543 { 1544 struct picl_snmphdl *smd; 1545 char *oidstrs; 1546 int n_oids; 1547 int row; 1548 int err; 1549 1550 (void) mutex_lock(&refreshq_lock); 1551 1552 if (n_refreshq_jobs == 0) { 1553 (void) mutex_unlock(&refreshq_lock); 1554 1555 return (-1); 1556 } 1557 1558 smd = refreshq[refreshq_next_job].smd; 1559 oidstrs = refreshq[refreshq_next_job].oidstrs; 1560 n_oids = refreshq[refreshq_next_job].n_oids; 1561 row = refreshq[refreshq_next_job].row; 1562 1563 refreshq_next_job = (refreshq_next_job + 1) % n_refreshq_slots; 1564 n_refreshq_jobs--; 1565 1566 (void) mutex_unlock(&refreshq_lock); 1567 1568 1569 /* 1570 * fetch_bulk() is going to come right back into the refresh code to add 1571 * a new job for the entry we just loaded, which means we have to make 1572 * the call without holding the refreshq_lock mutex. 1573 */ 1574 fetch_bulk(smd, oidstrs, n_oids, row, 1, &err); 1575 1576 return (0); 1577 } 1578