@@ -26,8 +26,33 @@ typedef struct {
size_t eof;
char **bufloc;
size_t *sizeloc;
+ size_t saved_at; /* Where a data byte was replaced by the */
+ char saved_ch; /* terminator, to put it back later. */
+ char have_saved;
} __oms_cookie;
+/* POSIX wants the terminator in place and the size updated after every
+ * fflush(), but a cookie stream has no flush callback: __stdio_wcommit() only
+ * reaches oms_write() when the FILE buffer holds something. So both are kept
+ * current at all times instead. Seeking back over existing data would destroy
+ * the byte at the new position, so that one byte is remembered and restored
+ * once the position moves on -- glibc writes the terminator from its flush hook
+ * (_IO_mem_sync) and leaves the data alone, and programs may rely on that. */
+static void oms_term(register __oms_cookie *c)
+{
+ if (c->have_saved) {
+ c->buf[c->saved_at] = c->saved_ch;
+ c->have_saved = 0;
+ }
+ if (c->pos < c->eof) {
+ c->saved_at = c->pos;
+ c->saved_ch = c->buf[c->pos];
+ c->have_saved = 1;
+ }
+ c->buf[c->pos] = 0;
+ *c->sizeloc = c->pos;
+}
+
/* Nothing to do here, as memstreams are write-only. */
/* static ssize_t oms_read(void *cookie, char *buf, size_t bufsize) */
/* { */
@@ -57,13 +82,17 @@ static ssize_t oms_write(register void *cookie, const char *buf, size_t bufsize)
}
}
+ /* bufsize is at least 1 here, so the saved byte -- which sits at pos --
+ * is about to be overwritten and must not be restored. */
+ COOKIE->have_saved = 0;
+
memcpy(COOKIE->buf + COOKIE->pos, buf, bufsize);
COOKIE->pos += bufsize;
if (COOKIE->pos > COOKIE->eof) {
- *COOKIE->sizeloc = COOKIE->eof = COOKIE->pos;
- COOKIE->buf[COOKIE->eof] = 0; /* Need to nul-terminate. */
+ COOKIE->eof = COOKIE->pos; /* Buffer defined this far. */
}
+ oms_term(COOKIE);
return bufsize;
}
@@ -78,8 +107,12 @@ static int oms_seek(register void *cookie, __offmax_t *pos, int whence)
* unless debugging. */
assert(((unsigned int) whence) <= 2);
+ /* POSIX leaves it implementation-defined whether SEEK_END refers to the
+ * buffer length or to the size an fflush() would report; glibc uses the
+ * latter, which for a write-only stream is the position, so both bases are
+ * the same here. musl uses the former. */
if (whence != SEEK_SET) {
- p += (whence == SEEK_CUR) ? COOKIE->pos : /* SEEK_END */ COOKIE->eof;
+ p += COOKIE->pos;
}
/* Note: glibc only allows seeking in the buffer. We'll actually restrict
@@ -105,10 +138,11 @@ static int oms_seek(register void *cookie, __offmax_t *pos, int whence)
*pos = COOKIE->pos = --leastlen;
- if (leastlen > COOKIE->eof) {
+ if (leastlen > COOKIE->eof) { /* Seeked past the end: 0-fill the gap. */
memset(COOKIE->buf + COOKIE->eof, 0, leastlen - COOKIE->eof);
- *COOKIE->sizeloc = COOKIE->eof;
+ COOKIE->eof = leastlen;
}
+ oms_term(COOKIE);
return 0;
}
@@ -142,6 +176,7 @@ FILE *open_memstream(char **bufloc, size_t *sizeloc)
*cookie->buf = 0; /* Set nul terminator for buffer. */
*(cookie->bufloc = bufloc) = cookie->buf;
*(cookie->sizeloc = sizeloc) = cookie->eof = cookie->pos = 0;
+ cookie->have_saved = 0;
#ifndef __BCC__
fp = fopencookie(cookie, "w", _oms_io_funcs);