summaryrefslogtreecommitdiffstats
path: root/src/lib/printf.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/printf.c')
-rwxr-xr-xsrc/lib/printf.c679
1 files changed, 679 insertions, 0 deletions
diff --git a/src/lib/printf.c b/src/lib/printf.c
new file mode 100755
index 0000000..96f08f3
--- /dev/null
+++ b/src/lib/printf.c
@@ -0,0 +1,679 @@
+// $Id: printf.c,v 1.1.1.1 2013/12/11 20:49:20 bcbrock Exp $
+// $Source: /afs/awd/projects/eclipz/KnowledgeBase/.cvsroot/eclipz/chips/p8/working/procedures/lib/printf.c,v $
+//-----------------------------------------------------------------------------
+// *! (C) Copyright International Business Machines Corp. 2013
+// *! All Rights Reserved -- Property of IBM
+// *! *** IBM Confidential ***
+//-----------------------------------------------------------------------------
+
+/// \file printf.c
+/// \brief Clean-room implementation of printf() functions for SSX I/O
+///
+/// For licensing reasons we are required to create our own version of the
+/// printf() family of functions. This implementation was created without
+/// reference to or inclusion of any licensed or copyrighted code.
+///
+/// The functions defined in this file have prototypes, behavior and return
+/// values as defined by C language standards. In the event of an error a
+/// negative value is returned, generally corresponding to a standard Unix
+/// 'errno' code. Note that SSX does not support either an application- or
+/// per-thread 'errno', so the only record of any error is the \a error field
+/// of the stream. Also note that SSX may be configured to cause a panic if an
+/// error is detected rather than returning an error code.
+///
+/// This implementation defines a limited but useful subset of the C standard
+/// for format control. This implementation includes the following:
+///
+/// - \b c, \b d, \b i, \b n, \b p, \b s, \b u, \b x, and \b X conversion
+/// specifiers, as well as '%%' to output a single '%'
+///
+/// - \b #, \b 0, \b ' ' and \b + flag characters
+///
+/// - Decimal field width specifiers including * (but indirect field widths
+/// must be positive as left-justification is not supported)
+///
+/// - Decimal precision specifiers (currently only apply to %s formats, may be
+/// indirect using *)
+///
+/// - \b l, \b ll and \b z length modifiers
+///
+/// \b Notes:
+///
+/// \a If a \c p conversion specifier is used without any flags (\c '%p'), the
+/// \c p conversion is interptered as if it were \c 0x%08lx for 32-bit address
+/// machines and \c 0x%016llx for 64-bit address machines. The GCC builtin
+/// format checker gives warnings about '0' flag characters for \c p
+/// conversion specifiers, so there is otherwise no 'un-warned' way to get
+/// this preferred (by some) format of pointer values. If you do include
+/// explicit flags (e.g., \c %30p) they will be processed as expected.
+///
+/// Similar to how printf() behaves on an X86-Linux machine, a null pointer
+/// will print as "(null)" with the %s format (unless the precision specifier
+/// precludes it) and "(nil)" with the %p format.
+///
+/// Note that calling formatted I/O functions on non-blocking streams may fail
+/// with the -EAGAIN error, and there is no clean way to restart these
+/// calls. Calling formatted (or any) I/O functions on blocking streams from
+/// interrupt contexts in SSX is also likely to fail intermittently since
+/// interrupt contexts can not block in SSX.
+///
+/// \todo I'd really like to implement the '-' flag for
+/// left-justification. Implementing the precision specifer for integers
+/// should be done for completeness.
+
+#include "ssx.h"
+#include "ssx_io.h"
+
+// Formatting options
+
+#define OPTION_ALTERNATE 0x0001
+#define OPTION_PAD_ZERO 0x0002
+#define OPTION_PLUS_SIGN 0x0004
+#define OPTION_FIELD_WIDTH 0x0008
+#define OPTION_PRECISION 0x0010
+#define OPTION_LONG 0x0020
+#define OPTION_LONG_LONG 0x0040
+#define OPTION_SIZE_T 0x0080
+#define OPTION_UPPERCASE 0x0100
+#define OPTION_HEX 0x0200
+#define OPTION_SPACE 0x0400
+
+
+// Generate padding if required, returning the total number of pad characters
+// output or a negative error code. The 'nchars' argument is the number of
+// non-pad characters to be output by the caller.
+
+#define PAD_SIZE 8
+static const char zeros[PAD_SIZE] = {'0', '0', '0', '0', '0', '0', '0', '0'};
+static const char blanks[PAD_SIZE] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '};
+
+static ssize_t
+pad(FILE *stream, size_t nchars, int options, size_t width)
+{
+ const char *padchars;
+ size_t chars, written;
+ int rc;
+
+ if (!(options & OPTION_FIELD_WIDTH) || (nchars >= width)) {
+ return 0;
+ }
+ chars = width - nchars;
+ if (options & OPTION_PAD_ZERO) {
+ padchars = zeros;
+ } else {
+ padchars = blanks;
+ }
+ while (chars) {
+ rc = swrite(stream, (void *)padchars, MIN(chars, PAD_SIZE), &written);
+ if (rc < 0) return rc;
+ chars -= written;
+ }
+ return width - nchars;
+}
+
+
+// Format a character
+
+static ssize_t
+format_char(FILE *stream, unsigned char c, int options, size_t width)
+{
+ ssize_t padchars, nchars;
+ int rc;
+
+ padchars = pad(stream, 1, options, width);
+ if (padchars < 0) return padchars;
+ nchars = padchars + 1;
+ rc = swrite(stream, (void *)(&c), 1, 0);
+ if (rc < 0) return rc;
+ return nchars;
+}
+
+
+// Format a string
+//
+// If the string is the NULL pointer then normally "(null)" is printed
+// unless the precision is < 6, in which case the empty string is printed.
+// The specification leaves it as undefined what happens if a string requests
+// 0 padding; Here we always pad with blanks (although GCC/PowerPC catches
+// this as an error).
+
+static ssize_t
+format_string(FILE *stream, const char *s, int options,
+ size_t width, size_t precision)
+{
+ size_t len;
+ ssize_t padchars, nchars;
+ int rc;
+
+ if (s == 0) {
+ if ((options & OPTION_PRECISION) && (precision < 6)) {
+ s = "";
+ } else {
+ s = "(null)";
+ }
+ }
+
+ len = strlen(s);
+ if (options & OPTION_PRECISION) {
+ len = MIN(len, precision);
+ }
+
+ options &= ~OPTION_PAD_ZERO;
+ padchars = pad(stream, len, options, width);
+ if (padchars < 0) return padchars;
+ nchars = padchars + len;
+ rc = swrite(stream, (void *)s, len, 0);
+ if (rc < 0) return rc;
+ return nchars;
+}
+
+
+// Format an integer - signed and unsigned. A 64-bit integer (assumed to be
+// the longest we'll see) has 20 decimal digits. An extra space is reserved
+// for the sign. If zero-padding is specified, the sign will be output
+// separately.
+
+static const char lower[16] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+static const char upper[16] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+static ssize_t
+format_int(FILE *stream, long long int lli, int options, size_t width)
+{
+ char digits[21];
+ int rc, k, ndigits, negative, positive, i;
+ ssize_t output;
+
+ negative = (lli < 0);
+ positive = (lli > 0);
+
+ // Unpack the integer to characters. The code is optimized for 32-bit
+ // machines where 64-bit division is not built in. The first part of the
+ // loop handles integers requiring a 64-bit divide, the second loop
+ // handles 32-bit integers.
+
+ if (lli == 0) {
+ digits[20] = '0';
+ k = 20;
+ } else if (negative) {
+ for (k = 21;
+ lli != (int)lli;
+ digits[--k] = lower[-(lli % 10)], lli = lli / 10);
+ for (i = (int)lli;
+ i != 0;
+ digits[--k] = lower[-(i % 10)], i = i / 10);
+ } else {
+ for (k = 21;
+ lli != (int)lli;
+ digits[--k] = lower[lli % 10], lli = lli / 10);
+ for (i = (int)lli;
+ i != 0;
+ digits[--k] = lower[i % 10], i = i / 10);
+ }
+
+ ndigits = 21 - k;
+
+ // Handle other options and output
+
+ output = 0;
+ if (options & OPTION_PAD_ZERO) {
+
+ if (negative) {
+ rc = swrite(stream, "-", 1, 0);
+ if (rc < 0) return rc;
+ output++;
+ } else if (positive) {
+ if (options & OPTION_PLUS_SIGN) {
+ rc = swrite(stream, "+", 1, 0);
+ if (rc < 0) return rc;
+ output++;
+ } else if (options & OPTION_SPACE) {
+ rc = swrite(stream, " ", 1, 0);
+ if (rc < 0) return rc;
+ output++;
+ }
+ }
+ rc = pad(stream, ndigits + output, options, width);
+ if (rc < 0) return rc;
+ output += rc;
+ rc = swrite(stream, &(digits[k]), ndigits, 0);
+ if (rc < 0) return rc;
+ output += ndigits;
+
+ } else {
+
+ if (negative) {
+ digits[--k] = '-';
+ ndigits++;
+ } else if (positive) {
+ if (options & OPTION_PLUS_SIGN) {
+ digits[--k] = '+';
+ ndigits++;
+ } else if (options & OPTION_SPACE) {
+ digits[--k] = ' ';
+ ndigits++;
+ }
+ }
+ rc = pad(stream, ndigits, options, width);
+ if (rc < 0) return rc;
+ output += rc;
+ rc = swrite(stream, &(digits[k]), ndigits, 0);
+ if (rc < 0) return rc;
+ output += ndigits;
+ }
+
+ return output;
+}
+
+
+static ssize_t
+format_unsigned(FILE *stream, unsigned long long ull, int options, size_t width)
+{
+ char digits[21], *alternate;
+ const char *xchars;
+ int rc, k, ndigits, zero;
+ unsigned u;
+ ssize_t output;
+
+ zero = (ull == 0);
+
+ // Determine hex case and alternate string
+
+ alternate = 0;
+ if (options & OPTION_HEX) {
+ if (options & OPTION_UPPERCASE) {
+ xchars = upper;
+ if (options & OPTION_ALTERNATE) {
+ alternate = "0X";
+ }
+ } else {
+ xchars = lower;
+ if (options & OPTION_ALTERNATE) {
+ alternate = "0x";
+ }
+ }
+ } else {
+ xchars = lower;
+ }
+
+ // Unpack the unsigned integer to characters. The Hex conversions are
+ // easier since they can be done with shift and mask rather than
+ // divison. The code is optimized for a 32-bit machine where 64-bit
+ // division is not built-in.
+
+ if (zero) {
+ digits[20] = '0';
+ k = 20;
+ } else if (options & OPTION_HEX) {
+ for (k = 21;
+ ull != (unsigned)ull;
+ digits[--k] = xchars[ull & 0xf], ull = ull >> 4);
+ for (u = (unsigned)ull;
+ u != 0;
+ digits[--k] = xchars[u & 0xf], u = u >> 4);
+ } else {
+ for (k = 21;
+ ull != (unsigned)ull;
+ digits[--k] = xchars[ull % 10], ull = ull / 10);
+ for (u = (unsigned)ull;
+ u != 0;
+ digits[--k] = xchars[u % 10], u = u / 10);
+ }
+
+ ndigits = 21 - k;
+
+ // Handle other options and output
+
+ output = 0;
+ if (options & OPTION_PAD_ZERO) {
+
+ if (!zero && alternate) {
+ rc = swrite(stream, (void *)alternate, 2, 0);
+ if (rc < 0) return rc;
+ output += 2;
+ }
+ rc = pad(stream, ndigits + output, options, width);
+ if (rc < 0) return rc;
+ output += rc;
+ rc = swrite(stream, &(digits[k]), ndigits, 0);
+ if (rc < 0) return rc;
+ output += ndigits;
+
+ } else {
+
+ if (!zero && alternate) {
+ output += 2;
+ }
+ rc = pad(stream, ndigits + output, options, width);
+ if (rc < 0) return rc;
+ output += rc;
+ if (!zero && alternate) {
+ rc = swrite(stream, alternate, 2, 0);
+ if (rc < 0) return rc;
+ output += 2;
+ }
+ rc = swrite(stream, &(digits[k]), ndigits, 0);
+ if (rc < 0) return rc;
+ output += ndigits;
+ }
+
+ return output;
+}
+
+
+int
+vfprintf(FILE *stream, const char *format, va_list argp)
+{
+ const char *fmt, *scan;
+ int rc, total_chars, options, done;
+ size_t width, precision;
+
+ int arg_i, *arg_pi;
+ long int arg_li;
+ long long int arg_lli;
+ ssize_t arg_zi;
+ unsigned arg_u;
+ unsigned long arg_lu;
+ unsigned long long arg_llu;
+ size_t arg_zu;
+ char *arg_s;
+
+ total_chars = 0;
+
+ fmt = format;
+ while (*fmt) {
+
+ // Scan until '%' or the end of the format, then output the text.
+
+ scan = fmt;
+ while (*scan && (*scan != '%')) {
+ scan++;
+ }
+ if (scan != fmt) {
+ rc = swrite(stream, fmt, scan - fmt, 0);
+ if (rc < 0) return rc;
+ total_chars += scan - fmt;
+ }
+ fmt = scan;
+ if (!*fmt) {
+ return total_chars;
+ }
+ fmt++;
+
+ // We got a '%'. Check for %% and %n.
+
+ switch (*fmt) {
+ case '\0':
+ SSX_IO_ERROR(stream, EINVAL);
+ break;
+ case '%':
+ rc = swrite(stream, "%", 1, 0);
+ if (rc < 0) return rc;
+ total_chars++;
+ fmt++;
+ continue;
+ case 'n':
+ arg_pi = va_arg(argp, int *);
+ *arg_pi = total_chars;
+ fmt++;
+ continue;
+ }
+
+ // Collect padding options, if any. Left justification is not
+ // implemeted.
+
+ options = 0;
+ done = 0;
+ do {
+ switch (*fmt) {
+ case '\0':
+ SSX_IO_ERROR(stream, EINVAL);
+ break;
+ case '#':
+ options |= OPTION_ALTERNATE;
+ break;
+ case '0':
+ options |= OPTION_PAD_ZERO;
+ break;
+ case '+':
+ options |= OPTION_PLUS_SIGN;
+ break;
+ case ' ':
+ options |= OPTION_SPACE;
+ break;
+ case '-':
+ SSX_IO_ERROR(stream, EINVAL); // Left just. not impl.
+ break;
+ default:
+ done = 1;
+ break;
+ }
+ if (!done) {
+ fmt++;
+ }
+ } while (!done);
+
+ // Collect the field width, if specified. A negative precision
+ // specified as an argument indicates left justification (not
+ // implemented).
+
+ width = 0;
+ if (isdigit(*fmt)) {
+ options |= OPTION_FIELD_WIDTH;
+ for (; isdigit(*fmt); fmt++) {
+ width = (width * 10) + (*fmt - '0');
+ }
+ } else if (*fmt == '*') {
+ fmt++;
+ options |= OPTION_FIELD_WIDTH;
+ arg_i = va_arg(argp, int);
+ if (arg_i < 0) {
+ SSX_IO_ERROR(stream, EINVAL); // Left just. not impl.
+ }
+ width = arg_i;
+ }
+
+ // Collect the precision, if specified. By standard specification an
+ // empty or negative precision is interpreted as 0.
+
+ precision = 0;
+ if (*fmt == '.') {
+ fmt++;
+ options |= OPTION_PRECISION;
+ if (isdigit(*fmt)) {
+ for(; isdigit(*fmt); fmt++) {
+ precision = (precision * 10) + (*fmt - '0');
+ }
+ } else if (*fmt == '*') {
+ fmt++;
+ arg_i = va_arg(argp, int);
+ if (arg_i < 0) {
+ arg_i = 0;
+ }
+ precision = arg_i;
+ }
+ }
+
+ // Collect length modifiers.
+
+ done = 0;
+ do {
+ switch (*fmt) {
+ case '\0':
+ SSX_IO_ERROR(stream, EINVAL);
+ break;
+ case 'l':
+ if (options & OPTION_LONG) {
+ options &= ~OPTION_LONG;
+ options |= OPTION_LONG_LONG;
+ } else if (options & OPTION_LONG_LONG) {
+ SSX_IO_ERROR(stream, EINVAL);
+ } else {
+ options |= OPTION_LONG;
+ }
+ if (options & OPTION_SIZE_T) {
+ SSX_IO_ERROR(stream, EINVAL);
+ }
+ break;
+ case 'z':
+ if ((options & OPTION_LONG) || (options & OPTION_LONG_LONG)) {
+ SSX_IO_ERROR(stream, EINVAL);
+ }
+ options |= OPTION_SIZE_T;
+ break;
+ default:
+ done = 1;
+ break;
+ }
+ if (!done) {
+ fmt++;
+ }
+ } while (!done);
+
+ // Use the conversion specifier to format the next argument
+
+ switch (*fmt) {
+
+ case 'c':
+ arg_i = va_arg(argp, int);
+ rc = format_char(stream, (unsigned char)arg_i, options, width);
+ if (rc < 0) return rc;
+ total_chars++;
+ break;
+
+ case 'd':
+ case 'i':
+ if (options & OPTION_LONG) {
+ arg_li = va_arg(argp, long int);
+ rc = format_int(stream, (long long int)arg_li, options,
+ width);
+ } else if (options & OPTION_LONG_LONG) {
+ arg_lli = va_arg(argp, long long int);
+ rc = format_int(stream, (long long int)arg_lli, options,
+ width);
+ } else if (options & OPTION_SIZE_T) {
+ arg_zi = va_arg(argp, ssize_t);
+ rc = format_int(stream, (long long int)arg_zi, options,
+ width);
+ } else {
+ arg_i = va_arg(argp, int);
+ rc = format_int(stream, (long long int)arg_i, options,
+ width);
+ }
+ if (rc < 0) return rc;
+ total_chars += rc;
+ break;
+
+ case 'p':
+ arg_lu = va_arg(argp, unsigned long);
+ options |= (OPTION_ALTERNATE | OPTION_HEX);
+ if (!(options & OPTION_PAD_ZERO) &&
+ !(options & OPTION_FIELD_WIDTH)) {
+ options |= (OPTION_PAD_ZERO | OPTION_FIELD_WIDTH);
+ width = (2 * sizeof(unsigned long)) + 2; /* 0x........ */
+ }
+ if (arg_lu == 0) {
+ options &= ~OPTION_PRECISION;
+ rc = format_string(stream, "(nil)", options, width, precision);
+ } else {
+ rc = format_unsigned(stream, (unsigned long long)arg_lu,
+ options, width);
+ }
+ if (rc < 0) return rc;
+ total_chars += rc;
+ break;
+
+ case 's':
+ arg_s = va_arg(argp, char *);
+ rc = format_string(stream, arg_s, options, width, precision);
+ if (rc < 0) return rc;
+ total_chars += rc;
+ break;
+
+ case 'X':
+ options |= OPTION_UPPERCASE;
+ case 'x':
+ options |= OPTION_HEX;
+ case 'u':
+ if (options & OPTION_LONG) {
+ arg_lu = va_arg(argp, unsigned long);
+ rc = format_unsigned(stream, (unsigned long long)arg_lu,
+ options, width);
+ } else if (options & OPTION_LONG_LONG) {
+ arg_llu = va_arg(argp, unsigned long long);
+ rc = format_unsigned(stream, (unsigned long long)arg_llu,
+ options, width);
+ } else if (options & OPTION_SIZE_T) {
+ arg_zu = va_arg(argp, size_t);
+ rc = format_unsigned(stream, (unsigned long long)arg_zu,
+ options, width);
+ } else {
+ arg_u = va_arg(argp, unsigned);
+ rc = format_unsigned(stream, (unsigned long long )arg_u,
+ options, width);
+ }
+ if (rc < 0) return rc;
+ total_chars += rc;
+ break;
+
+ default:
+ SSX_IO_ERROR(stream, EINVAL);
+ break;
+ }
+
+ fmt++;
+ }
+
+ return total_chars;
+}
+
+
+int
+vprintf(const char *format, va_list argp)
+{
+ return vfprintf(stdout, format, argp);
+}
+
+
+int
+fprintf(FILE *stream, const char *format, ...)
+{
+ va_list argp;
+ int rc;
+
+ va_start(argp, format);
+ rc = vfprintf(stream, format, argp);
+ va_end(argp);
+ return rc;
+}
+
+
+int
+printf(const char *format, ...)
+{
+ va_list argp;
+ int rc;
+
+ va_start(argp, format);
+ rc = vfprintf(stdout, format, argp);
+ va_end(argp);
+ return rc;
+}
+
+
+int
+printk(const char *format, ...)
+{
+ va_list argp;
+ int rc;
+
+ va_start(argp, format);
+ rc = vfprintf(ssxout, format, argp);
+ va_end(argp);
+ return rc;
+}
+
OpenPOWER on IntegriCloud