From 4f49b16079b27d7ce633619e4cddfdf2bffbc952 Mon Sep 17 00:00:00 2001 From: cashmere Date: Fri, 4 Sep 2026 09:05:24 +0200 Subject: initial commit --- src/printf.c | 1318 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1318 insertions(+) create mode 100644 src/printf.c (limited to 'src/printf.c') diff --git a/src/printf.c b/src/printf.c new file mode 100644 index 0000000..137f05c --- /dev/null +++ b/src/printf.c @@ -0,0 +1,1318 @@ +/* +** Copyright (c) 2006 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains implementations of routines for formatting output +** (ex: mprintf()) and for output to the console. +*/ +#include "config.h" +#include "printf.h" +#if defined(_WIN32) +# include +# include +#endif +#include + +/* Two custom conversions are used to show a prefix of artifact hashes: +** +** %!S Prefix of a length appropriate for URLs +** %S Prefix of a length appropriate for human display +** +** The following macros help determine those lengths. FOSSIL_HASH_DIGITS +** is the default number of digits to display to humans. This value can +** be overridden using the hash-digits setting. FOSSIL_HASH_DIGITS_URL +** is the minimum number of digits to be used in URLs. The number used +** will always be at least 6 more than the number used for human output, +** or HNAME_MAX, whichever is least. +*/ +#ifndef FOSSIL_HASH_DIGITS +# define FOSSIL_HASH_DIGITS 10 /* For %S (human display) */ +#endif +#ifndef FOSSIL_HASH_DIGITS_URL +# define FOSSIL_HASH_DIGITS_URL 16 /* For %!S (embedded in URLs) */ +#endif + +/* +** Return the number of artifact hash digits to display. The number is for +** human output if the bForUrl is false and is destined for a URL if +** bForUrl is false. +*/ +int hash_digits(int bForUrl){ + static int nDigitHuman = 0; + static int nDigitUrl = 0; + if( nDigitHuman==0 ){ + nDigitHuman = db_get_int("hash-digits", FOSSIL_HASH_DIGITS); + if( nDigitHuman < 6 ) nDigitHuman = 6; + if( nDigitHuman > HNAME_MAX ) nDigitHuman = HNAME_MAX; + nDigitUrl = nDigitHuman + 6; + if( nDigitUrl < FOSSIL_HASH_DIGITS_URL ) nDigitUrl = FOSSIL_HASH_DIGITS_URL; + if( nDigitUrl > HNAME_MAX ) nDigitUrl = HNAME_MAX; + } + return bForUrl ? nDigitUrl : nDigitHuman; +} + +/* +** Return the number of characters in a %S output. +*/ +int length_of_S_display(void){ + return hash_digits(0); +} + +/* +** Conversion types fall into various categories as defined by the +** following enumeration. +*/ +#define etRADIX 1 /* Integer types. %d, %x, %o, and so forth */ +#define etFLOAT 2 /* Floating point. %f */ +#define etEXP 3 /* Exponential notation. %e and %E */ +#define etGENERIC 4 /* Floating or exponential, depending on exponent. %g */ +#define etSIZE 5 /* Return number of characters processed so far. %n */ +#define etSTRING 6 /* Strings. %s */ +#define etDYNSTRING 7 /* Dynamically allocated strings. %z */ +#define etPERCENT 8 /* Percent symbol. %% */ +#define etCHARX 9 /* Characters. %c */ +#define etERROR 10 /* Used to indicate no such conversion type */ +/* The rest are extensions, not normally found in printf() */ +#define etBLOB 11 /* Blob objects. %b */ +#define etBLOBSQL 12 /* Blob objects quoted for SQL. %B */ +#define etSQLESCAPE 13 /* Strings with '\'' doubled. %q */ +#define etSQLESCAPE2 14 /* Strings with '\'' doubled and enclosed in '', + NULL pointers replaced by SQL NULL. %Q */ +#define etSQLESCAPE3 15 /* Double '"' characters within an identifier. %w */ +#define etPOINTER 16 /* The %p conversion */ +#define etHTMLIZE 17 /* Make text safe for HTML */ +#define etHTTPIZE 18 /* Make text safe for HTTP. "/" encoded as %2f */ +#define etURLIZE 19 /* Make text safe for HTTP. "/" not encoded */ +#define etFOSSILIZE 20 /* The fossil header encoding format. */ +#define etPATH 21 /* Path type */ +#define etWIKISTR 22 /* Timeline comment text rendered from a char*: %W */ +#define etSTRINGID 23 /* String with length limit for a hash prefix: %S */ +#define etROOT 24 /* String value of g.zTop: %R */ +#define etJSONSTR 25 /* String encoded as a JSON string literal: %j + Use %!j to include double-quotes around it. */ +#define etSHELLESC 26 /* Escape a filename for use in a shell command: %$ + See blob_append_escaped_arg() for details + "%$" -> adds "./" prefix if necessary. + "%!$" -> omits the "./" prefix. */ +#define etHEX 27 /* Encode a string as hexadecimal */ + + +/* +** An "etByte" is an 8-bit unsigned value. +*/ +typedef unsigned char etByte; + +/* +** Each builtin conversion character (ex: the 'd' in "%d") is described +** by an instance of the following structure +*/ +typedef struct et_info { /* Information about each format field */ + char fmttype; /* The format field code letter */ + etByte base; /* The base for radix conversion */ + etByte flags; /* One or more of FLAG_ constants below */ + etByte type; /* Conversion paradigm */ + etByte charset; /* Offset into aDigits[] of the digits string */ + etByte prefix; /* Offset into aPrefix[] of the prefix string */ +} et_info; + +/* +** Allowed values for et_info.flags +*/ +#define FLAG_SIGNED 1 /* True if the value to convert is signed */ +#define FLAG_INTERN 2 /* True if for internal use only */ +#define FLAG_STRING 4 /* Allow infinity precision */ + + +/* +** The following table is searched linearly, so it is good to put the +** most frequently used conversion types first. +** +** NB: When modifying this table is it vital that you also update the fmtchr[] +** variable to match!!! +*/ +static const char aDigits[] = "0123456789ABCDEF0123456789abcdef"; +static const char aPrefix[] = "-x0\000X0"; +static const char fmtchr[] = "dsgzqQbBWhRtTwFSjcouxXfeEGin%p/$H"; +static const et_info fmtinfo[] = { + { 'd', 10, 1, etRADIX, 0, 0 }, + { 's', 0, 4, etSTRING, 0, 0 }, + { 'g', 0, 1, etGENERIC, 30, 0 }, + { 'z', 0, 6, etDYNSTRING, 0, 0 }, + { 'q', 0, 4, etSQLESCAPE, 0, 0 }, + { 'Q', 0, 4, etSQLESCAPE2, 0, 0 }, + { 'b', 0, 2, etBLOB, 0, 0 }, + { 'B', 0, 2, etBLOBSQL, 0, 0 }, + { 'W', 0, 2, etWIKISTR, 0, 0 }, + { 'h', 0, 4, etHTMLIZE, 0, 0 }, + { 'R', 0, 0, etROOT, 0, 0 }, + { 't', 0, 4, etHTTPIZE, 0, 0 }, /* "/" -> "%2F" */ + { 'T', 0, 4, etURLIZE, 0, 0 }, /* "/" unchanged */ + { 'w', 0, 4, etSQLESCAPE3, 0, 0 }, + { 'F', 0, 4, etFOSSILIZE, 0, 0 }, + { 'S', 0, 4, etSTRINGID, 0, 0 }, + { 'j', 0, 0, etJSONSTR, 0, 0 }, + { 'c', 0, 0, etCHARX, 0, 0 }, + { 'o', 8, 0, etRADIX, 0, 2 }, + { 'u', 10, 0, etRADIX, 0, 0 }, + { 'x', 16, 0, etRADIX, 16, 1 }, + { 'X', 16, 0, etRADIX, 0, 4 }, + { 'f', 0, 1, etFLOAT, 0, 0 }, + { 'e', 0, 1, etEXP, 30, 0 }, + { 'E', 0, 1, etEXP, 14, 0 }, + { 'G', 0, 1, etGENERIC, 14, 0 }, + { 'i', 10, 1, etRADIX, 0, 0 }, + { 'n', 0, 0, etSIZE, 0, 0 }, + { '%', 0, 0, etPERCENT, 0, 0 }, + { 'p', 16, 0, etPOINTER, 0, 1 }, + { '/', 0, 0, etPATH, 0, 0 }, + { '$', 0, 0, etSHELLESC, 0, 0 }, + { 'H', 0, 0, etHEX, 0, 0 }, + { etERROR, 0,0,0,0,0} /* Must be last */ +}; +#define etNINFO count(fmtinfo) + +/* +** Verify that the fmtchr[] and fmtinfo[] arrays are in agreement. +** +** This routine is a defense against programming errors. +*/ +void fossil_printf_selfcheck(void){ + int i; + for(i=0; fmtchr[i]; i++){ + assert( fmtchr[i]==fmtinfo[i].fmttype ); + } +} + + +/* +** "*val" is a double such that 0.1 <= *val < 10.0 +** Return the ascii code for the leading digit of *val, then +** multiply "*val" by 10.0 to renormalize. +** +** Example: +** input: *val = 3.14159 +** output: *val = 1.4159 function return = '3' +** +** The counter *cnt is incremented each time. After counter exceeds +** 16 (the number of significant digits in a 64-bit float) '0' is +** always returned. +*/ +static int et_getdigit(long double *val, int *cnt){ + int digit; + long double d; + if( (*cnt)++ >= 16 ) return '0'; + digit = (int)*val; + d = digit; + digit += '0'; + *val = (*val - d)*10.0; + return digit; +} + +/* +** Size of temporary conversion buffer. +*/ +#define etBUFSIZE 500 + +/* +** Find the length of a string as long as that length does not +** exceed N bytes. If no zero terminator is seen in the first +** N bytes then return N. If N is negative, then this routine +** is an alias for strlen(). +*/ +#if _XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L +# define StrNLen32(Z,N) (int)strnlen(Z,N) +#else +static int StrNLen32(const char *z, int N){ + int n = 0; + while( (N-- != 0) && *(z++)!=0 ){ n++; } + return n; +} +#endif + +/* +** SETTING: timeline-plaintext boolean default=off +** +** If enabled, no wiki-formatting is done for timeline comment messages. +** Hyperlinks are activated, but they show up on screen using the +** complete input text, not just the display text. No other formatting +** is done. +*/ +/* +** SETTING: timeline-hard-newlines boolean default=off +** +** If enabled, the timeline honors newline characters in check-in comments. +** In other words, newlines are converted into
for HTML display. +** The default behavior, when this setting is off, is that newlines are +** treated like any other whitespace character. +*/ + +/* +** Return an appropriate set of flags for wiki_convert() for displaying +** comments on a timeline. These flag settings are determined by +** configuration parameters. +** +** The altForm2 argument is true for "%!W" (with the "!" alternate-form-2 +** flags) and is false for plain "%W". The ! flag indicates that the +** formatting is for display of a check-in comment on the timeline. Such +** comments used to be rendered differently, but ever since 2020, they +** have been rendered identically, so the ! flag does not make any different +** in the output any more. +*/ +int wiki_convert_flags(int altForm2){ + static int wikiFlags = 0; + (void)altForm2; + if( wikiFlags==0 ){ + wikiFlags = WIKI_INLINE | WIKI_NOBADLINKS; + if( db_get_boolean("timeline-plaintext", 0) ){ + wikiFlags |= WIKI_LINKSONLY; + } + if( db_get_boolean("timeline-hard-newlines", 0) ){ + wikiFlags |= WIKI_NEWLINE; + } + } + return wikiFlags; +} + + + +/* +** The root program. All variations call this core. +** +** INPUTS: +** pBlob This is the blob where the output will be built. +** +** fmt This is the format string, as in the usual print. +** +** ap This is a pointer to a list of arguments. Same as in +** vfprint. +** +** OUTPUTS: +** The return value is the total number of characters sent to +** the function "func". Returns -1 on error. +** +** Note that the order in which automatic variables are declared below +** seems to make a big difference in determining how fast this beast +** will run. +*/ +int vxprintf( + Blob *pBlob, /* Append output to this blob */ + const char *fmt, /* Format string */ + va_list ap /* arguments */ +){ + int c; /* Next character in the format string */ + char *bufpt; /* Pointer to the conversion buffer */ + int precision; /* Precision of the current field */ + int length; /* Length of the field */ + int idx; /* A general purpose loop counter */ + int count; /* Total number of characters output */ + int width; /* Width of the current field */ + etByte flag_leftjustify; /* True if "-" flag is present */ + etByte flag_plussign; /* True if "+" flag is present */ + etByte flag_blanksign; /* True if " " flag is present */ + etByte flag_alternateform; /* True if "#" flag is present */ + etByte flag_altform2; /* True if "!" flag is present */ + etByte flag_zeropad; /* True if field width constant starts with zero */ + etByte flag_long; /* True if "l" flag is present */ + etByte flag_longlong; /* True if the "ll" flag is present */ + etByte done; /* Loop termination flag */ + etByte cThousand; /* Thousands separator for %d and %u */ + u64 longvalue; /* Value for integer types */ + long double realvalue; /* Value for real types */ + const et_info *infop; /* Pointer to the appropriate info structure */ + char buf[etBUFSIZE]; /* Conversion buffer */ + char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ + etByte errorflag = 0; /* True if an error is encountered */ + etByte xtype; /* Conversion paradigm */ + char *zExtra; /* Extra memory used for etTCLESCAPE conversions */ + static const char spaces[] = + " "; +#define etSPACESIZE (sizeof(spaces)-1) + int exp, e2; /* exponent of real numbers */ + double rounder; /* Used for rounding floating point values */ + etByte flag_dp; /* True if decimal point should be shown */ + etByte flag_rtz; /* True if trailing zeros should be removed */ + etByte flag_exp; /* True to force display of the exponent */ + int nsd; /* Number of significant digits returned */ + const char *zFmtLookup; + + count = length = 0; + bufpt = 0; + for(; (c=(*fmt))!=0; ++fmt){ + if( c!='%' ){ + bufpt = (char *)fmt; +#if HAVE_STRCHRNUL + fmt = strchrnul(fmt, '%'); +#else + do{ fmt++; }while( *fmt && *fmt != '%' ); +#endif + blob_append(pBlob, bufpt, (int)(fmt - bufpt)); + if( *fmt==0 ) break; + } + if( (c=(*++fmt))==0 ){ + errorflag = 1; + blob_append(pBlob,"%",1); + count++; + break; + } + /* Find out what flags are present */ + flag_leftjustify = flag_plussign = flag_blanksign = cThousand = + flag_alternateform = flag_altform2 = flag_zeropad = 0; + done = 0; + do{ + switch( c ){ + case '-': flag_leftjustify = 1; break; + case '+': flag_plussign = 1; break; + case ' ': flag_blanksign = 1; break; + case '#': flag_alternateform = 1; break; + case '!': flag_altform2 = 1; break; + case '0': flag_zeropad = 1; break; + case ',': cThousand = ','; break; + default: done = 1; break; + } + }while( !done && (c=(*++fmt))!=0 ); + /* Get the field width */ + width = 0; + if( c=='*' ){ + width = va_arg(ap,int); + if( width<0 ){ + flag_leftjustify = 1; + width = -width; + } + c = *++fmt; + }else{ + while( c>='0' && c<='9' ){ + width = width*10 + c - '0'; + c = *++fmt; + } + } + if( width > etBUFSIZE-10 ){ + width = etBUFSIZE-10; + } + /* Get the precision */ + if( c=='.' ){ + precision = 0; + c = *++fmt; + if( c=='*' ){ + precision = va_arg(ap,int); + if( precision<0 ) precision = -precision; + c = *++fmt; + }else{ + while( c>='0' && c<='9' ){ + precision = precision*10 + c - '0'; + c = *++fmt; + } + } + }else{ + precision = -1; + } + /* Get the conversion type modifier */ + if( c=='l' ){ + flag_long = 1; + c = *++fmt; + if( c=='l' ){ + flag_longlong = 1; + c = *++fmt; + }else{ + flag_longlong = 0; + } + }else{ + flag_long = flag_longlong = 0; + } + /* Fetch the info entry for the field */ + zFmtLookup = strchr(fmtchr,c); + if( zFmtLookup ){ + infop = &fmtinfo[zFmtLookup-fmtchr]; + xtype = infop->type; + }else{ + infop = 0; + xtype = etERROR; + } + zExtra = 0; + + /* Limit the precision to prevent overflowing buf[] during conversion */ + if( precision>etBUFSIZE-40 && (infop->flags & FLAG_STRING)==0 ){ + precision = etBUFSIZE-40; + } + + /* + ** At this point, variables are initialized as follows: + ** + ** flag_alternateform TRUE if a '#' is present. + ** flag_altform2 TRUE if a '!' is present. + ** flag_plussign TRUE if a '+' is present. + ** flag_leftjustify TRUE if a '-' is present or if the + ** field width was negative. + ** flag_zeropad TRUE if the width began with 0. + ** flag_long TRUE if the letter 'l' (ell) prefixed + ** the conversion character. + ** flag_longlong TRUE if the letters 'll' (ell ell) prefixed + ** the conversion character. + ** flag_blanksign TRUE if a ' ' is present. + ** width The specified field width. This is + ** always non-negative. Zero is the default. + ** precision The specified precision. The default + ** is -1. + ** xtype The class of the conversion. + ** infop Pointer to the appropriate info struct. + */ + switch( xtype ){ + case etPOINTER: + flag_longlong = sizeof(char*)==sizeof(i64); + flag_long = sizeof(char*)==sizeof(long int); + /* Fall through into the next case */ + case etRADIX: + if( infop->flags & FLAG_SIGNED ){ + i64 v; + if( flag_longlong ) v = va_arg(ap,i64); + else if( flag_long ) v = va_arg(ap,long int); + else v = va_arg(ap,int); + if( v<0 ){ + longvalue = -v; + prefix = '-'; + }else{ + longvalue = v; + if( flag_plussign ) prefix = '+'; + else if( flag_blanksign ) prefix = ' '; + else prefix = 0; + } + }else{ + if( flag_longlong ) longvalue = va_arg(ap,u64); + else if( flag_long ) longvalue = va_arg(ap,unsigned long int); + else longvalue = va_arg(ap,unsigned int); + prefix = 0; + } + if( longvalue==0 ) flag_alternateform = 0; + if( flag_zeropad && precisioncharset]; + base = infop->base; + do{ /* Convert to ascii */ + *(--bufpt) = cset[longvalue%base]; + longvalue = longvalue/base; + }while( longvalue>0 ); + } + length = &buf[etBUFSIZE-1]-bufpt; + while( precision>length ){ + *(--bufpt) = '0'; /* Zero pad */ + length++; + } + if( cThousand ){ + int nn = (length - 1)/3; /* Number of "," to insert */ + int ix = (length - 1)%3 + 1; + bufpt -= nn; + for(idx=0; nn>0; idx++){ + bufpt[idx] = bufpt[idx+nn]; + ix--; + if( ix==0 ){ + bufpt[++idx] = cThousand; + nn--; + ix = 3; + } + } + } + if( prefix ) *(--bufpt) = prefix; /* Add sign */ + if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */ + const char *pre; + char x; + pre = &aPrefix[infop->prefix]; + if( *bufpt!=pre[0] ){ + for(; (x=(*pre))!=0; pre++) *(--bufpt) = x; + } + } + length = &buf[etBUFSIZE-1]-bufpt; + break; + case etFLOAT: + case etEXP: + case etGENERIC: + realvalue = va_arg(ap,double); + if( precision<0 ) precision = 6; /* Set default precision */ + if( precision>etBUFSIZE/2-10 ) precision = etBUFSIZE/2-10; + if( realvalue<0.0 ){ + realvalue = -realvalue; + prefix = '-'; + }else{ + if( flag_plussign ) prefix = '+'; + else if( flag_blanksign ) prefix = ' '; + else prefix = 0; + } + if( xtype==etGENERIC && precision>0 ) precision--; +#if 0 + /* Rounding works like BSD when the constant 0.4999 is used. Wierd! */ + for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1); +#else + /* It makes more sense to use 0.5 */ + for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1); +#endif + if( xtype==etFLOAT ) realvalue += rounder; + /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ + exp = 0; + if( realvalue>0.0 ){ + while( realvalue>=1e32 && exp<=350 ){ realvalue *= 1e-32; exp+=32; } + while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; } + while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; } + while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; } + while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; } + if( exp>350 || exp<-350 ){ + bufpt = "NaN"; + length = 3; + break; + } + } + bufpt = buf; + /* + ** If the field type is etGENERIC, then convert to either etEXP + ** or etFLOAT, as appropriate. + */ + flag_exp = xtype==etEXP; + if( xtype!=etFLOAT ){ + realvalue += rounder; + if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } + } + if( xtype==etGENERIC ){ + flag_rtz = !flag_alternateform; + if( exp<-4 || exp>precision ){ + xtype = etEXP; + }else{ + precision = precision - exp; + xtype = etFLOAT; + } + }else{ + flag_rtz = 0; + } + if( xtype==etEXP ){ + e2 = 0; + }else{ + e2 = exp; + } + nsd = 0; + flag_dp = (precision>0) | flag_alternateform | flag_altform2; + /* The sign in front of the number */ + if( prefix ){ + *(bufpt++) = prefix; + } + /* Digits prior to the decimal point */ + if( e2<0 ){ + *(bufpt++) = '0'; + }else{ + for(; e2>=0; e2--){ + *(bufpt++) = et_getdigit(&realvalue,&nsd); + } + } + /* The decimal point */ + if( flag_dp ){ + *(bufpt++) = '.'; + } + /* "0" digits after the decimal point but before the first + ** significant digit of the number */ + for(e2++; e2<0 && precision>0; precision--, e2++){ + *(bufpt++) = '0'; + } + /* Significant digits after the decimal point */ + while( (precision--)>0 ){ + *(bufpt++) = et_getdigit(&realvalue,&nsd); + } + /* Remove trailing zeros and the "." if no digits follow the "." */ + if( flag_rtz && flag_dp ){ + while( bufpt[-1]=='0' ) *(--bufpt) = 0; + assert( bufpt>buf ); + if( bufpt[-1]=='.' ){ + if( flag_altform2 ){ + *(bufpt++) = '0'; + }else{ + *(--bufpt) = 0; + } + } + } + /* Add the "eNNN" suffix */ + if( flag_exp || (xtype==etEXP && exp) ){ + *(bufpt++) = aDigits[infop->charset]; + if( exp<0 ){ + *(bufpt++) = '-'; exp = -exp; + }else{ + *(bufpt++) = '+'; + } + if( exp>=100 ){ + *(bufpt++) = (exp/100)+'0'; /* 100's digit */ + exp %= 100; + } + *(bufpt++) = exp/10+'0'; /* 10's digit */ + *(bufpt++) = exp%10+'0'; /* 1's digit */ + } + *bufpt = 0; + + /* The converted number is in buf[] and zero terminated. Output it. + ** Note that the number is in the usual order, not reversed as with + ** integer conversions. */ + length = bufpt-buf; + bufpt = buf; + + /* Special case: Add leading zeros if the flag_zeropad flag is + ** set and we are not left justified */ + if( flag_zeropad && !flag_leftjustify && length < width){ + int i; + int nPad = width - length; + for(i=width; i>=nPad; i--){ + bufpt[i] = bufpt[i-nPad]; + } + i = prefix!=0; + while( nPad-- ) bufpt[i++] = '0'; + length = width; + } + break; + case etSIZE: + *(va_arg(ap,int*)) = count; + length = width = 0; + break; + case etPERCENT: + buf[0] = '%'; + bufpt = buf; + length = 1; + break; + case etCHARX: + c = buf[0] = va_arg(ap,int); + if( precision>=0 ){ + for(idx=1; idx=0 && precision=0 && limit=0 && limit etBUFSIZE ){ + bufpt = zExtra = fossil_malloc( n + cnt + 2 ); + }else{ + bufpt = buf; + } + bufpt[0] = '\''; + for(i=0, j=1; i=0 && precisionetBUFSIZE ){ + bufpt = zExtra = fossil_malloc( n ); + }else{ + bufpt = buf; + } + j = 0; + if( needQuote ) bufpt[j++] = q; + for(i=0; i=0 && precision=0 && precision=0 && precision=0 && precision=0 && precision0 ){ + count += nspace; + while( nspace>=(int)etSPACESIZE ){ + blob_append(pBlob,spaces,etSPACESIZE); + nspace -= etSPACESIZE; + } + if( nspace>0 ) blob_append(pBlob,spaces,nspace); + } + } + if( length>0 ){ + blob_append(pBlob,bufpt,length); + count += length; + } + if( flag_leftjustify ){ + register int nspace; + nspace = width-length; + if( nspace>0 ){ + count += nspace; + while( nspace>=(int)etSPACESIZE ){ + blob_append(pBlob,spaces,etSPACESIZE); + nspace -= etSPACESIZE; + } + if( nspace>0 ) blob_append(pBlob,spaces,nspace); + } + } + if( zExtra ){ + fossil_free(zExtra); + } + }/* End for loop over the format string */ + return errorflag ? -1 : count; +} /* End of function */ + +/* +** Print into memory obtained from fossil_malloc(). +*/ +char *mprintf(const char *zFormat, ...){ + va_list ap; + char *z; + va_start(ap,zFormat); + z = vmprintf(zFormat, ap); + va_end(ap); + return z; +} +char *vmprintf(const char *zFormat, va_list ap){ + Blob blob = empty_blob; + blob_vappendf(&blob, zFormat, ap); + blob_materialize(&blob); + return blob.aData; +} + +/* +** Record an error message in the global g.zErrMsg variable. +** +** If there is already another error message, only overwrite it if +** the current error has a higher priority. +*/ +void fossil_error(int iPriority, const char *zFormat, ...){ + va_list ap; + if( iPriority<=0 ){ + return; + } + if( g.zErrMsg ){ + if( g.iErrPriority>=iPriority ){ + return; + } + free(g.zErrMsg); + } + va_start(ap, zFormat); + g.zErrMsg = vmprintf(zFormat, ap); + va_end(ap); + g.iErrPriority = iPriority; +} +void fossil_error_reset(void){ + free(g.zErrMsg); + g.zErrMsg = 0; + g.iErrPriority = 0; +} + +/* True if the last character standard output cursor is setting at +** the beginning of a blank link. False if a \r has been to move the +** cursor to the beginning of the line or if not at the beginning of +** a line. +** was a \n +*/ +static int stdoutAtBOL = 1; + +/* +** Write to standard output or standard error. +** +** On windows, transform the output into the current terminal encoding +** if the output is going to the screen. If output is redirected into +** a file, no translation occurs. Switch output mode to binary to +** properly process line-endings, make sure to switch the mode back to +** text when done. +** No translation ever occurs on unix. +*/ +void fossil_puts(const char *z, int toStdErr, int n){ + FILE* out = (toStdErr ? stderr : stdout); + if( n==0 ) return; + assert( toStdErr==0 || toStdErr==1 ); + if( toStdErr==0 ) stdoutAtBOL = (z[n-1]=='\n'); +#if defined(_WIN32) + if( fossil_utf8_to_console(z, n, toStdErr) >= 0 ){ + return; + } + fflush(out); + _setmode(_fileno(out), _O_BINARY); +#endif + fwrite(z, 1, n, out); +#if defined(_WIN32) + fflush(out); + _setmode(_fileno(out), _O_TEXT); +#endif +} + +/* +** Force the standard output cursor to move to the beginning +** of a line, if it is not there already. +*/ +int fossil_force_newline(void){ + if( g.cgiOutput==0 && stdoutAtBOL==0 ){ + fossil_puts("\n", 0, 1); + return 1; + } + return 0; +} + +/* +** Indicate that the cursor has moved to the start of a line by means +** other than writing to standard output. +*/ +void fossil_new_line_started(void){ + stdoutAtBOL = 1; +} + +/* +** Write output for user consumption. If g.cgiOutput is enabled, then +** send the output as part of the CGI reply. If g.cgiOutput is false, +** then write on standard output. +*/ +void fossil_print(const char *zFormat, ...){ + va_list ap; + va_start(ap, zFormat); + if( g.cgiOutput ){ + cgi_vprintf(zFormat, ap); + }else{ + vxprintf(0, zFormat, ap); + } + va_end(ap); +} +void fossil_vprint(const char *zFormat, va_list ap){ + if( g.cgiOutput ){ + cgi_vprintf(zFormat, ap); + }else{ + vxprintf(0, zFormat, ap); + } +} + +/* +** Print a trace message on standard error. +*/ +void fossil_trace(const char *zFormat, ...){ + va_list ap; + Blob b; + va_start(ap, zFormat); + b = empty_blob; + vxprintf(&b, zFormat, ap); + fossil_puts(blob_buffer(&b), 1, blob_size(&b)); + blob_reset(&b); + va_end(ap); +} + +/* +** Write a message to the error log, if the error log filename is +** defined. +** +** If the message format begins with 'X', then omit that X from the +** beginning of the message and add much more CGI context. +*/ +void fossil_errorlog(const char *zFormat, ...){ + struct tm *pNow; + time_t now; + FILE *out; + const char *z; + int i; + int bDetail = 0; + int bBrief = 0; + va_list ap; + static const char *const azEnv[] = { "HTTP_HOST", "HTTP_REFERER", + "HTTP_USER_AGENT", + "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD", + "REQUEST_URI", "SCRIPT_NAME" }; + if( g.zErrlog==0 ) return; + if( g.zErrlog[0]=='-' && g.zErrlog[1]==0 ){ + out = stderr; + }else{ + out = fossil_fopen(g.zErrlog, "a"); + if( out==0 ) return; + } + now = time(0); + pNow = gmtime(&now); + fprintf(out, "------------- %04d-%02d-%02d %02d:%02d:%02d UTC ------------\n", + pNow->tm_year+1900, pNow->tm_mon+1, pNow->tm_mday, + pNow->tm_hour, pNow->tm_min, pNow->tm_sec); + va_start(ap, zFormat); + if( zFormat[0]=='X' ){ + bDetail = 1; + zFormat++; + }else if( strncmp(zFormat,"SMTP:",5)==0 ){ + bBrief = 1; + } + vfprintf(out, zFormat, ap); + fprintf(out, " (pid %d)\n", (int)getpid()); + va_end(ap); + if( g.zPhase!=0 ) fprintf(out, "while in %s\n", g.zPhase); + if( bBrief ){ + /* Say nothing more */ + }else if( bDetail ){ + cgi_print_all(1,3,out); + }else{ + for(i=0; iSomething did not work right.

+ @

%h(z)

+ }else{ + @

%h(z)

+ cgi_set_status(400, "Bad Request"); + } + style_finish_page(); + cgi_reply(); + }else if( !g.fQuiet ){ + fossil_force_newline(); + fossil_trace("%s\n", z); + } + return rc; +} + +/* +** Print an error message, rollback all databases, and quit. These +** routines never return and produce a non-zero process exit status. +** +** The main difference between fossil_fatal() and fossil_panic() is that +** fossil_panic() makes an entry in the error log whereas fossil_fatal() +** does not. On POSIX platforms, if there is not an error log, then both +** routines work similarly with respect to user-visible effects. Hence, +** the routines are interchangeable for commands and only act differently +** when processing web pages. On the Windows platform, fossil_panic() +** also displays a pop-up stating that an error has occurred and allowing +** just-in-time debugging to commence. On all platforms, fossil_panic() +** ends execution with a SIGABRT signal, bypassing atexit processing. +** This signal can also produce a core dump on POSIX platforms. +** +** Use fossil_fatal() for malformed inputs that should be reported back +** to the user, but which do not represent a configuration problem or bug. +** +** Use fossil_panic() for any kind of error that should be brought to the +** attention of the system administrator or Fossil developers. It should +** be avoided for ordinary usage, parameter, OOM and I/O errors. +*/ +NORETURN void fossil_panic(const char *zFormat, ...){ + va_list ap; + int rc = 1; + char z[1000]; + static int once = 0; + + if( once ) exit(1); + once = 1; + mainInFatalError = 1; + /* db_force_rollback(); */ + va_start(ap, zFormat); + sqlite3_vsnprintf(sizeof(z),z,zFormat, ap); + va_end(ap); + if( g.fAnyTrace ){ + fprintf(stderr, "/***** panic on %d *****/\n", getpid()); + } + fossil_errorlog("panic: %s", z); + rc = fossil_print_error(rc, z); + abort(); + exit(rc); +} +NORETURN void fossil_fatal(const char *zFormat, ...){ + static int once = 0; + va_list ap; + char *z; + int rc = 1; + if( once ) exit(1); + once = 1; + mainInFatalError = 1; + va_start(ap, zFormat); + z = vmprintf(zFormat, ap); + va_end(ap); + rc = fossil_print_error(rc, z); + fossil_free(z); + db_force_rollback(); + fossil_exit(rc); +} + +/* This routine works like fossil_fatal() except that if called +** recursively, the recursive call is a no-op. +** +** Use this in places where an error might occur while doing +** fatal error shutdown processing. Unlike fossil_panic() and +** fossil_fatal() which never return, this routine might return if +** the fatal error handing is already in process. The caller must +** be prepared for this routine to return. +*/ +void fossil_fatal_recursive(const char *zFormat, ...){ + char *z; + va_list ap; + int rc = 1; + if( mainInFatalError ) return; + mainInFatalError = 1; + va_start(ap, zFormat); + z = vmprintf(zFormat, ap); + va_end(ap); + rc = fossil_print_error(rc, z); + db_force_rollback(); + fossil_exit(rc); +} + + +/* Print a warning message. +** +** Unlike fossil_fatal() and fossil_panic(), this routine does return +** and processing attempts to continue. A message is written to the +** error log, however, so this routine should only be used for situations +** that require administrator or developer attention. Minor problems +** in user inputs should not use this routine. +*/ +void fossil_warning(const char *zFormat, ...){ + char *z; + va_list ap; + va_start(ap, zFormat); + z = vmprintf(zFormat, ap); + va_end(ap); + fossil_errorlog("warning: %s", z); +#ifdef FOSSIL_ENABLE_JSON + if( g.json.isJsonMode!=0 ){ + /* + ** Avoid calling into the JSON support subsystem if it + ** has not yet been initialized, e.g. early SQLite log + ** messages, etc. + */ + if( !json_is_bootstrapped_early() ) json_bootstrap_early(); + json_warn( FSL_JSON_W_UNKNOWN, "%s", z ); + }else +#endif + { + if( g.cgiOutput==1 ){ + etag_cancel(); + cgi_printf("

\n%h\n

\n", z); + }else{ + fossil_force_newline(); + fossil_trace("%s\n", z); + } + } + free(z); +} + +/* +** Turn off any LF to CRLF translation on the stream given as an +** argument. This is a no-op on unix but is necessary on windows. +*/ +void fossil_binary_mode(FILE *p){ +#if defined(_WIN32) + _setmode(_fileno(p), _O_BINARY); +#endif +#ifdef __EMX__ /* OS/2 */ + setmode(fossil_fileno(p), O_BINARY); +#endif +} -- cgit v1.3.1