/* ** 2023 November 4 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ******************************************************************************** ** This file implements various interfaces used for console and stream I/O ** by the SQLite project command-line tools, as explained in console_io.h . ** Functions prefixed by "SQLITE_INTERNAL_LINKAGE" behave as described there. */ #ifndef SQLITE_CDECL # define SQLITE_CDECL #endif #ifndef SHELL_NO_SYSINC # include # include # include # include # include # include "sqlite3.h" #endif #ifndef HAVE_CONSOLE_IO_H # include "console_io.h" #endif #if defined(_MSC_VER) # pragma warning(disable : 4204) #endif #ifndef SQLITE_CIO_NO_TRANSLATE # if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT # ifndef SHELL_NO_SYSINC # include # include # undef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # include # endif # define CIO_WIN_WC_XLATE 1 /* Use WCHAR Windows APIs for console I/O */ # else # ifndef SHELL_NO_SYSINC # include # endif # define CIO_WIN_WC_XLATE 0 /* Use plain C library stream I/O at console */ # endif #else # define CIO_WIN_WC_XLATE 0 /* Not exposing translation routines at all */ #endif #if CIO_WIN_WC_XLATE /* Character used to represent a known-incomplete UTF-8 char group (�) */ static WCHAR cBadGroup = 0xfffd; #endif #if CIO_WIN_WC_XLATE static HANDLE handleOfFile(FILE *pf){ int fileDesc = _fileno(pf); union { intptr_t osfh; HANDLE fh; } fid = { (fileDesc>=0)? _get_osfhandle(fileDesc) : (intptr_t)INVALID_HANDLE_VALUE }; return fid.fh; } #endif #ifndef SQLITE_CIO_NO_TRANSLATE typedef struct PerStreamTags { # if CIO_WIN_WC_XLATE HANDLE hx; DWORD consMode; char acIncomplete[4]; # else short reachesConsole; # endif FILE *pf; } PerStreamTags; /* Define NULL-like value for things which can validly be 0. */ # define SHELL_INVALID_FILE_PTR ((FILE *)~0) # if CIO_WIN_WC_XLATE # define SHELL_INVALID_CONS_MODE 0xFFFF0000 # endif # if CIO_WIN_WC_XLATE # define PST_INITIALIZER { INVALID_HANDLE_VALUE, SHELL_INVALID_CONS_MODE, \ {0,0,0,0}, SHELL_INVALID_FILE_PTR } # else # define PST_INITIALIZER { 0, SHELL_INVALID_FILE_PTR } # endif /* Quickly say whether a known output is going to the console. */ # if CIO_WIN_WC_XLATE static short pstReachesConsole(PerStreamTags *ppst){ return (ppst->hx != INVALID_HANDLE_VALUE); } # else # define pstReachesConsole(ppst) 0 # endif # if CIO_WIN_WC_XLATE static void restoreConsoleArb(PerStreamTags *ppst){ if( pstReachesConsole(ppst) ) SetConsoleMode(ppst->hx, ppst->consMode); } # else # define restoreConsoleArb(ppst) # endif /* Say whether FILE* appears to be a console, collect associated info. */ static short streamOfConsole(FILE *pf, /* out */ PerStreamTags *ppst){ # if CIO_WIN_WC_XLATE short rv = 0; DWORD dwCM = SHELL_INVALID_CONS_MODE; HANDLE fh = handleOfFile(pf); ppst->pf = pf; if( INVALID_HANDLE_VALUE != fh ){ rv = (GetFileType(fh) == FILE_TYPE_CHAR && GetConsoleMode(fh,&dwCM)); } ppst->hx = (rv)? fh : INVALID_HANDLE_VALUE; ppst->consMode = dwCM; return rv; # else ppst->pf = pf; ppst->reachesConsole = ( (short)isatty(fileno(pf)) ); return ppst->reachesConsole; # endif } # ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING # define ENABLE_VIRTUAL_TERMINAL_PROCESSING (0x4) # endif # if CIO_WIN_WC_XLATE /* Define console modes for use with the Windows Console API. */ # define SHELL_CONI_MODE \ (ENABLE_ECHO_INPUT | ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | 0x80 \ | ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS | ENABLE_PROCESSED_INPUT) # define SHELL_CONO_MODE (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT \ | ENABLE_VIRTUAL_TERMINAL_PROCESSING) # endif typedef struct ConsoleInfo { PerStreamTags pstSetup[3]; PerStreamTags pstDesignated[3]; StreamsAreConsole sacSetup; } ConsoleInfo; static short isValidStreamInfo(PerStreamTags *ppst){ return (ppst->pf != SHELL_INVALID_FILE_PTR); } static ConsoleInfo consoleInfo = { { /* pstSetup */ PST_INITIALIZER, PST_INITIALIZER, PST_INITIALIZER }, { /* pstDesignated[] */ PST_INITIALIZER, PST_INITIALIZER, PST_INITIALIZER }, SAC_NoConsole /* sacSetup */ }; SQLITE_INTERNAL_LINKAGE FILE* invalidFileStream = (FILE *)~0; # if CIO_WIN_WC_XLATE static void maybeSetupAsConsole(PerStreamTags *ppst, short odir){ if( pstReachesConsole(ppst) ){ DWORD cm = odir? SHELL_CONO_MODE : SHELL_CONI_MODE; SetConsoleMode(ppst->hx, cm); } } # else # define maybeSetupAsConsole(ppst,odir) # endif SQLITE_INTERNAL_LINKAGE void consoleRenewSetup(void){ # if CIO_WIN_WC_XLATE int ix = 0; while( ix < 6 ){ PerStreamTags *ppst = (ix<3)? &consoleInfo.pstSetup[ix] : &consoleInfo.pstDesignated[ix-3]; maybeSetupAsConsole(ppst, (ix % 3)>0); ++ix; } # endif } SQLITE_INTERNAL_LINKAGE StreamsAreConsole consoleClassifySetup( FILE *pfIn, FILE *pfOut, FILE *pfErr ){ StreamsAreConsole rv = SAC_NoConsole; FILE* apf[3] = { pfIn, pfOut, pfErr }; int ix; for( ix = 2; ix >= 0; --ix ){ PerStreamTags *ppst = &consoleInfo.pstSetup[ix]; if( streamOfConsole(apf[ix], ppst) ){ rv |= (SAC_InConsole< 0 ) fflush(apf[ix]); } consoleInfo.sacSetup = rv; consoleRenewSetup(); return rv; } SQLITE_INTERNAL_LINKAGE void SQLITE_CDECL consoleRestore( void ){ # if CIO_WIN_WC_XLATE static ConsoleInfo *pci = &consoleInfo; if( pci->sacSetup ){ int ix; for( ix=0; ix<3; ++ix ){ if( pci->sacSetup & (SAC_InConsole<pstSetup[ix]; SetConsoleMode(ppst->hx, ppst->consMode); } } } # endif } #endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */ #ifdef SQLITE_CIO_INPUT_REDIR /* Say whether given FILE* is among those known, via either ** consoleClassifySetup() or set{Output,Error}Stream, as ** readable, and return an associated PerStreamTags pointer ** if so. Otherwise, return 0. */ static PerStreamTags * isKnownReadable(FILE *pf){ static PerStreamTags *apst[] = { &consoleInfo.pstDesignated[0], &consoleInfo.pstSetup[0], 0 }; int ix = 0; do { if( apst[ix]->pf == pf ) break; } while( apst[++ix] != 0 ); return apst[ix]; } #endif #ifndef SQLITE_CIO_NO_TRANSLATE /* Say whether given FILE* is among those known, via either ** consoleClassifySetup() or set{Output,Error}Stream, as ** writable, and return an associated PerStreamTags pointer ** if so. Otherwise, return 0. */ static PerStreamTags * isKnownWritable(FILE *pf){ static PerStreamTags *apst[] = { &consoleInfo.pstDesignated[1], &consoleInfo.pstDesignated[2], &consoleInfo.pstSetup[1], &consoleInfo.pstSetup[2], 0 }; int ix = 0; do { if( apst[ix]->pf == pf ) break; } while( apst[++ix] != 0 ); return apst[ix]; } static FILE *designateEmitStream(FILE *pf, unsigned chix){ FILE *rv = consoleInfo.pstDesignated[chix].pf; if( pf == invalidFileStream ) return rv; else{ /* Setting a possibly new output stream. */ PerStreamTags *ppst = isKnownWritable(pf); if( ppst != 0 ){ PerStreamTags pst = *ppst; consoleInfo.pstDesignated[chix] = pst; }else streamOfConsole(pf, &consoleInfo.pstDesignated[chix]); } return rv; } SQLITE_INTERNAL_LINKAGE FILE *setOutputStream(FILE *pf){ return designateEmitStream(pf, 1); } # ifdef CONSIO_SET_ERROR_STREAM SQLITE_INTERNAL_LINKAGE FILE *setErrorStream(FILE *pf){ return designateEmitStream(pf, 2); } # endif #endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */ #ifndef SQLITE_CIO_NO_SETMODE # if CIO_WIN_WC_XLATE static void setModeFlushQ(FILE *pf, short bFlush, int mode){ if( bFlush ) fflush(pf); _setmode(_fileno(pf), mode); } # else # define setModeFlushQ(f, b, m) if(b) fflush(f) # endif SQLITE_INTERNAL_LINKAGE void setBinaryMode(FILE *pf, short bFlush){ setModeFlushQ(pf, bFlush, _O_BINARY); } SQLITE_INTERNAL_LINKAGE void setTextMode(FILE *pf, short bFlush){ setModeFlushQ(pf, bFlush, _O_TEXT); } # undef setModeFlushQ #else /* defined(SQLITE_CIO_NO_SETMODE) */ # define setBinaryMode(f, bFlush) do{ if((bFlush)) fflush(f); }while(0) # define setTextMode(f, bFlush) do{ if((bFlush)) fflush(f); }while(0) #endif /* defined(SQLITE_CIO_NO_SETMODE) */ #ifndef SQLITE_CIO_NO_TRANSLATE # if CIO_WIN_WC_XLATE /* Write buffer cBuf as output to stream known to reach console, ** limited to ncTake char's. Return ncTake on success, else 0. */ static int conZstrEmit(PerStreamTags *ppst, const char *z, int ncTake){ int rv = 0; if( z!=NULL ){ int nwc = MultiByteToWideChar(CP_UTF8,0, z,ncTake, 0,0); if( nwc > 0 ){ WCHAR *zw = sqlite3_malloc64(nwc*sizeof(WCHAR)); if( zw!=NULL ){ nwc = MultiByteToWideChar(CP_UTF8,0, z,ncTake, zw,nwc); if( nwc > 0 ){ /* Translation from UTF-8 to UTF-16, then WCHARs out. */ if( WriteConsoleW(ppst->hx, zw,nwc, 0, NULL) ){ rv = ncTake; } } sqlite3_free(zw); } } } return rv; } /* For {f,o,e}PrintfUtf8() when stream is known to reach console. */ static int conioVmPrintf(PerStreamTags *ppst, const char *zFormat, va_list ap){ char *z = sqlite3_vmprintf(zFormat, ap); if( z ){ int rv = conZstrEmit(ppst, z, (int)strlen(z)); sqlite3_free(z); return rv; }else return 0; } # endif /* CIO_WIN_WC_XLATE */ # ifdef CONSIO_GET_EMIT_STREAM static PerStreamTags * getDesignatedEmitStream(FILE *pf, unsigned chix, PerStreamTags *ppst){ PerStreamTags *rv = isKnownWritable(pf); short isValid = (rv!=0)? isValidStreamInfo(rv) : 0; if( rv != 0 && isValid ) return rv; streamOfConsole(pf, ppst); return ppst; } # endif /* Get stream info, either for designated output or error stream when ** chix equals 1 or 2, or for an arbitrary stream when chix == 0. ** In either case, ppst references a caller-owned PerStreamTags ** struct which may be filled in if none of the known writable ** streams is being held by consoleInfo. The ppf parameter is a ** byref output when chix!=0 and a byref input when chix==0. */ static PerStreamTags * getEmitStreamInfo(unsigned chix, PerStreamTags *ppst, /* in/out */ FILE **ppf){ PerStreamTags *ppstTry; FILE *pfEmit; if( chix > 0 ){ ppstTry = &consoleInfo.pstDesignated[chix]; if( !isValidStreamInfo(ppstTry) ){ ppstTry = &consoleInfo.pstSetup[chix]; pfEmit = ppst->pf; }else pfEmit = ppstTry->pf; if( !isValidStreamInfo(ppstTry) ){ pfEmit = (chix > 1)? stderr : stdout; ppstTry = ppst; streamOfConsole(pfEmit, ppstTry); } *ppf = pfEmit; }else{ ppstTry = isKnownWritable(*ppf); if( ppstTry != 0 ) return ppstTry; streamOfConsole(*ppf, ppst); return ppst; } return ppstTry; } SQLITE_INTERNAL_LINKAGE int oPrintfUtf8(const char *zFormat, ...){ va_list ap; int rv; FILE *pfOut; PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ # if CIO_WIN_WC_XLATE PerStreamTags *ppst = getEmitStreamInfo(1, &pst, &pfOut); # else getEmitStreamInfo(1, &pst, &pfOut); # endif assert(zFormat!=0); va_start(ap, zFormat); # if CIO_WIN_WC_XLATE if( pstReachesConsole(ppst) ){ rv = conioVmPrintf(ppst, zFormat, ap); }else{ # endif rv = vfprintf(pfOut, zFormat, ap); # if CIO_WIN_WC_XLATE } # endif va_end(ap); return rv; } SQLITE_INTERNAL_LINKAGE int ePrintfUtf8(const char *zFormat, ...){ va_list ap; int rv; FILE *pfErr; PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ # if CIO_WIN_WC_XLATE PerStreamTags *ppst = getEmitStreamInfo(2, &pst, &pfErr); # else getEmitStreamInfo(2, &pst, &pfErr); # endif assert(zFormat!=0); va_start(ap, zFormat); # if CIO_WIN_WC_XLATE if( pstReachesConsole(ppst) ){ rv = conioVmPrintf(ppst, zFormat, ap); }else{ # endif rv = vfprintf(pfErr, zFormat, ap); # if CIO_WIN_WC_XLATE } # endif va_end(ap); return rv; } SQLITE_INTERNAL_LINKAGE int fPrintfUtf8(FILE *pfO, const char *zFormat, ...){ va_list ap; int rv; PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ # if CIO_WIN_WC_XLATE PerStreamTags *ppst = getEmitStreamInfo(0, &pst, &pfO); # else getEmitStreamInfo(0, &pst, &pfO); # endif assert(zFormat!=0); va_start(ap, zFormat); # if CIO_WIN_WC_XLATE if( pstReachesConsole(ppst) ){ maybeSetupAsConsole(ppst, 1); rv = conioVmPrintf(ppst, zFormat, ap); if( 0 == isKnownWritable(ppst->pf) ) restoreConsoleArb(ppst); }else{ # endif rv = vfprintf(pfO, zFormat, ap); # if CIO_WIN_WC_XLATE } # endif va_end(ap); return rv; } SQLITE_INTERNAL_LINKAGE int fPutsUtf8(const char *z, FILE *pfO){ PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ # if CIO_WIN_WC_XLATE PerStreamTags *ppst = getEmitStreamInfo(0, &pst, &pfO); # else getEmitStreamInfo(0, &pst, &pfO); # endif assert(z!=0); # if CIO_WIN_WC_XLATE if( pstReachesConsole(ppst) ){ int rv; maybeSetupAsConsole(ppst, 1); rv = conZstrEmit(ppst, z, (int)strlen(z)); if( 0 == isKnownWritable(ppst->pf) ) restoreConsoleArb(ppst); return rv; }else { # endif return (fputs(z, pfO)<0)? 0 : (int)strlen(z); # if CIO_WIN_WC_XLATE } # endif } SQLITE_INTERNAL_LINKAGE int ePutsUtf8(const char *z){ FILE *pfErr; PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ # if CIO_WIN_WC_XLATE PerStreamTags *ppst = getEmitStreamInfo(2, &pst, &pfErr); # else getEmitStreamInfo(2, &pst, &pfErr); # endif assert(z!=0); # if CIO_WIN_WC_XLATE if( pstReachesConsole(ppst) ) return conZstrEmit(ppst, z, (int)strlen(z)); else { # endif return (fputs(z, pfErr)<0)? 0 : (int)strlen(z); # if CIO_WIN_WC_XLATE } # endif } SQLITE_INTERNAL_LINKAGE int oPutsUtf8(const char *z){ FILE *pfOut; PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ # if CIO_WIN_WC_XLATE PerStreamTags *ppst = getEmitStreamInfo(1, &pst, &pfOut); # else getEmitStreamInfo(1, &pst, &pfOut); # endif assert(z!=0); # if CIO_WIN_WC_XLATE if( pstReachesConsole(ppst) ) return conZstrEmit(ppst, z, (int)strlen(z)); else { # endif return (fputs(z, pfOut)<0)? 0 : (int)strlen(z); # if CIO_WIN_WC_XLATE } # endif } #endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */ #if !(defined(SQLITE_CIO_NO_UTF8SCAN) && defined(SQLITE_CIO_NO_TRANSLATE)) /* Skip over as much z[] input char sequence as is valid UTF-8, ** limited per nAccept char's or whole characters and containing ** no char cn such that ((1<=0 => char count, nAccept<0 => character */ SQLITE_INTERNAL_LINKAGE const char* zSkipValidUtf8(const char *z, int nAccept, long ccm){ int ng = (nAccept<0)? -nAccept : 0; const char *pcLimit = (nAccept>=0)? z+nAccept : 0; assert(z!=0); while( (pcLimit)? (z= pcLimit ) return z; else{ char ct = *zt++; if( ct==0 || (zt-z)>4 || (ct & 0xC0)!=0x80 ){ /* Trailing bytes are too few, too many, or invalid. */ return z; } } } while( ((c <<= 1) & 0x40) == 0x40 ); /* Eat lead byte's count. */ z = zt; } } return z; } #endif /*!(defined(SQLITE_CIO_NO_UTF8SCAN)&&defined(SQLITE_CIO_NO_TRANSLATE))*/ #ifndef SQLITE_CIO_NO_TRANSLATE # ifdef CONSIO_SPUTB SQLITE_INTERNAL_LINKAGE int fPutbUtf8(FILE *pfO, const char *cBuf, int nAccept){ assert(pfO!=0); # if CIO_WIN_WC_XLATE PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ PerStreamTags *ppst = getEmitStreamInfo(0, &pst, &pfO); if( pstReachesConsole(ppst) ){ int rv; maybeSetupAsConsole(ppst, 1); rv = conZstrEmit(ppst, cBuf, nAccept); if( 0 == isKnownWritable(ppst->pf) ) restoreConsoleArb(ppst); return rv; }else { # endif return (int)fwrite(cBuf, 1, nAccept, pfO); # if CIO_WIN_WC_XLATE } # endif } # endif SQLITE_INTERNAL_LINKAGE int oPutbUtf8(const char *cBuf, int nAccept){ FILE *pfOut; PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ # if CIO_WIN_WC_XLATE PerStreamTags *ppst = getEmitStreamInfo(1, &pst, &pfOut); # else getEmitStreamInfo(1, &pst, &pfOut); # endif # if CIO_WIN_WC_XLATE if( pstReachesConsole(ppst) ){ return conZstrEmit(ppst, cBuf, nAccept); }else { # endif return (int)fwrite(cBuf, 1, nAccept, pfOut); # if CIO_WIN_WC_XLATE } # endif } # ifdef CONSIO_EPUTB SQLITE_INTERNAL_LINKAGE int ePutbUtf8(const char *cBuf, int nAccept){ FILE *pfErr; PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ PerStreamTags *ppst = getEmitStreamInfo(2, &pst, &pfErr); # if CIO_WIN_WC_XLATE if( pstReachesConsole(ppst) ){ return conZstrEmit(ppst, cBuf, nAccept); }else { # endif return (int)fwrite(cBuf, 1, nAccept, pfErr); # if CIO_WIN_WC_XLATE } # endif } # endif /* defined(CONSIO_EPUTB) */ SQLITE_INTERNAL_LINKAGE char* fGetsUtf8(char *cBuf, int ncMax, FILE *pfIn){ if( pfIn==0 ) pfIn = stdin; # if CIO_WIN_WC_XLATE if( pfIn == consoleInfo.pstSetup[0].pf && (consoleInfo.sacSetup & SAC_InConsole)!=0 ){ # if CIO_WIN_WC_XLATE==1 # define SHELL_GULP 150 /* Count of WCHARS to be gulped at a time */ WCHAR wcBuf[SHELL_GULP+1]; int lend = 0, noc = 0; if( ncMax > 0 ) cBuf[0] = 0; while( noc < ncMax-8-1 && !lend ){ /* There is room for at least 2 more characters and a 0-terminator. */ int na = (ncMax > SHELL_GULP*4+1 + noc)? SHELL_GULP : (ncMax-1 - noc)/4; # undef SHELL_GULP DWORD nbr = 0; BOOL bRC = ReadConsoleW(consoleInfo.pstSetup[0].hx, wcBuf, na, &nbr, 0); if( bRC && nbr>0 && (wcBuf[nbr-1]&0xF800)==0xD800 ){ /* Last WHAR read is first of a UTF-16 surrogate pair. Grab its mate. */ DWORD nbrx; bRC &= ReadConsoleW(consoleInfo.pstSetup[0].hx, wcBuf+nbr, 1, &nbrx, 0); if( bRC ) nbr += nbrx; } if( !bRC || (noc==0 && nbr==0) ) return 0; if( nbr > 0 ){ int nmb = WideCharToMultiByte(CP_UTF8, 0, wcBuf,nbr,0,0,0,0); if( nmb != 0 && noc+nmb <= ncMax ){ int iseg = noc; nmb = WideCharToMultiByte(CP_UTF8, 0, wcBuf,nbr,cBuf+noc,nmb,0,0); noc += nmb; /* Fixup line-ends as coded by Windows for CR (or "Enter".) ** This is done without regard for any setMode{Text,Binary}() ** call that might have been done on the interactive input. */ if( noc > 0 ){ if( cBuf[noc-1]=='\n' ){ lend = 1; if( noc > 1 && cBuf[noc-2]=='\r' ) cBuf[--noc-1] = '\n'; } } /* Check for ^Z (anywhere in line) too, to act as EOF. */ while( iseg < noc ){ if( cBuf[iseg]=='\x1a' ){ noc = iseg; /* Chop ^Z and anything following. */ lend = 1; /* Counts as end of line too. */ break; } ++iseg; } }else break; /* Drop apparent garbage in. (Could assert.) */ }else break; } /* If got nothing, (after ^Z chop), must be at end-of-file. */ if( noc > 0 ){ cBuf[noc] = 0; return cBuf; }else return 0; # endif }else{ # endif return fgets(cBuf, ncMax, pfIn); # if CIO_WIN_WC_XLATE } # endif } #endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */ #if defined(_MSC_VER) # pragma warning(default : 4204) #endif #undef SHELL_INVALID_FILE_PTR