// utils.cpp
/*
 *    Copyright 2010 10gen Inc.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

#include "pch.h"

#include <boost/thread/xtime.hpp>

#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <assert.h>
#include <iostream>
#include <map>
#include <sstream>
#include <vector>
#include <fcntl.h>

#ifdef _WIN32
# include <io.h>
# define SIGKILL 9
#else
# include <sys/socket.h>
# include <netinet/in.h>
# include <signal.h>
# include <sys/stat.h>
# include <sys/wait.h>
#endif

#include "utils.h"
#include "../client/dbclient.h"
#include "../util/md5.hpp"
#include "../util/processinfo.h"
#include "../util/text.h"
#include "../util/heapcheck.h"
#include "../util/time_support.h"
#include "../util/file.h"

namespace mongo {

    DBClientWithCommands *latestConn = 0;
    extern bool dbexitCalled;

#ifdef _WIN32
    inline int close(int fd) { return _close(fd); }
    inline int read(int fd, void* buf, size_t size) { return _read(fd, buf, size); }
    inline int pipe(int fds[2]) { return _pipe(fds, 4096, _O_TEXT | _O_NOINHERIT); }
#endif

    namespace JSFiles {
        extern const JSFile servers;
    }

    // these functions have not been audited for thread safety - currently they are called with an exclusive js mutex
    namespace shellUtils {

        Scope* theScope = 0;

        std::string _dbConnect;
        std::string _dbAuth;

        const char *argv0 = 0;
        void RecordMyLocation( const char *_argv0 ) { argv0 = _argv0; }

        // helpers

        BSONObj makeUndefined() {
            BSONObjBuilder b;
            b.appendUndefined( "" );
            return b.obj();
        }
        const BSONObj undefined_ = makeUndefined();

        BSONObj encapsulate( const BSONObj &obj ) {
            return BSON( "" << obj );
        }

        // real methods

        void goingAwaySoon();
        BSONObj Quit(const BSONObj& args, void* data) {
            // If not arguments are given first element will be EOO, which
            // converts to the integer value 0.
            goingAwaySoon();
            int exit_code = int( args.firstElement().number() );
            ::exit(exit_code);
            return undefined_;
        }

        BSONObj JSGetMemInfo( const BSONObj& args, void* data ) {
            ProcessInfo pi;
            uassert( 10258 ,  "processinfo not supported" , pi.supported() );

            BSONObjBuilder e;
            e.append( "virtual" , pi.getVirtualMemorySize() );
            e.append( "resident" , pi.getResidentSize() );

            BSONObjBuilder b;
            b.append( "ret" , e.obj() );

            return b.obj();
        }


#ifndef MONGO_SAFE_SHELL

        BSONObj listFiles(const BSONObj& _args, void* data) {
            static BSONObj cd = BSON( "0" << "." );
            BSONObj args = _args.isEmpty() ? cd : _args;

            uassert( 10257 ,  "need to specify 1 argument to listFiles" , args.nFields() == 1 );

            BSONArrayBuilder lst;

            string rootname = args.firstElement().valuestrsafe();
            path root( rootname );
            stringstream ss;
            ss << "listFiles: no such directory: " << rootname;
            string msg = ss.str();
            uassert( 12581, msg.c_str(), boost::filesystem::exists( root ) );

            directory_iterator end;
            directory_iterator i( root);

            while ( i != end ) {
                path p = *i;
                BSONObjBuilder b;
                b << "name" << p.string();
                b.appendBool( "isDirectory", is_directory( p ) );
                if ( ! is_directory( p ) ) {
                    try {
                        b.append( "size" , (double)file_size( p ) );
                    }
                    catch ( ... ) {
                        i++;
                        continue;
                    }
                }

                lst.append( b.obj() );
                i++;
            }
            
            BSONObjBuilder ret;
            ret.appendArray( "", lst.done() );
            return ret.obj();
        }

        BSONObj ls(const BSONObj& args, void* data) {
            BSONObj o = listFiles(args, data);
            if( !o.isEmpty() ) {
                for( BSONObj::iterator i = o.firstElement().Obj().begin(); i.more(); ) {
                    BSONObj f = i.next().Obj();
                    cout << f["name"].String();
                    if( f["isDirectory"].trueValue() ) cout << '/';
                    cout << '\n';
                }
                cout.flush();
            }
            return BSONObj();
        }

        BSONObj cd(const BSONObj& args, void* data) {
#if defined(_WIN32)
            std::wstring dir = toWideString( args.firstElement().String().c_str() );
            if( SetCurrentDirectory(dir.c_str()) )
                return BSONObj();
#else
            string dir = args.firstElement().String();
            /*            if( chdir(dir.c_str) ) == 0 )
                            return BSONObj();
                            */
            if( 1 ) return BSON(""<<"implementation not done for posix");
