/* * %CopyrightBegin% * * Copyright Ericsson AB 2005-2016. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * %CopyrightEnd% */ /* * fmt: * '%' * [ [.]][] * * flag: # | O | - | | + | ' | I * width: [0-9]+ | '*' * precision: [0-9]+ | '*' * length: hh | h | l | ll | L | j | t | b * conversion: d,i | o,u,x,X | e,E | f,F | g,G | a,A | c | s | T | R | * p | n | % * sz: 8 | 16 | 32 | 64 | p | e */ /* Without this, variable argument lists break on VxWorks */ #ifdef VXWORKS #include #endif #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef __WIN32__ #undef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #include #endif #include #include #include #include #include #include "erl_errno.h" #include #include "erl_printf.h" #include "erl_printf_format.h" #ifdef DEBUG #include #define ASSERT(X) assert(X) #else #define ASSERT(X) #endif #ifdef __WIN32__ #define long_long LONGLONG #define signed_long_long LONGLONG #define unsigned_long_long ULONGLONG #undef SIZEOF_LONG_LONG #define SIZEOF_LONG_LONG 8 #else #if SIZEOF_LONG_LONG #define long_long long long #define signed_long_long signed long long #define unsigned_long_long unsigned long long #endif #endif #ifndef ERTS_SIZEOF_ETERM #define ERTS_SIZEOF_ETERM SIZEOF_VOID_P #endif #if defined(__GNUC__) # undef inline # define inline __inline__ #elif defined(__WIN32__) # undef inline # define inline __forceinline #else # ifndef inline # define inline # endif #endif #define FMTC_d 0x0000 /*empty 0x0001 was RELATIVE */ #define FMTC_o 0x0002 #define FMTC_u 0x0003 #define FMTC_x 0x0004 #define FMTC_X 0x0005 #define FMTC_e 0x0006 #define FMTC_E 0x0007 #define FMTC_f 0x0008 #define FMTC_T 0x0009 #define FMTC_g 0x000a #define FMTC_G 0x000b #define FMTC_c 0x000c #define FMTC_s 0x000d #define FMTC_p 0x000e #define FMTC_n 0x000f #define FMTC_MASK 0x000f #define FMTL_no 0x0000 #define FMTL_hh 0x0010 #define FMTL_h 0x0020 #define FMTL_l 0x0030 #define FMTL_ll 0x0040 #define FMTL_L 0x0050 #define FMTL_j 0x0060 #define FMTL_t 0x0070 #define FMTL_MASK 0x00f0 #define FMTF_alt 0x0100 /* # alterlate form ie 0x */ #define FMTF_pad 0x0200 /* 0 zero pad */ #define FMTF_adj 0x0400 /* left adjust */ #define FMTF_blk 0x0800 /* add blank */ #define FMTF_sgn 0x1000 /* add sign */ #define FMTF_cnv 0x2000 /* decimal conversion */ #define FMTF_cnV 0x4000 /* alternate decimal conversion */ #define FMTF_MASK 0x7f00 static char zeros[] = "00000000000000000000000000000000"; static char blanks[] = " "; static char hex[] = "0123456789abcdef"; static char heX[] = "0123456789ABCDEF"; #define FMT(fn,arg,buf,len,count) do { \ int res__ = (fn)((arg),(buf),(len)); \ if (res__ < 0) \ return res__; \ (count) += (len); \ } while(0) #define FILL(fn,arg,cs,len, count) do { \ int __i = (len); \ while(__i >= sizeof(cs)-1) { \ FMT((fn),(arg),(cs),sizeof(cs)-1,(count)); \ __i -= sizeof(cs)-1; \ } \ if (__i) FMT((fn),(arg),(cs),__i,(count)); \ } while(0) #define BLANKS(fn,arg,n,count) FILL((fn),(arg),blanks,(n),count) #define ZEROS(fn,arg,n,count) FILL((fn),(arg),zeros,(n),count) #define SIGN(X) ((X) > 0 ? 1 : ((X) < 0 ? -1 : 0)) #define USIGN(X) ((X) == 0 ? 0 : 1) int (*erts_printf_eterm_func)(fmtfn_t, void*, ErlPfEterm, long) = NULL; static int noop_fn(void *vfp, char* buf, size_t len) { return 0; } static int fmt_fld(fmtfn_t fn,void* arg, char* wbuf, int w, int sign, int width,int precision,int fmt,int* count) { char prefix[8]; char* pp = prefix; int pw = 0; int len; /* format the prefix */ if ((sign || (fmt & (FMTF_sgn|FMTF_blk))) && (fmt & FMTC_MASK) == FMTC_d) { if (sign < 0) *pp++ = '-'; else if ((fmt & FMTF_sgn)) *pp++ = '+'; else if (fmt & FMTF_blk) *pp++ = ' '; } if ((fmt & FMTF_alt)) { switch((fmt & FMTC_MASK)) { case FMTC_X: *pp++ = '0'; *pp++ = 'X'; break; case FMTC_x: *pp++ = '0'; *pp++ = 'x'; break; case FMTC_o: *pp++ = '0'; if (precision>1) precision--; break; } } pw = pp-prefix; len = ((w < precision) ? precision : w) + pw; if (fmt & FMTF_adj) { /* left adjust */ if (pw) FMT(fn,arg,prefix,pw,*count); if (w < precision) ZEROS(fn,arg,precision-w,*count); FMT(fn,arg, wbuf, w, *count); if (len < width) BLANKS(fn,arg,width-len,*count); } else if ((fmt & FMTF_pad) && (precision<0)) { /* pad zeros */ if (pw) FMT(fn,arg, prefix, pw, *count); if (w < precision) ZEROS(fn, arg, precision-w, *count); if (len < width) ZEROS(fn,arg,width-len,*count); FMT(fn,arg,wbuf,w,*count); } else { if (len < width) BLANKS(fn,arg,width-len,*count); if (pw) FMT(fn,arg,prefix,pw,*count); if (w < precision) ZEROS(fn,arg,precision-w,*count); FMT(fn,arg,wbuf,w,*count); } return 0; } static int fmt_uword(fmtfn_t fn,void* arg,int sign,ErlPfUWord uval, int width,int precision,int fmt,int* count) { char buf[32]; int base = 10; int w = 0; char* dc = hex; char* p = buf+sizeof(buf); switch(fmt & FMTC_MASK) { case FMTC_d: case FMTC_u: break; case FMTC_o: base = 8; break; case FMTC_X: dc = heX; case FMTC_x: base = 16; break; default: return -EINVAL; } /* format the unsigned value */ if (!sign && precision) { *--p = '0'; w++; } else { while(uval) { *--p = dc[(uval % base)]; uval /= base; w++; } } return fmt_fld(fn, arg, p, w, sign, width, precision, fmt, count); } #if SIZEOF_LONG_LONG static inline int do_div(unsigned_long_long *n, unsigned_long_long base) { unsigned_long_long q = *n/base; int mod = (int) (*n - q*base); *n = q; return mod; } static int fmt_long_long(fmtfn_t fn,void* arg,int sign, unsigned_long_long uval, int width,int precision,int fmt,int* count) { char buf[32]; int base = 10; int w = 0; char* dc = hex; char* p = buf+sizeof(buf); switch(fmt & FMTC_MASK) { case FMTC_d: case FMTC_u: break; case FMTC_o: base = 8; break; case FMTC_X: dc = heX; case FMTC_x: base = 16; break; default: return -EINVAL; } /* format the unsigned value */ if (!sign && precision) { *--p = '0'; w++; } else { while(uval) { int m = do_div(&uval,base); *--p = dc[m]; w++; } } return fmt_fld(fn, arg, p, w, sign, width, precision, fmt, count); } #endif /* #if SIZEOF_LONG_LONG */ static int fmt_double(fmtfn_t fn,void*arg,double val, int width, int precision, int fmt,int* count) { int res; int fi = 0; char format_str[8]; char sbuf[32]; char *bufp = sbuf; double dexp; int exp; size_t max_size = 2; /* including possible sign */ int size; int new_fmt = fmt; int fpe_was_unmasked; fpe_was_unmasked = erts_printf_block_fpe ? (*erts_printf_block_fpe)() : 0; if (val < 0.0) dexp = log10(-val); else if (val == 0.0) dexp = 0.0; else dexp = log10(val); exp = (int) dexp; new_fmt &= ~FMTF_sgn; new_fmt &= ~FMTF_blk; format_str[fi++] = '%'; if (fmt & FMTF_alt) format_str[fi++] = '#'; if (fmt & FMTF_sgn) format_str[fi++] = '+'; else if (fmt & FMTF_blk) format_str[fi++] = ' '; format_str[fi++] = '0'; format_str[fi++] = '.'; format_str[fi++] = '*'; switch(fmt & FMTC_MASK) { case FMTC_G: format_str[fi] = 'E'; goto gG_common; case FMTC_g: format_str[fi] = 'e'; gG_common: if (dexp < -4.0 || exp >= precision) { fi++; precision--; if (precision < 1) precision = 1; goto eE_common; } /* fall through ... */ case FMTC_f: format_str[fi++] = 'f'; max_size += exp > 0 ? exp : 1; max_size++; if (precision) max_size += precision; else if (fmt & FMTF_alt) max_size++; break; case FMTC_E: format_str[fi++] = 'E'; goto eE_common; case FMTC_e: format_str[fi++] = 'e'; eE_common: { int aexp; max_size += 4; if (precision) max_size += precision; else if (fmt & FMTF_alt) max_size++; aexp = exp >= 0 ? exp : -exp; if (aexp < 100) max_size += 2; else { while (aexp) { max_size++; aexp /= 10; } } break; } default: res = -EINVAL; goto out; } format_str[fi++] = '\0'; ASSERT(fi <= sizeof(format_str)); max_size++; /* '\0' */ if (max_size >= sizeof(sbuf)) { bufp = (char *) malloc(sizeof(char)*max_size); if (!bufp) { res = -ENOMEM; /* Make sure not to trigger free */ bufp = sbuf; goto out; } } size = sprintf(bufp, format_str, precision, val); if (size < 0) { if (errno > 0) res = -errno; else res = -EIO; goto out; } ASSERT(max_size >= size); res = fmt_fld(fn, arg, bufp, size, 0, width, 0, new_fmt, count); out: if (bufp != sbuf) free((void *) bufp); if (erts_printf_unblock_fpe) (*erts_printf_unblock_fpe)(fpe_was_unmasked); return res; } /* strnlen doesn't exist everywhere */ static size_t my_strnlen(const char *s, size_t maxlen) { size_t i = 0; while (i < maxlen && s[i] != '\0') i++; return i; } int erts_printf_format(fmtfn_t fn, void* arg, char* fmt, va_list ap) { char* ptr0 = fmt; char* ptr = ptr0; int count = 0; int n; int res = 0; while(*ptr) { ErlPfUWord ul_val; int fmt = 0; int width = -1; int precision = -1; if (res < 0) return res; if (*ptr == '%') { if ((n=ptr-ptr0)) FMT(fn,arg,ptr0,n,count); ptr++; do_flag: switch(*ptr) { case '#': fmt |= FMTF_alt; ptr++; goto do_flag; case '0': fmt |= FMTF_pad; ptr++; goto do_flag; case '-': fmt |= FMTF_adj; ptr++; goto do_flag; case ' ': fmt |= FMTF_blk; ptr++; goto do_flag; case '+': fmt |= FMTF_sgn; ptr++; goto do_flag; case '\'': fmt |= FMTF_cnv; ptr++; goto do_flag; case 'I': fmt |= FMTF_cnV; ptr++; goto do_flag; } /* width */ if (*ptr == '*') { width = va_arg(ap, int); ptr++; } else if (isdigit((int) *ptr)) { width = *ptr++ - '0'; while(isdigit((int) *ptr)) width = 10*width + (*ptr++ - '0'); } /* precision */ if (*ptr == '.') { ptr++; if (*ptr == '*') { precision = va_arg(ap, int); ptr++; } else if (isdigit((int) *ptr)) { precision = *ptr++ - '0'; while(isdigit((int) *ptr)) precision = 10*precision + (*ptr++ - '0'); } } /* length modifier */ switch(*ptr) { case 'b': { ptr++; if (*ptr == 'p') { ptr++; #if SIZEOF_INT == SIZEOF_VOID_P #elif SIZEOF_LONG == SIZEOF_VOID_P fmt |= FMTL_l; #elif SIZEOF_LONG_LONG == SIZEOF_VOID_P fmt |= FMTL_ll; #else #error No integer datatype with the same size as 'void *' found #endif } else if (*ptr == 'e') { ptr++; #if SIZEOF_INT == ERTS_SIZEOF_ETERM #elif SIZEOF_LONG == ERTS_SIZEOF_ETERM fmt |= FMTL_l; #elif SIZEOF_LONG_LONG == ERTS_SIZEOF_ETERM fmt |= FMTL_ll; #else #error No integer datatype with the same size as Eterm found #endif } else { int bits = 0; while(isdigit((int) *ptr)) bits = 10*bits + (*ptr++ - '0'); switch (bits) { case 64: #if SIZEOF_INT == 8 #elif SIZEOF_LONG == 8 fmt |= FMTL_l; #elif SIZEOF_LONG_LONG == 8 fmt |= FMTL_ll; #else #error No 64-bit integer datatype found #endif break; case 32: #if SIZEOF_INT == 4 #elif SIZEOF_SHORT == 4 fmt |= FMTL_h; #elif SIZEOF_LONG == 4 fmt |= FMTL_l; #elif SIZEOF_LONG_LONG == 4 fmt |= FMTL_ll; #else #error No 32-bit integer datatype found #endif break; case 16: #if SIZEOF_INT == 2 #elif SIZEOF_SHORT == 2 fmt |= FMTL_h; #elif SIZEOF_LONG == 2 fmt |= FMTL_l; #else #error No 16-bit integer datatype found #endif case 8: #if SIZEOF_CHAR == 1 fmt |= FMTL_hh; #else #error Unexpected size of char #endif break; default: return -EINVAL; } } break; } case 'h': ptr++; if (*ptr == 'h') { ptr++; fmt |= FMTL_hh; } else fmt |= FMTL_h; break; case 'l': ptr++; if (*ptr == 'l') { ptr++; #if SIZEOF_LONG_LONG fmt |= FMTL_ll; #else fmt |= FMTL_l; #endif } else fmt |= FMTL_l; break; case 'L': ptr++; fmt |= FMTL_L; break; case 'j': ptr++; fmt |= FMTL_j; break; case 't': ptr++; fmt |= FMTL_t; break; } /* specifier */ switch(*ptr) { case 'd': ptr++; fmt |= FMTC_d; break; case 'i': ptr++; fmt |= FMTC_d; break; case 'o': ptr++; fmt |= FMTC_o; break; case 'u': ptr++; fmt |= FMTC_u; break; case 'x': ptr++; fmt |= FMTC_x; break; case 'X': ptr++; fmt |= FMTC_X; break; case 'e': ptr++; fmt |= FMTC_e; break; case 'E': ptr++; fmt |= FMTC_E; break; case 'f': ptr++; fmt |= FMTC_f; break; case 'g': ptr++; fmt |= FMTC_g; break; case 'G': ptr++; fmt |= FMTC_G; break; case 'c': ptr++; fmt |= FMTC_c; break; case 's': ptr++; fmt |= FMTC_s; break; case 'p': ptr++; fmt |= FMTC_p; break; case 'n': ptr++; fmt |= FMTC_n; break; case 'T': ptr++; fmt |= FMTC_T; break; case '%': FMT(fn,arg,ptr,1,count); ptr++; ptr0 = ptr; continue; default: /* ignore */ ptr0 = ptr; continue; } switch(fmt & FMTC_MASK) { case FMTC_d: switch(fmt & FMTL_MASK) { case FMTL_hh: { signed char tval = (signed char) va_arg(ap,int); ul_val = (ErlPfUWord) (tval < 0 ? (-tval) : tval); res = fmt_uword(fn,arg,SIGN(tval),ul_val, width,precision,fmt,&count); break; } case FMTL_h: { signed short tval = (signed short) va_arg(ap,int); ul_val = (ErlPfUWord) (tval < 0 ? (-tval) : tval); res = fmt_uword(fn,arg,SIGN(tval),ul_val, width,precision,fmt,&count); break; } case FMTL_l: { signed long tval = (signed long) va_arg(ap,long); ul_val = (ErlPfUWord) (tval < 0 ? (-tval) : tval); res = fmt_uword(fn,arg,SIGN(tval),ul_val, width,precision,fmt,&count); break; } #if SIZEOF_LONG_LONG case FMTL_ll: { unsigned_long_long ull_val; signed_long_long tval; tval = (signed_long_long) va_arg(ap,long_long); ull_val = (unsigned_long_long) (tval < 0 ? (-tval) : tval); res = fmt_long_long(fn,arg,SIGN(tval),ull_val, width,precision,fmt,&count); break; } #endif default: { signed int tval = (signed int) va_arg(ap,int); ul_val = (ErlPfUWord) (tval < 0 ? (-tval) : tval); res = fmt_uword(fn,arg,SIGN(tval),ul_val, width,precision,fmt,&count); break; } } break; case FMTC_o: case FMTC_u: case FMTC_x: case FMTC_X: switch(fmt & FMTL_MASK) { case FMTL_hh: { unsigned char tval = (unsigned char) va_arg(ap,int); ul_val = (ErlPfUWord) tval; res = fmt_uword(fn,arg,USIGN(tval),ul_val, width,precision,fmt,&count); break; } case FMTL_h: { unsigned short tval = (unsigned short) va_arg(ap,int); ul_val = (ErlPfUWord) tval; res = fmt_uword(fn,arg,USIGN(tval),ul_val, width,precision,fmt,&count); break; } case FMTL_l: { ul_val = (ErlPfUWord) va_arg(ap,long); res = fmt_uword(fn,arg,USIGN(ul_val),ul_val, width,precision,fmt,&count); break; } #if SIZEOF_LONG_LONG case FMTL_ll: { unsigned_long_long ull_val; ull_val = (signed_long_long) va_arg(ap,long_long); res = fmt_long_long(fn,arg,USIGN(ull_val),ull_val, width,precision,fmt,&count); break; } #endif default: { unsigned int tval = (unsigned int) va_arg(ap,int); ul_val = (ErlPfUWord) tval; res = fmt_uword(fn,arg,USIGN(tval),ul_val, width,precision,fmt,&count); break; } } break; case FMTC_e: case FMTC_E: case FMTC_f: case FMTC_g: case FMTC_G: if (precision < 0) precision = 6; switch(fmt & FMTL_MASK) { case FMTL_L: return -EINVAL; break; default: res = fmt_double(fn,arg,va_arg(ap,double), width,precision,fmt,&count); break; } break; case FMTC_c: { /* fixme: add wide char support l-modifier */ char c = va_arg(ap,int); int len = 1; if (precision == 0) len = 0; if (width > 0 && !(fmt & FMTF_adj)) { if (width > len) BLANKS(fn, arg, width - len, count); } if (len) FMT(fn,arg,&c,len,count); if (width > len && fmt & FMTF_adj) BLANKS(fn, arg, width - len, count); break; } case FMTC_s: { char* str = va_arg(ap,char*); int len = (precision >= 0) ? my_strnlen(str,precision) : strlen(str); if (width > 0 && !(fmt & FMTF_adj)) { if (width > len) BLANKS(fn, arg, width - len, count); } if (len) FMT(fn,arg,str,len,count); if (width > len && fmt & FMTF_adj) BLANKS(fn, arg, width - len, count); break; } case FMTC_p: { void* addr = va_arg(ap, void*); res = fmt_uword(fn, arg, USIGN((ErlPfUWord) addr), (ErlPfUWord) addr, width < 0 ? ((int) 2*sizeof(void *)) : width, (precision < 0 ? ((int) 2*sizeof(void *)) : precision), FMTC_x|FMTF_pad|FMTF_alt, &count); break; } case FMTC_n: switch(fmt & FMTL_MASK) { case FMTL_hh: *va_arg(ap,char*) = count; break; case FMTL_h: *va_arg(ap,short*) = count; break; case FMTL_l: *va_arg(ap,long*) = count; break; #if SIZEOF_LONG_LONG case FMTL_ll: *va_arg(ap,long_long*) = count; break; #endif default: *va_arg(ap,int*) = count; break; } break; case FMTC_T: { /* Eterm */ long prec; ErlPfEterm eterm; if (!erts_printf_eterm_func) return -EINVAL; if (precision < 0) prec = 100000; else if (precision == INT_MAX) prec = LONG_MAX; else prec = (long) precision; eterm = va_arg(ap, ErlPfEterm); if (width > 0 && !(fmt & FMTF_adj)) { res = (*erts_printf_eterm_func)(noop_fn, NULL, eterm, prec); if (res < 0) return res; if (width > res) BLANKS(fn, arg, width - res, count); } res = (*erts_printf_eterm_func)(fn, arg, eterm, prec); if (res < 0) return res; count += res; if (width > res && fmt & FMTF_adj) BLANKS(fn, arg, width - res, count); break; } default: if ((n=ptr-ptr0)) FMT(fn,arg,ptr0,n,count); } ptr0 = ptr; } else ptr++; } if ((n=ptr-ptr0)) FMT(fn,arg,ptr0,n,count); return count; } int erts_printf_char(fmtfn_t fn, void *arg, char c) { return (*fn)(arg, &c, 1); } int erts_printf_string(fmtfn_t fn, void *arg, char *str) { size_t sz = strlen(str); return (*fn)(arg, str, sz); } int erts_printf_buf(fmtfn_t fn, void *arg, char *buf, size_t sz) { return (*fn)(arg, buf, sz); } int erts_printf_pointer(fmtfn_t fn, void *arg, void *ptr) { int count = 0; int res = fmt_uword(fn, arg, USIGN((ErlPfUWord) ptr), (ErlPfUWord) ptr, 2*sizeof(void *), 2*sizeof(void *), FMTC_x|FMTF_pad|FMTF_alt, &count); if (res < 0) return res; return count; } int erts_printf_uword(fmtfn_t fn, void *arg, char conv, int pad, int width, ErlPfUWord val) { int count = 0; int res; int fmt = 0; int prec = -1; switch (conv) { case 'o': fmt |= FMTC_o; break; case 'u': fmt |= FMTC_u; break; case 'x': fmt |= FMTC_x; break; case 'X': fmt |= FMTC_X; break; case 'p': fmt |= FMTC_p; break; default: return -EINVAL; } if (pad) prec = width; res = fmt_uword(fn, arg, USIGN(val), val, width, prec, fmt, &count); if (res < 0) return res; return count; } int erts_printf_sword(fmtfn_t fn, void *arg, char conv, int pad, int width, ErlPfSWord val) { int count = 0; int res; int fmt = 0; int prec = -1; ErlPfUWord ul_val; switch (conv) { case 'd': fmt |= FMTC_d; break; case 'i': fmt |= FMTC_d; break; case 'o': fmt |= FMTC_o; break; case 'x': fmt |= FMTC_x; break; case 'X': fmt |= FMTC_X; break; default: return -EINVAL; } if (pad) prec = width; ul_val = (ErlPfUWord) (val < 0 ? -val : val); res = fmt_uword(fn, arg, SIGN(val), ul_val, width, prec, fmt, &count); if (res < 0) return res; return count; } int erts_printf_double(fmtfn_t fn, void *arg, char conv, int precision, int width, double val) { int count = 0; int res; int fmt = 0; switch (conv) { case 'e': fmt |= FMTC_e; break; case 'E': fmt |= FMTC_E; break; case 'f': fmt |= FMTC_f; break; case 'g': fmt |= FMTC_g; break; case 'G': fmt |= FMTC_G; break; default: return -EINVAL; } res = fmt_double(fn, arg, val, width, precision, fmt, &count); if (res < 0) return res; return count; }