ciabatta/src/_win/stdio.c

512 lines
12 KiB
C

enum stream_width_t {
STREAM_CHAR_UNSET,
STREAM_CHAR_NARROW,
STREAM_CHAR_WIDE,
} typedef stream_width_t;
enum stream_io_mode_t {
STREAM_INPUT,
STREAM_OUTPUT,
STREAM_UPDATE,
} typedef stream_io_mode_t;
enum stream_bt_mode_t {
STREAM_BINARY,
STREAM_TEXT,
} typedef stream_bt_mode_t;
struct stream_buffer_t typedef stream_buffer_t;
struct stream_buffer_t {
int is_internal;
int mode;
size_t size;
void *data;
size_t written;
};
struct FILE {
HANDLE handle;
stream_width_t char_width; // This technically needs not be stored
mbstate_t mbstate;
stream_buffer_t buffer;
stream_io_mode_t io_mode;
stream_bt_mode_t bt_mode;
int eof;
int err;
mtx_t lock;
FILE *prev;
FILE *next;
};
FILE *stdout;
FILE *stdin;
FILE *stderr;
// We hold a linked list of all file streams in order to flush all the buffers
// after the program terminates. It might be not a good idea to store all the
// files into this linked list, but for now performance is not a concern.
static FILE *streams_to_close = NULL;
static void close_list_add(FILE *stream) {
if(streams_to_close != NULL) {
streams_to_close->next = stream;
stream->prev = streams_to_close;
stream->next = NULL;
streams_to_close = stream;
}
else {
streams_to_close = stream;
stream->prev = NULL;
stream->next = NULL;
}
}
static void close_list_remove(FILE *stream) {
FILE *prev = stream->prev;
FILE *next = stream->next;
if(prev != NULL) prev->next = next;
if(next != NULL) next->prev = prev;
if(next == NULL) streams_to_close = prev;
}
static inline void init_stream(
FILE *stream,
HANDLE handle,
stream_io_mode_t io_mode,
stream_bt_mode_t bt_mode
) {
stream->handle = handle;
stream->char_width = STREAM_CHAR_UNSET;
stream->mbstate = (mbstate_t){0};
stream->buffer.mode = _IONBF;
stream->io_mode = io_mode;
stream->bt_mode = bt_mode;
stream->eof = 0;
stream->err = 0;
}
static inline FILE *create_stream(
HANDLE handle,
stream_io_mode_t io_mode,
stream_bt_mode_t bt_mode
) {
FILE *stream = malloc(sizeof(FILE));
if(stream == NULL) return NULL;
init_stream(stream, handle, io_mode, bt_mode);
mtx_init(&stream->lock, mtx_recursive);
close_list_add(stream);
return stream;
}
static inline void delete_stream(FILE *stream) {
mtx_t lock = stream->lock;
mtx_lock(&lock);
CloseHandle(stream->handle);
close_list_remove(stream);
free(stream);
mtx_unlock(&lock);
mtx_destroy(&lock);
}
void _setup_io() {
HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
HANDLE hstderr = GetStdHandle(STD_ERROR_HANDLE);
HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
stdout = create_stream(hstdout, STREAM_UPDATE, STREAM_TEXT);
stderr = create_stream(hstderr, STREAM_UPDATE, STREAM_TEXT);
stdin = create_stream(hstdin, STREAM_INPUT, STREAM_BINARY);
char *in_buf = calloc(BUFSIZ, sizeof(char));
char *out_buf = calloc(BUFSIZ, sizeof(char));
stdin->buffer = (stream_buffer_t){1, _IOLBF, BUFSIZ, in_buf};
stdout->buffer = (stream_buffer_t){1, _IOLBF, BUFSIZ, out_buf};
stderr->buffer = (stream_buffer_t){1, _IONBF, 0, NULL};
}
int win_parse_mode(
char const *mode,
int *pio_mode,
int *pbt_mode,
DWORD *paccess,
DWORD *pshare,
DWORD *pdisp
) {
DWORD access = 0;
DWORD share = 0;
DWORD disp = 0;
stream_io_mode_t io_mode = 0;
stream_bt_mode_t bt_mode = 0;
int flag_p = 0;
int flag_b = 0;
int flag_x = 0;
switch(*mode++) {
case 'r': io_mode = STREAM_INPUT; break;
case 'w': io_mode = STREAM_OUTPUT; break;
case 'a': io_mode = STREAM_UPDATE; break;
default: return 0;
}
while(*mode) switch(*mode++) {
case '+': flag_p = 1; break;
case 'b': flag_b = 1; break;
case 'x': flag_x = 1; break;
default: return 0;
}
bt_mode = flag_b? STREAM_BINARY : STREAM_TEXT;
// Not sure about the sharing modes
switch(io_mode) {
case STREAM_INPUT: {
access = GENERIC_READ | (flag_p? GENERIC_WRITE : 0);
share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
disp = OPEN_EXISTING;
} break;
case STREAM_OUTPUT: {
access = GENERIC_WRITE | (flag_p? GENERIC_READ : 0);
share = 0;
disp = CREATE_ALWAYS;
if(flag_x) {
disp = CREATE_NEW;
}
} break;
case STREAM_UPDATE: {
access = GENERIC_READ | GENERIC_WRITE;
share = 0;
disp = OPEN_ALWAYS;
} break;
}
*paccess = access;
*pshare = share;
*pdisp = disp;
*pio_mode = io_mode;
*pbt_mode = bt_mode;
return 1;
}
FILE *fopen(const char *restrict name, const char *restrict mode) {
DWORD access;
DWORD share;
DWORD disp;
DWORD flags = FILE_FLAG_WRITE_THROUGH;
stream_io_mode_t io_mode;
stream_bt_mode_t bt_mode;
if(!win_parse_mode(mode, &io_mode, &bt_mode, &access, &share, &disp)) {
return NULL;
}
HANDLE handle = CreateFileA(name, access, share, NULL, disp, flags, NULL);
if(handle == INVALID_HANDLE_VALUE) {
return NULL;
}
FILE *stream = create_stream(handle, io_mode, bt_mode);
void *buffer_data = malloc(BUFSIZ);
stream->buffer = (stream_buffer_t) {1, _IOFBF, BUFSIZ, buffer_data};
return stream;
}
FILE *freopen(const char *restrict name, const char *restrict mode, FILE *restrict stream) {
if(stream == NULL) {
return NULL;
}
fflush(stream);
DWORD access;
DWORD share;
DWORD disp;
DWORD flags = FILE_FLAG_WRITE_THROUGH;
stream_io_mode_t io_mode;
stream_bt_mode_t bt_mode;
if(!win_parse_mode(mode, &io_mode, &bt_mode, &access, &share, &disp)) {
return NULL;
}
if(name == NULL) {
HANDLE handle = ReOpenFile(stream->handle, access, share, flags);
if(handle == INVALID_HANDLE_VALUE) {
return NULL;
}
}
else {
CloseHandle(stream->handle);
HANDLE handle = CreateFileA(name, access, share, NULL, disp, flags, NULL);
if(handle == INVALID_HANDLE_VALUE) {
return NULL;
}
init_stream(stream, handle, io_mode, bt_mode);
}
return stream;
}
FILE *tmpfile(void) {
return NULL;
}
int fclose(FILE *stream) {
if(fflush(stream) == EOF) {
return EOF;
}
if(stream->buffer.is_internal) {
free(stream->buffer.data);
}
if(!CloseHandle(stream->handle)) {
return EOF;
}
return 0;
}
void _close_io() {
while(streams_to_close != NULL) {
FILE *stream = streams_to_close;
fflush(stream);
delete_stream(stream);
}
}
int feof(FILE *stream) {
return stream->eof;
}
int ferror(FILE *stream) {
return stream->err;
}
void clearerr(FILE *stream) {
stream->eof = 0;
stream->err = 0;
}
void perror(char const *s) {
printf("%s: %s\n", s, strerror(errno));
}
int setvbuf(FILE *restrict stream, char *restrict ptr, int mode, size_t size) {
if(mode != _IOFBF && mode != _IOLBF && mode != _IONBF) {
return 1;
}
mtx_lock(&stream->lock);
stream_buffer_t *buffer = &stream->buffer;
buffer->mode = mode;
if(ptr == NULL) {
buffer->data = realloc(buffer->data, size);
buffer->size = size;
}
else {
buffer->data = ptr;
buffer->size = size;
}
mtx_unlock(&stream->lock);
return 0;
}
void setbuf(FILE *restrict stream, char *restrict buf) {
if(buf == NULL) {
setvbuf(stream, NULL, _IONBF, 0);
}
else {
setvbuf(stream, buf, _IOFBF, BUFSIZ);
}
}
int fflush(FILE *stream) {
mtx_lock(&stream->lock);
int res = 0;
stream_buffer_t *buffer = &stream->buffer;
void *data = buffer->data;
size_t size = buffer->written;
DWORD bytes_written;
BOOL ok = WriteFile(stream->handle, data, size, &bytes_written, NULL);
if(!ok) {
res = EOF;
stream->eof = 1;
}
buffer->written = 0;
mtx_unlock(&stream->lock);
return res;
}
int fputc(int c, FILE *stream) {
mtx_lock(&stream->lock);
stream_buffer_t *buffer = &stream->buffer;
int res = c;
if(buffer->mode == _IONBF) {
unsigned char str[1] = {c};
DWORD bytes_written;
BOOL ok = WriteFile(stream->handle, &str, 1, &bytes_written, NULL);
if(!ok) {
res = EOF;
stream->err = 1;
goto cum;
}
}
else {
unsigned char *data = buffer->data;
data[buffer->written++] = (unsigned char)c;
int needs_flush;
needs_flush = (buffer->written == buffer->size);
if(buffer->mode == _IOLBF) {
needs_flush |= (c == '\n');
}
if(needs_flush) {
if(fflush(stream) == EOF) {
goto cum;
}
}
}
cum:
mtx_unlock(&stream->lock);
return res;
}
int fgetc(FILE *stream) {
mtx_lock(&stream->lock);
int res = 0;
stream_buffer_t *buffer = &stream->buffer;
int read_from_disk = 1;
if(buffer->mode != _IONBF) {
unsigned char *data = buffer->data;
if(buffer->written != 0) {
read_from_disk = 0;
res = data[--buffer->written];
}
}
if(read_from_disk) {
unsigned char buf[1];
DWORD bytes_read;
BOOL ok = ReadFile(stream->handle, buf, 1, &bytes_read, NULL);
if(bytes_read != 1) {
res = EOF;
stream->eof = 1;
goto cum;
}
if(!ok) {
res = EOF;
goto cum;
}
res = buf[0];
}
cum:
mtx_unlock(&stream->lock);
return res;
}
int ungetc(int c, FILE *stream) {
mtx_lock(&stream->lock);
int res;
stream_buffer_t *buffer = &stream->buffer;
if(buffer->mode == _IONBF) {
res = EOF;
goto cum;
}
else {
if(c == EOF) {
res = EOF;
goto cum;
}
if(buffer->written == buffer->size) {
res = EOF;
goto cum;
}
unsigned char *data = buffer->data;
data[buffer->written++] = (unsigned char)c;
res = c;
}
cum:
mtx_unlock(&stream->lock);
return 0;
}
int fgetpos(FILE *restrict stream, fpos_t *restrict pos) {
LONG pos_hi = 0;
DWORD pos_lo = SetFilePointer(stream->handle, 0, &pos_hi, FILE_CURRENT);
if(pos_lo == INVALID_SET_FILE_POINTER) {
return 1;
}
int64_t offset = ((int64_t)pos_hi << 32) | (int64_t)pos_lo;
pos->offset = offset;
pos->mbstate = stream->mbstate;
return 0;
}
int fseek(FILE *stream, long int offset, int whence) {
return 0;
}
int fsetpos(FILE *stream, const fpos_t *pos) {
LONG pos_hi = pos->offset >> 32;
LONG pos_lo = (LONG)(pos->offset & 0xffffffff);
DWORD status = SetFilePointer(stream->handle, pos_lo, &pos_hi, FILE_BEGIN);
if(status == INVALID_SET_FILE_POINTER) {
return 1;
}
stream->mbstate = pos->mbstate;
return 0;
}
long int ftell(FILE *stream) {
return 0;
}
void rewind(FILE *stream) {
}
int getchar(void) {
return fgetc(stdin);
}
int putchar(int ch) {
return fputc(ch, stdout);
}
char *fgets(char *restrict str, int count, FILE *restrict stream) {
if(count < 1) {
return str;
}
if(count == 1) {
str[0] = 0;
return str;
}
mtx_lock(&stream->lock);
int i;
for(i = 0; i < count-1; ++i) {
int c = fgetc(stream);
if(c == EOF) {
stream->eof = 1;
if(i == 0) {
return NULL;
}
break;
}
str[i] = c;
if(c == '\n') {
++i;
break;
}
}
str[i] = 0;
mtx_unlock(&stream->lock);
return str;
}
int fputs(char const *str, FILE *stream) {
mtx_lock(&stream->lock);
int res = 0;
while(*str) {
int c = fputc(*str++, stream);
if(c == EOF) {
res = EOF;
break;
}
}
mtx_unlock(&stream->lock);
return res;
}
int puts(char const *str) {
int res = fputs(str, stdout);
if(res == EOF) return EOF;
return putchar('\n');
}
char *gets(char *str) {
return fgets(str, 0x7fffffff, stdin);
}