Skip to content

Commit

Permalink
[Bug #20869] Discard rbuf and encoding converters at seeking
Browse files Browse the repository at this point in the history
  • Loading branch information
nobu committed Nov 7, 2024
1 parent 1701dde commit 7bd2ce2
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 18 deletions.
41 changes: 23 additions & 18 deletions io.c
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,8 @@ rb_cloexec_fcntl_dupfd(int fd, int minfd)
#endif

static int io_fflush(rb_io_t *);
static rb_io_t *flush_before_seek(rb_io_t *fptr);
static rb_io_t *flush_before_seek(rb_io_t *fptr, bool discard_rbuf);
static void clear_codeconv(rb_io_t *fptr);

#define FMODE_SIGNAL_ON_EPIPE (1<<17)

Expand Down Expand Up @@ -626,7 +627,7 @@ rb_sys_fail_on_write(rb_io_t *fptr)
* IO unread with taking care of removed '\r' in text mode.
*/
static void
io_unread(rb_io_t *fptr)
io_unread(rb_io_t *fptr, bool discard_rbuf)
{
rb_off_t r, pos;
ssize_t read_size;
Expand All @@ -647,19 +648,17 @@ io_unread(rb_io_t *fptr)
if (r < 0 && errno) {
if (errno == ESPIPE)
fptr->mode |= FMODE_DUPLEX;
return;
if (!discard_rbuf) return;
}

fptr->rbuf.off = 0;
fptr->rbuf.len = 0;
return;
goto end;
}

pos = lseek(fptr->fd, 0, SEEK_CUR);
if (pos < 0 && errno) {
if (errno == ESPIPE)
fptr->mode |= FMODE_DUPLEX;
return;
if (!discard_rbuf) goto end;
}

/* add extra offset for removed '\r' in rbuf */
Expand Down Expand Up @@ -700,8 +699,10 @@ io_unread(rb_io_t *fptr)
}
}
free(buf);
end:
fptr->rbuf.off = 0;
fptr->rbuf.len = 0;
clear_codeconv(fptr);
return;
}

Expand All @@ -720,7 +721,7 @@ set_binary_mode_with_seek_cur(rb_io_t *fptr)
if (fptr->rbuf.len == 0 || fptr->mode & FMODE_DUPLEX) {
return setmode(fptr->fd, O_BINARY);
}
flush_before_seek(fptr);
flush_before_seek(fptr, false);
return setmode(fptr->fd, O_BINARY);
}
#define SET_BINARY_MODE_WITH_SEEK_CUR(fptr) set_binary_mode_with_seek_cur(fptr)
Expand Down Expand Up @@ -916,7 +917,7 @@ rb_io_s_try_convert(VALUE dummy, VALUE io)

#if !RUBY_CRLF_ENVIRONMENT
static void
io_unread(rb_io_t *fptr)
io_unread(rb_io_t *fptr, bool discard_rbuf)
{
rb_off_t r;
rb_io_check_closed(fptr);
Expand All @@ -928,10 +929,11 @@ io_unread(rb_io_t *fptr)
if (r < 0 && errno) {
if (errno == ESPIPE)
fptr->mode |= FMODE_DUPLEX;
return;
if (!discard_rbuf) return;
}
fptr->rbuf.off = 0;
fptr->rbuf.len = 0;
clear_codeconv(fptr);
return;
}
#endif
Expand Down Expand Up @@ -972,17 +974,17 @@ io_ungetbyte(VALUE str, rb_io_t *fptr)
}

static rb_io_t *
flush_before_seek(rb_io_t *fptr)
flush_before_seek(rb_io_t *fptr, bool discard_rbuf)
{
if (io_fflush(fptr) < 0)
rb_sys_fail_on_write(fptr);
io_unread(fptr);
io_unread(fptr, discard_rbuf);
errno = 0;
return fptr;
}

#define io_seek(fptr, ofs, whence) (errno = 0, lseek(flush_before_seek(fptr)->fd, (ofs), (whence)))
#define io_tell(fptr) lseek(flush_before_seek(fptr)->fd, 0, SEEK_CUR)
#define io_seek(fptr, ofs, whence) (errno = 0, lseek(flush_before_seek(fptr, true)->fd, (ofs), (whence)))
#define io_tell(fptr) lseek(flush_before_seek(fptr, false)->fd, 0, SEEK_CUR)

#ifndef SEEK_CUR
# define SEEK_SET 0
Expand Down Expand Up @@ -1050,7 +1052,7 @@ rb_io_check_writable(rb_io_t *fptr)
rb_raise(rb_eIOError, "not opened for writing");
}
if (fptr->rbuf.len) {
io_unread(fptr);
io_unread(fptr, true);
}
}

Expand Down Expand Up @@ -2376,7 +2378,7 @@ rb_io_flush_raw(VALUE io, int sync)
rb_sys_fail_on_write(fptr);
}
if (fptr->mode & FMODE_READABLE) {
io_unread(fptr);
io_unread(fptr, true);
}

return io;
Expand Down Expand Up @@ -5471,7 +5473,6 @@ maygvl_fclose(FILE *file, int keepgvl)
}

static void free_io_buffer(rb_io_buffer_t *buf);
static void clear_codeconv(rb_io_t *fptr);

static void
fptr_finalize_flush(rb_io_t *fptr, int noraise, int keepgvl,
Expand Down Expand Up @@ -8314,7 +8315,7 @@ io_reopen(VALUE io, VALUE nfile)
rb_sys_fail_on_write(fptr);
}
else {
flush_before_seek(fptr);
flush_before_seek(fptr, true);
}
if (orig->mode & FMODE_READABLE) {
pos = io_tell(orig);
Expand Down Expand Up @@ -15069,6 +15070,9 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y)
* A new stream has position zero (and line number zero);
* method +rewind+ resets the position (and line number) to zero.
*
* These methods discard {buffers}[rdoc-ref:IO@Buffering] and the
* Encoding::Converter instances used for that \IO.
*
* The relevant methods:
*
* - IO#tell (aliased as +#pos+): Returns the current position (in bytes) in the stream.
Expand Down Expand Up @@ -15375,6 +15379,7 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y)
* - IO#putc: Writes a character to the stream.
* - IO#each_char: Reads each remaining character in the stream,
* passing the character to the given block.
*
* == Byte \IO
*
* You can process an \IO stream byte-by-byte using these methods:
Expand Down
26 changes: 26 additions & 0 deletions test/ruby/test_io.rb
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,19 @@ def test_ungetc
end)
end

def test_ungetc_with_seek
make_tempfile {|t|
t.open
t.write('0123456789')
t.rewind

t.ungetc('a')
t.seek(2, :SET)

assert_equal('2', t.getc)
}
end

def test_ungetbyte
make_tempfile {|t|
t.open
Expand All @@ -373,6 +386,19 @@ def test_ungetbyte
}
end

def test_ungetbyte_with_seek
make_tempfile {|t|
t.open
t.write('0123456789')
t.rewind

t.ungetbyte('a'.ord)
t.seek(2, :SET)

assert_equal('2'.ord, t.getbyte)
}
end

def test_each_byte
pipe(proc do |w|
w << "abc def"
Expand Down

0 comments on commit 7bd2ce2

Please sign in to comment.