📄 pager.c
字号:
if( rc!=eDb_OK ) goto end_ckpt_playback;
}
/* Figure out how many pages need to be copied out of the transaction
** journal.
*/
rc = eDbOsSeek(&pPager->jfd, pPager->ckptJSize);
if( rc!=eDb_OK ){
goto end_ckpt_playback;
}
rc = eDbOsFileSize(&pPager->jfd, &szJ);
if( rc!=eDb_OK ){
goto end_ckpt_playback;
}
nRec = (int)((szJ - pPager->ckptJSize)/JOURNAL_PG_SZ(journal_format));
for(i=nRec-1; i>=0; i--){
rc = pager_playback_one_page(pPager, &pPager->jfd, journal_format);
if( rc!=eDb_OK ){
assert( rc!=eDb_DONE );
goto end_ckpt_playback;
}
}
end_ckpt_playback:
if( rc!=eDb_OK ){
pPager->errMask |= PAGER_ERR_CORRUPT;
rc = eDb_CORRUPT;
}
return rc;
}
/*
** Change the maximum number of in-memory pages that are allowed.
**
** The maximum number is the absolute value of the mxPage parameter.
** If mxPage is negative, the noSync flag is also set. noSync bypasses
** calls to eDbOsSync(). The pager runs much faster with noSync on,
** but if the operating system crashes or there is an abrupt power
** failure, the database file might be left in an inconsistent and
** unrepairable state.
*/
void eDbpager_set_cachesize(Pager *pPager, int mxPage){
if( mxPage>=0 ){
pPager->noSync = pPager->tempFile;
if( pPager->noSync==0 ) pPager->needSync = 0;
}else{
pPager->noSync = 1;
mxPage = -mxPage;
}
if( mxPage>10 ){
pPager->mxPage = mxPage;
}
}
/*
** Adjust the robustness of the database to damage due to OS crashes
** or power failures by changing the number of syncs()s when writing
** the rollback journal. There are three levels:
**
** OFF eDbOsSync() is never called. This is the default
** for temporary and transient files.
**
** NORMAL The journal is synced once before writes begin on the
** database. This is normally adequate protection, but
** it is theoretically possible, though very unlikely,
** that an inopertune power failure could leave the journal
** in a state which would cause damage to the database
** when it is rolled back.
**
** FULL The journal is synced twice before writes begin on the
** database (with some additional information - the nRec field
** of the journal header - being written in between the two
** syncs). If we assume that writing a
** single disk sector is atomic, then this mode provides
** assurance that the journal will not be corrupted to the
** point of causing damage to the database during rollback.
**
** Numeric values associated with these states are OFF==1, NORMAL=2,
** and FULL=3.
*/
void eDbpager_set_safety_level(Pager *pPager, int level){
pPager->noSync = level==1 || pPager->tempFile;
pPager->fullSync = level==3 && !pPager->tempFile;
if( pPager->noSync==0 ) pPager->needSync = 0;
}
/*
** Open a temporary file. Write the name of the file into zName
** (zName must be at least eDb_TEMPNAME_SIZE bytes long.) Write
** the file descriptor into *fd. Return eDb_OK on success or some
** other error code if we fail.
**
** The OS will automatically delete the temporary file when it is
** closed.
*/
static int eDbpager_opentemp(char *zFile, OsFile *fd){
int cnt = 8;
int rc;
do{
cnt--;
eDbOsTempFileName(zFile);
rc = eDbOsOpenExclusive(zFile, fd, 1);
}while( cnt>0 && rc!=eDb_OK );
return rc;
}
/*
** Create a new page cache and put a pointer to the page cache in *ppPager.
** The file to be cached need not exist. The file is not locked until
** the first call to eDbpager_get() and is only held open until the
** last page is released using eDbpager_unref().
**
** If zFilename is NULL then a randomly-named temporary file is created
** and used as the file to be cached. The file will be deleted
** automatically when it is closed.
*/
int eDbpager_open(
Pager **ppPager, /* Return the Pager structure here */
const char *zFilename, /* Name of the database file to open */
int mxPage, /* Max number of in-memory cache pages */
int nExtra, /* Extra bytes append to each in-memory page */
int useJournal /* TRUE to use a rollback journal on this file */
){
Pager *pPager;
char *zFullPathname;
int nameLen;
OsFile fd;
int rc, i;
int tempFile;
int readOnly = 0;
char zTemp[eDb_TEMPNAME_SIZE];
*ppPager = 0;
if( eDb_malloc_failed ){
return eDb_NOMEM;
}
if( zFilename && zFilename[0] ){
zFullPathname = eDbOsFullPathname(zFilename);
rc = eDbOsOpenReadWrite(zFullPathname, &fd, &readOnly);
tempFile = 0;
}else{
rc = eDbpager_opentemp(zTemp, &fd);
zFilename = zTemp;
zFullPathname = eDbOsFullPathname(zFilename);
tempFile = 1;
}
if( eDb_malloc_failed ){
return eDb_NOMEM;
}
if( rc!=eDb_OK ){
eDbFree(zFullPathname);
return eDb_CANTOPEN;
}
nameLen = strlen(zFullPathname);
pPager = eDbMalloc( sizeof(*pPager) + nameLen*3 + 30 );
if( pPager==0 ){
eDbOsClose(&fd);
eDbFree(zFullPathname);
return eDb_NOMEM;
}
SET_PAGER(pPager);
pPager->zFilename = (char*)&pPager[1];
pPager->zDirectory = &pPager->zFilename[nameLen+1];
pPager->zJournal = &pPager->zDirectory[nameLen+1];
strcpy(pPager->zFilename, zFullPathname);
strcpy(pPager->zDirectory, zFullPathname);
for(i=nameLen; i>0 && pPager->zDirectory[i-1]!='\\'; i--){}
if( i>0 ) pPager->zDirectory[i-1] = 0;
strcpy(pPager->zJournal, zFullPathname);
eDbFree(zFullPathname);
strcpy(&pPager->zJournal[nameLen], "-journal");
pPager->fd = fd;
pPager->journalOpen = 0;
pPager->useJournal = useJournal;
pPager->ckptOpen = 0;
pPager->ckptInUse = 0;
pPager->nRef = 0;
pPager->dbSize = -1;
pPager->ckptSize = 0;
pPager->ckptJSize = 0;
pPager->nPage = 0;
pPager->mxPage = mxPage>5 ? mxPage : 10;
pPager->state = eDb_UNLOCK;
pPager->errMask = 0;
pPager->tempFile = tempFile;
pPager->readOnly = readOnly;
pPager->needSync = 0;
pPager->noSync = pPager->tempFile || !useJournal;
pPager->pFirst = 0;
pPager->pFirstSynced = 0;
pPager->pLast = 0;
pPager->nExtra = nExtra;
memset(pPager->aHash, 0, sizeof(pPager->aHash));
*ppPager = pPager;
return eDb_OK;
}
/*
** Set the destructor for this pager. If not NULL, the destructor is called
** when the reference count on each page reaches zero. The destructor can
** be used to clean up information in the extra segment appended to each page.
**
** The destructor is not called as a result eDbpager_close().
** Destructors are only called by eDbpager_unref().
*/
void eDbpager_set_destructor(Pager *pPager, void (*xDesc)(void*)){
pPager->xDestructor = xDesc;
}
/*
** Return the total number of pages in the disk file associated with
** pPager.
*/
int eDbpager_pagecount(Pager *pPager){
off_t n;
assert( pPager!=0 );
if( pPager->dbSize>=0 ){
return pPager->dbSize;
}
if( eDbOsFileSize(&pPager->fd, &n)!=eDb_OK ){
pPager->errMask |= PAGER_ERR_DISK;
return 0;
}
n /= eDb_PAGE_SIZE;
if( pPager->state!=eDb_UNLOCK ){
pPager->dbSize = (int)n;
}
return (int)n;
}
/*
** Forward declaration
*/
static int syncJournal(Pager*);
/*
** Truncate the file to the number of pages specified.
*/
int eDbpager_truncate(Pager *pPager, Pgno nPage){
int rc;
if( pPager->dbSize<0 ){
eDbpager_pagecount(pPager);
}
if( pPager->errMask!=0 ){
rc = pager_errcode(pPager);
return rc;
}
if( nPage>=(unsigned)pPager->dbSize ){
return eDb_OK;
}
syncJournal(pPager);
rc = eDbOsTruncate(&pPager->fd, eDb_PAGE_SIZE*(off_t)nPage);
if( rc==eDb_OK ){
pPager->dbSize = nPage;
}
return rc;
}
/*
** Shutdown the page cache. Free all memory and close all files.
**
** If a transaction was in progress when this routine is called, that
** transaction is rolled back. All outstanding pages are invalidated
** and their memory is freed. Any attempt to use a page associated
** with this page cache after this function returns will likely
** result in a coredump.
*/
int eDbpager_close(Pager *pPager){
PgHdr *pPg, *pNext;
switch( pPager->state ){
case eDb_WRITELOCK: {
eDbpager_rollback(pPager);
eDbOsUnlock(&pPager->fd);
assert( pPager->journalOpen==0 );
break;
}
case eDb_READLOCK: {
eDbOsUnlock(&pPager->fd);
break;
}
default: {
/* Do nothing */
break;
}
}
for(pPg=pPager->pAll; pPg; pPg=pNext){
pNext = pPg->pNextAll;
eDbFree(pPg);
}
eDbOsClose(&pPager->fd);
assert( pPager->journalOpen==0 );
/* Temp files are automatically deleted by the OS
** if( pPager->tempFile ){
** eDbOsDelete(pPager->zFilename);
** }
*/
CLR_PAGER(pPager);
if( pPager->zFilename!=(char*)&pPager[1] ){
assert( 0 ); /* Cannot happen */
eDbFree(pPager->zFilename);
eDbFree(pPager->zJournal);
eDbFree(pPager->zDirectory);
}
eDbFree(pPager);
return eDb_OK;
}
/*
** Return the page number for the given page data.
*/
Pgno eDbpager_pagenumber(void *pData){
PgHdr *p = DATA_TO_PGHDR(pData);
return p->pgno;
}
/*
** Increment the reference count for a page. If the page is
** currently on the freelist (the reference count is zero) then
** remove it from the freelist.
*/
#define page_ref(P) ((P)->nRef==0?_page_ref(P):(void)(P)->nRef++)
static void _page_ref(PgHdr *pPg){
if( pPg->nRef==0 ){
/* The page is currently on the freelist. Remove it. */
if( pPg==pPg->pPager->pFirstSynced ){
PgHdr *p = pPg->pNextFree;
while( p && p->needSync ){ p = p->pNextFree; }
pPg->pPager->pFirstSynced = p;
}
if( pPg->pPrevFree ){
pPg->pPrevFree->pNextFree = pPg->pNextFree;
}else{
pPg->pPager->pFirst = pPg->pNextFree;
}
if( pPg->pNextFree ){
pPg->pNextFree->pPrevFree = pPg->pPrevFree;
}else{
pPg->pPager->pLast = pPg->pPrevFree;
}
pPg->pPager->nRef++;
}
pPg->nRef++;
REFINFO(pPg);
}
/*
** Increment the reference count for a page. The input pointer is
** a reference to the page data.
*/
int eDbpager_ref(void *pData){
PgHdr *pPg = DATA_TO_PGHDR(pData);
page_ref(pPg);
return eDb_OK;
}
/*
** Sync the journal. In other words, make sure all the pages that have
** been written to the journal have actually reached the surface of the
** disk. It is not safe to modify the original database file until after
** the journal has been synced. If the original database is modified before
** the journal is synced and a power failure occurs, the unsynced journal
** data would be lost and we would be unable to completely rollback the
** database changes. Database corruption would occur.
**
** This routine also updates the nRec field in the header of the journal.
** (See comments on the pager_playback() routine for additional information.)
** If the sync mode is FULL, two syncs will occur. First the whole journal
** is synced, then the nRec field is updated, then a second sync occurs.
**
** For temporary databases, we do not care if we are able to rollback
** after a power failure, so sync occurs.
**
** This routine clears the needSync field of every page current held in
** memory.
*/
static int syncJournal(Pager *pPager){
PgHdr *pPg;
int rc = eDb_OK;
/* Sync the journal before modifying the main database
** (assuming there is a journal and it needs to be synced.)
*/
if( pPager->needSync ){
if( !pPager->tempFile ){
assert( pPager->journalOpen );
/* assert( !pPager->noSync ); // noSync might be set if synchronous
** was turned off after the transaction was started. Ticket #615 */
#ifndef NDEBUG
{
/* Make sure the pPager->nRec counter we are keeping agrees
** with the nRec computed from the size of the journal file.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -