added copyright headers to new files
[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
23 extern void pcboot_putchar(int c);
24
25 static void bwrite(char *buf, size_t buf_sz, char *str, int sz);
26 static int intern_printf(char *buf, size_t sz, const char *fmt, va_list ap);
27
28 int putchar(int c)
29 {
30         con_putchar(c);
31         return c;
32 }
33
34 int puts(const char *s)
35 {
36         while(*s) {
37                 putchar(*s++);
38         }
39         putchar('\n');
40         return 0;
41 }
42
43 /* -- printf and friends -- */
44
45 static char *convc = "dioxXucsfeEgGpn%";
46
47 #define IS_CONV(c)      strchr(convc, c)
48
49 int printf(const char *fmt, ...)
50 {
51         int res;
52         va_list ap;
53
54         va_start(ap, fmt);
55         res = intern_printf(0, 0, fmt, ap);
56         va_end(ap);
57         return res;
58 }
59
60 int vprintf(const char *fmt, va_list ap)
61 {
62         return intern_printf(0, 0, fmt, ap);
63 }
64
65 int sprintf(char *buf, const char *fmt, ...)
66 {
67         int res;
68         va_list ap;
69
70         va_start(ap, fmt);
71         res = intern_printf(buf, 0, fmt, ap);
72         va_end(ap);
73         return res;
74 }
75
76 int vsprintf(char *buf, const char *fmt, va_list ap)
77 {
78         return intern_printf(buf, 0, fmt, ap);
79 }
80
81 int snprintf(char *buf, size_t sz, const char *fmt, ...)
82 {
83         int res;
84         va_list ap;
85
86         va_start(ap, fmt);
87         res = intern_printf(buf, sz, fmt, ap);
88         va_end(ap);
89         return res;
90 }
91
92 int vsnprintf(char *buf, size_t sz, const char *fmt, va_list ap)
93 {
94         return intern_printf(buf, sz, fmt, ap);
95 }
96
97
98 /* intern_printf provides all the functionality needed by all the printf
99  * variants.
100  * - buf: optional buffer onto which the formatted results are written. If null
101  *   then the output goes to the terminal through putchar calls. This is used
102  *   by the (v)sprintf variants which write to an array of char.
103  * - sz: optional maximum size of the output, 0 means unlimited. This is used
104  *   by the (v)snprintf variants to avoid buffer overflows.
105  * The rest are obvious, format string and variable argument list.
106  */
107
108 #define BUF(x)  ((x) ? (x) + cnum : (x))
109 #define SZ(x)   ((x) ? (x) - cnum : (x))
110
111 static int intern_printf(char *buf, size_t sz, const char *fmt, va_list ap)
112 {
113         char conv_buf[32];
114         char *str;
115         int i, slen;
116         const char *fstart = 0;
117
118         /* state */
119         int cnum = 0;
120         int base = 10;
121         int alt = 0;
122         int fwidth = 0;
123         int padc = ' ';
124         int sign = 0;
125         int left_align = 0;     /* not implemented yet */
126         int hex_caps = 0;
127         int unsig = 0;
128
129         while(*fmt) {
130                 if(*fmt == '%') {
131                         fstart = fmt++;
132                         continue;
133                 }
134
135                 if(fstart) {
136                         if(IS_CONV(*fmt)) {
137                                 switch(*fmt) {
138                                 case 'X':
139                                         hex_caps = 1;
140
141                                 case 'x':
142                                 case 'p':
143                                         base = 16;
144
145                                         if(alt) {
146                                                 bwrite(BUF(buf), SZ(sz), "0x", 2);
147                                         }
148
149                                 case 'u':
150                                         unsig = 1;
151
152                                         if(0) {
153                                 case 'o':
154                                                 base = 8;
155
156                                                 if(alt) {
157                                                         bwrite(BUF(buf), SZ(sz), "0", 1);
158                                                 }
159                                         }
160
161                                 case 'd':
162                                 case 'i':
163                                         if(unsig) {
164                                                 utoa(va_arg(ap, unsigned int), conv_buf, base);
165                                         } else {
166                                                 itoa(va_arg(ap, int), conv_buf, base);
167                                         }
168                                         if(hex_caps) {
169                                                 for(i=0; conv_buf[i]; i++) {
170                                                         conv_buf[i] = toupper(conv_buf[i]);
171                                                 }
172                                         }
173
174                                         slen = strlen(conv_buf);
175                                         for(i=slen; i<fwidth; i++) {
176                                                 bwrite(BUF(buf), SZ(sz), (char*)&padc, 1);
177                                                 cnum++;
178                                         }
179
180                                         bwrite(BUF(buf), SZ(sz), conv_buf, strlen(conv_buf));
181                                         cnum += slen;
182                                         break;
183
184                                 case 'c':
185                                         {
186                                                 char c = va_arg(ap, int);
187                                                 bwrite(BUF(buf), SZ(sz), &c, 1);
188                                                 cnum++;
189                                         }
190                                         break;
191
192                                 case 's':
193                                         str = va_arg(ap, char*);
194                                         slen = strlen(str);
195
196                                         for(i=slen; i<fwidth; i++) {
197                                                 bwrite(BUF(buf), SZ(sz), (char*)&padc, 1);
198                                                 cnum++;
199                                         }
200                                         bwrite(BUF(buf), SZ(sz), str, slen);
201                                         cnum += slen;
202                                         break;
203
204                                 case 'n':
205                                         *va_arg(ap, int*) = cnum;
206                                         break;
207
208                                 default:
209                                         break;
210                                 }
211
212                                 /* restore default conversion state */
213                                 base = 10;
214                                 alt = 0;
215                                 fwidth = 0;
216                                 padc = ' ';
217                                 hex_caps = 0;
218
219                                 fstart = 0;
220                                 fmt++;
221                         } else {
222                                 switch(*fmt) {
223                                 case '#':
224                                         alt = 1;
225                                         break;
226
227                                 case '+':
228                                         sign = 1;
229                                         break;
230
231                                 case '-':
232                                         left_align = 1;
233                                         break;
234
235                                 case 'l':
236                                 case 'L':
237                                         break;
238
239                                 case '0':
240                                         padc = '0';
241                                         break;
242
243                                 default:
244                                         if(isdigit(*fmt)) {
245                                                 const char *fw = fmt;
246                                                 while(*fmt && isdigit(*fmt)) fmt++;
247
248                                                 fwidth = atoi(fw);
249                                                 continue;
250                                         }
251                                 }
252                                 fmt++;
253                         }
254                 } else {
255                         bwrite(BUF(buf), SZ(sz), (char*)fmt++, 1);
256                         cnum++;
257                 }
258         }
259
260         return 0;
261 }
262
263
264 /* bwrite is called by intern_printf to transparently handle writing into a
265  * buffer (if buf is non-null) or to the terminal (if buf is null).
266  */
267 static void bwrite(char *buf, size_t buf_sz, char *str, int sz)
268 {
269         if(buf) {
270                 if(buf_sz && buf_sz <= sz) sz = buf_sz - 1;
271                 memcpy(buf, str, sz);
272
273                 buf[sz] = 0;
274         } else {
275                 int i;
276                 for(i=0; i<sz; i++) {
277                         putchar(*str++);
278                 }
279         }
280 }
281