0
0
mirror of https://github.com/mongodb/mongo.git synced 2024-12-01 01:21:03 +01:00
mongodb/shell/ShellUtils.cpp

577 lines
17 KiB
C++
Raw Normal View History

2009-01-27 04:19:15 +01:00
// ShellUtils.cpp
#include "ShellUtils.h"
#include "../db/jsobj.h"
2009-03-05 22:06:11 +01:00
#include <boost/smart_ptr.hpp>
2009-01-27 04:19:15 +01:00
#include <boost/thread/thread.hpp>
#include <boost/thread/xtime.hpp>
2009-01-28 15:13:55 +01:00
#include <boost/filesystem/operations.hpp>
2009-01-27 04:19:15 +01:00
#include <iostream>
2009-01-29 18:21:58 +01:00
#include <map>
#include <sstream>
2009-03-06 16:22:32 +01:00
#include <vector>
2009-04-06 23:34:29 +02:00
#ifndef _WIN32
#include <sys/socket.h>
#include <netinet/in.h>
2009-04-06 23:34:29 +02:00
#endif
2009-01-27 04:19:15 +01:00
using namespace std;
using namespace v8;
2009-01-29 15:05:46 +01:00
using namespace boost::filesystem;
using namespace mongo;
2009-01-27 04:19:15 +01:00
BSONObj Print(const BSONObj &args) {
2009-01-27 04:19:15 +01:00
bool first = true;
BSONObjIterator i( args );
while( i.more() ) {
BSONElement e = i.next();
if ( e.eoo() )
break;
2009-01-27 04:19:15 +01:00
if (first) {
first = false;
} else {
printf(" ");
}
string str( e.jsonString( TenGen, false ) );
printf("%s", str.c_str());
2009-01-27 04:19:15 +01:00
}
printf("\n");
return undefined_;
2009-01-27 04:19:15 +01:00
}
2009-01-29 14:57:02 +01:00
std::string toSTLString( const Handle<v8::Value> & o ){
2009-01-27 04:19:15 +01:00
v8::String::Utf8Value str(o);
const char * foo = *str;
std::string s(foo);
return s;
}
2009-01-29 14:57:02 +01:00
std::ostream& operator<<( std::ostream &s, const Handle<v8::Value> & o ){
2009-01-27 04:19:15 +01:00
v8::String::Utf8Value str(o);
s << *str;
return s;
}
std::ostream& operator<<( std::ostream &s, const v8::TryCatch * try_catch ){
2009-01-29 14:57:02 +01:00
HandleScope handle_scope;
2009-01-27 04:19:15 +01:00
v8::String::Utf8Value exception(try_catch->Exception());
2009-01-29 14:57:02 +01:00
Handle<v8::Message> message = try_catch->Message();
2009-01-27 04:19:15 +01:00
if (message.IsEmpty()) {
s << *exception << endl;
}
else {
v8::String::Utf8Value filename(message->GetScriptResourceName());
int linenum = message->GetLineNumber();
cout << *filename << ":" << linenum << " " << *exception << endl;
v8::String::Utf8Value sourceline(message->GetSourceLine());
cout << *sourceline << endl;
int start = message->GetStartColumn();
for (int i = 0; i < start; i++)
cout << " ";
int end = message->GetEndColumn();
for (int i = start; i < end; i++)
cout << "^";
cout << endl;
}
if ( try_catch->next_ )
s << try_catch->next_;
return s;
}
BSONObj Quit(const BSONObj& args) {
// If not arguments are given first element will be EOO, which
2009-01-27 04:19:15 +01:00
// converts to the integer value 0.
2009-05-05 00:45:32 +02:00
int exit_code = int( args.firstElement().number() );
::exit(exit_code);
return undefined_;
2009-01-27 04:19:15 +01:00
}
BSONObj Version(const BSONObj& args) {
return BSON( "" << v8::V8::GetVersion() );
2009-01-27 04:19:15 +01:00
}
2009-01-29 14:57:02 +01:00
Handle<v8::String> ReadFile(const char* name) {
2009-01-28 15:13:55 +01:00
2009-01-29 15:05:46 +01:00
path p(name);
2009-01-28 15:13:55 +01:00
if ( is_directory( p ) ){
cerr << "can't read directory [" << name << "]" << endl;
return v8::String::New( "" );
}
2009-01-27 04:19:15 +01:00
FILE* file = fopen(name, "rb");
2009-01-29 14:57:02 +01:00
if (file == NULL) return Handle<v8::String>();
2009-01-27 04:19:15 +01:00
fseek(file, 0, SEEK_END);
int size = ftell(file);
rewind(file);
char* chars = new char[size + 1];
chars[size] = '\0';
for (int i = 0; i < size;) {
int read = fread(&chars[i], 1, size - i, file);
i += read;
}
fclose(file);
2009-01-29 14:57:02 +01:00
Handle<v8::String> result = v8::String::New(chars, size);
2009-01-27 04:19:15 +01:00
delete[] chars;
return result;
}
2009-01-29 14:57:02 +01:00
bool ExecuteString(Handle<v8::String> source, Handle<v8::Value> name,
2009-01-27 04:19:15 +01:00
bool print_result, bool report_exceptions ){
2009-01-29 14:57:02 +01:00
HandleScope handle_scope;
2009-01-27 04:19:15 +01:00
v8::TryCatch try_catch;
2009-01-29 14:57:02 +01:00
Handle<v8::Script> script = v8::Script::Compile(source, name);
2009-01-27 04:19:15 +01:00
if (script.IsEmpty()) {
if (report_exceptions)
ReportException(&try_catch);
return false;
}
2009-01-29 14:57:02 +01:00
Handle<v8::Value> result = script->Run();
2009-01-27 04:19:15 +01:00
if ( result.IsEmpty() ){
if (report_exceptions)
ReportException(&try_catch);
return false;
}
if ( print_result ){
Local<Context> current = Context::GetCurrent();
Local<v8::Object> global = current->Global();
2009-01-27 04:19:15 +01:00
Local<Value> shellPrint = global->Get( String::New( "shellPrint" ) );
if ( shellPrint->IsFunction() ){
v8::Function * f = (v8::Function*)(*shellPrint);
2009-01-29 14:57:02 +01:00
Handle<v8::Value> argv[1];
2009-01-27 04:19:15 +01:00
argv[0] = result;
f->Call( global , 1 , argv );
}
else if ( ! result->IsUndefined() ){
cout << result << endl;
}
}
return true;
}
void ReportException(v8::TryCatch* try_catch) {
cout << try_catch << endl;
}
2009-03-05 22:06:11 +01:00
extern v8::Handle< v8::Context > baseContext_;
class JSThreadConfig {
2009-03-05 06:07:09 +01:00
public:
2009-03-06 00:19:43 +01:00
JSThreadConfig( const Arguments &args ) : started_(), done_() {
2009-03-06 00:53:04 +01:00
jsassert( args.Length() > 0, "need at least one argument" );
2009-03-05 22:06:11 +01:00
jsassert( args[ 0 ]->IsFunction(), "first argument must be a function" );
Local< v8::Function > f = v8::Function::Cast( *args[ 0 ] );
f_ = Persistent< v8::Function >::New( f );
2009-03-05 22:06:11 +01:00
for( int i = 1; i < args.Length(); ++i )
args_.push_back( Persistent< Value >::New( args[ i ] ) );
}
~JSThreadConfig() {
f_.Dispose();
for( vector< Persistent< Value > >::iterator i = args_.begin(); i != args_.end(); ++i )
i->Dispose();
returnData_.Dispose();
}
void start() {
2009-03-06 00:19:43 +01:00
jsassert( !started_, "Thread already started" );
2009-03-05 22:06:11 +01:00
JSThread jt( *this );
thread_.reset( new boost::thread( jt ) );
2009-03-06 00:19:43 +01:00
started_ = true;
2009-03-05 22:06:11 +01:00
}
void join() {
2009-03-06 00:19:43 +01:00
jsassert( started_ && !done_, "Thread not running" );
2009-03-05 22:06:11 +01:00
Unlocker u;
thread_->join();
2009-03-06 00:19:43 +01:00
done_ = true;
2009-03-05 22:06:11 +01:00
}
Local< Value > returnData() {
2009-03-06 00:19:43 +01:00
if ( !done_ )
join();
2009-03-05 22:06:11 +01:00
return Local< Value >::New( returnData_ );
2009-03-05 06:07:09 +01:00
}
private:
2009-03-05 22:06:11 +01:00
class JSThread {
public:
JSThread( JSThreadConfig &config ) : config_( config ) {}
void operator()() {
Locker l;
2009-03-06 19:44:38 +01:00
// Context scope and handle scope held in thread specific storage,
// so need to configure for each thread.
2009-03-05 22:06:11 +01:00
Context::Scope context_scope( baseContext_ );
HandleScope handle_scope;
2009-03-06 00:19:43 +01:00
boost::scoped_array< Persistent< Value > > argv( new Persistent< Value >[ config_.args_.size() ] );
2009-03-05 22:06:11 +01:00
for( unsigned int i = 0; i < config_.args_.size(); ++i )
2009-03-06 00:19:43 +01:00
argv[ i ] = Persistent< Value >::New( config_.args_[ i ] );
2009-03-05 22:06:11 +01:00
Local< Value > ret = config_.f_->Call( Context::GetCurrent()->Global(), config_.args_.size(), argv.get() );
2009-03-06 00:19:43 +01:00
for( unsigned int i = 0; i < config_.args_.size(); ++i )
argv[ i ].Dispose();
2009-03-05 22:06:11 +01:00
config_.returnData_ = Persistent< Value >::New( ret );
}
private:
JSThreadConfig &config_;
};
2009-03-06 00:19:43 +01:00
bool started_;
bool done_;
Persistent< v8::Function > f_;
2009-03-05 22:06:11 +01:00
vector< Persistent< Value > > args_;
auto_ptr< boost::thread > thread_;
Persistent< Value > returnData_;
2009-03-05 06:07:09 +01:00
};
2009-03-05 22:06:11 +01:00
Handle< Value > ThreadInit( const Arguments &args ) {
Handle<v8::Object> it = args.This();
2009-03-06 00:53:04 +01:00
// NOTE I believe the passed JSThreadConfig will never be freed. If this
// policy is changed, JSThread may no longer be able to store JSThreadConfig
// by reference.
2009-03-05 22:06:11 +01:00
it->Set( String::New( "_JSThreadConfig" ), External::New( new JSThreadConfig( args ) ) );
return v8::Undefined();
2009-03-05 22:06:11 +01:00
}
JSThreadConfig *thisConfig( const Arguments &args ) {
Local< External > c = External::Cast( *(args.This()->Get( String::New( "_JSThreadConfig" ) ) ) );
JSThreadConfig *config = (JSThreadConfig *)( c->Value() );
return config;
}
Handle< Value > ThreadStart( const Arguments &args ) {
thisConfig( args )->start();
return v8::Undefined();
2009-03-05 22:06:11 +01:00
}
Handle< Value > ThreadJoin( const Arguments &args ) {
thisConfig( args )->join();
return v8::Undefined();
2009-03-05 22:06:11 +01:00
}
Handle< Value > ThreadReturnData( const Arguments &args ) {
return thisConfig( args )->returnData();
2009-03-05 06:07:09 +01:00
}
const char *argv0 = 0;
void RecordMyLocation( const char *_argv0 ) { argv0 = _argv0; }
#if !defined(_WIN32)
#include <signal.h>
#include <fcntl.h>
#include <sys/stat.h>
2009-01-29 18:21:58 +01:00
#include <sys/wait.h>
map< int, pair< pid_t, int > > dbs;
char *copyString( const char *original ) {
char *ret = reinterpret_cast< char * >( malloc( strlen( original ) + 1 ) );
strcpy( ret, original );
return ret;
}
boost::mutex &mongoProgramOutputMutex( *( new boost::mutex ) );
stringstream mongoProgramOutput_;
void writeMongoProgramOutputLine( int port, const char *line ) {
boost::mutex::scoped_lock lk( mongoProgramOutputMutex );
stringstream buf;
buf << "m" << port << "| " << line;
cout << buf.str() << endl;
mongoProgramOutput_ << buf.str() << endl;
}
BSONObj RawMongoProgramOutput( const BSONObj &args ) {
boost::mutex::scoped_lock lk( mongoProgramOutputMutex );
return BSON( "" << mongoProgramOutput_.str() );
}
2009-03-05 06:07:09 +01:00
class MongoProgramRunner {
char **argv_;
int port_;
2009-03-26 20:12:47 +01:00
int pipe_;
2009-03-05 06:07:09 +01:00
public:
MongoProgramRunner( const BSONObj &args ) {
assert( args.nFields() > 0 );
string program( args.firstElement().valuestrsafe() );
2009-03-05 06:07:09 +01:00
assert( !program.empty() );
2009-03-05 06:07:09 +01:00
boost::filesystem::path programPath = ( boost::filesystem::path( argv0 ) ).branch_path() / program;
assert( boost::filesystem::exists( programPath ) );
port_ = -1;
argv_ = new char *[ args.nFields() + 1 ];
2009-03-05 06:07:09 +01:00
{
string s = programPath.native_file_string();
if ( s == program )
s = "./" + s;
argv_[ 0 ] = copyString( s.c_str() );
}
BSONObjIterator j( args );
j.next();
for( int i = 1; i < args.nFields(); ++i ) {
BSONElement e = j.next();
string str;
if ( e.isNumber() ) {
stringstream ss;
ss << e.number();
str = ss.str();
} else {
assert( e.type() == mongo::String );
str = e.valuestr();
}
char *s = copyString( str.c_str() );
2009-03-05 06:07:09 +01:00
if ( string( "--port" ) == s )
port_ = -2;
else if ( port_ == -2 )
port_ = strtol( s, 0, 10 );
argv_[ i ] = s;
}
argv_[ args.nFields() ] = 0;
2009-03-05 06:07:09 +01:00
assert( port_ > 0 );
2009-04-13 04:19:04 +02:00
if ( dbs.count( port_ ) != 0 ){
cerr << "count for port: " << port_ << " is not 0 is: " << dbs.count( port_ ) << endl;
assert( dbs.count( port_ ) == 0 );
}
2009-03-05 06:07:09 +01:00
}
2009-03-26 20:12:47 +01:00
void start() {
2009-03-05 06:07:09 +01:00
int pipeEnds[ 2 ];
assert( pipe( pipeEnds ) != -1 );
fflush( 0 );
pid_t pid = fork();
assert( pid != -1 );
if ( pid == 0 ) {
assert( dup2( pipeEnds[ 1 ], STDOUT_FILENO ) != -1 );
assert( dup2( pipeEnds[ 1 ], STDERR_FILENO ) != -1 );
execvp( argv_[ 0 ], argv_ );
2009-03-06 19:47:02 +01:00
assert( "Unable to start program" == 0 );
}
2009-03-05 06:07:09 +01:00
cout << "shell: started mongo program";
2009-03-05 06:07:09 +01:00
int i = 0;
while( argv_[ i ] )
cout << " " << argv_[ i++ ];
cout << endl;
i = 0;
2009-03-05 06:07:09 +01:00
while( argv_[ i ] )
free( argv_[ i++ ] );
free( argv_ );
dbs.insert( make_pair( port_, make_pair( pid, pipeEnds[ 1 ] ) ) );
2009-03-26 20:12:47 +01:00
pipe_ = pipeEnds[ 0 ];
}
// Continue reading output
void operator()() {
2009-03-05 06:07:09 +01:00
// This assumes there aren't any 0's in the mongo program output.
// Hope that's ok.
char buf[ 1024 ];
char temp[ 1024 ];
char *start = buf;
while( 1 ) {
int lenToRead = 1023 - ( start - buf );
2009-03-26 20:12:47 +01:00
int ret = read( pipe_, (void *)start, lenToRead );
2009-03-05 06:07:09 +01:00
assert( ret != -1 );
start[ ret ] = '\0';
if ( strlen( start ) != unsigned( ret ) )
writeMongoProgramOutputLine( port_, "WARNING: mongod wrote null bytes to output" );
2009-03-05 06:07:09 +01:00
char *last = buf;
for( char *i = strchr( buf, '\n' ); i; last = i + 1, i = strchr( last, '\n' ) ) {
*i = '\0';
writeMongoProgramOutputLine( port_, last );
}
if ( ret == 0 ) {
if ( *last )
writeMongoProgramOutputLine( port_, last );
close( pipe_ );
2009-03-05 06:07:09 +01:00
break;
}
if ( last != buf ) {
strcpy( temp, last );
strcpy( buf, temp );
} else {
assert( strlen( buf ) < 1023 );
}
start = buf + strlen( buf );
}
}
2009-03-05 06:07:09 +01:00
};
BSONObj StartMongoProgram( const BSONObj &a ) {
2009-03-05 06:07:09 +01:00
MongoProgramRunner r( a );
2009-03-26 20:12:47 +01:00
r.start();
2009-03-05 06:07:09 +01:00
boost::thread t( r );
return undefined_;
}
BSONObj ResetDbpath( const BSONObj &a ) {
assert( a.nFields() == 1 );
string path = a.firstElement().valuestrsafe();
assert( !path.empty() );
if ( boost::filesystem::exists( path ) )
boost::filesystem::remove_all( path );
boost::filesystem::create_directory( path );
return undefined_;
}
2009-03-12 17:21:07 +01:00
void killDb( int port, int signal ) {
2009-01-30 21:15:48 +01:00
if( dbs.count( port ) != 1 ) {
cout << "No db started on port: " << port << endl;
return;
}
pid_t pid = dbs[ port ].first;
assert( 0 == kill( pid, signal ) );
2009-01-30 21:15:48 +01:00
int i = 0;
for( ; i < 65; ++i ) {
if ( i == 5 ) {
char now[64];
time_t_to_String(time(0), now);
now[ 20 ] = 0;
cout << now << " process on port " << port << ", with pid " << pid << " not terminated, sending sigkill" << endl;
assert( 0 == kill( pid, SIGKILL ) );
}
2009-01-30 21:15:48 +01:00
int temp;
2009-04-03 17:20:57 +02:00
int ret = waitpid( pid, &temp, WNOHANG );
if ( ret == pid )
2009-01-30 21:15:48 +01:00
break;
sleepms( 1000 );
2009-01-30 21:15:48 +01:00
}
if ( i == 65 ) {
char now[64];
time_t_to_String(time(0), now);
now[ 20 ] = 0;
cout << now << " failed to terminate process on port " << port << ", with pid " << pid << endl;
assert( "Failed to terminate process" == 0 );
}
close( dbs[ port ].second );
dbs.erase( port );
if ( i > 4 || signal == SIGKILL ) {
sleepms( 4000 ); // allow operating system to reclaim resources
}
}
BSONObj StopMongoProgram( const BSONObj &a ) {
assert( a.nFields() == 1 || a.nFields() == 2 );
assert( a.firstElement().isNumber() );
2009-05-05 00:45:32 +02:00
int port = int( a.firstElement().number() );
2009-03-12 17:21:07 +01:00
int signal = SIGTERM;
if ( a.nFields() == 2 ) {
BSONObjIterator i( a );
i.next();
BSONElement e = i.next();
assert( e.isNumber() );
2009-05-05 00:45:32 +02:00
signal = int( e.number() );
2009-03-12 17:21:07 +01:00
}
killDb( port, signal );
cout << "shell: stopped mongo program on port " << port << endl;
return undefined_;
}
void KillMongoProgramInstances() {
2009-03-26 20:12:47 +01:00
vector< int > ports;
for( map< int, pair< pid_t, int > >::iterator i = dbs.begin(); i != dbs.end(); ++i )
2009-03-26 20:12:47 +01:00
ports.push_back( i->first );
for( vector< int >::iterator i = ports.begin(); i != ports.end(); ++i )
killDb( *i, SIGTERM );
}
MongoProgramScope::~MongoProgramScope() {
try {
KillMongoProgramInstances();
} catch ( ... ) {
assert( false );
}
}
#else
MongoProgramScope::~MongoProgramScope() {}
void KillMongoProgramInstances() {}
#endif
2009-03-05 22:06:11 +01:00
Handle< Value > ThreadInject( const Arguments &args ) {
jsassert( args.Length() == 1 , "threadInject takes exactly 1 argument" );
jsassert( args[0]->IsObject() , "threadInject needs to be passed a prototype" );
Local<v8::Object> o = args[0]->ToObject();
o->Set( String::New( "init" ) , FunctionTemplate::New( ThreadInit )->GetFunction() );
o->Set( String::New( "start" ) , FunctionTemplate::New( ThreadStart )->GetFunction() );
o->Set( String::New( "join" ) , FunctionTemplate::New( ThreadJoin )->GetFunction() );
o->Set( String::New( "returnData" ) , FunctionTemplate::New( ThreadReturnData )->GetFunction() );
return v8::Undefined();
}
2009-04-06 23:34:29 +02:00
#ifndef _WIN32
BSONObj AllocatePorts( const BSONObj &args ) {
jsassert( args.nFields() == 1 , "allocatePorts takes exactly 1 argument" );
jsassert( args.firstElement().isNumber() , "allocatePorts needs to be passed an integer" );
2009-05-05 00:45:32 +02:00
int n = int( args.firstElement().number() );
vector< int > ports;
for( int i = 0; i < n; ++i ) {
int s = socket( AF_INET, SOCK_STREAM, 0 );
assert( s );
sockaddr_in address;
memset(address.sin_zero, 0, sizeof(address.sin_zero));
address.sin_family = AF_INET;
address.sin_port = 0;
address.sin_addr.s_addr = 0;
assert( 0 == ::bind( s, (sockaddr*)&address, sizeof( address ) ) );
sockaddr_in newAddress;
socklen_t len = sizeof( newAddress );
assert( 0 == getsockname( s, (sockaddr*)&newAddress, &len ) );
ports.push_back( ntohs( newAddress.sin_port ) );
assert( 0 == close( s ) );
}
sort( ports.begin(), ports.end() );
BSONObjBuilder b;
b.append( "", ports );
return b.obj();
}
2009-04-06 23:34:29 +02:00
#endif
void installShellUtils( mongo::Scope &scope, v8::Handle<v8::ObjectTemplate>& global ) {
scope.injectNative( "sleep", JSSleep );
scope.injectNative( "print", Print );
scope.injectNative( "quit", Quit );
scope.injectNative( "version", Version );
global->Set( String::New( "threadInject" ), FunctionTemplate::New( ThreadInject ) );
#if !defined(_WIN32)
scope.injectNative( "allocatePorts", AllocatePorts );
scope.injectNative( "_startMongoProgram", StartMongoProgram );
scope.injectNative( "stopMongod", StopMongoProgram );
scope.injectNative( "stopMongoProgram", StopMongoProgram );
scope.injectNative( "resetDbpath", ResetDbpath );
scope.injectNative( "rawMongoProgramOutput", RawMongoProgramOutput );
#endif
2009-01-27 04:19:15 +01:00
}