1/*
2 * Copyright (c) 2008 Travis Geiselbrecht
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files
6 * (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge,
8 * publish, distribute, sublicense, and/or sell copies of the Software,
9 * and to permit persons to whom the Software is furnished to do so,
10 * subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23#include <aboot/aboot.h>
24#include <aboot/types.h>
25#include <stdarg.h>
26
27int printf(const char *fmt, ...)
28{
29	char buf[128];
30	int err;
31
32	va_list ap;
33	va_start(ap, fmt);
34	err = vsnprintf(buf, sizeof(buf), fmt, ap);
35	va_end(ap);
36
37	serial_puts(buf);
38	return err;
39}
40
41int snprintf(char *str, size_t len, const char *fmt, ...)
42{
43	int err;
44
45	va_list ap;
46	va_start(ap, fmt);
47	err = vsnprintf(str, len, fmt, ap);
48	va_end(ap);
49
50	return err;
51}
52
53#define LONGFLAG     0x00000001
54#define LONGLONGFLAG 0x00000002
55#define HALFFLAG     0x00000004
56#define HALFHALFFLAG 0x00000008
57#define SIZETFLAG    0x00000010
58#define ALTFLAG      0x00000020
59#define CAPSFLAG     0x00000040
60#define SHOWSIGNFLAG 0x00000080
61#define SIGNEDFLAG   0x00000100
62#define LEFTFORMATFLAG 0x00000200
63#define LEADZEROFLAG 0x00000400
64
65static char *long_to_string(char *buf, unsigned long n, int len, uint flag)
66{
67	int pos = len;
68	int negative = 0;
69
70	if((flag & SIGNEDFLAG) && (long long)n < 0) {
71		negative = 1;
72		n = -n;
73	}
74
75	buf[--pos] = 0;
76
77	/* only do the math if the number is >= 10 */
78	while(n >= 10) {
79		int digit = n % 10;
80
81		n /= 10;
82
83		buf[--pos] = digit + '0';
84	}
85	buf[--pos] = n + '0';
86
87	if(negative)
88		buf[--pos] = '-';
89	else if((flag & SHOWSIGNFLAG))
90		buf[--pos] = '+';
91
92	return &buf[pos];
93}
94
95static char *long_to_hexstring(char *buf, unsigned long u, int len, uint flag)
96{
97	int pos = len;
98	static const char hextable[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
99	static const char hextable_caps[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
100	const char *table;
101
102	if((flag & CAPSFLAG))
103		table = hextable_caps;
104	else
105		table = hextable;
106
107	buf[--pos] = 0;
108	do {
109		unsigned int digit = u % 16;
110		u /= 16;
111
112		buf[--pos] = table[digit];
113	} while(u != 0);
114
115	return &buf[pos];
116}
117
118int vsprintf(char *str, const char *fmt, va_list ap)
119{
120	return vsnprintf(str, INT_MAX, fmt, ap);
121}
122
123int vsnprintf(char *str, size_t len, const char *fmt, va_list ap)
124{
125	char c;
126	unsigned char uc;
127	const char *s;
128	unsigned long n;
129	void *ptr;
130	int flags;
131	unsigned int format_num;
132	size_t chars_written = 0;
133	char num_buffer[32];
134
135#define OUTPUT_CHAR(c) do { (*str++ = c); chars_written++; if (chars_written + 1 == len) goto done; } while(0)
136#define OUTPUT_CHAR_NOLENCHECK(c) do { (*str++ = c); chars_written++; } while(0)
137
138	for(;;) {
139		/* handle regular chars that aren't format related */
140		while((c = *fmt++) != 0) {
141			if(c == '%')
142				break; /* we saw a '%', break and start parsing format */
143			OUTPUT_CHAR(c);
144		}
145
146		/* make sure we haven't just hit the end of the string */
147		if(c == 0)
148			break;
149
150		/* reset the format state */
151		flags = 0;
152		format_num = 0;
153
154next_format:
155		/* grab the next format character */
156		c = *fmt++;
157		if(c == 0)
158			break;
159
160		switch(c) {
161		case '0'...'9':
162			if (c == '0' && format_num == 0)
163				flags |= LEADZEROFLAG;
164			format_num *= 10;
165			format_num += c - '0';
166			goto next_format;
167		case '.':
168			/* XXX for now eat numeric formatting */
169			goto next_format;
170		case '%':
171			OUTPUT_CHAR('%');
172			break;
173		case 'c':
174			uc = va_arg(ap, unsigned int);
175			OUTPUT_CHAR(uc);
176			break;
177		case 's':
178			s = va_arg(ap, const char *);
179			if(s == 0)
180				s = "<null>";
181			goto _output_string;
182		case '-':
183			flags |= LEFTFORMATFLAG;
184			goto next_format;
185		case '+':
186			flags |= SHOWSIGNFLAG;
187			goto next_format;
188		case '#':
189			flags |= ALTFLAG;
190			goto next_format;
191		case 'l':
192			flags |= LONGFLAG;
193			goto next_format;
194		case 'h':
195			if(flags & HALFFLAG)
196				flags |= HALFHALFFLAG;
197			flags |= HALFFLAG;
198			goto next_format;
199		case 'z':
200			flags |= SIZETFLAG;
201			goto next_format;
202		case 'D':
203			flags |= LONGFLAG;
204			/* fallthrough */
205		case 'i':
206		case 'd':
207			n = (flags & LONGFLAG) ? va_arg(ap, long) :
208				(flags & HALFHALFFLAG) ? (signed char)va_arg(ap, int) :
209				(flags & HALFFLAG) ? (short)va_arg(ap, int) :
210				(flags & SIZETFLAG) ? va_arg(ap, ssize_t) :
211				va_arg(ap, int);
212			flags |= SIGNEDFLAG;
213			s = long_to_string(num_buffer, n, sizeof(num_buffer), flags);
214			goto _output_string;
215		case 'U':
216			flags |= LONGFLAG;
217			/* fallthrough */
218		case 'u':
219			n = (flags & LONGFLAG) ? va_arg(ap, unsigned long) :
220				(flags & HALFHALFFLAG) ? (unsigned char)va_arg(ap, unsigned int) :
221				(flags & HALFFLAG) ? (unsigned short)va_arg(ap, unsigned int) :
222				(flags & SIZETFLAG) ? va_arg(ap, size_t) :
223				va_arg(ap, unsigned int);
224			s = long_to_string(num_buffer, n, sizeof(num_buffer), flags);
225			goto _output_string;
226		case 'p':
227			flags |= LONGFLAG | ALTFLAG;
228			goto hex;
229		case 'X':
230			flags |= CAPSFLAG;
231			/* fallthrough */
232		hex:
233		case 'x':
234			n = (flags & LONGFLAG) ? va_arg(ap, unsigned long) :
235				(flags & HALFHALFFLAG) ? (unsigned char)va_arg(ap, unsigned int) :
236				(flags & HALFFLAG) ? (unsigned short)va_arg(ap, unsigned int) :
237				(flags & SIZETFLAG) ? va_arg(ap, size_t) :
238				va_arg(ap, unsigned int);
239			s = long_to_hexstring(num_buffer, n, sizeof(num_buffer), flags);
240			if(flags & ALTFLAG) {
241				OUTPUT_CHAR('0');
242				OUTPUT_CHAR((flags & CAPSFLAG) ? 'X': 'x');
243			}
244			goto _output_string;
245		case 'n':
246			ptr = va_arg(ap, void *);
247			if(flags & LONGLONGFLAG)
248				*(long long *)ptr = chars_written;
249			else if(flags & LONGFLAG)
250				*(long *)ptr = chars_written;
251			else if(flags & HALFHALFFLAG)
252				*(signed char *)ptr = chars_written;
253			else if(flags & HALFFLAG)
254				*(short *)ptr = chars_written;
255			else if(flags & SIZETFLAG)
256				*(size_t *)ptr = chars_written;
257			else
258				*(int *)ptr = chars_written;
259			break;
260		default:
261			OUTPUT_CHAR('%');
262			OUTPUT_CHAR(c);
263			break;
264		}
265
266		/* move on to the next field */
267		continue;
268
269		/* shared output code */
270_output_string:
271		if (flags & LEFTFORMATFLAG) {
272			/* left justify the text */
273			uint count = 0;
274			while(*s != 0) {
275				OUTPUT_CHAR(*s++);
276				count++;
277			}
278
279			/* pad to the right (if necessary) */
280			for (; format_num > count; format_num--)
281				OUTPUT_CHAR(' ');
282		} else {
283			/* right justify the text (digits) */
284			size_t string_len = strlen(s);
285			char outchar = (flags & LEADZEROFLAG) ? '0' : ' ';
286			for (; format_num > string_len; format_num--)
287				OUTPUT_CHAR(outchar);
288
289			/* output the string */
290			while(*s != 0)
291				OUTPUT_CHAR(*s++);
292		}
293		continue;
294	}
295
296done:
297	/* null terminate */
298	OUTPUT_CHAR_NOLENCHECK('\0');
299	chars_written--; /* don't count the null */
300
301#undef OUTPUT_CHAR
302#undef OUTPUT_CHAR_NOLENCHECK
303
304	return chars_written;
305}
306
307
308