diff --git a/configure.in b/configure.in index ad89e44..b3e1f46 100644 --- a/configure.in +++ b/configure.in @@ -1116,6 +1116,7 @@ AC_CHECK_HEADERS( \ sys/socket.h \ process.h \ sys/prctl.h \ + sys/uio.h \ atomic.h \ malloc.h \ malloc_np.h \ @@ -1900,6 +1901,7 @@ AC_CHECK_FUNCS(malloc_usable_size) AC_CHECK_FUNCS(malloc_size) AC_CHECK_FUNCS(mblen) AC_CHECK_FUNCS(memalign) +AC_CHECK_FUNCS(writev) AC_CHECK_FUNCS(memrchr) AC_CHECK_FUNCS(mktime) AC_CHECK_FUNCS(pipe2) diff --git a/include/ruby/intern.h b/include/ruby/intern.h index 6200e77..9eee0c3 100644 --- a/include/ruby/intern.h +++ b/include/ruby/intern.h @@ -519,6 +519,7 @@ RUBY_EXTERN VALUE rb_rs; RUBY_EXTERN VALUE rb_default_rs; RUBY_EXTERN VALUE rb_output_rs; VALUE rb_io_write(VALUE, VALUE); +VALUE rb_io_writev(VALUE, VALUE); VALUE rb_io_gets(VALUE); VALUE rb_io_getbyte(VALUE); VALUE rb_io_ungetc(VALUE, VALUE); diff --git a/io.c b/io.c index 5aa720b..1c0f1ee 100644 --- a/io.c +++ b/io.c @@ -86,6 +86,10 @@ #include #endif +#ifdef HAVE_SYS_UIO_H +#include +#endif + #if defined(__BEOS__) || defined(__HAIKU__) # ifndef NOFILE # define NOFILE (OPEN_MAX) @@ -157,7 +161,7 @@ VALUE rb_default_rs; static VALUE argf; -static ID id_write, id_read, id_getc, id_flush, id_readpartial, id_set_encoding; +static ID id_write, id_writev, id_read, id_getc, id_flush, id_readpartial, id_set_encoding; static VALUE sym_mode, sym_perm, sym_extenc, sym_intenc, sym_encoding, sym_open_args; static VALUE sym_textmode, sym_binmode, sym_autoclose, sym_exception; static VALUE sym_SET, sym_CUR, sym_END; @@ -918,6 +922,14 @@ struct io_internal_write_struct { size_t capa; }; +#ifdef HAVE_WRITEV +struct io_internal_writev_struct { + int fd; + const struct iovec *iov; + int iovcnt; +}; +#endif + static VALUE internal_read_func(void *ptr) { @@ -939,6 +951,15 @@ internal_write_func2(void *ptr) return (void*)(intptr_t)write(iis->fd, iis->buf, iis->capa); } +#ifdef HAVE_WRITEV +static VALUE +internal_writev_func(void *ptr) +{ + struct io_internal_writev_struct *iis = ptr; + return writev(iis->fd, iis->iov, iis->iovcnt); +} +#endif + static ssize_t rb_read_internal(int fd, void *buf, size_t count) { @@ -973,6 +994,19 @@ rb_write_internal2(int fd, const void *buf, size_t count) RUBY_UBF_IO, NULL); } +#ifdef HAVE_WRITEV +static ssize_t +rb_writev_internal(int fd, const struct iovec *iov, int iovcnt) +{ + struct io_internal_writev_struct iis; + iis.fd = fd; + iis.iov = iov; + iis.iovcnt = iovcnt; + + return (ssize_t)rb_thread_io_blocking_region(internal_writev_func, &iis, fd); +} +#endif + static long io_writable_length(rb_io_t *fptr, long l) { @@ -1450,6 +1484,190 @@ rb_io_addstr(VALUE io, VALUE str) return io; } +#ifdef HAVE_WRITEV +struct binwritev_arg { + rb_io_t *fptr; + const struct iovec *iov; + int iovcnt; +}; + +static VALUE +call_writev_internal(VALUE arg) +{ + struct binwritev_arg *p = (struct binwritev_arg *)arg; + return rb_writev_internal(p->fptr->fd, p->iov, p->iovcnt); +} + +static long +io_binwritev(struct iovec *iov, int iovcnt, rb_io_t *fptr) +{ + int i; + long r, total = 0, written_len = 0; + + /* don't write anything if current thread has a pending interrupt. */ + rb_thread_check_ints(); + + if (iovcnt == 0) return 0; + for (i = 1; i < iovcnt; i++) total += iov[i].iov_len; + + if (fptr->wbuf.ptr == NULL && !(fptr->mode & FMODE_SYNC)) { + fptr->wbuf.off = 0; + fptr->wbuf.len = 0; + fptr->wbuf.capa = IO_WBUF_CAPA_MIN; + fptr->wbuf.ptr = ALLOC_N(char, fptr->wbuf.capa); + fptr->write_lock = rb_mutex_new(); + rb_mutex_allow_trap(fptr->write_lock, 1); + } + + if (fptr->wbuf.ptr && fptr->wbuf.len) { + if (fptr->wbuf.off + fptr->wbuf.len + total <= fptr->wbuf.capa) { + long offset = fptr->wbuf.off; + for (i = 1; i < iovcnt; i++) { + memcpy(fptr->wbuf.ptr+offset, iov[i].iov_base, iov[i].iov_len); + offset += iov[i].iov_len; + } + fptr->wbuf.len += total; + return total; + } + else { + iov[0].iov_base = fptr->wbuf.ptr + fptr->wbuf.off; + iov[0].iov_len = fptr->wbuf.len; + } + } + else { + iov++; + iovcnt--; + } + + retry: + if (fptr->write_lock) { + struct binwritev_arg arg; + arg.fptr = fptr; + arg.iov = iov; + arg.iovcnt = iovcnt; + r = rb_mutex_synchronize(fptr->write_lock, call_writev_internal, (VALUE)&arg); + } + else { + r = rb_writev_internal(fptr->fd, iov, iovcnt); + } + + if (r >= 0) { + written_len += r; + if (fptr->wbuf.ptr && fptr->wbuf.len) { + if (written_len < fptr->wbuf.len) { + fptr->wbuf.off += r; + fptr->wbuf.len -= r; + } + else { + fptr->wbuf.off = 0; + fptr->wbuf.len = 0; + } + } + if (written_len == total) return written_len; + + for (i = 0; i < iovcnt; i++) { + if (r > (ssize_t)iov[i].iov_len) { + r -= iov[i].iov_len; + iov[i].iov_len = 0; + } + else { + iov[i].iov_base = (char *)iov[i].iov_base + r; + iov[i].iov_len -= r; + break; + } + } + + errno = EAGAIN; + } + if (rb_io_wait_writable(fptr->fd)) { + rb_io_check_closed(fptr); + goto retry; + } + + return -1L; +} + +static long +io_fwritev(VALUE ary, rb_io_t *fptr) +{ + int i, iovcnt = RARRAY_LEN(ary) + 1; + long n; + VALUE v, str; + struct iovec *iov = ALLOCV_N(struct iovec, v, iovcnt); + + for (i = 0; i < RARRAY_LEN(ary); i++) { + str = rb_obj_as_string(RARRAY_AREF(ary, i)); + str = do_writeconv(str, fptr); + str = rb_str_new_frozen(str); + iov[i+1].iov_base = RSTRING_PTR(str); + iov[i+1].iov_len = RSTRING_LEN(str); + } + + n = io_binwritev(iov, iovcnt, fptr); + if (v) ALLOCV_END(v); + + return n; +} + +static VALUE +io_writev(VALUE io, VALUE ary) +{ + rb_io_t *fptr; + long n; + VALUE tmp; + + io = GetWriteIO(io); + ary = rb_check_array_type(ary); + tmp = rb_io_check_io(io); + if (NIL_P(tmp)) { + /* port is not IO, call writev method for it. */ + return rb_funcallv(io, id_writev, 1, &ary); + } + io = tmp; + + GetOpenFile(io, fptr); + rb_io_check_writable(fptr); + + n = io_fwritev(ary, fptr); + if (n == -1L) rb_sys_fail_path(fptr->pathv); + + return LONG2FIX(n); +} +#else +static VALUE +io_writev(VALUE io, VALUE ary) +{ + rb_io_t *fptr; + long n; + VALUE str, tmp; + + io = GetWriteIO(io); + ary = rb_check_array_type(ary); + tmp = rb_io_check_io(io); + if (NIL_P(tmp)) { + /* port is not IO, call writev method for it. */ + return rb_funcallv(io, id_writev, 1, &ary); + } + io = tmp; + + str = rb_ary_join(ary, rb_str_new(0, 0)); + + GetOpenFile(io, fptr); + rb_io_check_writable(fptr); + + n = io_fwrite(str, fptr, 0); + if (n == -1L) rb_sys_fail_path(fptr->pathv); + + return LONG2FIX(n); +} +#endif /* HAVE_WRITEV */ + +VALUE +rb_io_writev(VALUE io, VALUE ary) +{ + return rb_funcallv(io, id_writev, 1, &ary); +} + #ifdef HAVE_FSYNC static VALUE nogvl_fsync(void *ptr) @@ -9899,10 +10117,6 @@ simple_sendfile(int out_fd, int in_fd, off_t *offset, off_t count) */ # define USE_SENDFILE -# ifdef HAVE_SYS_UIO_H -# include -# endif - static ssize_t simple_sendfile(int out_fd, int in_fd, off_t *offset, off_t count) { @@ -11869,6 +12083,7 @@ Init_IO(void) rb_eEOFError = rb_define_class("EOFError", rb_eIOError); id_write = rb_intern("write"); + id_writev = rb_intern("writev"); id_read = rb_intern("read"); id_getc = rb_intern("getc"); id_flush = rb_intern("flush"); @@ -11997,6 +12212,7 @@ Init_IO(void) rb_define_method(rb_cIO, "readpartial", io_readpartial, -1); rb_define_method(rb_cIO, "read", io_read, -1); rb_define_method(rb_cIO, "write", io_write_m, 1); + rb_define_method(rb_cIO, "writev", io_writev, 1); rb_define_method(rb_cIO, "gets", rb_io_gets_m, -1); rb_define_method(rb_cIO, "readline", rb_io_readline, -1); rb_define_method(rb_cIO, "getc", rb_io_getc, 0);