/*
** Copyright (c) 1999, 2000 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*************************************************************************
** This file contains code to implement the database baseend (DBBE)
** for sqlite. The database backend is the interface between
** sqlite and the code that does the actually reading and writing
** of information to the disk.
**
** This file uses GDBM as the database backend. It should be
** relatively simple to convert to a different database such
** as NDBM, SDBM, or BerkeleyDB.
**
** $Id: dbbe.c,v 1.10 2000/06/02 02:09:23 drh Exp $
*/
#include "sqliteInt.h"
#include <gdbm.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
#include <time.h>
/*
** Each open database file is an instance of this structure.
*/
typedef struct BeFile BeFile;
struct BeFile {
char *zName; /* Name of the file */
GDBM_FILE dbf; /* The file itself */
int nRef; /* Number of references */
int delOnClose; /* Delete when closing */
int writeable; /* Opened for writing */
BeFile *pNext, *pPrev; /* Next and previous on list of open files */
};
/*
** The following are state variables for the RC4 algorithm. We
** use RC4 as a random number generator. Each call to RC4 gives
** a random 8-bit number.
*/
struct rc4 {
int i, j;
int s[256];
};
/*
** The complete database is an instance of the following structure.
*/
struct Dbbe {
char *zDir; /* The directory containing the database */
int write; /* True for write permission */
BeFile *pOpen; /* List of open files */
int nTemp; /* Number of temporary files created */
FILE **apTemp; /* Space to hold temporary file pointers */
char **azTemp; /* Names of the temporary files */
struct rc4 rc4; /* The random number generator */
};
/*
** Each file within the database is an instance of this
** structure.
*/
struct DbbeTable {
Dbbe *pBe; /* The database of which this record is a part */
BeFile *pFile; /* The database file for this table */
datum key; /* Most recently used key */
datum data; /* Most recent data */
int needRewind; /* Next key should be the first */
int readPending; /* The fetch hasn't actually been done yet */
};
/*
** Initialize the RC4 algorithm.
*/
static void rc4init(struct rc4 *p, char *key, int keylen){
int i;
char k[256];
p->j = 0;
p->i = 0;
for(i=0; i<256; i++){
p->s[i] = i;
k[i] = key[i%keylen];
}
for(i=0; i<256; i++){
int t;
p->j = (p->j + p->s[i] + k[i]) & 0xff;
t = p->s[p->j];
p->s[p->j] = p->s[i];
p->s[i] = t;
}
}
/*
** Get a single 8-bit random value from the RC4 algorithm.
*/
static int rc4byte(struct rc4 *p){
int t;
p->i = (p->i + 1) & 0xff;
p->j = (p->j + p->s[p->i]) & 0xff;
t = p->s[p->i];
p->s[p->i] = p->s[p->j];
p->s[p->j] = t;
t = p->s[p->i] + p->s[p->j];
return t & 0xff;
}
/*
** This routine opens a new database. For the current driver scheme,
** the database name is the name of the directory
** containing all the files of the database.
*/
Dbbe *sqliteDbbeOpen(
const char *zName, /* The name of the database */
int writeFlag, /* True if we will be writing to the database */
int createFlag, /* True to create database if it doesn't exist */
char **pzErrMsg /* Write error messages (if any) here */
){
Dbbe *pNew;
struct stat statbuf;
char *zMaster;
if( !writeFlag ) createFlag = 0;
if( stat(zName, &statbuf)!=0 ){
if( createFlag ) mkdir(zName, 0750);
if( stat(zName, &statbuf)!=0 ){
sqliteSetString(pzErrMsg, "can't find or make directory \"",
zName, "\"", 0);
return 0;
}
}
if( !S_ISDIR(statbuf.st_mode) ){
sqliteSetString(pzErrMsg, "not a directory: \"", zName, "\"", 0);
return 0;
}
if( access(zName, writeFlag ? (X_OK|W_OK|R_OK) : (X_OK|R_OK)) ){
sqliteSetString(pzErrMsg, "access permission denied", 0);
return 0;
}
zMaster = 0;
sqliteSetString(&zMaster, zName, "/" MASTER_NAME, 0);
if( stat(zMaster, &statbuf)==0
&& access(zMaster, writeFlag ? (W_OK|R_OK) : R_OK)!=0 ){
sqliteSetString(pzErrMsg, "access permission denied for ", zMaster, 0);
sqliteFree(zMaster);
return 0;
}
sqliteFree(zMaster);
pNew = sqliteMalloc(sizeof(Dbbe) + strlen(zName) + 1);
if( pNew==0 ){
sqliteSetString(pzErrMsg, "out of memory", 0);
return 0;
}
pNew->zDir = (char*)&pNew[1];
strcpy(pNew->zDir, zName);
pNew->write = writeFlag;
pNew->pOpen = 0;
time(&statbuf.st_ctime);
rc4init(&pNew->rc4, (char*)&statbuf, sizeof(statbuf));
return pNew;
}
/*
** Completely shutdown the given database. Close all files. Free all memory.
*/
void sqliteDbbeClose(Dbbe *pBe){
BeFile *pFile, *pNext;
int i;
for(pFile=pBe->pOpen; pFile; pFile=pNext){
pNext = pFile->pNext;
gdbm_close(pFile->dbf);
memset(pFile, 0, sizeof(*pFile));
sqliteFree(pFile);
}
for(i=0; i<pBe->nTemp; i++){
if( pBe->apTemp[i]!=0 ){
unlink(pBe->azTemp[i]);
fclose(pBe->apTemp[i]);
sqliteFree(pBe->azTemp[i]);
pBe->apTemp[i] = 0;
pBe->azTemp[i] = 0;
break;
}
}
sqliteFree(pBe->azTemp);
sqliteFree(pBe->apTemp);
memset(pBe, 0, sizeof(*pBe));
sqliteFree(pBe);
}
/*
** Translate the name of a table into the name of a file that holds
** that table. Space to hold the filename is obtained from
** sqliteMalloc() and must be freed by the calling function.
*/
static char *sqliteFileOfTable(Dbbe *pBe, const char *zTable){
char *zFile = 0;
int i;
sqliteSetString(&zFile, pBe->zDir, "/", zTable, ".tbl", 0);
if( zFile==0 ) return 0;
for(i=strlen(pBe->zDir)+1; zFile[i]; i++){
int c = zFile[i];
if( isupper(c) ){
zFile[i] = tolower(c);
}else if( !isalnum(c) && c!='-' && c!='_' && c!='.' ){
zFile[i] = '+';
}
}
return zFile;
}
/*
** Generate a random filename with the given prefix.
*/
static void randomName(struct rc4 *pRc4, char *zBuf, char *zPrefix){
int i, j;
static const char zRandomChars[] = "abcdefghijklmnopqrstuvwxyz0123456789";
strcpy(zBuf, zPrefix);
j = strlen(zBuf);
for(i=0; i<15; i++){
int c = rc4byte(pRc4) % (sizeof(zRandomChars) - 1);
zBuf[j++] = zRandomChars[c];
}
zBuf[j] = 0;
}
/*
** Open a new table cursor
*/
int sqliteDbbeOpenTable(
Dbbe *pBe, /* The database the table belongs to */
const char *zTable, /* The name of the table */
int writeable, /* True to open for writing */
DbbeTable **ppTable /* Write the resulting table pointer here */
){
char *zFile; /* Name of the table file */
DbbeTable *pTable; /* The new table cursor */
BeFile *pFile; /* The underlying data file for this table */
int rc = SQLITE_OK; /* Return value */
int rw_mask; /* Permissions mask for opening a table */
int mode; /* Mode for opening a table */
*ppTable = 0;
pTable = sqliteMalloc( sizeof(*pTable) );
if( pTable==0 ) return SQLITE_NOMEM;
if( zTable ){
zFile = sqliteFileOfTable(pBe, zTable);
for(pFile=pBe->pOpen; pFile; pFile=pFile->pNext){
if( strcmp(pFile->zName,zFile)==0 ) break;
}
}else{
pFile = 0;
zFile = 0;
}
if( pFile==0 ){
if( writeable ){
rw_mask = GDBM_WRCREAT | GDBM_FAST;
mode = 0640;
}else{
rw_mask = GDBM_READER;
mode = 0640;
}
pFile = sqliteMalloc( sizeof(*pFile) );
if( pFile==0 ){
sqliteFree(zFile);
return SQLITE_NOMEM;
}
if( zFile ){
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
}else{
int limit;
struct rc4 *pRc4;
char zRandom[50];
pRc4 = &pBe->rc4;
zFile = 0;
limit = 5;
do {
randomName(&pBe->rc4, zRandom, "_temp_table_");
sqliteFree(zFile);
zFile = sqliteFileOfTable(pBe, zRandom);
pFile->dbf = gdbm_open(zFile, 0, rw_mask, mode, 0);
}while( pFile->dbf==0 && limit-- >= 0);
pFile->delOnClose = 1;
}
pFile->writeable = writeable;
pFile->zName = zFile;
pFile->nRef = 1;
pFile->pPrev = 0;
if( pBe->pOpen ){
pBe->pOpen->pPrev = pFile;
}
pFile->pNext = pBe->pOpen;
pBe->pOpen = pFile;
if( pFile->dbf==0 ){
if( !writeable && access(zFile,0) ){
rc = SQLITE_OK;
}else if( access(zFile,W_OK|R_OK) ){
rc = SQLITE_PERM;
}else{
rc = SQLITE_BUSY;
}
}
}else{
sqliteFree(zFile);
pFile->nRef++;
if( writeable && !pFile->writeable ){
rc = SQLITE_READONLY;
}
}
pTable->pBe = pBe;
pTable->pFile = pFile;
pTable->readPending = 0;
pTable->needRewind = 1;
*ppTable = pTable;
return rc;
}
/*
** Drop a table from the database.
*/
void sqliteDbbeDropTable(Dbbe *pBe, const char *zTable){
char *zFile; /* Name of the table file */
zFile = sqliteFileOfTable(pBe, zTable);
unlink(zFile);
sqliteFree(zFile);
}
/*
** Reorganize a table to reduce search times and disk usage.
*/
void sqliteDbbeReorganizeTable(Dbbe *pBe, const char *zTable){
char *zFile; /* Name of the table file */
DbbeTable *pTab;
if( sqliteDbbeOpenTable(pBe, zTable, 1, &pTab)!=SQLITE_OK ){
return;
}
if( pTab && pTab->pFile && pTab->pFile->dbf ){
gdbm_reorganize(pTab->pFile->dbf);
}
if( pTab ){
sqliteDbbeCloseTable(pTab);
}
}
/*
** Close a table previously opened by sqliteDbbeOpenTable().
*/
void sqliteDbbeCloseTable(DbbeTable *pTable){
BeFile *pFile;
Dbbe *pBe;
if( pTable==0 ) return;
pFile = pTable->pFile;
pBe = pTable->pBe;
pFile->nRef--;
if( pFile->dbf!=NULL ){
gdbm_sync(pFile->dbf);
}
if( pFile->nRef<=0 ){
if( pFile->dbf!=NULL ){
gdbm_close(pFile->dbf);
}
if( pFile->pPrev ){
pFile->pPrev->pNext = pFile->pNext;
}else{
pBe->pOpen = pFile->pNext;
}
if( pFile->pNext ){
pFile->pNext->pPrev = pFile->pPrev;
}
if( pFile->delOnClose ){
unlink(pFile->zName);
}
sqliteFree(pFile->zName);
memset(pFile, 0, sizeof(*pFile));
sqliteFree(pFile);
}
if( pTable->key.dptr ) free(pTable->key.dptr);
if( pTable->data.dptr ) free(pTable->data.dptr);
memset(pTable, 0, sizeof(*pTable));
sqliteFree(pTable);
}
/*
** Clear the given datum
*/
static void datumClear(datum *p){
if( p->dptr ) free(p->dptr);
p->dptr = 0;
p->dsize = 0;
}
/*
** Fetch a single record from an open table. Return 1 on success
** and 0 on failure.
*/
int sqliteDbbeFetch(DbbeTable *pTable, int nKey, char *pKey){
datum key;
key.dsize = nKey;
key.dptr = pKey;
datumClear(&pTable->key);
datumClear(&pTable->data);
if( pTable->pFile && pTable->pFile->dbf ){
pTable->data = gdbm_fetch(pTable->pFile->dbf, key);
}
return pTable->data.dptr!=0;
}
/*
** Return 1 if the given key is already in the table. Return 0
** if it is not.
*/
int sqliteDbbeTest(DbbeTable *pTable, int nKey, char *pKey){
datum key;
int result = 0;
key.dsize = nKey;
key.dptr = pKey;
if( pTable->pFile && pTable->pFile->dbf ){
result = gdbm_exists(pTable->pFile->dbf, key);
}
return result;
}
/*
** Copy bytes from the current key or data into a buffer supplied by
** the calling function. Return the number of bytes copied.
*/
int sqliteDbbeCopyKey(DbbeTable *pTable, int offset, int size, char *zBuf){
int n;
if( offset>=pTable->key.dsize ) return 0;
if( offset+size>pTable->key.dsize ){
n = pTable->key.dsize - offset;
}else{
n = size;
}
memcpy(zBuf, &pTable->key.dptr[offset], n);
return n;
}
int sqliteDbbeCopyData(DbbeTable *pTable, int offset, int size, char *zBuf){
int n;
if( pTable->readPending && pTable->pFile && pTable->pFile->dbf ){
pTable->data = gdbm_fetch(pTable->pFile->dbf, pTable->key);
pTable->readPending = 0;
}
if( offset>=pTable->data.dsize ) return 0;
if( offset+size>pTable->data.dsize ){
n = pTable->data.dsize - offset;
}else{
n = size;
}
memcpy(zBuf, &pTable->data.dptr[offset], n);
return n;
}
/*
** Return a pointer to bytes from the key or data. The data returned
** is ephemeral.
*/
char *sqliteDbbeReadKey(DbbeTable *pTable, int offset){
if( offset<0 || offset>=pTable->key.dsize ) return "";
return &pTable->key.dptr[offset];
}
char *sqliteDbbeReadData(DbbeTable *pTable, int offset){
if( pTable->readPending && pTable->pFile && pTable->pFile->dbf ){
pTable->data = gdbm_fetch(pTable->pFile->dbf, pTable->key);
pTable->readPending = 0;
}
if( offset<0 || offset>=pTable->data.dsize ) return "";
return &pTable->data.dptr[offset];
}
/*
** Return the total number of bytes in either data or key.
*/
int sqliteDbbeKeyLength(DbbeTable *pTable){
return pTable->key.dsize;
}
int sqliteDbbeDataLength(DbbeTable *pTable){
if( pTable->readPending && pTable->pFile && pTable->pFile->dbf ){
pTable->data = gdbm_fetch(pTable->pFile->dbf, pTable->key);
pTable->readPending = 0;
}
return pTable->data.dsize;
}
/*
** Make is so that the next call to sqliteNextKey() finds the first
** key of the table.
*/
int sqliteDbbeRewind(DbbeTable *pTable){
pTable->needRewind = 1;
return SQLITE_OK;
}
/*
** Read the next key from the table. Return 1 on success. Return
** 0 if there are no more keys.
*/
int sqliteDbbeNextKey(DbbeTable *pTable){
datum nextkey;
int rc;
if( pTable==0 || pTable->pFile==0 || pTable->pFile->dbf==0 ){
pTable->readPending = 0;
return 0;
}
if( pTable->needRewind ){
nextkey = gdbm_firstkey(pTable->pFile->dbf);
pTable->needRewind = 0;
}else{
nextkey = gdbm_nextkey(pTable->pFile->dbf, pTable->key);
}
datumClear(&pTable->key);
datumClear(&pTable->data);
pTable->key = nextkey;
if( pTable->key.dptr ){
pTable->readPending = 1;
rc = 1;
}else{
pTable->needRewind = 1;
pTable->readPending = 0;
rc = 0;
}
return rc;
}
/*
** Get a new integer key.
*/
int sqliteDbbeNew(DbbeTable *pTable){
int iKey;
datum key;
int go = 1;
int i;
struct rc4 *pRc4;
if( pTable->pFile==0 || pTable->pFile->dbf==0 ) return 1;
pRc4 = &pTable->pBe->rc4;
while( go ){
iKey = 0;
for(i=0; i<4; i++){
iKey = (iKey<<8) + rc4byte(pRc4);
}
key.dptr = (char*)&iKey;
key.dsize = 4;
go = gdbm_exists(pTable->pFile->dbf, key);
}
return iKey;
}
/*
** Write an entry into the table. Overwrite any prior entry with the
** same key.
*/
int sqliteDbbePut(DbbeTable *pTable, int nKey,char *pKey,int nData,char *pData){
datum data, key;
int rc;
if( pTable->pFile==0 || pTable->pFile->dbf==0 ) return SQLITE_ERROR;
data.dsize = nData;
data.dptr = pData;
key.dsize = nKey;
key.dptr = pKey;
rc = gdbm_store(pTable->pFile->dbf, key, data, GDBM_REPLACE);
if( rc ) rc = SQLITE_ERROR;
datumClear(&pTable->key);
datumClear(&pTable->data);
return rc;
}
/*
** Remove an entry from a table, if the entry exists.
*/
int sqliteDbbeDelete(DbbeTable *pTable, int nKey, char *pKey){
datum key;
int rc;
datumClear(&pTable->key);
datumClear(&pTable->data);
if( pTable->pFile==0 || pTable->pFile->dbf==0 ) return SQLITE_ERROR;
key.dsize = nKey;
key.dptr = pKey;
rc = gdbm_delete(pTable->pFile->dbf, key);
if( rc ) rc = SQLITE_ERROR;
return rc;
}
/*
** Open a temporary file.
*/
int sqliteDbbeOpenTempFile(Dbbe *pBe, FILE **ppFile){
char *zFile;
char zBuf[50];
int i, j;
int limit;
int rc = SQLITE_OK;
for(i=0; i<pBe->nTemp; i++){
if( pBe->apTemp[i]==0 ) break;
}
if( i>=pBe->nTemp ){
pBe->nTemp++;
pBe->apTemp = sqliteRealloc(pBe->apTemp, pBe->nTemp*sizeof(FILE*) );
pBe->azTemp = sqliteRealloc(pBe->azTemp, pBe->nTemp*sizeof(char*) );
}
if( pBe->apTemp==0 ){
*ppFile = 0;
return SQLITE_NOMEM;
}
limit = 4;
zFile = 0;
do{
randomName(&pBe->rc4, zBuf, "/_temp_file_");
sqliteFree(zFile);
zFile = 0;
sqliteSetString(&zFile, pBe->zDir, zBuf, 0);
}while( access(zFile,0)==0 && limit-- >= 0 );
*ppFile = pBe->apTemp[i] = fopen(zFile, "w+");
if( pBe->apTemp[i]==0 ){
rc = SQLITE_ERROR;
sqliteFree(zFile);
pBe->azTemp[i] = 0;
}else{
pBe->azTemp[i] = zFile;
}
return rc;
}
/*
** Close a temporary file opened using sqliteDbbeOpenTempFile()
*/
void sqliteDbbeCloseTempFile(Dbbe *pBe, FILE *f){
int i;
for(i=0; i<pBe->nTemp; i++){
if( pBe->apTemp[i]==f ){
unlink(pBe->azTemp[i]);
sqliteFree(pBe->azTemp[i]);
pBe->apTemp[i] = 0;
pBe->azTemp[i] = 0;
break;
}
}
fclose(f);
}