From: "YO4 (Yoshinao Muramatsu) via ruby-core" Date: 2025-11-15T16:30:19+00:00 Subject: [ruby-core:123805] [Ruby Bug#21686] In combination with IO#ungetbyte, the write position may become unpredictable. Issue #21686 has been updated by YO4 (Yoshinao Muramatsu). I made [PR #15204](https://github.com/ruby/ruby/pull/15204) ## Changed and Unchanged The behavior of this PR is listed below. Since IO#ungetc has a different file position behavior when combined with encoding conversion, IO#getbyte is used for explanation. 1. `IO#ungetbyte` moves the file position. This is to restore the file position as much as possible after executing IO#getbyte. 1. After `IO#ungetbyte` is invoked, the file position may become negative. This is to avoid situations where the file position does not advance even after multiple getbyte calls, by restricting it to non-negative values. 1. `IO#pos` always **retains the buffer**. Flushing the buffer when the file position is negative will not allow the next call to return the same file position. 1. `IO#seek` **may raise** an exception if `self.seek(0, File::SEEK_CUR)`* is called. This occurs when the file position is negative. ### Impact of Behavior Changes Regarding IO#pos, since the typical use case for IO#ungetbyte is to push back the same data that was read, buffer retention or reset will yield identical results in this scenario. The change to IO#seek is considered acceptable because, in the above typical use case, the file position never becomes negative, and the previous behavior is deemed of no practical value. ---------------------------------------- Bug #21686: In combination with IO#ungetbyte, the write position may become unpredictable. https://bugs.ruby-lang.org/issues/21686#change-115208 * Author: YO4 (Yoshinao Muramatsu) * Status: Open * ruby -v: ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [x64-mingw-ucrt] * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- In the current implementation, using `IO#ungetbyte` can cause `IO#pos` to become negative. Writing to the same file descriptor in this state will result in unexpected write positions. ```ruby require 'tempfile' Tempfile.open do |f| f.write("0123456789") f.rewind f.getbyte # rbuf filled f.ungetbyte("-") # f.pos => 0 f.ungetbyte("-") # f.pos => -1 f.write("x") # seek to -1 failed in io_unread f.rewind p f.read # => "0123456789x", wrote at the end, questionable end ``` When internal `io_unread()` in `io.c` is called to prepare for a write operation while the actual file position is advancing due to reading, lseek fails because it attempts to reset to a negative file position. `IO#seek` has a similar issue. ```ruby Tempfile.open do |f| f.write("0123456789") f.rewind f.getbyte # rbuf filled f.ungetbyte("-") # f.pos => 0 f.ungetbyte("-") # f.pos => -1 f.seek(0, File::SEEK_CUR) p f.pos # => 10, this points to the last of rbuf. end ``` I am preparing the PR, but I understand that there is discussion about the position where the write occurs and the possibility of the pos becoming negative. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/