#endif
            return BSON( "" << "change directory failed" );
        }

        BSONObj pwd(const BSONObj&, void* data) {
            boost::filesystem::path p = boost::filesystem::current_path();
            return BSON( "" << p.string() );
        }

        BSONObj hostname(const BSONObj&, void* data) {
            return BSON( "" << getHostName() );
        }

        static BSONElement oneArg(const BSONObj& args) {
            uassert( 12597 , "need to specify 1 argument" , args.nFields() == 1 );
            return args.firstElement();
        }

        const int CANT_OPEN_FILE = 13300;

        BSONObj cat(const BSONObj& args, void* data) {
            BSONElement e = oneArg(args);
            stringstream ss;
            ifstream f(e.valuestrsafe());
            uassert(CANT_OPEN_FILE, "couldn't open file", f.is_open() );

            streamsize sz = 0;
            while( 1 ) {
                char ch = 0;
                // slow...maybe change one day
                f.get(ch);
                if( ch == 0 ) break;
                ss << ch;
                sz += 1;
                uassert(13301, "cat() : file to big to load as a variable", sz < 1024 * 1024 * 16);
            }
            return BSON( "" << ss.str() );
        }

        BSONObj md5sumFile(const BSONObj& args, void* data) {
            BSONElement e = oneArg(args);
            stringstream ss;
            FILE* f = fopen(e.valuestrsafe(), "rb");
            uassert(CANT_OPEN_FILE, "couldn't open file", f );

            md5digest d;
            md5_state_t st;
            md5_init(&st);

            enum {BUFLEN = 4*1024};
            char buffer[BUFLEN];
            int bytes_read;
            while( (bytes_read = fread(buffer, 1, BUFLEN, f)) ) {
                md5_append( &st , (const md5_byte_t*)(buffer) , bytes_read );
            }

            md5_finish(&st, d);
            return BSON( "" << digestToString( d ) );
        }

        BSONObj mkdir(const BSONObj& args, void* data) {
            boost::filesystem::create_directories(args.firstElement().String());
            return BSON( "" << true );
        }

        BSONObj removeFile(const BSONObj& args, void* data) {
            BSONElement e = oneArg(args);
            bool found = false;

            path root( e.valuestrsafe() );
            if ( boost::filesystem::exists( root ) ) {
                found = true;
                boost::filesystem::remove_all( root );
            }

            BSONObjBuilder b;
            b.appendBool( "removed" , found );
            return b.obj();
        }

        /**
         * @param args - [ name, byte index ]
         * In this initial implementation, all bits in the specified byte are flipped.
         */
        BSONObj fuzzFile(const BSONObj& args, void* data) {
            uassert( 13619, "fuzzFile takes 2 arguments", args.nFields() == 2 );
            shared_ptr< File > f( new File() );
            f->open( args.getStringField( "0" ) );
            uassert( 13620, "couldn't open file to fuzz", !f->bad() && f->is_open() );

            char c;
            f->read( args.getIntField( "1" ), &c, 1 );
            c = ~c;
            f->write( args.getIntField( "1" ), &c, 1 );

            return undefined_;
            // f close is implicit
        }

        map< int, pair< pid_t, int > > dbs;
        map< pid_t, int > shells;
#ifdef _WIN32
        map< pid_t, HANDLE > handles;
