1f2bb13e029f923a3ecb464915b86435657e0636
[bootcensus] / src / libc / stdio.c
1 /*
2 pcboot - bootable PC demo/game kernel
3 Copyright (C) 2018  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 "contty.h"
22 #include "serial.h"
23
24 enum {
25         OUT_DEF,
26         OUT_BUF,
27         OUT_SCR,
28         OUT_SER
29 };
30
31 extern void pcboot_putchar(int c);
32
33 static void bwrite(int out, char *buf, size_t buf_sz, char *str, int sz);
34 static int intern_printf(int out, char *buf, size_t sz, const char *fmt, va_list ap);
35
36 int putchar(int c)
37 {
38         con_putchar(c);
39         return c;
40 }
41
42 int puts(const char *s)
43 {
44         while(*s) {
45                 putchar(*s++);
46         }
47         putchar('\n');
48         return 0;
49 }
50
51 /* -- printf and friends -- */
52
53 static char *convc = "dioxXucsfeEgGpn%";
54
55 #define IS_CONV(c)      strchr(convc, c)
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 ser_printf(const char *fmt, ...)
106 {
107         int res;
108         va_list ap;
109
110         va_start(ap, fmt);
111         res = intern_printf(OUT_SER, 0, 0, fmt, ap);
112         va_end(ap);
113         return res;
114 }
115
116 int ser_vprintf(const char *fmt, va_list ap)
117 {
118         return intern_printf(OUT_SER, 0, 0, fmt, ap);
119 }
120
121 /* intern_printf provides all the functionality needed by all the printf
122  * variants.
123  * - buf: optional buffer onto which the formatted results are written. If null
124  *   then the output goes to the terminal through putchar calls. This is used
125  *   by the (v)sprintf variants which write to an array of char.
126  * - sz: optional maximum size of the output, 0 means unlimited. This is used
127  *   by the (v)snprintf variants to avoid buffer overflows.
128  * The rest are obvious, format string and variable argument list.
129  */
130
131 #define BUF(x)  ((x) ? (x) + cnum : (x))
132 #define SZ(x)   ((x) ? (x) - cnum : (x))
133
134 static int intern_printf(int out, char *buf, size_t sz, const char *fmt, va_list ap)
135 {
136         char conv_buf[32];
137         char *str;
138         int i, slen;
139         const char *fstart = 0;
140
141         /* state */
142         int cnum = 0;
143         int base = 10;
144         int alt = 0;
145         int fwidth = 0;
146         int padc = ' ';
147         int sign = 0;
148         int left_align = 0;     /* not implemented yet */
149         int hex_caps = 0;
150         int unsig = 0;
151
152         while(*fmt) {
153                 if(*fmt == '%') {
154                         fstart = fmt++;
155                         continue;
156                 }
157
158                 if(fstart) {
159                         if(IS_CONV(*fmt)) {
160                                 switch(*fmt) {
161                                 case 'X':
162                                         hex_caps = 1;
163
164                                 case 'x':
165                                 case 'p':
166                                         base = 16;
167
168                                         if(alt) {
169                                                 bwrite(out, BUF(buf), SZ(sz), "0x", 2);
170                                         }
171
172                                 case 'u':
173                                         unsig = 1;
174
175                                         if(0) {
176                                 case 'o':
177                                                 base = 8;
178
179                                                 if(alt) {
180                                                         bwrite(out, BUF(buf), SZ(sz), "0", 1);
181                                                 }
182                                         }
183
184                                 case 'd':
185                                 case 'i':
186                                         if(unsig) {
187                                                 utoa(va_arg(ap, unsigned int), conv_buf, base);
188                                         } else {
189                                                 itoa(va_arg(ap, int), conv_buf, base);
190                                         }
191                                         if(hex_caps) {
192                                                 for(i=0; conv_buf[i]; i++) {
193                                                         conv_buf[i] = toupper(conv_buf[i]);
194                                                 }
195                                         }
196
197                                         slen = strlen(conv_buf);
198                                         for(i=slen; i<fwidth; i++) {
199                                                 bwrite(out, BUF(buf), SZ(sz), (char*)&padc, 1);
200                                                 cnum++;
201                                         }
202
203                                         bwrite(out, BUF(buf), SZ(sz), conv_buf, strlen(conv_buf));
204                                         cnum += slen;
205                                         break;
206
207                                 case 'c':
208                                         {
209                                                 char c = va_arg(ap, int);
210                                                 bwrite(out, BUF(buf), SZ(sz), &c, 1);
211                                                 cnum++;
212                                         }
213                                         break;
214
215                                 case 's':
216                                         str = va_arg(ap, char*);
217                                         slen = strlen(str);
218
219                                         for(i=slen; i<fwidth; i++) {
220                                                 bwrite(out, BUF(buf), SZ(sz), (char*)&padc, 1);
221                                                 cnum++;
222                                         }
223                                         bwrite(out, BUF(buf), SZ(sz), str, slen);
224                                         cnum += slen;
225                                         break;
226
227                                 case 'n':
228                                         *va_arg(ap, int*) = cnum;
229                                         break;
230
231                                 default:
232                                         break;
233                                 }
234
235                                 /* restore default conversion state */
236                                 base = 10;
237                                 alt = 0;
238                                 fwidth = 0;
239                                 padc = ' ';
240                                 hex_caps = 0;
241
242                                 fstart = 0;
243                                 fmt++;
244                         } else {
245                                 switch(*fmt) {
246                                 case '#':
247                                         alt = 1;
248                                         break;
249
250                                 case '+':
251                                         sign = 1;
252                                         break;
253
254                                 case '-':
255                                         left_align = 1;
256                                         break;
257
258                                 case 'l':
259                                 case 'L':
260                                         break;
261
262                                 case '0':
263                                         padc = '0';
264                                         break;
265
266                                 default:
267                                         if(isdigit(*fmt)) {
268                                                 const char *fw = fmt;
269                                                 while(*fmt && isdigit(*fmt)) fmt++;
270
271                                                 fwidth = atoi(fw);
272                                                 continue;
273                                         }
274                                 }
275                                 fmt++;
276                         }
277                 } else {
278                         bwrite(out, BUF(buf), SZ(sz), (char*)fmt++, 1);
279                         cnum++;
280                 }
281         }
282
283         return 0;
284 }
285
286
287 /* bwrite is called by intern_printf to transparently handle writing into a
288  * buffer or to the terminal
289  */
290 static void bwrite(int out, char *buf, size_t buf_sz, char *str, int sz)
291 {
292         int i;
293
294         if(out == OUT_BUF) {
295                 if(buf_sz && buf_sz <= sz) sz = buf_sz - 1;
296                 memcpy(buf, str, sz);
297
298                 buf[sz] = 0;
299         } else {
300                 switch(out) {
301                 case OUT_DEF:
302                         for(i=0; i<sz; i++) {
303                                 putchar(*str++);
304                         }
305                         break;
306
307                 case OUT_SER:
308                         for(i=0; i<sz; i++) {
309                                 ser_putchar(*str++);
310                         }
311                         break;
312
313                 default:
314                         /* TODO: OUT_SCR */
315                         break;
316                 }
317         }
318 }
319