2b461679844b9cc1e7e7bb44f433fe36c5e42c98
[monolithium.git] / libraries / mlcrt / 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 "io_priv.h"
23
24 static list_entry_t open_files;
25
26 FILE *stdin  = NULL;
27 FILE *stdout = NULL;
28 FILE *stderr = NULL;
29
30 int fileno_unlocked(FILE *stream)
31 {
32     if (stream->fd == -1) errno = EBADF;
33     return stream->fd;
34 }
35
36 int fileno(FILE *stream)
37 {
38     syscall_wait_mutex(stream->mutex, NO_TIMEOUT);
39     int ret = fileno_unlocked(stream);
40     syscall_release_mutex(stream->mutex);
41     return ret;
42 }
43
44 static int __crt_fflush_stream(FILE *stream)
45 {
46     if (!stream->buffer) return EOF;
47
48     if (!(stream->flags & FILE_BUFFER_DIR))
49     {
50         stream->buffer_start = stream->buffer_end = 0;
51         stream->flags &= ~FILE_BUFFER_FULL;
52         return 0;
53     }
54
55     if (stream->fd == -1) return 0;
56
57     if (stream->buffer_start < stream->buffer_end)
58     {
59         ssize_t written = write(stream->fd, &stream->buffer[stream->buffer_start], stream->buffer_end - stream->buffer_start);
60         if (written < 0) return EOF;
61
62         stream->buffer_start += written;
63         stream->buffer_start %= stream->buffer_size;
64         if (written) stream->flags &= ~FILE_BUFFER_FULL;
65     }
66     else
67     {
68         if (stream->buffer_start < stream->buffer_size)
69         {
70             ssize_t written = write(stream->fd, &stream->buffer[stream->buffer_start], stream->buffer_size - stream->buffer_start);
71             if (written < 0) return EOF;
72
73             stream->buffer_start += written;
74             stream->buffer_start %= stream->buffer_size;
75             if (written) stream->flags &= ~FILE_BUFFER_FULL;
76         }
77
78         if (stream->buffer_end)
79         {
80             ssize_t written = write(stream->fd, stream->buffer, stream->buffer_end);
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     }
88
89     return 0;
90 }
91
92 int fflush_unlocked(FILE *stream)
93 {
94     if (stream) return __crt_fflush_stream(stream);
95
96     list_entry_t *entry;
97     int result = 0;
98
99     for (entry = open_files.next; entry != &open_files; entry = entry->next)
100     {
101         if (__crt_fflush_stream(CONTAINER_OF(entry, struct __crt_file, link)) == EOF)
102         {
103             result = EOF;
104         }
105     }
106
107     return result;
108 }
109
110 int fflush(FILE *stream)
111 {
112     syscall_wait_mutex(stream->mutex, NO_TIMEOUT);
113     int ret = fflush_unlocked(stream);
114     syscall_release_mutex(stream->mutex);
115     return ret;
116 }
117
118 int setvbuf(FILE *stream, char *buf, int mode, size_t size)
119 {
120     int ret = EOF;
121     syscall_wait_mutex(stream->mutex, NO_TIMEOUT);
122
123     switch (mode)
124     {
125     case _IONBF:
126         if (fflush_unlocked(stream) == 0)
127         {
128             stream->buffer = NULL;
129             stream->buffer_size = 0;
130             ret = 0;
131         }
132         break;
133
134     case _IOLBF:
135         if (!buf || fflush_unlocked(stream) == 0)
136         {
137             if (buf)
138             {
139                 stream->buffer = buf;
140                 stream->buffer_size = size;
141             }
142
143             stream->flags |= FILE_BUFFER_LINE;
144             ret = 0;
145         }
146         break;
147
148     case _IOFBF:
149         if (!buf || fflush_unlocked(stream) == 0)
150         {
151             if (buf)
152             {
153                 stream->buffer = buf;
154                 stream->buffer_size = size;
155             }
156
157             stream->flags &= ~FILE_BUFFER_LINE;
158             ret = 0;
159         }
160         break;
161     }
162
163     syscall_release_mutex(stream->mutex);
164     return ret;
165 }
166
167 void setbuf(FILE *stream, char *buf)
168 {
169     setvbuf(stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ);
170 }
171
172 void setbuffer(FILE *stream, char *buf, size_t size)
173 {
174     setvbuf(stream, buf, buf ? _IOFBF : _IONBF, size);
175 }
176
177 void setlinebuf(FILE *stream)
178 {
179     setvbuf(stream, NULL, _IOLBF, 0);
180 }
181
182 inline int fgetc_unlocked(FILE *stream)
183 {
184     char c;
185
186     if (!(stream->flags & FILE_READ))
187     {
188         errno = EPERM;
189         return EOF;
190     }
191
192     if (stream->buffer)
193     {
194         if (stream->flags & FILE_BUFFER_DIR)
195         {
196             fflush_unlocked(stream);
197             stream->flags &= ~FILE_BUFFER_DIR;
198         }
199
200         if (stream->buffer_start == stream->buffer_end && !(stream->flags & FILE_BUFFER_FULL))
201         {
202             if (stream->fd == -1) return EOF;
203
204             if (stream->buffer_end < stream->buffer_size)
205             {
206                 ssize_t amount = read(stream->fd, &stream->buffer[stream->buffer_end], stream->buffer_size - stream->buffer_end);
207                 if (amount < 0) return EOF;
208
209                 stream->buffer_end += amount;
210                 stream->buffer_end %= stream->buffer_size;
211                 if (amount && (stream->buffer_start == stream->buffer_end)) stream->flags |= FILE_BUFFER_FULL;
212             }
213
214             if (stream->buffer_end < stream->buffer_start)
215             {
216                 ssize_t amount = read(stream->fd, &stream->buffer[stream->buffer_end], stream->buffer_start - stream->buffer_end);
217                 if (amount < 0) return EOF;
218
219                 stream->buffer_end += amount;
220                 stream->buffer_end %= stream->buffer_size;
221                 if (amount && (stream->buffer_start == stream->buffer_end)) stream->flags |= FILE_BUFFER_FULL;
222             }
223         }
224
225         c = stream->buffer[stream->buffer_start];
226         stream->buffer_start++;
227         stream->buffer_end %= stream->buffer_size;
228
229         stream->flags &= ~FILE_BUFFER_FULL;
230     }
231     else
232     {
233         if (read(stream->fd, &c, 1) != 1) c = EOF;
234     }
235
236     return (int)((unsigned int)c);
237 }
238
239 int fgetc(FILE *stream)
240 {
241     syscall_wait_mutex(stream->mutex, NO_TIMEOUT);
242     int ret = fgetc_unlocked(stream);
243     syscall_release_mutex(stream->mutex);
244     return ret;
245 }
246
247 inline int fputc_unlocked(int c, FILE *stream)
248 {
249     if (!(stream->flags & FILE_WRITE))
250     {
251         errno = EPERM;
252         return EOF;
253     }
254
255     if (stream->buffer)
256     {
257         if (!(stream->flags & FILE_BUFFER_DIR))
258         {
259             fflush_unlocked(stream);
260             stream->flags |= FILE_BUFFER_DIR;
261         }
262
263         if (stream->flags & FILE_BUFFER_FULL)
264         {
265             errno = ENOSPC;
266             return EOF;
267         }
268
269         stream->buffer[stream->buffer_end] = (uint8_t)c;
270         stream->buffer_end++;
271         stream->buffer_end %= stream->buffer_size;
272
273         if (stream->buffer_start == stream->buffer_end)
274         {
275             stream->flags |= FILE_BUFFER_FULL;
276         }
277
278         if (stream->fd != -1 && ((stream->flags & FILE_BUFFER_FULL) || ((stream->flags & FILE_BUFFER_LINE) && (char)c == '\n')))
279         {
280             fflush_unlocked(stream);
281         }
282     }
283     else
284     {
285         if (write(stream->fd, &c, 1) != 1) return EOF;
286     }
287
288     return 0;
289 }
290
291 int fputc(int c, FILE *stream)
292 {
293     syscall_wait_mutex(stream->mutex, NO_TIMEOUT);
294     int ret = fputc_unlocked(c, stream);
295     syscall_release_mutex(stream->mutex);
296     return ret;
297 }
298
299 char *fgets_unlocked(char *s, int size, FILE *stream)
300 {
301     int c;
302     char *ptr = s;
303
304     while (size-- > 0 && (c = fgetc_unlocked(stream)) != EOF)
305     {
306         *ptr++ = (char)c;
307         if (c == '\n') break;
308     }
309
310     return (s != ptr) ? s : NULL;
311 }
312
313 char *fgets(char *s, int size, FILE *stream)
314 {
315     syscall_wait_mutex(stream->mutex, NO_TIMEOUT);
316     char *ret = fgets_unlocked(s, size, stream);
317     syscall_release_mutex(stream->mutex);
318     return ret;
319 }
320
321 int fputs_unlocked(const char *s, FILE *stream)
322 {
323     const char *ptr = s;
324     while (*ptr) if (fputc_unlocked(*ptr++, stream) == EOF) return EOF;
325     return 0;
326 }
327
328 int fputs(const char *s, FILE *stream)
329 {
330     syscall_wait_mutex(stream->mutex, NO_TIMEOUT);
331     int ret = fputs_unlocked(s, stream);
332     syscall_release_mutex(stream->mutex);
333     return ret;
334 }
335
336 int ungetc(int c, FILE *stream)
337 {
338     syscall_wait_mutex(stream->mutex, NO_TIMEOUT);
339
340     if (stream->flags & FILE_BUFFER_DIR)
341     {
342         fflush_unlocked(stream);
343         stream->flags &= ~FILE_BUFFER_DIR;
344     }
345
346     if (stream->buffer_start == 0) stream->buffer_start = stream->buffer_size;
347     stream->buffer_start--;
348
349     stream->buffer[stream->buffer_start] = (uint8_t)c;
350
351     syscall_release_mutex(stream->mutex);
352     return c;
353 }
354
355 char *gets(char *s)
356 {
357     char *ptr = s;
358     int c;
359     while ((c = getchar()) != EOF) *ptr++ = (char)c;
360     return ptr != s ? s : NULL;
361 }
362
363 int puts(const char *s)
364 {
365     if (fputs(s, stdout) == EOF) return EOF;
366     if (fputc('\n', stdout) == EOF) return EOF;
367     return 0;
368 }
369
370 FILE *fdopen(int fd, const char *mode)
371 {
372     FILE *stream = (FILE*)malloc(sizeof(FILE) + BUFSIZ);
373
374     if (stream == NULL)
375     {
376         errno = ENOMEM;
377         return NULL;
378     }
379
380     sysret_t status = syscall_create_mutex(NULL, TRUE, &stream->mutex);
381     if (status != ERR_SUCCESS)
382     {
383         free(stream);
384         errno = __crt_translate_error(status);
385         return NULL;
386     }
387
388     stream->flags = 0;
389
390     const char *ptr;
391     for (ptr = mode; *ptr; ptr++)
392     {
393         switch (*ptr)
394         {
395         case 'r':
396             stream->flags |= FILE_READ;
397             break;
398         case 'w':
399             stream->flags |= FILE_WRITE;
400             break;
401         case 'a':
402             stream->flags |= FILE_APPEND;
403             break;
404         case '+':
405             stream->flags |= FILE_READ | FILE_WRITE;
406             break;
407         }
408     }
409
410     stream->fd = fd;
411
412     char *buffer = (char*)((uintptr_t)stream + sizeof(FILE));
413     setbuf(stream, buffer);
414     return stream;
415 }
416
417 FILE *fmemopen(void *buf, size_t size, const char *mode)
418 {
419     FILE *stream = (FILE*)malloc(sizeof(FILE));
420     if (stream == NULL)
421     {
422         errno = ENOMEM;
423         return NULL;
424     }
425
426     sysret_t status = syscall_create_mutex(NULL, TRUE, &stream->mutex);
427     if (status != ERR_SUCCESS)
428     {
429         free(stream);
430         errno = __crt_translate_error(status);
431         return NULL;
432     }
433
434     stream->flags = 0;
435
436     const char *ptr;
437     for (ptr = mode; *ptr; ptr++)
438     {
439         switch (*ptr)
440         {
441         case 'r':
442             stream->flags |= FILE_READ;
443             break;
444         case 'w':
445             stream->flags |= FILE_WRITE;
446             break;
447         case 'a':
448             stream->flags |= FILE_APPEND;
449             break;
450         case '+':
451             stream->flags |= FILE_READ | FILE_WRITE;
452             break;
453         }
454     }
455
456     stream->fd = -1;
457     stream->buffer = buf;
458     stream->buffer_size = size;
459     stream->buffer_start = stream->buffer_end = 0;
460
461     list_append(&open_files, &stream->link);
462     return stream;
463 }
464
465 FILE *fopen(const char *pathname, const char *mode)
466 {
467     int open_flags = 0;
468
469     const char *ptr;
470     for (ptr = mode; *ptr; ptr++)
471     {
472         switch (*ptr)
473         {
474         case 'r':
475             open_flags = (open_flags & ~3) | O_RDONLY;
476             break;
477         case 'w':
478             open_flags = (open_flags & ~3) | O_WRONLY;
479             break;
480         case 'a':
481             open_flags |= O_APPEND;
482             break;
483         case '+':
484             open_flags = (open_flags & ~3) | O_RDWR;
485             break;
486         }
487     }
488
489     int fd = open(pathname, open_flags);
490     if (fd < 0) return NULL;
491
492     FILE *stream = fdopen(fd, mode);
493     if (!stream) close(fd);
494     return NULL;
495 }
496
497 int fclose(FILE *stream)
498 {
499     fflush(stream);
500     list_remove(&stream->link);
501     syscall_wait_mutex(stream->mutex, NO_TIMEOUT);
502
503     if (stream->fd) close(stream->fd);
504
505     syscall_close_object(stream->mutex);
506     free(stream);
507     return 0;
508 }