#endif

        mongo::mutex mongoProgramOutputMutex("mongoProgramOutputMutex");
        stringstream mongoProgramOutput_;

        void goingAwaySoon() {
            mongo::mutex::scoped_lock lk( mongoProgramOutputMutex );
            mongo::dbexitCalled = true;
        }

        void writeMongoProgramOutputLine( int port, int pid, const char *line ) {
            mongo::mutex::scoped_lock lk( mongoProgramOutputMutex );
            if( mongo::dbexitCalled ) throw "program is terminating";
            stringstream buf;
            if ( port > 0 )
                buf << " m" << port << "| " << line;
            else
                buf << "sh" << pid << "| " << line;
            cout << buf.str() << endl;
            mongoProgramOutput_ << buf.str() << endl;
        }

        // only returns last 100000 characters
        BSONObj RawMongoProgramOutput( const BSONObj &args, void* data ) {
            mongo::mutex::scoped_lock lk( mongoProgramOutputMutex );
            string out = mongoProgramOutput_.str();
            size_t len = out.length();
            if ( len > 100000 )
                out = out.substr( len - 100000, 100000 );
            return BSON( "" << out );
        }

        BSONObj ClearRawMongoProgramOutput( const BSONObj &args, void* data ) {
            mongo::mutex::scoped_lock lk( mongoProgramOutputMutex );
            mongoProgramOutput_.str( "" );
            return undefined_;
        }

        class ProgramRunner {
            vector<string> argv_;
            int port_;
            int pipe_;
            pid_t pid_;
        public:
            pid_t pid() const { return pid_; }
            int port() const { return port_; }

            boost::filesystem::path find(string prog) {
                boost::filesystem::path p = prog;
#ifdef _WIN32
                p = change_extension(p, ".exe");
#endif

                if( boost::filesystem::exists(p) ) {
#ifndef _WIN32
                    p = boost::filesystem::initial_path() / p;
#endif
                    return p;
                }

                {
                    boost::filesystem::path t = boost::filesystem::current_path() / p;
                    if( boost::filesystem::exists(t)  ) return t;
                }
                try {
                    if( theScope->type("_path") == String ) {
                        string path = theScope->getString("_path");
                        if( !path.empty() ) {
                            boost::filesystem::path t = boost::filesystem::path(path) / p;
                            if( boost::filesystem::exists(t) ) return t;
                        }
                    }
                }
                catch(...) { }
                {
                    boost::filesystem::path t = boost::filesystem::initial_path() / p;
                    if( boost::filesystem::exists(t)  ) return t;
                }
                return p; // not found; might find via system path
            }

            ProgramRunner( const BSONObj &args , bool isMongoProgram=true) {
                assert( !args.isEmpty() );

                string program( args.firstElement().valuestrsafe() );
                assert( !program.empty() );
                boost::filesystem::path programPath = find(program);

                if (isMongoProgram) {
#if 0
                    if (program == "mongos") {
                        argv_.push_back("valgrind");
                        argv_.push_back("--log-file=/tmp/mongos-%p.valgrind");
                        argv_.push_back("--leak-check=yes");
                        argv_.push_back("--suppressions=valgrind.suppressions");
                        //argv_.push_back("--error-exitcode=1");
                        argv_.push_back("--");
                    }
#endif
                }

                argv_.push_back( programPath.native_file_string() );

                port_ = -1;

                BSONObjIterator j( args );
                j.next(); // skip program name (handled above)
                while(j.more()) {
                    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();
                    }
                    if ( str == "--port" )
                        port_ = -2;
                    else if ( port_ == -2 )
                        port_ = strtol( str.c_str(), 0, 10 );
                    argv_.push_back(str);
                }

                if ( program != "mongod" && program != "mongos" && program != "mongobridge" )
                    port_ = 0;
                else {
                    if ( port_ <= 0 )
                        cout << "error: a port number is expected when running mongod (etc.) from the shell" << endl;
                    assert( port_ > 0 );
                }
                if ( port_ > 0 && dbs.count( port_ ) != 0 ) {
                    cerr << "count for port: " << port_ << " is not 0 is: " << dbs.count( port_ ) << endl;
                    assert( dbs.count( port_ ) == 0 );
                }
            }

            void start() {
                int pipeEnds[ 2 ];
                assert( pipe( pipeEnds ) != -1 );

                fflush( 0 );
                launch_process(pipeEnds[1]); //sets pid_

                {
                    stringstream ss;
                    ss << "shell: started program";
                    for (unsigned i=0; i < argv_.size(); i++)
                        ss << " " << argv_[i];
                    ss << '\n';
                    cout << ss.str(); cout.flush();
                }

                if ( port_ > 0 )
                    dbs.insert( make_pair( port_, make_pair( pid_, pipeEnds[ 1 ] ) ) );
                else
                    shells.insert( make_pair( pid_, pipeEnds[ 1 ] ) );
                pipe_ = pipeEnds[ 0 ];
            }

            // Continue reading output
            void operator()() {
                try {
                    // This assumes there aren't any 0's in the mongo program output.
                    // Hope that's ok.
                    const unsigned bufSize = 128 * 1024;
                    char buf[ bufSize ];
                    char temp[ bufSize ];
                    char *start = buf;
                    while( 1 ) {
                        int lenToRead = ( bufSize - 1 ) - ( start - buf );
                        if ( lenToRead <= 0 ) {
                            cout << "error: lenToRead: " << lenToRead << endl;
                            cout << "first 300: " << string(buf,0,300) << endl;
                        }
                        assert( lenToRead > 0 );
                        int ret = read( pipe_, (void *)start, lenToRead );
                        if( mongo::dbexitCalled )
                            break;
                        assert( ret != -1 );
                        start[ ret ] = '\0';
                        if ( strlen( start ) != unsigned( ret ) )
                            writeMongoProgramOutputLine( port_, pid_, "WARNING: mongod wrote null bytes to output" );
                        char *last = buf;
                        for( char *i = strchr( buf, '\n' ); i; last = i + 1, i = strchr( last, '\n' ) ) {
                            *i = '\0';
                            writeMongoProgramOutputLine( port_, pid_, last );
                        }
                        if ( ret == 0 ) {
                            if ( *last )
                                writeMongoProgramOutputLine( port_, pid_, last );
                            close( pipe_ );
                            break;
                        }
                        if ( last != buf ) {
                            strcpy( temp, last );
                            strcpy( buf, temp );
                        }
                        else {
                            assert( strlen( buf ) < bufSize );
                        }
                        start = buf + strlen( buf );
                    }
                }
                catch(...) {
                }
            }
            void launch_process(int child_stdout) {
#ifdef _WIN32
                stringstream ss;
                for( unsigned i=0; i < argv_.size(); i++ ) {
                    if (i) ss << ' ';
                    if (argv_[i].find(' ') == string::npos)
                        ss << argv_[i];
                    else
                        ss << '"' << argv_[i] << '"';
                }

                string args = ss.str();

                boost::scoped_array<TCHAR> args_tchar (new TCHAR[args.size() + 1]);
                size_t i;
                for(i=0; i < args.size(); i++)
                    args_tchar[i] = args[i];
                args_tchar[i] = 0;

                HANDLE h = (HANDLE)_get_osfhandle(child_stdout);
                assert(h != INVALID_HANDLE_VALUE);
                assert(SetHandleInformation(h, HANDLE_FLAG_INHERIT, 1));

                STARTUPINFO si;
                ZeroMemory(&si, sizeof(si));
                si.cb = sizeof(si);
                si.hStdError = h;
                si.hStdOutput = h;
                si.dwFlags |= STARTF_USESTDHANDLES;

                PROCESS_INFORMATION pi;
                ZeroMemory(&pi, sizeof(pi));

                bool success = CreateProcess( NULL, args_tchar.get(), NULL, NULL, true, 0, NULL, NULL, &si, &pi) != 0;
                {
                    stringstream ss;
                    ss << "couldn't start process " << argv_[0];
                    uassert(14042, ss.str(), success);
                }

                CloseHandle(pi.hThread);

                pid_ = pi.dwProcessId;
                handles.insert( make_pair( pid_, pi.hProcess ) );

#else

                pid_ = fork();
                assert( pid_ != -1 );

                if ( pid_ == 0 ) {
                    // DON'T ASSERT IN THIS BLOCK - very bad things will happen

                    const char** argv = new const char* [argv_.size()+1]; // don't need to free - in child
                    for (unsigned i=0; i < argv_.size(); i++) {
                        argv[i] = argv_[i].c_str();
                    }
                    argv[argv_.size()] = 0;

                    if ( dup2( child_stdout, STDOUT_FILENO ) == -1 ||
                            dup2( child_stdout, STDERR_FILENO ) == -1 ) {
                        cout << "Unable to dup2 child output: " << errnoWithDescription() << endl;
                        ::_Exit(-1); //do not pass go, do not call atexit handlers
                    }

                    const char** env = new const char* [2]; // don't need to free - in child
                    env[0] = NULL;
#if defined(HEAP_CHECKING)
                    env[0] = "HEAPCHECK=normal";
                    env[1] = NULL;

                    // Heap-check for mongos only. 'argv[0]' must be in the path format.
                    if ( argv_[0].find("mongos") != string::npos) {
                        execvpe( argv[ 0 ], const_cast<char**>(argv) , const_cast<char**>(env) );
                    }
#endif // HEAP_CHECKING

                    execvp( argv[ 0 ], const_cast<char**>(argv) );

                    cout << "Unable to start program " << argv[0] << ' ' << errnoWithDescription() << endl;
                    ::_Exit(-1);
                }

#endif
            }
        };

        //returns true if process exited
        bool wait_for_pid(pid_t pid, bool block=true, int* exit_code=NULL) {
#ifdef _WIN32
            assert(handles.count(pid));
            HANDLE h = handles[pid];

            if (block)
                WaitForSingleObject(h, INFINITE);

            DWORD tmp;
            if(GetExitCodeProcess(h, &tmp)) {
                if ( tmp == STILL_ACTIVE ) {
                    return false;
                }
                CloseHandle(h);
                handles.erase(pid);
                if (exit_code)
                    *exit_code = tmp;
                return true;
            }
            else {
                return false;
            }
#else
            int tmp;
            bool ret = (pid == waitpid(pid, &tmp, (block ? 0 : WNOHANG)));
            if (exit_code)
                *exit_code = WEXITSTATUS(tmp);
            return ret;

#endif
        }

        BSONObj WaitProgram( const BSONObj& a, void* data ) {
            int pid = oneArg( a ).numberInt();
            BSONObj x = BSON( "" << wait_for_pid( pid ) );
            shells.erase( pid );
            return x;
        }

        BSONObj WaitMongoProgramOnPort( const BSONObj &a, void* data ) {
            int port = oneArg( a ).numberInt();
            uassert( 13621, "no known mongo program on port", dbs.count( port ) != 0 );
            log() << "waiting port: " << port << ", pid: " << dbs[ port ].first << endl;
            bool ret = wait_for_pid( dbs[ port ].first );
            if ( ret ) {
                dbs.erase( port );
            }
            return BSON( "" << ret );
        }

        BSONObj StartMongoProgram( const BSONObj &a, void* data ) {
            _nokillop = true;
            ProgramRunner r( a );
            r.start();
            boost::thread t( r );
            return BSON( string( "" ) << int( r.pid() ) );
        }

        BSONObj RunMongoProgram( const BSONObj &a, void* data ) {
            ProgramRunner r( a );
            r.start();
            boost::thread t( r );
            int exit_code;
            wait_for_pid( r.pid(), true, &exit_code );
            if ( r.port() > 0 ) {
                dbs.erase( r.port() );
            }
            else {
                shells.erase( r.pid() );
            }
            return BSON( string( "" ) << exit_code );
        }

        BSONObj RunProgram(const BSONObj &a, void* data) {
            ProgramRunner r( a, false );
            r.start();
            boost::thread t( r );
            int exit_code;
            wait_for_pid(r.pid(), true,  &exit_code);
            shells.erase( r.pid() );
            return BSON( string( "" ) << exit_code );
        }

        BSONObj ResetDbpath( const BSONObj &a, void* data ) {
            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_;
        }

        void copyDir( const path &from, const path &to ) {
            directory_iterator end;
            directory_iterator i( from );
            while( i != end ) {
                path p = *i;
                if ( p.leaf() != "mongod.lock" ) {
                    if ( is_directory( p ) ) {
                        path newDir = to / p.leaf();
                        boost::filesystem::create_directory( newDir );
                        copyDir( p, newDir );
                    }
                    else {
                        boost::filesystem::copy_file( p, to / p.leaf() );
                    }
                }
                ++i;
            }
        }

        // NOTE target dbpath will be cleared first
        BSONObj CopyDbpath( const BSONObj &a, void* data ) {
            assert( a.nFields() == 2 );
            BSONObjIterator i( a );
            string from = i.next().str();
            string to = i.next().str();
            assert( !from.empty() );
            assert( !to.empty() );
            if ( boost::filesystem::exists( to ) )
                boost::filesystem::remove_all( to );
            boost::filesystem::create_directory( to );
            copyDir( from, to );
            return undefined_;
        }

        inline void kill_wrapper(pid_t pid, int sig, int port) {
#ifdef _WIN32
            if (sig == SIGKILL || port == 0) {
                assert( handles.count(pid) );
                TerminateProcess(handles[pid], 1); // returns failure for "zombie" processes.
            }
            else {
                DBClientConnection conn;
                conn.connect("127.0.0.1:" + BSONObjBuilder::numStr(port));
                try {
                    conn.simpleCommand("admin", NULL, "shutdown");
                }
                catch (...) {
                    //Do nothing. This command never returns data to the client and the driver doesn't like that.
                }
            }
#else
            int x = kill( pid, sig );
            if ( x ) {
                if ( errno == ESRCH ) {
                }
                else {
                    cout << "killFailed: " << errnoWithDescription() << endl;
                    assert( x == 0 );
                }
            }

#endif
        }

        int killDb( int port, pid_t _pid, int signal ) {
            pid_t pid;
            int exitCode = 0;
            if ( port > 0 ) {
                if( dbs.count( port ) != 1 ) {
                    cout << "No db started on port: " << port << endl;
                    return 0;
                }
                pid = dbs[ port ].first;
            }
            else {
                pid = _pid;
            }

            kill_wrapper( pid, signal, port );

            int i = 0;
            for( ; i < 130; ++i ) {
                if ( i == 30 ) {
                    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;
                    kill_wrapper( pid, SIGKILL, port );
                }
                if(wait_for_pid(pid, false, &exitCode))
                    break;
                sleepmillis( 1000 );
            }
            if ( i == 130 ) {
                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 );
            }

            if ( port > 0 ) {
                close( dbs[ port ].second );
                dbs.erase( port );
            }
            else {
                close( shells[ pid ] );
                shells.erase( pid );
            }
            // FIXME I think the intention here is to do an extra sleep only when SIGKILL is sent to the child process.
            // We may want to change the 4 below to 29, since values of i greater than that indicate we sent a SIGKILL.
            if ( i > 4 || signal == SIGKILL ) {
                sleepmillis( 4000 ); // allow operating system to reclaim resources
            }

            return exitCode;
        }

        int getSignal( const BSONObj &a ) {
            int ret = SIGTERM;
            if ( a.nFields() == 2 ) {
                BSONObjIterator i( a );
                i.next();
                BSONElement e = i.next();
                assert( e.isNumber() );
                ret = int( e.number() );
            }
            return ret;
        }

        /** stopMongoProgram(port[, signal]) */
        BSONObj StopMongoProgram( const BSONObj &a, void* data ) {
            assert( a.nFields() == 1 || a.nFields() == 2 );
            uassert( 15853 , "stopMongo needs a number" , a.firstElement().isNumber() );
            int port = int( a.firstElement().number() );
            int code = killDb( port, 0, getSignal( a ) );
            cout << "shell: stopped mongo program on port " << port << endl;
            return BSON( "" << (double)code );
        }

        BSONObj StopMongoProgramByPid( const BSONObj &a, void* data ) {
            assert( a.nFields() == 1 || a.nFields() == 2 );
            uassert( 15852 , "stopMongoByPid needs a number" , a.firstElement().isNumber() );
            int pid = int( a.firstElement().number() );
            int code = killDb( 0, pid, getSignal( a ) );
            cout << "shell: stopped mongo program on pid " << pid << endl;
            return BSON( "" << (double)code );
        }

        void KillMongoProgramInstances() {
            vector< int > ports;
            for( map< int, pair< pid_t, int > >::iterator i = dbs.begin(); i != dbs.end(); ++i )
                ports.push_back( i->first );
            for( vector< int >::iterator i = ports.begin(); i != ports.end(); ++i )
                killDb( *i, 0, SIGTERM );
            vector< pid_t > pids;
            for( map< pid_t, int >::iterator i = shells.begin(); i != shells.end(); ++i )
                pids.push_back( i->first );
            for( vector< pid_t >::iterator i = pids.begin(); i != pids.end(); ++i )
                killDb( 0, *i, SIGTERM );
        }
