1 /* $OpenBSD: server_file.c,v 1.54 2015/05/05 11:10:13 florian Exp $ */ 2 3 /* 4 * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 #include <sys/time.h> 21 #include <sys/stat.h> 22 23 #include <limits.h> 24 #include <errno.h> 25 #include <fcntl.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <unistd.h> 29 #include <stdio.h> 30 #include <dirent.h> 31 #include <time.h> 32 #include <event.h> 33 34 #include "httpd.h" 35 #include "http.h" 36 37 #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) 38 #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) 39 #define MAX_RANGES 4 40 41 struct range { 42 off_t start; 43 off_t end; 44 }; 45 46 int server_file_access(struct httpd *, struct client *, 47 char *, size_t); 48 int server_file_request(struct httpd *, struct client *, 49 char *, struct stat *); 50 int server_partial_file_request(struct httpd *, struct client *, 51 char *, struct stat *, char *); 52 int server_file_index(struct httpd *, struct client *, 53 struct stat *); 54 int server_file_modified_since(struct http_descriptor *, 55 struct stat *); 56 int server_file_method(struct client *); 57 int parse_range_spec(char *, size_t, struct range *); 58 struct range *parse_range(char *, size_t, int *); 59 int buffer_add_range(int, struct evbuffer *, struct range *); 60 61 int 62 server_file_access(struct httpd *env, struct client *clt, 63 char *path, size_t len) 64 { 65 struct http_descriptor *desc = clt->clt_descreq; 66 struct server_config *srv_conf = clt->clt_srv_conf; 67 struct stat st; 68 struct kv *r, key; 69 char *newpath; 70 int ret; 71 72 errno = 0; 73 74 if (access(path, R_OK) == -1) { 75 goto fail; 76 } else if (stat(path, &st) == -1) { 77 goto fail; 78 } else if (S_ISDIR(st.st_mode)) { 79 /* Deny access if directory indexing is disabled */ 80 if (srv_conf->flags & SRVFLAG_NO_INDEX) { 81 errno = EACCES; 82 goto fail; 83 } 84 85 if (desc->http_path_alias != NULL) { 86 /* Recursion - the index "file" is a directory? */ 87 errno = EINVAL; 88 goto fail; 89 } 90 91 /* Redirect to path with trailing "/" */ 92 if (path[strlen(path) - 1] != '/') { 93 if (asprintf(&newpath, "http%s://%s%s/", 94 srv_conf->flags & SRVFLAG_TLS ? "s" : "", 95 desc->http_host, desc->http_path) == -1) 96 return (500); 97 /* Path alias will be used for the redirection */ 98 desc->http_path_alias = newpath; 99 100 /* Indicate that the file has been moved */ 101 return (301); 102 } 103 104 /* Append the default index file to the location */ 105 if (asprintf(&newpath, "%s%s", desc->http_path, 106 srv_conf->index) == -1) 107 return (500); 108 desc->http_path_alias = newpath; 109 if (server_getlocation(clt, newpath) != srv_conf) { 110 /* The location has changed */ 111 return (server_file(env, clt)); 112 } 113 114 /* Otherwise append the default index file to the path */ 115 if (strlcat(path, srv_conf->index, len) >= len) { 116 errno = EACCES; 117 goto fail; 118 } 119 120 ret = server_file_access(env, clt, path, len); 121 if (ret == 404) { 122 /* 123 * Index file not found; fail if auto-indexing is 124 * not enabled, otherwise return success but 125 * indicate directory with S_ISDIR of the previous 126 * stat. 127 */ 128 if ((srv_conf->flags & SRVFLAG_AUTO_INDEX) == 0) { 129 errno = EACCES; 130 goto fail; 131 } 132 133 return (server_file_index(env, clt, &st)); 134 } 135 return (ret); 136 } else if (!S_ISREG(st.st_mode)) { 137 /* Don't follow symlinks and ignore special files */ 138 errno = EACCES; 139 goto fail; 140 } 141 142 key.kv_key = "Range"; 143 r = kv_find(&desc->http_headers, &key); 144 if (r != NULL) 145 return (server_partial_file_request(env, clt, path, &st, 146 r->kv_value)); 147 else 148 return (server_file_request(env, clt, path, &st)); 149 150 fail: 151 switch (errno) { 152 case ENOENT: 153 case ENOTDIR: 154 return (404); 155 case EACCES: 156 return (403); 157 default: 158 return (500); 159 } 160 161 /* NOTREACHED */ 162 } 163 164 int 165 server_file(struct httpd *env, struct client *clt) 166 { 167 struct http_descriptor *desc = clt->clt_descreq; 168 struct server_config *srv_conf = clt->clt_srv_conf; 169 char path[PATH_MAX]; 170 const char *stripped, *errstr = NULL; 171 int ret = 500; 172 173 if (srv_conf->flags & SRVFLAG_FCGI) 174 return (server_fcgi(env, clt)); 175 176 /* Request path is already canonicalized */ 177 stripped = server_root_strip( 178 desc->http_path_alias != NULL ? 179 desc->http_path_alias : desc->http_path, 180 srv_conf->strip); 181 if ((size_t)snprintf(path, sizeof(path), "%s%s", 182 srv_conf->root, stripped) >= sizeof(path)) { 183 errstr = desc->http_path; 184 goto abort; 185 } 186 187 /* Returns HTTP status code on error */ 188 if ((ret = server_file_access(env, clt, path, sizeof(path))) > 0) { 189 errstr = desc->http_path_alias != NULL ? 190 desc->http_path_alias : desc->http_path; 191 goto abort; 192 } 193 194 return (ret); 195 196 abort: 197 if (errstr == NULL) 198 errstr = strerror(errno); 199 server_abort_http(clt, ret, errstr); 200 return (-1); 201 } 202 203 int 204 server_file_method(struct client *clt) 205 { 206 struct http_descriptor *desc = clt->clt_descreq; 207 208 switch (desc->http_method) { 209 case HTTP_METHOD_GET: 210 case HTTP_METHOD_HEAD: 211 return (0); 212 default: 213 /* Other methods are not allowed */ 214 errno = EACCES; 215 return (405); 216 } 217 /* NOTREACHED */ 218 } 219 220 int 221 server_file_request(struct httpd *env, struct client *clt, char *path, 222 struct stat *st) 223 { 224 struct server_config *srv_conf = clt->clt_srv_conf; 225 struct media_type *media; 226 const char *errstr = NULL; 227 int fd = -1, ret, code = 500; 228 229 if ((ret = server_file_method(clt)) != 0) { 230 code = ret; 231 goto abort; 232 } 233 234 if ((ret = server_file_modified_since(clt->clt_descreq, st)) != -1) 235 return ret; 236 237 /* Now open the file, should be readable or we have another problem */ 238 if ((fd = open(path, O_RDONLY)) == -1) 239 goto abort; 240 241 media = media_find(env->sc_mediatypes, path); 242 ret = server_response_http(clt, 200, media, st->st_size, 243 MINIMUM(time(NULL), st->st_mtim.tv_sec)); 244 switch (ret) { 245 case -1: 246 goto fail; 247 case 0: 248 /* Connection is already finished */ 249 close(fd); 250 goto done; 251 default: 252 break; 253 } 254 255 clt->clt_fd = fd; 256 if (clt->clt_srvbev != NULL) 257 bufferevent_free(clt->clt_srvbev); 258 259 clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read, 260 server_write, server_file_error, clt); 261 if (clt->clt_srvbev == NULL) { 262 errstr = "failed to allocate file buffer event"; 263 goto fail; 264 } 265 266 /* Adjust read watermark to the socket output buffer size */ 267 bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0, 268 clt->clt_sndbufsiz); 269 270 bufferevent_settimeout(clt->clt_srvbev, 271 srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); 272 bufferevent_enable(clt->clt_srvbev, EV_READ); 273 bufferevent_disable(clt->clt_bev, EV_READ); 274 275 done: 276 server_reset_http(clt); 277 return (0); 278 fail: 279 bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); 280 bufferevent_free(clt->clt_bev); 281 clt->clt_bev = NULL; 282 abort: 283 if (errstr == NULL) 284 errstr = strerror(errno); 285 server_abort_http(clt, code, errstr); 286 return (-1); 287 } 288 289 int 290 server_partial_file_request(struct httpd *env, struct client *clt, char *path, 291 struct stat *st, char *range_str) 292 { 293 struct http_descriptor *resp = clt->clt_descresp; 294 struct http_descriptor *desc = clt->clt_descreq; 295 struct media_type *media, multipart_media; 296 struct range *range; 297 struct evbuffer *evb = NULL; 298 size_t content_length; 299 int code = 500, fd = -1, i, nranges, ret; 300 uint32_t boundary; 301 char content_range[64]; 302 const char *errstr = NULL; 303 304 /* Ignore range request for methods other than GET */ 305 if (desc->http_method != HTTP_METHOD_GET) 306 return server_file_request(env, clt, path, st); 307 308 if ((range = parse_range(range_str, st->st_size, &nranges)) == NULL) { 309 code = 416; 310 (void)snprintf(content_range, sizeof(content_range), 311 "bytes */%lld", st->st_size); 312 errstr = content_range; 313 goto abort; 314 } 315 316 /* Now open the file, should be readable or we have another problem */ 317 if ((fd = open(path, O_RDONLY)) == -1) 318 goto abort; 319 320 media = media_find(env->sc_mediatypes, path); 321 if ((evb = evbuffer_new()) == NULL) { 322 errstr = "failed to allocate file buffer"; 323 goto abort; 324 } 325 326 if (nranges == 1) { 327 (void)snprintf(content_range, sizeof(content_range), 328 "bytes %lld-%lld/%lld", range->start, range->end, 329 st->st_size); 330 if (kv_add(&resp->http_headers, "Content-Range", 331 content_range) == NULL) 332 goto abort; 333 334 content_length = range->end - range->start + 1; 335 if (buffer_add_range(fd, evb, range) == 0) 336 goto abort; 337 338 } else { 339 content_length = 0; 340 boundary = arc4random(); 341 /* Generate a multipart payload of byteranges */ 342 while (nranges--) { 343 if ((i = evbuffer_add_printf(evb, "\r\n--%ud\r\n", 344 boundary)) == -1) 345 goto abort; 346 347 content_length += i; 348 if ((i = evbuffer_add_printf(evb, 349 "Content-Type: %s/%s\r\n", 350 media == NULL ? "application" : media->media_type, 351 media == NULL ? 352 "octet-stream" : media->media_subtype)) == -1) 353 goto abort; 354 355 content_length += i; 356 if ((i = evbuffer_add_printf(evb, 357 "Content-Range: bytes %lld-%lld/%lld\r\n\r\n", 358 range->start, range->end, st->st_size)) == -1) 359 goto abort; 360 361 content_length += i; 362 if (buffer_add_range(fd, evb, range) == 0) 363 goto abort; 364 365 content_length += range->end - range->start + 1; 366 range++; 367 } 368 369 if ((i = evbuffer_add_printf(evb, "\r\n--%ud--\r\n", 370 boundary)) == -1) 371 goto abort; 372 373 content_length += i; 374 375 /* prepare multipart/byteranges media type */ 376 (void)strlcpy(multipart_media.media_type, "multipart", 377 sizeof(multipart_media.media_type)); 378 (void)snprintf(multipart_media.media_subtype, 379 sizeof(multipart_media.media_subtype), 380 "byteranges; boundary=%ud", boundary); 381 media = &multipart_media; 382 } 383 384 ret = server_response_http(clt, 206, media, content_length, 385 MINIMUM(time(NULL), st->st_mtim.tv_sec)); 386 switch (ret) { 387 case -1: 388 goto fail; 389 case 0: 390 /* Connection is already finished */ 391 close(fd); 392 evbuffer_free(evb); 393 evb = NULL; 394 goto done; 395 default: 396 break; 397 } 398 399 if (server_bufferevent_write_buffer(clt, evb) == -1) 400 goto fail; 401 402 evbuffer_free(evb); 403 evb = NULL; 404 405 bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); 406 if (clt->clt_persist) 407 clt->clt_toread = TOREAD_HTTP_HEADER; 408 else 409 clt->clt_toread = TOREAD_HTTP_NONE; 410 clt->clt_done = 0; 411 412 done: 413 server_reset_http(clt); 414 return (0); 415 fail: 416 bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); 417 bufferevent_free(clt->clt_bev); 418 clt->clt_bev = NULL; 419 abort: 420 if (errstr == NULL) 421 errstr = strerror(errno); 422 server_abort_http(clt, code, errstr); 423 return (-1); 424 } 425 426 int 427 server_file_index(struct httpd *env, struct client *clt, struct stat *st) 428 { 429 char path[PATH_MAX]; 430 char tmstr[21]; 431 struct http_descriptor *desc = clt->clt_descreq; 432 struct server_config *srv_conf = clt->clt_srv_conf; 433 struct dirent **namelist, *dp; 434 int namesize, i, ret, fd = -1, namewidth, skip; 435 int code = 500; 436 struct evbuffer *evb = NULL; 437 struct media_type *media; 438 const char *stripped, *style; 439 char *escapeduri, *escapedhtml, *escapedpath; 440 struct tm tm; 441 time_t t, dir_mtime; 442 443 if ((ret = server_file_method(clt)) != 0) { 444 code = ret; 445 goto abort; 446 } 447 448 /* Request path is already canonicalized */ 449 stripped = server_root_strip(desc->http_path, srv_conf->strip); 450 if ((size_t)snprintf(path, sizeof(path), "%s%s", 451 srv_conf->root, stripped) >= sizeof(path)) 452 goto abort; 453 454 /* Now open the file, should be readable or we have another problem */ 455 if ((fd = open(path, O_RDONLY)) == -1) 456 goto abort; 457 458 /* Save last modification time */ 459 dir_mtime = MINIMUM(time(NULL), st->st_mtim.tv_sec); 460 461 if ((evb = evbuffer_new()) == NULL) 462 goto abort; 463 464 if ((namesize = scandir(path, &namelist, NULL, alphasort)) == -1) 465 goto abort; 466 467 /* Indicate failure but continue going through the list */ 468 skip = 0; 469 470 if ((escapedpath = escape_html(desc->http_path)) == NULL) 471 goto fail; 472 473 /* A CSS stylesheet allows minimal customization by the user */ 474 style = "body { background-color: white; color: black; font-family: " 475 "sans-serif; }\nhr { border: 0; border-bottom: 1px dashed; }\n"; 476 /* Generate simple HTML index document */ 477 if (evbuffer_add_printf(evb, 478 "<!DOCTYPE html>\n" 479 "<html>\n" 480 "<head>\n" 481 "<title>Index of %s</title>\n" 482 "<style type=\"text/css\"><!--\n%s\n--></style>\n" 483 "</head>\n" 484 "<body>\n" 485 "<h1>Index of %s</h1>\n" 486 "<hr>\n<pre>\n", 487 escapedpath, style, escapedpath) == -1) 488 skip = 1; 489 490 free(escapedpath); 491 492 for (i = 0; i < namesize; i++) { 493 dp = namelist[i]; 494 495 if (skip || 496 fstatat(fd, dp->d_name, st, 0) == -1) { 497 free(dp); 498 continue; 499 } 500 501 t = st->st_mtime; 502 localtime_r(&t, &tm); 503 strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm); 504 namewidth = 51 - strlen(dp->d_name); 505 506 if ((escapeduri = url_encode(dp->d_name)) == NULL) 507 goto fail; 508 if ((escapedhtml = escape_html(dp->d_name)) == NULL) 509 goto fail; 510 511 if (dp->d_name[0] == '.' && 512 !(dp->d_name[1] == '.' && dp->d_name[2] == '\0')) { 513 /* ignore hidden files starting with a dot */ 514 } else if (S_ISDIR(st->st_mode)) { 515 namewidth -= 1; /* trailing slash */ 516 if (evbuffer_add_printf(evb, 517 "<a href=\"%s%s/\">%s/</a>%*s%s%20s\n", 518 strchr(escapeduri, ':') != NULL ? "./" : "", 519 escapeduri, escapedhtml, 520 MAXIMUM(namewidth, 0), " ", tmstr, "-") == -1) 521 skip = 1; 522 } else if (S_ISREG(st->st_mode)) { 523 if (evbuffer_add_printf(evb, 524 "<a href=\"%s%s\">%s</a>%*s%s%20llu\n", 525 strchr(escapeduri, ':') != NULL ? "./" : "", 526 escapeduri, escapedhtml, 527 MAXIMUM(namewidth, 0), " ", 528 tmstr, st->st_size) == -1) 529 skip = 1; 530 } 531 free(escapeduri); 532 free(escapedhtml); 533 free(dp); 534 } 535 free(namelist); 536 537 if (skip || 538 evbuffer_add_printf(evb, 539 "</pre>\n<hr>\n</body>\n</html>\n") == -1) 540 goto abort; 541 542 close(fd); 543 fd = -1; 544 545 media = media_find(env->sc_mediatypes, "index.html"); 546 ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb), 547 dir_mtime); 548 switch (ret) { 549 case -1: 550 goto fail; 551 case 0: 552 /* Connection is already finished */ 553 evbuffer_free(evb); 554 goto done; 555 default: 556 break; 557 } 558 559 if (server_bufferevent_write_buffer(clt, evb) == -1) 560 goto fail; 561 evbuffer_free(evb); 562 evb = NULL; 563 564 bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); 565 if (clt->clt_persist) 566 clt->clt_toread = TOREAD_HTTP_HEADER; 567 else 568 clt->clt_toread = TOREAD_HTTP_NONE; 569 clt->clt_done = 0; 570 571 done: 572 server_reset_http(clt); 573 return (0); 574 fail: 575 bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); 576 bufferevent_free(clt->clt_bev); 577 clt->clt_bev = NULL; 578 abort: 579 if (fd != -1) 580 close(fd); 581 if (evb != NULL) 582 evbuffer_free(evb); 583 server_abort_http(clt, code, desc->http_path); 584 return (-1); 585 } 586 587 void 588 server_file_error(struct bufferevent *bev, short error, void *arg) 589 { 590 struct client *clt = arg; 591 struct evbuffer *dst; 592 593 if (error & EVBUFFER_TIMEOUT) { 594 server_close(clt, "buffer event timeout"); 595 return; 596 } 597 if (error & EVBUFFER_ERROR) { 598 if (errno == EFBIG) { 599 bufferevent_enable(bev, EV_READ); 600 return; 601 } 602 server_close(clt, "buffer event error"); 603 return; 604 } 605 if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) { 606 bufferevent_disable(bev, EV_READ|EV_WRITE); 607 608 clt->clt_done = 1; 609 610 if (clt->clt_persist) { 611 /* Close input file and wait for next HTTP request */ 612 if (clt->clt_fd != -1) 613 close(clt->clt_fd); 614 clt->clt_fd = -1; 615 clt->clt_toread = TOREAD_HTTP_HEADER; 616 server_reset_http(clt); 617 bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); 618 return; 619 } 620 621 dst = EVBUFFER_OUTPUT(clt->clt_bev); 622 if (EVBUFFER_LENGTH(dst)) { 623 /* Finish writing all data first */ 624 bufferevent_enable(clt->clt_bev, EV_WRITE); 625 return; 626 } 627 628 server_close(clt, "done"); 629 return; 630 } 631 server_close(clt, "unknown event error"); 632 return; 633 } 634 635 int 636 server_file_modified_since(struct http_descriptor * desc, struct stat * st) 637 { 638 struct kv key, *since; 639 struct tm tm; 640 641 memset(&tm, 0, sizeof(struct tm)); 642 643 key.kv_key = "If-Modified-Since"; 644 if ((since = kv_find(&desc->http_headers, &key)) != NULL && 645 since->kv_value != NULL) { 646 if (strptime(since->kv_value, "%a, %d %h %Y %T %Z", &tm) != 647 NULL && timegm(&tm) >= st->st_mtim.tv_sec) { 648 return (304); 649 } 650 } 651 652 return (-1); 653 } 654 655 struct range * 656 parse_range(char *str, size_t file_sz, int *nranges) 657 { 658 static struct range ranges[MAX_RANGES]; 659 int i = 0; 660 char *p, *q; 661 662 /* Extract range unit */ 663 if ((p = strchr(str, '=')) == NULL) 664 return (NULL); 665 666 *p++ = '\0'; 667 /* Check if it's a bytes range spec */ 668 if (strcmp(str, "bytes") != 0) 669 return (NULL); 670 671 while ((q = strchr(p, ',')) != NULL) { 672 *q++ = '\0'; 673 674 /* Extract start and end positions */ 675 if (parse_range_spec(p, file_sz, &ranges[i]) == 0) 676 continue; 677 678 i++; 679 if (i == MAX_RANGES) 680 return (NULL); 681 682 p = q; 683 } 684 685 if (parse_range_spec(p, file_sz, &ranges[i]) != 0) 686 i++; 687 688 *nranges = i; 689 return (i ? ranges : NULL); 690 } 691 692 int 693 parse_range_spec(char *str, size_t size, struct range *r) 694 { 695 size_t start_str_len, end_str_len; 696 char *p, *start_str, *end_str; 697 const char *errstr; 698 699 if ((p = strchr(str, '-')) == NULL) 700 return (0); 701 702 *p++ = '\0'; 703 start_str = str; 704 end_str = p; 705 start_str_len = strlen(start_str); 706 end_str_len = strlen(end_str); 707 708 /* Either 'start' or 'end' is optional but not both */ 709 if ((start_str_len == 0) && (end_str_len == 0)) 710 return (0); 711 712 if (end_str_len) { 713 r->end = strtonum(end_str, 0, LLONG_MAX, &errstr); 714 if (errstr) 715 return (0); 716 717 if ((size_t)r->end >= size) 718 r->end = size - 1; 719 } else 720 r->end = size - 1; 721 722 if (start_str_len) { 723 r->start = strtonum(start_str, 0, LLONG_MAX, &errstr); 724 if (errstr) 725 return (0); 726 727 if ((size_t)r->start >= size) 728 return (0); 729 } else { 730 r->start = size - r->end; 731 r->end = size - 1; 732 } 733 734 if (r->end < r->start) 735 return (0); 736 737 return (1); 738 } 739 740 int 741 buffer_add_range(int fd, struct evbuffer *evb, struct range *range) 742 { 743 char buf[BUFSIZ]; 744 size_t n, range_sz; 745 ssize_t nread; 746 747 if (lseek(fd, range->start, SEEK_SET) == -1) 748 return (0); 749 750 range_sz = range->end - range->start + 1; 751 while (range_sz) { 752 n = MINIMUM(range_sz, sizeof(buf)); 753 if ((nread = read(fd, buf, n)) == -1) 754 return (0); 755 756 evbuffer_add(evb, buf, nread); 757 range_sz -= nread; 758 } 759 760 return (1); 761 } 762