Implement stdio buffering #36
@ -41,3 +41,4 @@ luna_app(pivot_root.cpp pivot_root)
|
||||
luna_app(cp.cpp cp)
|
||||
luna_app(kill.cpp kill)
|
||||
luna_app(gol.cpp gol)
|
||||
luna_app(buffer-test.cpp buffer-test)
|
||||
|
27
apps/buffer-test.cpp
Normal file
27
apps/buffer-test.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
fprintf(stderr, "Writing incomplete line to stdout (_IOLBF=%d)...\n", stdout->_buf.mode);
|
||||
fputs("hi!", stdout);
|
||||
sleep(3);
|
||||
putchar('\n');
|
||||
fprintf(stderr, "Incomplete line should have been written.\n");
|
||||
|
||||
FILE* f = fopen("/dev/console", "w+");
|
||||
assert(f);
|
||||
assert(setvbuf(f, NULL, _IOFBF, 0) == 0);
|
||||
|
||||
fprintf(stderr, "Writing long text to file (_IOFBF=%d)...\n", f->_buf.mode);
|
||||
|
||||
fputs("Hello world!\nHow are you doing!\nThis is a test for many lines of buffering.\n", f);
|
||||
|
||||
sleep(3);
|
||||
fflush(f);
|
||||
|
||||
fprintf(stderr, "Long text should have been written.\n");
|
||||
|
||||
fclose(f);
|
||||
}
|
@ -31,6 +31,7 @@ char* getpass()
|
||||
tcsetpgrp(STDIN_FILENO, getpgid(0));
|
||||
|
||||
fputs("Password: ", stdout);
|
||||
fflush(stdout);
|
||||
|
||||
if (tcgetattr(STDIN_FILENO, &orig) < 0)
|
||||
{
|
||||
|
@ -10,11 +10,23 @@
|
||||
#define __need_NULL
|
||||
#include <stddef.h>
|
||||
|
||||
#define FOPEN_MAX 64 // Make sure this value matches FD_MAX in the kernel source.
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int _fd;
|
||||
int _err;
|
||||
int _eof;
|
||||
int _fd; // The underlying file descriptor.
|
||||
int _err; // The error status flag.
|
||||
int _eof; // The end-of-file status flag.
|
||||
struct
|
||||
{
|
||||
size_t capacity; // The buffer's total capacity.
|
||||
size_t size; // The buffer's used size.
|
||||
size_t index; // The read index into the buffer.
|
||||
char* buffer; // The memory used for the buffer.
|
||||
int status; // The buffer status flags.
|
||||
int mode; // The buffering mode.
|
||||
} _buf;
|
||||
int _flags; // The file access mode with which the file was opened.
|
||||
} FILE;
|
||||
|
||||
#define EOF -1
|
||||
@ -26,11 +38,15 @@ extern FILE* stderr;
|
||||
#define stdout stdout
|
||||
#define stderr stderr
|
||||
|
||||
#define BUFSIZ 1024
|
||||
#define BUFSIZ 4096
|
||||
#define FILENAME_MAX \
|
||||
1024 // As Luna does not impose a limit on this, this is the recommended size for character arrays holding a file
|
||||
// name.
|
||||
|
||||
#define _IONBF 0
|
||||
#define _IOLBF 1
|
||||
#define _IOFBF 2
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
@ -116,8 +132,6 @@ extern "C"
|
||||
/* Clear the error and end-of-file indicators in stream. */
|
||||
void clearerr(FILE* stream);
|
||||
|
||||
void setbuf(FILE*, char*);
|
||||
|
||||
/* Write formatted output to a file. */
|
||||
int fprintf(FILE* stream, const char* format, ...);
|
||||
|
||||
@ -172,6 +186,18 @@ extern "C"
|
||||
/* Create a unique temporary file. */
|
||||
FILE* tmpfile(void);
|
||||
|
||||
/* Change a file's buffering mode and internal buffer. */
|
||||
int setvbuf(FILE* stream, char* buf, int mode, size_t size);
|
||||
|
||||
/* Change a file's internal buffer. */
|
||||
void setbuf(FILE* stream, char* buf);
|
||||
|
||||
/* Change a file's internal buffer. */
|
||||
void setbuffer(FILE* stream, char* buf, size_t size);
|
||||
|
||||
/* Change a file's buffering mode to line buffered. */
|
||||
void setlinebuf(FILE* stream);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -1,3 +1,4 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef void (*atexit_func_t)(void);
|
||||
@ -23,6 +24,8 @@ extern "C"
|
||||
{
|
||||
while (atexit_registered_funcs--) { atexit_funcs[atexit_registered_funcs](); }
|
||||
|
||||
fflush(NULL);
|
||||
|
||||
_Exit(status);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,9 @@
|
||||
|
||||
extern char** environ;
|
||||
|
||||
extern "C" FILE* _fdopen_impl(int, const char*, int);
|
||||
extern "C" void _init_stdio();
|
||||
|
||||
extern "C"
|
||||
{
|
||||
void libc_init(int argc, char** argv, int envc, char** envp)
|
||||
@ -11,8 +14,10 @@ extern "C"
|
||||
ignore(argc, argv, envc);
|
||||
environ = envp;
|
||||
|
||||
stdin = fdopen(STDIN_FILENO, "r");
|
||||
stdout = fdopen(STDOUT_FILENO, "w");
|
||||
stderr = fdopen(STDERR_FILENO, "w");
|
||||
_init_stdio();
|
||||
|
||||
stdin = _fdopen_impl(STDIN_FILENO, "r", _IOLBF);
|
||||
stdout = _fdopen_impl(STDOUT_FILENO, "w", _IOLBF);
|
||||
stderr = _fdopen_impl(STDERR_FILENO, "w", _IONBF);
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,15 @@ FILE* stdin = nullptr;
|
||||
FILE* stderr = nullptr;
|
||||
FILE* stdout = nullptr;
|
||||
|
||||
FILE* s_open_files[FOPEN_MAX];
|
||||
|
||||
enum FileStatusFlags
|
||||
{
|
||||
BufferIsMalloced = (1 << 0),
|
||||
LastRead = (1 << 1),
|
||||
LastWrite = (1 << 2),
|
||||
};
|
||||
|
||||
static const char* read_tmpdir()
|
||||
{
|
||||
const char* tmpdir = getenv("TMPDIR");
|
||||
@ -51,11 +60,143 @@ static int fdopen_check_compatible_mode(int fd, int new_flags)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int flush_write_buffer(FILE* stream)
|
||||
{
|
||||
if (stream->_buf.mode == _IONBF) return 0;
|
||||
|
||||
ssize_t result = write(stream->_fd, stream->_buf.buffer, stream->_buf.size);
|
||||
|
||||
stream->_buf.index = 0;
|
||||
stream->_buf.size = 0;
|
||||
|
||||
stream->_buf.status &= ~FileStatusFlags::LastWrite;
|
||||
|
||||
return result < 0 ? EOF : 0;
|
||||
}
|
||||
|
||||
static int flush_read_buffer(FILE* stream)
|
||||
{
|
||||
if (stream->_buf.mode == _IONBF) return 0;
|
||||
|
||||
// Reset the stream to its expected position.
|
||||
ssize_t unread_bytes = stream->_buf.size - stream->_buf.index;
|
||||
lseek(stream->_fd, -unread_bytes, SEEK_CUR);
|
||||
|
||||
stream->_buf.index = 0;
|
||||
stream->_buf.size = 0;
|
||||
|
||||
stream->_buf.status &= ~FileStatusFlags::LastRead;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t write_into_buffer(FILE* stream, const u8* data, ssize_t size)
|
||||
{
|
||||
ssize_t total_written = 0;
|
||||
|
||||
while (size > 0)
|
||||
{
|
||||
ssize_t nwritten;
|
||||
if (stream->_buf.mode != _IONBF)
|
||||
{
|
||||
if (stream->_buf.status & FileStatusFlags::LastRead) flush_read_buffer(stream);
|
||||
|
||||
if ((stream->_buf.size + size) > stream->_buf.capacity)
|
||||
{
|
||||
if (flush_write_buffer(stream) < 0) return -1;
|
||||
}
|
||||
|
||||
ssize_t size_remaining = stream->_buf.capacity - stream->_buf.size;
|
||||
nwritten = size > size_remaining ? size_remaining : size;
|
||||
memcpy(stream->_buf.buffer + stream->_buf.size, data, nwritten);
|
||||
|
||||
stream->_buf.status |= FileStatusFlags::LastWrite;
|
||||
|
||||
stream->_buf.size += nwritten;
|
||||
|
||||
if (stream->_buf.mode == _IOLBF && memchr(data, '\n', nwritten))
|
||||
{
|
||||
if (flush_write_buffer(stream) < 0) return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
nwritten = write(stream->_fd, data, size > BUFSIZ ? BUFSIZ : size);
|
||||
if (nwritten < 0) return nwritten;
|
||||
size -= nwritten;
|
||||
data += nwritten;
|
||||
total_written += nwritten;
|
||||
}
|
||||
|
||||
return total_written;
|
||||
}
|
||||
|
||||
static ssize_t read_data_into_buffer(FILE* stream)
|
||||
{
|
||||
stream->_buf.index = 0;
|
||||
ssize_t nread = read(stream->_fd, stream->_buf.buffer, stream->_buf.capacity);
|
||||
if (nread >= 0) stream->_buf.size = nread;
|
||||
stream->_buf.status |= FileStatusFlags::LastRead;
|
||||
return nread;
|
||||
}
|
||||
|
||||
static ssize_t read_from_buffer(FILE* stream, u8* data, ssize_t size)
|
||||
{
|
||||
ssize_t total_read = 0;
|
||||
|
||||
while (size > 0)
|
||||
{
|
||||
ssize_t nread;
|
||||
if (stream->_buf.mode != _IONBF)
|
||||
{
|
||||
if (stream->_buf.status & FileStatusFlags::LastWrite) flush_write_buffer(stream);
|
||||
|
||||
if (stream->_buf.size == stream->_buf.index)
|
||||
{
|
||||
ssize_t rc;
|
||||
if ((rc = read_data_into_buffer(stream)) < 0) return -1;
|
||||
if (rc == 0) return total_read;
|
||||
}
|
||||
|
||||
ssize_t size_remaining = stream->_buf.size - stream->_buf.index;
|
||||
nread = size > size_remaining ? size_remaining : size;
|
||||
memcpy(data, stream->_buf.buffer + stream->_buf.index, nread);
|
||||
|
||||
stream->_buf.index += nread;
|
||||
}
|
||||
else
|
||||
nread = read(stream->_fd, data, size > BUFSIZ ? BUFSIZ : size);
|
||||
if (nread < 0) return nread;
|
||||
if (nread == 0) return total_read;
|
||||
size -= nread;
|
||||
data += nread;
|
||||
total_read += nread;
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
{
|
||||
int fflush(FILE*)
|
||||
void _init_stdio()
|
||||
{
|
||||
// FIXME: Files are not buffered right now.
|
||||
memset(&s_open_files, 0, sizeof(s_open_files));
|
||||
}
|
||||
|
||||
int fflush(FILE* stream)
|
||||
{
|
||||
if (stream && stream->_buf.mode != _IONBF)
|
||||
{
|
||||
if (stream->_buf.status & FileStatusFlags::LastWrite) flush_write_buffer(stream);
|
||||
else if (stream->_buf.status & FileStatusFlags::LastRead)
|
||||
flush_read_buffer(stream);
|
||||
}
|
||||
else if (!stream)
|
||||
{
|
||||
for (int i = 0; i < FOPEN_MAX; i++)
|
||||
{
|
||||
if (s_open_files[i]) fflush(s_open_files[i]);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -78,10 +219,19 @@ extern "C"
|
||||
f->_fd = fd;
|
||||
clearerr(f);
|
||||
|
||||
f->_flags = flags;
|
||||
f->_buf.status = 0;
|
||||
f->_buf.mode = isatty(fd) ? _IOLBF : _IOFBF;
|
||||
f->_buf.size = f->_buf.index = 0;
|
||||
f->_buf.buffer = nullptr;
|
||||
setvbuf(f, NULL, f->_buf.mode, 0);
|
||||
|
||||
s_open_files[fd] = f;
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
FILE* fdopen(int fd, const char* mode)
|
||||
FILE* _fdopen_impl(int fd, const char* mode, int buffering_mode)
|
||||
{
|
||||
int flags;
|
||||
|
||||
@ -95,9 +245,23 @@ extern "C"
|
||||
f->_fd = fd;
|
||||
clearerr(f);
|
||||
|
||||
f->_flags = flags;
|
||||
f->_buf.status = 0;
|
||||
f->_buf.mode = buffering_mode < 0 ? (isatty(fd) ? _IOLBF : _IOFBF) : buffering_mode;
|
||||
f->_buf.size = f->_buf.index = 0;
|
||||
f->_buf.buffer = nullptr;
|
||||
setvbuf(f, NULL, f->_buf.mode, 0);
|
||||
|
||||
s_open_files[fd] = f;
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
FILE* fdopen(int fd, const char* mode)
|
||||
{
|
||||
return _fdopen_impl(fd, mode, -1);
|
||||
}
|
||||
|
||||
FILE* freopen(const char* path, const char* mode, FILE* stream)
|
||||
{
|
||||
int flags;
|
||||
@ -106,6 +270,10 @@ extern "C"
|
||||
|
||||
close(stream->_fd);
|
||||
|
||||
s_open_files[stream->_fd] = nullptr;
|
||||
|
||||
if (stream->_buf.buffer && (stream->_buf.status & FileStatusFlags::BufferIsMalloced)) free(stream->_buf.buffer);
|
||||
|
||||
if (!path) { fail("FIXME: freopen() called with path=nullptr"); }
|
||||
|
||||
int fd = open(path, flags, 0666);
|
||||
@ -114,13 +282,28 @@ extern "C"
|
||||
stream->_fd = fd;
|
||||
clearerr(stream);
|
||||
|
||||
stream->_flags = flags;
|
||||
stream->_buf.status = 0;
|
||||
stream->_buf.mode = isatty(fd) ? _IOLBF : _IOFBF;
|
||||
stream->_buf.size = stream->_buf.index = 0;
|
||||
stream->_buf.buffer = nullptr;
|
||||
setvbuf(stream, NULL, stream->_buf.mode, 0);
|
||||
|
||||
s_open_files[fd] = stream;
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
int fclose(FILE* stream)
|
||||
{
|
||||
if (fflush(stream) < 0) return EOF;
|
||||
|
||||
if (close(stream->_fd) < 0) return EOF;
|
||||
|
||||
if (stream->_buf.buffer && (stream->_buf.status & FileStatusFlags::BufferIsMalloced)) free(stream->_buf.buffer);
|
||||
|
||||
s_open_files[stream->_fd] = nullptr;
|
||||
|
||||
free(stream);
|
||||
|
||||
return 0;
|
||||
@ -135,7 +318,7 @@ extern "C"
|
||||
{
|
||||
if (size * nmemb == 0) return 0;
|
||||
|
||||
ssize_t nread = read(stream->_fd, buf, size * nmemb);
|
||||
ssize_t nread = read_from_buffer(stream, (u8*)buf, size * nmemb);
|
||||
|
||||
if (nread < 0)
|
||||
{
|
||||
@ -155,13 +338,20 @@ extern "C"
|
||||
{
|
||||
if (size * nmemb == 0) return 0;
|
||||
|
||||
ssize_t nwrite = write(stream->_fd, buf, size * nmemb);
|
||||
ssize_t nwrite = write_into_buffer(stream, (const u8*)buf, size * nmemb);
|
||||
if (nwrite < 0)
|
||||
{
|
||||
stream->_err = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (size_t)nwrite / size;
|
||||
}
|
||||
|
||||
int fseek(FILE* stream, long offset, int whence)
|
||||
{
|
||||
fflush(stream);
|
||||
|
||||
long result = lseek(stream->_fd, offset, whence);
|
||||
if (result < 0) return -1;
|
||||
|
||||
@ -173,11 +363,15 @@ extern "C"
|
||||
|
||||
long ftell(FILE* stream)
|
||||
{
|
||||
fflush(stream);
|
||||
|
||||
return lseek(stream->_fd, 0, SEEK_CUR);
|
||||
}
|
||||
|
||||
void rewind(FILE* stream)
|
||||
{
|
||||
fflush(stream);
|
||||
|
||||
lseek(stream->_fd, 0, SEEK_SET);
|
||||
|
||||
clearerr(stream);
|
||||
@ -195,6 +389,8 @@ extern "C"
|
||||
|
||||
int fsetpos(FILE* stream, const fpos_t* pos)
|
||||
{
|
||||
fflush(stream);
|
||||
|
||||
return fseek(stream, *pos, SEEK_SET);
|
||||
}
|
||||
|
||||
@ -211,7 +407,7 @@ extern "C"
|
||||
int fputc(int c, FILE* stream)
|
||||
{
|
||||
u8 value = (u8)c;
|
||||
ssize_t rc = write(stream->_fd, &value, 1);
|
||||
ssize_t rc = write_into_buffer(stream, &value, 1);
|
||||
if (rc <= 0) return EOF;
|
||||
return c;
|
||||
}
|
||||
@ -228,14 +424,14 @@ extern "C"
|
||||
|
||||
int fputs(const char* str, FILE* stream)
|
||||
{
|
||||
ssize_t rc = write(stream->_fd, str, strlen(str));
|
||||
ssize_t rc = write_into_buffer(stream, (const u8*)str, strlen(str));
|
||||
return (rc < 0) ? -1 : 0;
|
||||
}
|
||||
|
||||
int fgetc(FILE* stream)
|
||||
{
|
||||
u8 value;
|
||||
ssize_t rc = read(stream->_fd, &value, 1);
|
||||
ssize_t rc = read_from_buffer(stream, &value, 1);
|
||||
if (rc <= 0) return EOF;
|
||||
return value;
|
||||
}
|
||||
@ -492,8 +688,60 @@ extern "C"
|
||||
return f;
|
||||
}
|
||||
|
||||
int ungetc(int, FILE*)
|
||||
int ungetc(int c, FILE* stream)
|
||||
{
|
||||
fail("FIXME: ungetc: not implemented");
|
||||
if (stream->_buf.index == 0)
|
||||
return EOF; // No data currently in the read buffer, or no data has been read from it.
|
||||
|
||||
if (stream->_buf.mode == _IONBF)
|
||||
return EOF; // FIXME: C doesn't state that ungetc() should only work on buffered streams.
|
||||
|
||||
stream->_buf.index--;
|
||||
stream->_buf.buffer[stream->_buf.index] = (char)c;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int setvbuf(FILE* stream, char* buf, int mode, size_t size)
|
||||
{
|
||||
int status = 0;
|
||||
if (mode < 0 || mode > _IOFBF) return errno = EINVAL, -1;
|
||||
if (stream->_buf.size != 0 || stream->_buf.index != 0) return -1; // Buffer is already in use.
|
||||
if (mode != _IONBF && buf == NULL)
|
||||
{
|
||||
size = BUFSIZ;
|
||||
buf = (char*)calloc(size, 1);
|
||||
if (!buf) return -1;
|
||||
status = FileStatusFlags::BufferIsMalloced;
|
||||
}
|
||||
else if (mode == _IONBF)
|
||||
{
|
||||
buf = NULL;
|
||||
size = 0;
|
||||
}
|
||||
|
||||
if (stream->_buf.buffer && (stream->_buf.status & FileStatusFlags::BufferIsMalloced)) free(stream->_buf.buffer);
|
||||
|
||||
stream->_buf.buffer = buf;
|
||||
stream->_buf.capacity = size;
|
||||
stream->_buf.mode = mode;
|
||||
stream->_buf.status = status;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void setbuf(FILE* stream, char* buf)
|
||||
{
|
||||
setvbuf(stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ);
|
||||
}
|
||||
|
||||
void setbuffer(FILE* stream, char* buf, size_t size)
|
||||
{
|
||||
setvbuf(stream, buf, buf ? _IOFBF : _IONBF, size);
|
||||
}
|
||||
|
||||
void setlinebuf(FILE* stream)
|
||||
{
|
||||
setvbuf(stream, NULL, _IOLBF, 0);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user