#else // ndef MONGO_SAFE_SHELL
        void KillMongoProgramInstances() {}
#endif

        MongoProgramScope::~MongoProgramScope() {
            DESTRUCTOR_GUARD(
                KillMongoProgramInstances();
                ClearRawMongoProgramOutput( BSONObj(), 0 );
            )
        }

        unsigned _randomSeed;

        BSONObj JSSrand( const BSONObj &a, void* data ) {
            uassert( 12518, "srand requires a single numeric argument",
                     a.nFields() == 1 && a.firstElement().isNumber() );
            _randomSeed = (unsigned)a.firstElement().numberLong(); // grab least significant digits
            return undefined_;
        }

        BSONObj JSRand( const BSONObj &a, void* data ) {
            uassert( 12519, "rand accepts no arguments", a.nFields() == 0 );
            unsigned r;
#if !defined(_WIN32)
            r = rand_r( &_randomSeed );
#else
            r = rand(); // seed not used in this case
#endif
            return BSON( "" << double( r ) / ( double( RAND_MAX ) + 1 ) );
        }

        BSONObj isWindows(const BSONObj& a, void* data) {
            uassert( 13006, "isWindows accepts no arguments", a.nFields() == 0 );
#ifdef _WIN32
            return BSON( "" << true );
#else
            return BSON( "" << false );
#endif
        }

        BSONObj getHostName(const BSONObj& a, void* data) {
            uassert( 13411, "getHostName accepts no arguments", a.nFields() == 0 );
            char buf[260]; // HOST_NAME_MAX is usually 255
            assert(gethostname(buf, 260) == 0);
            buf[259] = '\0';
            return BSON("" << buf);

        }

        void installShellUtils( Scope& scope ) {
            theScope = &scope;
            scope.injectNative( "quit", Quit );
            scope.injectNative( "getMemInfo" , JSGetMemInfo );
            scope.injectNative( "_srand" , JSSrand );
            scope.injectNative( "_rand" , JSRand );
            scope.injectNative( "_isWindows" , isWindows );

#ifndef MONGO_SAFE_SHELL
            //can't launch programs
            scope.injectNative( "_startMongoProgram", StartMongoProgram );
            scope.injectNative( "runProgram", RunProgram );
            scope.injectNative( "run", RunProgram );
            scope.injectNative( "runMongoProgram", RunMongoProgram );
            scope.injectNative( "stopMongod", StopMongoProgram );
            scope.injectNative( "stopMongoProgram", StopMongoProgram );
            scope.injectNative( "stopMongoProgramByPid", StopMongoProgramByPid );
            scope.injectNative( "rawMongoProgramOutput", RawMongoProgramOutput );
            scope.injectNative( "clearRawMongoProgramOutput", ClearRawMongoProgramOutput );
            scope.injectNative( "waitProgram" , WaitProgram );
            scope.injectNative( "waitMongoProgramOnPort" , WaitMongoProgramOnPort );

            scope.injectNative( "getHostName" , getHostName );
            scope.injectNative( "removeFile" , removeFile );
            scope.injectNative( "fuzzFile" , fuzzFile );
            scope.injectNative( "listFiles" , listFiles );
            scope.injectNative( "ls" , ls );
            scope.injectNative( "pwd", pwd );
            scope.injectNative( "cd", cd );
            scope.injectNative( "cat", cat );
            scope.injectNative( "hostname", hostname);
            scope.injectNative( "resetDbpath", ResetDbpath );
            scope.injectNative( "copyDbpath", CopyDbpath );
            scope.injectNative( "md5sumFile", md5sumFile );
            scope.injectNative( "mkdir" , mkdir );
#endif
        }

        void initScope( Scope &scope ) {
            scope.externalSetup();
            mongo::shellUtils::installShellUtils( scope );
            scope.execSetup(JSFiles::servers);

            if ( !_dbConnect.empty() ) {
                uassert( 12513, "connect failed", scope.exec( _dbConnect , "(connect)" , false , true , false ) );
                if ( !_dbAuth.empty() ) {
                    installGlobalUtils( scope );
                    uassert( 12514, "login failed", scope.exec( _dbAuth , "(auth)" , true , true , false ) );
                }
            }
        }

        //   connstr, myuris
        map< string, set<string> > _allMyUris;
        mongo::mutex _allMyUrisMutex("_allMyUrisMutex");
        bool _nokillop = false;
        void onConnect( DBClientWithCommands &c ) {
            latestConn = &c;
            if ( _nokillop ) {
                return;
            }
            BSONObj info;
            if ( c.runCommand( "admin", BSON( "whatsmyuri" << 1 ), info ) ) {
                string connstr = dynamic_cast<DBClientBase&>(c).getServerAddress();
                mongo::mutex::scoped_lock lk( _allMyUrisMutex );
                _allMyUris[connstr].insert(info[ "you" ].str());
            }
        }
    }
}
