Beispiel #1
0
        public RC Step(int pages)
        {
            MutexEx.Enter(SrcCtx.Mutex);
            Src.Enter();
            if (DestCtx != null)
                MutexEx.Enter(DestCtx.Mutex);

            RC rc = RC_;
            if (!IsFatalError(rc))
            {
                Pager srcPager = Src.get_Pager(); // Source pager
                Pager destPager = Dest.get_Pager(); // Dest pager
                Pid srcPage = 0; // Size of source db in pages
                bool closeTrans = false; // True if src db requires unlocking

                // If the source pager is currently in a write-transaction, return SQLITE_BUSY immediately.
                rc = (DestCtx != null && Src.Bt.InTransaction == TRANS.WRITE ? RC.BUSY : RC.OK);

                // Lock the destination database, if it is not locked already.
                if (rc == RC.OK && !DestLocked && (rc = Dest.BeginTrans(2)) == RC.OK)
                {
                    DestLocked = true;
                    Dest.GetMeta(Btree.META.SCHEMA_VERSION, ref DestSchema);
                }

                // If there is no open read-transaction on the source database, open one now. If a transaction is opened here, then it will be closed
                // before this function exits.
                if (rc == RC.OK && !Src.IsInReadTrans())
                {
                    rc = Src.BeginTrans(0);
                    closeTrans = true;
                }

                // Do not allow backup if the destination database is in WAL mode and the page sizes are different between source and destination
                int pgszSrc = Src.GetPageSize(); // Source page size
                int pgszDest = Dest.GetPageSize(); // Destination page size
                IPager.JOURNALMODE destMode = Dest.get_Pager().GetJournalMode(); // Destination journal mode
                if (rc == RC.OK && destMode == IPager.JOURNALMODE.WAL && pgszSrc != pgszDest)
                    rc = RC.READONLY;

                // Now that there is a read-lock on the source database, query the source pager for the number of pages in the database.
                srcPage = Src.LastPage();
                Debug.Assert(srcPage >= 0);
                for (int ii = 0; (pages < 0 || ii < pages) && NextId <= (Pid)srcPage && rc == 0; ii++)
                {
                    Pid srcPg = NextId; // Source page number
                    if (srcPg != Btree.PENDING_BYTE_PAGE(Src.Bt))
                    {
                        IPage srcPgAsObj = null; // Source page object
                        rc = srcPager.Acquire(srcPg, ref srcPgAsObj, false);
                        if (rc == RC.OK)
                        {
                            rc = BackupOnePage(p, srcPg, Pager.GetData(srcPgAsObj), false);
                            Pager.Unref(srcPgAsObj);
                        }
                    }
                    NextId++;
                }
                if (rc == RC.OK)
                {
                    Pagecount = srcPage;
                    Remaining = (srcPage + 1 - NextId);
                    if (NextId > srcPage)
                        rc = RC.DONE;
                    else if (!IsAttached)
                        AttachBackupObject(p);
                }

                // Update the schema version field in the destination database. This is to make sure that the schema-version really does change in
                // the case where the source and destination databases have the same schema version.
                if (rc == RC.DONE)
                {
                    if (srcPage == null)
                    {
                        rc = Dest.NewDb();
                        srcPage = 1;
                    }
                    if (rc == RC.OK || rc == RC.DONE)
                        rc = Dest.UpdateMeta(Btree.META.SCHEMA_VERSION, DestSchema + 1);
                    if (rc == RC.OK)
                    {
                        if (DestCtx != null)
                            Main.ResetAllSchemasOfConnection(DestCtx);
                        if (destMode == IPager.JOURNALMODE.WAL)
                            rc = Dest.SetVersion(2);
                    }
                    if (rc == RC.OK)
                    {
                        // Set nDestTruncate to the final number of pages in the destination database. The complication here is that the destination page
                        // size may be different to the source page size. 
                        //
                        // If the source page size is smaller than the destination page size, round up. In this case the call to sqlite3OsTruncate() below will
                        // fix the size of the file. However it is important to call sqlite3PagerTruncateImage() here so that any pages in the 
                        // destination file that lie beyond the nDestTruncate page mark are journalled by PagerCommitPhaseOne() before they are destroyed
                        // by the file truncation.

                        Debug.Assert(pgszSrc == Src.GetPageSize());
                        Debug.Assert(pgszDest == Dest.GetPageSize());
                        Pid destTruncate;
                        if (pgszSrc < pgszDest)
                        {
                            int ratio = pgszDest / pgszSrc;
                            destTruncate = (Pid)((srcPage + ratio - 1) / ratio);
                            if (destTruncate == Btree.PENDING_BYTE_PAGE(Dest.Bt))
                                destTruncate--;
                        }
                        else
                            destTruncate = (Pid)(srcPage * (pgszSrc / pgszDest));
                        Debug.Assert(destTruncate > 0);

                        if (pgszSrc < pgszDest)
                        {
                            // If the source page-size is smaller than the destination page-size, two extra things may need to happen:
                            //
                            //   * The destination may need to be truncated, and
                            //
                            //   * Data stored on the pages immediately following the pending-byte page in the source database may need to be
                            //     copied into the destination database.
                            int size = (int)(pgszSrc * srcPage);
                            VFile file = destPager.get_File();
                            Debug.Assert(file != null);
                            Debug.Assert((long)destTruncate * (long)pgszDest >= size || (destTruncate == (int)(Btree.PENDING_BYTE_PAGE(Dest.Bt) - 1) && size >= VFile.PENDING_BYTE && size <= VFile.PENDING_BYTE + pgszDest));

                            // This block ensures that all data required to recreate the original database has been stored in the journal for pDestPager and the
                            // journal synced to disk. So at this point we may safely modify the database file in any way, knowing that if a power failure
                            // occurs, the original database will be reconstructed from the journal file.
                            uint dstPage;
                            destPager.Pages(out dstPage);
                            for (Pid pg = destTruncate; rc == RC.OK && pg <= (Pid)dstPage; pg++)
                            {
                                if (pg != Btree.PENDING_BYTE_PAGE(Dest.Bt))
                                {
                                    IPage pgAsObj;
                                    rc = destPager.Acquire(pg, ref pgAsObj, false);
                                    if (rc == RC.OK)
                                    {
                                        rc = Pager.Write(pgAsObj);
                                        Pager.Unref(pgAsObj);
                                    }
                                }
                            }
                            if (rc == RC.OK)
                                rc = destPager.CommitPhaseOne(null, true);

                            // Write the extra pages and truncate the database file as required.
                            long end = Math.Min(VFile.PENDING_BYTE + pgszDest, size);
                            for (long off = VFile.PENDING_BYTE + pgszSrc; rc == RC.OK && off < end; off += pgszSrc)
                            {
                                Pid srcPg = (Pid)((off / pgszSrc) + 1);
                                PgHdr srcPgAsObj = null;
                                rc = srcPager.Acquire(srcPg, ref srcPgAsObj, false);
                                if (rc == RC.OK)
                                {
                                    byte[] data = Pager.GetData(srcPgAsObj);
                                    rc = file.Write(data, pgszSrc, off);
                                }
                                Pager.Unref(srcPgAsObj);
                            }
                            if (rc == RC.OK)
                                rc = BackupTruncateFile(file, (int)size);

                            // Sync the database file to disk. 
                            if (rc == RC.OK)
                                rc = destPager.Sync();
                        }
                        else
                        {
                            destPager.TruncateImage(destTruncate);
                            rc = destPager.CommitPhaseOne(null, false);
                        }

                        // Finish committing the transaction to the destination database.
                        if (rc == RC.OK && (rc = Dest.CommitPhaseTwo(false)) == RC.OK)
                            rc = RC.DONE;
                    }
                }

                // If bCloseTrans is true, then this function opened a read transaction on the source database. Close the read transaction here. There is
                // no need to check the return values of the btree methods here, as "committing" a read-only transaction cannot fail.
                if (closeTrans)
                {
#if !DEBUG || COVERAGE_TEST
                    RC rc2 = Src.CommitPhaseOne(null);
                    rc2 |= Src.CommitPhaseTwo(false);
                    Debug.Assert(rc2 == RC.OK);
#else
                    Src.CommitPhaseOne(null);
                    Src.CommitPhaseTwo(false);
#endif
                }

                if (rc == RC.IOERR_NOMEM)
                    rc = RC.NOMEM;
                RC_ = rc;
            }
            if (DestCtx != null)
                MutexEx.Leave(DestCtx.Mutex);
            Src.Leave();
            MutexEx.Leave(SrcCtx.Mutex);
            return rc;
        }
