1 /* $NetBSD: sp_common.c,v 1.33 2012/09/21 14:33:03 pooka Exp $ */ 2 3 /* 4 * Copyright (c) 2010, 2011 Antti Kantee. All Rights Reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 16 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 /* 29 * Common client/server sysproxy routines. #included. 30 */ 31 32 #include "rumpuser_port.h" 33 34 #include <sys/types.h> 35 #include <sys/mman.h> 36 #include <sys/queue.h> 37 #include <sys/socket.h> 38 #include <sys/un.h> 39 40 #include <arpa/inet.h> 41 #include <netinet/in.h> 42 #include <netinet/tcp.h> 43 44 #include <assert.h> 45 #include <errno.h> 46 #include <fcntl.h> 47 #include <inttypes.h> 48 #include <limits.h> 49 #include <poll.h> 50 #include <pthread.h> 51 #include <stdarg.h> 52 #include <stddef.h> 53 #include <stdio.h> 54 #include <stdlib.h> 55 #include <string.h> 56 #include <unistd.h> 57 58 /* 59 * XXX: NetBSD's __unused collides with Linux headers, so we cannot 60 * define it before we've included everything. 61 */ 62 #if !defined(__unused) && defined(__GNUC__) 63 #define __unused __attribute__((__unused__)) 64 #endif 65 66 //#define DEBUG 67 #ifdef DEBUG 68 #define DPRINTF(x) mydprintf x 69 static void 70 mydprintf(const char *fmt, ...) 71 { 72 va_list ap; 73 74 va_start(ap, fmt); 75 vfprintf(stderr, fmt, ap); 76 va_end(ap); 77 } 78 #else 79 #define DPRINTF(x) 80 #endif 81 82 #ifndef HOSTOPS 83 #define host_poll poll 84 #define host_read read 85 #define host_sendmsg sendmsg 86 #define host_setsockopt setsockopt 87 #endif 88 89 #define IOVPUT(_io_, _b_) _io_.iov_base = &_b_; _io_.iov_len = sizeof(_b_); 90 #define IOVPUT_WITHSIZE(_io_, _b_, _l_) _io_.iov_base = _b_; _io_.iov_len = _l_; 91 #define SENDIOV(_spc_, _iov_) dosend(_spc_, _iov_, __arraycount(_iov_)) 92 93 /* 94 * Bah, I hate writing on-off-wire conversions in C 95 */ 96 97 enum { RUMPSP_REQ, RUMPSP_RESP, RUMPSP_ERROR }; 98 enum { RUMPSP_HANDSHAKE, 99 RUMPSP_SYSCALL, 100 RUMPSP_COPYIN, RUMPSP_COPYINSTR, 101 RUMPSP_COPYOUT, RUMPSP_COPYOUTSTR, 102 RUMPSP_ANONMMAP, 103 RUMPSP_PREFORK, 104 RUMPSP_RAISE }; 105 106 enum { HANDSHAKE_GUEST, HANDSHAKE_AUTH, HANDSHAKE_FORK, HANDSHAKE_EXEC }; 107 108 /* 109 * error types used for RUMPSP_ERROR 110 */ 111 enum rumpsp_err { RUMPSP_ERR_NONE = 0, RUMPSP_ERR_TRYAGAIN, RUMPSP_ERR_AUTH, 112 RUMPSP_ERR_INVALID_PREFORK, RUMPSP_ERR_RFORK_FAILED, 113 RUMPSP_ERR_INEXEC, RUMPSP_ERR_NOMEM, RUMPSP_ERR_MALFORMED_REQUEST }; 114 115 /* 116 * The mapping of the above types to errno. They are almost never exposed 117 * to the client after handshake (except for a server resource shortage 118 * and the client trying to be funny). This is a function instead of 119 * an array to catch missing values. Theoretically, the compiled code 120 * should be the same. 121 */ 122 static int 123 errmap(enum rumpsp_err error) 124 { 125 126 switch (error) { 127 /* XXX: no EAUTH on Linux */ 128 case RUMPSP_ERR_NONE: return 0; 129 case RUMPSP_ERR_AUTH: return EPERM; 130 case RUMPSP_ERR_TRYAGAIN: return EAGAIN; 131 case RUMPSP_ERR_INVALID_PREFORK: return ESRCH; 132 case RUMPSP_ERR_RFORK_FAILED: return EIO; /* got a light? */ 133 case RUMPSP_ERR_INEXEC: return EBUSY; 134 case RUMPSP_ERR_NOMEM: return ENOMEM; 135 case RUMPSP_ERR_MALFORMED_REQUEST: return EINVAL; 136 } 137 138 return -1; 139 } 140 141 #define AUTHLEN 4 /* 128bit fork auth */ 142 143 struct rsp_hdr { 144 uint64_t rsp_len; 145 uint64_t rsp_reqno; 146 uint16_t rsp_class; 147 uint16_t rsp_type; 148 /* 149 * We want this structure 64bit-aligned for typecast fun, 150 * so might as well use the following for something. 151 */ 152 union { 153 uint32_t sysnum; 154 uint32_t error; 155 uint32_t handshake; 156 uint32_t signo; 157 } u; 158 }; 159 #define HDRSZ sizeof(struct rsp_hdr) 160 #define rsp_sysnum u.sysnum 161 #define rsp_error u.error 162 #define rsp_handshake u.handshake 163 #define rsp_signo u.signo 164 165 #define MAXBANNER 96 166 167 /* 168 * Data follows the header. We have two types of structured data. 169 */ 170 171 /* copyin/copyout */ 172 struct rsp_copydata { 173 size_t rcp_len; 174 void *rcp_addr; 175 uint8_t rcp_data[0]; 176 }; 177 178 /* syscall response */ 179 struct rsp_sysresp { 180 int rsys_error; 181 register_t rsys_retval[2]; 182 }; 183 184 struct handshake_fork { 185 uint32_t rf_auth[4]; 186 int rf_cancel; 187 }; 188 189 struct respwait { 190 uint64_t rw_reqno; 191 void *rw_data; 192 size_t rw_dlen; 193 int rw_done; 194 int rw_error; 195 196 pthread_cond_t rw_cv; 197 198 TAILQ_ENTRY(respwait) rw_entries; 199 }; 200 201 struct prefork; 202 struct spclient { 203 int spc_fd; 204 int spc_refcnt; 205 int spc_state; 206 207 pthread_mutex_t spc_mtx; 208 pthread_cond_t spc_cv; 209 210 struct lwp *spc_mainlwp; 211 pid_t spc_pid; 212 213 TAILQ_HEAD(, respwait) spc_respwait; 214 215 /* rest of the fields are zeroed upon disconnect */ 216 #define SPC_ZEROFF offsetof(struct spclient, spc_pfd) 217 struct pollfd *spc_pfd; 218 219 struct rsp_hdr spc_hdr; 220 uint8_t *spc_buf; 221 size_t spc_off; 222 223 uint64_t spc_nextreq; 224 uint64_t spc_syscallreq; 225 uint64_t spc_generation; 226 int spc_ostatus, spc_istatus; 227 int spc_reconnecting; 228 int spc_inexec; 229 230 LIST_HEAD(, prefork) spc_pflist; 231 }; 232 #define SPCSTATUS_FREE 0 233 #define SPCSTATUS_BUSY 1 234 #define SPCSTATUS_WANTED 2 235 236 #define SPCSTATE_NEW 0 237 #define SPCSTATE_RUNNING 1 238 #define SPCSTATE_DYING 2 239 240 typedef int (*addrparse_fn)(const char *, struct sockaddr **, int); 241 typedef int (*connecthook_fn)(int); 242 typedef void (*cleanup_fn)(struct sockaddr *); 243 244 static int readframe(struct spclient *); 245 static void handlereq(struct spclient *); 246 247 static __inline void 248 spcresetbuf(struct spclient *spc) 249 { 250 251 spc->spc_buf = NULL; 252 spc->spc_off = 0; 253 } 254 255 static __inline void 256 spcfreebuf(struct spclient *spc) 257 { 258 259 free(spc->spc_buf); 260 spcresetbuf(spc); 261 } 262 263 static void 264 sendlockl(struct spclient *spc) 265 { 266 267 while (spc->spc_ostatus != SPCSTATUS_FREE) { 268 spc->spc_ostatus = SPCSTATUS_WANTED; 269 pthread_cond_wait(&spc->spc_cv, &spc->spc_mtx); 270 } 271 spc->spc_ostatus = SPCSTATUS_BUSY; 272 } 273 274 static void __unused 275 sendlock(struct spclient *spc) 276 { 277 278 pthread_mutex_lock(&spc->spc_mtx); 279 sendlockl(spc); 280 pthread_mutex_unlock(&spc->spc_mtx); 281 } 282 283 static void 284 sendunlockl(struct spclient *spc) 285 { 286 287 if (spc->spc_ostatus == SPCSTATUS_WANTED) 288 pthread_cond_broadcast(&spc->spc_cv); 289 spc->spc_ostatus = SPCSTATUS_FREE; 290 } 291 292 static void 293 sendunlock(struct spclient *spc) 294 { 295 296 pthread_mutex_lock(&spc->spc_mtx); 297 sendunlockl(spc); 298 pthread_mutex_unlock(&spc->spc_mtx); 299 } 300 301 static int 302 dosend(struct spclient *spc, struct iovec *iov, size_t iovlen) 303 { 304 struct msghdr msg; 305 struct pollfd pfd; 306 ssize_t n = 0; 307 int fd = spc->spc_fd; 308 309 pfd.fd = fd; 310 pfd.events = POLLOUT; 311 312 memset(&msg, 0, sizeof(msg)); 313 314 for (;;) { 315 /* not first round? poll */ 316 if (n) { 317 if (host_poll(&pfd, 1, INFTIM) == -1) { 318 if (errno == EINTR) 319 continue; 320 return errno; 321 } 322 } 323 324 msg.msg_iov = iov; 325 msg.msg_iovlen = iovlen; 326 n = host_sendmsg(fd, &msg, MSG_NOSIGNAL); 327 if (n == -1) { 328 if (errno == EPIPE) 329 return ENOTCONN; 330 if (errno != EAGAIN) 331 return errno; 332 continue; 333 } 334 if (n == 0) { 335 return ENOTCONN; 336 } 337 338 /* ok, need to adjust iovec for potential next round */ 339 while (n >= (ssize_t)iov[0].iov_len && iovlen) { 340 n -= iov[0].iov_len; 341 iov++; 342 iovlen--; 343 } 344 345 if (iovlen == 0) { 346 _DIAGASSERT(n == 0); 347 break; 348 } else { 349 iov[0].iov_base = (uint8_t *)iov[0].iov_base + n; 350 iov[0].iov_len -= n; 351 } 352 } 353 354 return 0; 355 } 356 357 static void 358 doputwait(struct spclient *spc, struct respwait *rw, struct rsp_hdr *rhdr) 359 { 360 361 rw->rw_data = NULL; 362 rw->rw_dlen = rw->rw_done = rw->rw_error = 0; 363 pthread_cond_init(&rw->rw_cv, NULL); 364 365 pthread_mutex_lock(&spc->spc_mtx); 366 rw->rw_reqno = rhdr->rsp_reqno = spc->spc_nextreq++; 367 TAILQ_INSERT_TAIL(&spc->spc_respwait, rw, rw_entries); 368 } 369 370 static void __unused 371 putwait_locked(struct spclient *spc, struct respwait *rw, struct rsp_hdr *rhdr) 372 { 373 374 doputwait(spc, rw, rhdr); 375 pthread_mutex_unlock(&spc->spc_mtx); 376 } 377 378 static void 379 putwait(struct spclient *spc, struct respwait *rw, struct rsp_hdr *rhdr) 380 { 381 382 doputwait(spc, rw, rhdr); 383 sendlockl(spc); 384 pthread_mutex_unlock(&spc->spc_mtx); 385 } 386 387 static void 388 dounputwait(struct spclient *spc, struct respwait *rw) 389 { 390 391 TAILQ_REMOVE(&spc->spc_respwait, rw, rw_entries); 392 pthread_mutex_unlock(&spc->spc_mtx); 393 pthread_cond_destroy(&rw->rw_cv); 394 395 } 396 397 static void __unused 398 unputwait_locked(struct spclient *spc, struct respwait *rw) 399 { 400 401 pthread_mutex_lock(&spc->spc_mtx); 402 dounputwait(spc, rw); 403 } 404 405 static void 406 unputwait(struct spclient *spc, struct respwait *rw) 407 { 408 409 pthread_mutex_lock(&spc->spc_mtx); 410 sendunlockl(spc); 411 412 dounputwait(spc, rw); 413 } 414 415 static void 416 kickwaiter(struct spclient *spc) 417 { 418 struct respwait *rw; 419 int error = 0; 420 421 pthread_mutex_lock(&spc->spc_mtx); 422 TAILQ_FOREACH(rw, &spc->spc_respwait, rw_entries) { 423 if (rw->rw_reqno == spc->spc_hdr.rsp_reqno) 424 break; 425 } 426 if (rw == NULL) { 427 DPRINTF(("no waiter found, invalid reqno %" PRIu64 "?\n", 428 spc->spc_hdr.rsp_reqno)); 429 pthread_mutex_unlock(&spc->spc_mtx); 430 spcfreebuf(spc); 431 return; 432 } 433 DPRINTF(("rump_sp: client %p woke up waiter at %p\n", spc, rw)); 434 rw->rw_data = spc->spc_buf; 435 rw->rw_done = 1; 436 rw->rw_dlen = (size_t)(spc->spc_off - HDRSZ); 437 if (spc->spc_hdr.rsp_class == RUMPSP_ERROR) { 438 error = rw->rw_error = errmap(spc->spc_hdr.rsp_error); 439 } 440 pthread_cond_signal(&rw->rw_cv); 441 pthread_mutex_unlock(&spc->spc_mtx); 442 443 if (error) 444 spcfreebuf(spc); 445 else 446 spcresetbuf(spc); 447 } 448 449 static void 450 kickall(struct spclient *spc) 451 { 452 struct respwait *rw; 453 454 /* DIAGASSERT(mutex_owned(spc_lock)) */ 455 TAILQ_FOREACH(rw, &spc->spc_respwait, rw_entries) 456 pthread_cond_broadcast(&rw->rw_cv); 457 } 458 459 static int 460 readframe(struct spclient *spc) 461 { 462 int fd = spc->spc_fd; 463 size_t left; 464 size_t framelen; 465 ssize_t n; 466 467 /* still reading header? */ 468 if (spc->spc_off < HDRSZ) { 469 DPRINTF(("rump_sp: readframe getting header at offset %zu\n", 470 spc->spc_off)); 471 472 left = HDRSZ - spc->spc_off; 473 /*LINTED: cast ok */ 474 n = host_read(fd, (uint8_t*)&spc->spc_hdr + spc->spc_off, left); 475 if (n == 0) { 476 return -1; 477 } 478 if (n == -1) { 479 if (errno == EAGAIN) 480 return 0; 481 return -1; 482 } 483 484 spc->spc_off += n; 485 if (spc->spc_off < HDRSZ) { 486 return 0; 487 } 488 489 /*LINTED*/ 490 framelen = spc->spc_hdr.rsp_len; 491 492 if (framelen < HDRSZ) { 493 return -1; 494 } else if (framelen == HDRSZ) { 495 return 1; 496 } 497 498 spc->spc_buf = malloc(framelen - HDRSZ); 499 if (spc->spc_buf == NULL) { 500 return -1; 501 } 502 memset(spc->spc_buf, 0, framelen - HDRSZ); 503 504 /* "fallthrough" */ 505 } else { 506 /*LINTED*/ 507 framelen = spc->spc_hdr.rsp_len; 508 } 509 510 left = framelen - spc->spc_off; 511 512 DPRINTF(("rump_sp: readframe getting body at offset %zu, left %zu\n", 513 spc->spc_off, left)); 514 515 if (left == 0) 516 return 1; 517 n = host_read(fd, spc->spc_buf + (spc->spc_off - HDRSZ), left); 518 if (n == 0) { 519 return -1; 520 } 521 if (n == -1) { 522 if (errno == EAGAIN) 523 return 0; 524 return -1; 525 } 526 spc->spc_off += n; 527 left -= n; 528 529 /* got everything? */ 530 if (left == 0) 531 return 1; 532 else 533 return 0; 534 } 535 536 static int 537 tcp_parse(const char *addr, struct sockaddr **sa, int allow_wildcard) 538 { 539 struct sockaddr_in sin; 540 char buf[64]; 541 const char *p; 542 size_t l; 543 int port; 544 545 memset(&sin, 0, sizeof(sin)); 546 SA_SETLEN(&sin, sizeof(sin)); 547 sin.sin_family = AF_INET; 548 549 p = strchr(addr, ':'); 550 if (!p) { 551 fprintf(stderr, "rump_sp_tcp: missing port specifier\n"); 552 return EINVAL; 553 } 554 555 l = p - addr; 556 if (l > sizeof(buf)-1) { 557 fprintf(stderr, "rump_sp_tcp: address too long\n"); 558 return EINVAL; 559 } 560 strncpy(buf, addr, l); 561 buf[l] = '\0'; 562 563 /* special INADDR_ANY treatment */ 564 if (strcmp(buf, "*") == 0 || strcmp(buf, "0") == 0) { 565 sin.sin_addr.s_addr = INADDR_ANY; 566 } else { 567 switch (inet_pton(AF_INET, buf, &sin.sin_addr)) { 568 case 1: 569 break; 570 case 0: 571 fprintf(stderr, "rump_sp_tcp: cannot parse %s\n", buf); 572 return EINVAL; 573 case -1: 574 fprintf(stderr, "rump_sp_tcp: inet_pton failed\n"); 575 return errno; 576 default: 577 assert(/*CONSTCOND*/0); 578 return EINVAL; 579 } 580 } 581 582 if (!allow_wildcard && sin.sin_addr.s_addr == INADDR_ANY) { 583 fprintf(stderr, "rump_sp_tcp: client needs !INADDR_ANY\n"); 584 return EINVAL; 585 } 586 587 /* advance to port number & parse */ 588 p++; 589 l = strspn(p, "0123456789"); 590 if (l == 0) { 591 fprintf(stderr, "rump_sp_tcp: port now found: %s\n", p); 592 return EINVAL; 593 } 594 strncpy(buf, p, l); 595 buf[l] = '\0'; 596 597 if (*(p+l) != '/' && *(p+l) != '\0') { 598 fprintf(stderr, "rump_sp_tcp: junk at end of port: %s\n", addr); 599 return EINVAL; 600 } 601 602 port = atoi(buf); 603 if (port < 0 || port >= (1<<(8*sizeof(in_port_t)))) { 604 fprintf(stderr, "rump_sp_tcp: port %d out of range\n", port); 605 return ERANGE; 606 } 607 sin.sin_port = htons(port); 608 609 *sa = malloc(sizeof(sin)); 610 if (*sa == NULL) 611 return errno; 612 memcpy(*sa, &sin, sizeof(sin)); 613 return 0; 614 } 615 616 static int 617 tcp_connecthook(int s) 618 { 619 int x; 620 621 x = 1; 622 host_setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &x, sizeof(x)); 623 624 return 0; 625 } 626 627 static char parsedurl[256]; 628 629 /*ARGSUSED*/ 630 static int 631 unix_parse(const char *addr, struct sockaddr **sa, int allow_wildcard) 632 { 633 struct sockaddr_un sun; 634 size_t slen; 635 int savepath = 0; 636 637 if (strlen(addr) >= sizeof(sun.sun_path)) 638 return ENAMETOOLONG; 639 640 /* 641 * The pathname can be all kinds of spaghetti elementals, 642 * so meek and obidient we accept everything. However, use 643 * full path for easy cleanup in case someone gives a relative 644 * one and the server does a chdir() between now than the 645 * cleanup. 646 */ 647 memset(&sun, 0, sizeof(sun)); 648 sun.sun_family = AF_LOCAL; 649 if (*addr != '/') { 650 char mywd[PATH_MAX]; 651 652 if (getcwd(mywd, sizeof(mywd)) == NULL) { 653 fprintf(stderr, "warning: cannot determine cwd, " 654 "omitting socket cleanup\n"); 655 } else { 656 if (strlen(addr)+strlen(mywd)+1 >= sizeof(sun.sun_path)) 657 return ENAMETOOLONG; 658 strcpy(sun.sun_path, mywd); 659 strcat(sun.sun_path, "/"); 660 savepath = 1; 661 } 662 } 663 strcat(sun.sun_path, addr); 664 #ifdef __linux__ 665 slen = sizeof(sun); 666 #else 667 sun.sun_len = SUN_LEN(&sun); 668 slen = sun.sun_len+1; /* get the 0 too */ 669 #endif 670 671 if (savepath && *parsedurl == '\0') { 672 snprintf(parsedurl, sizeof(parsedurl), 673 "unix://%s", sun.sun_path); 674 } 675 676 *sa = malloc(slen); 677 if (*sa == NULL) 678 return errno; 679 memcpy(*sa, &sun, slen); 680 681 return 0; 682 } 683 684 static void 685 unix_cleanup(struct sockaddr *sa) 686 { 687 struct sockaddr_un *sun = (void *)sa; 688 689 /* 690 * cleanup only absolute paths. see unix_parse() above 691 */ 692 if (*sun->sun_path == '/') { 693 unlink(sun->sun_path); 694 } 695 } 696 697 /*ARGSUSED*/ 698 static int 699 notsupp(void) 700 { 701 702 fprintf(stderr, "rump_sp: support not yet implemented\n"); 703 return EOPNOTSUPP; 704 } 705 706 static int 707 success(void) 708 { 709 710 return 0; 711 } 712 713 struct { 714 const char *id; 715 int domain; 716 socklen_t slen; 717 addrparse_fn ap; 718 connecthook_fn connhook; 719 cleanup_fn cleanup; 720 } parsetab[] = { 721 { "tcp", PF_INET, sizeof(struct sockaddr_in), 722 tcp_parse, tcp_connecthook, (cleanup_fn)success }, 723 { "unix", PF_LOCAL, sizeof(struct sockaddr_un), 724 unix_parse, (connecthook_fn)success, unix_cleanup }, 725 { "tcp6", PF_INET6, sizeof(struct sockaddr_in6), 726 (addrparse_fn)notsupp, (connecthook_fn)success, 727 (cleanup_fn)success }, 728 }; 729 #define NPARSE (sizeof(parsetab)/sizeof(parsetab[0])) 730 731 static int 732 parseurl(const char *url, struct sockaddr **sap, unsigned *idxp, 733 int allow_wildcard) 734 { 735 char id[16]; 736 const char *p, *p2; 737 size_t l; 738 unsigned i; 739 int error; 740 741 /* 742 * Parse the url 743 */ 744 745 p = url; 746 p2 = strstr(p, "://"); 747 if (!p2) { 748 fprintf(stderr, "rump_sp: invalid locator ``%s''\n", p); 749 return EINVAL; 750 } 751 l = p2-p; 752 if (l > sizeof(id)-1) { 753 fprintf(stderr, "rump_sp: identifier too long in ``%s''\n", p); 754 return EINVAL; 755 } 756 757 strncpy(id, p, l); 758 id[l] = '\0'; 759 p2 += 3; /* beginning of address */ 760 761 for (i = 0; i < NPARSE; i++) { 762 if (strcmp(id, parsetab[i].id) == 0) { 763 error = parsetab[i].ap(p2, sap, allow_wildcard); 764 if (error) 765 return error; 766 break; 767 } 768 } 769 if (i == NPARSE) { 770 fprintf(stderr, "rump_sp: invalid identifier ``%s''\n", p); 771 return EINVAL; 772 } 773 774 *idxp = i; 775 return 0; 776 } 777