1 /* $OpenBSD: sys_process.c,v 1.48 2011/04/02 17:04:35 guenther Exp $ */ 2 /* $NetBSD: sys_process.c,v 1.55 1996/05/15 06:17:47 tls Exp $ */ 3 4 /*- 5 * Copyright (c) 1994 Christopher G. Demetriou. All rights reserved. 6 * Copyright (c) 1982, 1986, 1989, 1993 7 * The Regents of the University of California. All rights reserved. 8 * (c) UNIX System Laboratories, Inc. 9 * All or some portions of this file are derived from material licensed 10 * to the University of California by American Telephone and Telegraph 11 * Co. or Unix System Laboratories, Inc. and are reproduced herein with 12 * the permission of UNIX System Laboratories, Inc. 13 * 14 * Redistribution and use in source and binary forms, with or without 15 * modification, are permitted provided that the following conditions 16 * are met: 17 * 1. Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * 2. Redistributions in binary form must reproduce the above copyright 20 * notice, this list of conditions and the following disclaimer in the 21 * documentation and/or other materials provided with the distribution. 22 * 3. Neither the name of the University nor the names of its contributors 23 * may be used to endorse or promote products derived from this software 24 * without specific prior written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 36 * SUCH DAMAGE. 37 * 38 * from: @(#)sys_process.c 8.1 (Berkeley) 6/10/93 39 */ 40 41 /* 42 * References: 43 * (1) Bach's "The Design of the UNIX Operating System", 44 * (2) sys/miscfs/procfs from UCB's 4.4BSD-Lite distribution, 45 * (3) the "4.4BSD Programmer's Reference Manual" published 46 * by USENIX and O'Reilly & Associates. 47 * The 4.4BSD PRM does a reasonably good job of documenting what the various 48 * ptrace() requests should actually do, and its text is quoted several times 49 * in this file. 50 */ 51 52 #include <sys/param.h> 53 #include <sys/systm.h> 54 #include <sys/exec.h> 55 #include <sys/proc.h> 56 #include <sys/signalvar.h> 57 #include <sys/errno.h> 58 #include <sys/malloc.h> 59 #include <sys/ptrace.h> 60 #include <sys/uio.h> 61 #include <sys/sched.h> 62 63 #include <sys/mount.h> 64 #include <sys/syscallargs.h> 65 66 #include <uvm/uvm_extern.h> 67 68 #include <machine/reg.h> 69 70 int process_auxv_offset(struct proc *, struct proc *, struct uio *); 71 72 #ifdef PTRACE 73 /* 74 * Process debugging system call. 75 */ 76 int 77 sys_ptrace(struct proc *p, void *v, register_t *retval) 78 { 79 struct sys_ptrace_args /* { 80 syscallarg(int) req; 81 syscallarg(pid_t) pid; 82 syscallarg(caddr_t) addr; 83 syscallarg(int) data; 84 } */ *uap = v; 85 struct proc *t; /* target process */ 86 struct uio uio; 87 struct iovec iov; 88 struct ptrace_io_desc piod; 89 struct ptrace_event pe; 90 struct reg *regs; 91 #if defined (PT_SETFPREGS) || defined (PT_GETFPREGS) 92 struct fpreg *fpregs; 93 #endif 94 #if defined (PT_SETXMMREGS) || defined (PT_GETXMMREGS) 95 struct xmmregs *xmmregs; 96 #endif 97 #ifdef PT_WCOOKIE 98 register_t wcookie; 99 #endif 100 int error, write; 101 int temp; 102 int req; 103 int s; 104 105 /* "A foolish consistency..." XXX */ 106 if (SCARG(uap, req) == PT_TRACE_ME) 107 t = p; 108 else { 109 110 /* Find the process we're supposed to be operating on. */ 111 if ((t = pfind(SCARG(uap, pid))) == NULL) 112 return (ESRCH); 113 } 114 115 if ((t->p_flag & P_INEXEC) != 0) 116 return (EAGAIN); 117 118 /* Make sure we can operate on it. */ 119 switch (SCARG(uap, req)) { 120 case PT_TRACE_ME: 121 /* Saying that you're being traced is always legal. */ 122 break; 123 124 case PT_ATTACH: 125 /* 126 * You can't attach to a process if: 127 * (1) it's the process that's doing the attaching, 128 */ 129 if (t->p_pid == p->p_pid) 130 return (EINVAL); 131 132 /* 133 * (2) it's a system process 134 */ 135 if (ISSET(t->p_flag, P_SYSTEM)) 136 return (EPERM); 137 138 /* 139 * (3) it's already being traced, or 140 */ 141 if (ISSET(t->p_flag, P_TRACED)) 142 return (EBUSY); 143 144 /* 145 * (4) it's not owned by you, or the last exec 146 * gave us setuid/setgid privs (unless 147 * you're root), or... 148 * 149 * [Note: once PS_SUGID or PS_SUGIDEXEC gets set in 150 * execve(), they stay set until the process does 151 * another execve(). Hence this prevents a setuid 152 * process which revokes its special privileges using 153 * setuid() from being traced. This is good security.] 154 */ 155 if ((t->p_cred->p_ruid != p->p_cred->p_ruid || 156 ISSET(t->p_p->ps_flags, PS_SUGIDEXEC | PS_SUGID)) && 157 (error = suser(p, 0)) != 0) 158 return (error); 159 160 /* 161 * (5) ...it's init, which controls the security level 162 * of the entire system, and the system was not 163 * compiled with permanently insecure mode turned 164 * on. 165 */ 166 if ((t->p_pid == 1) && (securelevel > -1)) 167 return (EPERM); 168 169 /* 170 * (6) it's an ancestor of the current process and 171 * not init (because that would create a loop in 172 * the process graph). 173 */ 174 if (t->p_pid != 1 && inferior(p->p_p, t->p_p)) 175 return (EINVAL); 176 break; 177 178 case PT_READ_I: 179 case PT_READ_D: 180 case PT_WRITE_I: 181 case PT_WRITE_D: 182 case PT_IO: 183 case PT_CONTINUE: 184 case PT_KILL: 185 case PT_DETACH: 186 #ifdef PT_STEP 187 case PT_STEP: 188 #endif 189 case PT_SET_EVENT_MASK: 190 case PT_GET_EVENT_MASK: 191 case PT_GET_PROCESS_STATE: 192 case PT_GETREGS: 193 case PT_SETREGS: 194 #ifdef PT_GETFPREGS 195 case PT_GETFPREGS: 196 #endif 197 #ifdef PT_SETFPREGS 198 case PT_SETFPREGS: 199 #endif 200 #ifdef PT_GETXMMREGS 201 case PT_GETXMMREGS: 202 #endif 203 #ifdef PT_SETXMMREGS 204 case PT_SETXMMREGS: 205 #endif 206 #ifdef PT_WCOOKIE 207 case PT_WCOOKIE: 208 #endif 209 /* 210 * You can't do what you want to the process if: 211 * (1) It's not being traced at all, 212 */ 213 if (!ISSET(t->p_flag, P_TRACED)) 214 return (EPERM); 215 216 /* 217 * (2) it's not being traced by _you_, or 218 */ 219 if (t->p_p->ps_pptr != p->p_p) 220 return (EBUSY); 221 222 /* 223 * (3) it's not currently stopped. 224 */ 225 if (t->p_stat != SSTOP || !ISSET(t->p_flag, P_WAITED)) 226 return (EBUSY); 227 break; 228 229 default: /* It was not a legal request. */ 230 return (EINVAL); 231 } 232 233 /* Do single-step fixup if needed. */ 234 FIX_SSTEP(t); 235 236 /* Now do the operation. */ 237 write = 0; 238 *retval = 0; 239 240 switch (SCARG(uap, req)) { 241 case PT_TRACE_ME: 242 /* Just set the trace flag. */ 243 atomic_setbits_int(&t->p_flag, P_TRACED); 244 t->p_oppid = t->p_p->ps_pptr->ps_pid; 245 if (t->p_ptstat == NULL) 246 t->p_ptstat = malloc(sizeof(*t->p_ptstat), 247 M_SUBPROC, M_WAITOK); 248 bzero(t->p_ptstat, sizeof(*t->p_ptstat)); 249 return (0); 250 251 case PT_WRITE_I: /* XXX no separate I and D spaces */ 252 case PT_WRITE_D: 253 write = 1; 254 temp = SCARG(uap, data); 255 case PT_READ_I: /* XXX no separate I and D spaces */ 256 case PT_READ_D: 257 /* write = 0 done above. */ 258 iov.iov_base = (caddr_t)&temp; 259 iov.iov_len = sizeof(int); 260 uio.uio_iov = &iov; 261 uio.uio_iovcnt = 1; 262 uio.uio_offset = (off_t)(vaddr_t)SCARG(uap, addr); 263 uio.uio_resid = sizeof(int); 264 uio.uio_segflg = UIO_SYSSPACE; 265 uio.uio_rw = write ? UIO_WRITE : UIO_READ; 266 uio.uio_procp = p; 267 error = process_domem(p, t, &uio, write ? PT_WRITE_I : 268 PT_READ_I); 269 if (write == 0) 270 *retval = temp; 271 return (error); 272 case PT_IO: 273 error = copyin(SCARG(uap, addr), &piod, sizeof(piod)); 274 if (error) 275 return (error); 276 iov.iov_base = piod.piod_addr; 277 iov.iov_len = piod.piod_len; 278 uio.uio_iov = &iov; 279 uio.uio_iovcnt = 1; 280 uio.uio_offset = (off_t)(vaddr_t)piod.piod_offs; 281 uio.uio_resid = piod.piod_len; 282 uio.uio_segflg = UIO_USERSPACE; 283 uio.uio_procp = p; 284 switch (piod.piod_op) { 285 case PIOD_READ_I: 286 req = PT_READ_I; 287 uio.uio_rw = UIO_READ; 288 break; 289 case PIOD_READ_D: 290 req = PT_READ_D; 291 uio.uio_rw = UIO_READ; 292 break; 293 case PIOD_WRITE_I: 294 req = PT_WRITE_I; 295 uio.uio_rw = UIO_WRITE; 296 break; 297 case PIOD_WRITE_D: 298 req = PT_WRITE_D; 299 uio.uio_rw = UIO_WRITE; 300 break; 301 case PIOD_READ_AUXV: 302 req = PT_READ_D; 303 uio.uio_rw = UIO_READ; 304 temp = t->p_emul->e_arglen * sizeof(char *); 305 if (uio.uio_offset > temp) 306 return (EIO); 307 if (uio.uio_resid > temp - uio.uio_offset) 308 uio.uio_resid = temp - uio.uio_offset; 309 piod.piod_len = iov.iov_len = uio.uio_resid; 310 error = process_auxv_offset(p, t, &uio); 311 if (error) 312 return (error); 313 break; 314 default: 315 return (EINVAL); 316 } 317 error = process_domem(p, t, &uio, req); 318 piod.piod_len -= uio.uio_resid; 319 (void) copyout(&piod, SCARG(uap, addr), sizeof(piod)); 320 return (error); 321 #ifdef PT_STEP 322 case PT_STEP: 323 /* 324 * From the 4.4BSD PRM: 325 * "Execution continues as in request PT_CONTINUE; however 326 * as soon as possible after execution of at least one 327 * instruction, execution stops again. [ ... ]" 328 */ 329 #endif 330 case PT_CONTINUE: 331 /* 332 * From the 4.4BSD PRM: 333 * "The data argument is taken as a signal number and the 334 * child's execution continues at location addr as if it 335 * incurred that signal. Normally the signal number will 336 * be either 0 to indicate that the signal that caused the 337 * stop should be ignored, or that value fetched out of 338 * the process's image indicating which signal caused 339 * the stop. If addr is (int *)1 then execution continues 340 * from where it stopped." 341 */ 342 343 /* Check that the data is a valid signal number or zero. */ 344 if (SCARG(uap, data) < 0 || SCARG(uap, data) >= NSIG) 345 return (EINVAL); 346 347 /* If the address parameter is not (int *)1, set the pc. */ 348 if ((int *)SCARG(uap, addr) != (int *)1) 349 if ((error = process_set_pc(t, SCARG(uap, addr))) != 0) 350 goto relebad; 351 352 #ifdef PT_STEP 353 /* 354 * Arrange for a single-step, if that's requested and possible. 355 */ 356 error = process_sstep(t, SCARG(uap, req) == PT_STEP); 357 if (error) 358 goto relebad; 359 #endif 360 goto sendsig; 361 362 case PT_DETACH: 363 /* 364 * From the 4.4BSD PRM: 365 * "The data argument is taken as a signal number and the 366 * child's execution continues at location addr as if it 367 * incurred that signal. Normally the signal number will 368 * be either 0 to indicate that the signal that caused the 369 * stop should be ignored, or that value fetched out of 370 * the process's image indicating which signal caused 371 * the stop. If addr is (int *)1 then execution continues 372 * from where it stopped." 373 */ 374 375 /* Check that the data is a valid signal number or zero. */ 376 if (SCARG(uap, data) < 0 || SCARG(uap, data) >= NSIG) 377 return (EINVAL); 378 379 #ifdef PT_STEP 380 /* 381 * Arrange for a single-step, if that's requested and possible. 382 */ 383 error = process_sstep(t, SCARG(uap, req) == PT_STEP); 384 if (error) 385 goto relebad; 386 #endif 387 388 /* give process back to original parent or init */ 389 if (t->p_oppid != t->p_p->ps_pptr->ps_pid) { 390 struct process *ppr; 391 392 ppr = prfind(t->p_oppid); 393 proc_reparent(t->p_p, ppr ? ppr : initproc->p_p); 394 } 395 396 /* not being traced any more */ 397 t->p_oppid = 0; 398 atomic_clearbits_int(&t->p_flag, P_TRACED|P_WAITED); 399 400 sendsig: 401 bzero(t->p_ptstat, sizeof(*t->p_ptstat)); 402 403 /* Finally, deliver the requested signal (or none). */ 404 if (t->p_stat == SSTOP) { 405 t->p_xstat = SCARG(uap, data); 406 SCHED_LOCK(s); 407 setrunnable(t); 408 SCHED_UNLOCK(s); 409 } else { 410 if (SCARG(uap, data) != 0) 411 psignal(t, SCARG(uap, data)); 412 } 413 return (0); 414 415 relebad: 416 return (error); 417 418 case PT_KILL: 419 /* just send the process a KILL signal. */ 420 SCARG(uap, data) = SIGKILL; 421 goto sendsig; /* in PT_CONTINUE, above. */ 422 423 case PT_ATTACH: 424 /* 425 * As done in procfs: 426 * Go ahead and set the trace flag. 427 * Save the old parent (it's reset in 428 * _DETACH, and also in kern_exit.c:wait4() 429 * Reparent the process so that the tracing 430 * proc gets to see all the action. 431 * Stop the target. 432 */ 433 atomic_setbits_int(&t->p_flag, P_TRACED); 434 t->p_oppid = t->p_p->ps_pptr->ps_pid; 435 if (t->p_p->ps_pptr != p->p_p) 436 proc_reparent(t->p_p, p->p_p); 437 if (t->p_ptstat == NULL) 438 t->p_ptstat = malloc(sizeof(*t->p_ptstat), 439 M_SUBPROC, M_WAITOK); 440 SCARG(uap, data) = SIGSTOP; 441 goto sendsig; 442 443 case PT_GET_EVENT_MASK: 444 if (SCARG(uap, data) != sizeof(pe)) 445 return (EINVAL); 446 bzero(&pe, sizeof(pe)); 447 pe.pe_set_event = t->p_ptmask; 448 return (copyout(&pe, SCARG(uap, addr), sizeof(pe))); 449 case PT_SET_EVENT_MASK: 450 if (SCARG(uap, data) != sizeof(pe)) 451 return (EINVAL); 452 if ((error = copyin(SCARG(uap, addr), &pe, sizeof(pe)))) 453 return (error); 454 t->p_ptmask = pe.pe_set_event; 455 return (0); 456 457 case PT_GET_PROCESS_STATE: 458 if (SCARG(uap, data) != sizeof(*t->p_ptstat)) 459 return (EINVAL); 460 return (copyout(t->p_ptstat, SCARG(uap, addr), 461 sizeof(*t->p_ptstat))); 462 463 case PT_SETREGS: 464 KASSERT((p->p_flag & P_SYSTEM) == 0); 465 if ((error = process_checkioperm(p, t)) != 0) 466 return (error); 467 468 regs = malloc(sizeof(*regs), M_TEMP, M_WAITOK); 469 error = copyin(SCARG(uap, addr), regs, sizeof(*regs)); 470 if (error == 0) { 471 error = process_write_regs(t, regs); 472 } 473 free(regs, M_TEMP); 474 return (error); 475 case PT_GETREGS: 476 KASSERT((p->p_flag & P_SYSTEM) == 0); 477 if ((error = process_checkioperm(p, t)) != 0) 478 return (error); 479 480 regs = malloc(sizeof(*regs), M_TEMP, M_WAITOK); 481 error = process_read_regs(t, regs); 482 if (error == 0) 483 error = copyout(regs, 484 SCARG(uap, addr), sizeof (*regs)); 485 free(regs, M_TEMP); 486 return (error); 487 #ifdef PT_SETFPREGS 488 case PT_SETFPREGS: 489 KASSERT((p->p_flag & P_SYSTEM) == 0); 490 if ((error = process_checkioperm(p, t)) != 0) 491 return (error); 492 493 fpregs = malloc(sizeof(*fpregs), M_TEMP, M_WAITOK); 494 error = copyin(SCARG(uap, addr), fpregs, sizeof(*fpregs)); 495 if (error == 0) { 496 error = process_write_fpregs(t, fpregs); 497 } 498 free(fpregs, M_TEMP); 499 return (error); 500 #endif 501 #ifdef PT_GETFPREGS 502 case PT_GETFPREGS: 503 KASSERT((p->p_flag & P_SYSTEM) == 0); 504 if ((error = process_checkioperm(p, t)) != 0) 505 return (error); 506 507 fpregs = malloc(sizeof(*fpregs), M_TEMP, M_WAITOK); 508 error = process_read_fpregs(t, fpregs); 509 if (error == 0) 510 error = copyout(fpregs, 511 SCARG(uap, addr), sizeof(*fpregs)); 512 free(fpregs, M_TEMP); 513 return (error); 514 #endif 515 #ifdef PT_SETXMMREGS 516 case PT_SETXMMREGS: 517 KASSERT((p->p_flag & P_SYSTEM) == 0); 518 if ((error = process_checkioperm(p, t)) != 0) 519 return (error); 520 521 xmmregs = malloc(sizeof(*xmmregs), M_TEMP, M_WAITOK); 522 error = copyin(SCARG(uap, addr), xmmregs, sizeof(*xmmregs)); 523 if (error == 0) { 524 error = process_write_xmmregs(t, xmmregs); 525 } 526 free(xmmregs, M_TEMP); 527 return (error); 528 #endif 529 #ifdef PT_GETXMMREGS 530 case PT_GETXMMREGS: 531 KASSERT((p->p_flag & P_SYSTEM) == 0); 532 if ((error = process_checkioperm(p, t)) != 0) 533 return (error); 534 535 xmmregs = malloc(sizeof(*xmmregs), M_TEMP, M_WAITOK); 536 error = process_read_xmmregs(t, xmmregs); 537 if (error == 0) 538 error = copyout(xmmregs, 539 SCARG(uap, addr), sizeof(*xmmregs)); 540 free(xmmregs, M_TEMP); 541 return (error); 542 #endif 543 #ifdef PT_WCOOKIE 544 case PT_WCOOKIE: 545 wcookie = process_get_wcookie (t); 546 return (copyout(&wcookie, SCARG(uap, addr), 547 sizeof (register_t))); 548 #endif 549 } 550 551 #ifdef DIAGNOSTIC 552 panic("ptrace: impossible"); 553 #endif 554 return 0; 555 } 556 #endif /* PTRACE */ 557 558 /* 559 * Check if a process is allowed to fiddle with the memory of another. 560 * 561 * p = tracer 562 * t = tracee 563 * 564 * 1. You can't attach to a process not owned by you or one that has raised 565 * its privileges. 566 * 1a. ...unless you are root. 567 * 568 * 2. init is always off-limits because it can control the securelevel. 569 * 2a. ...unless securelevel is permanently set to insecure. 570 * 571 * 3. Processes that are in the process of doing an exec() are always 572 * off-limits because of the can of worms they are. Just wait a 573 * second. 574 */ 575 int 576 process_checkioperm(struct proc *p, struct proc *t) 577 { 578 int error; 579 580 if ((t->p_cred->p_ruid != p->p_cred->p_ruid || 581 ISSET(t->p_p->ps_flags, PS_SUGIDEXEC | PS_SUGID)) && 582 (error = suser(p, 0)) != 0) 583 return (error); 584 585 if ((t->p_pid == 1) && (securelevel > -1)) 586 return (EPERM); 587 588 if (t->p_flag & P_INEXEC) 589 return (EAGAIN); 590 591 return (0); 592 } 593 594 int 595 process_domem(struct proc *curp, struct proc *p, struct uio *uio, int req) 596 { 597 struct vmspace *vm; 598 int error; 599 vaddr_t addr; 600 vsize_t len; 601 602 len = uio->uio_resid; 603 if (len == 0) 604 return (0); 605 606 if ((error = process_checkioperm(curp, p)) != 0) 607 return (error); 608 609 /* XXXCDC: how should locking work here? */ 610 if ((p->p_flag & P_WEXIT) || (p->p_vmspace->vm_refcnt < 1)) 611 return(EFAULT); 612 addr = uio->uio_offset; 613 614 vm = p->p_vmspace; 615 vm->vm_refcnt++; 616 617 error = uvm_io(&vm->vm_map, uio, 618 (req == PT_WRITE_I) ? UVM_IO_FIXPROT : 0); 619 620 uvmspace_free(vm); 621 622 if (error == 0 && req == PT_WRITE_I) 623 pmap_proc_iflush(p, addr, len); 624 625 return (error); 626 } 627 628 #ifdef PTRACE 629 int 630 process_auxv_offset(struct proc *curp, struct proc *p, struct uio *uiop) 631 { 632 struct ps_strings pss; 633 struct iovec iov; 634 struct uio uio; 635 int error; 636 637 iov.iov_base = &pss; 638 iov.iov_len = sizeof(pss); 639 uio.uio_iov = &iov; 640 uio.uio_iovcnt = 1; 641 uio.uio_offset = (off_t)(vaddr_t)PS_STRINGS; 642 uio.uio_resid = sizeof(pss); 643 uio.uio_segflg = UIO_SYSSPACE; 644 uio.uio_rw = UIO_READ; 645 uio.uio_procp = curp; 646 647 if ((error = uvm_io(&p->p_vmspace->vm_map, &uio, 0)) != 0) 648 return (error); 649 650 if (pss.ps_envstr == NULL) 651 return (EIO); 652 653 uiop->uio_offset += (off_t)(vaddr_t)(pss.ps_envstr + pss.ps_nenvstr + 1); 654 #ifdef MACHINE_STACK_GROWS_UP 655 if (uiop->uio_offset < (off_t)(vaddr_t)PS_STRINGS) 656 return (EIO); 657 #else 658 if (uiop->uio_offset > (off_t)(vaddr_t)PS_STRINGS) 659 return (EIO); 660 if ((uiop->uio_offset + uiop->uio_resid) > (off_t)(vaddr_t)PS_STRINGS) 661 uiop->uio_resid = (off_t)(vaddr_t)PS_STRINGS - uiop->uio_offset; 662 #endif 663 664 return (0); 665 } 666 #endif 667