1/* $NetBSD: kttcp.c,v 1.29 2011/12/20 23:56:28 christos Exp $ */ 2 3/* 4 * Copyright (c) 2002 Wasabi Systems, Inc. 5 * All rights reserved. 6 * 7 * Written by Frank van der Linden and Jason R. Thorpe for 8 * Wasabi Systems, Inc. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. All advertising materials mentioning features or use of this software 19 * must display the following acknowledgement: 20 * This product includes software developed for the NetBSD Project by 21 * Wasabi Systems, Inc. 22 * 4. The name of Wasabi Systems, Inc. may not be used to endorse 23 * or promote products derived from this software without specific prior 24 * written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND 27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC 30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 36 * POSSIBILITY OF SUCH DAMAGE. 37 */ 38 39/* 40 * kttcp.c -- provides kernel support for testing network testing, 41 * see kttcp(4) 42 */ 43 44#include <sys/cdefs.h> 45__KERNEL_RCSID(0, "$NetBSD: kttcp.c,v 1.29 2011/12/20 23:56:28 christos Exp $"); 46 47#include <sys/param.h> 48#include <sys/types.h> 49#include <sys/ioctl.h> 50#include <sys/file.h> 51#include <sys/filedesc.h> 52#include <sys/conf.h> 53#include <sys/systm.h> 54#include <sys/protosw.h> 55#include <sys/proc.h> 56#include <sys/resourcevar.h> 57#include <sys/signal.h> 58#include <sys/socketvar.h> 59#include <sys/socket.h> 60#include <sys/mbuf.h> 61#include <sys/mount.h> 62#include <sys/syscallargs.h> 63 64#include <dev/kttcpio.h> 65 66static int kttcp_send(struct lwp *l, struct kttcp_io_args *); 67static int kttcp_recv(struct lwp *l, struct kttcp_io_args *); 68static int kttcp_sosend(struct socket *, unsigned long long, 69 unsigned long long *, struct lwp *, int); 70static int kttcp_soreceive(struct socket *, unsigned long long, 71 unsigned long long *, struct lwp *, int *); 72 73void kttcpattach(int); 74 75dev_type_ioctl(kttcpioctl); 76 77const struct cdevsw kttcp_cdevsw = { 78 nullopen, nullclose, noread, nowrite, kttcpioctl, 79 nostop, notty, nopoll, nommap, nokqfilter, D_OTHER 80}; 81 82void 83kttcpattach(int count) 84{ 85 /* Do nothing. */ 86} 87 88int 89kttcpioctl(dev_t dev, u_long cmd, void *data, int flag, 90 struct lwp *l) 91{ 92 int error; 93 94 if ((flag & FWRITE) == 0) 95 return EPERM; 96 97 switch (cmd) { 98 case KTTCP_IO_SEND: 99 error = kttcp_send(l, (struct kttcp_io_args *) data); 100 break; 101 102 case KTTCP_IO_RECV: 103 error = kttcp_recv(l, (struct kttcp_io_args *) data); 104 break; 105 106 default: 107 return EINVAL; 108 } 109 110 return error; 111} 112 113static int 114kttcp_send(struct lwp *l, struct kttcp_io_args *kio) 115{ 116 struct socket *so; 117 int error; 118 struct timeval t0, t1; 119 unsigned long long len, done; 120 121 if (kio->kio_totalsize >= KTTCP_MAX_XMIT) 122 return EINVAL; 123 124 if ((error = fd_getsock(kio->kio_socket, &so)) != 0) 125 return error; 126 127 len = kio->kio_totalsize; 128 microtime(&t0); 129 do { 130 error = kttcp_sosend(so, len, &done, l, 0); 131 len -= done; 132 } while (error == 0 && len > 0); 133 134 fd_putfile(kio->kio_socket); 135 136 microtime(&t1); 137 if (error != 0) 138 return error; 139 timersub(&t1, &t0, &kio->kio_elapsed); 140 141 kio->kio_bytesdone = kio->kio_totalsize - len; 142 143 return 0; 144} 145 146static int 147kttcp_recv(struct lwp *l, struct kttcp_io_args *kio) 148{ 149 struct socket *so; 150 int error; 151 struct timeval t0, t1; 152 unsigned long long len, done; 153 154 done = 0; /* XXX gcc */ 155 156 if (kio->kio_totalsize > KTTCP_MAX_XMIT) 157 return EINVAL; 158 159 if ((error = fd_getsock(kio->kio_socket, &so)) != 0) 160 return error; 161 len = kio->kio_totalsize; 162 microtime(&t0); 163 do { 164 error = kttcp_soreceive(so, len, &done, l, NULL); 165 len -= done; 166 } while (error == 0 && len > 0 && done > 0); 167 168 fd_putfile(kio->kio_socket); 169 170 microtime(&t1); 171 if (error == EPIPE) 172 error = 0; 173 if (error != 0) 174 return error; 175 timersub(&t1, &t0, &kio->kio_elapsed); 176 177 kio->kio_bytesdone = kio->kio_totalsize - len; 178 179 return 0; 180} 181 182#define SBLOCKWAIT(f) (((f) & MSG_DONTWAIT) ? M_NOWAIT : M_WAITOK) 183 184/* 185 * Slightly changed version of sosend() 186 */ 187static int 188kttcp_sosend(struct socket *so, unsigned long long slen, 189 unsigned long long *done, struct lwp *l, int flags) 190{ 191 struct mbuf **mp, *m, *top; 192 long space, len, mlen; 193 int error, dontroute, atomic; 194 long long resid; 195 196 atomic = sosendallatonce(so); 197 resid = slen; 198 top = NULL; 199 /* 200 * In theory resid should be unsigned. 201 * However, space must be signed, as it might be less than 0 202 * if we over-committed, and we must use a signed comparison 203 * of space and resid. On the other hand, a negative resid 204 * causes us to loop sending 0-length segments to the protocol. 205 */ 206 if (resid < 0) { 207 error = EINVAL; 208 goto out; 209 } 210 dontroute = 211 (flags & MSG_DONTROUTE) && (so->so_options & SO_DONTROUTE) == 0 && 212 (so->so_proto->pr_flags & PR_ATOMIC); 213 l->l_ru.ru_msgsnd++; 214#define snderr(errno) { error = errno; goto release; } 215 solock(so); 216 restart: 217 if ((error = sblock(&so->so_snd, SBLOCKWAIT(flags))) != 0) 218 goto out; 219 do { 220 if (so->so_state & SS_CANTSENDMORE) 221 snderr(EPIPE); 222 if (so->so_error) { 223 error = so->so_error; 224 so->so_error = 0; 225 goto release; 226 } 227 if ((so->so_state & SS_ISCONNECTED) == 0) { 228 if (so->so_proto->pr_flags & PR_CONNREQUIRED) { 229 if ((so->so_state & SS_ISCONFIRMING) == 0) 230 snderr(ENOTCONN); 231 } else 232 snderr(EDESTADDRREQ); 233 } 234 space = sbspace(&so->so_snd); 235 if (flags & MSG_OOB) 236 space += 1024; 237 if ((atomic && resid > so->so_snd.sb_hiwat)) 238 snderr(EMSGSIZE); 239 if (space < resid && (atomic || space < so->so_snd.sb_lowat)) { 240 if (so->so_state & SS_NBIO) 241 snderr(EWOULDBLOCK); 242 SBLASTRECORDCHK(&so->so_rcv, 243 "kttcp_soreceive sbwait 1"); 244 SBLASTMBUFCHK(&so->so_rcv, 245 "kttcp_soreceive sbwait 1"); 246 sbunlock(&so->so_snd); 247 error = sbwait(&so->so_snd); 248 if (error) 249 goto out; 250 goto restart; 251 } 252 mp = ⊤ 253 do { 254 sounlock(so); 255 do { 256 if (top == 0) { 257 m = m_gethdr(M_WAIT, MT_DATA); 258 mlen = MHLEN; 259 m->m_pkthdr.len = 0; 260 m->m_pkthdr.rcvif = NULL; 261 } else { 262 m = m_get(M_WAIT, MT_DATA); 263 mlen = MLEN; 264 } 265 if (resid >= MINCLSIZE && space >= MCLBYTES) { 266 m_clget(m, M_WAIT); 267 if ((m->m_flags & M_EXT) == 0) 268 goto nopages; 269 mlen = MCLBYTES; 270#ifdef MAPPED_MBUFS 271 len = lmin(MCLBYTES, resid); 272#else 273 if (atomic && top == 0) { 274 len = lmin(MCLBYTES - max_hdr, 275 resid); 276 m->m_data += max_hdr; 277 } else 278 len = lmin(MCLBYTES, resid); 279#endif 280 space -= len; 281 } else { 282nopages: 283 len = lmin(lmin(mlen, resid), space); 284 space -= len; 285 /* 286 * For datagram protocols, leave room 287 * for protocol headers in first mbuf. 288 */ 289 if (atomic && top == 0 && len < mlen) 290 MH_ALIGN(m, len); 291 } 292 resid -= len; 293 m->m_len = len; 294 *mp = m; 295 top->m_pkthdr.len += len; 296 if (error) 297 goto release; 298 mp = &m->m_next; 299 if (resid <= 0) { 300 if (flags & MSG_EOR) 301 top->m_flags |= M_EOR; 302 break; 303 } 304 } while (space > 0 && atomic); 305 solock(so); 306 307 if (so->so_state & SS_CANTSENDMORE) 308 snderr(EPIPE); 309 if (dontroute) 310 so->so_options |= SO_DONTROUTE; 311 if (resid > 0) 312 so->so_state |= SS_MORETOCOME; 313 error = (*so->so_proto->pr_usrreq)(so, 314 (flags & MSG_OOB) ? PRU_SENDOOB : PRU_SEND, 315 top, NULL, NULL, l); 316 if (dontroute) 317 so->so_options &= ~SO_DONTROUTE; 318 if (resid > 0) 319 so->so_state &= ~SS_MORETOCOME; 320 top = 0; 321 mp = ⊤ 322 if (error) 323 goto release; 324 } while (resid && space > 0); 325 } while (resid); 326 327 release: 328 sbunlock(&so->so_snd); 329 out: 330 sounlock(so); 331 if (top) 332 m_freem(top); 333 *done = slen - resid; 334#if 0 335 printf("sosend: error %d slen %llu resid %lld\n", error, slen, resid); 336#endif 337 return (error); 338} 339 340static int 341kttcp_soreceive(struct socket *so, unsigned long long slen, 342 unsigned long long *done, struct lwp *l, int *flagsp) 343{ 344 struct mbuf *m, **mp; 345 int flags, len, error, offset, moff, type; 346 long long orig_resid, resid; 347 const struct protosw *pr; 348 struct mbuf *nextrecord; 349 350 pr = so->so_proto; 351 mp = NULL; 352 type = 0; 353 resid = orig_resid = slen; 354 if (flagsp) 355 flags = *flagsp &~ MSG_EOR; 356 else 357 flags = 0; 358 if (flags & MSG_OOB) { 359 m = m_get(M_WAIT, MT_DATA); 360 solock(so); 361 error = (*pr->pr_usrreq)(so, PRU_RCVOOB, m, 362 (struct mbuf *)(long)(flags & MSG_PEEK), NULL, NULL); 363 sounlock(so); 364 if (error) 365 goto bad; 366 do { 367 resid -= min(resid, m->m_len); 368 m = m_free(m); 369 } while (resid && error == 0 && m); 370 bad: 371 if (m) 372 m_freem(m); 373 return (error); 374 } 375 if (mp) 376 *mp = NULL; 377 solock(so); 378 if (so->so_state & SS_ISCONFIRMING && resid) 379 (*pr->pr_usrreq)(so, PRU_RCVD, NULL, NULL, NULL, NULL); 380 restart: 381 if ((error = sblock(&so->so_rcv, SBLOCKWAIT(flags))) != 0) 382 return (error); 383 m = so->so_rcv.sb_mb; 384 /* 385 * If we have less data than requested, block awaiting more 386 * (subject to any timeout) if: 387 * 1. the current count is less than the low water mark, 388 * 2. MSG_WAITALL is set, and it is possible to do the entire 389 * receive operation at once if we block (resid <= hiwat), or 390 * 3. MSG_DONTWAIT is not set. 391 * If MSG_WAITALL is set but resid is larger than the receive buffer, 392 * we have to do the receive in sections, and thus risk returning 393 * a short count if a timeout or signal occurs after we start. 394 */ 395 if (m == NULL || (((flags & MSG_DONTWAIT) == 0 && 396 so->so_rcv.sb_cc < resid) && 397 (so->so_rcv.sb_cc < so->so_rcv.sb_lowat || 398 ((flags & MSG_WAITALL) && resid <= so->so_rcv.sb_hiwat)) && 399 m->m_nextpkt == NULL && (pr->pr_flags & PR_ATOMIC) == 0)) { 400#ifdef DIAGNOSTIC 401 if (m == NULL && so->so_rcv.sb_cc) 402 panic("receive 1"); 403#endif 404 if (so->so_error) { 405 if (m) 406 goto dontblock; 407 error = so->so_error; 408 if ((flags & MSG_PEEK) == 0) 409 so->so_error = 0; 410 goto release; 411 } 412 if (so->so_state & SS_CANTRCVMORE) { 413 if (m) 414 goto dontblock; 415 else 416 goto release; 417 } 418 for (; m; m = m->m_next) 419 if (m->m_type == MT_OOBDATA || (m->m_flags & M_EOR)) { 420 m = so->so_rcv.sb_mb; 421 goto dontblock; 422 } 423 if ((so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING)) == 0 && 424 (so->so_proto->pr_flags & PR_CONNREQUIRED)) { 425 error = ENOTCONN; 426 goto release; 427 } 428 if (resid == 0) 429 goto release; 430 if ((so->so_state & SS_NBIO) || 431 (flags & (MSG_DONTWAIT|MSG_NBIO))) { 432 error = EWOULDBLOCK; 433 goto release; 434 } 435 sbunlock(&so->so_rcv); 436 error = sbwait(&so->so_rcv); 437 if (error) { 438 sounlock(so); 439 return (error); 440 } 441 goto restart; 442 } 443 dontblock: 444 /* 445 * On entry here, m points to the first record of the socket buffer. 446 * While we process the initial mbufs containing address and control 447 * info, we save a copy of m->m_nextpkt into nextrecord. 448 */ 449#ifdef notyet /* XXXX */ 450 if (uio->uio_lwp) 451 uio->uio_lwp->l_ru.ru_msgrcv++; 452#endif 453 KASSERT(m == so->so_rcv.sb_mb); 454 SBLASTRECORDCHK(&so->so_rcv, "kttcp_soreceive 1"); 455 SBLASTMBUFCHK(&so->so_rcv, "kttcp_soreceive 1"); 456 nextrecord = m->m_nextpkt; 457 if (pr->pr_flags & PR_ADDR) { 458#ifdef DIAGNOSTIC 459 if (m->m_type != MT_SONAME) 460 panic("receive 1a"); 461#endif 462 orig_resid = 0; 463 if (flags & MSG_PEEK) { 464 m = m->m_next; 465 } else { 466 sbfree(&so->so_rcv, m); 467 MFREE(m, so->so_rcv.sb_mb); 468 m = so->so_rcv.sb_mb; 469 } 470 } 471 while (m && m->m_type == MT_CONTROL && error == 0) { 472 if (flags & MSG_PEEK) { 473 m = m->m_next; 474 } else { 475 sbfree(&so->so_rcv, m); 476 MFREE(m, so->so_rcv.sb_mb); 477 m = so->so_rcv.sb_mb; 478 } 479 } 480 481 /* 482 * If m is non-NULL, we have some data to read. From now on, 483 * make sure to keep sb_lastrecord consistent when working on 484 * the last packet on the chain (nextrecord == NULL) and we 485 * change m->m_nextpkt. 486 */ 487 if (m) { 488 if ((flags & MSG_PEEK) == 0) { 489 m->m_nextpkt = nextrecord; 490 /* 491 * If nextrecord == NULL (this is a single chain), 492 * then sb_lastrecord may not be valid here if m 493 * was changed earlier. 494 */ 495 if (nextrecord == NULL) { 496 KASSERT(so->so_rcv.sb_mb == m); 497 so->so_rcv.sb_lastrecord = m; 498 } 499 } 500 type = m->m_type; 501 if (type == MT_OOBDATA) 502 flags |= MSG_OOB; 503 } else { 504 if ((flags & MSG_PEEK) == 0) { 505 KASSERT(so->so_rcv.sb_mb == m); 506 so->so_rcv.sb_mb = nextrecord; 507 SB_EMPTY_FIXUP(&so->so_rcv); 508 } 509 } 510 SBLASTRECORDCHK(&so->so_rcv, "kttcp_soreceive 2"); 511 SBLASTMBUFCHK(&so->so_rcv, "kttcp_soreceive 2"); 512 513 moff = 0; 514 offset = 0; 515 while (m && resid > 0 && error == 0) { 516 if (m->m_type == MT_OOBDATA) { 517 if (type != MT_OOBDATA) 518 break; 519 } else if (type == MT_OOBDATA) 520 break; 521#ifdef DIAGNOSTIC 522 else if (m->m_type != MT_DATA && m->m_type != MT_HEADER) 523 panic("receive 3"); 524#endif 525 so->so_state &= ~SS_RCVATMARK; 526 len = resid; 527 if (so->so_oobmark && len > so->so_oobmark - offset) 528 len = so->so_oobmark - offset; 529 if (len > m->m_len - moff) 530 len = m->m_len - moff; 531 /* 532 * If mp is set, just pass back the mbufs. 533 * Otherwise copy them out via the uio, then free. 534 * Sockbuf must be consistent here (points to current mbuf, 535 * it points to next record) when we drop priority; 536 * we must note any additions to the sockbuf when we 537 * block interrupts again. 538 */ 539 resid -= len; 540 if (len == m->m_len - moff) { 541 if (m->m_flags & M_EOR) 542 flags |= MSG_EOR; 543 if (flags & MSG_PEEK) { 544 m = m->m_next; 545 moff = 0; 546 } else { 547 nextrecord = m->m_nextpkt; 548 sbfree(&so->so_rcv, m); 549 if (mp) { 550 *mp = m; 551 mp = &m->m_next; 552 so->so_rcv.sb_mb = m = m->m_next; 553 *mp = NULL; 554 } else { 555 MFREE(m, so->so_rcv.sb_mb); 556 m = so->so_rcv.sb_mb; 557 } 558 /* 559 * If m != NULL, we also know that 560 * so->so_rcv.sb_mb != NULL. 561 */ 562 KASSERT(so->so_rcv.sb_mb == m); 563 if (m) { 564 m->m_nextpkt = nextrecord; 565 if (nextrecord == NULL) 566 so->so_rcv.sb_lastrecord = m; 567 } else { 568 so->so_rcv.sb_mb = nextrecord; 569 SB_EMPTY_FIXUP(&so->so_rcv); 570 } 571 SBLASTRECORDCHK(&so->so_rcv, 572 "kttcp_soreceive 3"); 573 SBLASTMBUFCHK(&so->so_rcv, 574 "kttcp_soreceive 3"); 575 } 576 } else { 577 if (flags & MSG_PEEK) 578 moff += len; 579 else { 580 if (mp) { 581 sounlock(so); 582 *mp = m_copym(m, 0, len, M_WAIT); 583 solock(so); 584 } 585 m->m_data += len; 586 m->m_len -= len; 587 so->so_rcv.sb_cc -= len; 588 } 589 } 590 if (so->so_oobmark) { 591 if ((flags & MSG_PEEK) == 0) { 592 so->so_oobmark -= len; 593 if (so->so_oobmark == 0) { 594 so->so_state |= SS_RCVATMARK; 595 break; 596 } 597 } else { 598 offset += len; 599 if (offset == so->so_oobmark) 600 break; 601 } 602 } 603 if (flags & MSG_EOR) 604 break; 605 /* 606 * If the MSG_WAITALL flag is set (for non-atomic socket), 607 * we must not quit until "uio->uio_resid == 0" or an error 608 * termination. If a signal/timeout occurs, return 609 * with a short count but without error. 610 * Keep sockbuf locked against other readers. 611 */ 612 while (flags & MSG_WAITALL && m == NULL && resid > 0 && 613 !sosendallatonce(so) && !nextrecord) { 614 if (so->so_error || so->so_state & SS_CANTRCVMORE) 615 break; 616 /* 617 * If we are peeking and the socket receive buffer is 618 * full, stop since we can't get more data to peek at. 619 */ 620 if ((flags & MSG_PEEK) && sbspace(&so->so_rcv) <= 0) 621 break; 622 /* 623 * If we've drained the socket buffer, tell the 624 * protocol in case it needs to do something to 625 * get it filled again. 626 */ 627 if ((pr->pr_flags & PR_WANTRCVD) && so->so_pcb) 628 (*pr->pr_usrreq)(so, PRU_RCVD, NULL, 629 (struct mbuf *)(long)flags, NULL, NULL); 630 SBLASTRECORDCHK(&so->so_rcv, 631 "kttcp_soreceive sbwait 2"); 632 SBLASTMBUFCHK(&so->so_rcv, 633 "kttcp_soreceive sbwait 2"); 634 error = sbwait(&so->so_rcv); 635 if (error) { 636 sbunlock(&so->so_rcv); 637 sounlock(so); 638 return (0); 639 } 640 if ((m = so->so_rcv.sb_mb) != NULL) 641 nextrecord = m->m_nextpkt; 642 } 643 } 644 645 if (m && pr->pr_flags & PR_ATOMIC) { 646 flags |= MSG_TRUNC; 647 if ((flags & MSG_PEEK) == 0) 648 (void) sbdroprecord(&so->so_rcv); 649 } 650 if ((flags & MSG_PEEK) == 0) { 651 if (m == NULL) { 652 /* 653 * First part is an SB_EMPTY_FIXUP(). Second part 654 * makes sure sb_lastrecord is up-to-date if 655 * there is still data in the socket buffer. 656 */ 657 so->so_rcv.sb_mb = nextrecord; 658 if (so->so_rcv.sb_mb == NULL) { 659 so->so_rcv.sb_mbtail = NULL; 660 so->so_rcv.sb_lastrecord = NULL; 661 } else if (nextrecord->m_nextpkt == NULL) 662 so->so_rcv.sb_lastrecord = nextrecord; 663 } 664 SBLASTRECORDCHK(&so->so_rcv, "kttcp_soreceive 4"); 665 SBLASTMBUFCHK(&so->so_rcv, "kttcp_soreceive 4"); 666 if (pr->pr_flags & PR_WANTRCVD && so->so_pcb) 667 (*pr->pr_usrreq)(so, PRU_RCVD, NULL, 668 (struct mbuf *)(long)flags, NULL, NULL); 669 } 670 if (orig_resid == resid && orig_resid && 671 (flags & MSG_EOR) == 0 && (so->so_state & SS_CANTRCVMORE) == 0) { 672 sbunlock(&so->so_rcv); 673 goto restart; 674 } 675 676 if (flagsp) 677 *flagsp |= flags; 678 release: 679 sbunlock(&so->so_rcv); 680 sounlock(so); 681 *done = slen - resid; 682#if 0 683 printf("soreceive: error %d slen %llu resid %lld\n", error, slen, resid); 684#endif 685 return (error); 686} 687