2005-04-17 02:20:36 +04:00
/*
* linux / lib / vsprintf . c
*
* Copyright ( C ) 1991 , 1992 Linus Torvalds
*/
/* vsprintf.c -- Lars Wirzenius & Linus Torvalds. */
/*
* Wirzenius wrote this portably , Torvalds fucked it up : - )
*/
/*
* Fri Jul 13 2001 Crutcher Dunnavant < crutcher + kernel @ datastacks . com >
* - changed to provide snprintf and vsnprintf functions
* So Feb 1 16 : 51 : 32 CET 2004 Juergen Quade < quade @ hsnr . de >
* - scnprintf and vscnprintf
*/
# include <stdarg.h>
# include <linux/module.h>
# include <linux/types.h>
# include <linux/string.h>
# include <linux/ctype.h>
# include <linux/kernel.h>
2005-10-31 02:03:48 +03:00
# include <asm/page.h> /* for PAGE_SIZE */
2005-04-17 02:20:36 +04:00
# include <asm/div64.h>
/**
* simple_strtoul - convert a string to an unsigned long
* @ cp : The start of the string
* @ endp : A pointer to the end of the parsed string will be placed here
* @ base : The number base to use
*/
unsigned long simple_strtoul ( const char * cp , char * * endp , unsigned int base )
{
unsigned long result = 0 , value ;
if ( ! base ) {
base = 10 ;
if ( * cp = = ' 0 ' ) {
base = 8 ;
cp + + ;
if ( ( toupper ( * cp ) = = ' X ' ) & & isxdigit ( cp [ 1 ] ) ) {
cp + + ;
base = 16 ;
}
}
} else if ( base = = 16 ) {
if ( cp [ 0 ] = = ' 0 ' & & toupper ( cp [ 1 ] ) = = ' X ' )
cp + = 2 ;
}
while ( isxdigit ( * cp ) & &
( value = isdigit ( * cp ) ? * cp - ' 0 ' : toupper ( * cp ) - ' A ' + 10 ) < base ) {
result = result * base + value ;
cp + + ;
}
if ( endp )
* endp = ( char * ) cp ;
return result ;
}
EXPORT_SYMBOL ( simple_strtoul ) ;
/**
* simple_strtol - convert a string to a signed long
* @ cp : The start of the string
* @ endp : A pointer to the end of the parsed string will be placed here
* @ base : The number base to use
*/
long simple_strtol ( const char * cp , char * * endp , unsigned int base )
{
if ( * cp = = ' - ' )
return - simple_strtoul ( cp + 1 , endp , base ) ;
return simple_strtoul ( cp , endp , base ) ;
}
EXPORT_SYMBOL ( simple_strtol ) ;
/**
* simple_strtoull - convert a string to an unsigned long long
* @ cp : The start of the string
* @ endp : A pointer to the end of the parsed string will be placed here
* @ base : The number base to use
*/
unsigned long long simple_strtoull ( const char * cp , char * * endp , unsigned int base )
{
unsigned long long result = 0 , value ;
if ( ! base ) {
base = 10 ;
if ( * cp = = ' 0 ' ) {
base = 8 ;
cp + + ;
if ( ( toupper ( * cp ) = = ' X ' ) & & isxdigit ( cp [ 1 ] ) ) {
cp + + ;
base = 16 ;
}
}
} else if ( base = = 16 ) {
if ( cp [ 0 ] = = ' 0 ' & & toupper ( cp [ 1 ] ) = = ' X ' )
cp + = 2 ;
}
while ( isxdigit ( * cp ) & & ( value = isdigit ( * cp ) ? * cp - ' 0 ' : ( islower ( * cp )
? toupper ( * cp ) : * cp ) - ' A ' + 10 ) < base ) {
result = result * base + value ;
cp + + ;
}
if ( endp )
* endp = ( char * ) cp ;
return result ;
}
EXPORT_SYMBOL ( simple_strtoull ) ;
/**
* simple_strtoll - convert a string to a signed long long
* @ cp : The start of the string
* @ endp : A pointer to the end of the parsed string will be placed here
* @ base : The number base to use
*/
long long simple_strtoll ( const char * cp , char * * endp , unsigned int base )
{
if ( * cp = = ' - ' )
return - simple_strtoull ( cp + 1 , endp , base ) ;
return simple_strtoull ( cp , endp , base ) ;
}
static int skip_atoi ( const char * * s )
{
int i = 0 ;
while ( isdigit ( * * s ) )
i = i * 10 + * ( ( * s ) + + ) - ' 0 ' ;
return i ;
}
# define ZEROPAD 1 /* pad with zero */
# define SIGN 2 /* unsigned/signed long */
# define PLUS 4 /* show plus */
# define SPACE 8 /* space if plus */
# define LEFT 16 /* left justified */
# define SPECIAL 32 /* 0x */
# define LARGE 64 /* use 'ABCDEF' instead of 'abcdef' */
static char * number ( char * buf , char * end , unsigned long long num , int base , int size , int precision , int type )
{
char c , sign , tmp [ 66 ] ;
const char * digits ;
static const char small_digits [ ] = " 0123456789abcdefghijklmnopqrstuvwxyz " ;
static const char large_digits [ ] = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ " ;
int i ;
digits = ( type & LARGE ) ? large_digits : small_digits ;
if ( type & LEFT )
type & = ~ ZEROPAD ;
if ( base < 2 | | base > 36 )
return NULL ;
c = ( type & ZEROPAD ) ? ' 0 ' : ' ' ;
sign = 0 ;
if ( type & SIGN ) {
if ( ( signed long long ) num < 0 ) {
sign = ' - ' ;
num = - ( signed long long ) num ;
size - - ;
} else if ( type & PLUS ) {
sign = ' + ' ;
size - - ;
} else if ( type & SPACE ) {
sign = ' ' ;
size - - ;
}
}
if ( type & SPECIAL ) {
if ( base = = 16 )
size - = 2 ;
else if ( base = = 8 )
size - - ;
}
i = 0 ;
if ( num = = 0 )
tmp [ i + + ] = ' 0 ' ;
else while ( num ! = 0 )
tmp [ i + + ] = digits [ do_div ( num , base ) ] ;
if ( i > precision )
precision = i ;
size - = precision ;
if ( ! ( type & ( ZEROPAD + LEFT ) ) ) {
while ( size - - > 0 ) {
if ( buf < = end )
* buf = ' ' ;
+ + buf ;
}
}
if ( sign ) {
if ( buf < = end )
* buf = sign ;
+ + buf ;
}
if ( type & SPECIAL ) {
if ( base = = 8 ) {
if ( buf < = end )
* buf = ' 0 ' ;
+ + buf ;
} else if ( base = = 16 ) {
if ( buf < = end )
* buf = ' 0 ' ;
+ + buf ;
if ( buf < = end )
* buf = digits [ 33 ] ;
+ + buf ;
}
}
if ( ! ( type & LEFT ) ) {
while ( size - - > 0 ) {
if ( buf < = end )
* buf = c ;
+ + buf ;
}
}
while ( i < precision - - ) {
if ( buf < = end )
* buf = ' 0 ' ;
+ + buf ;
}
while ( i - - > 0 ) {
if ( buf < = end )
* buf = tmp [ i ] ;
+ + buf ;
}
while ( size - - > 0 ) {
if ( buf < = end )
* buf = ' ' ;
+ + buf ;
}
return buf ;
}
/**
* vsnprintf - Format a string and place it in a buffer
* @ buf : The buffer to place the result into
* @ size : The size of the buffer , including the trailing null space
* @ fmt : The format string to use
* @ args : Arguments for the format string
*
* The return value is the number of characters which would
* be generated for the given input , excluding the trailing
* ' \0 ' , as per ISO C99 . If you want to have the exact
* number of characters written into @ buf as return value
* ( not including the trailing ' \0 ' ) , use vscnprintf . If the
* return is greater than or equal to @ size , the resulting
* string is truncated .
*
* Call this function if you are already dealing with a va_list .
* You probably want snprintf instead .
*/
int vsnprintf ( char * buf , size_t size , const char * fmt , va_list args )
{
int len ;
unsigned long long num ;
int i , base ;
char * str , * end , c ;
const char * s ;
int flags ; /* flags to number() */
int field_width ; /* width of output field */
int precision ; /* min. # of digits for integers; max
number of chars for from string */
int qualifier ; /* 'h', 'l', or 'L' for integer fields */
/* 'z' support added 23/7/1999 S.H. */
/* 'z' changed to 'Z' --davidm 1/25/99 */
2005-08-24 01:48:17 +04:00
/* 't' added for ptrdiff_t */
2005-04-17 02:20:36 +04:00
/* Reject out-of-range values early */
if ( unlikely ( ( int ) size < 0 ) ) {
/* There can be only one.. */
static int warn = 1 ;
WARN_ON ( warn ) ;
warn = 0 ;
return 0 ;
}
str = buf ;
end = buf + size - 1 ;
if ( end < buf - 1 ) {
end = ( ( void * ) - 1 ) ;
size = end - buf + 1 ;
}
for ( ; * fmt ; + + fmt ) {
if ( * fmt ! = ' % ' ) {
if ( str < = end )
* str = * fmt ;
+ + str ;
continue ;
}
/* process flags */
flags = 0 ;
repeat :
+ + fmt ; /* this also skips first '%' */
switch ( * fmt ) {
case ' - ' : flags | = LEFT ; goto repeat ;
case ' + ' : flags | = PLUS ; goto repeat ;
case ' ' : flags | = SPACE ; goto repeat ;
case ' # ' : flags | = SPECIAL ; goto repeat ;
case ' 0 ' : flags | = ZEROPAD ; goto repeat ;
}
/* get field width */
field_width = - 1 ;
if ( isdigit ( * fmt ) )
field_width = skip_atoi ( & fmt ) ;
else if ( * fmt = = ' * ' ) {
+ + fmt ;
/* it's the next argument */
field_width = va_arg ( args , int ) ;
if ( field_width < 0 ) {
field_width = - field_width ;
flags | = LEFT ;
}
}
/* get the precision */
precision = - 1 ;
if ( * fmt = = ' . ' ) {
+ + fmt ;
if ( isdigit ( * fmt ) )
precision = skip_atoi ( & fmt ) ;
else if ( * fmt = = ' * ' ) {
+ + fmt ;
/* it's the next argument */
precision = va_arg ( args , int ) ;
}
if ( precision < 0 )
precision = 0 ;
}
/* get the conversion qualifier */
qualifier = - 1 ;
if ( * fmt = = ' h ' | | * fmt = = ' l ' | | * fmt = = ' L ' | |
2005-08-24 01:48:17 +04:00
* fmt = = ' Z ' | | * fmt = = ' z ' | | * fmt = = ' t ' ) {
2005-04-17 02:20:36 +04:00
qualifier = * fmt ;
+ + fmt ;
if ( qualifier = = ' l ' & & * fmt = = ' l ' ) {
qualifier = ' L ' ;
+ + fmt ;
}
}
/* default base */
base = 10 ;
switch ( * fmt ) {
case ' c ' :
if ( ! ( flags & LEFT ) ) {
while ( - - field_width > 0 ) {
if ( str < = end )
* str = ' ' ;
+ + str ;
}
}
c = ( unsigned char ) va_arg ( args , int ) ;
if ( str < = end )
* str = c ;
+ + str ;
while ( - - field_width > 0 ) {
if ( str < = end )
* str = ' ' ;
+ + str ;
}
continue ;
case ' s ' :
s = va_arg ( args , char * ) ;
if ( ( unsigned long ) s < PAGE_SIZE )
s = " <NULL> " ;
len = strnlen ( s , precision ) ;
if ( ! ( flags & LEFT ) ) {
while ( len < field_width - - ) {
if ( str < = end )
* str = ' ' ;
+ + str ;
}
}
for ( i = 0 ; i < len ; + + i ) {
if ( str < = end )
* str = * s ;
+ + str ; + + s ;
}
while ( len < field_width - - ) {
if ( str < = end )
* str = ' ' ;
+ + str ;
}
continue ;
case ' p ' :
if ( field_width = = - 1 ) {
field_width = 2 * sizeof ( void * ) ;
flags | = ZEROPAD ;
}
str = number ( str , end ,
( unsigned long ) va_arg ( args , void * ) ,
16 , field_width , precision , flags ) ;
continue ;
case ' n ' :
/* FIXME:
* What does C99 say about the overflow case here ? */
if ( qualifier = = ' l ' ) {
long * ip = va_arg ( args , long * ) ;
* ip = ( str - buf ) ;
} else if ( qualifier = = ' Z ' | | qualifier = = ' z ' ) {
size_t * ip = va_arg ( args , size_t * ) ;
* ip = ( str - buf ) ;
} else {
int * ip = va_arg ( args , int * ) ;
* ip = ( str - buf ) ;
}
continue ;
case ' % ' :
if ( str < = end )
* str = ' % ' ;
+ + str ;
continue ;
/* integer number formats - set up the flags and "break" */
case ' o ' :
base = 8 ;
break ;
case ' X ' :
flags | = LARGE ;
case ' x ' :
base = 16 ;
break ;
case ' d ' :
case ' i ' :
flags | = SIGN ;
case ' u ' :
break ;
default :
if ( str < = end )
* str = ' % ' ;
+ + str ;
if ( * fmt ) {
if ( str < = end )
* str = * fmt ;
+ + str ;
} else {
- - fmt ;
}
continue ;
}
if ( qualifier = = ' L ' )
num = va_arg ( args , long long ) ;
else if ( qualifier = = ' l ' ) {
num = va_arg ( args , unsigned long ) ;
if ( flags & SIGN )
num = ( signed long ) num ;
} else if ( qualifier = = ' Z ' | | qualifier = = ' z ' ) {
num = va_arg ( args , size_t ) ;
2005-08-24 01:48:17 +04:00
} else if ( qualifier = = ' t ' ) {
num = va_arg ( args , ptrdiff_t ) ;
2005-04-17 02:20:36 +04:00
} else if ( qualifier = = ' h ' ) {
num = ( unsigned short ) va_arg ( args , int ) ;
if ( flags & SIGN )
num = ( signed short ) num ;
} else {
num = va_arg ( args , unsigned int ) ;
if ( flags & SIGN )
num = ( signed int ) num ;
}
str = number ( str , end , num , base ,
field_width , precision , flags ) ;
}
if ( str < = end )
* str = ' \0 ' ;
else if ( size > 0 )
/* don't write out a null byte if the buf size is zero */
* end = ' \0 ' ;
/* the trailing null byte doesn't count towards the total
* + + str ;
*/
return str - buf ;
}
EXPORT_SYMBOL ( vsnprintf ) ;
/**
* vscnprintf - Format a string and place it in a buffer
* @ buf : The buffer to place the result into
* @ size : The size of the buffer , including the trailing null space
* @ fmt : The format string to use
* @ args : Arguments for the format string
*
* The return value is the number of characters which have been written into
* the @ buf not including the trailing ' \0 ' . If @ size is < = 0 the function
* returns 0.
*
* Call this function if you are already dealing with a va_list .
* You probably want scnprintf instead .
*/
int vscnprintf ( char * buf , size_t size , const char * fmt , va_list args )
{
int i ;
i = vsnprintf ( buf , size , fmt , args ) ;
return ( i > = size ) ? ( size - 1 ) : i ;
}
EXPORT_SYMBOL ( vscnprintf ) ;
/**
* snprintf - Format a string and place it in a buffer
* @ buf : The buffer to place the result into
* @ size : The size of the buffer , including the trailing null space
* @ fmt : The format string to use
* @ . . . : Arguments for the format string
*
* The return value is the number of characters which would be
* generated for the given input , excluding the trailing null ,
* as per ISO C99 . If the return is greater than or equal to
* @ size , the resulting string is truncated .
*/
int snprintf ( char * buf , size_t size , const char * fmt , . . . )
{
va_list args ;
int i ;
va_start ( args , fmt ) ;
i = vsnprintf ( buf , size , fmt , args ) ;
va_end ( args ) ;
return i ;
}
EXPORT_SYMBOL ( snprintf ) ;
/**
* scnprintf - Format a string and place it in a buffer
* @ buf : The buffer to place the result into
* @ size : The size of the buffer , including the trailing null space
* @ fmt : The format string to use
* @ . . . : Arguments for the format string
*
* The return value is the number of characters written into @ buf not including
* the trailing ' \0 ' . If @ size is < = 0 the function returns 0. If the return is
* greater than or equal to @ size , the resulting string is truncated .
*/
int scnprintf ( char * buf , size_t size , const char * fmt , . . . )
{
va_list args ;
int i ;
va_start ( args , fmt ) ;
i = vsnprintf ( buf , size , fmt , args ) ;
va_end ( args ) ;
return ( i > = size ) ? ( size - 1 ) : i ;
}
EXPORT_SYMBOL ( scnprintf ) ;
/**
* vsprintf - Format a string and place it in a buffer
* @ buf : The buffer to place the result into
* @ fmt : The format string to use
* @ args : Arguments for the format string
*
* The function returns the number of characters written
* into @ buf . Use vsnprintf or vscnprintf in order to avoid
* buffer overflows .
*
* Call this function if you are already dealing with a va_list .
* You probably want sprintf instead .
*/
int vsprintf ( char * buf , const char * fmt , va_list args )
{
return vsnprintf ( buf , INT_MAX , fmt , args ) ;
}
EXPORT_SYMBOL ( vsprintf ) ;
/**
* sprintf - Format a string and place it in a buffer
* @ buf : The buffer to place the result into
* @ fmt : The format string to use
* @ . . . : Arguments for the format string
*
* The function returns the number of characters written
* into @ buf . Use snprintf or scnprintf in order to avoid
* buffer overflows .
*/
int sprintf ( char * buf , const char * fmt , . . . )
{
va_list args ;
int i ;
va_start ( args , fmt ) ;
i = vsnprintf ( buf , INT_MAX , fmt , args ) ;
va_end ( args ) ;
return i ;
}
EXPORT_SYMBOL ( sprintf ) ;
/**
* vsscanf - Unformat a buffer into a list of arguments
* @ buf : input buffer
* @ fmt : format of buffer
* @ args : arguments
*/
int vsscanf ( const char * buf , const char * fmt , va_list args )
{
const char * str = buf ;
char * next ;
char digit ;
int num = 0 ;
int qualifier ;
int base ;
int field_width ;
int is_sign = 0 ;
while ( * fmt & & * str ) {
/* skip any white space in format */
/* white space in format matchs any amount of
* white space , including none , in the input .
*/
if ( isspace ( * fmt ) ) {
while ( isspace ( * fmt ) )
+ + fmt ;
while ( isspace ( * str ) )
+ + str ;
}
/* anything that is not a conversion must match exactly */
if ( * fmt ! = ' % ' & & * fmt ) {
if ( * fmt + + ! = * str + + )
break ;
continue ;
}
if ( ! * fmt )
break ;
+ + fmt ;
/* skip this conversion.
* advance both strings to next white space
*/
if ( * fmt = = ' * ' ) {
while ( ! isspace ( * fmt ) & & * fmt )
fmt + + ;
while ( ! isspace ( * str ) & & * str )
str + + ;
continue ;
}
/* get field width */
field_width = - 1 ;
if ( isdigit ( * fmt ) )
field_width = skip_atoi ( & fmt ) ;
/* get conversion qualifier */
qualifier = - 1 ;
if ( * fmt = = ' h ' | | * fmt = = ' l ' | | * fmt = = ' L ' | |
* fmt = = ' Z ' | | * fmt = = ' z ' ) {
qualifier = * fmt + + ;
if ( unlikely ( qualifier = = * fmt ) ) {
if ( qualifier = = ' h ' ) {
qualifier = ' H ' ;
fmt + + ;
} else if ( qualifier = = ' l ' ) {
qualifier = ' L ' ;
fmt + + ;
}
}
}
base = 10 ;
is_sign = 0 ;
if ( ! * fmt | | ! * str )
break ;
switch ( * fmt + + ) {
case ' c ' :
{
char * s = ( char * ) va_arg ( args , char * ) ;
if ( field_width = = - 1 )
field_width = 1 ;
do {
* s + + = * str + + ;
} while ( - - field_width > 0 & & * str ) ;
num + + ;
}
continue ;
case ' s ' :
{
char * s = ( char * ) va_arg ( args , char * ) ;
if ( field_width = = - 1 )
field_width = INT_MAX ;
/* first, skip leading white space in buffer */
while ( isspace ( * str ) )
str + + ;
/* now copy until next white space */
while ( * str & & ! isspace ( * str ) & & field_width - - ) {
* s + + = * str + + ;
}
* s = ' \0 ' ;
num + + ;
}
continue ;
case ' n ' :
/* return number of characters read so far */
{
int * i = ( int * ) va_arg ( args , int * ) ;
* i = str - buf ;
}
continue ;
case ' o ' :
base = 8 ;
break ;
case ' x ' :
case ' X ' :
base = 16 ;
break ;
case ' i ' :
base = 0 ;
case ' d ' :
is_sign = 1 ;
case ' u ' :
break ;
case ' % ' :
/* looking for '%' in str */
if ( * str + + ! = ' % ' )
return num ;
continue ;
default :
/* invalid format; stop here */
return num ;
}
/* have some sort of integer conversion.
* first , skip white space in buffer .
*/
while ( isspace ( * str ) )
str + + ;
digit = * str ;
if ( is_sign & & digit = = ' - ' )
digit = * ( str + 1 ) ;
if ( ! digit
| | ( base = = 16 & & ! isxdigit ( digit ) )
| | ( base = = 10 & & ! isdigit ( digit ) )
| | ( base = = 8 & & ( ! isdigit ( digit ) | | digit > ' 7 ' ) )
| | ( base = = 0 & & ! isdigit ( digit ) ) )
break ;
switch ( qualifier ) {
case ' H ' : /* that's 'hh' in format */
if ( is_sign ) {
signed char * s = ( signed char * ) va_arg ( args , signed char * ) ;
* s = ( signed char ) simple_strtol ( str , & next , base ) ;
} else {
unsigned char * s = ( unsigned char * ) va_arg ( args , unsigned char * ) ;
* s = ( unsigned char ) simple_strtoul ( str , & next , base ) ;
}
break ;
case ' h ' :
if ( is_sign ) {
short * s = ( short * ) va_arg ( args , short * ) ;
* s = ( short ) simple_strtol ( str , & next , base ) ;
} else {
unsigned short * s = ( unsigned short * ) va_arg ( args , unsigned short * ) ;
* s = ( unsigned short ) simple_strtoul ( str , & next , base ) ;
}
break ;
case ' l ' :
if ( is_sign ) {
long * l = ( long * ) va_arg ( args , long * ) ;
* l = simple_strtol ( str , & next , base ) ;
} else {
unsigned long * l = ( unsigned long * ) va_arg ( args , unsigned long * ) ;
* l = simple_strtoul ( str , & next , base ) ;
}
break ;
case ' L ' :
if ( is_sign ) {
long long * l = ( long long * ) va_arg ( args , long long * ) ;
* l = simple_strtoll ( str , & next , base ) ;
} else {
unsigned long long * l = ( unsigned long long * ) va_arg ( args , unsigned long long * ) ;
* l = simple_strtoull ( str , & next , base ) ;
}
break ;
case ' Z ' :
case ' z ' :
{
size_t * s = ( size_t * ) va_arg ( args , size_t * ) ;
* s = ( size_t ) simple_strtoul ( str , & next , base ) ;
}
break ;
default :
if ( is_sign ) {
int * i = ( int * ) va_arg ( args , int * ) ;
* i = ( int ) simple_strtol ( str , & next , base ) ;
} else {
unsigned int * i = ( unsigned int * ) va_arg ( args , unsigned int * ) ;
* i = ( unsigned int ) simple_strtoul ( str , & next , base ) ;
}
break ;
}
num + + ;
if ( ! next )
break ;
str = next ;
}
return num ;
}
EXPORT_SYMBOL ( vsscanf ) ;
/**
* sscanf - Unformat a buffer into a list of arguments
* @ buf : input buffer
* @ fmt : formatting of buffer
* @ . . . : resulting arguments
*/
int sscanf ( const char * buf , const char * fmt , . . . )
{
va_list args ;
int i ;
va_start ( args , fmt ) ;
i = vsscanf ( buf , fmt , args ) ;
va_end ( args ) ;
return i ;
}
EXPORT_SYMBOL ( sscanf ) ;