Beispiel #2
0
        static RC BackupOnePage(Backup p, Pid srcPg, byte[] srcData, bool update)
        {
            Pager destPager = p.Dest.get_Pager();
            int srcPgsz = p.Src.GetPageSize();
            int destPgsz = p.Dest.GetPageSize();
            int copy = Math.Min(srcPgsz, destPgsz);
            long end = (long)srcPg * (long)srcPgsz;
            RC rc = RC.OK;

            Debug.Assert(p.Src.GetReserveNoMutex() >= 0);
            Debug.Assert(p.DestLocked);
            Debug.Assert(!IsFatalError(p.RC_));
            Debug.Assert(srcPg != Btree.PENDING_BYTE_PAGE(p.Src.Bt));
            Debug.Assert(srcData != null);

            // Catch the case where the destination is an in-memory database and the page sizes of the source and destination differ.
            if (srcPgsz != destPgsz && destPager.get_MemoryDB)
                rc = RC.READONLY;

#if HAS_CODEC
            int srcReserve = p.Src.GetReserveNoMutex();
            int destReserve = p.Dest.GetReserve();


            // Backup is not possible if the page size of the destination is changing and a codec is in use.
            if (srcPgsz != destPgsz && Pager.GetCodec(destPager) != null)
                rc = RC.READONLY;

            // Backup is not possible if the number of bytes of reserve space differ between source and destination.  If there is a difference, try to
            // fix the destination to agree with the source.  If that is not possible, then the backup cannot proceed.
            if (srcReserve != destReserve)
            {
                uint newPgsz = (uint)srcPgsz;
                rc = destPager.SetPageSize(ref newPgsz, srcReserve);
                if (rc == RC.OK && newPgsz != srcPgsz)
                    rc = RC.READONLY;
            }
#endif

            // This loop runs once for each destination page spanned by the source page. For each iteration, variable iOff is set to the byte offset
            // of the destination page.
            for (long off = end - (long)srcPgsz; rc == RC.OK && off < end; off += destPgsz)
            {
                IPage destPg = null;
                uint dest = (uint)(off / destPgsz) + 1;
                if (dest == Btree.PENDING_BYTE_PAGE(p.Dest.Bt))
                    continue;
                if ((rc = destPager.Acquire(dest, ref destPg, false)) == RC.OK && (rc = Pager.Write(destPg)) == RC.OK)
                {
                    byte[] destData = Pager.GetData(destPg);

                    // Copy the data from the source page into the destination page. Then clear the Btree layer MemPage.isInit flag. Both this module
                    // and the pager code use this trick (clearing the first byte of the page 'extra' space to invalidate the Btree layers
                    // cached parse of the page). MemPage.isInit is marked "MUST BE FIRST" for this purpose.
                    Buffer.BlockCopy(srcData, (int)(off % srcPgsz), destData, (int)(off % destPgsz), copy);
                    Pager.GetExtra(destPg).IsInit = false;
                }
                Pager.Unref(destPg);
            }

            return rc;
        }
