summaryrefslogtreecommitdiff
path: root/rushs/tinyprintf/tinylibstream
diff options
context:
space:
mode:
Diffstat (limited to 'rushs/tinyprintf/tinylibstream')
-rw-r--r--rushs/tinyprintf/tinylibstream/Makefile13
-rw-r--r--rushs/tinyprintf/tinylibstream/include/libstream.h167
-rw-r--r--rushs/tinyprintf/tinylibstream/src/tinylibstream.c221
-rw-r--r--rushs/tinyprintf/tinylibstream/stdin_buffering_test.c70
-rw-r--r--rushs/tinyprintf/tinylibstream/stdout_buffering_test.c13
5 files changed, 484 insertions, 0 deletions
diff --git a/rushs/tinyprintf/tinylibstream/Makefile b/rushs/tinyprintf/tinylibstream/Makefile
new file mode 100644
index 0000000..b060495
--- /dev/null
+++ b/rushs/tinyprintf/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/tinyprintf/tinylibstream/include/libstream.h b/rushs/tinyprintf/tinylibstream/include/libstream.h
new file mode 100644
index 0000000..459432d
--- /dev/null
+++ b/rushs/tinyprintf/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/tinyprintf/tinylibstream/src/tinylibstream.c b/rushs/tinyprintf/tinylibstream/src/tinylibstream.c
new file mode 100644
index 0000000..dca1c01
--- /dev/null
+++ b/rushs/tinyprintf/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/tinyprintf/tinylibstream/stdin_buffering_test.c b/rushs/tinyprintf/tinylibstream/stdin_buffering_test.c
new file mode 100644
index 0000000..6d3361b
--- /dev/null
+++ b/rushs/tinyprintf/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/tinyprintf/tinylibstream/stdout_buffering_test.c b/rushs/tinyprintf/tinylibstream/stdout_buffering_test.c
new file mode 100644
index 0000000..45c0a83
--- /dev/null
+++ b/rushs/tinyprintf/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]);
+ }
+}