/* Copyright 2013 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 . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects * for all of the code used other than as permitted herein. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you do not * wish to do so, delete this exception statement from your version. If you * delete this exception statement from all source files in the program, * then also delete it in the license file. */ #include "mongo/util/options_parser/option_section.h" #include #include #include #include "mongo/bson/util/builder.h" #include "mongo/util/assert_util.h" #include "mongo/util/options_parser/value.h" namespace mongo { namespace optionenvironment { using std::shared_ptr; // Registration interface // TODO: Make sure the section we are adding does not have duplicate options Status OptionSection::addSection(const OptionSection& subSection) { std::list::const_iterator oditerator; for (oditerator = subSection._options.begin(); oditerator != subSection._options.end(); oditerator++) { if (oditerator->_positionalStart != -1) { StringBuilder sb; sb << "Attempted to add subsection with positional option: " << oditerator->_dottedName; return Status(ErrorCodes::InternalError, sb.str()); } } _subSections.push_back(subSection); return Status::OK(); } OptionDescription& OptionSection::addOptionChaining(const std::string& dottedName, const std::string& singleName, const OptionType type, const std::string& description) { std::vector v; return addOptionChaining(dottedName, singleName, type, description, v); } OptionDescription& OptionSection::addOptionChaining(const std::string& dottedName, const std::string& singleName, const OptionType type, const std::string& description, const std::string& deprecatedDottedName) { std::vector v; v.push_back(deprecatedDottedName); return addOptionChaining(dottedName, singleName, type, description, v); } OptionDescription& OptionSection::addOptionChaining( const std::string& dottedName, const std::string& singleName, const OptionType type, const std::string& description, const std::vector& deprecatedDottedNames) { OptionDescription option(dottedName, singleName, type, description, deprecatedDottedNames); // Verify that single name, the dotted name and deprecated dotted names for this option // conflicts with the names for any options we have already registered. std::list::const_iterator oditerator; for (oditerator = _options.begin(); oditerator != _options.end(); oditerator++) { if (option._dottedName == oditerator->_dottedName) { StringBuilder sb; sb << "Attempted to register option with duplicate dottedName: " << option._dottedName; uasserted(ErrorCodes::InternalError, sb.str()); } // Allow options with empty singleName since some options are not allowed on the command // line if (!option._singleName.empty() && option._singleName == oditerator->_singleName) { StringBuilder sb; sb << "Attempted to register option with duplicate singleName: " << option._singleName; uasserted(ErrorCodes::InternalError, sb.str()); } // Deprecated dotted names should not conflict with dotted names or deprecated dotted // names of any other options. if (std::count(option._deprecatedDottedNames.begin(), option._deprecatedDottedNames.end(), oditerator->_dottedName)) { StringBuilder sb; sb << "Attempted to register option with duplicate deprecated dotted name " << "(with another option's dotted name): " << option._dottedName; uasserted(ErrorCodes::BadValue, sb.str()); } for (std::vector::const_iterator i = oditerator->_deprecatedDottedNames.begin(); i != oditerator->_deprecatedDottedNames.end(); ++i) { if (std::count(option._deprecatedDottedNames.begin(), option._deprecatedDottedNames.end(), *i)) { StringBuilder sb; sb << "Attempted to register option with duplicate deprecated dotted name " << *i << " (other option " << oditerator->_dottedName << ")"; uasserted(ErrorCodes::BadValue, sb.str()); } } } _options.push_back(option); return _options.back(); } // Stuff for dealing with Boost namespace { /* * Helper for option types that should be interpreted as a string by boost. We do this to * take the responsibility away from boost for handling type conversions, since sometimes * those conversions are inconsistent with our own. See SERVER-14110 For an example. */ template Status typeToBoostStringType(std::unique_ptr* boostType, const Value defaultValue = Value(), const Value implicitValue = Value()) { std::unique_ptr> boostTypeBuilder(po::value()); if (!implicitValue.isEmpty()) { Type implicitValueType; Status ret = implicitValue.get(&implicitValueType); if (!ret.isOK()) { StringBuilder sb; sb << "Error getting implicit value: " << ret.toString(); return Status(ErrorCodes::InternalError, sb.str()); } StringBuilder sb; sb << implicitValueType; boostTypeBuilder->implicit_value(sb.str()); } if (!defaultValue.isEmpty()) { Type defaultValueType; Status ret = defaultValue.get(&defaultValueType); if (!ret.isOK()) { StringBuilder sb; sb << "Error getting default value: " << ret.toString(); return Status(ErrorCodes::InternalError, sb.str()); } StringBuilder sb; sb << defaultValueType; boostTypeBuilder->default_value(sb.str()); } *boostType = std::move(boostTypeBuilder); return Status::OK(); } /** Helper function to convert the values of our OptionType enum into the classes that * boost::program_option uses to pass around this information */ Status typeToBoostType(std::unique_ptr* boostType, OptionType type, const Value defaultValue = Value(), const Value implicitValue = Value(), bool getSwitchAsBool = false) { switch (type) { case StringVector: { *boostType = std::unique_ptr(po::value>()); if (!implicitValue.isEmpty()) { StringBuilder sb; sb << "Implicit value not supported for string vector"; return Status(ErrorCodes::InternalError, sb.str()); } if (!defaultValue.isEmpty()) { StringBuilder sb; sb << "Default value not supported for string vector"; return Status(ErrorCodes::InternalError, sb.str()); } return Status::OK(); } case StringMap: { // Boost doesn't support maps, so we just register a vector parameter and // parse it as "key=value" strings *boostType = std::unique_ptr(po::value>()); if (!implicitValue.isEmpty()) { StringBuilder sb; sb << "Implicit value not supported for string map"; return Status(ErrorCodes::InternalError, sb.str()); } if (!defaultValue.isEmpty()) { StringBuilder sb; sb << "Default value not supported for string map"; return Status(ErrorCodes::InternalError, sb.str()); } return Status::OK(); } case Switch: { // In boost, switches default to false which makes it impossible to tell if // a switch in a config file is not present or was explicitly set to false. // // Because of this, and because of the fact that we use the same set of // options for the legacy key=value config file, we need a way to control // whether we are telling boost that an option is a switch type or that an // option is a bool type. if (!getSwitchAsBool) { *boostType = std::unique_ptr(po::bool_switch()); return Status::OK(); } else { // Switches should be true if they are present with no explicit value. *boostType = std::unique_ptr>(po::value()->implicit_value(true)); return Status::OK(); } } case Bool: { std::unique_ptr> boostTypeBuilder(po::value()); if (!implicitValue.isEmpty()) { bool implicitValueType; Status ret = implicitValue.get(&implicitValueType); if (!ret.isOK()) { StringBuilder sb; sb << "Error getting implicit value: " << ret.toString(); return Status(ErrorCodes::InternalError, sb.str()); } boostTypeBuilder->implicit_value(implicitValueType); } if (!defaultValue.isEmpty()) { bool defaultValueType; Status ret = defaultValue.get(&defaultValueType); if (!ret.isOK()) { StringBuilder sb; sb << "Error getting default value: " << ret.toString(); return Status(ErrorCodes::InternalError, sb.str()); } boostTypeBuilder->default_value(defaultValueType); } *boostType = std::move(boostTypeBuilder); return Status::OK(); } case String: return typeToBoostStringType(boostType, defaultValue, implicitValue); case Double: return typeToBoostStringType(boostType, defaultValue, implicitValue); case Int: return typeToBoostStringType(boostType, defaultValue, implicitValue); case Long: return typeToBoostStringType(boostType, defaultValue, implicitValue); case UnsignedLongLong: return typeToBoostStringType( boostType, defaultValue, implicitValue); case Unsigned: return typeToBoostStringType(boostType, defaultValue, implicitValue); default: { StringBuilder sb; sb << "Unrecognized option type: " << type; return Status(ErrorCodes::InternalError, sb.str()); } } } } // namespace Status OptionSection::getBoostOptions(po::options_description* boostOptions, bool visibleOnly, bool includeDefaults, OptionSources sources, bool getEmptySections) const { std::list::const_iterator oditerator; for (oditerator = _options.begin(); oditerator != _options.end(); oditerator++) { // Only include this option if it matches the sources we specified and the option is // either visible or we are requesting hidden options if ((!visibleOnly || (oditerator->_isVisible)) && (oditerator->_sources & sources)) { std::unique_ptr boostType; Status ret = typeToBoostType(&boostType, oditerator->_type, includeDefaults ? oditerator->_default : Value(), oditerator->_implicit, !(sources & SourceCommandLine)); if (!ret.isOK()) { StringBuilder sb; sb << "Error getting boost type for option \"" << oditerator->_dottedName << "\": " << ret.toString(); return Status(ErrorCodes::InternalError, sb.str()); } if (oditerator->_singleName.empty()) { StringBuilder sb; sb << "Single name is empty for option \"" << oditerator->_dottedName << "\", but trying to use it on the command line " << "or INI config file. Only options that are exclusive to the YAML config " << "file can have an empty single name"; return Status(ErrorCodes::InternalError, sb.str()); } boostOptions->add_options()(oditerator->_singleName.c_str(), boostType.release(), oditerator->_description.c_str()); } } std::list::const_iterator ositerator; for (ositerator = _subSections.begin(); ositerator != _subSections.end(); ositerator++) { po::options_description subGroup = ositerator->_name.empty() ? po::options_description() : po::options_description(ositerator->_name.c_str()); // Do not add empty sections to our option_description unless we specifically requested. int numOptions; Status ret = ositerator->countOptions(&numOptions, visibleOnly, sources); if (!ret.isOK()) { return ret; } if (numOptions == 0 && getEmptySections == false) { continue; } ret = ositerator->getBoostOptions( &subGroup, visibleOnly, includeDefaults, sources, getEmptySections); if (!ret.isOK()) { return ret; } boostOptions->add(subGroup); } return Status::OK(); } /* * The way we specify positional options in our interface differs from the way boost does it, so * we have to convert them here. * * For example, to specify positionals such that you can run "./exec [pos1] [pos2] [pos2]": * * Our interface: * * options.addOptionChaining("pos2", "pos2", moe::StringVector, "Pos2") * .hidden() <- doesn't show up in help * .sources(moe::SourceCommandLine) <- only allowed on command line * .positional(2, <- start position * 3); <- end position * options.addOptionChaining("pos1", "pos1", moe::String, "Pos1") * .hidden() <- doesn't show up in help * .sources(moe::SourceCommandLine) <- only allowed on command line * .positional(1, <- start position * 1); <- end position * // Note that order doesn't matter * * Boost's interface: * * boostHiddenOptions->add_options()("pos1", po::value(), "Pos1") * ("pos2", po::value(), "Pos2") * * boostPositionalOptions->add("pos1", 1); <- count of option (number of times it appears) * boostPositionalOptions->add("pos2", 2); <- count of option (number of times it appears) * // Note that order does matter * * Because of this, we have to perform the conversion in this function. The tasks performed by * this function are: * * 1. Making sure the ranges are valid as a whole (no overlap or holes) * 2. Convert to the boost options and add them in the correct order */ Status OptionSection::getBoostPositionalOptions( po::positional_options_description* boostPositionalOptions) const { std::list positionalOptions; std::list::const_iterator oditerator; for (oditerator = _options.begin(); oditerator != _options.end(); oditerator++) { // Check if this is a positional option, and extract it if it is if (oditerator->_positionalStart != -1) { positionalOptions.push_back(*oditerator); } } int nextPosition = 1; bool foundAtPosition = false; while (!positionalOptions.empty()) { foundAtPosition = false; std::list::iterator poditerator; for (poditerator = positionalOptions.begin(); poditerator != positionalOptions.end();) { if (poditerator->_positionalStart < nextPosition) { StringBuilder sb; sb << "Found option with overlapping positional range: " << " Expected next option at position: " << nextPosition << ", but \"" << poditerator->_dottedName << "\" starts at position: " << poditerator->_positionalStart; return Status(ErrorCodes::InternalError, sb.str()); } if (poditerator->_positionalStart == nextPosition) { foundAtPosition = true; int count; if (poditerator->_positionalEnd == -1) { count = -1; if (positionalOptions.size() != 1) { StringBuilder sb; sb << "Found positional option with infinite count, but still have " << "more positional options registered"; return Status(ErrorCodes::InternalError, sb.str()); } } else { count = (poditerator->_positionalEnd + 1) - poditerator->_positionalStart; } boostPositionalOptions->add(poditerator->_dottedName.c_str(), count); nextPosition += count; std::list::iterator old_poditerator = poditerator; poditerator++; positionalOptions.erase(old_poditerator); } else { poditerator++; } } if (!foundAtPosition) { StringBuilder sb; sb << "Did not find option at position: " << nextPosition; return Status(ErrorCodes::InternalError, sb.str()); } } // XXX: Right now only the top level section can have positional options return Status::OK(); } // Get options for iterating // TODO: should I make this an iterator? Status OptionSection::getAllOptions(std::vector* options) const { std::list::const_iterator oditerator; for (oditerator = _options.begin(); oditerator != _options.end(); oditerator++) { // We need to check here that we didn't register an option with an empty single name // that is allowed on the command line or in an old style config, since we don't have // this information available all at once when the option is registered if (oditerator->_singleName.empty() && oditerator->_sources & SourceAllLegacy) { StringBuilder sb; sb << "Found option allowed on the command line with an empty singleName: " << oditerator->_dottedName; return Status(ErrorCodes::InternalError, sb.str()); } options->push_back(*oditerator); } std::list::const_iterator ositerator; for (ositerator = _subSections.begin(); ositerator != _subSections.end(); ositerator++) { ositerator->getAllOptions(options).transitional_ignore(); } return Status::OK(); } Status OptionSection::getDefaults(std::map* values) const { std::list::const_iterator oditerator; for (oditerator = _options.begin(); oditerator != _options.end(); oditerator++) { if (!oditerator->_default.isEmpty()) { (*values)[oditerator->_dottedName] = oditerator->_default; } } std::list::const_iterator ositerator; for (ositerator = _subSections.begin(); ositerator != _subSections.end(); ositerator++) { ositerator->getDefaults(values).transitional_ignore(); } return Status::OK(); } Status OptionSection::countOptions(int* numOptions, bool visibleOnly, OptionSources sources) const { *numOptions = 0; std::list::const_iterator oditerator; for (oditerator = _options.begin(); oditerator != _options.end(); oditerator++) { // Only count this option if it matches the sources we specified and the option is // either visible or we are requesting hidden options if ((!visibleOnly || (oditerator->_isVisible)) && (oditerator->_sources & sources)) { (*numOptions)++; } } std::list::const_iterator ositerator; for (ositerator = _subSections.begin(); ositerator != _subSections.end(); ositerator++) { int numSubOptions = 0; ositerator->countOptions(&numSubOptions, visibleOnly, sources).transitional_ignore(); *numOptions += numSubOptions; } return Status::OK(); } Status OptionSection::getConstraints(std::vector>* constraints) const { std::list::const_iterator oditerator; for (oditerator = _options.begin(); oditerator != _options.end(); oditerator++) { std::vector>::const_iterator citerator; for (citerator = oditerator->_constraints.begin(); citerator != oditerator->_constraints.end(); citerator++) { constraints->push_back(*citerator); } } std::list::const_iterator ositerator; for (ositerator = _subSections.begin(); ositerator != _subSections.end(); ositerator++) { ositerator->getConstraints(constraints).transitional_ignore(); } return Status::OK(); } std::string OptionSection::positionalHelpString(const std::string& execName) const { po::positional_options_description boostPositionalOptions; Status ret = getBoostPositionalOptions(&boostPositionalOptions); if (!ret.isOK()) { StringBuilder sb; sb << "Error constructing help string: " << ret.toString(); return sb.str(); } StringBuilder posHelpStringBuilder; posHelpStringBuilder << execName; // If we can have unlimited positional options, this returns // std::numeric_limits::max(). Check here for that case and record what name to // look for. unsigned int numPositional = boostPositionalOptions.max_total_count(); std::string trailingPositionName; if (numPositional == std::numeric_limits::max()) { trailingPositionName = boostPositionalOptions.name_for_position(numPositional - 1); } unsigned int position; std::string positionName; for (position = 0; position < numPositional; position++) { positionName = boostPositionalOptions.name_for_position(position); if (!trailingPositionName.empty() && trailingPositionName == positionName) { // If we have a trailing position, we break when we see it the first time. posHelpStringBuilder << " [" << trailingPositionName << " ... ]"; break; } posHelpStringBuilder << " [" << positionName << "]"; } return posHelpStringBuilder.str(); } std::string OptionSection::helpString() const { po::options_description boostOptions = _name.empty() ? po::options_description() : po::options_description(_name.c_str()); Status ret = getBoostOptions(&boostOptions, true, /* visibleOnly */ true, /* includeDefaults */ SourceAllLegacy, false); /* getEmptySections */ if (!ret.isOK()) { StringBuilder sb; sb << "Error constructing help string: " << ret.toString(); return sb.str(); } // Can't use a StringBuilder here because boost::program_options only has functions that // output to std::ostream std::ostringstream os; os << boostOptions; return os.str(); } /* Debugging */ void OptionSection::dump() const { std::list::const_iterator oditerator; for (oditerator = _options.begin(); oditerator != _options.end(); oditerator++) { std::cout << " _dottedName: " << oditerator->_dottedName << " _singleName: " << oditerator->_singleName << " _type: " << oditerator->_type << " _description: " << oditerator->_description << " _isVisible: " << oditerator->_isVisible << std::endl; } std::list::const_iterator ositerator; for (ositerator = _subSections.begin(); ositerator != _subSections.end(); ositerator++) { std::cout << "Section Name: " << ositerator->_name << std::endl; ositerator->dump(); } } } // namespace optionenvironment } // namespace mongo