[crt] Properly implement I/O buffers
[monolithium.git] / crt / src / io.c
1 /*
2  * io.c
3  *
4  * Copyright (C) 2017 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as
8  * published by the Free Software Foundation, either version 3 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <stdio.h>
21 #include <unistd.h>
22 #include <monolithium.h>
23
24 #define FILE_READ        (1 << 0)
25 #define FILE_WRITE       (1 << 1)
26 #define FILE_APPEND      (1 << 2)
27 #define FILE_BUFFER_LINE (1 << 3)
28 #define FILE_BUFFER_DIR  (1 << 4)
29 #define FILE_BUFFER_FULL (1 << 31)
30
31 struct __crt_file
32 {
33     list_entry_t link;
34     handle_t mutex;
35     uint32_t flags;
36
37     int fd;
38     size_t size;
39
40     char *buffer;
41     size_t buffer_size;
42     size_t buffer_start, buffer_end;
43 };
44
45 static list_entry_t open_files;
46
47 FILE *stdin  = NULL;
48 FILE *stdout = NULL;
49 FILE *stderr = NULL;
50
51 int fileno_unlocked(FILE *stream)
52 {
53     if (stream->fd == -1) errno = EBADF;
54     return stream->fd;
55 }
56
57 int fileno(FILE *stream)
58 {
59     syscall_wait_mutex(stream->mutex, NO_TIMEOUT);
60     int ret = fileno_unlocked(stream);
61     syscall_release_mutex(stream->mutex);
62     return ret;
63 }
64
65 static int __crt_fflush_stream(FILE *stream)
66 {
67     if (!stream->buffer) return EOF;
68
69     if (!(stream->flags & FILE_BUFFER_DIR))
70     {
71         stream->buffer_start = stream->buffer_end = 0;
72         stream->flags &= ~FILE_BUFFER_FULL;
73         return 0;
74     }
75
76     if (stream->fd == -1) return 0;
77
78     if (stream->buffer_start < stream->buffer_end)
79     {
80         ssize_t written = write(stream->fd, &stream->buffer[stream->buffer_start], stream->buffer_end - stream->buffer_start);
81         if (written < 0) return EOF;
82
83         stream->buffer_start += written;
84         stream->buffer_start %= stream->buffer_size;
85         if (written) stream->flags &= ~FILE_BUFFER_FULL;
86     }
87     else
88     {
89         if (stream->buffer_start < stream->buffer_size)
90         {
91             ssize_t written = write(stream->fd, &stream->buffer[stream->buffer_start], stream->buffer_size - stream->buffer_start);
92             if (written < 0) return EOF;
93
94             stream->buffer_start += written;
95             stream->buffer_start %= stream->buffer_size;
96             if (written) stream->flags &= ~FILE_BUFFER_FULL;
97         }
98
99         if (stream->buffer_end)
100         {
101             ssize_t written = write(stream->fd, stream->buffer, stream->buffer_end);
102             if (written < 0) return EOF;
103
104             stream->buffer_start += written;
105             stream->buffer_start %= stream->buffer_size;
106             if (written) stream->flags &= ~FILE_BUFFER_FULL;
107         }
108     }
109
110     return 0;
111 }
112
113 int fflush_unlocked(FILE *stream)
114 {
115     if (stream) return __crt_fflush_stream(stream);
116
117     list_entry_t *entry;
118     int result = 0;
119
120     for (entry = open_files.next; entry != &open_files; entry = entry->next)
121     {
122         if (__crt_fflush_stream(CONTAINER_OF(entry, struct __crt_file, link)) == EOF)
123         {
124             result = EOF;
125         }
126     }
127
128     return result;
129 }
130
131 int fflush(FILE *stream)
132 {
133     syscall_wait_mutex(stream->mutex, NO_TIMEOUT);
134     int ret = fflush_unlocked(stream);
135     syscall_release_mutex(stream->mutex);
136     return ret;
137 }
138
139 int setvbuf(FILE *stream, char *buf, int mode, size_t size)
140 {
141     int ret = EOF;
142     syscall_wait_mutex(stream->mutex, NO_TIMEOUT);
143
144     switch (mode)
145     {
146     case _IONBF:
147         if (fflush_unlocked(stream) == 0)
148         {
149             stream->buffer = NULL;
150             stream->buffer_size = 0;
151             ret = 0;
152         }
153         break;
154
155     case _IOLBF:
156         if (!buf || fflush_unlocked(stream) == 0)
157         {
158             if (buf)
159             {
160                 stream->buffer = buf;
161                 stream->buffer_size = size;
162             }
163
164             stream->flags |= FILE_BUFFER_LINE;
165             ret = 0;
166         }
167         break;
168
169     case _IOFBF:
170         if (!buf || fflush_unlocked(stream) == 0)
171         {
172             if (buf)
173             {
174                 stream->buffer = buf;
175                 stream->buffer_size = size;
176             }
177
178             stream->flags &= ~FILE_BUFFER_LINE;
179             ret = 0;
180         }
181         break;
182     }
183
184     syscall_release_mutex(stream->mutex);
185     return ret;
186 }
187
188 void setbuf(FILE *stream, char *buf)
189 {
190     setvbuf(stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ);
191 }
192
193 void setbuffer(FILE *stream, char *buf, size_t size)
194 {
195     setvbuf(stream, buf, buf ? _IOFBF : _IONBF, size);
196 }
197
198 void setlinebuf(FILE *stream)
199 {
200     setvbuf(stream, NULL, _IOLBF, 0);
201 }
202
203 inline int fgetc_unlocked(FILE *stream)
204 {
205     char c;
206
207     if (!(stream->flags & FILE_READ))
208     {
209         errno = EPERM;
210         return EOF;
211     }
212
213     if (stream->buffer)
214     {
215         if (stream->flags & FILE_BUFFER_DIR)
216         {
217             fflush_unlocked(stream);
218             stream->flags &= ~FILE_BUFFER_DIR;
219         }
220
221         if (stream->buffer_start == stream->buffer_end && !(stream->flags & FILE_BUFFER_FULL))
222         {
223             if (stream->fd == -1) return EOF;
224
225             if (stream->buffer_end < stream->buffer_size)
226             {
227                 ssize_t amount = read(stream->fd, &stream->buffer[stream->buffer_end], stream->buffer_size - stream->buffer_end);
228                 if (amount < 0) return EOF;
229
230                 stream->buffer_end += amount;
231                 stream->buffer_end %= stream->buffer_size;
232                 if (amount && (stream->buffer_start == stream->buffer_end)) stream->flags |= FILE_BUFFER_FULL;
233             }
234
235             if (stream->buffer_end < stream->buffer_start)
236             {
237                 ssize_t amount = read(stream->fd, &stream->buffer[stream->buffer_end], stream->buffer_start - stream->buffer_end);
238                 if (amount < 0) return EOF;
239
240                 stream->buffer_end += amount;
241                 stream->buffer_end %= stream->buffer_size;
242                 if (amount && (stream->buffer_start == stream->buffer_end)) stream->flags |= FILE_BUFFER_FULL;
243             }
244         }
245
246         c = stream->buffer[stream->buffer_start];
247         stream->buffer_start++;
248         stream->buffer_end %= stream->buffer_size;
249
250         stream->flags &= ~FILE_BUFFER_FULL;
251     }
252     else
253     {
254         if (read(stream->fd, &c, 1) != 1) c = EOF;
255     }
256
257     return (int)((unsigned int)c);
258 }
259
260 int fgetc(FILE *stream)
261 {
262     syscall_wait_mutex(stream->mutex, NO_TIMEOUT);
263     int ret = fgetc_unlocked(stream);
264     syscall_release_mutex(stream->mutex);
265     return ret;
266 }
267
268 inline int fputc_unlocked(int c, FILE *stream)
269 {
270     if (!(stream->flags & FILE_WRITE))
271     {
272         errno = EPERM;
273         return EOF;
274     }
275
276     if (stream->buffer)
277     {
278         if (!(stream->flags & FILE_BUFFER_DIR))
279         {
280             fflush_unlocked(stream);
281             stream->flags |= FILE_BUFFER_DIR;
282         }
283
284         if (stream->flags & FILE_BUFFER_FULL)
285         {
286             errno = ENOSPC;
287             return EOF;
288         }
289
290         stream->buffer[stream->buffer_end] = (uint8_t)c;
291         stream->buffer_end++;
292         stream->buffer_end %= stream->buffer_size;
293
294         if (stream->buffer_start == stream->buffer_end)
295         {
296             stream->flags |= FILE_BUFFER_FULL;
297         }
298
299         if (stream->fd != -1 && ((stream->flags & FILE_BUFFER_FULL) || ((stream->flags & FILE_BUFFER_LINE) && (char)c == '\n')))
300         {
301             fflush_unlocked(stream);
302         }
303     }
304     else
305     {
306         if (write(stream->fd, &c, 1) != 1) return EOF;
307     }
308
309     return 0;
310 }
311
312 int fputc(int c, FILE *stream)
313 {
314     syscall_wait_mutex(stream->mutex, NO_TIMEOUT);
315     int ret = fputc_unlocked(c, stream);
316     syscall_release_mutex(stream->mutex);
317     return ret;
318 }
319
320 char *fgets_unlocked(char *s, int size, FILE *stream)
321 {
322     int c;
323     char *ptr = s;
324
325     while (size-- > 0 && (c = fgetc_unlocked(stream)) != EOF)
326     {
327         *ptr++ = (char)c;
328         if (c == '\n') break;
329     }
330
331     return (s != ptr) ? s : NULL;
332 }
333
334 char *fgets(char *s, int size, FILE *stream)
335 {
336     syscall_wait_mutex(stream->mutex, NO_TIMEOUT);
337     char *ret = fgets_unlocked(s, size, stream);
338     syscall_release_mutex(stream->mutex);
339     return ret;
340 }
341
342 int fputs_unlocked(const char *s, FILE *stream)
343 {
344     const char *ptr = s;
345     while (*ptr) if (fputc_unlocked(*ptr++, stream) == EOF) return EOF;
346     return 0;
347 }
348
349 int fputs(const char *s, FILE *stream)
350 {
351     syscall_wait_mutex(stream->mutex, NO_TIMEOUT);
352     int ret = fputs_unlocked(s, stream);
353     syscall_release_mutex(stream->mutex);
354     return ret;
355 }
356
357 int ungetc(int c, FILE *stream)
358 {
359     syscall_wait_mutex(stream->mutex, NO_TIMEOUT);
360
361     if (stream->flags & FILE_BUFFER_DIR)
362     {
363         fflush_unlocked(stream);
364         stream->flags &= ~FILE_BUFFER_DIR;
365     }
366
367     if (stream->buffer_start == 0) stream->buffer_start = stream->buffer_size;
368     stream->buffer_start--;
369
370     stream->buffer[stream->buffer_start] = (uint8_t)c;
371
372     syscall_release_mutex(stream->mutex);
373     return c;
374 }
375
376 char *gets(char *s)
377 {
378     char *ptr = s;
379     int c;
380     while ((c = getchar()) != EOF) *ptr++ = (char)c;
381     return ptr != s ? s : NULL;
382 }
383
384 int puts(const char *s)
385 {
386     if (fputs(s, stdout) == EOF) return EOF;
387     if (fputc('\n', stdout) == EOF) return EOF;
388     return 0;
389 }
390
391 FILE *fmemopen(void *buf, size_t size, const char *mode)
392 {
393     FILE *stream = (FILE*)malloc(sizeof(FILE));
394
395     sysret_t status = syscall_create_mutex(NULL, TRUE, &stream->mutex);
396     if (status != ERR_SUCCESS)
397     {
398         free(stream);
399         errno = __crt_translate_error(status);
400         return NULL;
401     }
402
403     stream->flags = 0;
404
405     const char *ptr;
406     for (ptr = mode; *ptr; ptr++)
407     {
408         switch (*ptr)
409         {
410         case 'r':
411             stream->flags |= FILE_READ;
412             break;
413         case 'w':
414             stream->flags |= FILE_WRITE;
415             break;
416         case 'a':
417             stream->flags |= FILE_APPEND;
418             break;
419         case '+':
420             stream->flags |= FILE_READ | FILE_WRITE;
421             break;
422         }
423     }
424
425     stream->fd = -1;
426     stream->size = size;
427     stream->buffer = buf;
428     stream->buffer_size = size;
429     stream->buffer_start = stream->buffer_end = 0;
430
431     list_append(&open_files, &stream->link);
432     return stream;
433 }
434
435 int fclose(FILE *stream)
436 {
437     list_remove(&stream->link);
438     free(stream);
439     return 0;
440 }