snprintf.c 5.88 KB
Newer Older
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
1
/*
2
 * Copyright (c) 2017-2021, ARM Limited and Contributors. All rights reserved.
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
3
4
5
6
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

7
#include <assert.h>
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
8
9
#include <stdarg.h>

10
11
12
#include <common/debug.h>
#include <plat/common/platform.h>

13
14
15
16
17
18
19
20
21
#define CHECK_AND_PUT_CHAR(buf, size, chars_printed, ch)	\
	do {						\
		if ((chars_printed) < (size)) {		\
			*(buf) = (ch);			\
			(buf)++;			\
		}					\
		(chars_printed)++;			\
	} while (false)

22
23
24
static void string_print(char **s, size_t n, size_t *chars_printed,
			 const char *str)
{
25
	while (*str != '\0') {
26
		CHECK_AND_PUT_CHAR(*s, n, *chars_printed, *str);
27
28
29
30
		str++;
	}
}

31
32
33
34
static void unsigned_num_print(char **s, size_t n, size_t *chars_printed,
			      unsigned long long int unum,
			      unsigned int radix, char padc, int padn,
			      bool capitalise)
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
35
{
36
37
	/* Just need enough space to store 64 bit decimal integer */
	char num_buf[20];
38
	int i = 0;
39
	int width;
40
	unsigned int rem;
41
	char ascii_a = capitalise ? 'A' : 'a';
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
42
43

	do {
44
45
46
47
48
49
50
51
		rem = unum % radix;
		if (rem < 10U) {
			num_buf[i] = '0' + rem;
		} else {
			num_buf[i] = ascii_a + (rem - 10U);
		}
		i++;
		unum /= radix;
52
	} while (unum > 0U);
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
53

54
55
56
57
58
59
60
61
62
63
64
65
66
67
	width = i;
	if (padn > width) {
		(*chars_printed) += (size_t)padn;
	} else {
		(*chars_printed) += (size_t)width;
	}

	if (*chars_printed < n) {

		if (padn > 0) {
			while (width < padn) {
				*(*s)++ = padc;
				padn--;
			}
68
69
		}

70
71
72
73
74
75
76
77
78
79
		while (--i >= 0) {
			*(*s)++ = num_buf[i];
		}

		if (padn < 0) {
			while (width < -padn) {
				*(*s)++ = padc;
				padn++;
			}
		}
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
80
81
82
83
	}
}

/*******************************************************************
84
 * Reduced vsnprintf to be used for Trusted firmware.
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
85
86
 * The following type specifiers are supported:
 *
87
 * %x (or %X) - hexadecimal format
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
88
 * %d or %i - signed decimal format
89
 * %s - string format
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
90
 * %u - unsigned decimal format
91
92
93
94
95
96
 * %p - pointer format
 *
 * The following padding specifiers are supported by this print
 * %0NN - Left-pad the number with 0s (NN is a decimal number)
 * %NN - Left-pad the number or string with spaces (NN is a decimal number)
 * %-NN - Right-pad the number or string with spaces (NN is a decimal number)
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
97
98
99
100
101
102
103
 *
 * The function panics on all other formats specifiers.
 *
 * It returns the number of characters that would be written if the
 * buffer was big enough. If it returns a value lower than n, the
 * whole string has been written.
 *******************************************************************/
104
int vsnprintf(char *s, size_t n, const char *fmt, va_list args)
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
105
106
{
	int num;
107
	unsigned long long int unum;
108
	char *str;
109
110
111
112
	char padc;		/* Padding character */
	int padn;		/* Number of characters to pad */
	bool left;
	bool capitalise;
113
	size_t chars_printed = 0U;
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
114

115
116
117
	if (n == 0U) {
		/* There isn't space for anything. */
	} else if (n == 1U) {
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
118
119
		/* Buffer is too small to actually write anything else. */
		*s = '\0';
120
121
		n = 0U;
	} else {
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
122
123
124
125
		/* Reserve space for the terminator character. */
		n--;
	}

126
	while (*fmt != '\0') {
127
128
129
130
		left = false;
		padc ='\0';
		padn = 0;
		capitalise = false;
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
131
132
133
134

		if (*fmt == '%') {
			fmt++;
			/* Check the format specifier. */
135
loop:
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
136
			switch (*fmt) {
137
			case '%':
138
				CHECK_AND_PUT_CHAR(s, n, chars_printed, '%');
139
				break;
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
				padc = (*fmt == '0') ? '0' : ' ';
				for (padn = 0; *fmt >= '0' && *fmt <= '9'; fmt++) {
					padn = (padn * 10) + (*fmt - '0');
				}
				if (left) {
					padn = -padn;
				}
				goto loop;
			case '-':
				left = true;
				fmt++;
				goto loop;

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
163
164
165
166
167
			case 'i':
			case 'd':
				num = va_arg(args, int);

				if (num < 0) {
168
169
					CHECK_AND_PUT_CHAR(s, n, chars_printed,
						'-');
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
170
171
172
173
174
					unum = (unsigned int)-num;
				} else {
					unum = (unsigned int)num;
				}

175
176
				unsigned_num_print(&s, n, &chars_printed,
						   unum, 10, padc, padn, false);
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
177
				break;
178
179
180
181
			case 's':
				str = va_arg(args, char *);
				string_print(&s, n, &chars_printed, str);
				break;
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
182
183
			case 'u':
				unum = va_arg(args, unsigned int);
184
185
				unsigned_num_print(&s, n, &chars_printed,
						   unum, 10, padc, padn, false);
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
186
				break;
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
			case 'p':
				unum = (uintptr_t)va_arg(args, void *);
				if (unum > 0U) {
					string_print(&s, n, &chars_printed, "0x");
					padn -= 2;
				}
				unsigned_num_print(&s, n, &chars_printed,
						   unum, 16, padc, padn, false);
				break;
			case 'X':
				capitalise = true;
			case 'x':
				unum = va_arg(args, unsigned int);
				unsigned_num_print(&s, n, &chars_printed,
						   unum, 16, padc, padn,
						   capitalise);
				break;

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
205
206
			default:
				/* Panic on any other format specifier. */
207
				ERROR("snprintf: specifier with ASCII code '%d' not supported.",
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
208
209
				      *fmt);
				plat_panic_handler();
210
				assert(0); /* Unreachable */
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
211
212
213
214
215
			}
			fmt++;
			continue;
		}

216
		CHECK_AND_PUT_CHAR(s, n, chars_printed, *fmt);
217

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
218
219
220
		fmt++;
	}

221
	if (n > 0U) {
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
222
		*s = '\0';
223
	}
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
224

225
	return (int)chars_printed;
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
226
}
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259

/*******************************************************************
 * Reduced snprintf to be used for Trusted firmware.
 * The following type specifiers are supported:
 *
 * %x (or %X) - hexadecimal format
 * %d or %i - signed decimal format
 * %s - string format
 * %u - unsigned decimal format
 * %p - pointer format
 *
 * The following padding specifiers are supported by this print
 * %0NN - Left-pad the number with 0s (NN is a decimal number)
 * %NN - Left-pad the number or string with spaces (NN is a decimal number)
 * %-NN - Right-pad the number or string with spaces (NN is a decimal number)
 *
 * The function panics on all other formats specifiers.
 *
 * It returns the number of characters that would be written if the
 * buffer was big enough. If it returns a value lower than n, the
 * whole string has been written.
 *******************************************************************/
int snprintf(char *s, size_t n, const char *fmt, ...)
{
	int count;
	va_list all_args;

	va_start(all_args, fmt);
	count = vsnprintf(s, n, fmt, all_args);
	va_end(all_args);

	return count;
}