diff --git a/include/ruby/win32.h b/include/ruby/win32.h index 83da87a..d49a9b4 100644 --- a/include/ruby/win32.h +++ b/include/ruby/win32.h @@ -304,6 +304,7 @@ extern int rb_w32_stati64(const char *, struct stati64 *); extern int rb_w32_ustati64(const char *, struct stati64 *); extern int rb_w32_access(const char *, int); extern int rb_w32_uaccess(const char *, int); +extern char rb_w32_fd_is_text(int); #ifdef __BORLANDC__ extern int rb_w32_fstati64(int, struct stati64 *); diff --git a/io.c b/io.c index 0713f35..3e184f2 100644 --- a/io.c +++ b/io.c @@ -374,6 +374,8 @@ rb_cloexec_fcntl_dupfd(int fd, int minfd) # endif #endif +static int io_fflush(rb_io_t *); + #define NEED_NEWLINE_DECORATOR_ON_READ(fptr) ((fptr)->mode & FMODE_TEXTMODE) #define NEED_NEWLINE_DECORATOR_ON_WRITE(fptr) ((fptr)->mode & FMODE_TEXTMODE) #if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) @@ -413,22 +415,59 @@ rb_cloexec_fcntl_dupfd(int fd, int minfd) * but stdin and pipe cannot seek back. Stdin and pipe read should use encoding * conversion for working properly with mode change. */ -#define SET_BINARY_MODE_WITH_SEEK_CUR(fptr) do {\ - if ((fptr)->rbuf.len > 0 && !((fptr)->mode & FMODE_DUPLEX)) {\ - off_t r;\ - errno = 0;\ - r = io_seek((fptr), -(fptr)->rbuf.len, SEEK_CUR);\ - if (r < 0 && errno) {\ - if (errno == ESPIPE)\ - (fptr)->mode |= FMODE_DUPLEX;\ - }\ - else {\ - (fptr)->rbuf.off = 0;\ - (fptr)->rbuf.len = 0;\ - }\ - }\ - setmode((fptr)->fd, O_BINARY);\ -} while(0) +/* + * Return previous translation mode. + */ +inline static int set_binary_mode_with_seek_cur(rb_io_t *fptr) { + off_t r, pos; + ssize_t read_size; + long i; + long newlines = 0; + char *p; + + if (!rb_w32_fd_is_text((fptr)->fd)) return O_BINARY; + + if ((fptr)->rbuf.len == 0 || (fptr)->mode & FMODE_DUPLEX) { + setmode((fptr)->fd, O_BINARY); + return O_TEXT; + } + + if (io_fflush(fptr) < 0) { + rb_sys_fail(0); + } + errno = 0; + pos = lseek((fptr)->fd, 0, SEEK_CUR); + if (pos < 0 && errno) { + if (errno == ESPIPE) + (fptr)->mode |= FMODE_DUPLEX; + setmode((fptr)->fd, O_BINARY); + return O_TEXT; + } + /* add extra offset for '\r' */ + p = (fptr)->rbuf.ptr+(fptr)->rbuf.off; + for (i = 0; i < (fptr)->rbuf.len; i++) { + if (*p == '\n') newlines++; + p++; + } + while (newlines >= 0) { + r = lseek((fptr)->fd, pos - (fptr)->rbuf.len - newlines, SEEK_SET); + if (newlines == 0) break; + if (read_size = _read((fptr)->fd, (fptr)->rbuf.ptr, (fptr)->rbuf.len + newlines)) { + if (read_size == (fptr)->rbuf.len) { + lseek((fptr)->fd, r, SEEK_SET); + break; + } + else { + newlines--; + } + } + } + (fptr)->rbuf.off = 0; + (fptr)->rbuf.len = 0; + setmode((fptr)->fd, O_BINARY); + return O_TEXT; +} +#define SET_BINARY_MODE_WITH_SEEK_CUR(fptr) set_binary_mode_with_seek_cur(fptr) #else /* Unix */ @@ -494,7 +533,6 @@ rb_io_check_closed(rb_io_t *fptr) } } -static int io_fflush(rb_io_t *); VALUE rb_io_get_io(VALUE io) @@ -1142,6 +1180,9 @@ do_writeconv(VALUE str, rb_io_t *fptr) !(fptr->encs.ecflags & ECONV_NEWLINE_DECORATOR_MASK)) { setmode(fptr->fd, O_BINARY); } + else { + setmode(fptr->fd, O_TEXT); + } if (!rb_enc_asciicompat(rb_enc_get(str))) { rb_raise(rb_eArgError, "ASCII incompatible string written for text mode IO without encoding conversion: %s", rb_enc_name(rb_enc_get(str))); @@ -2462,6 +2503,9 @@ io_read(int argc, VALUE *argv, VALUE io) rb_io_t *fptr; long n, len; VALUE length, str; +#if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) + int previous_mode; +#endif rb_scan_args(argc, argv, "02", &length, &str); @@ -2482,7 +2526,15 @@ io_read(int argc, VALUE *argv, VALUE io) if (len == 0) return str; READ_CHECK(fptr); +#if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) + previous_mode = set_binary_mode_with_seek_cur(fptr); +#endif n = io_fread(str, 0, fptr); +#if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) + if (previous_mode == O_TEXT) { + setmode(fptr->fd, O_TEXT); + } +#endif if (n == 0) { if (fptr->fd < 0) return Qnil; rb_str_resize(str, 0); diff --git a/test/ruby/test_io_m17n.rb b/test/ruby/test_io_m17n.rb index 6f823c8..b9c88ab 100644 --- a/test/ruby/test_io_m17n.rb +++ b/test/ruby/test_io_m17n.rb @@ -2222,4 +2222,96 @@ EOT end end end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_read_with_length + with_tmpdir { + str = "a\nb" + generate_file("tmp", str) + open("tmp", "r") do |f| + assert_equal(str, f.read(3)) + end + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_read_with_length_binmode + with_tmpdir { + str = "a\r\nb\r\nc\r\n\r\n" + generate_file("tmp", str) + open("tmp", "r") do |f| + # read with length should be binary mode + assert_equal("a\r\n", f.read(3)) # binary + assert_equal("b\nc\n\n", f.read) # text + end + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_gets_and_read_with_binmode + with_tmpdir { + str = "a\r\nb\r\nc\r\n\n\r\n" + generate_file("tmp", str) + open("tmp", "r") do |f| + assert_equal("a\n", f.gets) # text + assert_equal("b\r\n", f.read(3)) # binary + assert_equal("c\r\n", f.read(3)) # binary + assert_equal("\n\n", f.read) # text + end + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_getc_and_read_with_binmode + with_tmpdir { + str = "a\r\nb\r\nc\n\n\r\n\r\n" + generate_file("tmp", str) + open("tmp", "r") do |f| + assert_equal("a", f.getc) # text + assert_equal("\n", f.getc) # text + assert_equal("b\r\n", f.read(3)) # binary + assert_equal("c\n\n\n\n", f.read) # text + end + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_read_with_binmode_and_gets + with_tmpdir { + str = "a\r\nb\r\nc\r\n\r\n" + open("tmp", "wb") { |f| f.write str } + open("tmp", "r") do |f| + assert_equal("a", f.getc) # text + assert_equal("\n", f.getc) # text + assert_equal("b\r\n", f.read(3)) # binary + assert_equal("c\n", f.gets) # text + assert_equal("\n", f.gets) # text + end + } + end + + def test_read_with_binmode_and_getc + with_tmpdir { + str = "a\r\nb\r\nc\r\n\r\n" + open("tmp", "wb") { |f| f.write str } + open("tmp", "r") do |f| + assert_equal("a", f.getc) # text + assert_equal("\n", f.getc) # text + assert_equal("b\r\n", f.read(3)) # binary + assert_equal("c", f.getc) # text + assert_equal("\n", f.getc) # text + assert_equal("\n", f.getc) # text + end + } + end + + def test_read_write_with_binmode + with_tmpdir { + str = "a\r\n" + generate_file("tmp", str) + open("tmp", "r+") do |f| + assert_equal("a\r\n", f.read(3)) # binary + f.write("b\n\n"); # text + f.rewind + assert_equal("a\nb\n\n", f.read) # text + f.rewind + assert_equal("a\r\nb\r\n\r\n", f.binmode.read) # binary + end + } + end if /mswin|mingw/ =~ RUBY_PLATFORM end diff --git a/win32/win32.c b/win32/win32.c index 67a392e..79df655 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -2259,6 +2259,9 @@ init_stdhandle(void) if (fileno(stdin) < 0) { stdin->_file = open_null(0); } + else { + setmode(fileno(stdin), O_BINARY); + } if (fileno(stdout) < 0) { stdout->_file = open_null(1); } @@ -6137,3 +6140,8 @@ rb_w32_inet_ntop(int af, const void *addr, char *numaddr, size_t numaddr_len) } return numaddr; } + +char +rb_w32_fd_is_text(int fd) { + return _osfile(fd) & FTEXT; +}