// cmdline.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 <http://www.gnu.org/licenses/>.
*/

#include "pch.h"
#include "cmdline.h"
#include "commands.h"
#include "../util/password.h"
#include "../util/processinfo.h"
#include "../util/net/listen.h"
#include "security_common.h"

#ifdef _WIN32
#include <direct.h>
#endif

#define MAX_LINE_LENGTH 256

namespace po = boost::program_options;
namespace fs = boost::filesystem;

namespace mongo {

    void setupSignals( bool inFork );
    string getHostNameCached();
    static BSONArray argvArray;
    static BSONObj parsedOpts;

    void CmdLine::addGlobalOptions( boost::program_options::options_description& general ,
                                    boost::program_options::options_description& hidden ) {
        /* support for -vv -vvvv etc. */
        for (string s = "vv"; s.length() <= 12; s.append("v")) {
            hidden.add_options()(s.c_str(), "verbose");
        }

        general.add_options()
        ("help,h", "show this usage information")
        ("version", "show version information")
        ("config,f", po::value<string>(), "configuration file specifying additional options")
        ("verbose,v", "be more verbose (include multiple times for more verbosity e.g. -vvvvv)")
        ("quiet", "quieter output")
        ("port", po::value<int>(&cmdLine.port), "specify port number")
        ("bind_ip", po::value<string>(&cmdLine.bind_ip), "comma separated list of ip addresses to listen on - all local ips by default")
        ("maxConns",po::value<int>(), "max number of simultaneous connections")
        ("objcheck", "inspect client data for validity on receipt")
        ("logpath", po::value<string>() , "log file to send write to instead of stdout - has to be a file, not directory" )
        ("logappend" , "append to logpath instead of over-writing" )
        ("pidfilepath", po::value<string>(), "full path to pidfile (if not set, no pidfile is created)")
        ("keyFile", po::value<string>(), "private key for cluster authentication (only for replica sets)")
#ifndef _WIN32
        ("nounixsocket", "disable listening on unix sockets")
        ("unixSocketPrefix", po::value<string>(), "alternative directory for UNIX domain sockets (defaults to /tmp)")
        ("fork" , "fork server process" )
#endif
        ;
        
        hidden.add_options()
#ifdef MONGO_SSL
        ("sslOnNormalPorts" , "use ssl on configured ports" )
        ("sslPEMKeyFile" , po::value<string>(&cmdLine.sslPEMKeyFile), "PEM file for ssl" )
        ("sslPEMKeyPassword" , new PasswordValue(&cmdLine.sslPEMKeyPassword) , "PEM file password" )
#endif
        ;

    }


#if defined(_WIN32)
    void CmdLine::addWindowsOptions( boost::program_options::options_description& windows ,
                                     boost::program_options::options_description& hidden ) {
        windows.add_options()
        ("install", "install mongodb service")
        ("remove", "remove mongodb service")
        ("reinstall", "reinstall mongodb service (equivilant of mongod --remove followed by mongod --install)")
        ("serviceName", po::value<string>(), "windows service name")
        ("serviceDisplayName", po::value<string>(), "windows service display name")
        ("serviceDescription", po::value<string>(), "windows service description")
        ("serviceUser", po::value<string>(), "user name service executes as")
        ("servicePassword", po::value<string>(), "password used to authenticate serviceUser")
        ;
        hidden.add_options()("service", "start mongodb service");
    }
#endif

    void CmdLine::parseConfigFile( istream &f, stringstream &ss ) {
        string s;
        char line[MAX_LINE_LENGTH];

        while ( f ) {
            f.getline(line, MAX_LINE_LENGTH);
            s = line;
            std::remove(s.begin(), s.end(), ' ');
            std::remove(s.begin(), s.end(), '\t');
            boost::to_upper(s);

            if ( s.find( "FASTSYNC" ) != string::npos )
                cout << "warning \"fastsync\" should not be put in your configuration file" << endl;

            if ( s.c_str()[0] == '#' ) { 
                // skipping commented line
            } else if ( s.find( "=FALSE" ) == string::npos ) {
                ss << line << endl;
            } else {
                cout << "warning: remove or comment out this line by starting it with \'#\', skipping now : " << line << endl;
            }
        }
        return;
    }



