diff options
author | dan <dan@noemail.net> | 2011-04-22 19:37:32 +0000 |
---|---|---|
committer | dan <dan@noemail.net> | 2011-04-22 19:37:32 +0000 |
commit | cd74b611f4a36335e864f02f6be3e34dc39f711c (patch) | |
tree | 5f424307a796789cf7893c26b419ca91dc87c9ad /src | |
parent | fc083ab9731a59ceeece88453a097297260d414e (diff) | |
download | sqlite-cd74b611f4a36335e864f02f6be3e34dc39f711c.tar.gz sqlite-cd74b611f4a36335e864f02f6be3e34dc39f711c.zip |
Add the start of the "uri-filenames" feature.
FossilOrigin-Name: b8a8132e7148a7c90ca1352f20ab71d97b0bc4b0
Diffstat (limited to 'src')
-rw-r--r-- | src/global.c | 5 | ||||
-rw-r--r-- | src/main.c | 214 | ||||
-rw-r--r-- | src/pager.c | 14 | ||||
-rw-r--r-- | src/pragma.c | 10 | ||||
-rw-r--r-- | src/sqlite.h.in | 2 | ||||
-rw-r--r-- | src/sqliteInt.h | 3 | ||||
-rw-r--r-- | src/test_malloc.c | 30 | ||||
-rw-r--r-- | src/test_vfs.c | 16 | ||||
-rw-r--r-- | src/util.c | 6 |
9 files changed, 257 insertions, 43 deletions
diff --git a/src/global.c b/src/global.c index 0c890684d..f01eaa8f4 100644 --- a/src/global.c +++ b/src/global.c @@ -129,7 +129,9 @@ const unsigned char sqlite3CtypeMap[256] = { }; #endif - +#ifndef SQLITE_USE_URI +# define SQLITE_USE_URI 0 +#endif /* ** The following singleton contains the global configuration for @@ -139,6 +141,7 @@ SQLITE_WSD struct Sqlite3Config sqlite3Config = { SQLITE_DEFAULT_MEMSTATUS, /* bMemstat */ 1, /* bCoreMutex */ SQLITE_THREADSAFE==1, /* bFullMutex */ + SQLITE_USE_URI, /* bOpenUri */ 0x7ffffffe, /* mxStrlen */ 100, /* szLookaside */ 500, /* nLookaside */ diff --git a/src/main.c b/src/main.c index 4aaa61899..e2bcf009b 100644 --- a/src/main.c +++ b/src/main.c @@ -426,6 +426,11 @@ int sqlite3_config(int op, ...){ break; } + case SQLITE_CONFIG_URI: { + sqlite3GlobalConfig.bOpenUri = va_arg(ap, int); + break; + } + default: { rc = SQLITE_ERROR; break; @@ -1786,6 +1791,150 @@ int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ } /* +** This function is used to parse filenames passed by the user to API +** functions sqlite3_open() or sqlite3_open_v2(), and for database filenames +** specified as part of ATTACH statements. +*/ +int sqlite3ParseUri( + const char *zDefaultVfs, /* VFS to use if no "vfs=xxx" query option */ + const char *zUri, /* Nul-terminated URI to parse */ + int *pFlags, /* IN/OUT: SQLITE_OPEN_XXX flags */ + sqlite3_vfs **ppVfs, /* OUT: VFS to use */ + char **pzFile, /* OUT: Filename component of URI */ + char **pzErrMsg /* OUT: Error message (if rc!=SQLITE_OK) */ +){ + int flags = *pFlags; + const char *zVfs = zDefaultVfs; + char *zFile; + int nUri = sqlite3Strlen30(zUri); + + assert( *pzErrMsg==0 ); + + if( ((flags & SQLITE_OPEN_URI) || sqlite3GlobalConfig.bOpenUri) + && nUri>=5 && memcmp(zUri, "file:", 5)==0 + ){ + char *zOpt = 0; + int eState; /* Parser state when parsing URI */ + int iIn; /* Input character index */ + int iOut = 0; /* Output character index */ + int nByte = nUri+2; /* Bytes of space to allocate */ + for(iIn=0; iIn<nUri; iIn++) nByte += (zUri[iIn]=='&'); + + zFile = sqlite3_malloc(nByte); + if( !zFile ) return SQLITE_NOMEM; + + /* Discard the scheme and authority segments of the URI. */ + if( zUri[5]=='/' && zUri[6]=='/' ){ + iIn = 7; + while( zUri[iIn] && zUri[iIn]!='/' ) iIn++; + }else{ + iIn = 5; + } + + /* Copy the filename and any query parameters into the zFile buffer. + ** Decode %HH escape codes along the way. + ** + ** Within this loop, variable eState may be set to 0, 1 or 2, depending + ** on the parsing context. As follows: + ** + ** 0: Parsing file-name. + ** 1: Parsing name section of a name=value query parameter. + ** 2: Parsing value section of a name=value query parameter. + */ + eState = 0; + while( zUri[iIn] && zUri[iIn]!='#' ){ + char c = zUri[iIn++]; + if( c=='%' + && sqlite3Isxdigit(zUri[iIn]) + && sqlite3Isxdigit(zUri[iIn+1]) + ){ + int codepoint = (sqlite3HexToInt(zUri[iIn++]) << 4); + codepoint += sqlite3HexToInt(zUri[iIn++]); + + assert( codepoint>=0 && codepoint<256 ); + if( codepoint==0 ) continue; + c = codepoint; + }else if( (eState==0 && c=='?') || (eState==1 && c=='=') ){ + if( eState==0 ){ + zOpt = &zFile[iOut+1]; + } + eState++; + c = 0; + }else if( eState!=0 && c=='&' ){ + if( eState==1 ) zFile[iOut++] = '\0'; + eState = 1; + c = 0; + } + zFile[iOut++] = c; + } + if( eState==1 ) zFile[iOut++] = '\0'; + zFile[iOut++] = '\0'; + zFile[iOut++] = '\0'; + + /* Check if there were any options specified that should be interpreted + ** here. Options that are interpreted here include "vfs" and those that + ** correspond to flags that may be passed to the sqlite3_open_v2() + ** method. */ + if( zOpt ){ + struct Option { + const char *zOption; + int mask; + } aOpt [] = { + { "vfs", 0 }, + { "readonly", SQLITE_OPEN_READONLY }, + { "readwrite", SQLITE_OPEN_READWRITE }, + { "create", SQLITE_OPEN_CREATE }, + { "sharedcache", SQLITE_OPEN_SHAREDCACHE }, + { "privatecache", SQLITE_OPEN_PRIVATECACHE } + }; + + while( zOpt[0] ){ + int nOpt = sqlite3Strlen30(zOpt); + char *zVal = &zOpt[nOpt+1]; + int nVal = sqlite3Strlen30(zVal); + int i; + + for(i=0; i<ArraySize(aOpt); i++){ + const char *z = aOpt[i].zOption; + if( nOpt==sqlite3Strlen30(z) && 0==memcmp(zOpt, z, nOpt) ){ + int mask = aOpt[i].mask; + if( mask==0 ){ + zVfs = zVal; + }else{ + if( zVal[0]=='\0' || sqlite3GetBoolean(zVal) ){ + flags |= mask; + }else{ + flags &= ~mask; + } + } + } + } + + zOpt = &zVal[nVal+1]; + } + } + + }else{ + zFile = sqlite3_malloc(nUri+2); + if( !zFile ) return SQLITE_NOMEM; + memcpy(zFile, zUri, nUri); + zFile[nUri] = '\0'; + zFile[nUri+1] = '\0'; + } + + *ppVfs = sqlite3_vfs_find(zVfs); + if( *ppVfs==0 ){ + sqlite3_free(zFile); + *pzErrMsg = sqlite3_mprintf("no such vfs: %s", zVfs); + return SQLITE_ERROR; + } + *pFlags = flags; + *pzFile = zFile; + return SQLITE_OK; +} + + +/* ** This routine does the work of opening a database on behalf of ** sqlite3_open() and sqlite3_open16(). The database filename "zFilename" ** is UTF-8 encoded. @@ -1796,9 +1945,11 @@ static int openDatabase( unsigned flags, /* Operational flags */ const char *zVfs /* Name of the VFS to use */ ){ - sqlite3 *db; - int rc; - int isThreadsafe; + sqlite3 *db; /* Store allocated handle here */ + int rc; /* Return code */ + int isThreadsafe; /* True for threadsafe connections */ + char *zOpen = 0; /* Filename argument to pass to BtreeOpen() */ + char *zErrMsg = 0; /* Error message from sqlite3ParseUri() */ *ppDb = 0; #ifndef SQLITE_OMIT_AUTOINIT @@ -1806,24 +1957,6 @@ static int openDatabase( if( rc ) return rc; #endif - /* Only allow sensible combinations of bits in the flags argument. - ** Throw an error if any non-sense combination is used. If we - ** do not block illegal combinations here, it could trigger - ** assert() statements in deeper layers. Sensible combinations - ** are: - ** - ** 1: SQLITE_OPEN_READONLY - ** 2: SQLITE_OPEN_READWRITE - ** 6: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE - */ - assert( SQLITE_OPEN_READONLY == 0x01 ); - assert( SQLITE_OPEN_READWRITE == 0x02 ); - assert( SQLITE_OPEN_CREATE == 0x04 ); - testcase( (1<<(flags&7))==0x02 ); /* READONLY */ - testcase( (1<<(flags&7))==0x04 ); /* READWRITE */ - testcase( (1<<(flags&7))==0x40 ); /* READWRITE | CREATE */ - if( ((1<<(flags&7)) & 0x46)==0 ) return SQLITE_MISUSE; - if( sqlite3GlobalConfig.bCoreMutex==0 ){ isThreadsafe = 0; }else if( flags & SQLITE_OPEN_NOMUTEX ){ @@ -1903,13 +2036,6 @@ static int openDatabase( sqlite3HashInit(&db->aModule); #endif - db->pVfs = sqlite3_vfs_find(zVfs); - if( !db->pVfs ){ - rc = SQLITE_ERROR; - sqlite3Error(db, rc, "no such vfs: %s", zVfs); - goto opendb_out; - } - /* Add the default collation sequence BINARY. BINARY works for both UTF-8 ** and UTF-16, so add a version for each to avoid any unnecessary ** conversions. The only error that can occur here is a malloc() failure. @@ -1932,9 +2058,38 @@ static int openDatabase( createCollation(db, "NOCASE", SQLITE_UTF8, SQLITE_COLL_NOCASE, 0, nocaseCollatingFunc, 0); + /* Parse the filename/URI argument. */ + rc = sqlite3ParseUri(zVfs, zFilename, &flags, &db->pVfs, &zOpen, &zErrMsg); + if( rc!=SQLITE_OK ){ + sqlite3Error(db, rc, "%s", zErrMsg); + sqlite3_free(zErrMsg); + goto opendb_out; + } + + /* Only allow sensible combinations of bits in the flags argument. + ** Throw an error if any non-sense combination is used. If we + ** do not block illegal combinations here, it could trigger + ** assert() statements in deeper layers. Sensible combinations + ** are: + ** + ** 1: SQLITE_OPEN_READONLY + ** 2: SQLITE_OPEN_READWRITE + ** 6: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE + */ + assert( SQLITE_OPEN_READONLY == 0x01 ); + assert( SQLITE_OPEN_READWRITE == 0x02 ); + assert( SQLITE_OPEN_CREATE == 0x04 ); + testcase( (1<<(flags&7))==0x02 ); /* READONLY */ + testcase( (1<<(flags&7))==0x04 ); /* READWRITE */ + testcase( (1<<(flags&7))==0x40 ); /* READWRITE | CREATE */ + if( ((1<<(flags&7)) & 0x46)==0 ){ + rc = SQLITE_MISUSE; + goto opendb_out; + } + /* Open the backend database driver */ db->openFlags = flags; - rc = sqlite3BtreeOpen(zFilename, db, &db->aDb[0].pBt, 0, + rc = sqlite3BtreeOpen(zOpen, db, &db->aDb[0].pBt, 0, flags | SQLITE_OPEN_MAIN_DB); if( rc!=SQLITE_OK ){ if( rc==SQLITE_IOERR_NOMEM ){ @@ -2027,6 +2182,7 @@ static int openDatabase( sqlite3_wal_autocheckpoint(db, SQLITE_DEFAULT_WAL_AUTOCHECKPOINT); opendb_out: + sqlite3_free(zOpen); if( db ){ assert( db->mutex!=0 || isThreadsafe==0 || sqlite3GlobalConfig.bFullMutex==0 ); sqlite3_mutex_leave(db->mutex); diff --git a/src/pager.c b/src/pager.c index 94f647dcc..8ad6984f8 100644 --- a/src/pager.c +++ b/src/pager.c @@ -4299,6 +4299,8 @@ int sqlite3PagerOpen( int noReadlock = (flags & PAGER_NO_READLOCK)!=0; /* True to omit read-lock */ int pcacheSize = sqlite3PcacheSize(); /* Bytes to allocate for PCache */ u32 szPageDflt = SQLITE_DEFAULT_PAGE_SIZE; /* Default page size */ + const char *zUri = 0; /* URI args to copy */ + int nUri = 0; /* Number of bytes of URI args at *zUri */ /* Figure out how much space is required for each journal file-handle ** (there are two of them, the main journal and the sub-journal). This @@ -4329,6 +4331,7 @@ int sqlite3PagerOpen( ** leave both nPathname and zPathname set to 0. */ if( zFilename && zFilename[0] ){ + const char *z; nPathname = pVfs->mxPathname+1; zPathname = sqlite3Malloc(nPathname*2); if( zPathname==0 ){ @@ -4337,6 +4340,12 @@ int sqlite3PagerOpen( zPathname[0] = 0; /* Make sure initialized even if FullPathname() fails */ rc = sqlite3OsFullPathname(pVfs, zFilename, nPathname, zPathname); nPathname = sqlite3Strlen30(zPathname); + z = zUri = &zFilename[sqlite3Strlen30(zFilename)+1]; + while( *z ){ + z += sqlite3Strlen30(z)+1; + z += sqlite3Strlen30(z)+1; + } + nUri = &z[1] - zUri; if( rc==SQLITE_OK && nPathname+8>pVfs->mxPathname ){ /* This branch is taken when the journal path required by ** the database being opened will be more than pVfs->mxPathname @@ -4369,7 +4378,7 @@ int sqlite3PagerOpen( ROUND8(pcacheSize) + /* PCache object */ ROUND8(pVfs->szOsFile) + /* The main db file */ journalFileSize * 2 + /* The two journal files */ - nPathname + 1 + /* zFilename */ + nPathname + 1 + nUri + /* zFilename */ nPathname + 8 + 1 /* zJournal */ #ifndef SQLITE_OMIT_WAL + nPathname + 4 + 1 /* zWal */ @@ -4391,8 +4400,9 @@ int sqlite3PagerOpen( /* Fill in the Pager.zFilename and Pager.zJournal buffers, if required. */ if( zPathname ){ assert( nPathname>0 ); - pPager->zJournal = (char*)(pPtr += nPathname + 1); + pPager->zJournal = (char*)(pPtr += nPathname + 1 + nUri); memcpy(pPager->zFilename, zPathname, nPathname); + memcpy(&pPager->zFilename[nPathname+1], zUri, nUri); memcpy(pPager->zJournal, zPathname, nPathname); memcpy(&pPager->zJournal[nPathname], "-journal", 8); #ifndef SQLITE_OMIT_WAL diff --git a/src/pragma.c b/src/pragma.c index 75ab26d44..799805c40 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -49,7 +49,7 @@ static u8 getSafetyLevel(const char *z){ /* ** Interpret the given string as a boolean value. */ -static u8 getBoolean(const char *z){ +u8 sqlite3GetBoolean(const char *z){ return getSafetyLevel(z)&1; } @@ -219,7 +219,7 @@ static int flagPragma(Parse *pParse, const char *zLeft, const char *zRight){ mask &= ~(SQLITE_ForeignKeys); } - if( getBoolean(zRight) ){ + if( sqlite3GetBoolean(zRight) ){ db->flags |= mask; }else{ db->flags &= ~mask; @@ -433,7 +433,7 @@ void sqlite3Pragma( int b = -1; assert( pBt!=0 ); if( zRight ){ - b = getBoolean(zRight); + b = sqlite3GetBoolean(zRight); } if( pId2->n==0 && b>=0 ){ int ii; @@ -1033,7 +1033,7 @@ void sqlite3Pragma( #ifndef NDEBUG if( sqlite3StrICmp(zLeft, "parser_trace")==0 ){ if( zRight ){ - if( getBoolean(zRight) ){ + if( sqlite3GetBoolean(zRight) ){ sqlite3ParserTrace(stderr, "parser: "); }else{ sqlite3ParserTrace(0, 0); @@ -1047,7 +1047,7 @@ void sqlite3Pragma( */ if( sqlite3StrICmp(zLeft, "case_sensitive_like")==0 ){ if( zRight ){ - sqlite3RegisterLikeFunctions(db, getBoolean(zRight)); + sqlite3RegisterLikeFunctions(db, sqlite3GetBoolean(zRight)); } }else diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 421da8e6b..abe5bc008 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -479,6 +479,7 @@ int sqlite3_exec( #define SQLITE_OPEN_SHAREDCACHE 0x00020000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_PRIVATECACHE 0x00040000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_WAL 0x00080000 /* VFS only */ +#define SQLITE_OPEN_URI 0x00100000 /* Ok for sqlite3_open_v2() */ /* Reserved: 0x00F00000 */ @@ -1444,6 +1445,7 @@ struct sqlite3_mem_methods { #define SQLITE_CONFIG_PCACHE 14 /* sqlite3_pcache_methods* */ #define SQLITE_CONFIG_GETPCACHE 15 /* sqlite3_pcache_methods* */ #define SQLITE_CONFIG_LOG 16 /* xFunc, void* */ +#define SQLITE_CONFIG_URI 17 /* int */ /* ** CAPI3REF: Database Connection Configuration Options diff --git a/src/sqliteInt.h b/src/sqliteInt.h index ea0925e41..da1d42207 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -2420,6 +2420,7 @@ struct Sqlite3Config { int bMemstat; /* True to enable memory status */ int bCoreMutex; /* True to enable core mutexing */ int bFullMutex; /* True to enable full mutexing */ + int bOpenUri; /* True to interpret filenames as URIs */ int mxStrlen; /* Maximum string length */ int szLookaside; /* Default lookaside buffer size */ int nLookaside; /* Default lookaside buffer count */ @@ -2655,6 +2656,7 @@ void sqlite3ExprListDelete(sqlite3*, ExprList*); int sqlite3Init(sqlite3*, char**); int sqlite3InitCallback(void*, int, char**, char**); void sqlite3Pragma(Parse*,Token*,Token*,Token*,int); +u8 sqlite3GetBoolean(const char *z); void sqlite3ResetInternalSchema(sqlite3*, int); void sqlite3BeginParse(Parse*,int); void sqlite3CommitInternalChanges(sqlite3*); @@ -2919,6 +2921,7 @@ char sqlite3ExprAffinity(Expr *pExpr); int sqlite3Atoi64(const char*, i64*, int, u8); void sqlite3Error(sqlite3*, int, const char*,...); void *sqlite3HexToBlob(sqlite3*, const char *z, int n); +u8 sqlite3HexToInt(int h); int sqlite3TwoPartName(Parse *, Token *, Token *, Token **); const char *sqlite3ErrStr(int); int sqlite3ReadSchema(Parse *pParse); diff --git a/src/test_malloc.c b/src/test_malloc.c index c63ded703..5023dca44 100644 --- a/src/test_malloc.c +++ b/src/test_malloc.c @@ -1175,6 +1175,35 @@ static int test_config_error( } /* +** tclcmd: sqlite3_config_uri BOOLEAN +** +** Invoke sqlite3_config() or sqlite3_db_config() with invalid +** opcodes and verify that they return errors. +*/ +static int test_config_uri( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int rc; + int bOpenUri; + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "BOOL"); + return TCL_ERROR; + } + if( Tcl_GetBooleanFromObj(interp, objv[1], &bOpenUri) ){ + return TCL_ERROR; + } + + rc = sqlite3_config(SQLITE_CONFIG_URI, bOpenUri); + Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_VOLATILE); + + return TCL_OK; +} + +/* ** Usage: ** ** sqlite3_dump_memsys3 FILENAME @@ -1422,6 +1451,7 @@ int Sqlitetest_malloc_Init(Tcl_Interp *interp){ { "sqlite3_config_memstatus", test_config_memstatus ,0 }, { "sqlite3_config_lookaside", test_config_lookaside ,0 }, { "sqlite3_config_error", test_config_error ,0 }, + { "sqlite3_config_uri", test_config_uri ,0 }, { "sqlite3_db_config_lookaside",test_db_config_lookaside ,0 }, { "sqlite3_dump_memsys3", test_dump_memsys3 ,3 }, { "sqlite3_dump_memsys5", test_dump_memsys3 ,5 }, diff --git a/src/test_vfs.c b/src/test_vfs.c index 64b3cb574..53bdca68c 100644 --- a/src/test_vfs.c +++ b/src/test_vfs.c @@ -545,7 +545,7 @@ static int tvfsOpen( /* Evaluate the Tcl script: ** - ** SCRIPT xOpen FILENAME + ** SCRIPT xOpen FILENAME KEY-VALUE-ARGS ** ** If the script returns an SQLite error code other than SQLITE_OK, an ** error is returned to the caller. If it returns SQLITE_OK, the new @@ -554,7 +554,19 @@ static int tvfsOpen( */ Tcl_ResetResult(p->interp); if( p->pScript && p->mask&TESTVFS_OPEN_MASK ){ - tvfsExecTcl(p, "xOpen", Tcl_NewStringObj(pFd->zFilename, -1), 0, 0); + Tcl_Obj *pArg = Tcl_NewObj(); + Tcl_IncrRefCount(pArg); + if( flags&SQLITE_OPEN_MAIN_DB ){ + const char *z = &zName[strlen(zName)+1]; + while( *z ){ + Tcl_ListObjAppendElement(0, pArg, Tcl_NewStringObj(z, -1)); + z += strlen(z) + 1; + Tcl_ListObjAppendElement(0, pArg, Tcl_NewStringObj(z, -1)); + z += strlen(z) + 1; + } + } + tvfsExecTcl(p, "xOpen", Tcl_NewStringObj(pFd->zFilename, -1), pArg, 0); + Tcl_DecrRefCount(pArg); if( tvfsResultCode(p, &rc) ){ if( rc!=SQLITE_OK ) return rc; }else{ diff --git a/src/util.c b/src/util.c index 1c9b401f8..50dc59120 100644 --- a/src/util.c +++ b/src/util.c @@ -983,13 +983,12 @@ void sqlite3Put4byte(unsigned char *p, u32 v){ -#if !defined(SQLITE_OMIT_BLOB_LITERAL) || defined(SQLITE_HAS_CODEC) /* ** Translate a single byte of Hex into an integer. ** This routine only works if h really is a valid hexadecimal ** character: 0..9a..fA..F */ -static u8 hexToInt(int h){ +u8 sqlite3HexToInt(int h){ assert( (h>='0' && h<='9') || (h>='a' && h<='f') || (h>='A' && h<='F') ); #ifdef SQLITE_ASCII h += 9*(1&(h>>6)); @@ -999,7 +998,6 @@ static u8 hexToInt(int h){ #endif return (u8)(h & 0xf); } -#endif /* !SQLITE_OMIT_BLOB_LITERAL || SQLITE_HAS_CODEC */ #if !defined(SQLITE_OMIT_BLOB_LITERAL) || defined(SQLITE_HAS_CODEC) /* @@ -1016,7 +1014,7 @@ void *sqlite3HexToBlob(sqlite3 *db, const char *z, int n){ n--; if( zBlob ){ for(i=0; i<n; i+=2){ - zBlob[i/2] = (hexToInt(z[i])<<4) | hexToInt(z[i+1]); + zBlob[i/2] = (sqlite3HexToInt(z[i])<<4) | sqlite3HexToInt(z[i+1]); } zBlob[i/2] = 0; } |