diff options
Diffstat (limited to 'libio/wfileops.c')
-rw-r--r-- | libio/wfileops.c | 116 |
1 files changed, 96 insertions, 20 deletions
diff --git a/libio/wfileops.c b/libio/wfileops.c index d3f46b2ed1..1087e8df8d 100644 --- a/libio/wfileops.c +++ b/libio/wfileops.c @@ -613,6 +613,10 @@ _IO_wfile_seekoff (fp, offset, dir, mode) && (fp->_wide_data->_IO_write_base == fp->_wide_data->_IO_write_ptr)); + bool was_writing = ((fp->_wide_data->_IO_write_ptr + > fp->_wide_data->_IO_write_base) + || _IO_in_put_mode (fp)); + if (mode == 0) { /* XXX For wide stream with backup store it is not very @@ -644,11 +648,8 @@ _IO_wfile_seekoff (fp, offset, dir, mode) which assumes file_ptr() is eGptr. Anyway, since we probably end up flushing when we close(), it doesn't make much difference.) FIXME: simulate mem-mapped files. */ - - if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base - || _IO_in_put_mode (fp)) - if (_IO_switch_to_wget_mode (fp)) - return WEOF; + else if (was_writing && _IO_switch_to_wget_mode (fp)) + return WEOF; if (fp->_wide_data->_IO_buf_base == NULL) { @@ -679,29 +680,104 @@ _IO_wfile_seekoff (fp, offset, dir, mode) cv = fp->_codecvt; clen = (*cv->__codecvt_do_encoding) (cv); - if (clen > 0) + if (mode != 0 || !was_writing) { - offset -= (fp->_wide_data->_IO_read_end - - fp->_wide_data->_IO_read_ptr) * clen; - /* Adjust by readahead in external buffer. */ - offset -= fp->_IO_read_end - fp->_IO_read_ptr; + if (clen > 0) + { + offset -= (fp->_wide_data->_IO_read_end + - fp->_wide_data->_IO_read_ptr) * clen; + /* Adjust by readahead in external buffer. */ + offset -= fp->_IO_read_end - fp->_IO_read_ptr; + } + else + { + int nread; + + flushed: + delta = (fp->_wide_data->_IO_read_ptr + - fp->_wide_data->_IO_read_base); + fp->_wide_data->_IO_state = fp->_wide_data->_IO_last_state; + nread = (*cv->__codecvt_do_length) (cv, + &fp->_wide_data->_IO_state, + fp->_IO_read_base, + fp->_IO_read_end, delta); + fp->_IO_read_ptr = fp->_IO_read_base + nread; + fp->_wide_data->_IO_read_end = fp->_wide_data->_IO_read_ptr; + offset -= fp->_IO_read_end - fp->_IO_read_base - nread; + } } else { - int nread; + char *new_write_ptr = fp->_IO_write_ptr; - delta = fp->_wide_data->_IO_read_ptr - fp->_wide_data->_IO_read_base; - fp->_wide_data->_IO_state = fp->_wide_data->_IO_last_state; - nread = (*cv->__codecvt_do_length) (cv, &fp->_wide_data->_IO_state, - fp->_IO_read_base, - fp->_IO_read_end, delta); - fp->_IO_read_ptr = fp->_IO_read_base + nread; - fp->_wide_data->_IO_read_end = fp->_wide_data->_IO_read_ptr; - offset -= fp->_IO_read_end - fp->_IO_read_base - nread; + if (clen > 0) + offset += (fp->_wide_data->_IO_write_ptr + - fp->_wide_data->_IO_write_base) / clen; + else + { + enum __codecvt_result status; + delta = (fp->_wide_data->_IO_write_ptr + - fp->_wide_data->_IO_write_base); + const wchar_t *write_base = fp->_wide_data->_IO_write_base; + + /* FIXME: This actually ends up in two iterations of conversion, + one here and the next when the buffer actually gets flushed. + It may be possible to optimize this in future so that + wdo_write identifies already converted content and does not + redo it. In any case, this is much better than having to + flush buffers for every ftell. */ + do + { + /* Ugh, no point trying to avoid the flush. Just do it + and go back to how it was with the read mode. */ + if (delta > 0 && new_write_ptr == fp->_IO_buf_end) + { + if (_IO_switch_to_wget_mode (fp)) + return WEOF; + goto flushed; + } + + const wchar_t *new_wbase = fp->_wide_data->_IO_write_base; + fp->_wide_data->_IO_state = fp->_wide_data->_IO_last_state; + status = (*cv->__codecvt_do_out) (cv, + &fp->_wide_data->_IO_state, + write_base, + write_base + delta, + &new_wbase, + new_write_ptr, + fp->_IO_buf_end, + &new_write_ptr); + + delta -= new_wbase - write_base; + + /* If there was an error, then return WEOF. + TODO: set buffer state. */ + if (__builtin_expect (status == __codecvt_error, 0)) + return WEOF; + } + while (delta > 0); + } + + /* _IO_read_end coincides with fp._offset, so the actual file position + is fp._offset - (_IO_read_end - new_write_ptr). This is fine + even if fp._offset is not set, since fp->_IO_read_end is then at + _IO_buf_base and this adjustment is for unbuffered output. */ + offset -= fp->_IO_read_end - new_write_ptr; } if (fp->_offset == _IO_pos_BAD) - goto dumb; + { + if (mode != 0) + goto dumb; + else + { + result = _IO_SYSSEEK (fp, 0, dir); + if (result == EOF) + return result; + fp->_offset = result; + } + } + /* Make offset absolute, assuming current pointer is file_ptr(). */ offset += fp->_offset; |