blob: 48a9eab725602fcd758309aa166273128ce7bd1a [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 "kudu/util/flags.h"
#include <strings.h>
#include <sys/stat.h>
#include <unistd.h> // IWYU pragma: keep
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <iostream>
#include <iterator>
#include <map>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include <gflags/gflags.h>
#include <glog/logging.h>
#ifdef TCMALLOC_ENABLED
#include <gperftools/heap-profiler.h>
#endif
#include "kudu/gutil/macros.h"
#include "kudu/gutil/map-util.h"
#include "kudu/gutil/stringprintf.h"
#include "kudu/gutil/strings/join.h"
#include "kudu/gutil/strings/numbers.h"
#include "kudu/gutil/strings/split.h"
#include "kudu/gutil/strings/stringpiece.h"
#include "kudu/gutil/strings/substitute.h"
#include "kudu/util/flag_tags.h"
#include "kudu/util/flag_validators.h"
#include "kudu/util/logging.h"
#include "kudu/util/metrics.h"
#include "kudu/util/os-util.h"
#include "kudu/util/path_util.h"
#include "kudu/util/string_case.h"
#include "kudu/util/url-coding.h"
#include "kudu/util/version_info.h"
using google::CommandLineFlagInfo;
using std::cout;
using std::endl;
using std::string;
using std::ostringstream;
using std::unordered_set;
using std::vector;
using strings::Substitute;
// Because every binary initializes its flags here, we use it as a convenient place
// to offer some global flags as well.
DEFINE_bool(dump_metrics_json, false,
"Dump a JSON document describing all of the metrics which may be emitted "
"by this binary.");
TAG_FLAG(dump_metrics_json, hidden);
DEFINE_bool(dump_metrics_xml, false,
"Dump an XML document describing all of the metrics which may be emitted "
"by this binary.");
TAG_FLAG(dump_metrics_xml, hidden);
#ifdef TCMALLOC_ENABLED
DEFINE_bool(enable_process_lifetime_heap_profiling, false, "Enables heap "
"profiling for the lifetime of the process. Profile output will be stored in the "
"directory specified by -heap_profile_path.");
TAG_FLAG(enable_process_lifetime_heap_profiling, stable);
TAG_FLAG(enable_process_lifetime_heap_profiling, advanced);
DEFINE_string(heap_profile_path, "", "Output path to store heap profiles. If not set " \
"profiles are stored in /tmp/<process-name>.<pid>.<n>.heap.");
TAG_FLAG(heap_profile_path, stable);
TAG_FLAG(heap_profile_path, advanced);
DEFINE_int64(heap_sample_every_n_bytes, 0,
"Enable heap occupancy sampling. If this flag is set to some positive "
"value N, a memory allocation will be sampled approximately every N bytes. "
"Lower values of N incur larger overhead but give more accurate results. "
"A value such as 524288 (512KB) is a reasonable choice with relatively "
"low overhead.");
TAG_FLAG(heap_sample_every_n_bytes, advanced);
TAG_FLAG(heap_sample_every_n_bytes, experimental);
#endif
DEFINE_bool(disable_core_dumps, false, "Disable core dumps when this process crashes.");
TAG_FLAG(disable_core_dumps, advanced);
TAG_FLAG(disable_core_dumps, evolving);
DEFINE_string(umask, "077",
"The umask that will be used when creating files and directories. "
"Permissions of top-level data directories will also be modified at "
"start-up to conform to the given umask. Changing this value may "
"enable unauthorized local users to read or modify data stored by Kudu.");
TAG_FLAG(umask, advanced);
static bool ValidateUmask(const char* /*flagname*/, const string& value) {
uint32_t parsed;
if (!safe_strtou32_base(value.c_str(), &parsed, 8)) {
LOG(ERROR) << "Invalid umask: must be an octal string";
return false;
}
// Verify that the umask doesn't restrict the permissions of the owner.
// If it did, we'd end up creating files that we can't read.
if ((parsed & 0700) != 0) {
LOG(ERROR) << "Invalid umask value: must not restrict owner permissions";
return false;
}
return true;
}
DEFINE_validator(umask, &ValidateUmask);
DEFINE_bool(unlock_experimental_flags, false,
"Unlock flags marked as 'experimental'. These flags are not guaranteed to "
"be maintained across releases of Kudu, and may enable features or behavior "
"known to be unstable. Use at your own risk.");
TAG_FLAG(unlock_experimental_flags, advanced);
TAG_FLAG(unlock_experimental_flags, stable);
DEFINE_bool(unlock_unsafe_flags, false,
"Unlock flags marked as 'unsafe'. These flags are not guaranteed to "
"be maintained across releases of Kudu, and enable features or behavior "
"known to be unsafe. Use at your own risk.");
TAG_FLAG(unlock_unsafe_flags, advanced);
TAG_FLAG(unlock_unsafe_flags, stable);
DEFINE_string(redact, "all",
"Comma-separated list that controls redaction context. Supported options "
"are 'all','log', and 'none'. If 'all' is specified, sensitive data "
"(sensitive configuration flags and row data) will be redacted from "
"the web UI as well as glog and error messages. If 'log' is specified, "
"sensitive data will only be redacted from glog and error messages. "
"If 'none' is specified, no redaction will occur.");
TAG_FLAG(redact, advanced);
TAG_FLAG(redact, evolving);
static bool ValidateRedact(const char* /*flagname*/, const string& value) {
kudu::g_should_redact = kudu::RedactContext::NONE;
// Flag value is case insensitive.
string redact_flags;
kudu::ToUpperCase(value, &redact_flags);
// 'all', 'none', and '' must be specified without any other option.
if (redact_flags == "ALL") {
kudu::g_should_redact = kudu::RedactContext::ALL;
return true;
}
if (redact_flags == "NONE" || redact_flags.empty()) {
return true;
}
for (const auto& t : strings::Split(redact_flags, ",", strings::SkipEmpty())) {
if (t == "LOG") {
kudu::g_should_redact = kudu::RedactContext::LOG;
} else if (t == "ALL" || t == "NONE") {
LOG(ERROR) << "Invalid redaction options: "
<< value << ", '" << t << "' must be specified by itself.";
return false;
} else {
LOG(ERROR) << "Invalid redaction context: " << t <<
". Available types are 'all', 'log', and 'none'.";
return false;
}
}
return true;
}
DEFINE_validator(redact, &ValidateRedact);
// Tag a bunch of the flags that we inherit from glog/gflags.
//------------------------------------------------------------
// GLog flags
//------------------------------------------------------------
// Most of these are considered stable. The ones related to email are
// marked unsafe because sending email inline from a server is a pretty
// bad idea.
DECLARE_string(alsologtoemail);
TAG_FLAG(alsologtoemail, hidden);
TAG_FLAG(alsologtoemail, unsafe);
// --alsologtostderr is deprecated in favor of --stderrthreshold
DECLARE_bool(alsologtostderr);
TAG_FLAG(alsologtostderr, hidden);
TAG_FLAG(alsologtostderr, runtime);
DECLARE_bool(colorlogtostderr);
TAG_FLAG(colorlogtostderr, stable);
TAG_FLAG(colorlogtostderr, runtime);
DECLARE_bool(drop_log_memory);
TAG_FLAG(drop_log_memory, advanced);
TAG_FLAG(drop_log_memory, runtime);
DECLARE_string(log_backtrace_at);
TAG_FLAG(log_backtrace_at, advanced);
DECLARE_string(log_dir);
TAG_FLAG(log_dir, stable);
DECLARE_string(log_link);
TAG_FLAG(log_link, stable);
TAG_FLAG(log_link, advanced);
DECLARE_bool(log_prefix);
TAG_FLAG(log_prefix, stable);
TAG_FLAG(log_prefix, advanced);
TAG_FLAG(log_prefix, runtime);
DECLARE_int32(logbuflevel);
TAG_FLAG(logbuflevel, advanced);
TAG_FLAG(logbuflevel, runtime);
DECLARE_int32(logbufsecs);
TAG_FLAG(logbufsecs, advanced);
TAG_FLAG(logbufsecs, runtime);
DECLARE_int32(logemaillevel);
TAG_FLAG(logemaillevel, hidden);
TAG_FLAG(logemaillevel, unsafe);
DECLARE_string(logmailer);
TAG_FLAG(logmailer, hidden);
DECLARE_bool(logtostderr);
TAG_FLAG(logtostderr, stable);
TAG_FLAG(logtostderr, runtime);
DECLARE_uint32(max_log_size);
TAG_FLAG(max_log_size, stable);
TAG_FLAG(max_log_size, runtime);
DECLARE_int32(minloglevel);
TAG_FLAG(minloglevel, stable);
TAG_FLAG(minloglevel, advanced);
TAG_FLAG(minloglevel, runtime);
DECLARE_int32(stderrthreshold);
TAG_FLAG(stderrthreshold, stable);
TAG_FLAG(stderrthreshold, advanced);
TAG_FLAG(stderrthreshold, runtime);
DECLARE_bool(stop_logging_if_full_disk);
TAG_FLAG(stop_logging_if_full_disk, stable);
TAG_FLAG(stop_logging_if_full_disk, advanced);
TAG_FLAG(stop_logging_if_full_disk, runtime);
DECLARE_int32(v);
TAG_FLAG(v, stable);
TAG_FLAG(v, advanced);
TAG_FLAG(v, runtime);
DECLARE_string(vmodule);
TAG_FLAG(vmodule, stable);
TAG_FLAG(vmodule, advanced);
DECLARE_bool(symbolize_stacktrace);
TAG_FLAG(symbolize_stacktrace, stable);
TAG_FLAG(symbolize_stacktrace, runtime);
TAG_FLAG(symbolize_stacktrace, advanced);
//------------------------------------------------------------
// GFlags flags
//------------------------------------------------------------
DECLARE_string(flagfile);
TAG_FLAG(flagfile, stable);
DECLARE_string(fromenv);
TAG_FLAG(fromenv, stable);
TAG_FLAG(fromenv, advanced);
DECLARE_string(tryfromenv);
TAG_FLAG(tryfromenv, stable);
TAG_FLAG(tryfromenv, advanced);
DECLARE_string(undefok);
TAG_FLAG(undefok, stable);
TAG_FLAG(undefok, advanced);
DECLARE_int32(tab_completion_columns);
TAG_FLAG(tab_completion_columns, stable);
TAG_FLAG(tab_completion_columns, hidden);
DECLARE_string(tab_completion_word);
TAG_FLAG(tab_completion_word, stable);
TAG_FLAG(tab_completion_word, hidden);
DECLARE_bool(help);
TAG_FLAG(help, stable);
DECLARE_bool(helpfull);
// We hide -helpfull because it's the same as -help for now.
TAG_FLAG(helpfull, stable);
TAG_FLAG(helpfull, hidden);
DECLARE_string(helpmatch);
TAG_FLAG(helpmatch, stable);
TAG_FLAG(helpmatch, advanced);
DECLARE_string(helpon);
TAG_FLAG(helpon, stable);
TAG_FLAG(helpon, advanced);
DECLARE_bool(helppackage);
TAG_FLAG(helppackage, stable);
TAG_FLAG(helppackage, advanced);
DECLARE_bool(helpshort);
TAG_FLAG(helpshort, stable);
TAG_FLAG(helpshort, advanced);
DECLARE_bool(helpxml);
TAG_FLAG(helpxml, stable);
TAG_FLAG(helpxml, advanced);
DECLARE_bool(version);
TAG_FLAG(version, stable);
//------------------------------------------------------------
// TCMalloc flags.
// These are tricky because tcmalloc doesn't use gflags. So we have to
// reach into its internal namespace.
//------------------------------------------------------------
#define TCM_NAMESPACE FLAG__namespace_do_not_use_directly_use_DECLARE_int64_instead
namespace TCM_NAMESPACE {
extern int64_t FLAGS_tcmalloc_sample_parameter;
} // namespace TCM_NAMESPACE
namespace kudu {
// After flags have been parsed, the umask value is filled in here.
uint32_t g_parsed_umask = -1;
namespace {
void AppendXMLTag(const char* tag, const string& txt, string* r) {
strings::SubstituteAndAppend(r, "<$0>$1</$0>", tag, EscapeForHtmlToString(txt));
}
static string DescribeOneFlagInXML(const CommandLineFlagInfo& flag) {
unordered_set<string> tags;
GetFlagTags(flag.name, &tags);
string r("<flag>");
AppendXMLTag("file", flag.filename, &r);
AppendXMLTag("name", flag.name, &r);
AppendXMLTag("meaning", flag.description, &r);
AppendXMLTag("default", flag.default_value, &r);
AppendXMLTag("current", flag.current_value, &r);
AppendXMLTag("type", flag.type, &r);
AppendXMLTag("tags", JoinStrings(tags, ","), &r);
r += "</flag>";
return r;
}
void DumpFlagsXML() {
vector<CommandLineFlagInfo> flags;
GetAllFlags(&flags);
cout << "<?xml version=\"1.0\"?>" << endl;
cout << "<AllFlags>" << endl;
cout << strings::Substitute(
"<program>$0</program>",
EscapeForHtmlToString(BaseName(google::ProgramInvocationShortName()))) << endl;
cout << strings::Substitute(
"<usage>$0</usage>",
EscapeForHtmlToString(google::ProgramUsage())) << endl;
for (const CommandLineFlagInfo& flag : flags) {
cout << DescribeOneFlagInXML(flag) << endl;
}
cout << "</AllFlags>" << endl;
}
// Check that, if any flags tagged with 'tag' have been specified to
// non-default values, that 'unlocked' is true. If so (i.e. if the
// flags have been appropriately unlocked), emits a warning message
// for each flag and returns false. Otherwise, emits an error message
// and returns true.
bool CheckFlagsAndWarn(const string& tag, bool unlocked) {
vector<CommandLineFlagInfo> flags;
GetAllFlags(&flags);
int use_count = 0;
for (const auto& f : flags) {
if (f.is_default) continue;
unordered_set<string> tags;
GetFlagTags(f.name, &tags);
if (!ContainsKey(tags, tag)) continue;
if (unlocked) {
LOG(WARNING) << "Enabled " << tag << " flag: --" << f.name << "=" << f.current_value;
} else {
LOG(ERROR) << "Flag --" << f.name << " is " << tag << " and unsupported.";
use_count++;
}
}
if (!unlocked && use_count > 0) {
LOG(ERROR) << use_count << " " << tag << " flag(s) in use.";
LOG(ERROR) << "Use --unlock_" << tag << "_flags to proceed at your own risk.";
return true;
}
return false;
}
// Check that any flags specified on the command line are allowed
// to be set. This ensures that, if the user is using any unsafe
// or experimental flags, they have explicitly unlocked them.
void CheckFlagsAllowed() {
bool should_exit = false;
should_exit |= CheckFlagsAndWarn("unsafe", FLAGS_unlock_unsafe_flags);
should_exit |= CheckFlagsAndWarn("experimental", FLAGS_unlock_experimental_flags);
if (should_exit) {
exit(1);
}
}
// Run 'late phase' custom validators: these can be run only when all flags are
// already parsed and individually validated.
void RunCustomValidators() {
const auto& validators(GetFlagValidators());
bool found_inconsistency = false;
for (const auto& e : validators) {
found_inconsistency |= !e.second();
}
if (found_inconsistency) {
LOG(ERROR) << "Detected inconsistency in command-line flags; exiting";
exit(1);
}
}
void SetUmask() {
// We already validated with a nice error message using the ValidateUmask
// FlagValidator above.
CHECK(safe_strtou32_base(FLAGS_umask.c_str(), &g_parsed_umask, 8));
uint32_t old_mask = umask(g_parsed_umask);
if (old_mask != g_parsed_umask) {
VLOG(2) << "Changed umask from " << StringPrintf("%03o", old_mask) << " to "
<< StringPrintf("%03o", g_parsed_umask);
}
}
} // anonymous namespace
// If --redact indicates, redact the flag tagged as 'sensitive'.
// Otherwise, return its value as-is. If EscapeMode is set to HTML,
// return HTML escaped string.
string CheckFlagAndRedact(const CommandLineFlagInfo& flag, EscapeMode mode) {
string ret_value;
unordered_set<string> tags;
GetFlagTags(flag.name, &tags);
if (ContainsKey(tags, "sensitive") && KUDU_SHOULD_REDACT()) {
ret_value = kRedactionMessage;
} else {
ret_value = flag.current_value;
}
if (mode == EscapeMode::HTML) {
ret_value = EscapeForHtmlToString(ret_value);
}
return ret_value;
}
int ParseCommandLineFlags(int* argc, char*** argv, bool remove_flags) {
// The logbufsecs default is 30 seconds which is a bit too long.
google::SetCommandLineOptionWithMode("logbufsecs", "5",
google::FlagSettingMode::SET_FLAGS_DEFAULT);
int ret = google::ParseCommandLineNonHelpFlags(argc, argv, remove_flags);
HandleCommonFlags();
ValidateFlags();
return ret;
}
void HandleCommonFlags() {
if (FLAGS_helpxml) {
DumpFlagsXML();
exit(1);
} else if (FLAGS_dump_metrics_json) {
MetricPrototypeRegistry::get()->WriteAsJson();
exit(0);
} else if (FLAGS_dump_metrics_xml) {
MetricPrototypeRegistry::get()->WriteAsXML();
exit(0);
} else if (FLAGS_version) {
cout << VersionInfo::GetAllVersionInfo() << endl;
exit(0);
}
google::HandleCommandLineHelpFlags();
if (FLAGS_disable_core_dumps) {
DisableCoreDumps();
}
SetUmask();
#ifdef TCMALLOC_ENABLED
if (FLAGS_heap_profile_path.empty()) {
FLAGS_heap_profile_path = strings::Substitute(
"/tmp/$0.$1", google::ProgramInvocationShortName(), getpid());
}
if (FLAGS_enable_process_lifetime_heap_profiling) {
HeapProfilerStart(FLAGS_heap_profile_path.c_str());
}
// Set the internal tcmalloc flag unless it was already set using the built-in
// environment-variable-based method. It doesn't appear that this is settable
// in any less hacky fashion.
if (!getenv("TCMALLOC_SAMPLE_PARAMETER")) {
TCM_NAMESPACE::FLAGS_tcmalloc_sample_parameter = FLAGS_heap_sample_every_n_bytes;
} else if (!google::GetCommandLineFlagInfoOrDie("heap_sample_every_n_bytes").is_default) {
LOG(ERROR) << "Heap sampling configured using both --heap-sample-every-n-bytes and "
<< "TCMALLOC_SAMPLE_PARAMETER. Ignoring command line flag.";
}
#endif
}
void ValidateFlags() {
CheckFlagsAllowed();
RunCustomValidators();
}
string CommandlineFlagsIntoString(EscapeMode mode) {
string ret_value;
vector<CommandLineFlagInfo> flags;
GetAllFlags(&flags);
for (const auto& f : flags) {
ret_value += "--";
if (mode == EscapeMode::HTML) {
ret_value += EscapeForHtmlToString(f.name);
} else if (mode == EscapeMode::NONE) {
ret_value += f.name;
}
ret_value += "=";
ret_value += CheckFlagAndRedact(f, mode);
ret_value += "\n";
}
return ret_value;
}
vector<CommandLineFlagInfo> GetNonDefaultFlagsHelper() {
vector<CommandLineFlagInfo> all_flags;
GetAllFlags(&all_flags);
vector<CommandLineFlagInfo> non_default_flags;
std::copy_if(all_flags.begin(), all_flags.end(), std::back_inserter(non_default_flags),
[] (const auto& flag) {
// In addition to checking that the flag has been rewritten with 'is_default',
// we also check that the value is different from the default value.
return !flag.is_default && flag.current_value != flag.default_value;
});
return non_default_flags;
}
string GetNonDefaultFlags() {
ostringstream args;
for (const auto& flag : GetNonDefaultFlagsHelper()) {
// Redact the flags tagged as sensitive, if redaction is enabled.
string flag_value = CheckFlagAndRedact(flag, EscapeMode::NONE);
args << "--" << flag.name << '=' << flag_value << "\n";
}
return args.str();
}
GFlagsMap GetNonDefaultFlagsMap() {
GFlagsMap flags_by_name;
for (auto& flag : GetNonDefaultFlagsHelper()) {
// Instead of "flags_by_name.emplace(flag.name, std::move(flag))" using
// following approach as order of evaluation of parameters could be different
// leading to unexpected value in "flag.name".
flags_by_name[flag.name] = std::move(flag);
}
return flags_by_name;
}
GFlagsMap GetFlagsMap() {
vector<CommandLineFlagInfo> default_flags;
GetAllFlags(&default_flags);
GFlagsMap flags_by_name;
for (auto& flag : default_flags) {
// See the note in GetNonDefaultFlagsMap() above.
flags_by_name[flag.name] = std::move(flag);
}
return flags_by_name;
}
Status ParseTriState(const char* flag_name, const std::string& flag_value,
TriStateFlag* tri_state) {
if (iequals(flag_value, "required")) {
*tri_state = TriStateFlag::REQUIRED;
} else if (iequals(flag_value, "optional")) {
*tri_state = TriStateFlag::OPTIONAL;
} else if (iequals(flag_value, "disabled")) {
*tri_state = TriStateFlag::DISABLED;
} else {
return Status::InvalidArgument(strings::Substitute(
"$0 flag must be one of 'required', 'optional', or 'disabled'",
flag_name));
}
return Status::OK();
}
// Get the value of an environment variable that has boolean semantics.
bool GetBooleanEnvironmentVariable(const char* env_var_name) {
const char* const e = getenv(env_var_name);
if ((e == nullptr) ||
(strlen(e) == 0) ||
(strcasecmp(e, "false") == 0) ||
(strcasecmp(e, "0") == 0) ||
(strcasecmp(e, "no") == 0)) {
return false;
}
if ((strcasecmp(e, "true") == 0) ||
(strcasecmp(e, "1") == 0) ||
(strcasecmp(e, "yes") == 0)) {
return true;
}
LOG(FATAL) << Substitute("$0: invalid value for environment variable $0",
e, env_var_name);
return false; // unreachable
}
} // namespace kudu