summaryrefslogtreecommitdiffstats
path: root/lib/libc/snprintf.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libc/snprintf.c')
-rw-r--r--lib/libc/snprintf.c275
1 files changed, 275 insertions, 0 deletions
diff --git a/lib/libc/snprintf.c b/lib/libc/snprintf.c
new file mode 100644
index 0000000..21d3416
--- /dev/null
+++ b/lib/libc/snprintf.c
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2017-2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#define get_num_va_args(_args, _lcount) \
+ (((_lcount) > 1) ? va_arg(_args, long long int) : \
+ (((_lcount) == 1) ? va_arg(_args, long int) : \
+ va_arg(_args, int)))
+
+#define get_unum_va_args(_args, _lcount) \
+ (((_lcount) > 1) ? va_arg(_args, unsigned long long int) : \
+ (((_lcount) == 1) ? va_arg(_args, unsigned long int) : \
+ va_arg(_args, unsigned int)))
+
+#define CHECK_AND_PUT_CHAR(buf, size, chars_printed, ch) \
+ do { \
+ if ((chars_printed) < (size)) { \
+ *(buf) = (ch); \
+ (buf)++; \
+ } \
+ (chars_printed)++; \
+ } while (false)
+
+static void string_print(char **s, size_t n, size_t *chars_printed,
+ const char *str)
+{
+ while (*str != '\0') {
+ CHECK_AND_PUT_CHAR(*s, n, *chars_printed, *str);
+ str++;
+ }
+}
+
+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)
+{
+ /* Just need enough space to store 64 bit decimal integer */
+ char num_buf[20];
+ int i = 0;
+ int width;
+ unsigned int rem;
+ char ascii_a = capitalise ? 'A' : 'a';
+
+ /* num_buf is only large enough for radix >= 10 */
+ if (radix < 10) {
+ assert(0);
+ return;
+ }
+
+ do {
+ rem = unum % radix;
+ if (rem < 10U) {
+ num_buf[i] = '0' + rem;
+ } else {
+ num_buf[i] = ascii_a + (rem - 10U);
+ }
+ i++;
+ unum /= radix;
+ } while (unum > 0U);
+
+ width = i;
+ for (i = padn - width; i > 0; i--) {
+ CHECK_AND_PUT_CHAR(*s, n, *chars_printed, padc);
+ }
+ for (i = width; i > 0; i--) {
+ CHECK_AND_PUT_CHAR(*s, n, *chars_printed, num_buf[i - 1]);
+ }
+ for (i = width + padn; i < 0; i++) {
+ CHECK_AND_PUT_CHAR(*s, n, *chars_printed, padc);
+ }
+}
+
+/*******************************************************************
+ * Reduced vsnprintf to be used for Trusted firmware.
+ * The following type specifiers are supported:
+ *
+ * %x (or %X) - hexadecimal format
+ * %d or %i - signed decimal format
+ * %c - character format
+ * %s - string format
+ * %u - unsigned decimal format
+ * %p - pointer format
+ *
+ * The following length specifiers are supported by this print
+ * %l - long int
+ * %ll - long long int
+ * %z - size_t sized integer formats
+ *
+ * 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 vsnprintf(char *s, size_t n, const char *fmt, va_list args)
+{
+ int num;
+ unsigned long long int unum;
+ char *str;
+ char padc; /* Padding character */
+ int padn; /* Number of characters to pad */
+ bool left;
+ bool capitalise;
+ size_t chars_printed = 0U;
+ unsigned int l_count;
+
+ if (n == 0U) {
+ /* There isn't space for anything. */
+ } else if (n == 1U) {
+ /* Buffer is too small to actually write anything else. */
+ *s = '\0';
+ n = 0U;
+ } else {
+ /* Reserve space for the terminator character. */
+ n--;
+ }
+
+ while (*fmt != '\0') {
+ left = false;
+ padc ='\0';
+ padn = 0;
+ capitalise = false;
+ l_count = 0;
+
+ if (*fmt == '%') {
+ fmt++;
+ /* Check the format specifier. */
+loop:
+ switch (*fmt) {
+ case '%':
+ CHECK_AND_PUT_CHAR(s, n, chars_printed, '%');
+ break;
+ 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;
+
+ case 'i':
+ case 'd':
+ num = get_num_va_args(args, l_count);
+
+ if (num < 0) {
+ CHECK_AND_PUT_CHAR(s, n, chars_printed,
+ '-');
+ unum = (unsigned int)-num;
+ } else {
+ unum = (unsigned int)num;
+ }
+
+ unsigned_num_print(&s, n, &chars_printed,
+ unum, 10, padc, padn, false);
+ break;
+ case 'c':
+ CHECK_AND_PUT_CHAR(s, n, chars_printed, va_arg(args, int));
+ break;
+ case 's':
+ str = va_arg(args, char *);
+ string_print(&s, n, &chars_printed, str);
+ break;
+ case 'u':
+ unum = get_unum_va_args(args, l_count);
+ unsigned_num_print(&s, n, &chars_printed,
+ unum, 10, padc, padn, false);
+ break;
+ case 'z':
+ l_count = 1;
+ fmt++;
+ goto loop;
+ case 'l':
+ l_count++;
+ fmt++;
+ goto loop;
+ 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;
+ /* fallthrough */
+ case 'x':
+ unum = get_unum_va_args(args, l_count);
+ unsigned_num_print(&s, n, &chars_printed,
+ unum, 16, padc, padn,
+ capitalise);
+ break;
+
+ default:
+ CHECK_AND_PUT_CHAR(s, n, chars_printed, '%');
+ CHECK_AND_PUT_CHAR(s, n, chars_printed, *fmt);
+ }
+ fmt++;
+ continue;
+ }
+
+ CHECK_AND_PUT_CHAR(s, n, chars_printed, *fmt);
+
+ fmt++;
+ }
+
+ if (n > 0U) {
+ *s = '\0';
+ }
+
+ return (int)chars_printed;
+}
+
+/*******************************************************************
+ * 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;
+}