Ah, another solution occurred to me. The generic layer packages the prefetched character in the
return value of port-position, so that set-port-position! will restore prefetched state of the port. With that,
the issue of the textual input port will be solved.
Yes, that makes sense. But ....
It doesn't solve textual input/output port, though. Even set-port-position! restores the prefetched character,
next write-char must write at the logical position of the port. So it must set the underlying position one
character before.
I read about how fseek interacts with ungetc (which is a sort of dual of peek-char). Posix says that the character stashed by ungetc is discarded when you do a fseek, so I think it's not a problem that doing a set-position! when a character has been peeked forgets the character. So I think it is acceptable not to worry about getting it right in this situation.
I note also that Racket's native custom ports are specified with a peek! argument equivalent to read! except that it puts the peeked bytes in the specified bytevector, and if you don't specify this, you simply can't peek on such a port. Similarly, Larceny doesn't actually invoke the port-position and set-port-position arguments: everything is done in the generic port implementation, which is aware that it's dealing with a custom port.