Subversion Repositories HelenOS

Compare Revisions

Ignore whitespace Rev 4580 → Rev 4581

/branches/network/uspace/lib/libc/generic/io/io.c
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;
}
 
/** @}
*/