/* ** Create a new bitmap object able to handle bits between 0 and iSize, ** inclusive. Return a pointer to the new object. Return NULL if ** malloc fails. */ private static Bitvec sqlite3BitvecCreate(uint iSize) { Bitvec p; //Debug.Assert( sizeof(p)==BITVEC_SZ ); p = new Bitvec(); //sqlite3MallocZero( sizeof(p) ); if (p != null) { p.iSize = iSize; } return(p); }
/* ** Clear the i-th bit. ** ** pBuf must be a pointer to at least BITVEC_SZ bytes of temporary storage ** that BitvecClear can use to rebuilt its hash table. */ private static void sqlite3BitvecClear(Bitvec p, uint i, uint[] pBuf) { if (p == null) { return; } Debug.Assert(i > 0); i--; while (p.iDivisor != 0) { var bin = i / p.iDivisor; i = i % p.iDivisor; p = p.u.apSub[bin]; if (null == p) { return; } } if (p.iSize <= BITVEC_NBIT) { p.u.aBitmap[i / BITVEC_SZELEM] &= (byte)~(1 << (int)(i & (BITVEC_SZELEM - 1))); } else { uint j; var aiValues = pBuf; Array.Copy(p.u.aHash, aiValues, p.u.aHash.Length); //memcpy(aiValues, p->u.aHash, sizeof(p->u.aHash)); p.u.aHash = new uint[aiValues.Length]; // memset(p->u.aHash, 0, sizeof(p->u.aHash)); p.nSet = 0; for (j = 0; j < BITVEC_NINT; j++) { if (aiValues[j] != 0 && aiValues[j] != i + 1) { var h = BITVEC_HASH(aiValues[j] - 1); p.nSet++; while (p.u.aHash[h] != 0) { h++; if (h >= BITVEC_NINT) { h = 0; } } p.u.aHash[h] = aiValues[j]; } } } }
/* ** Destroy a bitmap object. Reclaim all memory used. */ static void sqlite3BitvecDestroy(ref Bitvec p) { if (p == null) { return; } if (p.iDivisor != 0) { u32 i; for (i = 0; i < BITVEC_NPTR; i++) { sqlite3BitvecDestroy(ref p.u.apSub[i]); } } //sqlite3_free( ref p ); }
/* ** Check to see if the i-th bit is set. Return true or false. ** If p is NULL (if the bitmap has not been created) or if ** i is out of range, then return false. */ private static int sqlite3BitvecTest(Bitvec p, uint i) { if (p == null || i == 0) { return(0); } if (i > p.iSize) { return(0); } i--; while (p.iDivisor != 0) { var bin = i / p.iDivisor; i = i % p.iDivisor; p = p.u.apSub[bin]; if (null == p) { return(0); } } if (p.iSize <= BITVEC_NBIT) { return((p.u.aBitmap[i / BITVEC_SZELEM] & (1 << (int)(i & (BITVEC_SZELEM - 1)))) != 0 ? 1 : 0); } var h = BITVEC_HASH(i++); while (p.u.aHash[h] != 0) { if (p.u.aHash[h] == i) { return(1); } h = (h + 1) % BITVEC_NINT; } return(0); }
/* ** This routine runs an extensive test of the Bitvec code. ** ** The input is an array of integers that acts as a program ** to test the Bitvec. The integers are opcodes followed ** by 0, 1, or 3 operands, depending on the opcode. Another ** opcode follows immediately after the last operand. ** ** There are 6 opcodes numbered from 0 through 5. 0 is the ** "halt" opcode and causes the test to end. ** ** 0 Halt and return the number of errors ** 1 N S X Set N bits beginning with S and incrementing by X ** 2 N S X Clear N bits beginning with S and incrementing by X ** 3 N Set N randomly chosen bits ** 4 N Clear N randomly chosen bits ** 5 N S X Set N bits from S increment X in array only, not in bitvec ** ** The opcodes 1 through 4 perform set and clear operations are performed ** on both a Bitvec object and on a linear array of bits obtained from malloc. ** Opcode 5 works on the linear array only, not on the Bitvec. ** Opcode 5 is used to deliberately induce a fault in order to ** confirm that error detection works. ** ** At the conclusion of the test the linear array is compared ** against the Bitvec object. If there are any differences, ** an error is returned. If they are the same, zero is returned. ** ** If a memory allocation error occurs, return -1. */ private static int sqlite3BitvecBuiltinTest(uint sz, int[] aOp) { Bitvec pBitvec = null; byte[] pV = null; var rc = -1; int i, nx, pc, op; uint[] pTmpSpace; /* Allocate the Bitvec to be tested and a linear array of ** bits to act as the reference */ pBitvec = sqlite3BitvecCreate(sz); pV = sqlite3_malloc((int)(sz + 7) / 8 + 1); pTmpSpace = new uint[BITVEC_SZ]; // sqlite3_malloc( BITVEC_SZ ); if (pBitvec == null || pV == null || pTmpSpace == null) { goto bitvec_end; } Array.Clear(pV, 0, (int)(sz + 7) / 8 + 1); // memset( pV, 0, ( sz + 7 ) / 8 + 1 ); /* NULL pBitvec tests */ sqlite3BitvecSet(null, 1); sqlite3BitvecClear(null, 1, pTmpSpace); /* Run the program */ pc = 0; while ((op = aOp[pc]) != 0) { switch (op) { case 1: case 2: case 5: { nx = 4; i = aOp[pc + 2] - 1; aOp[pc + 2] += aOp[pc + 3]; break; } case 3: case 4: default: { nx = 2; long i64Temp = 0; sqlite3_randomness(sizeof(long), ref i64Temp); i = (int)i64Temp; break; } } if (--aOp[pc + 1] > 0) { nx = 0; } pc += nx; i = (int)((i & 0x7fffffff) % sz); if ((op & 1) != 0) { SETBIT(pV, i + 1); if (op != 5) { if (sqlite3BitvecSet(pBitvec, (uint)i + 1) != 0) { goto bitvec_end; } } } else { CLEARBIT(pV, i + 1); sqlite3BitvecClear(pBitvec, (uint)i + 1, pTmpSpace); } } /* Test to make sure the linear array exactly matches the ** Bitvec object. Start with the assumption that they do ** match (rc==0). Change rc to non-zero if a discrepancy ** is found. */ rc = sqlite3BitvecTest(null, 0) + sqlite3BitvecTest(pBitvec, sz + 1) + sqlite3BitvecTest(pBitvec, 0) + (int)(sqlite3BitvecSize(pBitvec) - sz); for (i = 1; i <= sz; i++) { if (TESTBIT(pV, i) != sqlite3BitvecTest(pBitvec, (uint)i)) { rc = i; break; } } /* Free allocated structure */ bitvec_end: //sqlite3_free( ref pTmpSpace ); //sqlite3_free( ref pV ); sqlite3BitvecDestroy(ref pBitvec); return(rc); }
/* ** Return the value of the iSize parameter specified when Bitvec *p ** was created. */ private static uint sqlite3BitvecSize(Bitvec p) { return(p.iSize); }
/* ** Set the i-th bit. Return 0 on success and an error code if ** anything goes wrong. ** ** This routine might cause sub-bitmaps to be allocated. Failing ** to get the memory needed to hold the sub-bitmap is the only ** that can go wrong with an insert, assuming p and i are valid. ** ** The calling function must ensure that p is a valid Bitvec object ** and that the value for "i" is within range of the Bitvec object. ** Otherwise the behavior is undefined. */ private static int sqlite3BitvecSet(Bitvec p, uint i) { uint h; if (p == null) { return(SQLITE_OK); } Debug.Assert(i > 0); Debug.Assert(i <= p.iSize); i--; while (p.iSize > BITVEC_NBIT && p.iDivisor != 0) { var bin = i / p.iDivisor; i = i % p.iDivisor; if (p.u.apSub[bin] == null) { p.u.apSub[bin] = sqlite3BitvecCreate(p.iDivisor); if (p.u.apSub[bin] == null) { return(SQLITE_NOMEM); } } p = p.u.apSub[bin]; } if (p.iSize <= BITVEC_NBIT) { p.u.aBitmap[i / BITVEC_SZELEM] |= (byte)(1 << (int)(i & (BITVEC_SZELEM - 1))); return(SQLITE_OK); } h = BITVEC_HASH(i++); /* if there wasn't a hash collision, and this doesn't */ /* completely fill the hash, then just add it without */ /* worring about sub-dividing and re-hashing. */ if (0 == p.u.aHash[h]) { if (p.nSet < BITVEC_NINT - 1) { goto bitvec_set_end; } goto bitvec_set_rehash; } /* there was a collision, check to see if it's already */ /* in hash, if not, try to find a spot for it */ do { if (p.u.aHash[h] == i) { return(SQLITE_OK); } h++; if (h >= BITVEC_NINT) { h = 0; } } while (p.u.aHash[h] != 0); /* we didn't find it in the hash. h points to the first */ /* available free spot. check to see if this is going to */ /* make our hash too "full". */ bitvec_set_rehash: if (p.nSet >= BITVEC_MXHASH) { uint j; int rc; var aiValues = new uint[BITVEC_NINT]; // = sqlite3StackAllocRaw(0, sizeof(p->u.aHash)); if (aiValues == null) { return(SQLITE_NOMEM); } Buffer.BlockCopy(p.u.aHash, 0, aiValues, 0, aiValues.Length * sizeof(uint)); // memcpy(aiValues, p->u.aHash, sizeof(p->u.aHash)); p.u.apSub = new Bitvec[BITVEC_NPTR]; //memset(p->u.apSub, 0, sizeof(p->u.apSub)); p.iDivisor = (uint)((p.iSize + BITVEC_NPTR - 1) / BITVEC_NPTR); rc = sqlite3BitvecSet(p, i); for (j = 0; j < BITVEC_NINT; j++) { if (aiValues[j] != 0) { rc |= sqlite3BitvecSet(p, aiValues[j]); } } //sqlite3StackFree( null, aiValues ); return(rc); } bitvec_set_end: p.nSet++; p.u.aHash[h] = i; return(SQLITE_OK); }
/* ** Return the value of the iSize parameter specified when Bitvec *p ** was created. */ static u32 sqlite3BitvecSize( Bitvec p ) { return p.iSize; }
/* ** Destroy a bitmap object. Reclaim all memory used. */ static void sqlite3BitvecDestroy( ref Bitvec p ) { if ( p == null ) return; if ( p.iDivisor != 0 ) { u32 i; for ( i = 0; i < BITVEC_NPTR; i++ ) { sqlite3BitvecDestroy( ref p.u.apSub[i] ); } } //sqlite3_free( ref p ); }
/* ** Clear the i-th bit. ** ** pBuf must be a pointer to at least BITVEC_SZ bytes of temporary storage ** that BitvecClear can use to rebuilt its hash table. */ static void sqlite3BitvecClear( Bitvec p, u32 i, u32[] pBuf ) { if ( p == null ) return; Debug.Assert( i > 0 ); i--; while ( p.iDivisor != 0 ) { u32 bin = i / p.iDivisor; i = i % p.iDivisor; p = p.u.apSub[bin]; if ( null == p ) { return; } } if ( p.iSize <= BITVEC_NBIT ) { p.u.aBitmap[i / BITVEC_SZELEM] &= (byte)~( ( 1 << (int)( i & ( BITVEC_SZELEM - 1 ) ) ) ); } else { u32 j; u32[] aiValues = pBuf; Array.Copy( p.u.aHash, aiValues, p.u.aHash.Length );//memcpy(aiValues, p->u.aHash, sizeof(p->u.aHash)); p.u.aHash = new u32[aiValues.Length];// memset(p->u.aHash, 0, sizeof(p->u.aHash)); p.nSet = 0; for ( j = 0; j < BITVEC_NINT; j++ ) { if ( aiValues[j] != 0 && aiValues[j] != ( i + 1 ) ) { u32 h = BITVEC_HASH( aiValues[j] - 1 ); p.nSet++; while ( p.u.aHash[h] != 0 ) { h++; if ( h >= BITVEC_NINT ) h = 0; } p.u.aHash[h] = aiValues[j]; } } } }
/* ** Set the i-th bit. Return 0 on success and an error code if ** anything goes wrong. ** ** This routine might cause sub-bitmaps to be allocated. Failing ** to get the memory needed to hold the sub-bitmap is the only ** that can go wrong with an insert, assuming p and i are valid. ** ** The calling function must ensure that p is a valid Bitvec object ** and that the value for "i" is within range of the Bitvec object. ** Otherwise the behavior is undefined. */ static int sqlite3BitvecSet( Bitvec p, u32 i ) { u32 h; if ( p == null ) return SQLITE_OK; Debug.Assert( i > 0 ); Debug.Assert( i <= p.iSize ); i--; while ( ( p.iSize > BITVEC_NBIT ) && p.iDivisor != 0 ) { u32 bin = i / p.iDivisor; i = i % p.iDivisor; if ( p.u.apSub[bin] == null ) { p.u.apSub[bin] = sqlite3BitvecCreate( p.iDivisor ); //if ( p.u.apSub[bin] == null ) // return SQLITE_NOMEM; } p = p.u.apSub[bin]; } if ( p.iSize <= BITVEC_NBIT ) { p.u.aBitmap[i / BITVEC_SZELEM] |= (byte)( 1 << (int)( i & ( BITVEC_SZELEM - 1 ) ) ); return SQLITE_OK; } h = BITVEC_HASH( i++ ); /* if there wasn't a hash collision, and this doesn't */ /* completely fill the hash, then just add it without */ /* worring about sub-dividing and re-hashing. */ if ( 0 == p.u.aHash[h] ) { if ( p.nSet < ( BITVEC_NINT - 1 ) ) { goto bitvec_set_end; } else { goto bitvec_set_rehash; } } /* there was a collision, check to see if it's already */ /* in hash, if not, try to find a spot for it */ do { if ( p.u.aHash[h] == i ) return SQLITE_OK; h++; if ( h >= BITVEC_NINT ) h = 0; } while ( p.u.aHash[h] != 0 ); /* we didn't find it in the hash. h points to the first */ /* available free spot. check to see if this is going to */ /* make our hash too "full". */ bitvec_set_rehash: if ( p.nSet >= BITVEC_MXHASH ) { u32 j; int rc; u32[] aiValues = new u32[BITVEC_NINT];// = sqlite3StackAllocRaw(0, sizeof(p->u.aHash)); //if ( aiValues == null ) //{ // return SQLITE_NOMEM; //} //else { Buffer.BlockCopy( p.u.aHash, 0, aiValues, 0, aiValues.Length * ( sizeof( u32 ) ) );// memcpy(aiValues, p->u.aHash, sizeof(p->u.aHash)); p.u.apSub = new Bitvec[BITVEC_NPTR];//memset(p->u.apSub, 0, sizeof(p->u.apSub)); p.iDivisor = (u32)( ( p.iSize + BITVEC_NPTR - 1 ) / BITVEC_NPTR ); rc = sqlite3BitvecSet( p, i ); for ( j = 0; j < BITVEC_NINT; j++ ) { if ( aiValues[j] != 0 ) rc |= sqlite3BitvecSet( p, aiValues[j] ); } //sqlite3StackFree( null, aiValues ); return rc; } } bitvec_set_end: p.nSet++; p.u.aHash[h] = i; return SQLITE_OK; }
/* ** Check to see if the i-th bit is set. Return true or false. ** If p is NULL (if the bitmap has not been created) or if ** i is out of range, then return false. */ static int sqlite3BitvecTest( Bitvec p, u32 i ) { if ( p == null || i == 0 ) return 0; if ( i > p.iSize ) return 0; i--; while ( p.iDivisor != 0 ) { u32 bin = i / p.iDivisor; i = i % p.iDivisor; p = p.u.apSub[bin]; if ( null == p ) { return 0; } } if ( p.iSize <= BITVEC_NBIT ) { return ( ( p.u.aBitmap[i / BITVEC_SZELEM] & ( 1 << (int)( i & ( BITVEC_SZELEM - 1 ) ) ) ) != 0 ) ? 1 : 0; } else { u32 h = BITVEC_HASH( i++ ); while ( p.u.aHash[h] != 0 ) { if ( p.u.aHash[h] == i ) return 1; h = ( h + 1 ) % BITVEC_NINT; } return 0; } }
/* ** Create a new bitmap object able to handle bits between 0 and iSize, ** inclusive. Return a pointer to the new object. Return NULL if ** malloc fails. */ static Bitvec sqlite3BitvecCreate( u32 iSize ) { Bitvec p; //Debug.Assert( sizeof(p)==BITVEC_SZ ); p = new Bitvec();//sqlite3MallocZero( sizeof(p) ); if ( p != null ) { p.iSize = iSize; } return p; }
/* ** Return the value of the iSize parameter specified when Bitvec *p ** was created. */ static u32 sqlite3BitvecSize(Bitvec p) { return(p.iSize); }
/* ** Read a single page from either the journal file (if isMainJrnl==1) or ** from the sub-journal (if isMainJrnl==0) and playback that page. ** The page begins at offset *pOffset into the file. The *pOffset ** value is increased to the start of the next page in the journal. ** ** The isMainJrnl flag is true if this is the main rollback journal and ** false for the statement journal. The main rollback journal uses ** checksums - the statement journal does not. ** ** If the page number of the page record read from the (sub-)journal file ** is greater than the current value of Pager.dbSize, then playback is ** skipped and SQLITE_OK is returned. ** ** If pDone is not NULL, then it is a record of pages that have already ** been played back. If the page at *pOffset has already been played back ** (if the corresponding pDone bit is set) then skip the playback. ** Make sure the pDone bit corresponding to the *pOffset page is set ** prior to returning. ** ** If the page record is successfully read from the (sub-)journal file ** and played back, then SQLITE_OK is returned. If an IO error occurs ** while reading the record from the (sub-)journal file or while writing ** to the database file, then the IO error code is returned. If data ** is successfully read from the (sub-)journal file but appears to be ** corrupted, SQLITE_DONE is returned. Data is considered corrupted in ** two circumstances: ** ** * If the record page-number is illegal (0 or PAGER_MJ_PGNO), or ** * If the record is being rolled back from the main journal file ** and the checksum field does not match the record content. ** ** Neither of these two scenarios are possible during a savepoint rollback. ** ** If this is a savepoint rollback, then memory may have to be dynamically ** allocated by this function. If this is the case and an allocation fails, ** SQLITE_NOMEM is returned. */ static int pager_playback_one_page( Pager pPager, /* The pager being played back */ int isMainJrnl, /* True for main rollback journal. False for Stmt jrnl */ int isUnsync, /* True if reading from unsynced main journal */ ref i64 pOffset, /* Offset of record to playback */ int isSavepnt, /* True for a savepoint rollback */ Bitvec pDone /* Bitvec of pages already played back */ ) { int rc; PgHdr pPg; /* An existing page in the cache */ Pgno pgno = 0; /* The page number of a page in journal */ u32 cksum = 0; /* Checksum used for sanity checking */ byte[] aData; /* Temporary storage for the page */ sqlite3_file jfd; /* The file descriptor for the journal file */ Debug.Assert( ( isMainJrnl & ~1 ) == 0 ); /* isMainJrnl is 0 or 1 */ Debug.Assert( ( isSavepnt & ~1 ) == 0 ); /* isSavepnt is 0 or 1 */ Debug.Assert( isMainJrnl != 0 || pDone != null ); /* pDone always used on sub-journals */ Debug.Assert( isSavepnt != 0 || pDone == null ); /* pDone never used on non-savepoint */ aData = pPager.pTmpSpace; Debug.Assert( aData != null ); /* Temp storage must have already been allocated */ /* Read the page number and page data from the journal or sub-journal ** file. Return an error code to the caller if an IO error occurs. */ jfd = isMainJrnl != 0 ? pPager.jfd : pPager.sjfd; rc = read32bits( jfd, pOffset, ref pgno ); if ( rc != SQLITE_OK ) return rc; rc = sqlite3OsRead( jfd, aData, pPager.pageSize, ( pOffset ) + 4 ); if ( rc != SQLITE_OK ) return rc; pOffset += pPager.pageSize + 4 + isMainJrnl * 4; /* Sanity checking on the page. This is more important that I originally ** thought. If a power failure occurs while the journal is being written, ** it could cause invalid data to be written into the journal. We need to ** detect this invalid data (with high probability) and ignore it. */ if ( pgno == 0 || pgno == PAGER_MJ_PGNO( pPager ) ) { Debug.Assert( 0 == isSavepnt ); return SQLITE_DONE; } if ( pgno > pPager.dbSize || sqlite3BitvecTest( pDone, pgno ) != 0 ) { return SQLITE_OK; } if ( isMainJrnl != 0 ) { rc = read32bits( jfd, ( pOffset ) - 4, ref cksum ); if ( rc != 0 ) return rc; if ( 0 == isSavepnt && pager_cksum( pPager, aData ) != cksum ) { return SQLITE_DONE; } } if ( pDone != null && ( rc = sqlite3BitvecSet( pDone, pgno ) ) != SQLITE_OK ) { return rc; } Debug.Assert( pPager.state == PAGER_RESERVED || pPager.state >= PAGER_EXCLUSIVE ); /* If the pager is in RESERVED state, then there must be a copy of this ** page in the pager cache. In this case just update the pager cache, ** not the database file. The page is left marked dirty in this case. ** ** An exception to the above rule: If the database is in no-sync mode ** and a page is moved during an incremental vacuum then the page may ** not be in the pager cache. Later: if a malloc() or IO error occurs ** during a Movepage() call, then the page may not be in the cache ** either. So the condition described in the above paragraph is not ** Debug.Assert()able. ** ** If in EXCLUSIVE state, then we update the pager cache if it exists ** and the main file. The page is then marked not dirty. ** ** Ticket #1171: The statement journal might contain page content that is ** different from the page content at the start of the transaction. ** This occurs when a page is changed prior to the start of a statement ** then changed again within the statement. When rolling back such a ** statement we must not write to the original database unless we know ** for certain that original page contents are synced into the main rollback ** journal. Otherwise, a power loss might leave modified data in the ** database file without an entry in the rollback journal that can ** restore the database to its original form. Two conditions must be ** met before writing to the database files. (1) the database must be ** locked. (2) we know that the original page content is fully synced ** in the main journal either because the page is not in cache or else ** the page is marked as needSync==0. ** ** 2008-04-14: When attempting to vacuum a corrupt database file, it ** is possible to fail a statement on a database that does not yet exist. ** Do not attempt to write if database file has never been opened. */ pPg = pager_lookup( pPager, pgno ); Debug.Assert( pPg != null || #if SQLITE_OMIT_MEMORYDB 0==MEMDB #else pPager.memDb == 0 #endif ); PAGERTRACE( "PLAYBACK %d page %d hash(%08x) %s\n", PAGERID( pPager ), pgno, pager_datahash( pPager.pageSize, aData ), ( isMainJrnl != 0 ? "main-journal" : "sub-journal" ) ); if ( ( pPager.state >= PAGER_EXCLUSIVE ) && ( pPg == null || 0 == ( pPg.flags & PGHDR_NEED_SYNC ) ) && isOpen( pPager.fd ) && 0 == isUnsync ) { i64 ofst = ( pgno - 1 ) * (i64)pPager.pageSize; rc = sqlite3OsWrite( pPager.fd, (u8[])aData, pPager.pageSize, ofst ); if ( pgno > pPager.dbFileSize ) { pPager.dbFileSize = (u32)pgno; } if ( pPager.pBackup != null ) { if ( CODEC1( pPager, aData, pgno, SQLITE_DECRYPT ) ) rc = SQLITE_NOMEM; // CODEC1( pPager, aData, pgno, 3, rc = SQLITE_NOMEM ); sqlite3BackupUpdate( pPager.pBackup, pgno, (u8[])aData ); if ( CODEC2( pPager, aData, pgno, SQLITE_ENCRYPT_READ_CTX,ref aData ) ) rc = SQLITE_NOMEM;//CODEC2( pPager, aData, pgno, 7, rc = SQLITE_NOMEM, aData); } } else if ( 0 == isMainJrnl && pPg == null ) { /* If this is a rollback of a savepoint and data was not written to ** the database and the page is not in-memory, there is a potential ** problem. When the page is next fetched by the b-tree layer, it ** will be read from the database file, which may or may not be ** current. ** ** There are a couple of different ways this can happen. All are quite ** obscure. When running in synchronous mode, this can only happen ** if the page is on the free-list at the start of the transaction, then ** populated, then moved using sqlite3PagerMovepage(). ** ** The solution is to add an in-memory page to the cache containing ** the data just read from the sub-journal. Mark the page as dirty ** and if the pager requires a journal-sync, then mark the page as ** requiring a journal-sync before it is written. */ Debug.Assert( isSavepnt != 0 ); if ( ( rc = sqlite3PagerAcquire( pPager, (u32)pgno, ref pPg, 1 ) ) != SQLITE_OK ) { return rc; } pPg.flags &= ~PGHDR_NEED_READ; sqlite3PcacheMakeDirty( pPg ); } if ( pPg != null ) { /* No page should ever be explicitly rolled back that is in use, except ** for page 1 which is held in use in order to keep the lock on the ** database active. However such a page may be rolled back as a result ** of an internal error resulting in an automatic call to ** sqlite3PagerRollback(). */ byte[] pData = pPg.pData; Buffer.BlockCopy( aData, 0, pData, 0, pPager.pageSize );// memcpy(pData, (u8[])aData, pPager.pageSize); pPager.xReiniter( pPg ); if ( isMainJrnl != 0 && ( 0 == isSavepnt || pOffset <= pPager.journalHdr ) ) { /* If the contents of this page were just restored from the main ** journal file, then its content must be as they were when the ** transaction was first opened. In this case we can mark the page ** as clean, since there will be no need to write it out to the. ** ** There is one exception to this rule. If the page is being rolled ** back as part of a savepoint (or statement) rollback from an ** unsynced portion of the main journal file, then it is not safe ** to mark the page as clean. This is because marking the page as ** clean will clear the PGHDR_NEED_SYNC flag. Since the page is ** already in the journal file (recorded in Pager.pInJournal) and ** the PGHDR_NEED_SYNC flag is cleared, if the page is written to ** again within this transaction, it will be marked as dirty but ** the PGHDR_NEED_SYNC flag will not be set. It could then potentially ** be written out into the database file before its journal file ** segment is synced. If a crash occurs during or following this, ** database corruption may ensue. */ sqlite3PcacheMakeClean( pPg ); } #if SQLITE_CHECK_PAGES pPg.pageHash = pager_pagehash(pPg); #endif /* If this was page 1, then restore the value of Pager.dbFileVers. ** Do this before any decoding. */ if ( pgno == 1 ) { Buffer.BlockCopy( pData, 24, pPager.dbFileVers, 0, pPager.dbFileVers.Length ); //memcpy(pPager.dbFileVers, ((u8*)pData)[24], sizeof(pPager.dbFileVers)); } /* Decode the page just read from disk */ if ( CODEC1( pPager, pData, pPg.pgno, SQLITE_DECRYPT ) ) rc = SQLITE_NOMEM; //CODEC1(pPager, pData, pPg.pgno, 3, rc=SQLITE_NOMEM); sqlite3PcacheRelease( pPg ); } return rc; }