Beispiel #3
0
 static void pageReinit(IPage dbPage)
 {
     MemPage page = Pager.GetExtra<MemPage>(dbPage);
     Debug.Assert(Pager.get_PageRefs(dbPage) > 0);
     if (page.IsInit)
     {
         Debug.Assert(MutexEx.Held(page.Bt.Mutex));
         page.IsInit = false;
         if (Pager.get_PageRefs(dbPage) > 1)
         {
             // pPage might not be a btree page;  it might be an overflow page or ptrmap page or a free page.  In those cases, the following
             // call to btreeInitPage() will likely return SQLITE_CORRUPT. But no harm is done by this.  And it is very important that
             // btreeInitPage() be called on every btree page so we make the call for every page that comes in for re-initing.
             btreeInitPage(page);
         }
     }
 }
Beispiel #4
0
 static RC copyPayload(byte[] payload, uint payloadOffset, byte[] buf, uint bufOffset, uint bytes, int op, IPage dbPage)
 {
     if (op != 0)
     {
         // Copy data from buffer to page (a write operation)
         var rc = Pager.Write(dbPage);
         if (rc != RC.OK)
             return rc;
         Buffer.BlockCopy(buf, (int)bufOffset, payload, (int)payloadOffset, (int)bytes);
     }
     else
         // Copy data from page to buffer (a read operation)
         Buffer.BlockCopy(payload, (int)payloadOffset, buf, (int)bufOffset, (int)bytes);
     return RC.OK;
 }
Beispiel #5
0
 static MemPage btreePageFromDbPage(IPage dbPage, Pid id, BtShared bt)
 {
     MemPage page = Pager.GetExtra<MemPage>(dbPage);
     page.Data = Pager.GetData(dbPage);
     page.DBPage = dbPage;
     page.Bt = bt;
     page.ID = id;
     page.HdrOffset = (byte)(page.ID == 1 ? 100 : 0);
     return page;
 }