mirror of https://github.com/flysand7/ciabatta.git
629 lines
15 KiB
C
629 lines
15 KiB
C
|
|
enum FileMode {
|
|
MODE_INPUT,
|
|
MODE_OUTPUT,
|
|
MODE_UPDATE,
|
|
} typedef FileMode;
|
|
|
|
enum FileType {
|
|
FILE_BINARY,
|
|
FILE_TEXT,
|
|
} typedef FileType;
|
|
|
|
struct FileBuffer typedef FileBuffer;
|
|
struct FileBuffer {
|
|
int is_internal;
|
|
int mode;
|
|
size_t size;
|
|
void *data;
|
|
size_t written;
|
|
};
|
|
|
|
struct FILE {
|
|
HANDLE handle;
|
|
mbstate_t mbstate;
|
|
FileBuffer buffer;
|
|
FileMode io_mode;
|
|
FileType bt_mode;
|
|
int eof;
|
|
int err;
|
|
mtx_t lock;
|
|
bool temp;
|
|
char *temp_name;
|
|
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 *_file_tracker = NULL;
|
|
|
|
static void _file_track(FILE *stream) {
|
|
if(_file_tracker != NULL) {
|
|
_file_tracker->next = stream;
|
|
stream->prev = _file_tracker;
|
|
stream->next = NULL;
|
|
_file_tracker = stream;
|
|
}
|
|
else {
|
|
_file_tracker = stream;
|
|
stream->prev = NULL;
|
|
stream->next = NULL;
|
|
}
|
|
}
|
|
|
|
static void _file_untrack(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) _file_tracker = prev;
|
|
}
|
|
|
|
// Multithreaded access
|
|
|
|
static inline void _file_lock_init(FILE *stream) {
|
|
mtx_init(&stream->lock, mtx_plain);
|
|
}
|
|
|
|
static inline void _file_lock_destroy(FILE *stream) {
|
|
mtx_destroy(&stream->lock);
|
|
}
|
|
|
|
static inline void _file_lock(FILE *stream) {
|
|
mtx_lock(&stream->lock);
|
|
}
|
|
|
|
static inline void _file_unlock(FILE *stream) {
|
|
mtx_unlock(&stream->lock);
|
|
}
|
|
|
|
// Managing file handles
|
|
|
|
static inline FILE *_file_create(HANDLE handle, FileMode io_mode, FileType bt_mode) {
|
|
FILE *stream = calloc(1, sizeof(FILE));
|
|
if(stream == NULL) {
|
|
return NULL;
|
|
}
|
|
stream->handle = handle;
|
|
stream->io_mode = io_mode;
|
|
stream->bt_mode = bt_mode;
|
|
_file_lock_init(stream);
|
|
_file_track(stream);
|
|
return stream;
|
|
}
|
|
|
|
static inline void _file_close(FILE *stream) {
|
|
_file_lock(stream);
|
|
CloseHandle(stream->handle);
|
|
_file_untrack(stream);
|
|
_file_unlock(stream);
|
|
_file_lock_destroy(stream);
|
|
free(stream);
|
|
}
|
|
|
|
static void _io_init() {
|
|
HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
HANDLE hstderr = GetStdHandle(STD_ERROR_HANDLE);
|
|
HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
|
|
|
|
stdout = _file_create(hstdout, MODE_UPDATE, FILE_TEXT);
|
|
stderr = _file_create(hstderr, MODE_UPDATE, FILE_TEXT);
|
|
stdin = _file_create(hstdin, MODE_INPUT, FILE_BINARY);
|
|
|
|
char *in_buf = calloc(BUFSIZ, sizeof(char));
|
|
char *out_buf = calloc(BUFSIZ, sizeof(char));
|
|
stdin->buffer = (FileBuffer){1, _IOLBF, BUFSIZ, in_buf};
|
|
stdout->buffer = (FileBuffer){1, _IOLBF, BUFSIZ, out_buf};
|
|
stderr->buffer = (FileBuffer){1, _IONBF, 0, NULL};
|
|
|
|
SetConsoleCP(CP_UTF8); // Maybe it will work someday
|
|
SetConsoleOutputCP(CP_UTF8);
|
|
}
|
|
|
|
static void _io_close() {
|
|
while(_file_tracker != NULL) {
|
|
FILE *stream = _file_tracker;
|
|
fclose(stream);
|
|
_file_tracker = _file_tracker->next;
|
|
}
|
|
}
|
|
|
|
// File mode parsing
|
|
|
|
struct WindowsMode typedef WindowsMode;
|
|
struct WindowsMode {
|
|
DWORD access;
|
|
DWORD share;
|
|
DWORD disp;
|
|
};
|
|
|
|
static int _mode_parse(char const *mode, FileMode *pio_mode, FileType *pbt_mode, WindowsMode *win_mode) {
|
|
DWORD access = 0;
|
|
DWORD share = 0;
|
|
DWORD disp = 0;
|
|
FileMode io_mode = 0;
|
|
FileType bt_mode = 0;
|
|
int flag_p = 0;
|
|
int flag_b = 0;
|
|
int flag_x = 0;
|
|
switch(*mode++) {
|
|
case 'r': io_mode = MODE_INPUT; break;
|
|
case 'w': io_mode = MODE_OUTPUT; break;
|
|
case 'a': io_mode = MODE_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? FILE_BINARY : FILE_TEXT;
|
|
switch(io_mode) {
|
|
case MODE_INPUT: {
|
|
access = GENERIC_READ | (flag_p? GENERIC_WRITE : 0);
|
|
share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
|
|
disp = OPEN_EXISTING;
|
|
} break;
|
|
case MODE_OUTPUT: {
|
|
access = GENERIC_WRITE | (flag_p? GENERIC_READ : 0);
|
|
share = 0;
|
|
disp = CREATE_ALWAYS;
|
|
if(flag_x) {
|
|
disp = CREATE_NEW;
|
|
}
|
|
} break;
|
|
case MODE_UPDATE: {
|
|
access = GENERIC_READ | GENERIC_WRITE;
|
|
share = 0;
|
|
disp = OPEN_ALWAYS;
|
|
} break;
|
|
}
|
|
win_mode->access = access;
|
|
win_mode->share = share;
|
|
win_mode->disp = disp;
|
|
*pio_mode = io_mode;
|
|
*pbt_mode = bt_mode;
|
|
return 1;
|
|
}
|
|
|
|
// Operations on files
|
|
|
|
int remove(const char *filename) {
|
|
BOOL ok = DeleteFileA(filename);
|
|
return !ok;
|
|
}
|
|
|
|
int rename(const char *old, const char *new) {
|
|
BOOL ok = MoveFileA(old, new);
|
|
return !ok;
|
|
}
|
|
|
|
// Opening/closing files
|
|
|
|
char *tmpnam(char *s) {
|
|
static char internal_buffer[L_tmpnam];
|
|
char *buffer;
|
|
if(s != NULL) {
|
|
buffer = s;
|
|
}
|
|
else {
|
|
buffer = &internal_buffer[0];
|
|
}
|
|
UINT num = GetTempFileNameA(".", ".t_", 0, buffer);
|
|
if(num == 0) {
|
|
return NULL;
|
|
}
|
|
else {
|
|
return buffer;
|
|
}
|
|
}
|
|
|
|
FILE *tmpfile(void) {
|
|
char *file_name = malloc(L_tmpnam);
|
|
UINT num = GetTempFileNameA(".", ".t_", 0, &file_name[0]);
|
|
if(!num) {
|
|
return NULL;
|
|
}
|
|
FILE *tmp_file = fopen(&file_name[0], "wb+");
|
|
tmp_file->temp = true;
|
|
tmp_file->temp_name = file_name;
|
|
return tmp_file;
|
|
}
|
|
|
|
FILE *fopen(const char *restrict name, const char *restrict mode) {
|
|
WindowsMode win_mode;
|
|
DWORD flags = FILE_FLAG_WRITE_THROUGH;
|
|
FileMode io_mode;
|
|
FileType bt_mode;
|
|
if(!_mode_parse(mode, &io_mode, &bt_mode, &win_mode)) {
|
|
return NULL;
|
|
}
|
|
DWORD access = win_mode.access;
|
|
DWORD share = win_mode.share;
|
|
DWORD disp = win_mode.disp;
|
|
HANDLE handle = CreateFileA(name, access, share, NULL, disp, flags, NULL);
|
|
if(handle == INVALID_HANDLE_VALUE) {
|
|
return NULL;
|
|
}
|
|
FILE *stream = _file_create(handle, io_mode, bt_mode);
|
|
void *buffer_data = malloc(BUFSIZ);
|
|
stream->buffer = (FileBuffer) {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);
|
|
WindowsMode win_mode;
|
|
DWORD flags = FILE_FLAG_WRITE_THROUGH;
|
|
FileMode io_mode;
|
|
FileType bt_mode;
|
|
if(!_mode_parse(mode, &io_mode, &bt_mode, &win_mode)) {
|
|
return NULL;
|
|
}
|
|
DWORD access = win_mode.access;
|
|
DWORD share = win_mode.share;
|
|
DWORD disp = win_mode.disp;
|
|
if(name == NULL) {
|
|
// This codepath doesn't work
|
|
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;
|
|
}
|
|
*stream = (FILE) {0};
|
|
stream->handle = handle;
|
|
stream->io_mode = io_mode;
|
|
stream->bt_mode = bt_mode;
|
|
}
|
|
return stream;
|
|
}
|
|
|
|
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;
|
|
}
|
|
if(stream->temp) {
|
|
BOOL ok = DeleteFileA(stream->temp_name);
|
|
free(stream->temp_name);
|
|
if(!ok) {
|
|
return EOF;
|
|
}
|
|
}
|
|
_file_untrack(stream);
|
|
return 0;
|
|
}
|
|
|
|
// Buffering
|
|
|
|
int setvbuf(FILE *restrict stream, char *restrict ptr, int mode, size_t size) {
|
|
if(mode != _IOFBF && mode != _IOLBF && mode != _IONBF) {
|
|
return 1;
|
|
}
|
|
_file_lock(stream);
|
|
FileBuffer *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;
|
|
}
|
|
_file_unlock(stream);
|
|
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) {
|
|
_file_lock(stream);
|
|
int res = 0;
|
|
FileBuffer *buffer = &stream->buffer;
|
|
if(buffer->written != 0) {
|
|
size_t size = buffer->written;
|
|
void *data = buffer->data;
|
|
DWORD bytes_written;
|
|
BOOL ok = WriteFile(stream->handle, data, size, &bytes_written, NULL);
|
|
if(!ok) {
|
|
res = EOF;
|
|
stream->eof = 1;
|
|
}
|
|
buffer->written = 0;
|
|
}
|
|
_file_unlock(stream);
|
|
return res;
|
|
}
|
|
|
|
// Errors
|
|
|
|
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));
|
|
}
|
|
|
|
// File reads/writes
|
|
|
|
int fputc(int c, FILE *stream) {
|
|
_file_lock(stream);
|
|
FileBuffer *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 end;
|
|
}
|
|
|
|
}
|
|
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 end;
|
|
}
|
|
}
|
|
}
|
|
end:
|
|
_file_unlock(stream);
|
|
return res;
|
|
}
|
|
|
|
int fgetc(FILE *stream) {
|
|
_file_lock(stream);
|
|
int res = 0;
|
|
FileBuffer *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 end;
|
|
}
|
|
if(!ok) {
|
|
res = EOF;
|
|
goto end;
|
|
}
|
|
res = buf[0];
|
|
}
|
|
end:
|
|
_file_unlock(stream);
|
|
return res;
|
|
}
|
|
|
|
int ungetc(int c, FILE *stream) {
|
|
_file_lock(stream);
|
|
int res;
|
|
FileBuffer *buffer = &stream->buffer;
|
|
if(buffer->mode == _IONBF) {
|
|
res = EOF;
|
|
goto end;
|
|
}
|
|
else {
|
|
if(c == EOF) {
|
|
res = EOF;
|
|
goto end;
|
|
}
|
|
if(buffer->written == buffer->size) {
|
|
res = EOF;
|
|
goto end;
|
|
}
|
|
unsigned char *data = buffer->data;
|
|
data[buffer->written++] = (unsigned char)c;
|
|
res = c;
|
|
}
|
|
end:
|
|
_file_unlock(stream);
|
|
return res;
|
|
}
|
|
|
|
size_t fwrite(void const *restrict buffer, size_t size, size_t count, FILE *restrict stream) {
|
|
_file_lock(stream);
|
|
unsigned char const *bytes = (unsigned char const*)buffer;
|
|
size_t num_written;
|
|
for(num_written = 0; num_written < count; ++num_written) {
|
|
for(size_t bytes_written = 0; bytes_written < size; ++bytes_written) {
|
|
int ch = fputc(bytes[bytes_written], stream);
|
|
if(ch == EOF) {
|
|
goto end;
|
|
}
|
|
}
|
|
bytes += size;
|
|
}
|
|
end:
|
|
_file_unlock(stream);
|
|
return num_written;
|
|
}
|
|
|
|
size_t fread(void *restrict buffer, size_t size, size_t count, FILE *restrict stream) {
|
|
_file_lock(stream);
|
|
unsigned char *bytes = (unsigned char *)buffer;
|
|
size_t num_read;
|
|
for(num_read = 0; num_read < count; ++num_read) {
|
|
for(size_t bytes_read = 0; bytes_read < size; ++bytes_read) {
|
|
int ch = fgetc(stream);
|
|
if(ch == EOF) {
|
|
goto end;
|
|
}
|
|
bytes[bytes_read] = ch;
|
|
}
|
|
bytes += size;
|
|
}
|
|
end:
|
|
_file_unlock(stream);
|
|
return num_read;
|
|
}
|
|
|
|
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;
|
|
}
|
|
_file_lock(stream);
|
|
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;
|
|
_file_unlock(stream);
|
|
return str;
|
|
}
|
|
|
|
int fputs(char const *str, FILE *stream) {
|
|
_file_lock(stream);
|
|
int res = 0;
|
|
while(*str) {
|
|
int c = fputc(*str++, stream);
|
|
if(c == EOF) {
|
|
res = EOF;
|
|
break;
|
|
}
|
|
}
|
|
_file_unlock(stream);
|
|
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);
|
|
}
|
|
|
|
// File positioning:
|
|
|
|
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.leftover = stream->mbstate.leftover;
|
|
pos->mbstate.high_surrogate = stream->mbstate.high_surrogate;
|
|
return 0;
|
|
}
|
|
|
|
int fseek(FILE *stream, long int offset, int whence) {
|
|
// Note(bumbread): the SEEK_SET, SEEK_CUR and SEEK_END are defined to match
|
|
// the Windows constants, so no conversion is requierd between them.
|
|
DWORD pos_lo = SetFilePointer(stream->handle, offset, NULL, whence);
|
|
if(pos_lo == INVALID_SET_FILE_POINTER) {
|
|
return -1L;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int fsetpos(FILE *stream, const fpos_t *pos) {
|
|
LONG pos_lo = (LONG)(pos->offset & 0xffffffff);
|
|
DWORD status = SetFilePointer(stream->handle, pos_lo, NULL, FILE_BEGIN);
|
|
if(status == INVALID_SET_FILE_POINTER) {
|
|
return 1;
|
|
}
|
|
stream->mbstate.leftover = pos->mbstate.leftover;
|
|
stream->mbstate.high_surrogate = pos->mbstate.high_surrogate;
|
|
return 0;
|
|
}
|
|
|
|
long int ftell(FILE *stream) {
|
|
LONG pos_hi = 0;
|
|
DWORD pos_lo = SetFilePointer(stream->handle, 0, &pos_hi, FILE_CURRENT);
|
|
if(pos_lo == INVALID_SET_FILE_POINTER) {
|
|
return -1L;
|
|
}
|
|
int64_t offset = ((int64_t)pos_hi << 32) | (int64_t)pos_lo;
|
|
return offset;
|
|
}
|
|
|
|
void rewind(FILE *stream) {
|
|
fseek(stream, 0, SEEK_SET);
|
|
}
|