// database.cpp
/**
* Copyright (C) 2008 10gen Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
#include "pch.h"
#include "pdfile.h"
#include "database.h"
#include "instance.h"
#include "clientcursor.h"
namespace mongo {
bool Database::_openAllFiles = false;
Database::~Database() {
magic = 0;
size_t n = files.size();
for ( size_t i = 0; i < n; i++ )
delete files[i];
if( ccByLoc.size() ) {
log() << "\n\n\nWARNING: ccByLoc not empty on database close! " << ccByLoc.size() << ' ' << name << endl;
}
}
Database::Database(const char *nm, bool& newDb, const string& _path )
: name(nm), path(_path), namespaceIndex( path, name ),
profileName(name + ".system.profile") {
try {
{
// check db name is valid
size_t L = strlen(nm);
uassert( 10028 , "db name is empty", L > 0 );
uassert( 10029 , "bad db name [1]", *nm != '.' );
uassert( 10030 , "bad db name [2]", nm[L-1] != '.' );
uassert( 10031 , "bad char(s) in db name", strchr(nm, ' ') == 0 );
uassert( 10032 , "db name too long", L < 64 );
}
newDb = namespaceIndex.exists();
profile = cmdLine.defaultProfile;
checkDuplicateUncasedNames();
// If already exists, open. Otherwise behave as if empty until
// there's a write, then open.
if ( ! newDb || cmdLine.defaultProfile ) {
namespaceIndex.init();
if( _openAllFiles )
openAllFiles();
}
magic = 781231;
} catch(...) {
// since destructor won't be called:
for ( size_t i = 0; i < files.size(); i++ )
delete files[i];
throw;
}
}
void Database::checkDuplicateUncasedNames() const {
string duplicate = duplicateUncasedName( name, path );
if ( !duplicate.empty() ) {
stringstream ss;
ss << "db already exists with different case other: [" << duplicate << "] me [" << name << "]";
uasserted( DatabaseDifferCaseCode , ss.str() );
}
}
string Database::duplicateUncasedName( const string &name, const string &path, set< string > *duplicates ) {
if ( duplicates ) {
duplicates->clear();
}
vector others;
getDatabaseNames( others , path );
set allShortNames;
dbHolder.getAllShortNames( allShortNames );
others.insert( others.end(), allShortNames.begin(), allShortNames.end() );
for ( unsigned i=0; iinsert( others[i] );
} else {
return others[i];
}
}
if ( duplicates ) {
return duplicates->empty() ? "" : *duplicates->begin();
}
return "";
}
boost::filesystem::path Database::fileName( int n ) const {
stringstream ss;
ss << name << '.' << n;
boost::filesystem::path fullName;
fullName = boost::filesystem::path(path);
if ( directoryperdb )
fullName /= name;
fullName /= ss.str();
return fullName;
}
void Database::openAllFiles() {
int n = 0;
while( exists(n) ) {
getFile(n);
n++;
}
// If last file is empty, consider it preallocated and make sure it's not mapped
// until a write is requested
if ( n > 1 && getFile( n - 1 )->getHeader()->isEmpty() ) {
delete files[ n - 1 ];
files.pop_back();
}
}
MongoDataFile* Database::getFile( int n, int sizeNeeded , bool preallocateOnly) {
assert(this);
namespaceIndex.init();
if ( n < 0 || n >= DiskLoc::MaxFiles ) {
out() << "getFile(): n=" << n << endl;
massert( 10295 , "getFile(): bad file number value (corrupt db?): run repair", false);
}
DEV {
if ( n > 100 )
out() << "getFile(): n=" << n << "?" << endl;
}
MongoDataFile* p = 0;
if ( !preallocateOnly ) {
while ( n >= (int) files.size() )
files.push_back(0);
p = files[n];
}
if ( p == 0 ) {
boost::filesystem::path fullName = fileName( n );
string fullNameString = fullName.string();
p = new MongoDataFile(n);
int minSize = 0;
if ( n != 0 && files[ n - 1 ] )
minSize = files[ n - 1 ]->getHeader()->fileLength;
if ( sizeNeeded + DataFileHeader::HeaderSize > minSize )
minSize = sizeNeeded + DataFileHeader::HeaderSize;
try {
p->open( fullNameString.c_str(), minSize, preallocateOnly );
}
catch ( AssertionException& ) {
delete p;
throw;
}
if ( preallocateOnly )
delete p;
else
files[n] = p;
}
return preallocateOnly ? 0 : p;
}
MongoDataFile* Database::addAFile( int sizeNeeded, bool preallocateNextFile ) {
int n = (int) files.size();
MongoDataFile *ret = getFile( n, sizeNeeded );
if ( preallocateNextFile )
preallocateAFile();
return ret;
}
bool fileIndexExceedsQuota( const char *ns, int fileIndex, bool enforceQuota ) {
return
cmdLine.quota &&
enforceQuota &&
fileIndex >= cmdLine.quotaFiles &&
// we don't enforce the quota on "special" namespaces as that could lead to problems -- e.g.
// rejecting an index insert after inserting the main record.
!NamespaceString::special( ns ) &&
NamespaceString( ns ).db != "local";
}
MongoDataFile* Database::suitableFile( const char *ns, int sizeNeeded, bool preallocate, bool enforceQuota ) {
// check existing files
for ( int i=numFiles()-1; i>=0; i-- ) {
MongoDataFile* f = getFile( i );
if ( f->getHeader()->unusedLength >= sizeNeeded ) {
if ( fileIndexExceedsQuota( ns, i-1, enforceQuota ) ) // NOTE i-1 is the value used historically for this check.
;
else
return f;
}
}
if ( fileIndexExceedsQuota( ns, numFiles(), enforceQuota ) )
uasserted(12501, "quota exceeded");
// allocate files until we either get one big enough or hit maxSize
for ( int i = 0; i < 8; i++ ) {
MongoDataFile* f = addAFile( sizeNeeded, preallocate );
if ( f->getHeader()->unusedLength >= sizeNeeded )
return f;
if ( f->getHeader()->fileLength >= MongoDataFile::maxSize() ) // this is as big as they get so might as well stop
return f;
}
uasserted(14810, "couldn't allocate space (suitableFile)"); // callers don't check for null return code
return 0;
}
MongoDataFile* Database::newestFile() {
int n = numFiles();
if ( n == 0 )
return 0;
return getFile(n-1);
}
Extent* Database::allocExtent( const char *ns, int size, bool capped, bool enforceQuota ) {
Extent *e = DataFileMgr::allocFromFreeList( ns, size, capped );
if( e )
return e;
return suitableFile( ns, size, !capped, enforceQuota )->createExtent( ns, size, capped );
}
bool Database::setProfilingLevel( int newLevel , string& errmsg ) {
if ( profile == newLevel )
return true;
if ( newLevel < 0 || newLevel > 2 ) {
errmsg = "profiling level has to be >=0 and <= 2";
return false;
}
if ( newLevel == 0 ) {
profile = 0;
return true;
}
assert( cc().database() == this );
if ( ! namespaceIndex.details( profileName.c_str() ) ) {
log() << "creating profile collection: " << profileName << endl;
BSONObjBuilder spec;
spec.appendBool( "capped", true );
spec.append( "size", 1024*1024 );
if ( ! userCreateNS( profileName.c_str(), spec.done(), errmsg , false /* we don't replica profile messages */ ) ) {
return false;
}
}
profile = newLevel;
return true;
}
bool Database::validDBName( const string& ns ) {
if ( ns.size() == 0 || ns.size() > 64 )
return false;
size_t good = strcspn( ns.c_str() , "/\\. \"" );
return good == ns.size();
}
void Database::flushFiles( bool sync ) const {
dbMutex.assertAtLeastReadLocked();
for ( unsigned i=0; iflush( sync );
}
}
long long Database::fileSize() const {
long long size=0;
for (int n=0; exists(n); n++)
size += boost::filesystem::file_size( fileName(n) );
return size;
}
Database* DatabaseHolder::getOrCreate( const string& ns , const string& path , bool& justCreated ) {
dbMutex.assertWriteLocked();
DBs& m = _paths[path];
string dbname = _todb( ns );
Database* & db = m[dbname];
if ( db ) {
justCreated = false;
return db;
}
log(1) << "Accessing: " << dbname << " for the first time" << endl;
try {
db = new Database( dbname.c_str() , justCreated , path );
}
catch ( ... ) {
m.erase( dbname );
throw;
}
_size++;
return db;
}
} // namespace mongo