    bool CmdLine::store( int argc , char ** argv ,
                         boost::program_options::options_description& visible,
                         boost::program_options::options_description& hidden,
                         boost::program_options::positional_options_description& positional,
                         boost::program_options::variables_map &params ) {


        {
            // setup binary name
            cmdLine.binaryName = argv[0];
            size_t i = cmdLine.binaryName.rfind( '/' );
            if ( i != string::npos )
                cmdLine.binaryName = cmdLine.binaryName.substr( i + 1 );
            
            // setup cwd
            char buffer[1024];
#ifdef _WIN32
            assert( _getcwd( buffer , 1000 ) );
#else
            assert( getcwd( buffer , 1000 ) );
#endif
            cmdLine.cwd = buffer;
        }
        

        /* don't allow guessing - creates ambiguities when some options are
         * prefixes of others. allow long disguises and don't allow guessing
         * to get away with our vvvvvvv trick. */
        int style = (((po::command_line_style::unix_style ^
                       po::command_line_style::allow_guessing) |
                      po::command_line_style::allow_long_disguise) ^
                     po::command_line_style::allow_sticky);


        try {

            po::options_description all;
            all.add( visible );
            all.add( hidden );

            po::store( po::command_line_parser(argc, argv)
                       .options( all )
                       .positional( positional )
                       .style( style )
                       .run(),
                       params );

            if ( params.count("config") ) {
                ifstream f( params["config"].as<string>().c_str() );
                if ( ! f.is_open() ) {
                    cout << "ERROR: could not read from config file" << endl << endl;
                    cout << visible << endl;
                    return false;
                }

                stringstream ss;
                CmdLine::parseConfigFile( f, ss );
                po::store( po::parse_config_file( ss , all ) , params );
                f.close();
            }

            po::notify(params);
        }
        catch (po::error &e) {
            cout << "error command line: " << e.what() << endl;
            cout << "use --help for help" << endl;
            //cout << visible << endl;
            return false;
        }

        if (params.count("verbose")) {
            logLevel = 1;
        }

        for (string s = "vv"; s.length() <= 12; s.append("v")) {
            if (params.count(s)) {
                logLevel = s.length();
            }
        }

        if (params.count("quiet")) {
            cmdLine.quiet = true;
        }

        if ( params.count( "maxConns" ) ) {
            int newSize = params["maxConns"].as<int>();
            if ( newSize < 5 ) {
                out() << "maxConns has to be at least 5" << endl;
                dbexit( EXIT_BADOPTIONS );
            }
            else if ( newSize >= 10000000 ) {
                out() << "maxConns can't be greater than 10000000" << endl;
                dbexit( EXIT_BADOPTIONS );
            }
            connTicketHolder.resize( newSize );
        }

        if (params.count("objcheck")) {
            cmdLine.objcheck = true;
        }

        string logpath;

#ifndef _WIN32
        if (params.count("unixSocketPrefix")) {
            cmdLine.socket = params["unixSocketPrefix"].as<string>();
            if (!fs::is_directory(cmdLine.socket)) {
                cout << cmdLine.socket << " must be a directory" << endl;
                ::exit(-1);
            }
        }

        if (params.count("nounixsocket")) {
            cmdLine.noUnixSocket = true;
        }

        if (params.count("fork")) {
            if ( ! params.count( "logpath" ) ) {
                cout << "--fork has to be used with --logpath" << endl;
                ::exit(-1);
            }

            {
                // test logpath
                logpath = params["logpath"].as<string>();
                assert( logpath.size() );
                if ( logpath[0] != '/' ) {
                    logpath = cmdLine.cwd + "/" + logpath;
                }
                FILE * test = fopen( logpath.c_str() , "a" );
                if ( ! test ) {
                    cout << "can't open [" << logpath << "] for log file: " << errnoWithDescription() << endl;
                    ::exit(-1);
                }
                fclose( test );
            }

            cout.flush();
            cerr.flush();

            pid_t c = fork();
            if ( c ) {
                _exit(0);
            }

            if ( chdir("/") < 0 ) {
                cout << "Cant chdir() while forking server process: " << strerror(errno) << endl;
                ::exit(-1);
            }
            setsid();

            pid_t c2 = fork();
            if ( c2 ) {
                cout << "forked process: " << c2 << endl;
                _exit(0);
            }

            // stdout handled in initLogging
            //fclose(stdout);
            //freopen("/dev/null", "w", stdout);

            fclose(stderr);
            fclose(stdin);

            FILE* f = freopen("/dev/null", "w", stderr);
            if ( f == NULL ) {
                cout << "Cant reassign stderr while forking server process: " << strerror(errno) << endl;
                ::exit(-1);
            }

            f = freopen("/dev/null", "r", stdin);
            if ( f == NULL ) {
                cout << "Cant reassign stdin while forking server process: " << strerror(errno) << endl;
                ::exit(-1);
            }

            setupCoreSignals();
            setupSignals( true );
        }

#endif
        if (params.count("logpath")) {
            if ( logpath.size() == 0 )
                logpath = params["logpath"].as<string>();
            uassert( 10033 ,  "logpath has to be non-zero" , logpath.size() );
            initLogging( logpath , params.count( "logappend" ) );
        }

        if ( params.count("pidfilepath")) {
            writePidFile( params["pidfilepath"].as<string>() );
        }

        if (params.count("keyFile")) {
            const string f = params["keyFile"].as<string>();

            if (!setUpSecurityKey(f)) {
                // error message printed in setUpPrivateKey
                dbexit(EXIT_BADOPTIONS);
            }

            cmdLine.keyFile = true;
            noauth = false;
        }
        else {
            cmdLine.keyFile = false;
        }

#ifdef MONGO_SSL
        if (params.count("sslOnNormalPorts") ) {
            cmdLine.sslOnNormalPorts = true;

            if ( cmdLine.sslPEMKeyPassword.size() == 0 ) {
                log() << "need sslPEMKeyPassword" << endl;
                dbexit(EXIT_BADOPTIONS);
            }
            
            if ( cmdLine.sslPEMKeyFile.size() == 0 ) {
                log() << "need sslPEMKeyFile" << endl;
                dbexit(EXIT_BADOPTIONS);
            }
            
            cmdLine.sslServerManager = new SSLManager( false );
            cmdLine.sslServerManager->setupPEM( cmdLine.sslPEMKeyFile , cmdLine.sslPEMKeyPassword );
        }
#endif
        
        {
            BSONObjBuilder b;
            for (po::variables_map::const_iterator it(params.begin()), end(params.end()); it != end; it++){
                if (!it->second.defaulted()){
                    const string& key = it->first;
                    const po::variable_value& value = it->second;
                    const type_info& type = value.value().type();

                    if (type == typeid(string)){
                        if (value.as<string>().empty())
                            b.appendBool(key, true); // boost po uses empty string for flags like --quiet
                        else
                            b.append(key, value.as<string>());
                    }
                    else if (type == typeid(int))
                        b.append(key, value.as<int>());
                    else if (type == typeid(double))
                        b.append(key, value.as<double>());
                    else if (type == typeid(bool))
                        b.appendBool(key, value.as<bool>());
                    else if (type == typeid(long))
                        b.appendNumber(key, (long long)value.as<long>());
                    else if (type == typeid(unsigned))
                        b.appendNumber(key, (long long)value.as<unsigned>());
                    else if (type == typeid(unsigned long long))
                        b.appendNumber(key, (long long)value.as<unsigned long long>());
                    else if (type == typeid(vector<string>))
                        b.append(key, value.as<vector<string> >());
                    else
                        b.append(key, "UNKNOWN TYPE: " + demangleName(type));
                }
            }
            parsedOpts = b.obj();
        }

        {
            BSONArrayBuilder b;
            for (int i=0; i < argc; i++)
                b << argv[i];
            argvArray = b.arr();
        }

        return true;
    }

