iPXE
vsprintf.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 #include <stddef.h>
00027 #include <stdarg.h>
00028 #include <stdio.h>
00029 #include <errno.h>
00030 #include <wchar.h>
00031 #include <ipxe/vsprintf.h>
00032 
00033 /** @file */
00034 
00035 #define CHAR_LEN        0       /**< "hh" length modifier */
00036 #define SHORT_LEN       1       /**< "h" length modifier */
00037 #define INT_LEN         2       /**< no length modifier */
00038 #define LONG_LEN        3       /**< "l" length modifier */
00039 #define LONGLONG_LEN    4       /**< "ll" length modifier */
00040 #define SIZE_T_LEN      5       /**< "z" length modifier */
00041 
00042 static uint8_t type_sizes[] = {
00043         [CHAR_LEN]      = sizeof ( char ),
00044         [SHORT_LEN]     = sizeof ( short ),
00045         [INT_LEN]       = sizeof ( int ),
00046         [LONG_LEN]      = sizeof ( long ),
00047         [LONGLONG_LEN]  = sizeof ( long long ),
00048         [SIZE_T_LEN]    = sizeof ( size_t ),
00049 };
00050 
00051 /**
00052  * Use lower-case for hexadecimal digits
00053  *
00054  * Note that this value is set to 0x20 since that makes for very
00055  * efficient calculations.  (Bitwise-ORing with @c LCASE converts to a
00056  * lower-case character, for example.)
00057  */
00058 #define LCASE 0x20
00059 
00060 /**
00061  * Use "alternate form"
00062  *
00063  * For hexadecimal numbers, this means to add a "0x" or "0X" prefix to
00064  * the number.
00065  */
00066 #define ALT_FORM 0x02
00067 
00068 /**
00069  * Use zero padding
00070  *
00071  * Note that this value is set to 0x10 since that allows the pad
00072  * character to be calculated as @c 0x20|(flags&ZPAD)
00073  */
00074 #define ZPAD 0x10
00075 
00076 /**
00077  * Format a hexadecimal number
00078  *
00079  * @v end               End of buffer to contain number
00080  * @v num               Number to format
00081  * @v width             Minimum field width
00082  * @v flags             Format flags
00083  * @ret ptr             End of buffer
00084  *
00085  * Fills a buffer in reverse order with a formatted hexadecimal
00086  * number.  The number will be zero-padded to the specified width.
00087  * Lower-case and "alternate form" (i.e. "0x" prefix) flags may be
00088  * set.
00089  *
00090  * There must be enough space in the buffer to contain the largest
00091  * number that this function can format.
00092  */
00093 static char * format_hex ( char *end, unsigned long long num, int width,
00094                            int flags ) {
00095         char *ptr = end;
00096         int case_mod = ( flags & LCASE );
00097         int pad = ( ( flags & ZPAD ) | ' ' );
00098 
00099         /* Generate the number */
00100         do {
00101                 *(--ptr) = "0123456789ABCDEF"[ num & 0xf ] | case_mod;
00102                 num >>= 4;
00103         } while ( num );
00104 
00105         /* Pad to width */
00106         while ( ( end - ptr ) < width )
00107                 *(--ptr) = pad;
00108 
00109         /* Add "0x" or "0X" if alternate form specified */
00110         if ( flags & ALT_FORM ) {
00111                 *(--ptr) = 'X' | case_mod;
00112                 *(--ptr) = '0';
00113         }
00114 
00115         return ptr;
00116 }
00117 
00118 /**
00119  * Format a decimal number
00120  *
00121  * @v end               End of buffer to contain number
00122  * @v num               Number to format
00123  * @v width             Minimum field width
00124  * @v flags             Format flags
00125  * @ret ptr             End of buffer
00126  *
00127  * Fills a buffer in reverse order with a formatted decimal number.
00128  * The number will be space-padded to the specified width.
00129  *
00130  * There must be enough space in the buffer to contain the largest
00131  * number that this function can format.
00132  */
00133 static char * format_decimal ( char *end, signed long num, int width,
00134                                int flags ) {
00135         char *ptr = end;
00136         int negative = 0;
00137         int zpad = ( flags & ZPAD );
00138         int pad = ( zpad | ' ' );
00139 
00140         /* Generate the number */
00141         if ( num < 0 ) {
00142                 negative = 1;
00143                 num = -num;
00144         }
00145         do {
00146                 *(--ptr) = '0' + ( num % 10 );
00147                 num /= 10;
00148         } while ( num );
00149 
00150         /* Add "-" if necessary */
00151         if ( negative && ( ! zpad ) )
00152                 *(--ptr) = '-';
00153 
00154         /* Pad to width */
00155         while ( ( end - ptr ) < width )
00156                 *(--ptr) = pad;
00157 
00158         /* Add "-" if necessary */
00159         if ( negative && zpad )
00160                 *ptr = '-';
00161 
00162         return ptr;
00163 }
00164 
00165 /**
00166  * Print character via a printf context
00167  *
00168  * @v ctx               Context
00169  * @v c                 Character
00170  *
00171  * Call's the printf_context::handler() method and increments
00172  * printf_context::len.
00173  */
00174 static inline void cputchar ( struct printf_context *ctx, unsigned char c ) {
00175         ctx->handler ( ctx, c );
00176         ++ctx->len;
00177 }
00178 
00179 /**
00180  * Write a formatted string to a printf context
00181  *
00182  * @v ctx               Context
00183  * @v fmt               Format string
00184  * @v args              Arguments corresponding to the format string
00185  * @ret len             Length of formatted string
00186  */
00187 size_t vcprintf ( struct printf_context *ctx, const char *fmt, va_list args ) {
00188         int flags;
00189         int width;
00190         uint8_t *length;
00191         char *ptr;
00192         char tmp_buf[32]; /* 32 is enough for all numerical formats.
00193                            * Insane width fields could overflow this buffer. */
00194         wchar_t *wptr;
00195 
00196         /* Initialise context */
00197         ctx->len = 0;
00198 
00199         for ( ; *fmt ; fmt++ ) {
00200                 /* Pass through ordinary characters */
00201                 if ( *fmt != '%' ) {
00202                         cputchar ( ctx, *fmt );
00203                         continue;
00204                 }
00205                 fmt++;
00206                 /* Process flag characters */
00207                 flags = 0;
00208                 for ( ; ; fmt++ ) {
00209                         if ( *fmt == '#' ) {
00210                                 flags |= ALT_FORM;
00211                         } else if ( *fmt == '0' ) {
00212                                 flags |= ZPAD;
00213                         } else {
00214                                 /* End of flag characters */
00215                                 break;
00216                         }
00217                 }
00218                 /* Process field width */
00219                 width = 0;
00220                 for ( ; ; fmt++ ) {
00221                         if ( ( ( unsigned ) ( *fmt - '0' ) ) < 10 ) {
00222                                 width = ( width * 10 ) + ( *fmt - '0' );
00223                         } else {
00224                                 break;
00225                         }
00226                 }
00227                 /* We don't do floating point */
00228                 /* Process length modifier */
00229                 length = &type_sizes[INT_LEN];
00230                 for ( ; ; fmt++ ) {
00231                         if ( *fmt == 'h' ) {
00232                                 length--;
00233                         } else if ( *fmt == 'l' ) {
00234                                 length++;
00235                         } else if ( *fmt == 'z' ) {
00236                                 length = &type_sizes[SIZE_T_LEN];
00237                         } else {
00238                                 break;
00239                         }
00240                 }
00241                 /* Process conversion specifier */
00242                 ptr = tmp_buf + sizeof ( tmp_buf ) - 1;
00243                 *ptr = '\0';
00244                 wptr = NULL;
00245                 if ( *fmt == 'c' ) {
00246                         if ( length < &type_sizes[LONG_LEN] ) {
00247                                 cputchar ( ctx, va_arg ( args, unsigned int ) );
00248                         } else {
00249                                 wchar_t wc;
00250                                 size_t len;
00251 
00252                                 wc = va_arg ( args, wint_t );
00253                                 len = wcrtomb ( tmp_buf, wc, NULL );
00254                                 tmp_buf[len] = '\0';
00255                                 ptr = tmp_buf;
00256                         }
00257                 } else if ( *fmt == 's' ) {
00258                         if ( length < &type_sizes[LONG_LEN] ) {
00259                                 ptr = va_arg ( args, char * );
00260                                 if ( ! ptr )
00261                                         ptr = "<NULL>";
00262                         } else {
00263                                 wptr = va_arg ( args, wchar_t * );
00264                                 if ( ! wptr )
00265                                         ptr = "<NULL>";
00266                         }
00267                 } else if ( *fmt == 'p' ) {
00268                         intptr_t ptrval;
00269 
00270                         ptrval = ( intptr_t ) va_arg ( args, void * );
00271                         ptr = format_hex ( ptr, ptrval, width, 
00272                                            ( ALT_FORM | LCASE ) );
00273                 } else if ( ( *fmt & ~0x20 ) == 'X' ) {
00274                         unsigned long long hex;
00275 
00276                         flags |= ( *fmt & 0x20 ); /* LCASE */
00277                         if ( *length >= sizeof ( unsigned long long ) ) {
00278                                 hex = va_arg ( args, unsigned long long );
00279                         } else if ( *length >= sizeof ( unsigned long ) ) {
00280                                 hex = va_arg ( args, unsigned long );
00281                         } else {
00282                                 hex = va_arg ( args, unsigned int );
00283                         }
00284                         ptr = format_hex ( ptr, hex, width, flags );
00285                 } else if ( ( *fmt == 'd' ) || ( *fmt == 'i' ) ){
00286                         signed long decimal;
00287 
00288                         if ( *length >= sizeof ( signed long ) ) {
00289                                 decimal = va_arg ( args, signed long );
00290                         } else {
00291                                 decimal = va_arg ( args, signed int );
00292                         }
00293                         ptr = format_decimal ( ptr, decimal, width, flags );
00294                 } else {
00295                         *(--ptr) = *fmt;
00296                 }
00297                 /* Write out conversion result */
00298                 if ( wptr == NULL ) {
00299                         for ( ; *ptr ; ptr++ ) {
00300                                 cputchar ( ctx, *ptr );
00301                         }
00302                 } else {
00303                         for ( ; *wptr ; wptr++ ) {
00304                                 size_t len = wcrtomb ( tmp_buf, *wptr, NULL );
00305                                 for ( ptr = tmp_buf ; len-- ; ptr++ ) {
00306                                         cputchar ( ctx, *ptr );
00307                                 }
00308                         }
00309                 }
00310         }
00311 
00312         return ctx->len;
00313 }
00314 
00315 /** Context used by vsnprintf() and friends */
00316 struct sputc_context {
00317         struct printf_context ctx;
00318         /** Buffer for formatted string (used by printf_sputc()) */
00319         char *buf;
00320         /** Buffer length (used by printf_sputc()) */
00321         size_t max_len; 
00322 };
00323 
00324 /**
00325  * Write character to buffer
00326  *
00327  * @v ctx               Context
00328  * @v c                 Character
00329  */
00330 static void printf_sputc ( struct printf_context *ctx, unsigned int c ) {
00331         struct sputc_context * sctx =
00332                 container_of ( ctx, struct sputc_context, ctx );
00333 
00334         if ( ctx->len < sctx->max_len )
00335                 sctx->buf[ctx->len] = c;
00336 }
00337 
00338 /**
00339  * Write a formatted string to a buffer
00340  *
00341  * @v buf               Buffer into which to write the string
00342  * @v size              Size of buffer
00343  * @v fmt               Format string
00344  * @v args              Arguments corresponding to the format string
00345  * @ret len             Length of formatted string
00346  *
00347  * If the buffer is too small to contain the string, the returned
00348  * length is the length that would have been written had enough space
00349  * been available.
00350  */
00351 int vsnprintf ( char *buf, size_t size, const char *fmt, va_list args ) {
00352         struct sputc_context sctx;
00353         size_t len;
00354         size_t end;
00355 
00356         /* Hand off to vcprintf */
00357         sctx.ctx.handler = printf_sputc;
00358         sctx.buf = buf;
00359         sctx.max_len = size;
00360         len = vcprintf ( &sctx.ctx, fmt, args );
00361 
00362         /* Add trailing NUL */
00363         if ( size ) {
00364                 end = size - 1;
00365                 if ( len < end )
00366                         end = len;
00367                 buf[end] = '\0';
00368         }
00369 
00370         return len;
00371 }
00372 
00373 /**
00374  * Write a formatted string to a buffer
00375  *
00376  * @v buf               Buffer into which to write the string
00377  * @v size              Size of buffer
00378  * @v fmt               Format string
00379  * @v ...               Arguments corresponding to the format string
00380  * @ret len             Length of formatted string
00381  */
00382 int snprintf ( char *buf, size_t size, const char *fmt, ... ) {
00383         va_list args;
00384         int i;
00385 
00386         va_start ( args, fmt );
00387         i = vsnprintf ( buf, size, fmt, args );
00388         va_end ( args );
00389         return i;
00390 }
00391 
00392 /**
00393  * Version of vsnprintf() that accepts a signed buffer size
00394  *
00395  * @v buf               Buffer into which to write the string
00396  * @v size              Size of buffer
00397  * @v fmt               Format string
00398  * @v args              Arguments corresponding to the format string
00399  * @ret len             Length of formatted string
00400  */
00401 int vssnprintf ( char *buf, ssize_t ssize, const char *fmt, va_list args ) {
00402 
00403         /* Treat negative buffer size as zero buffer size */
00404         if ( ssize < 0 )
00405                 ssize = 0;
00406 
00407         /* Hand off to vsnprintf */
00408         return vsnprintf ( buf, ssize, fmt, args );
00409 }
00410 
00411 /**
00412  * Version of vsnprintf() that accepts a signed buffer size
00413  *
00414  * @v buf               Buffer into which to write the string
00415  * @v size              Size of buffer
00416  * @v fmt               Format string
00417  * @v ...               Arguments corresponding to the format string
00418  * @ret len             Length of formatted string
00419  */
00420 int ssnprintf ( char *buf, ssize_t ssize, const char *fmt, ... ) {
00421         va_list args;
00422         int len;
00423 
00424         /* Hand off to vssnprintf */
00425         va_start ( args, fmt );
00426         len = vssnprintf ( buf, ssize, fmt, args );
00427         va_end ( args );
00428         return len;
00429 }
00430 
00431 /**
00432  * Write character to console
00433  *
00434  * @v ctx               Context
00435  * @v c                 Character
00436  */
00437 static void printf_putchar ( struct printf_context *ctx __unused,
00438                              unsigned int c ) {
00439         putchar ( c );
00440 }
00441 
00442 /**
00443  * Write a formatted string to the console
00444  *
00445  * @v fmt               Format string
00446  * @v args              Arguments corresponding to the format string
00447  * @ret len             Length of formatted string
00448  */
00449 int vprintf ( const char *fmt, va_list args ) {
00450         struct printf_context ctx;
00451 
00452         /* Hand off to vcprintf */
00453         ctx.handler = printf_putchar;   
00454         return vcprintf ( &ctx, fmt, args );    
00455 }
00456 
00457 /**
00458  * Write a formatted string to the console.
00459  *
00460  * @v fmt               Format string
00461  * @v ...               Arguments corresponding to the format string
00462  * @ret len             Length of formatted string
00463  */
00464 int printf ( const char *fmt, ... ) {
00465         va_list args;
00466         int i;
00467 
00468         va_start ( args, fmt );
00469         i = vprintf ( fmt, args );
00470         va_end ( args );
00471         return i;
00472 }