backported fixes from 256boss
[bootcensus] / src / libc / stdio.c
1 /*
2 pcboot - bootable PC demo/game kernel
3 Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY, without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18 #include <stdio.h>
19 #include <string.h>
20 #include <ctype.h>
21 #include <errno.h>
22 #include "contty.h"
23 #include "serial.h"
24 #include "panic.h"
25
26 enum {
27         OUT_DEF,
28         OUT_BUF,
29         OUT_SCR,
30         OUT_SER
31 };
32
33 extern void pcboot_putchar(int c);
34
35 static int intern_printf(int out, char *buf, size_t sz, const char *fmt, va_list ap);
36 static int intern_scanf(const char *instr, FILE *infile, const char *fmt, va_list ap);
37 static void bwrite(int out, char *buf, size_t buf_sz, char *str, int sz);
38 /*static int readchar(const char *str, FILE *fp);*/
39
40 int putchar(int c)
41 {
42         con_putchar(c);
43         return c;
44 }
45
46 int puts(const char *s)
47 {
48         while(*s) {
49                 putchar(*s++);
50         }
51         putchar('\n');
52         return 0;
53 }
54
55 /* -- printf and friends -- */
56
57 int printf(const char *fmt, ...)
58 {
59         int res;
60         va_list ap;
61
62         va_start(ap, fmt);
63         res = intern_printf(OUT_DEF, 0, 0, fmt, ap);
64         va_end(ap);
65         return res;
66 }
67
68 int vprintf(const char *fmt, va_list ap)
69 {
70         return intern_printf(OUT_DEF, 0, 0, fmt, ap);
71 }
72
73 int sprintf(char *buf, const char *fmt, ...)
74 {
75         int res;
76         va_list ap;
77
78         va_start(ap, fmt);
79         res = intern_printf(OUT_BUF, buf, 0, fmt, ap);
80         va_end(ap);
81         return res;
82 }
83
84 int vsprintf(char *buf, const char *fmt, va_list ap)
85 {
86         return intern_printf(OUT_BUF, buf, 0, fmt, ap);
87 }
88
89 int snprintf(char *buf, size_t sz, const char *fmt, ...)
90 {
91         int res;
92         va_list ap;
93
94         va_start(ap, fmt);
95         res = intern_printf(OUT_BUF, buf, sz, fmt, ap);
96         va_end(ap);
97         return res;
98 }
99
100 int vsnprintf(char *buf, size_t sz, const char *fmt, va_list ap)
101 {
102         return intern_printf(OUT_BUF, buf, sz, fmt, ap);
103 }
104
105 int fprintf(FILE *fp, const char *fmt, ...)
106 {
107         int res;
108         va_list ap;
109
110         va_start(ap, fmt);
111         res = vfprintf(fp, fmt, ap);
112         va_end(ap);
113         return res;
114 }
115
116 int vfprintf(FILE *fp, const char *fmt, va_list ap)
117 {
118         if(fp == stdout || fp == stderr) {
119                 return vprintf(fmt, ap);
120         }
121
122         panic("*fprintf for anything other than stdout/stderr, not implemented yet\n");
123         return 0;
124 }
125
126 int ser_printf(const char *fmt, ...)
127 {
128         int res;
129         va_list ap;
130
131         va_start(ap, fmt);
132         res = intern_printf(OUT_SER, 0, 0, fmt, ap);
133         va_end(ap);
134         return res;
135 }
136
137 int ser_vprintf(const char *fmt, va_list ap)
138 {
139         return intern_printf(OUT_SER, 0, 0, fmt, ap);
140 }
141
142 void perror(const char *s)
143 {
144         printf("%s: %s\n", s, strerror(errno));
145 }
146
147 /* intern_printf provides all the functionality needed by all the printf
148  * variants.
149  * - buf: optional buffer onto which the formatted results are written. If null
150  *   then the output goes to the terminal through putchar calls. This is used
151  *   by the (v)sprintf variants which write to an array of char.
152  * - sz: optional maximum size of the output, 0 means unlimited. This is used
153  *   by the (v)snprintf variants to avoid buffer overflows.
154  * The rest are obvious, format string and variable argument list.
155  */
156 static char *convc = "dioxXucsfeEgGpn%";
157
158 #define IS_CONV(c)      strchr(convc, c)
159
160 #define BUF(x)  ((x) ? (x) + cnum : (x))
161 #define SZ(x)   ((x) ? (x) - cnum : (x))
162
163 static int intern_printf(int out, char *buf, size_t sz, const char *fmt, va_list ap)
164 {
165         char conv_buf[32];
166         char *str;
167         int i, slen;
168         const char *fstart = 0;
169
170         /* state */
171         int cnum = 0;
172         int base = 10;
173         int alt = 0;
174         int fwidth = 0;
175         int padc = ' ';
176         int sign = 0;
177         int left_align = 0;
178         int hex_caps = 0;
179         int unsig = 0;
180         int num, unum;
181
182         while(*fmt) {
183                 if(*fmt == '%') {
184                         fstart = fmt++;
185                         continue;
186                 }
187
188                 if(fstart) {
189                         if(IS_CONV(*fmt)) {
190                                 switch(*fmt) {
191                                 case 'X':
192                                         hex_caps = 1;
193
194                                 case 'x':
195                                 case 'p':
196                                         base = 16;
197
198                                         if(alt) {
199                                                 bwrite(out, BUF(buf), SZ(sz), "0x", 2);
200                                                 cnum += 2;
201                                         }
202
203                                 case 'u':
204                                         unsig = 1;
205
206                                         if(0) {
207                                 case 'o':
208                                                 base = 8;
209
210                                                 if(alt) {
211                                                         bwrite(out, BUF(buf), SZ(sz), "0", 1);
212                                                         cnum++;
213                                                 }
214                                         }
215
216                                 case 'd':
217                                 case 'i':
218                                         if(unsig) {
219                                                 unum = va_arg(ap, unsigned int);
220                                                 utoa(unum, conv_buf, base);
221                                         } else {
222                                                 num = va_arg(ap, int);
223                                                 itoa(num, conv_buf, base);
224                                         }
225                                         if(hex_caps) {
226                                                 for(i=0; conv_buf[i]; i++) {
227                                                         conv_buf[i] = toupper(conv_buf[i]);
228                                                 }
229                                         }
230
231                                         slen = strlen(conv_buf);
232
233                                         if(left_align) {
234                                                 if(!unsig && sign && num >= 0) {
235                                                         bwrite(out, BUF(buf), SZ(sz), "+", 1);
236                                                         cnum++;
237                                                 }
238                                                 bwrite(out, BUF(buf), SZ(sz), conv_buf, slen);
239                                                 cnum += slen;
240                                                 padc = ' ';
241                                         }
242                                         for(i=slen; i<fwidth; i++) {
243                                                 bwrite(out, BUF(buf), SZ(sz), (char*)&padc, 1);
244                                                 cnum++;
245                                         }
246                                         if(!left_align) {
247                                                 if(!unsig && sign && num >= 0) {
248                                                         bwrite(out, BUF(buf), SZ(sz), "+", 1);
249                                                         cnum++;
250                                                 }
251                                                 bwrite(out, BUF(buf), SZ(sz), conv_buf, slen);
252                                                 cnum += slen;
253                                         }
254                                         break;
255
256                                 case 'c':
257                                         {
258                                                 char c = va_arg(ap, int);
259                                                 bwrite(out, BUF(buf), SZ(sz), &c, 1);
260                                                 cnum++;
261                                         }
262                                         break;
263
264                                 case 's':
265                                         str = va_arg(ap, char*);
266                                         slen = strlen(str);
267
268                                         if(left_align) {
269                                                 bwrite(out, BUF(buf), SZ(sz), str, slen);
270                                                 cnum += slen;
271                                                 padc = ' ';
272                                         }
273                                         for(i=slen; i<fwidth; i++) {
274                                                 bwrite(out, BUF(buf), SZ(sz), (char*)&padc, 1);
275                                                 cnum++;
276                                         }
277                                         if(!left_align) {
278                                                 bwrite(out, BUF(buf), SZ(sz), str, slen);
279                                                 cnum += slen;
280                                         }
281                                         break;
282
283                                 case 'n':
284                                         *va_arg(ap, int*) = cnum;
285                                         break;
286
287                                 default:
288                                         break;
289                                 }
290
291                                 /* restore default conversion state */
292                                 base = 10;
293                                 alt = 0;
294                                 fwidth = 0;
295                                 padc = ' ';
296                                 hex_caps = 0;
297
298                                 fstart = 0;
299                                 fmt++;
300                         } else {
301                                 switch(*fmt) {
302                                 case '#':
303                                         alt = 1;
304                                         break;
305
306                                 case '+':
307                                         sign = 1;
308                                         break;
309
310                                 case '-':
311                                         left_align = 1;
312                                         break;
313
314                                 case 'l':
315                                 case 'L':
316                                         break;
317
318                                 case '0':
319                                         padc = '0';
320                                         break;
321
322                                 default:
323                                         if(isdigit(*fmt)) {
324                                                 const char *fw = fmt;
325                                                 while(*fmt && isdigit(*fmt)) fmt++;
326
327                                                 fwidth = atoi(fw);
328                                                 continue;
329                                         }
330                                 }
331                                 fmt++;
332                         }
333                 } else {
334                         bwrite(out, BUF(buf), SZ(sz), (char*)fmt++, 1);
335                         cnum++;
336                 }
337         }
338
339         return cnum;
340 }
341
342
343 #if 0
344 static char *sconvc = "diouxcsefg%";
345
346 #define IS_SCONV(c)     strchr(sconvc, c)
347
348 static int intern_scanf(const char *instr, FILE *infile, const char *fmt, va_list ap)
349 {
350         return -1;      /* TODO */
351 }
352 #endif
353
354
355 /* bwrite is called by intern_printf to transparently handle writing into a
356  * buffer or to the terminal
357  */
358 static void bwrite(int out, char *buf, size_t buf_sz, char *str, int sz)
359 {
360         int i;
361
362         if(out == OUT_BUF) {
363                 if(buf_sz && buf_sz <= sz) sz = buf_sz;
364                 buf[sz] = 0;
365                 memcpy(buf, str, sz);
366         } else {
367                 switch(out) {
368                 case OUT_DEF:
369                         for(i=0; i<sz; i++) {
370                                 putchar(*str++);
371                         }
372                         break;
373
374                 case OUT_SER:
375                         for(i=0; i<sz; i++) {
376                                 ser_putchar(*str++);
377                         }
378                         break;
379
380                 default:
381                         /* TODO: OUT_SCR */
382                         break;
383                 }
384         }
385 }
386
387 /*
388 static int readchar(const char *str, FILE *fp)
389 {
390         static const char *orig_str;
391         static const char *sptr;
392
393         if(str) {
394                 if(str == orig_str) {
395                         if(!*sptr) return -1;
396                         return *sptr++;
397                 } else {
398                         orig_str = sptr = str;
399                         return readchar(str, fp);
400                 }
401         } else {
402                 return fgetc(fp);
403         }
404
405         return -1;
406 }
407 */