/* * io.c * * Copyright (C) 2017 Aleksandar Andrejevic * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include #include #include "io_priv.h" static list_entry_t open_files; FILE *stdin = NULL; FILE *stdout = NULL; FILE *stderr = NULL; int fileno_unlocked(FILE *stream) { if (stream->fd == -1) errno = EBADF; return stream->fd; } int fileno(FILE *stream) { syscall_wait_mutex(stream->mutex, NO_TIMEOUT); int ret = fileno_unlocked(stream); syscall_release_mutex(stream->mutex); return ret; } static int __crt_fflush_stream(FILE *stream) { if (!stream->buffer) return EOF; if (!(stream->flags & FILE_BUFFER_DIR)) { stream->buffer_start = stream->buffer_end = 0; stream->flags &= ~FILE_BUFFER_FULL; return 0; } if (stream->fd == -1) return 0; if (stream->buffer_start < stream->buffer_end) { ssize_t written = write(stream->fd, &stream->buffer[stream->buffer_start], stream->buffer_end - stream->buffer_start); if (written < 0) return EOF; stream->buffer_start += written; stream->buffer_start %= stream->buffer_size; if (written) stream->flags &= ~FILE_BUFFER_FULL; } else { if (stream->buffer_start < stream->buffer_size) { ssize_t written = write(stream->fd, &stream->buffer[stream->buffer_start], stream->buffer_size - stream->buffer_start); if (written < 0) return EOF; stream->buffer_start += written; stream->buffer_start %= stream->buffer_size; if (written) stream->flags &= ~FILE_BUFFER_FULL; } if (stream->buffer_end) { ssize_t written = write(stream->fd, stream->buffer, stream->buffer_end); if (written < 0) return EOF; stream->buffer_start += written; stream->buffer_start %= stream->buffer_size; if (written) stream->flags &= ~FILE_BUFFER_FULL; } } return 0; } int fflush_unlocked(FILE *stream) { if (stream) return __crt_fflush_stream(stream); list_entry_t *entry; int result = 0; for (entry = open_files.next; entry != &open_files; entry = entry->next) { if (__crt_fflush_stream(CONTAINER_OF(entry, struct __crt_file, link)) == EOF) { result = EOF; } } return result; } int fflush(FILE *stream) { syscall_wait_mutex(stream->mutex, NO_TIMEOUT); int ret = fflush_unlocked(stream); syscall_release_mutex(stream->mutex); return ret; } int setvbuf(FILE *stream, char *buf, int mode, size_t size) { int ret = EOF; syscall_wait_mutex(stream->mutex, NO_TIMEOUT); switch (mode) { case _IONBF: if (fflush_unlocked(stream) == 0) { stream->buffer = NULL; stream->buffer_size = 0; ret = 0; } break; case _IOLBF: if (!buf || fflush_unlocked(stream) == 0) { if (buf) { stream->buffer = buf; stream->buffer_size = size; } stream->flags |= FILE_BUFFER_LINE; ret = 0; } break; case _IOFBF: if (!buf || fflush_unlocked(stream) == 0) { if (buf) { stream->buffer = buf; stream->buffer_size = size; } stream->flags &= ~FILE_BUFFER_LINE; ret = 0; } break; } syscall_release_mutex(stream->mutex); return ret; } 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); } inline int fgetc_unlocked(FILE *stream) { char c; if (!(stream->flags & FILE_READ)) { errno = EPERM; return EOF; } if (stream->buffer) { if (stream->flags & FILE_BUFFER_DIR) { fflush_unlocked(stream); stream->flags &= ~FILE_BUFFER_DIR; } if (stream->buffer_start == stream->buffer_end && !(stream->flags & FILE_BUFFER_FULL)) { if (stream->fd == -1) return EOF; if (stream->buffer_end < stream->buffer_size) { ssize_t amount = read(stream->fd, &stream->buffer[stream->buffer_end], stream->buffer_size - stream->buffer_end); if (amount < 0) return EOF; stream->buffer_end += amount; stream->buffer_end %= stream->buffer_size; if (amount && (stream->buffer_start == stream->buffer_end)) stream->flags |= FILE_BUFFER_FULL; } if (stream->buffer_end < stream->buffer_start) { ssize_t amount = read(stream->fd, &stream->buffer[stream->buffer_end], stream->buffer_start - stream->buffer_end); if (amount < 0) return EOF; stream->buffer_end += amount; stream->buffer_end %= stream->buffer_size; if (amount && (stream->buffer_start == stream->buffer_end)) stream->flags |= FILE_BUFFER_FULL; } } c = stream->buffer[stream->buffer_start]; stream->buffer_start++; stream->buffer_end %= stream->buffer_size; stream->flags &= ~FILE_BUFFER_FULL; } else { if (read(stream->fd, &c, 1) != 1) c = EOF; } return (int)((unsigned int)c); } int fgetc(FILE *stream) { syscall_wait_mutex(stream->mutex, NO_TIMEOUT); int ret = fgetc_unlocked(stream); syscall_release_mutex(stream->mutex); return ret; } inline int fputc_unlocked(int c, FILE *stream) { if (!(stream->flags & FILE_WRITE)) { errno = EPERM; return EOF; } if (stream->buffer) { if (!(stream->flags & FILE_BUFFER_DIR)) { fflush_unlocked(stream); stream->flags |= FILE_BUFFER_DIR; } if (stream->flags & FILE_BUFFER_FULL) { errno = ENOSPC; return EOF; } stream->buffer[stream->buffer_end] = (uint8_t)c; stream->buffer_end++; stream->buffer_end %= stream->buffer_size; if (stream->buffer_start == stream->buffer_end) { stream->flags |= FILE_BUFFER_FULL; } if (stream->fd != -1 && ((stream->flags & FILE_BUFFER_FULL) || ((stream->flags & FILE_BUFFER_LINE) && (char)c == '\n'))) { fflush_unlocked(stream); } } else { if (write(stream->fd, &c, 1) != 1) return EOF; } return 0; } int fputc(int c, FILE *stream) { syscall_wait_mutex(stream->mutex, NO_TIMEOUT); int ret = fputc_unlocked(c, stream); syscall_release_mutex(stream->mutex); return ret; } char *fgets_unlocked(char *s, int size, FILE *stream) { int c; char *ptr = s; while (size-- > 0 && (c = fgetc_unlocked(stream)) != EOF) { *ptr++ = (char)c; if (c == '\n') break; } return (s != ptr) ? s : NULL; } char *fgets(char *s, int size, FILE *stream) { syscall_wait_mutex(stream->mutex, NO_TIMEOUT); char *ret = fgets_unlocked(s, size, stream); syscall_release_mutex(stream->mutex); return ret; } int fputs_unlocked(const char *s, FILE *stream) { const char *ptr = s; while (*ptr) if (fputc_unlocked(*ptr++, stream) == EOF) return EOF; return 0; } int fputs(const char *s, FILE *stream) { syscall_wait_mutex(stream->mutex, NO_TIMEOUT); int ret = fputs_unlocked(s, stream); syscall_release_mutex(stream->mutex); return ret; } int ungetc(int c, FILE *stream) { syscall_wait_mutex(stream->mutex, NO_TIMEOUT); if (stream->flags & FILE_BUFFER_DIR) { fflush_unlocked(stream); stream->flags &= ~FILE_BUFFER_DIR; } if (stream->buffer_start == 0) stream->buffer_start = stream->buffer_size; stream->buffer_start--; stream->buffer[stream->buffer_start] = (uint8_t)c; syscall_release_mutex(stream->mutex); return c; } char *gets(char *s) { char *ptr = s; int c; while ((c = getchar()) != EOF) *ptr++ = (char)c; return ptr != s ? s : NULL; } int puts(const char *s) { if (fputs(s, stdout) == EOF) return EOF; if (fputc('\n', stdout) == EOF) return EOF; return 0; } FILE *fdopen(int fd, const char *mode) { FILE *stream = (FILE*)malloc(sizeof(FILE) + BUFSIZ); if (stream == NULL) { errno = ENOMEM; return NULL; } sysret_t status = syscall_create_mutex(NULL, TRUE, &stream->mutex); if (status != ERR_SUCCESS) { free(stream); errno = __crt_translate_error(status); return NULL; } stream->flags = 0; const char *ptr; for (ptr = mode; *ptr; ptr++) { switch (*ptr) { case 'r': stream->flags |= FILE_READ; break; case 'w': stream->flags |= FILE_WRITE; break; case 'a': stream->flags |= FILE_APPEND; break; case '+': stream->flags |= FILE_READ | FILE_WRITE; break; } } stream->fd = fd; char *buffer = (char*)((uintptr_t)stream + sizeof(FILE)); setbuf(stream, buffer); return stream; } FILE *fmemopen(void *buf, size_t size, const char *mode) { FILE *stream = (FILE*)malloc(sizeof(FILE)); if (stream == NULL) { errno = ENOMEM; return NULL; } sysret_t status = syscall_create_mutex(NULL, TRUE, &stream->mutex); if (status != ERR_SUCCESS) { free(stream); errno = __crt_translate_error(status); return NULL; } stream->flags = 0; const char *ptr; for (ptr = mode; *ptr; ptr++) { switch (*ptr) { case 'r': stream->flags |= FILE_READ; break; case 'w': stream->flags |= FILE_WRITE; break; case 'a': stream->flags |= FILE_APPEND; break; case '+': stream->flags |= FILE_READ | FILE_WRITE; break; } } stream->fd = -1; stream->buffer = buf; stream->buffer_size = size; stream->buffer_start = stream->buffer_end = 0; list_append(&open_files, &stream->link); return stream; } FILE *fopen(const char *pathname, const char *mode) { int open_flags = 0; const char *ptr; for (ptr = mode; *ptr; ptr++) { switch (*ptr) { case 'r': open_flags = (open_flags & ~3) | O_RDONLY; break; case 'w': open_flags = (open_flags & ~3) | O_WRONLY; break; case 'a': open_flags |= O_APPEND; break; case '+': open_flags = (open_flags & ~3) | O_RDWR; break; } } int fd = open(pathname, open_flags); if (fd < 0) return NULL; FILE *stream = fdopen(fd, mode); if (!stream) close(fd); return NULL; } int fclose(FILE *stream) { fflush(stream); list_remove(&stream->link); syscall_wait_mutex(stream->mutex, NO_TIMEOUT); if (stream->fd) close(stream->fd); syscall_close_object(stream->mutex); free(stream); return 0; }