diff options
Diffstat (limited to 'rushs/evalexpr/tinylibstream')
| -rw-r--r-- | rushs/evalexpr/tinylibstream/Makefile | 13 | ||||
| -rw-r--r-- | rushs/evalexpr/tinylibstream/include/libstream.h | 167 | ||||
| -rw-r--r-- | rushs/evalexpr/tinylibstream/src/tinylibstream.c | 221 | ||||
| -rw-r--r-- | rushs/evalexpr/tinylibstream/stdin_buffering_test.c | 70 | ||||
| -rw-r--r-- | rushs/evalexpr/tinylibstream/stdout_buffering_test.c | 13 |
5 files changed, 484 insertions, 0 deletions
diff --git a/rushs/evalexpr/tinylibstream/Makefile b/rushs/evalexpr/tinylibstream/Makefile new file mode 100644 index 0000000..b060495 --- /dev/null +++ b/rushs/evalexpr/tinylibstream/Makefile @@ -0,0 +1,13 @@ +CC = gcc +CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla + +.PHONY: library clean + +library: src/tinylibstream.o + ar csr libstream.a src/tinylibstream.o + +clean: + rm libstream.a src/tinylibstream.o + +check: + $(CC) $(CFLAGS) -lcriterion src/tinylibstream.c tests/tests.c diff --git a/rushs/evalexpr/tinylibstream/include/libstream.h b/rushs/evalexpr/tinylibstream/include/libstream.h new file mode 100644 index 0000000..459432d --- /dev/null +++ b/rushs/evalexpr/tinylibstream/include/libstream.h @@ -0,0 +1,167 @@ +#ifndef LIBSTREAM_H +#define LIBSTREAM_H + +#include <fcntl.h> +#include <stdbool.h> +#include <stddef.h> +#include <unistd.h> + +/* +** /!\ DO NOT MODIFY THIS FILE, AS IT WILL BE OVERRIDDEN DURING CORRECTION. /!\ +** +** You can add your own functions declarations to OTHER HEADER FILES. +*/ + +/* the value returned when end of file is reached */ +#define LBS_EOF (-1) + +/* the size of the buffer */ +#define LBS_BUFFER_SIZE 32 + +/* +** Describes the current operation: +** - if reading, the buffer contains read-buffered data +** - if writing, the buffer contains write-buffered data +*/ +enum stream_io_operation +{ + STREAM_READING = 0, + STREAM_WRITING, +}; + +/* +** Controls when to flush the buffer: +** - when unbuffered, flush every time a character is written +** - when buffered, flush when the buffer is full +** - when line buffered, flush when the buffer is full or when a \n +** character is written +*/ +enum stream_buffering +{ + STREAM_UNBUFFERED = 0, + STREAM_LINE_BUFFERED, + STREAM_BUFFERED, +}; + +struct stream +{ + /* the flags passed to open */ + int flags; + + /* + ** Initially, this variable is 0. + ** When a function such as fgetc fails, it is set to 1 to indicate + ** something went wrong. This is useful to make the difference between + ** reaching the end of file and read errors while using fgetc and + ** some others. + ** It is often referred to as the error indicator. + */ + int error; + + /* the file descriptor, as returned by open(2) */ + int fd; + + /* + ** the kind of data stored by the buffer. + ** The default value shouldn't matter. + */ + enum stream_io_operation io_operation; + + /* + ** defines when to flush **output**. + ** This field does not control input buffering (which is always fully + ** buffered). + ** + ** The default value is LINE_BUFFERED if isatty(fd), BUFFERED otherwise. + */ + enum stream_buffering buffering_mode; + + /* the amount of used bytes in the buffer */ + size_t buffered_size; + + /* + ** /!\ This field only makes sense when io_operation is STREAM_READING /!\ + ** the amount of data already read from the buffer by the user. + */ + size_t already_read; + + /* + ** buffer + ** --------------> + ** +==============+====================+---------------------+ + ** | already_read | remaining_buffered | unused_buffer_space | + ** +==============+====================+---------------------+ + ** \_______________________________/ + ** buffered_size + ** + ** /!\ The buffer can contain either read-buffered or write-buffered data, + ** depending on the value of io_operation /!\ + */ + char buffer[LBS_BUFFER_SIZE]; +}; + +/* +** These functions are defined in a header for optimization reasons: +** each .c file that includes this header will get its own copy of the +** function's code, thus easily make optimizations. +** +** ``static`` means each compilation unit (.c file) will have its own copy +** of the function without them clashing. +** +** ``inline`` means the content of the function should be "copy pasted" +** where it's called. It also tells the compiler not to complain when the +** function isn't used. +** +** They're just like a macro, except the type of arguments is checked. +*/ + +static inline size_t stream_remaining_buffered(struct stream *stream) +{ + return stream->buffered_size - stream->already_read; +} + +static inline size_t stream_unused_buffer_space(struct stream *stream) +{ + return sizeof(stream->buffer) - stream->buffered_size; +} + +static inline bool stream_readable(struct stream *stream) +{ + int access_mode = stream->flags & O_ACCMODE; + if (access_mode == O_RDWR) + return true; + return access_mode == O_RDONLY; +} + +static inline bool stream_writable(struct stream *stream) +{ + int access_mode = stream->flags & O_ACCMODE; + if (access_mode == O_RDWR) + return true; + return access_mode == O_WRONLY; +} + +static inline int lbs_ferror(struct stream *stream) +{ + return stream->error; +} + +static inline void lbs_clearerr(struct stream *stream) +{ + stream->error = 0; +} + +static inline void lbs_setbufmode(struct stream *stream, + enum stream_buffering mode) +{ + stream->buffering_mode = mode; +} + +struct stream *lbs_fopen(const char *path, const char *mode); +struct stream *lbs_fdopen(int fd, const char *mode); +int lbs_fflush(struct stream *stream); +int lbs_fclose(struct stream *stream); +int lbs_fputc(int c, struct stream *stream); +int lbs_fgetc(struct stream *stream); + +#endif /* !LIBSTREAM_H */ diff --git a/rushs/evalexpr/tinylibstream/src/tinylibstream.c b/rushs/evalexpr/tinylibstream/src/tinylibstream.c new file mode 100644 index 0000000..dca1c01 --- /dev/null +++ b/rushs/evalexpr/tinylibstream/src/tinylibstream.c @@ -0,0 +1,221 @@ +#include <stdlib.h> +#include <string.h> + +#include "../include/libstream.h" + +int get_flags(const char *mode) +{ + int flags; + if (strcmp(mode, "r") == 0) + { + flags = O_RDONLY; + } + else if (strcmp(mode, "r+") == 0) + { + flags = O_RDWR; + } + else if (strcmp(mode, "w") == 0) + { + flags = O_WRONLY | O_TRUNC | O_CREAT; + } + else + { + flags = O_RDWR | O_TRUNC | O_CREAT; + } + + return flags; +} + +struct stream *lbs_fopen(const char *path, const char *mode) +{ + int fd = open(path, get_flags(mode)); + + return lbs_fdopen(fd, mode); +} + +struct stream *lbs_fdopen(int fd, const char *mode) +{ + if (fd == -1) + { + return NULL; + } + + struct stream *s = malloc(sizeof(struct stream)); + if (s == NULL) + { + return NULL; + } + + s->flags = get_flags(mode); + s->error = 0; + s->fd = fd; + if (isatty(fd)) + { + s->buffering_mode = STREAM_LINE_BUFFERED; + } + else + { + s->buffering_mode = STREAM_BUFFERED; + } + s->buffered_size = 0; + s->already_read = 0; + + return s; +} + +int lbs_fflush(struct stream *stream) +{ + if (stream == NULL || stream->buffered_size == 0) + { + return 0; + } + + if (stream->io_operation == STREAM_READING) + { + if (!stream_readable(stream)) + { + stream->error = 1; + return LBS_EOF; + } + if (stream_remaining_buffered(stream) != 0 + && lseek(stream->fd, -stream_remaining_buffered(stream), SEEK_CUR) + == -1) + { + stream->error = 1; + return LBS_EOF; + } + stream->buffered_size = 0; + stream->already_read = 0; + } + else + { + if (!stream_writable(stream)) + { + stream->error = 1; + return LBS_EOF; + } + ssize_t w; + if ((w = write(stream->fd, stream->buffer, stream->buffered_size)) + == -1) + { + stream->error = 1; + return LBS_EOF; + } + stream->buffered_size = 0; + stream->already_read = 0; + } + return 0; +} + +int lbs_fclose(struct stream *stream) +{ + if (stream == NULL) + { + return 1; + } + + lbs_fflush(stream); + if (close(stream->fd) == -1) + { + return 1; + } + + free(stream); + + return 0; +} + +int lbs_fputc(int c, struct stream *stream) +{ + if (!stream_writable(stream)) + { + stream->error = 1; + return -1; + } + + if (stream->io_operation == STREAM_READING) + { + if (lbs_fflush(stream) != 0) + { + return -1; + } + stream->buffered_size = 0; + stream->already_read = 0; + } + stream->io_operation = STREAM_WRITING; + + if (stream_unused_buffer_space(stream) == 0 + || stream->buffering_mode == STREAM_UNBUFFERED) + { + if (lbs_fflush(stream) != 0) + { + return -1; + } + } + + stream->buffer[stream->buffered_size] = c; + stream->buffered_size++; + if (stream_unused_buffer_space(stream) == 0 + || stream->buffering_mode == STREAM_UNBUFFERED + || (stream->buffering_mode == STREAM_LINE_BUFFERED && c == '\n')) + { + if (lbs_fflush(stream) != 0) + { + return -1; + } + } + + return c; +} + +int refill_buffer(struct stream *stream) +{ + stream->already_read = 0; + ssize_t r; + if ((r = read(stream->fd, stream->buffer, LBS_BUFFER_SIZE)) == -1) + { + stream->error = 1; + return -1; + } + if (r == 0) + { + return -1; + } + stream->buffered_size = r; + return r; +} + +int lbs_fgetc(struct stream *stream) +{ + if (!stream_readable(stream)) + { + stream->error = 1; + return -1; + } + if (stream->io_operation == STREAM_WRITING) + { + if (lbs_fflush(stream) != 0) + { + stream->error = 1; + return -1; + } + stream->already_read = 0; + } + stream->io_operation = STREAM_READING; + + if (stream_remaining_buffered(stream) == 0) + { + int r; + if ((r = refill_buffer(stream)) == -1) + { + stream->error = 1; + return -1; + } + } + + int res = stream->buffer[stream->already_read++]; + + unsigned char c = res; + + return c; +} diff --git a/rushs/evalexpr/tinylibstream/stdin_buffering_test.c b/rushs/evalexpr/tinylibstream/stdin_buffering_test.c new file mode 100644 index 0000000..6d3361b --- /dev/null +++ b/rushs/evalexpr/tinylibstream/stdin_buffering_test.c @@ -0,0 +1,70 @@ +#define _GNU_SOURCE +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +struct slow_cookie +{ + int fd; +}; + +static ssize_t slow_read(void *vcookie, char *buf, size_t count) +{ + struct slow_cookie *cookie = vcookie; + usleep(500000); // sleep for half of a second + + ssize_t res; + + while ((res = read(cookie->fd, buf, count)) < 0) + if (errno != EINTR && errno != EAGAIN) + break; + + return res; +} + +static int slow_close(void *vcookie) +{ + struct slow_cookie *cookie = vcookie; + return close(cookie->fd); +} + +cookie_io_functions_t slow_stdio = { + .read = slow_read, + .close = slow_close, +}; + +int main(void) +{ + /* setup a custom stdin stream with a slow read function */ + struct slow_cookie cookie = { + .fd = STDIN_FILENO, + }; + + FILE *input_stream = fopencookie(&cookie, "r", slow_stdio); + + /* change the buffer size to the one given by stdbuf. + ** it doesn't work out of the box as stdbuf only does + ** this for the already existing stdin stream. + */ + char *buffer = NULL; + char *buffer_size = getenv("_STDBUF_I"); + if (buffer_size) + { + size_t size = atoi(buffer_size); + buffer = malloc(size); + setvbuf(input_stream, buffer, _IOFBF, size); + } + + /* forward all characters from stdin to stdout */ + int c; + while ((c = fgetc(input_stream)) != EOF) + { + fputc(c, stdout); + fflush(stdout); + } + + fclose(input_stream); + free(buffer); + return 0; +} diff --git a/rushs/evalexpr/tinylibstream/stdout_buffering_test.c b/rushs/evalexpr/tinylibstream/stdout_buffering_test.c new file mode 100644 index 0000000..45c0a83 --- /dev/null +++ b/rushs/evalexpr/tinylibstream/stdout_buffering_test.c @@ -0,0 +1,13 @@ +#define _XOPEN_SOURCE 500 +#include <stdio.h> +#include <unistd.h> + +int main(void) +{ + const char test_string[] = "Robin\nloves\nBatman\n"; + for (size_t i = 0; test_string[i]; i++) + { + usleep(100000); // wait a tenth of a second + putchar(test_string[i]); + } +} |
