Changeset dae0aec in mod_gnutls for src/gnutls_io.c
- Timestamp:
- Sep 27, 2004, 8:20:51 PM (19 years ago)
- Branches:
- asyncio, debian/master, debian/stretch-backports, jessie-backports, main, master, msva, proxy-ticket, upstream
- Children:
- b1f7f11
- Parents:
- 2e12226
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
src/gnutls_io.c
r2e12226 rdae0aec 20 20 /** 21 21 * Describe how the GnuTLS Filter system works here 22 * - It is basicly the same as what mod_ssl uses in that respect. 22 * - Basicly the same as what mod_ssl does with OpenSSL. 23 * 23 24 */ 24 25 25 apr_status_t mod_gnutls_filter_input(ap_filter_t * f, 26 apr_bucket_brigade * bb, 27 ap_input_mode_t mode, 28 apr_read_type_e block, 29 apr_off_t readbytes) 30 { 31 apr_bucket *b; 32 apr_status_t status = APR_SUCCESS; 26 #define HTTP_ON_HTTPS_PORT \ 27 "GET /" CRLF 28 29 #define HTTP_ON_HTTPS_PORT_BUCKET(alloc) \ 30 apr_bucket_immortal_create(HTTP_ON_HTTPS_PORT, \ 31 sizeof(HTTP_ON_HTTPS_PORT) - 1, \ 32 alloc) 33 34 static apr_status_t gnutls_io_filter_error(ap_filter_t * f, 35 apr_bucket_brigade * bb, 36 apr_status_t status) 37 { 33 38 mod_gnutls_handle_t *ctxt = (mod_gnutls_handle_t *) f->ctx; 34 35 if (f->c->aborted) { 36 apr_bucket *bucket = apr_bucket_eos_create(f->c->bucket_alloc); 37 APR_BRIGADE_INSERT_TAIL(bb, bucket); 38 return APR_ECONNABORTED; 39 } 40 41 #if 0 42 for (b = APR_BRIGADE_FIRST(bb); 43 b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) { 44 if (APR_BUCKET_IS_EOS(b)) { 45 /* end of connection */ 46 } 47 else if (apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ) 48 == APR_SUCCESS) { 49 /* more data */ 50 } 51 } 52 #endif 53 return status; 54 } 55 56 #define GNUTLS_HANDSHAKE_ATTEMPTS 10 57 58 apr_status_t mod_gnutls_filter_output(ap_filter_t * f, 59 apr_bucket_brigade * bb) 60 { 61 int ret, i; 62 const char *buf = 0; 63 apr_size_t bytes = 0; 64 mod_gnutls_handle_t *ctxt = (mod_gnutls_handle_t *) f->ctx; 65 apr_status_t status = APR_SUCCESS; 66 apr_read_type_e rblock = APR_NONBLOCK_READ; 67 68 if (f->c->aborted) { 69 apr_brigade_cleanup(bb); 70 return APR_ECONNABORTED; 71 } 72 73 if (ctxt->status == 0) { 74 for (i = GNUTLS_HANDSHAKE_ATTEMPTS; i > 0; i--) { 75 ret = gnutls_handshake(ctxt->session); 76 77 if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { 78 continue; 79 } 80 81 if (ret < 0) { 82 if (ret == GNUTLS_E_WARNING_ALERT_RECEIVED 83 || ret == GNUTLS_E_FATAL_ALERT_RECEIVED) { 84 ret = gnutls_alert_get(ctxt->session); 85 ap_log_error(APLOG_MARK, APLOG_ERR, 0, f->c->base_server, 86 "GnuTLS: Hanshake Alert (%d) '%s'.\n", ret, 87 gnutls_alert_get_name(ret)); 88 } 89 90 if (gnutls_error_is_fatal(ret) != 0) { 91 gnutls_deinit(ctxt->session); 92 ap_log_error(APLOG_MARK, APLOG_ERR, 0, f->c->base_server, 93 "GnuTLS: Handshake Failed (%d) '%s'", ret, 94 gnutls_strerror(ret)); 95 ctxt->status = -1; 96 break; 97 } 98 } 99 else { 100 ctxt->status = 1; 101 break; /* all done with the handshake */ 102 } 103 } 104 } 105 106 if (ctxt->status < 0) { 107 return ap_pass_brigade(f->next, bb); 108 } 109 110 while (!APR_BRIGADE_EMPTY(bb)) { 111 apr_bucket *bucket = APR_BRIGADE_FIRST(bb); 112 if (APR_BUCKET_IS_EOS(bucket) || APR_BUCKET_IS_FLUSH(bucket)) { 113 /** TODO: GnuTLS doesn't have a special flush method? **/ 114 if ((status = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) { 115 return status; 116 } 117 break; 118 } 119 else if (AP_BUCKET_IS_EOC(bucket)) { 120 gnutls_bye(ctxt->session, GNUTLS_SHUT_WR); 121 122 if ((status = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) { 123 return status; 124 } 125 break; 126 } 127 else { 128 /* filter output */ 129 const char *data; 130 apr_size_t len; 131 132 status = apr_bucket_read(bucket, &data, &len, rblock); 133 134 if (APR_STATUS_IS_EAGAIN(status)) { 135 rblock = APR_BLOCK_READ; 136 continue; /* and try again with a blocking read. */ 137 } 138 139 rblock = APR_NONBLOCK_READ; 140 141 if (!APR_STATUS_IS_EOF(status) && (status != APR_SUCCESS)) { 142 break; 143 } 144 145 ret = gnutls_record_send(ctxt->session, data, len); 146 if (ret < 0) { 147 /* error sending output */ 148 } 149 else if ((apr_size_t) ret != len) { 150 /* not all of the data was sent. */ 151 /* mod_ssl basicly errors out here.. this doesn't seem right? */ 152 } 153 else { 154 /* send complete */ 155 156 } 157 158 apr_bucket_delete(bucket); 159 160 if (status != APR_SUCCESS) { 161 break; 162 } 163 164 } 165 } 166 167 return status; 39 apr_bucket *bucket; 40 41 switch (status) { 42 case HTTP_BAD_REQUEST: 43 /* log the situation */ 44 ap_log_error(APLOG_MARK, APLOG_INFO, 0, 45 f->c->base_server, 46 "GnuTLS handshake failed: HTTP spoken on HTTPS port; " 47 "trying to send HTML error page"); 48 49 ctxt->status = -1; 50 51 /* fake the request line */ 52 bucket = HTTP_ON_HTTPS_PORT_BUCKET(f->c->bucket_alloc); 53 break; 54 55 default: 56 return status; 57 } 58 59 APR_BRIGADE_INSERT_TAIL(bb, bucket); 60 bucket = apr_bucket_eos_create(f->c->bucket_alloc); 61 APR_BRIGADE_INSERT_TAIL(bb, bucket); 62 63 return APR_SUCCESS; 64 } 65 66 static int char_buffer_read(mod_gnutls_char_buffer_t * buffer, char *in, 67 int inl) 68 { 69 if (!buffer->length) { 70 return 0; 71 } 72 73 if (buffer->length > inl) { 74 /* we have have enough to fill the caller's buffer */ 75 memcpy(in, buffer->value, inl); 76 buffer->value += inl; 77 buffer->length -= inl; 78 } 79 else { 80 /* swallow remainder of the buffer */ 81 memcpy(in, buffer->value, buffer->length); 82 inl = buffer->length; 83 buffer->value = NULL; 84 buffer->length = 0; 85 } 86 87 return inl; 88 } 89 90 static int char_buffer_write(mod_gnutls_char_buffer_t * buffer, char *in, 91 int inl) 92 { 93 buffer->value = in; 94 buffer->length = inl; 95 return inl; 168 96 } 169 97 … … 254 182 255 183 184 static apr_status_t gnutls_io_input_read(mod_gnutls_handle_t * ctxt, 185 char *buf, apr_size_t * len) 186 { 187 apr_size_t wanted = *len; 188 apr_size_t bytes = 0; 189 int rc; 190 191 *len = 0; 192 193 /* If we have something leftover from last time, try that first. */ 194 if ((bytes = char_buffer_read(&ctxt->input_cbuf, buf, wanted))) { 195 *len = bytes; 196 if (ctxt->input_mode == AP_MODE_SPECULATIVE) { 197 /* We want to rollback this read. */ 198 if (ctxt->input_cbuf.length > 0) { 199 ctxt->input_cbuf.value -= bytes; 200 ctxt->input_cbuf.length += bytes; 201 } 202 else { 203 char_buffer_write(&ctxt->input_cbuf, buf, (int) bytes); 204 } 205 return APR_SUCCESS; 206 } 207 /* This could probably be *len == wanted, but be safe from stray 208 * photons. 209 */ 210 if (*len >= wanted) { 211 return APR_SUCCESS; 212 } 213 if (ctxt->input_mode == AP_MODE_GETLINE) { 214 if (memchr(buf, APR_ASCII_LF, *len)) { 215 return APR_SUCCESS; 216 } 217 } 218 else { 219 /* Down to a nonblock pattern as we have some data already 220 */ 221 ctxt->input_block = APR_NONBLOCK_READ; 222 } 223 } 224 225 while (1) { 226 227 if (ctxt->status < 0) { 228 /* Ensure a non-zero error code is returned */ 229 if (ctxt->input_rc == APR_SUCCESS) { 230 ctxt->input_rc = APR_EGENERAL; 231 } 232 break; 233 } 234 235 rc = gnutls_record_recv(ctxt->session, buf + bytes, wanted - bytes); 236 237 if (rc > 0) { 238 *len += rc; 239 if (ctxt->input_mode == AP_MODE_SPECULATIVE) { 240 /* We want to rollback this read. */ 241 char_buffer_write(&ctxt->input_cbuf, buf, rc); 242 } 243 return ctxt->input_rc; 244 } 245 else if (rc == 0) { 246 /* If EAGAIN, we will loop given a blocking read, 247 * otherwise consider ourselves at EOF. 248 */ 249 if (APR_STATUS_IS_EAGAIN(ctxt->input_rc) 250 || APR_STATUS_IS_EINTR(ctxt->input_rc)) { 251 /* Already read something, return APR_SUCCESS instead. 252 * On win32 in particular, but perhaps on other kernels, 253 * a blocking call isn't 'always' blocking. 254 */ 255 if (*len > 0) { 256 ctxt->input_rc = APR_SUCCESS; 257 break; 258 } 259 if (ctxt->input_block == APR_NONBLOCK_READ) { 260 break; 261 } 262 } 263 else { 264 if (*len > 0) { 265 ctxt->input_rc = APR_SUCCESS; 266 } 267 else { 268 ctxt->input_rc = APR_EOF; 269 } 270 break; 271 } 272 } 273 else { /* (rc < 0) */ 274 275 if (rc == GNUTLS_E_REHANDSHAKE) { 276 /* A client has asked for a new Hankshake. Currently, we don't do it */ 277 ap_log_error(APLOG_MARK, APLOG_INFO, ctxt->input_rc, 278 ctxt->c->base_server, 279 "GnuTLS: Error reading data. Client Requested a New Handshake." 280 " (%d) '%s'", rc, gnutls_strerror(rc)); 281 } 282 else { 283 /* Some Other Error. Report it. Die. */ 284 ap_log_error(APLOG_MARK, APLOG_INFO, ctxt->input_rc, 285 ctxt->c->base_server, 286 "GnuTLS: Error reading data. (%d) '%s'", rc, 287 gnutls_strerror(rc)); 288 } 289 290 if (ctxt->input_rc == APR_SUCCESS) { 291 ctxt->input_rc = APR_EGENERAL; 292 } 293 break; 294 } 295 } 296 return ctxt->input_rc; 297 } 298 299 static apr_status_t gnutls_io_input_getline(mod_gnutls_handle_t * ctxt, 300 char *buf, apr_size_t * len) 301 { 302 const char *pos = NULL; 303 apr_status_t status; 304 apr_size_t tmplen = *len, buflen = *len, offset = 0; 305 306 *len = 0; 307 308 while (tmplen > 0) { 309 status = gnutls_io_input_read(ctxt, buf + offset, &tmplen); 310 311 if (status != APR_SUCCESS) { 312 return status; 313 } 314 315 *len += tmplen; 316 317 if ((pos = memchr(buf, APR_ASCII_LF, *len))) { 318 break; 319 } 320 321 offset += tmplen; 322 tmplen = buflen - offset; 323 } 324 325 if (pos) { 326 char *value; 327 int length; 328 apr_size_t bytes = pos - buf; 329 330 bytes += 1; 331 value = buf + bytes; 332 length = *len - bytes; 333 334 char_buffer_write(&ctxt->input_cbuf, value, length); 335 336 *len = bytes; 337 } 338 339 return APR_SUCCESS; 340 } 341 342 343 #define GNUTLS_HANDSHAKE_ATTEMPTS 10 344 345 static void gnutls_do_handshake(mod_gnutls_handle_t * ctxt) 346 { 347 int i, ret; 348 349 if (ctxt->status != 0) 350 return; 351 352 for (i = GNUTLS_HANDSHAKE_ATTEMPTS; i > 0; i--) { 353 ret = gnutls_handshake(ctxt->session); 354 if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { 355 continue; 356 } 357 358 if (ret < 0) { 359 if (ret == GNUTLS_E_WARNING_ALERT_RECEIVED 360 || ret == GNUTLS_E_FATAL_ALERT_RECEIVED) { 361 ret = gnutls_alert_get(ctxt->session); 362 ap_log_error(APLOG_MARK, APLOG_ERR, 0, ctxt->c->base_server, 363 "GnuTLS: Hanshake Alert (%d) '%s'.\n", ret, 364 gnutls_alert_get_name(ret)); 365 } 366 367 if (gnutls_error_is_fatal(ret) != 0) { 368 gnutls_deinit(ctxt->session); 369 ap_log_error(APLOG_MARK, APLOG_ERR, 0, ctxt->c->base_server, 370 "GnuTLS: Handshake Failed (%d) '%s'", ret, 371 gnutls_strerror(ret)); 372 ctxt->status = -1; 373 return; 374 } 375 } 376 else { 377 ctxt->status = 1; 378 return; /* all done with the handshake */ 379 } 380 } 381 ctxt->status = -1; 382 return; 383 } 384 385 386 apr_status_t mod_gnutls_filter_input(ap_filter_t * f, 387 apr_bucket_brigade * bb, 388 ap_input_mode_t mode, 389 apr_read_type_e block, 390 apr_off_t readbytes) 391 { 392 apr_status_t status = APR_SUCCESS; 393 mod_gnutls_handle_t *ctxt = (mod_gnutls_handle_t *) f->ctx; 394 apr_size_t len = sizeof(ctxt->input_buffer); 395 396 if (f->c->aborted) { 397 apr_bucket *bucket = apr_bucket_eos_create(f->c->bucket_alloc); 398 APR_BRIGADE_INSERT_TAIL(bb, bucket); 399 return APR_ECONNABORTED; 400 } 401 402 if (ctxt->status == 0) { 403 gnutls_do_handshake(ctxt); 404 } 405 406 if (ctxt->status < 0) { 407 return ap_get_brigade(f->next, bb, mode, block, readbytes); 408 } 409 410 /* XXX: we don't currently support anything other than these modes. */ 411 if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE && 412 mode != AP_MODE_SPECULATIVE && mode != AP_MODE_INIT) { 413 return APR_ENOTIMPL; 414 } 415 416 ctxt->input_mode = mode; 417 ctxt->input_block = block; 418 419 if (ctxt->input_mode == AP_MODE_READBYTES || 420 ctxt->input_mode == AP_MODE_SPECULATIVE) { 421 /* Err. This is bad. readbytes *can* be a 64bit int! len.. is NOT */ 422 if (readbytes < len) { 423 len = (apr_size_t) readbytes; 424 } 425 status = gnutls_io_input_read(ctxt, ctxt->input_buffer, &len); 426 } 427 else if (ctxt->input_mode == AP_MODE_GETLINE) { 428 status = gnutls_io_input_getline(ctxt, ctxt->input_buffer, &len); 429 } 430 else { 431 /* We have no idea what you are talking about, so return an error. */ 432 return APR_ENOTIMPL; 433 } 434 435 if (status != APR_SUCCESS) { 436 return gnutls_io_filter_error(f, bb, status); 437 } 438 439 /* Create a transient bucket out of the decrypted data. */ 440 if (len > 0) { 441 apr_bucket *bucket = 442 apr_bucket_transient_create(ctxt->input_buffer, len, 443 f->c->bucket_alloc); 444 APR_BRIGADE_INSERT_TAIL(bb, bucket); 445 } 446 447 return status; 448 } 449 450 apr_status_t mod_gnutls_filter_output(ap_filter_t * f, 451 apr_bucket_brigade * bb) 452 { 453 int ret; 454 mod_gnutls_handle_t *ctxt = (mod_gnutls_handle_t *) f->ctx; 455 apr_status_t status = APR_SUCCESS; 456 apr_read_type_e rblock = APR_NONBLOCK_READ; 457 458 if (f->c->aborted) { 459 apr_brigade_cleanup(bb); 460 return APR_ECONNABORTED; 461 } 462 463 if (ctxt->status == 0) { 464 gnutls_do_handshake(ctxt); 465 } 466 467 if (ctxt->status < 0) { 468 return ap_pass_brigade(f->next, bb); 469 } 470 471 while (!APR_BRIGADE_EMPTY(bb)) { 472 apr_bucket *bucket = APR_BRIGADE_FIRST(bb); 473 if (APR_BUCKET_IS_EOS(bucket) || APR_BUCKET_IS_FLUSH(bucket)) { 474 /** TODO: GnuTLS doesn't have a special flush method? **/ 475 if ((status = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) { 476 return status; 477 } 478 break; 479 } 480 else if (AP_BUCKET_IS_EOC(bucket)) { 481 gnutls_bye(ctxt->session, GNUTLS_SHUT_WR); 482 483 if ((status = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) { 484 return status; 485 } 486 break; 487 } 488 else { 489 /* filter output */ 490 const char *data; 491 apr_size_t len; 492 493 status = apr_bucket_read(bucket, &data, &len, rblock); 494 495 if (APR_STATUS_IS_EAGAIN(status)) { 496 rblock = APR_BLOCK_READ; 497 continue; /* and try again with a blocking read. */ 498 } 499 500 rblock = APR_NONBLOCK_READ; 501 502 if (!APR_STATUS_IS_EOF(status) && (status != APR_SUCCESS)) { 503 break; 504 } 505 506 ret = gnutls_record_send(ctxt->session, data, len); 507 508 if (ret < 0) { 509 /* error sending output */ 510 ap_log_error(APLOG_MARK, APLOG_INFO, ctxt->output_rc, 511 ctxt->c->base_server, 512 "GnuTLS: Error writing data." 513 " (%d) '%s'", ret, gnutls_strerror(ret)); 514 if (ctxt->output_rc == APR_SUCCESS) { 515 ctxt->output_rc = APR_EGENERAL; 516 } 517 } 518 else if ((apr_size_t) ret != len) { 519 /* not all of the data was sent. */ 520 /* mod_ssl basicly errors out here.. this doesn't seem right? */ 521 ap_log_error(APLOG_MARK, APLOG_INFO, ctxt->output_rc, 522 ctxt->c->base_server, 523 "GnuTLS: failed to write %" APR_SSIZE_T_FMT 524 " of %" APR_SIZE_T_FMT " bytes.", 525 len - (apr_size_t) ret, len); 526 if (ctxt->output_rc == APR_SUCCESS) { 527 ctxt->output_rc = APR_EGENERAL; 528 } 529 } 530 531 apr_bucket_delete(bucket); 532 533 if (ctxt->output_rc != APR_SUCCESS) { 534 break; 535 } 536 } 537 } 538 539 return status; 540 } 541 256 542 ssize_t mod_gnutls_transport_read(gnutls_transport_ptr_t ptr, 257 543 void *buffer, size_t len) … … 260 546 apr_status_t rc; 261 547 apr_size_t in = len; 548 apr_read_type_e block = ctxt->input_block; 549 550 ctxt->input_rc = APR_SUCCESS; 551 262 552 /* If Len = 0, we don't do anything. */ 263 553 if (!len) 264 554 return 0; 555 556 if (!ctxt->input_bb) { 557 ctxt->input_rc = APR_EOF; 558 return -1; 559 } 265 560 266 561 if (APR_BRIGADE_EMPTY(ctxt->input_bb)) { … … 284 579 } 285 580 286 // brigade_consume(ctxt->input_bb, ctxt->input_block, buffer, &len); 287 288 289 ap_get_brigade(ctxt->input_filter->next, ctxt->input_bb, 290 AP_MODE_READBYTES, ctxt->input_block, len); 291 292 return len; 581 ctxt->input_rc = brigade_consume(ctxt->input_bb, block, buffer, &len); 582 583 if (ctxt->input_rc == APR_SUCCESS) { 584 return (ssize_t) len; 585 } 586 587 if (APR_STATUS_IS_EAGAIN(ctxt->input_rc) 588 || APR_STATUS_IS_EINTR(ctxt->input_rc)) { 589 return (ssize_t) len; 590 } 591 592 /* Unexpected errors and APR_EOF clean out the brigade. 593 * Subsequent calls will return APR_EOF. 594 */ 595 apr_brigade_cleanup(ctxt->input_bb); 596 ctxt->input_bb = NULL; 597 598 if (APR_STATUS_IS_EOF(ctxt->input_rc) && len) { 599 /* Provide the results of this read pass, 600 * without resetting the BIO retry_read flag 601 */ 602 return (ssize_t) len; 603 } 604 605 return -1; 606 } 607 608 609 static ssize_t write_flush(mod_gnutls_handle_t * ctxt) 610 { 611 apr_bucket *e; 612 613 if (!(ctxt->output_blen || ctxt->output_length)) { 614 ctxt->output_rc = APR_SUCCESS; 615 return 1; 616 } 617 618 if (ctxt->output_blen) { 619 e = apr_bucket_transient_create(ctxt->output_buffer, 620 ctxt->output_blen, 621 ctxt->output_bb->bucket_alloc); 622 /* we filled this buffer first so add it to the 623 * head of the brigade 624 */ 625 APR_BRIGADE_INSERT_HEAD(ctxt->output_bb, e); 626 ctxt->output_blen = 0; 627 } 628 629 ctxt->output_length = 0; 630 e = apr_bucket_flush_create(ctxt->output_bb->bucket_alloc); 631 APR_BRIGADE_INSERT_TAIL(ctxt->output_bb, e); 632 633 ctxt->output_rc = ap_pass_brigade(ctxt->output_filter->next, 634 ctxt->output_bb); 635 /* create new brigade ready for next time through */ 636 ctxt->output_bb = 637 apr_brigade_create(ctxt->c->pool, ctxt->c->bucket_alloc); 638 return (ctxt->output_rc == APR_SUCCESS) ? 1 : -1; 293 639 } 294 640 … … 298 644 mod_gnutls_handle_t *ctxt = ptr; 299 645 300 // apr_bucket *bucket = apr_bucket_transient_create(in, inl, 301 // outctx->bb-> 302 // bucket_alloc); 303 304 // outctx->length += inl; 305 //APR_BRIGADE_INSERT_TAIL(outctx->bb, bucket); 306 return 0; 307 } 646 if (!ctxt->output_length 647 && (len + ctxt->output_blen < sizeof(ctxt->output_buffer))) { 648 /* the first two SSL_writes (of 1024 and 261 bytes) 649 * need to be in the same packet (vec[0].iov_base) 650 */ 651 /* XXX: could use apr_brigade_write() to make code look cleaner 652 * but this way we avoid the malloc(APR_BUCKET_BUFF_SIZE) 653 * and free() of it later 654 */ 655 memcpy(&ctxt->output_buffer[ctxt->output_blen], buffer, len); 656 ctxt->output_blen += len; 657 } 658 else { 659 /* pass along the encrypted data 660 * need to flush since we're using SSL's malloc-ed buffer 661 * which will be overwritten once we leave here 662 */ 663 apr_bucket *bucket = apr_bucket_transient_create(buffer, len, 664 ctxt->output_bb-> 665 bucket_alloc); 666 667 ctxt->output_length += len; 668 APR_BRIGADE_INSERT_TAIL(ctxt->output_bb, bucket); 669 670 if (write_flush(ctxt) < 0) { 671 return -1; 672 } 673 } 674 675 return len; 676 }
Note: See TracChangeset
for help on using the changeset viewer.