Add %E, %I, and %M formats.
[akaros.git] / kern / src / printfmt.c
1 // Stripped-down primitive printf-style formatting routines,
2 // used in common by printf, sprintf, fprintf, etc.
3 // This code is also used by both the kernel and user programs.
4
5 #ifdef __SHARC__
6 #pragma nosharc
7 #endif
8
9 #include <ros/common.h>
10 #include <error.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <stdarg.h>
14 #include <kthread.h>
15
16 /* Print a number (base <= 16) in reverse order,
17  * using specified putch function and associated pointer putdat. */
18 void printnum(void (*putch)(int, void**), void **putdat,
19               unsigned long long num, unsigned base, int width, int padc)
20 {
21         unsigned long long temp = num;
22         int nr_digits = 1;
23         /* Determine how many leading zeros we need.
24          * For every digit/nibble beyond base, we do one less width padding */
25         while ((temp /= base)) {
26                 nr_digits++;
27                 width--;
28         }
29         /* And another one less, since we'll always print the last digit */
30         while (--width > 0)
31                 putch(padc, putdat);
32         for (int i = nr_digits; i > 0; i--) {
33                 temp = num;
34                 /* To get digit i, we only div (i-1) times */
35                 for (int j = 0; j < i - 1; j++) {
36                         temp /= base;
37                 }
38                 putch("0123456789abcdef"[temp % base], putdat);
39         }
40 }
41
42 // Main function to format and print a string.
43 #ifdef __DEPUTY__
44 void printfmt(void (*putch)(int, TV(t)), TV(t) putdat, const char *fmt, ...);
45 #else
46 void printfmt(void (*putch)(int, void**), void **putdat, const char *fmt, ...);
47 #endif
48
49 #ifdef __DEPUTY__
50 void vprintfmt(void (*putch)(int, TV(t)), TV(t) putdat, const char *fmt, va_list ap)
51 #else
52 void vprintfmt(void (*putch)(int, void**), void **putdat, const char *fmt, va_list ap)
53 #endif
54 {
55         register const char *NTS p;
56         const char *NTS last_fmt;
57         register int ch, err;
58         unsigned long long num;
59         int base, lflag, width, precision, altflag;
60         char padc;
61         uint8_t *mac, *ip, *mask;
62         int i;
63
64         while (1) {
65                 while ((ch = *(unsigned char *) fmt) != '%') {
66                         if (ch == '\0')
67                                 return;
68                         fmt++;
69                         putch(ch, putdat);
70                 }
71                 fmt++;
72
73                 // Process a %-escape sequence
74                 last_fmt = fmt;
75                 padc = ' ';
76                 width = -1;
77                 precision = -1;
78                 lflag = 0;
79                 altflag = 0;
80         reswitch:
81                 switch (ch = *(unsigned char *) fmt++) {
82
83                 // flag to pad on the right
84                 case '-':
85                         padc = '-';
86                         goto reswitch;
87
88                 // flag to pad with 0's instead of spaces
89                 case '0':
90                         padc = '0';
91                         goto reswitch;
92
93                 // width field
94                 case '1':
95                 case '2':
96                 case '3':
97                 case '4':
98                 case '5':
99                 case '6':
100                 case '7':
101                 case '8':
102                 case '9':
103                         for (precision = 0; ; ++fmt) {
104                                 precision = precision * 10 + ch - '0';
105                                 ch = *fmt;
106                                 if (ch < '0' || ch > '9')
107                                         break;
108                         }
109                         goto process_precision;
110
111                 case '*':
112                         precision = va_arg(ap, int);
113                         goto process_precision;
114
115                 case '.':
116                         if (width < 0)
117                                 width = 0;
118                         goto reswitch;
119
120                 case '#':
121                         altflag = 1;
122                         goto reswitch;
123
124                 process_precision:
125                         if (width < 0)
126                                 width = precision, precision = -1;
127                         goto reswitch;
128
129                 // long flag (doubled for long long)
130                 case 'l':
131                         lflag++;
132                         goto reswitch;
133
134                 // character
135                 case 'c':
136                         putch(va_arg(ap, int), putdat);
137                         break;
138
139                 // error message
140                 case 'e':
141                         err = va_arg(ap, int);
142                         if (err < 0)
143                                 err = -err;
144                         if (err >= NUMERRORS)
145                                 printfmt(putch, putdat, "error %d", err);
146                         else
147                                 printfmt(putch, putdat, "%s", error_string[err]);
148                         break;
149
150                 case 'E': // ENET MAC
151                         if ((mac = va_arg(ap, uint8_t *)) == NULL){
152                                 char *s = "00:00:00:00:00:00";
153                                 while(*s)
154                                         putch(*s++, putdat);
155                         }
156                         printemac(putch, putdat, mac);
157                         break;
158                 case 'I':
159                         /* what to do if they screw up? */
160                         if ((ip = va_arg(ap, uint8_t *)) != NULL)
161                                 printip(putch, putdat, ip);
162                         break;
163                 case 'M':
164                         /* what to do if they screw up? */
165                         if ((mask = va_arg(ap, uint8_t *)) != NULL)
166                                 printipmask(putch, putdat, mask);
167                         break;
168                 // string
169                 case 's':
170                         if ((p = va_arg(ap, char *)) == NULL)
171                                 p = "(null)";
172                         if (width > 0 && padc != '-')
173                                 for (width -= strnlen(p, precision); width > 0; width--)
174                                         putch(padc, putdat);
175                         for (; (ch = *p) != '\0' && (precision < 0 || --precision >= 0); width--) {
176                                 if (altflag && (ch < ' ' || ch > '~'))
177                                         putch('?', putdat);
178                                 else
179                                         putch(ch, putdat);
180                                 // zra: make sure *p isn't '\0' before inc'ing
181                                 p++;
182                         }
183                         for (; width > 0; width--)
184                                 putch(' ', putdat);
185                         break;
186
187                 case 'd': /* (signed) decimal */
188                         if (lflag >= 2)
189                                 num = va_arg(ap, long long);
190                         else if (lflag)
191                                 num = va_arg(ap, long);
192                         else
193                                 num = va_arg(ap, int);
194                         if ((long long) num < 0) {
195                                 putch('-', putdat);
196                                 num = -(long long) num;
197                         }
198                         base = 10;
199                         goto number;
200
201                 case 'u': /* unsigned decimal */
202                 case 'o': /* (unsigned) octal */
203                 case 'x': /* (unsigned) hexadecimal */
204                         if (lflag >= 2)
205                                 num = va_arg(ap, unsigned long long);
206                         else if (lflag)
207                                 num = va_arg(ap, unsigned long);
208                         else
209                                 num = va_arg(ap, unsigned int);
210                         if (ch == 'u')
211                                 base = 10;
212                         else if (ch == 'o')
213                                 base = 8;
214                         else    /* x */
215                                 base = 16;
216                         goto number;
217
218                 // pointer
219                 case 'p':
220                         putch('0', putdat);
221                         putch('x', putdat);
222                         /* automatically zero-pad pointers, out to the length of a ptr */
223                         padc = '0';
224                         width = sizeof(void*) * 2;      /* 8 bits per byte / 4 bits per char */
225                         num = (unsigned long long)
226                                 (uintptr_t) va_arg(ap, void *);
227                         base = 16;
228                         goto number;
229
230                 number:
231                         printnum(putch, putdat, num, base, width, padc);
232                         break;
233
234                 // escaped '%' character
235                 case '%':
236                         putch(ch, putdat);
237                         break;
238
239                 // unrecognized escape sequence - just print it literally
240                 default:
241                         putch('%', putdat);
242                         fmt = last_fmt;
243                         //for (fmt--; fmt[-1] != '%'; fmt--)
244                                 /* do nothing */;
245                         break;
246                 }
247         }
248 }
249
250 #ifdef __DEPUTY__
251 void printfmt(void (*putch)(int, TV(t)), TV(t) putdat, const char *fmt, ...)
252 #else
253 void printfmt(void (*putch)(int, void**), void **putdat, const char *fmt, ...)
254 #endif
255 {
256         va_list ap;
257
258         va_start(ap, fmt);
259         vprintfmt(putch, putdat, fmt, ap);
260         va_end(ap);
261         check_poison("printfmt");
262 }
263
264 typedef struct sprintbuf {
265         char *BND(__this,ebuf) buf;
266         char *SNT ebuf;
267         int cnt;
268 } sprintbuf_t;
269
270 static void sprintputch(int ch, sprintbuf_t *NONNULL *NONNULL b)
271 {
272         (*b)->cnt++;
273         if ((*b)->buf < (*b)->ebuf)
274                 *((*b)->buf++) = ch;
275 }
276
277 int vsnprintf(char *buf, int n, const char *fmt, va_list ap)
278 {
279         sprintbuf_t b;// = {buf, buf+n-1, 0};
280         sprintbuf_t *COUNT(1) NONNULL bp = &b;
281
282         if (buf == NULL || n < 1)
283                 return -EINVAL;
284
285         b.buf = NULL; // zra : help out the Deputy optimizer a bit
286         b.ebuf = buf+n-1;
287         b.cnt = 0;
288         b.buf = buf;
289
290         // print the string to the buffer
291         #ifdef __DEPUTY__
292         vprintfmt((void*)sprintputch, (sprintbuf_t *NONNULL*NONNULL)&bp, fmt, ap);
293         #else
294         vprintfmt((void*)sprintputch, (void*)&bp, fmt, ap);
295         #endif
296
297         // null terminate the buffer
298         *b.buf = '\0';
299
300         return b.cnt;
301 }
302
303 int snprintf(char *buf, int n, const char *fmt, ...)
304 {
305         va_list ap;
306         int rc;
307
308         va_start(ap, fmt);
309         rc = vsnprintf(buf, n, fmt, ap);
310         va_end(ap);
311
312         check_poison("snprintf");
313         return rc;
314 }
315
316 /* convenience function: do a print, return the pointer to the end. */
317 char *seprintf(char *buf, char *end, const char *fmt, ...)
318 {
319         va_list ap;
320         int rc;
321         int n = end - buf;
322
323         if (n <= 0)
324                 return buf;
325
326         va_start(ap, fmt);
327         rc = vsnprintf(buf, n, fmt, ap);
328         va_end(ap);
329         check_poison("seprintf");
330
331         if (rc >= 0)
332                 return buf + rc;
333         else
334                 return buf;
335 }