fix(stdio): correct position tracking on mode switch and free owned buffers on close
This commit is contained in:
+40
-4
@@ -278,6 +278,10 @@ stdio_alloc_file(int fd, int m)
|
||||
f->wstop = f->buf + BUFSIZ;
|
||||
f->fd = fd;
|
||||
f->flags = m | F_OWNBUF | F_HEAP;
|
||||
if ((m & F_READ) && (m & F_WRITE))
|
||||
{
|
||||
f->flags |= F_RDWR;
|
||||
}
|
||||
f->pos = 0;
|
||||
f->ungot = 0;
|
||||
f->next = open_list;
|
||||
@@ -320,6 +324,10 @@ stdio_discard_read(FILE *f)
|
||||
f->flags |= F_ERR;
|
||||
return -1;
|
||||
}
|
||||
/* The kernel now sits at the logical position; re-anchor `pos`
|
||||
* there so the invariant holds after the buffer is emptied
|
||||
* below (empty buffer => kernel == pos). */
|
||||
f->pos += (off_t)(f->rpos - f->buf);
|
||||
}
|
||||
f->rpos = f->rstop = f->buf;
|
||||
return 0;
|
||||
@@ -462,7 +470,11 @@ freopen(const char *restrict path, const char *restrict mode, FILE *restrict str
|
||||
if (path == NULL)
|
||||
{
|
||||
/* Change the mode only; keep the descriptor. */
|
||||
stream->flags = (stream->flags & ~(F_READ | F_WRITE | F_APPEND)) | m;
|
||||
stream->flags = (stream->flags & ~(F_READ | F_WRITE | F_APPEND | F_RDWR)) | m;
|
||||
if ((m & F_READ) && (m & F_WRITE))
|
||||
{
|
||||
stream->flags |= F_RDWR;
|
||||
}
|
||||
stream->flags &= ~(F_EOF | F_ERR | F_PUSHED);
|
||||
return stream;
|
||||
}
|
||||
@@ -483,7 +495,12 @@ freopen(const char *restrict path, const char *restrict mode, FILE *restrict str
|
||||
return NULL;
|
||||
}
|
||||
stream->fd = newfd;
|
||||
stream->flags = (stream->flags & ~(F_READ | F_WRITE | F_APPEND | F_EOF | F_ERR | F_PUSHED)) | m;
|
||||
stream->flags =
|
||||
(stream->flags & ~(F_READ | F_WRITE | F_APPEND | F_EOF | F_ERR | F_PUSHED | F_RDWR)) | m;
|
||||
if ((m & F_READ) && (m & F_WRITE))
|
||||
{
|
||||
stream->flags |= F_RDWR;
|
||||
}
|
||||
stream->rpos = stream->rstop = stream->buf;
|
||||
stream->wpos = stream->buf;
|
||||
stream->pos = 0;
|
||||
@@ -522,6 +539,16 @@ fclose(FILE *stream)
|
||||
{
|
||||
*link = stream->next;
|
||||
}
|
||||
if (stream->flags & F_OWNBUF)
|
||||
{
|
||||
/* The FILE struct is heap-owned, so its library-allocated
|
||||
* buffer is too (a user buffer via setvbuf clears F_OWNBUF
|
||||
* and must not be freed). The static std streams are not
|
||||
* F_HEAP, so their lazily allocated buffers are
|
||||
* intentionally never freed: process-lifetime storage,
|
||||
* matching their registry/global nature. */
|
||||
__libc_free(stream->buf);
|
||||
}
|
||||
__libc_free(stream);
|
||||
}
|
||||
return rc;
|
||||
@@ -675,14 +702,16 @@ int
|
||||
fputc(int c, FILE *stream)
|
||||
{
|
||||
stdio_init_if_needed(stream);
|
||||
if (!(stream->flags & F_WRITE))
|
||||
if (!(stream->flags & F_WRITE) && !(stream->flags & F_RDWR))
|
||||
{
|
||||
/* The open mode does not allow writes. */
|
||||
stream->flags |= F_ERR;
|
||||
return EOF;
|
||||
}
|
||||
if (stream->rpos < stream->rstop)
|
||||
{
|
||||
/* Pending read data: discard it before writing. */
|
||||
/* Pending read data: discard it before writing (the read -> write
|
||||
* mode switch on an update stream). */
|
||||
if (stdio_discard_read(stream) < 0)
|
||||
{
|
||||
return EOF;
|
||||
@@ -808,6 +837,13 @@ fgets(char *restrict s, int n, FILE *restrict stream)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
/* Mid-line end of input: a genuine EOF keeps the partial
|
||||
* line, but a read error makes the array contents
|
||||
* indeterminate and fgets must return NULL (C23 7.23.7.2). */
|
||||
if (stream->flags & F_ERR)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
s[i] = (char)c;
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
*
|
||||
* Mode switching: reading while write data is pending flushes it first;
|
||||
* writing while unread buffered data is pending seeks the kernel back to
|
||||
* the logical position (discarding the unread data). F_PUSHED marks the
|
||||
* single guaranteed ungetc pushback byte in `ungot`; a successful seek
|
||||
* discards it.
|
||||
* the logical position (discarding the unread data). F_WRITE doubles as
|
||||
* the current-mode marker and is cleared by the read switch; F_RDWR
|
||||
* records the write capability of update streams so the write switch can
|
||||
* re-set F_WRITE. F_PUSHED marks the single guaranteed ungetc pushback
|
||||
* byte in `ungot`; a successful seek discards it.
|
||||
*
|
||||
* The helper prototypes below are shared with the formatted-I/O todos
|
||||
* (16: vfprintf, 17: vfscanf) so they operate on the same buffer state.
|
||||
@@ -55,6 +57,7 @@ struct vlibc_FILE
|
||||
#define F_OWNBUF 0x080 /* buf was allocated by the library */
|
||||
#define F_HEAP 0x100 /* the FILE struct itself is heap-allocated */
|
||||
#define F_PUSHED 0x200 /* ungot holds a pushed-back byte */
|
||||
#define F_RDWR 0x400 /* update stream: reads and writes both enabled */
|
||||
|
||||
/* Flush pending write data; returns 0, or -1 with F_ERR set. */
|
||||
hidden int
|
||||
|
||||
Reference in New Issue
Block a user