30,98 → 30,551 |
* @{ |
*/ |
/** @file |
*/ |
*/ |
|
#include <libc.h> |
#include <stdio.h> |
#include <unistd.h> |
#include <stdio.h> |
#include <io/io.h> |
#include <fcntl.h> |
#include <assert.h> |
#include <string.h> |
#include <errno.h> |
#include <console.h> |
#include <bool.h> |
#include <malloc.h> |
#include <io/klog.h> |
#include <vfs/vfs.h> |
#include <ipc/devmap.h> |
#include <adt/list.h> |
|
const static char nl = '\n'; |
static void _fflushbuf(FILE *stream); |
|
int puts(const char *str) |
static FILE stdin_null = { |
.fd = -1, |
.error = true, |
.eof = true, |
.klog = false, |
.phone = -1, |
.btype = _IONBF, |
.buf = NULL, |
.buf_size = 0, |
.buf_head = NULL |
}; |
|
static FILE stdout_klog = { |
.fd = -1, |
.error = false, |
.eof = false, |
.klog = true, |
.phone = -1, |
.btype = _IOLBF, |
.buf = NULL, |
.buf_size = BUFSIZ, |
.buf_head = NULL |
}; |
|
static FILE stderr_klog = { |
.fd = -1, |
.error = false, |
.eof = false, |
.klog = true, |
.phone = -1, |
.btype = _IONBF, |
.buf = NULL, |
.buf_size = 0, |
.buf_head = NULL |
}; |
|
FILE *stdin = NULL; |
FILE *stdout = NULL; |
FILE *stderr = NULL; |
|
static LIST_INITIALIZE(files); |
|
void stdio_init(int filc, fdi_node_t *filv[]) |
{ |
size_t count; |
if (filc > 0) { |
stdin = fopen_node(filv[0], "r"); |
} else { |
stdin = &stdin_null; |
list_append(&stdin->link, &files); |
} |
|
if (str == NULL) |
return putnchars("(NULL)", 6); |
if (filc > 1) { |
stdout = fopen_node(filv[1], "w"); |
} else { |
stdout = &stdout_klog; |
list_append(&stdout->link, &files); |
} |
|
for (count = 0; str[count] != 0; count++); |
if (filc > 2) { |
stderr = fopen_node(filv[2], "w"); |
} else { |
stderr = &stderr_klog; |
list_append(&stderr->link, &files); |
} |
} |
|
void stdio_done(void) |
{ |
link_t *link = files.next; |
|
if (console_write((void *) str, count) == count) { |
if (console_write(&nl, 1) == 1) |
return 0; |
while (link != &files) { |
FILE *file = list_get_instance(link, FILE, link); |
fclose(file); |
link = files.next; |
} |
} |
|
static bool parse_mode(const char *mode, int *flags) |
{ |
/* Parse mode except first character. */ |
const char *mp = mode; |
if (*mp++ == 0) { |
errno = EINVAL; |
return false; |
} |
|
return EOF; |
if ((*mp == 'b') || (*mp == 't')) |
mp++; |
|
bool plus; |
if (*mp == '+') { |
mp++; |
plus = true; |
} else |
plus = false; |
|
if (*mp != 0) { |
errno = EINVAL; |
return false; |
} |
|
/* Parse first character of mode and determine flags for open(). */ |
switch (mode[0]) { |
case 'r': |
*flags = plus ? O_RDWR : O_RDONLY; |
break; |
case 'w': |
*flags = (O_TRUNC | O_CREAT) | (plus ? O_RDWR : O_WRONLY); |
break; |
case 'a': |
/* TODO: a+ must read from beginning, append to the end. */ |
if (plus) { |
errno = ENOTSUP; |
return false; |
} |
*flags = (O_APPEND | O_CREAT) | (plus ? O_RDWR : O_WRONLY); |
default: |
errno = EINVAL; |
return false; |
} |
|
return true; |
} |
|
/** Put count chars from buffer to stdout without adding newline |
* @param buf Buffer with size at least count bytes - NULL pointer NOT allowed! |
* @param count |
* @return 0 on succes, EOF on fail |
/** Set stream buffer. */ |
void setvbuf(FILE *stream, void *buf, int mode, size_t size) |
{ |
stream->btype = mode; |
stream->buf = buf; |
stream->buf_size = size; |
stream->buf_head = stream->buf; |
} |
|
/** Allocate stream buffer. */ |
static int _fallocbuf(FILE *stream) |
{ |
assert(stream->buf == NULL); |
|
stream->buf = malloc(stream->buf_size); |
if (stream->buf == NULL) { |
errno = ENOMEM; |
return -1; |
} |
|
stream->buf_head = stream->buf; |
return 0; |
} |
|
/** Open a stream. |
* |
* @param path Path of the file to open. |
* @param mode Mode string, (r|w|a)[b|t][+]. |
* |
*/ |
int putnchars(const char *buf, size_t count) |
FILE *fopen(const char *path, const char *mode) |
{ |
if (console_write((void *) buf, count) == count) |
return 0; |
int flags; |
if (!parse_mode(mode, &flags)) |
return NULL; |
|
return EOF; |
/* Open file. */ |
FILE *stream = malloc(sizeof(FILE)); |
if (stream == NULL) { |
errno = ENOMEM; |
return NULL; |
} |
|
stream->fd = open(path, flags, 0666); |
if (stream->fd < 0) { |
/* errno was set by open() */ |
free(stream); |
return NULL; |
} |
|
stream->error = false; |
stream->eof = false; |
stream->klog = false; |
stream->phone = -1; |
|
/* FIXME: Should select buffering type based on what was opened. */ |
setvbuf(stream, NULL, _IOFBF, BUFSIZ); |
|
list_append(&stream->link, &files); |
|
return stream; |
} |
|
/** Same as puts, but does not print newline at end |
FILE *fdopen(int fd, const char *mode) |
{ |
/* Open file. */ |
FILE *stream = malloc(sizeof(FILE)); |
if (stream == NULL) { |
errno = ENOMEM; |
return NULL; |
} |
|
stream->fd = fd; |
stream->error = false; |
stream->eof = false; |
stream->klog = false; |
stream->phone = -1; |
|
/* FIXME: Should select buffering type based on what was opened. */ |
setvbuf(stream, NULL, _IOLBF, BUFSIZ); |
|
list_append(&stream->link, &files); |
|
return stream; |
} |
|
FILE *fopen_node(fdi_node_t *node, const char *mode) |
{ |
int flags; |
if (!parse_mode(mode, &flags)) |
return NULL; |
|
/* Open file. */ |
FILE *stream = malloc(sizeof(FILE)); |
if (stream == NULL) { |
errno = ENOMEM; |
return NULL; |
} |
|
stream->fd = open_node(node, flags); |
if (stream->fd < 0) { |
/* errno was set by open_node() */ |
free(stream); |
return NULL; |
} |
|
stream->error = false; |
stream->eof = false; |
stream->klog = false; |
stream->phone = -1; |
|
/* FIXME: Should select buffering type based on what was opened. */ |
setvbuf(stream, NULL, _IOLBF, BUFSIZ); |
|
list_append(&stream->link, &files); |
|
return stream; |
} |
|
int fclose(FILE *stream) |
{ |
int rc = 0; |
|
fflush(stream); |
|
if (stream->phone >= 0) |
ipc_hangup(stream->phone); |
|
if (stream->fd >= 0) |
rc = close(stream->fd); |
|
list_remove(&stream->link); |
|
if ((stream != &stdin_null) |
&& (stream != &stdout_klog) |
&& (stream != &stderr_klog)) |
free(stream); |
|
stream = NULL; |
|
if (rc != 0) { |
/* errno was set by close() */ |
return EOF; |
} |
|
return 0; |
} |
|
/** Read from a stream. |
* |
* @param buf Destination buffer. |
* @param size Size of each record. |
* @param nmemb Number of records to read. |
* @param stream Pointer to the stream. |
* |
*/ |
int putstr(const char *str) |
size_t fread(void *buf, size_t size, size_t nmemb, FILE *stream) |
{ |
size_t count; |
size_t left = size * nmemb; |
size_t done = 0; |
|
/* Make sure no data is pending write. */ |
_fflushbuf(stream); |
|
while ((left > 0) && (!stream->error) && (!stream->eof)) { |
ssize_t rd = read(stream->fd, buf + done, left); |
|
if (rd < 0) |
stream->error = true; |
else if (rd == 0) |
stream->eof = true; |
else { |
left -= rd; |
done += rd; |
} |
} |
|
if (str == NULL) |
return putnchars("(NULL)", 6); |
return (done / size); |
} |
|
for (count = 0; str[count] != 0; count++); |
if (console_write((void *) str, count) == count) |
return 0; |
static size_t _fwrite(const void *buf, size_t size, size_t nmemb, FILE *stream) |
{ |
size_t left = size * nmemb; |
size_t done = 0; |
|
return EOF; |
while ((left > 0) && (!stream->error)) { |
ssize_t wr; |
|
if (stream->klog) |
wr = klog_write(buf + done, left); |
else |
wr = write(stream->fd, buf + done, left); |
|
if (wr <= 0) |
stream->error = true; |
else { |
left -= wr; |
done += wr; |
} |
} |
|
return (done / size); |
} |
|
int putchar(int c) |
/** Drain stream buffer, do not sync stream. */ |
static void _fflushbuf(FILE *stream) |
{ |
char buf[STR_BOUNDS(1)]; |
size_t offs; |
size_t bytes_used; |
|
offs = 0; |
if (chr_encode(c, buf, &offs, STR_BOUNDS(1)) != EOK) |
return EOF; |
if (!stream->buf || stream->btype == _IONBF || stream->error) |
return; |
|
if (console_write((void *) buf, offs) == offs) |
return c; |
bytes_used = stream->buf_head - stream->buf; |
if (bytes_used == 0) |
return; |
|
return EOF; |
(void) _fwrite(stream->buf, 1, bytes_used, stream); |
stream->buf_head = stream->buf; |
} |
|
int getchar(void) |
/** Write to a stream. |
* |
* @param buf Source buffer. |
* @param size Size of each record. |
* @param nmemb Number of records to write. |
* @param stream Pointer to the stream. |
* |
*/ |
size_t fwrite(const void *buf, size_t size, size_t nmemb, FILE *stream) |
{ |
unsigned char c; |
uint8_t *data; |
size_t bytes_left; |
size_t now; |
size_t buf_free; |
size_t total_written; |
size_t i; |
uint8_t b; |
bool need_flush; |
|
/* If not buffered stream, write out directly. */ |
if (stream->btype == _IONBF) |
return _fwrite(buf, size, nmemb, stream); |
|
/* Perform lazy allocation of stream buffer. */ |
if (stream->buf == NULL) { |
if (_fallocbuf(stream) != 0) |
return 0; /* Errno set by _fallocbuf(). */ |
} |
|
data = (uint8_t *) buf; |
bytes_left = size * nmemb; |
total_written = 0; |
need_flush = false; |
|
while (!stream->error && bytes_left > 0) { |
|
buf_free = stream->buf_size - (stream->buf_head - stream->buf); |
if (bytes_left > buf_free) |
now = buf_free; |
else |
now = bytes_left; |
|
for (i = 0; i < now; i++) { |
b = data[i]; |
stream->buf_head[i] = b; |
|
if (b == '\n' && stream->btype == _IOLBF) |
need_flush = true; |
} |
|
buf += now; |
stream->buf_head += now; |
buf_free -= now; |
bytes_left -= now; |
total_written += now; |
|
if (buf_free == 0) { |
/* Only need to drain buffer. */ |
_fflushbuf(stream); |
need_flush = false; |
} |
} |
|
if (need_flush) |
fflush(stream); |
|
return (total_written / size); |
} |
|
int fputc(wchar_t c, FILE *stream) |
{ |
char buf[STR_BOUNDS(1)]; |
size_t sz = 0; |
|
console_flush(); |
if (read_stdin((void *) &c, 1) == 1) |
return c; |
if (chr_encode(c, buf, &sz, STR_BOUNDS(1)) == EOK) { |
size_t wr = fwrite(buf, sz, 1, stream); |
|
if (wr < sz) |
return EOF; |
|
return (int) c; |
} |
|
return EOF; |
} |
|
int fflush(FILE *f) |
int putchar(wchar_t c) |
{ |
/* Dummy implementation */ |
(void) f; |
console_flush(); |
return fputc(c, stdout); |
} |
|
int fputs(const char *str, FILE *stream) |
{ |
return fwrite(str, str_size(str), 1, stream); |
} |
|
int puts(const char *str) |
{ |
return fputs(str, stdout); |
} |
|
int fgetc(FILE *stream) |
{ |
char c; |
|
/* This could be made faster by only flushing when needed. */ |
if (stdout) |
fflush(stdout); |
if (stderr) |
fflush(stderr); |
|
if (fread(&c, sizeof(char), 1, stream) < sizeof(char)) |
return EOF; |
|
return (int) c; |
} |
|
int getchar(void) |
{ |
return fgetc(stdin); |
} |
|
int fseek(FILE *stream, long offset, int origin) |
{ |
off_t rc = lseek(stream->fd, offset, origin); |
if (rc == (off_t) (-1)) { |
/* errno has been set by lseek. */ |
return -1; |
} |
|
stream->eof = false; |
|
return 0; |
} |
|
void rewind(FILE *stream) |
{ |
(void) fseek(stream, 0, SEEK_SET); |
} |
|
int fflush(FILE *stream) |
{ |
_fflushbuf(stream); |
|
if (stream->klog) { |
klog_update(); |
return EOK; |
} |
|
if (stream->fd >= 0) |
return fsync(stream->fd); |
|
return ENOENT; |
} |
|
int feof(FILE *stream) |
{ |
return stream->eof; |
} |
|
int ferror(FILE *stream) |
{ |
return stream->error; |
} |
|
int fphone(FILE *stream) |
{ |
if (stream->fd >= 0) { |
if (stream->phone < 0) |
stream->phone = fd_phone(stream->fd); |
|
return stream->phone; |
} |
|
return -1; |
} |
|
int fnode(FILE *stream, fdi_node_t *node) |
{ |
if (stream->fd >= 0) |
return fd_node(stream->fd, node); |
|
return ENOENT; |
} |
|
/** @} |
*/ |