1 /*- 2 * Copyright (c) 2010 Weongyo Jeong <weongyo@freebsd.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer, 10 * without modification. 11 * 2. Redistributions in binary form must reproduce at minimum a disclaimer 12 * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any 13 * redistribution must be conditioned upon including a substantially 14 * similar Disclaimer requirement for further binary redistribution. 15 * 16 * NO WARRANTY 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY 20 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, 22 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 25 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 27 * THE POSSIBILITY OF SUCH DAMAGES. 28 * 29 * $FreeBSD$ 30 */ 31 32 #include <sys/param.h> 33 #include <sys/endian.h> 34 #include <sys/ioctl.h> 35 #include <sys/socket.h> 36 #include <sys/stat.h> 37 #include <sys/utsname.h> 38 #include <net/if.h> 39 #include <net/bpf.h> 40 #include <dev/usb/usb.h> 41 #include <dev/usb/usb_pf.h> 42 #include <dev/usb/usbdi.h> 43 #include <errno.h> 44 #include <fcntl.h> 45 #include <limits.h> 46 #include <stdio.h> 47 #include <stdlib.h> 48 #include <string.h> 49 #include <time.h> 50 #include <unistd.h> 51 #include <sysexits.h> 52 #include <err.h> 53 54 struct usbcap { 55 int fd; /* fd for /dev/usbpf */ 56 uint32_t bufsize; 57 uint8_t *buffer; 58 59 /* for -w option */ 60 int wfd; 61 /* for -r option */ 62 int rfd; 63 }; 64 65 struct usbcap_filehdr { 66 uint32_t magic; 67 #define USBCAP_FILEHDR_MAGIC 0x9a90000e 68 uint8_t major; 69 uint8_t minor; 70 uint8_t reserved[26]; 71 } __packed; 72 73 static int doexit = 0; 74 static int pkt_captured = 0; 75 static int verbose = 0; 76 static const char *i_arg = "usbus0"; 77 static const char *r_arg = NULL; 78 static const char *w_arg = NULL; 79 static const char *errstr_table[USB_ERR_MAX] = { 80 [USB_ERR_NORMAL_COMPLETION] = "0", 81 [USB_ERR_PENDING_REQUESTS] = "PENDING_REQUESTS", 82 [USB_ERR_NOT_STARTED] = "NOT_STARTED", 83 [USB_ERR_INVAL] = "INVAL", 84 [USB_ERR_NOMEM] = "NOMEM", 85 [USB_ERR_CANCELLED] = "CANCELLED", 86 [USB_ERR_BAD_ADDRESS] = "BAD_ADDRESS", 87 [USB_ERR_BAD_BUFSIZE] = "BAD_BUFSIZE", 88 [USB_ERR_BAD_FLAG] = "BAD_FLAG", 89 [USB_ERR_NO_CALLBACK] = "NO_CALLBACK", 90 [USB_ERR_IN_USE] = "IN_USE", 91 [USB_ERR_NO_ADDR] = "NO_ADDR", 92 [USB_ERR_NO_PIPE] = "NO_PIPE", 93 [USB_ERR_ZERO_NFRAMES] = "ZERO_NFRAMES", 94 [USB_ERR_ZERO_MAXP] = "ZERO_MAXP", 95 [USB_ERR_SET_ADDR_FAILED] = "SET_ADDR_FAILED", 96 [USB_ERR_NO_POWER] = "NO_POWER", 97 [USB_ERR_TOO_DEEP] = "TOO_DEEP", 98 [USB_ERR_IOERROR] = "IOERROR", 99 [USB_ERR_NOT_CONFIGURED] = "NOT_CONFIGURED", 100 [USB_ERR_TIMEOUT] = "TIMEOUT", 101 [USB_ERR_SHORT_XFER] = "SHORT_XFER", 102 [USB_ERR_STALLED] = "STALLED", 103 [USB_ERR_INTERRUPTED] = "INTERRUPTED", 104 [USB_ERR_DMA_LOAD_FAILED] = "DMA_LOAD_FAILED", 105 [USB_ERR_BAD_CONTEXT] = "BAD_CONTEXT", 106 [USB_ERR_NO_ROOT_HUB] = "NO_ROOT_HUB", 107 [USB_ERR_NO_INTR_THREAD] = "NO_INTR_THREAD", 108 [USB_ERR_NOT_LOCKED] = "NOT_LOCKED", 109 }; 110 111 static const char *xfertype_table[4] = { 112 [UE_CONTROL] = "CTRL", 113 [UE_ISOCHRONOUS] = "ISOC", 114 [UE_BULK] = "BULK", 115 [UE_INTERRUPT] = "INTR" 116 }; 117 118 static const char *speed_table[USB_SPEED_MAX] = { 119 [USB_SPEED_FULL] = "FULL", 120 [USB_SPEED_HIGH] = "HIGH", 121 [USB_SPEED_LOW] = "LOW", 122 [USB_SPEED_VARIABLE] = "VARI", 123 [USB_SPEED_SUPER] = "SUPER", 124 }; 125 126 static void 127 handle_sigint(int sig) 128 { 129 130 (void)sig; 131 doexit = 1; 132 } 133 134 #define FLAGS(x, name) \ 135 (((x) & USBPF_FLAG_##name) ? #name "|" : "") 136 137 #define STATUS(x, name) \ 138 (((x) & USBPF_STATUS_##name) ? #name "|" : "") 139 140 static const char * 141 usb_errstr(uint32_t error) 142 { 143 if (error >= USB_ERR_MAX || errstr_table[error] == NULL) 144 return ("UNKNOWN"); 145 else 146 return (errstr_table[error]); 147 } 148 149 static const char * 150 usb_speedstr(uint8_t speed) 151 { 152 if (speed >= USB_SPEED_MAX || speed_table[speed] == NULL) 153 return ("UNKNOWN"); 154 else 155 return (speed_table[speed]); 156 } 157 158 static void 159 print_flags(uint32_t flags) 160 { 161 printf(" flags %#x <%s%s%s%s%s%s%s%s%s0>\n", 162 flags, 163 FLAGS(flags, FORCE_SHORT_XFER), 164 FLAGS(flags, SHORT_XFER_OK), 165 FLAGS(flags, SHORT_FRAMES_OK), 166 FLAGS(flags, PIPE_BOF), 167 FLAGS(flags, PROXY_BUFFER), 168 FLAGS(flags, EXT_BUFFER), 169 FLAGS(flags, MANUAL_STATUS), 170 FLAGS(flags, NO_PIPE_OK), 171 FLAGS(flags, STALL_PIPE)); 172 } 173 174 static void 175 print_status(uint32_t status) 176 { 177 printf(" status %#x <%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s0>\n", 178 status, 179 STATUS(status, OPEN), 180 STATUS(status, TRANSFERRING), 181 STATUS(status, DID_DMA_DELAY), 182 STATUS(status, DID_CLOSE), 183 STATUS(status, DRAINING), 184 STATUS(status, STARTED), 185 STATUS(status, BW_RECLAIMED), 186 STATUS(status, CONTROL_XFR), 187 STATUS(status, CONTROL_HDR), 188 STATUS(status, CONTROL_ACT), 189 STATUS(status, CONTROL_STALL), 190 STATUS(status, SHORT_FRAMES_OK), 191 STATUS(status, SHORT_XFER_OK), 192 STATUS(status, BDMA_ENABLE), 193 STATUS(status, BDMA_NO_POST_SYNC), 194 STATUS(status, BDMA_SETUP), 195 STATUS(status, ISOCHRONOUS_XFR), 196 STATUS(status, CURR_DMA_SET), 197 STATUS(status, CAN_CANCEL_IMMED), 198 STATUS(status, DOING_CALLBACK)); 199 } 200 201 /* 202 * Dump a byte into hex format. 203 */ 204 static void 205 hexbyte(char *buf, uint8_t temp) 206 { 207 uint8_t lo; 208 uint8_t hi; 209 210 lo = temp & 0xF; 211 hi = temp >> 4; 212 213 if (hi < 10) 214 buf[0] = '0' + hi; 215 else 216 buf[0] = 'A' + hi - 10; 217 218 if (lo < 10) 219 buf[1] = '0' + lo; 220 else 221 buf[1] = 'A' + lo - 10; 222 } 223 224 /* 225 * Display a region in traditional hexdump format. 226 */ 227 static void 228 hexdump(const uint8_t *region, uint32_t len) 229 { 230 const uint8_t *line; 231 char linebuf[128]; 232 int i; 233 int x; 234 int c; 235 236 for (line = region; line < (region + len); line += 16) { 237 238 i = 0; 239 240 linebuf[i] = ' '; 241 hexbyte(linebuf + i + 1, ((line - region) >> 8) & 0xFF); 242 hexbyte(linebuf + i + 3, (line - region) & 0xFF); 243 linebuf[i + 5] = ' '; 244 linebuf[i + 6] = ' '; 245 i += 7; 246 247 for (x = 0; x < 16; x++) { 248 if ((line + x) < (region + len)) { 249 hexbyte(linebuf + i, 250 *(const u_int8_t *)(line + x)); 251 } else { 252 linebuf[i] = '-'; 253 linebuf[i + 1] = '-'; 254 } 255 linebuf[i + 2] = ' '; 256 if (x == 7) { 257 linebuf[i + 3] = ' '; 258 i += 4; 259 } else { 260 i += 3; 261 } 262 } 263 linebuf[i] = ' '; 264 linebuf[i + 1] = '|'; 265 i += 2; 266 for (x = 0; x < 16; x++) { 267 if ((line + x) < (region + len)) { 268 c = *(const u_int8_t *)(line + x); 269 /* !isprint(c) */ 270 if ((c < ' ') || (c > '~')) 271 c = '.'; 272 linebuf[i] = c; 273 } else { 274 linebuf[i] = ' '; 275 } 276 i++; 277 } 278 linebuf[i] = '|'; 279 linebuf[i + 1] = 0; 280 i += 2; 281 puts(linebuf); 282 } 283 } 284 285 static void 286 print_apacket(const struct bpf_xhdr *hdr, const uint8_t *ptr, int ptr_len) 287 { 288 struct tm *tm; 289 struct usbpf_pkthdr up_temp; 290 struct usbpf_pkthdr *up; 291 struct timeval tv; 292 size_t len; 293 uint32_t x; 294 char buf[64]; 295 296 ptr += USBPF_HDR_LEN; 297 ptr_len -= USBPF_HDR_LEN; 298 if (ptr_len < 0) 299 return; 300 301 /* make sure we don't change the source buffer */ 302 memcpy(&up_temp, ptr - USBPF_HDR_LEN, sizeof(up_temp)); 303 up = &up_temp; 304 305 /* 306 * A packet from the kernel is based on little endian byte 307 * order. 308 */ 309 up->up_totlen = le32toh(up->up_totlen); 310 up->up_busunit = le32toh(up->up_busunit); 311 up->up_address = le32toh(up->up_address); 312 up->up_flags = le32toh(up->up_flags); 313 up->up_status = le32toh(up->up_status); 314 up->up_error = le32toh(up->up_error); 315 up->up_interval = le32toh(up->up_interval); 316 up->up_frames = le32toh(up->up_frames); 317 up->up_packet_size = le32toh(up->up_packet_size); 318 up->up_packet_count = le32toh(up->up_packet_count); 319 up->up_endpoint = le32toh(up->up_endpoint); 320 321 tv.tv_sec = hdr->bh_tstamp.bt_sec; 322 tv.tv_usec = hdr->bh_tstamp.bt_frac; 323 tm = localtime(&tv.tv_sec); 324 325 len = strftime(buf, sizeof(buf), "%H:%M:%S", tm); 326 327 printf("%.*s.%06ld usbus%d.%d %s-%s-EP=%08x,SPD=%s,NFR=%d,SLEN=%d,IVAL=%d%s%s\n", 328 (int)len, buf, tv.tv_usec, 329 (int)up->up_busunit, (int)up->up_address, 330 (up->up_type == USBPF_XFERTAP_SUBMIT) ? "SUBM" : "DONE", 331 xfertype_table[up->up_xfertype], 332 (unsigned int)up->up_endpoint, 333 usb_speedstr(up->up_speed), 334 (int)up->up_frames, 335 (int)(up->up_totlen - USBPF_HDR_LEN - 336 (USBPF_FRAME_HDR_LEN * up->up_frames)), 337 (int)up->up_interval, 338 (up->up_type == USBPF_XFERTAP_DONE) ? ",ERR=" : "", 339 (up->up_type == USBPF_XFERTAP_DONE) ? 340 usb_errstr(up->up_error) : ""); 341 342 if (verbose >= 1) { 343 for (x = 0; x != up->up_frames; x++) { 344 const struct usbpf_framehdr *uf; 345 uint32_t framelen; 346 uint32_t flags; 347 348 uf = (const struct usbpf_framehdr *)ptr; 349 ptr += USBPF_FRAME_HDR_LEN; 350 ptr_len -= USBPF_FRAME_HDR_LEN; 351 if (ptr_len < 0) 352 return; 353 354 framelen = le32toh(uf->length); 355 flags = le32toh(uf->flags); 356 357 printf(" frame[%u] %s %d bytes\n", 358 (unsigned int)x, 359 (flags & USBPF_FRAMEFLAG_READ) ? "READ" : "WRITE", 360 (int)framelen); 361 362 if (flags & USBPF_FRAMEFLAG_DATA_FOLLOWS) { 363 364 int tot_frame_len; 365 366 tot_frame_len = USBPF_FRAME_ALIGN(framelen); 367 368 ptr_len -= tot_frame_len; 369 370 if (tot_frame_len < 0 || 371 (int)framelen < 0 || (int)ptr_len < 0) 372 break; 373 374 hexdump(ptr, framelen); 375 376 ptr += tot_frame_len; 377 } 378 } 379 } 380 if (verbose >= 2) 381 print_flags(up->up_flags); 382 if (verbose >= 3) 383 print_status(up->up_status); 384 } 385 386 static void 387 print_packets(uint8_t *data, const int datalen) 388 { 389 const struct bpf_xhdr *hdr; 390 uint8_t *ptr; 391 uint8_t *next; 392 393 for (ptr = data; ptr < (data + datalen); ptr = next) { 394 hdr = (const struct bpf_xhdr *)ptr; 395 next = ptr + BPF_WORDALIGN(hdr->bh_hdrlen + hdr->bh_caplen); 396 397 if (w_arg == NULL) { 398 print_apacket(hdr, ptr + 399 hdr->bh_hdrlen, hdr->bh_caplen); 400 } 401 pkt_captured++; 402 } 403 } 404 405 static void 406 write_packets(struct usbcap *p, const uint8_t *data, const int datalen) 407 { 408 int len = htole32(datalen); 409 int ret; 410 411 ret = write(p->wfd, &len, sizeof(int)); 412 if (ret != sizeof(int)) { 413 err(EXIT_FAILURE, "Could not write length " 414 "field of USB data payload"); 415 } 416 ret = write(p->wfd, data, datalen); 417 if (ret != datalen) { 418 err(EXIT_FAILURE, "Could not write " 419 "complete USB data payload"); 420 } 421 } 422 423 static void 424 read_file(struct usbcap *p) 425 { 426 int datalen; 427 int ret; 428 uint8_t *data; 429 430 while ((ret = read(p->rfd, &datalen, sizeof(int))) == sizeof(int)) { 431 datalen = le32toh(datalen); 432 data = malloc(datalen); 433 if (data == NULL) 434 errx(EX_SOFTWARE, "Out of memory."); 435 ret = read(p->rfd, data, datalen); 436 if (ret != datalen) { 437 err(EXIT_FAILURE, "Could not read complete " 438 "USB data payload"); 439 } 440 print_packets(data, datalen); 441 free(data); 442 } 443 } 444 445 static void 446 do_loop(struct usbcap *p) 447 { 448 int cc; 449 450 while (doexit == 0) { 451 cc = read(p->fd, (uint8_t *)p->buffer, p->bufsize); 452 if (cc < 0) { 453 switch (errno) { 454 case EINTR: 455 break; 456 default: 457 fprintf(stderr, "read: %s\n", strerror(errno)); 458 return; 459 } 460 continue; 461 } 462 if (cc == 0) 463 continue; 464 if (w_arg != NULL) 465 write_packets(p, p->buffer, cc); 466 print_packets(p->buffer, cc); 467 } 468 } 469 470 static void 471 init_rfile(struct usbcap *p) 472 { 473 struct usbcap_filehdr uf; 474 int ret; 475 476 p->rfd = open(r_arg, O_RDONLY); 477 if (p->rfd < 0) { 478 err(EXIT_FAILURE, "Could not open " 479 "'%s' for read", r_arg); 480 } 481 ret = read(p->rfd, &uf, sizeof(uf)); 482 if (ret != sizeof(uf)) { 483 err(EXIT_FAILURE, "Could not read USB capture " 484 "file header"); 485 } 486 if (le32toh(uf.magic) != USBCAP_FILEHDR_MAGIC) { 487 errx(EX_SOFTWARE, "Invalid magic field(0x%08x) " 488 "in USB capture file header.", 489 (unsigned int)le32toh(uf.magic)); 490 } 491 if (uf.major != 0) { 492 errx(EX_SOFTWARE, "Invalid major version(%d) " 493 "field in USB capture file header.", (int)uf.major); 494 } 495 if (uf.minor != 2) { 496 errx(EX_SOFTWARE, "Invalid minor version(%d) " 497 "field in USB capture file header.", (int)uf.minor); 498 } 499 } 500 501 static void 502 init_wfile(struct usbcap *p) 503 { 504 struct usbcap_filehdr uf; 505 int ret; 506 507 p->wfd = open(w_arg, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR); 508 if (p->wfd < 0) { 509 err(EXIT_FAILURE, "Could not open " 510 "'%s' for write", r_arg); 511 } 512 memset(&uf, 0, sizeof(uf)); 513 uf.magic = htole32(USBCAP_FILEHDR_MAGIC); 514 uf.major = 0; 515 uf.minor = 2; 516 ret = write(p->wfd, (const void *)&uf, sizeof(uf)); 517 if (ret != sizeof(uf)) { 518 err(EXIT_FAILURE, "Could not write " 519 "USB capture header"); 520 } 521 } 522 523 static void 524 usage(void) 525 { 526 527 #define FMT " %-14s %s\n" 528 fprintf(stderr, "usage: usbdump [options]\n"); 529 fprintf(stderr, FMT, "-i <usbusX>", "Listen on USB bus interface"); 530 fprintf(stderr, FMT, "-r <file>", "Read the raw packets from file"); 531 fprintf(stderr, FMT, "-s <snaplen>", "Snapshot bytes from each packet"); 532 fprintf(stderr, FMT, "-v", "Increase the verbose level"); 533 fprintf(stderr, FMT, "-w <file>", "Write the raw packets to file"); 534 #undef FMT 535 exit(EX_USAGE); 536 } 537 538 int 539 main(int argc, char *argv[]) 540 { 541 struct timeval tv; 542 struct bpf_insn total_insn; 543 struct bpf_program total_prog; 544 struct bpf_stat us; 545 struct bpf_version bv; 546 struct usbcap uc, *p = &uc; 547 struct ifreq ifr; 548 long snapshot = 192; 549 uint32_t v; 550 int fd, o; 551 const char *optstring; 552 553 memset(&uc, 0, sizeof(struct usbcap)); 554 555 optstring = "i:r:s:vw:"; 556 while ((o = getopt(argc, argv, optstring)) != -1) { 557 switch (o) { 558 case 'i': 559 i_arg = optarg; 560 break; 561 case 'r': 562 r_arg = optarg; 563 init_rfile(p); 564 break; 565 case 's': 566 snapshot = strtol(optarg, NULL, 10); 567 errno = 0; 568 if (snapshot == 0 && errno == EINVAL) 569 usage(); 570 /* snapeshot == 0 is special */ 571 if (snapshot == 0) 572 snapshot = -1; 573 break; 574 case 'v': 575 verbose++; 576 break; 577 case 'w': 578 w_arg = optarg; 579 init_wfile(p); 580 break; 581 default: 582 usage(); 583 /* NOTREACHED */ 584 } 585 } 586 587 if (r_arg != NULL) { 588 read_file(p); 589 exit(EXIT_SUCCESS); 590 } 591 592 p->fd = fd = open("/dev/bpf", O_RDONLY); 593 if (p->fd < 0) 594 err(EXIT_FAILURE, "Could not open BPF device"); 595 596 if (ioctl(fd, BIOCVERSION, (caddr_t)&bv) < 0) 597 err(EXIT_FAILURE, "BIOCVERSION ioctl failed"); 598 599 if (bv.bv_major != BPF_MAJOR_VERSION || 600 bv.bv_minor < BPF_MINOR_VERSION) 601 errx(EXIT_FAILURE, "Kernel BPF filter out of date"); 602 603 /* USB transfers can be greater than 64KByte */ 604 v = 1U << 16; 605 606 /* clear ifr structure */ 607 memset(&ifr, 0, sizeof(ifr)); 608 609 for ( ; v >= USBPF_HDR_LEN; v >>= 1) { 610 (void)ioctl(fd, BIOCSBLEN, (caddr_t)&v); 611 (void)strncpy(ifr.ifr_name, i_arg, sizeof(ifr.ifr_name)); 612 if (ioctl(fd, BIOCSETIF, (caddr_t)&ifr) >= 0) 613 break; 614 } 615 if (v == 0) 616 errx(EXIT_FAILURE, "No buffer size worked."); 617 618 if (ioctl(fd, BIOCGBLEN, (caddr_t)&v) < 0) 619 err(EXIT_FAILURE, "BIOCGBLEN ioctl failed"); 620 621 p->bufsize = v; 622 p->buffer = (uint8_t *)malloc(p->bufsize); 623 if (p->buffer == NULL) 624 errx(EX_SOFTWARE, "Out of memory."); 625 626 /* XXX no read filter rules yet so at this moment accept everything */ 627 total_insn.code = (u_short)(BPF_RET | BPF_K); 628 total_insn.jt = 0; 629 total_insn.jf = 0; 630 total_insn.k = snapshot; 631 632 total_prog.bf_len = 1; 633 total_prog.bf_insns = &total_insn; 634 if (ioctl(p->fd, BIOCSETF, (caddr_t)&total_prog) < 0) 635 err(EXIT_FAILURE, "BIOCSETF ioctl failed"); 636 637 /* 1 second read timeout */ 638 tv.tv_sec = 1; 639 tv.tv_usec = 0; 640 if (ioctl(p->fd, BIOCSRTIMEOUT, (caddr_t)&tv) < 0) 641 err(EXIT_FAILURE, "BIOCSRTIMEOUT ioctl failed"); 642 643 (void)signal(SIGINT, handle_sigint); 644 645 do_loop(p); 646 647 if (ioctl(fd, BIOCGSTATS, (caddr_t)&us) < 0) 648 err(EXIT_FAILURE, "BIOCGSTATS ioctl failed"); 649 650 /* XXX what's difference between pkt_captured and us.us_recv? */ 651 printf("\n"); 652 printf("%d packets captured\n", pkt_captured); 653 printf("%d packets received by filter\n", us.bs_recv); 654 printf("%d packets dropped by kernel\n", us.bs_drop); 655 656 if (p->fd > 0) 657 close(p->fd); 658 if (p->rfd > 0) 659 close(p->rfd); 660 if (p->wfd > 0) 661 close(p->wfd); 662 663 return (EXIT_SUCCESS); 664 } 665