diff options
Diffstat (limited to 'src/mongo/util')
-rw-r--r-- | src/mongo/util/options_parser/options_parser.cpp | 171 | ||||
-rw-r--r-- | src/mongo/util/options_parser/options_parser_test.cpp | 23 |
2 files changed, 192 insertions, 2 deletions
diff --git a/src/mongo/util/options_parser/options_parser.cpp b/src/mongo/util/options_parser/options_parser.cpp index ceebed92fd3..ff2bcb49a16 100644 --- a/src/mongo/util/options_parser/options_parser.cpp +++ b/src/mongo/util/options_parser/options_parser.cpp @@ -884,6 +884,166 @@ Status OptionsParser::readConfigFile(const std::string& filename, std::string* c return Status::OK(); } +namespace { +/** + * Find implicit options and merge them with "=". + * Implicit options in boost 1.59 no longer support + * --option value + * instead they only support "--option=value", this function + * attempts to workound this by translating the former into the later. + */ +StatusWith<std::vector<std::string>> transformImplictOptions( + const OptionSection& options, const std::vector<std::string>& argvOriginal) { + if (argvOriginal.empty()) { + return {std::vector<std::string>()}; + } + + std::vector<OptionDescription> optionDescs; + Status ret = options.getAllOptions(&optionDescs); + if (!ret.isOK()) { + return ret; + } + + std::map<string, const OptionDescription*> implicitOptions; + for (const auto& opt : optionDescs) { + if (opt._implicit.isEmpty()) { + continue; + } + + // Some options have a short name (a single letter) in addition to a long name. + // In this case, the format for the name of the option is "long_name,S" where S is a + // single character. + // This is validated as such by the boost option parser later in the code. + size_t pos = opt._singleName.find(','); + if (pos != string::npos) { + implicitOptions[opt._singleName.substr(0, pos)] = &opt; + implicitOptions[opt._singleName.substr(pos + 1)] = &opt; + } else { + implicitOptions[opt._singleName] = &opt; + } + } + + std::vector<std::string> args; + args.reserve(argvOriginal.size()); + + // If there are no implicit options for this option parser instance, no filtering is needed. + if (implicitOptions.empty()) { + std::copy(argvOriginal.begin(), argvOriginal.end(), std::back_inserter(args)); + + return {args}; + } + + // Now try to merge the implicit arguments + // Candidates to merge: + // -arg value + // --arg value + // -arg "" + // --arg "" + // Candidates not to merge: + // -arg=value + // --arg=value + for (size_t i = 0; i < argvOriginal.size(); i++) { + std::string arg = argvOriginal[i]; + + // Enable us to fall through to the default code path of pushing another arg into the vector + do { + // Skip processing of the last argument in the array since there would be nothing to + // merge it with + if (i == argvOriginal.size() - 1) { + break; + } + + // Skip empty strings + if (arg.empty()) { + break; + } + + // All options start with at least one "-" + if (arg[0] == '-') { + int argPrefix = 1; + + // Is the argument just a single "-", i.e. short form or a disguised long form? + if (arg.size() == 1) { + break; + } + + if (arg[argPrefix] == '-') { + // Is the argument just a double "--", i.e. long form? + if (arg.size() == 2) { + break; + } + + ++argPrefix; + } + + // Now we strip the prefix, and do a match on the parameter name. + // At this point we may have "option=value" which is not in our option list so + // we move onto the next option. + std::string parameter = arg.substr(argPrefix); + + const auto iterator = implicitOptions.find(parameter); + if (iterator == implicitOptions.end()) { + break; + } + + // If the next argument is the empty string, just eat the empty string + if (argvOriginal[i + 1].empty()) { + const auto& value = iterator->second->_implicit; + std::string defaultStr; + + Status stringStatus = value.get(&defaultStr); + + // If the type of the option was a string type with a implicit value other than + // "", then we cannot handle merging these two strings because simply leaving + // just the option name around has a different behavior. + // i.e., it is impossible to have "--option=''" on the command line as some + // non-empty string must follow the equal sign. + // This specific case is only known to affect "verbose" in the long form in + // mongod and mongos which makes it a breaking change for this one specific + // change. Users can get similar behavior by removing both the option and the + // original string in this case. + if (stringStatus.isOK() && !defaultStr.empty()) { + return Status(ErrorCodes::BadValue, + "The empty string is not supported with the option " + + parameter); + }; + + // For strings that have an implicit value of "", we just + // disregard the empty string argument since it has the same meaning. + // For other types like integers this is not a problem in non-test code as the + // only options with implicit options are string types. + } else { + // Before we decide to merge the arguments, we must see if the next argument + // is actually an option. Boost checks its list of options, but we will just + // guess based on a leading '-' rather then try to match Boost's logic. + if (argvOriginal[i + 1][0] == '-') { + break; + } + + if (parameter.size() == 1) { + // Merge this short form argument and the next into one. + // Note: we do not support allow_sticky so a simple append works + arg = str::stream() << argvOriginal[i] << argvOriginal[i + 1]; + } else { + // Merge this long form argument and the next into one. + arg = str::stream() << argvOriginal[i] << "=" << argvOriginal[i + 1]; + } + } + + // Advance to the next argument + i++; + } + } while (false); + + // Add the option to the final list, this may be an option that we simply ignore. + args.push_back(arg); + } + + return {args}; +} + +} // namespace + /** * Run the OptionsParser * @@ -897,14 +1057,21 @@ Status OptionsParser::readConfigFile(const std::string& filename, std::string* c * 6. Add the results to the output Environment in the proper order to ensure correct precedence */ Status OptionsParser::run(const OptionSection& options, - const std::vector<std::string>& argv, + const std::vector<std::string>& argvOriginal, const std::map<std::string, std::string>& env, // XXX: Currently unused Environment* environment) { Environment commandLineEnvironment; Environment configEnvironment; Environment composedEnvironment; - Status ret = parseCommandLine(options, argv, &commandLineEnvironment); + auto swTransform = transformImplictOptions(options, argvOriginal); + if (!swTransform.isOK()) { + return swTransform.getStatus(); + } + + std::vector<std::string> argvTransformed = std::move(swTransform.getValue()); + + Status ret = parseCommandLine(options, argvTransformed, &commandLineEnvironment); if (!ret.isOK()) { return ret; } diff --git a/src/mongo/util/options_parser/options_parser_test.cpp b/src/mongo/util/options_parser/options_parser_test.cpp index 880e4ad13ba..79de691ea17 100644 --- a/src/mongo/util/options_parser/options_parser_test.cpp +++ b/src/mongo/util/options_parser/options_parser_test.cpp @@ -756,6 +756,29 @@ TEST(Parsing, ImplicitValueOverride) { ASSERT_EQUALS(port, 5); } +TEST(Parsing, ImplicitValueOverrideWithEqualsSign) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOptionChaining("help", "help", moe::Switch, "Display help"); + testOpts.addOptionChaining("port", "port", moe::Int, "Port") + .setDefault(moe::Value(6)) + .setImplicit(moe::Value(7)); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--port=5"); + std::map<std::string, std::string> env_map; + + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + moe::Value value; + ASSERT_OK(environment.get(moe::Key("port"), &value)); + int port; + ASSERT_OK(value.get(&port)); + ASSERT_EQUALS(port, 5); +} + TEST(Parsing, ShortName) { moe::OptionsParser parser; moe::Environment environment; |