1 /*- 2 * SPDX-License-Identifier: BSD-4-Clause 3 * 4 * Copyright (c) 2003 Craig Rodrigues <rodrigc@attbi.com>. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by Craig Rodrigues. 18 * 4. Neither the name of the author nor the names of any co-contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY CRAIG RODRIGUES AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 * 34 */ 35 36 /* 37 * Copyright (c) 1998 Daniel Eischen <eischen@vigrid.com>. 38 * Copyright (C) 2001 Jason Evans <jasone@freebsd.org>. 39 * Copyright (c) 2002,2003 Alexey Zelkin <phantom@FreeBSD.org> 40 * All rights reserved. 41 * 42 * Redistribution and use in source and binary forms, with or without 43 * modification, are permitted provided that the following conditions 44 * are met: 45 * 1. Redistributions of source code must retain the above copyright 46 * notice(s), this list of conditions and the following disclaimer 47 * unmodified other than the allowable addition of one or more 48 * copyright notices. 49 * 2. Redistributions in binary form must reproduce the above copyright 50 * notice(s), this list of conditions and the following disclaimer in 51 * the documentation and/or other materials provided with the 52 * distribution. 53 * 54 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY 55 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 56 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 57 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE 58 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 59 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 60 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 61 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 62 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 63 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 64 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 65 */ 66 67 /* 68 * Copyright (c) 1996 John Birrell <jb@cimlogic.com.au>. 69 * All rights reserved. 70 * 71 * Redistribution and use in source and binary forms, with or without 72 * modification, are permitted provided that the following conditions 73 * are met: 74 * 1. Redistributions of source code must retain the above copyright 75 * notice, this list of conditions and the following disclaimer. 76 * 2. Redistributions in binary form must reproduce the above copyright 77 * notice, this list of conditions and the following disclaimer in the 78 * documentation and/or other materials provided with the distribution. 79 * 3. Neither the name of the author nor the names of any co-contributors 80 * may be used to endorse or promote products derived from this software 81 * without specific prior written permission. 82 * 83 * THIS SOFTWARE IS PROVIDED BY JOHN BIRRELL AND CONTRIBUTORS ``AS IS'' AND 84 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 85 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 86 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 87 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 88 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 89 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 90 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 91 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 92 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 93 * SUCH DAMAGE. 94 */ 95 96 #include <sys/cdefs.h> 97 __FBSDID("$FreeBSD$"); 98 99 #include "namespace.h" 100 #include <errno.h> 101 #include <pthread.h> 102 #include <stdlib.h> 103 #include <string.h> 104 #include <pthread_np.h> 105 #include <sys/sysctl.h> 106 #include "un-namespace.h" 107 108 #include "thr_private.h" 109 110 static size_t _get_kern_cpuset_size(void); 111 112 __weak_reference(_pthread_attr_destroy, pthread_attr_destroy); 113 114 int 115 _pthread_attr_destroy(pthread_attr_t *attr) 116 { 117 int ret; 118 119 /* Check for invalid arguments: */ 120 if (attr == NULL || *attr == NULL) 121 /* Invalid argument: */ 122 ret = EINVAL; 123 else { 124 if ((*attr)->cpuset != NULL) 125 free((*attr)->cpuset); 126 /* Free the memory allocated to the attribute object: */ 127 free(*attr); 128 129 /* 130 * Leave the attribute pointer NULL now that the memory 131 * has been freed: 132 */ 133 *attr = NULL; 134 ret = 0; 135 } 136 return(ret); 137 } 138 139 __weak_reference(_pthread_attr_get_np, pthread_attr_get_np); 140 141 int 142 _pthread_attr_get_np(pthread_t pthread, pthread_attr_t *dstattr) 143 { 144 struct pthread *curthread; 145 struct pthread_attr attr, *dst; 146 int ret; 147 size_t kern_size; 148 149 if (pthread == NULL || dstattr == NULL || (dst = *dstattr) == NULL) 150 return (EINVAL); 151 kern_size = _get_kern_cpuset_size(); 152 if (dst->cpuset == NULL) { 153 dst->cpuset = calloc(1, kern_size); 154 dst->cpusetsize = kern_size; 155 } 156 curthread = _get_curthread(); 157 if ((ret = _thr_find_thread(curthread, pthread, /*include dead*/0)) != 0) 158 return (ret); 159 attr = pthread->attr; 160 if (pthread->flags & THR_FLAGS_DETACHED) 161 attr.flags |= PTHREAD_DETACHED; 162 ret = cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, TID(pthread), 163 dst->cpusetsize, dst->cpuset); 164 if (ret == -1) 165 ret = errno; 166 THR_THREAD_UNLOCK(curthread, pthread); 167 if (ret == 0) { 168 memcpy(&dst->pthread_attr_start_copy, 169 &attr.pthread_attr_start_copy, 170 offsetof(struct pthread_attr, pthread_attr_end_copy) - 171 offsetof(struct pthread_attr, pthread_attr_start_copy)); 172 } 173 return (ret); 174 } 175 176 __weak_reference(_pthread_attr_getdetachstate, pthread_attr_getdetachstate); 177 178 int 179 _pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate) 180 { 181 int ret; 182 183 /* Check for invalid arguments: */ 184 if (attr == NULL || *attr == NULL || detachstate == NULL) 185 ret = EINVAL; 186 else { 187 /* Check if the detached flag is set: */ 188 if ((*attr)->flags & PTHREAD_DETACHED) 189 /* Return detached: */ 190 *detachstate = PTHREAD_CREATE_DETACHED; 191 else 192 /* Return joinable: */ 193 *detachstate = PTHREAD_CREATE_JOINABLE; 194 ret = 0; 195 } 196 return(ret); 197 } 198 199 __weak_reference(_pthread_attr_getguardsize, pthread_attr_getguardsize); 200 201 int 202 _pthread_attr_getguardsize(const pthread_attr_t * __restrict attr, 203 size_t * __restrict guardsize) 204 { 205 int ret; 206 207 /* Check for invalid arguments: */ 208 if (attr == NULL || *attr == NULL || guardsize == NULL) 209 ret = EINVAL; 210 else { 211 /* Return the guard size: */ 212 *guardsize = (*attr)->guardsize_attr; 213 ret = 0; 214 } 215 return(ret); 216 } 217 218 __weak_reference(_pthread_attr_getinheritsched, pthread_attr_getinheritsched); 219 220 int 221 _pthread_attr_getinheritsched(const pthread_attr_t * __restrict attr, 222 int * __restrict sched_inherit) 223 { 224 int ret = 0; 225 226 if ((attr == NULL) || (*attr == NULL)) 227 ret = EINVAL; 228 else 229 *sched_inherit = (*attr)->sched_inherit; 230 231 return(ret); 232 } 233 234 __weak_reference(_pthread_attr_getschedparam, pthread_attr_getschedparam); 235 236 int 237 _pthread_attr_getschedparam(const pthread_attr_t * __restrict attr, 238 struct sched_param * __restrict param) 239 { 240 int ret = 0; 241 242 if ((attr == NULL) || (*attr == NULL) || (param == NULL)) 243 ret = EINVAL; 244 else 245 param->sched_priority = (*attr)->prio; 246 247 return(ret); 248 } 249 250 __weak_reference(_pthread_attr_getschedpolicy, pthread_attr_getschedpolicy); 251 252 int 253 _pthread_attr_getschedpolicy(const pthread_attr_t * __restrict attr, 254 int * __restrict policy) 255 { 256 int ret = 0; 257 258 if ((attr == NULL) || (*attr == NULL) || (policy == NULL)) 259 ret = EINVAL; 260 else 261 *policy = (*attr)->sched_policy; 262 263 return(ret); 264 } 265 266 __weak_reference(_pthread_attr_getscope, pthread_attr_getscope); 267 268 int 269 _pthread_attr_getscope(const pthread_attr_t * __restrict attr, 270 int * __restrict contentionscope) 271 { 272 int ret = 0; 273 274 if ((attr == NULL) || (*attr == NULL) || (contentionscope == NULL)) 275 /* Return an invalid argument: */ 276 ret = EINVAL; 277 278 else 279 *contentionscope = (*attr)->flags & PTHREAD_SCOPE_SYSTEM ? 280 PTHREAD_SCOPE_SYSTEM : PTHREAD_SCOPE_PROCESS; 281 282 return(ret); 283 } 284 285 __weak_reference(_pthread_attr_getstack, pthread_attr_getstack); 286 287 int 288 _pthread_attr_getstack(const pthread_attr_t * __restrict attr, 289 void ** __restrict stackaddr, 290 size_t * __restrict stacksize) 291 { 292 int ret; 293 294 /* Check for invalid arguments: */ 295 if (attr == NULL || *attr == NULL || stackaddr == NULL 296 || stacksize == NULL ) 297 ret = EINVAL; 298 else { 299 /* Return the stack address and size */ 300 *stackaddr = (*attr)->stackaddr_attr; 301 *stacksize = (*attr)->stacksize_attr; 302 ret = 0; 303 } 304 return(ret); 305 } 306 307 __weak_reference(_pthread_attr_getstackaddr, pthread_attr_getstackaddr); 308 309 int 310 _pthread_attr_getstackaddr(const pthread_attr_t *attr, void **stackaddr) 311 { 312 int ret; 313 314 /* Check for invalid arguments: */ 315 if (attr == NULL || *attr == NULL || stackaddr == NULL) 316 ret = EINVAL; 317 else { 318 /* Return the stack address: */ 319 *stackaddr = (*attr)->stackaddr_attr; 320 ret = 0; 321 } 322 return(ret); 323 } 324 325 __weak_reference(_pthread_attr_getstacksize, pthread_attr_getstacksize); 326 327 int 328 _pthread_attr_getstacksize(const pthread_attr_t * __restrict attr, 329 size_t * __restrict stacksize) 330 { 331 int ret; 332 333 /* Check for invalid arguments: */ 334 if (attr == NULL || *attr == NULL || stacksize == NULL) 335 ret = EINVAL; 336 else { 337 /* Return the stack size: */ 338 *stacksize = (*attr)->stacksize_attr; 339 ret = 0; 340 } 341 return(ret); 342 } 343 344 __weak_reference(_pthread_attr_init, pthread_attr_init); 345 346 int 347 _pthread_attr_init(pthread_attr_t *attr) 348 { 349 int ret; 350 pthread_attr_t pattr; 351 352 _thr_check_init(); 353 354 /* Allocate memory for the attribute object: */ 355 if ((pattr = (pthread_attr_t) malloc(sizeof(struct pthread_attr))) == NULL) 356 /* Insufficient memory: */ 357 ret = ENOMEM; 358 else { 359 /* Initialise the attribute object with the defaults: */ 360 memcpy(pattr, &_pthread_attr_default, sizeof(struct pthread_attr)); 361 362 /* Return a pointer to the attribute object: */ 363 *attr = pattr; 364 ret = 0; 365 } 366 return(ret); 367 } 368 369 __weak_reference(_pthread_attr_setcreatesuspend_np, pthread_attr_setcreatesuspend_np); 370 371 int 372 _pthread_attr_setcreatesuspend_np(pthread_attr_t *attr) 373 { 374 int ret; 375 376 if (attr == NULL || *attr == NULL) { 377 ret = EINVAL; 378 } else { 379 (*attr)->suspend = THR_CREATE_SUSPENDED; 380 ret = 0; 381 } 382 return(ret); 383 } 384 385 __weak_reference(_pthread_attr_setdetachstate, pthread_attr_setdetachstate); 386 387 int 388 _pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate) 389 { 390 int ret; 391 392 /* Check for invalid arguments: */ 393 if (attr == NULL || *attr == NULL || 394 (detachstate != PTHREAD_CREATE_DETACHED && 395 detachstate != PTHREAD_CREATE_JOINABLE)) 396 ret = EINVAL; 397 else { 398 /* Check if detached state: */ 399 if (detachstate == PTHREAD_CREATE_DETACHED) 400 /* Set the detached flag: */ 401 (*attr)->flags |= PTHREAD_DETACHED; 402 else 403 /* Reset the detached flag: */ 404 (*attr)->flags &= ~PTHREAD_DETACHED; 405 ret = 0; 406 } 407 return(ret); 408 } 409 410 __weak_reference(_pthread_attr_setguardsize, pthread_attr_setguardsize); 411 412 int 413 _pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize) 414 { 415 int ret; 416 417 /* Check for invalid arguments. */ 418 if (attr == NULL || *attr == NULL) 419 ret = EINVAL; 420 else { 421 /* Save the stack size. */ 422 (*attr)->guardsize_attr = guardsize; 423 ret = 0; 424 } 425 return(ret); 426 } 427 428 __weak_reference(_pthread_attr_setinheritsched, pthread_attr_setinheritsched); 429 430 int 431 _pthread_attr_setinheritsched(pthread_attr_t *attr, int sched_inherit) 432 { 433 int ret = 0; 434 435 if ((attr == NULL) || (*attr == NULL)) 436 ret = EINVAL; 437 else if (sched_inherit != PTHREAD_INHERIT_SCHED && 438 sched_inherit != PTHREAD_EXPLICIT_SCHED) 439 ret = ENOTSUP; 440 else 441 (*attr)->sched_inherit = sched_inherit; 442 443 return(ret); 444 } 445 446 __weak_reference(_pthread_attr_setschedparam, pthread_attr_setschedparam); 447 448 int 449 _pthread_attr_setschedparam(pthread_attr_t * __restrict attr, 450 const struct sched_param * __restrict param) 451 { 452 int policy; 453 454 if ((attr == NULL) || (*attr == NULL)) 455 return (EINVAL); 456 457 if (param == NULL) 458 return (ENOTSUP); 459 460 policy = (*attr)->sched_policy; 461 462 if (policy == SCHED_FIFO || policy == SCHED_RR) { 463 if (param->sched_priority < _thr_priorities[policy-1].pri_min || 464 param->sched_priority > _thr_priorities[policy-1].pri_max) 465 return (ENOTSUP); 466 } else { 467 /* 468 * Ignore it for SCHED_OTHER now, patches for glib ports 469 * are wrongly using M:N thread library's internal macro 470 * THR_MIN_PRIORITY and THR_MAX_PRIORITY. 471 */ 472 } 473 474 (*attr)->prio = param->sched_priority; 475 476 return (0); 477 } 478 479 __weak_reference(_pthread_attr_setschedpolicy, pthread_attr_setschedpolicy); 480 481 int 482 _pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy) 483 { 484 int ret = 0; 485 486 if ((attr == NULL) || (*attr == NULL)) 487 ret = EINVAL; 488 else if ((policy < SCHED_FIFO) || (policy > SCHED_RR)) { 489 ret = ENOTSUP; 490 } else { 491 (*attr)->sched_policy = policy; 492 (*attr)->prio = _thr_priorities[policy-1].pri_default; 493 } 494 return(ret); 495 } 496 497 __weak_reference(_pthread_attr_setscope, pthread_attr_setscope); 498 499 int 500 _pthread_attr_setscope(pthread_attr_t *attr, int contentionscope) 501 { 502 int ret = 0; 503 504 if ((attr == NULL) || (*attr == NULL)) { 505 /* Return an invalid argument: */ 506 ret = EINVAL; 507 } else if ((contentionscope != PTHREAD_SCOPE_PROCESS) && 508 (contentionscope != PTHREAD_SCOPE_SYSTEM)) { 509 ret = EINVAL; 510 } else if (contentionscope == PTHREAD_SCOPE_SYSTEM) { 511 (*attr)->flags |= contentionscope; 512 } else { 513 (*attr)->flags &= ~PTHREAD_SCOPE_SYSTEM; 514 } 515 return (ret); 516 } 517 518 __weak_reference(_pthread_attr_setstack, pthread_attr_setstack); 519 520 int 521 _pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, 522 size_t stacksize) 523 { 524 int ret; 525 526 /* Check for invalid arguments: */ 527 if (attr == NULL || *attr == NULL || stackaddr == NULL 528 || stacksize < PTHREAD_STACK_MIN) 529 ret = EINVAL; 530 else { 531 /* Save the stack address and stack size */ 532 (*attr)->stackaddr_attr = stackaddr; 533 (*attr)->stacksize_attr = stacksize; 534 ret = 0; 535 } 536 return(ret); 537 } 538 539 __weak_reference(_pthread_attr_setstackaddr, pthread_attr_setstackaddr); 540 541 int 542 _pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr) 543 { 544 int ret; 545 546 /* Check for invalid arguments: */ 547 if (attr == NULL || *attr == NULL || stackaddr == NULL) 548 ret = EINVAL; 549 else { 550 /* Save the stack address: */ 551 (*attr)->stackaddr_attr = stackaddr; 552 ret = 0; 553 } 554 return(ret); 555 } 556 557 __weak_reference(_pthread_attr_setstacksize, pthread_attr_setstacksize); 558 559 int 560 _pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize) 561 { 562 int ret; 563 564 /* Check for invalid arguments: */ 565 if (attr == NULL || *attr == NULL || stacksize < PTHREAD_STACK_MIN) 566 ret = EINVAL; 567 else { 568 /* Save the stack size: */ 569 (*attr)->stacksize_attr = stacksize; 570 ret = 0; 571 } 572 return(ret); 573 } 574 575 static size_t 576 _get_kern_cpuset_size(void) 577 { 578 static int kern_cpuset_size = 0; 579 580 if (kern_cpuset_size == 0) { 581 size_t len; 582 583 len = sizeof(kern_cpuset_size); 584 if (sysctlbyname("kern.sched.cpusetsize", &kern_cpuset_size, 585 &len, NULL, 0)) 586 PANIC("failed to get sysctl kern.sched.cpusetsize"); 587 } 588 589 return (kern_cpuset_size); 590 } 591 592 __weak_reference(_pthread_attr_setaffinity_np, pthread_attr_setaffinity_np); 593 int 594 _pthread_attr_setaffinity_np(pthread_attr_t *pattr, size_t cpusetsize, 595 const cpuset_t *cpusetp) 596 { 597 pthread_attr_t attr; 598 int ret; 599 600 if (pattr == NULL || (attr = (*pattr)) == NULL) 601 ret = EINVAL; 602 else { 603 if (cpusetsize == 0 || cpusetp == NULL) { 604 if (attr->cpuset != NULL) { 605 free(attr->cpuset); 606 attr->cpuset = NULL; 607 attr->cpusetsize = 0; 608 } 609 return (0); 610 } 611 size_t kern_size = _get_kern_cpuset_size(); 612 /* Kernel rejects small set, we check it here too. */ 613 if (cpusetsize < kern_size) 614 return (ERANGE); 615 if (cpusetsize > kern_size) { 616 /* Kernel checks invalid bits, we check it here too. */ 617 size_t i; 618 for (i = kern_size; i < cpusetsize; ++i) { 619 if (((const char *)cpusetp)[i]) 620 return (EINVAL); 621 } 622 } 623 if (attr->cpuset == NULL) { 624 attr->cpuset = calloc(1, kern_size); 625 if (attr->cpuset == NULL) 626 return (errno); 627 attr->cpusetsize = kern_size; 628 } 629 memcpy(attr->cpuset, cpusetp, kern_size); 630 ret = 0; 631 } 632 return (ret); 633 } 634 635 __weak_reference(_pthread_attr_getaffinity_np, pthread_attr_getaffinity_np); 636 int 637 _pthread_attr_getaffinity_np(const pthread_attr_t *pattr, size_t cpusetsize, 638 cpuset_t *cpusetp) 639 { 640 pthread_attr_t attr; 641 int ret = 0; 642 643 if (pattr == NULL || (attr = (*pattr)) == NULL) 644 ret = EINVAL; 645 else { 646 /* Kernel rejects small set, we check it here too. */ 647 size_t kern_size = _get_kern_cpuset_size(); 648 if (cpusetsize < kern_size) 649 return (ERANGE); 650 if (attr->cpuset != NULL) 651 memcpy(cpusetp, attr->cpuset, MIN(cpusetsize, 652 attr->cpusetsize)); 653 else 654 memset(cpusetp, -1, kern_size); 655 if (cpusetsize > kern_size) 656 memset(((char *)cpusetp) + kern_size, 0, 657 cpusetsize - kern_size); 658 } 659 return (ret); 660 } 661