    void printCommandLineOpts() {
        log() << "options: " << parsedOpts << endl;
    }

    void ignoreSignal( int sig ) {}

    void setupCoreSignals() {
#if !defined(_WIN32)
        assert( signal(SIGUSR1 , rotateLogs ) != SIG_ERR );
        assert( signal(SIGHUP , ignoreSignal ) != SIG_ERR );
#endif
    }

    class CmdGetCmdLineOpts : Command {
    public:
        CmdGetCmdLineOpts(): Command("getCmdLineOpts") {}
        void help(stringstream& h) const { h << "get argv"; }
        virtual LockType locktype() const { return NONE; }
        virtual bool adminOnly() const { return true; }
        virtual bool slaveOk() const { return true; }

        virtual bool run(const string&, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) {
            result.append("argv", argvArray);
            result.append("parsed", parsedOpts);
            return true;
        }

    } cmdGetCmdLineOpts;

    string prettyHostName() {
        StringBuilder s(128);
        s << getHostNameCached();
        if( cmdLine.port != CmdLine::DefaultDBPort )
            s << ':' << mongo::cmdLine.port;
        return s.str();
    }

    ParameterValidator::ParameterValidator( const string& name ) : _name( name ) {
        if ( ! _all )
            _all = new map<string,ParameterValidator*>();
        (*_all)[_name] = this;
    }

    ParameterValidator * ParameterValidator::get( const string& name ) {
        map<string,ParameterValidator*>::iterator i = _all->find( name );
        if ( i == _all->end() )
            return NULL;
        return i->second;
    }
    map<string,ParameterValidator*> * ParameterValidator::_all = 0;

}
