blob: 5babad1a26c3d44c00d7a77131e8bd65766c543b [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/setting_schema.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/console/command.h"
#include "src/developer/debug/zxdb/console/command_utils.h"
#include "src/developer/debug/zxdb/console/console.h"
#include "src/developer/debug/zxdb/console/format_settings.h"
#include "src/developer/debug/zxdb/console/output_buffer.h"
#include "src/developer/debug/zxdb/console/verbs.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
// get -------------------------------------------------------------------------
const char kGetShortHelp[] = "get: Get a setting(s) value(s).";
const char kGetHelp[] =
R"(get (--system|-s) [setting_name]
Gets the value of all the settings or the detailed description of one.
Arguments
--system|-s
Refer to the system context instead of the current one.
See below for more details.
[setting_name]
Filter for one setting. Will show detailed information, such as a
description and more easily copyable values.
Setting Types
Settings have a particular type: bool, int, string or list (of strings).
The type is set beforehand and cannot change. Getting the detailed information
of a setting will show the type of setting it is, though normally it is easy
to tell from the list of values.
Contexts
Within zxdb, there is the concept of the current context. This means that at
any given moment, there is a current process, thread and breakpoint. This also
applies when handling settings. By default, get will query the settings for
the current thread. If you want to query the settings for the current target
or system, you need to qualify at such.
There are currently 3 contexts where settings live:
- System
- Target (roughly equivalent to a Process, but remains even when not running).
- Thread
In order to query a particular context, you need to qualify it:
get foo
Unqualified. Queries the current thread settings.
p 1 get foo
Qualified. Queries the selected process settings.
p 3 t 2 get foo
Qualified. Queries the selectedthread settings.
For system settings, we need to override the context, so we need to explicitly
ask for it. Any explicit context will be ignored in this case:
get -s foo
Retrieves the value of "foo" for the system.
Schemas
Each setting level (thread, target, etc.) has an associated schema.
This defines what settings are available for it and the default values.
Initially, all objects default to their schemas, but values can be overridden
for individual objects.
Instance Overrides
Values overriding means that you can modify behaviour for a particular object.
If a setting has not been overridden for that object, it will fallback to the
settings of parent object. The fallback order is as follows:
Thread -> Process -> System -> Schema Default
This means that if a thread has not overridden a value, it will check if the
owning process has overridden it, then is the system has overridden it. If
there are none, it will get the default value of the thread schema.
For example, if t1 has overridden "foo" but t2 has not:
t 1 foo
Gets the value of "foo" for t1.
t 2 foo
Queries the owning process for foo. If that process doesn't have it (no
override), it will query the system. If there is no override, it will
fallback to the schema default.
NOTE:
Not all settings are present in all schemas, as some settings only make sense
in a particular context. If the thread schema holds a setting "foo" which the
process schema does not define, asking for "foo" on a thread will only default
to the schema default, as the concept of "foo" does not makes sense to a
process.
Examples
get
List the global settings for the System context.
p get foo
Get the value of foo for the global Process context.
p 2 t1 get
List the values of settings for t1 of p2.
This will list all the settings within the Thread schema, highlighting
which ones are overridden.
get -s
List the values of settings at the system level.
)";
constexpr int kGetSystemSwitch = 0;
Err SettingToOutput(const Command& cmd, const std::string& key, ConsoleContext* context,
OutputBuffer* out) {
// We search in the following order: Thread -> Target -> Job -> System.
if (Thread* thread = cmd.thread()) {
Setting setting = thread->settings().GetSetting(key);
if (!setting.value.is_null()) {
*out = FormatSetting(setting);
return Err();
}
}
if (Target* target = cmd.target()) {
Setting setting = target->settings().GetSetting(key);
if (!setting.value.is_null()) {
*out = FormatSetting(setting);
return Err();
}
}
if (JobContext* job_context = cmd.job_context()) {
Setting setting = job_context->settings().GetSetting(key);
if (!setting.value.is_null()) {
*out = FormatSetting(setting);
return Err();
}
}
Setting setting = context->session()->system().settings().GetSetting(key);
if (!setting.value.is_null()) {
*out = FormatSetting(setting);
return Err();
}
return Err("Could not find setting %s", key.c_str());
}
Err CompleteSettingsToOutput(const Command& cmd, ConsoleContext* context, OutputBuffer* out) {
// Output in the following order: System -> Job -> Target -> Thread
out->Append(OutputBuffer(Syntax::kHeading, "Global\n"));
out->Append(FormatSettingStore(context->session()->system().settings()));
out->Append("\n");
if (JobContext* job = cmd.job_context(); job && !job->settings().schema()->empty()) {
auto title = fxl::StringPrintf("Job %d\n", context->IdForJobContext(job));
out->Append(OutputBuffer(Syntax::kHeading, std::move(title)));
out->Append(FormatSettingStore(job->settings()));
out->Append("\n");
}
if (Target* target = cmd.target(); target && !target->settings().schema()->empty()) {
auto title = fxl::StringPrintf("Target %d\n", context->IdForTarget(target));
out->Append(OutputBuffer(Syntax::kHeading, std::move(title)));
out->Append(FormatSettingStore(target->settings()));
out->Append("\n");
}
if (Thread* thread = cmd.thread(); thread && !thread->settings().schema()->empty()) {
auto title = fxl::StringPrintf("Thread %d\n", context->IdForThread(thread));
out->Append(OutputBuffer(Syntax::kHeading, std::move(title)));
out->Append(FormatSettingStore(thread->settings()));
out->Append("\n");
}
return Err();
}
Err DoGet(ConsoleContext* context, const Command& cmd) {
std::string setting_name;
if (!cmd.args().empty()) {
if (cmd.args().size() > 1)
return Err("Expected only one setting name");
setting_name = cmd.args()[0];
}
Err err;
OutputBuffer out;
if (!setting_name.empty()) {
err = SettingToOutput(cmd, setting_name, context, &out);
} else {
err = CompleteSettingsToOutput(cmd, context, &out);
}
if (err.has_error())
return err;
Console::get()->Output(out);
return Err();
}
// Set -------------------------------------------------------------------------
constexpr int kSetSystemSwitch = 0;
const char kSetShortHelp[] = "set: Set a setting value.";
const char kSetHelp[] =
R"(set <setting_name> <value>
Sets the value of a setting.
Arguments
<setting_name>
The setting that will modified. Must match exactly.
<value>
The value to set. Keep in mind that settings have different types, so the
value will be validated. Read more below.
Contexts, Schemas and Instance Overrides
Settings have a hierarchical system of contexts where settings are defined.
When setting a value, if it is not qualified, it will be set the setting at
the highest level it can, in order to make it as general as possible.
In most cases these higher level will be system-wide, to change behavior to
the whole system, that can be overriden per-process or per-thread. Sometimes
though, the setting only makes sense on a per-object basis (eg. new process
filters for jobs). In this case, the unqualified set will work on the current
object in the context.
In order to override a setting at a job, target or thread level, the setting
command has to be explicitly qualified. This works for both avoiding setting
the value at a global context or to set the value for an object other than
the current one. See examples below.
There is detailed information on contexts and schemas in "help get".
Setting Types
Settings have a particular type: bool, int, string or list (of strings).
The type is set beforehand and cannot change. Getting the detailed information
of a setting will show the type of setting it is, though normally it is easy
to tell from the list of valued.
The valid inputs for each type are:
- bool: "0", "false" -> false
"1", "true" -> true
- int: Any string convertible to integer (think std::atoi).
- string: Any one-word string. Working on getting multi-word strings.
- list: List uses a representation of colon (:) separated values. While
showing the list value uses bullet points, setting it requires the
colon-separated representation. Running "get <setting_name>" will give
the current "list setting value" for a list setting, which can be
copy-pasted for easier editing. See example for a demostration.
Examples
[zxdb] set boolean_setting true
Set boolean_setting system-wide:
true
[zxdb] pr set int_setting 1024
Set int_setting for process 2:
1024
[zxdb] p 3 t 2 set string_setting somesuperlongstring
Set setting for thread 2 of process 3:
somesuperlongstring
[zxdb] get foo
...
• first
• second
• third
...
Set value: first:second:third
[zxdb] set foo first:second:third:fourth
Set foo for job 3:
• first
• second
• third
• fourth
NOTE: In the last case, even though the setting was not qualified, it was
set at the job level. This is because this is a job-specific setting
that doesn't make sense system-wide, but rather only per job.
)";
// Struct to represents all the context needed to correctly reason about the
// set command.
struct SetContext {
enum class Level {
kGlobal,
kJob,
kTarget,
kThread,
};
SettingStore* store = nullptr;
JobContext* job_context = nullptr;
Target* target = nullptr;
Thread* thread = nullptr;
Setting setting; // What kind of setting this is.
// At what level the setting was applied.
Level level = Level::kGlobal;
// What kind of operation this is.
AssignType assign_type = AssignType::kAssign;
// On append, it is the elements added.
// On remove, it is the elements removed.
std::vector<std::string> elements_changed;
};
Err SetBool(SettingStore* store, const std::string& setting_name, const std::string& value) {
if (value == "0" || value == "false") {
store->SetBool(setting_name, false);
} else if (value == "1" || value == "true") {
store->SetBool(setting_name, true);
} else {
return Err("%s expects a boolean. See \"help set\" for valid values.", setting_name.data());
}
return Err();
}
Err SetInt(SettingStore* store, const std::string& setting_name, const std::string& value) {
int out;
Err err = StringToInt(value, &out);
if (err.has_error()) {
return Err("%s expects a valid int: %s", setting_name.data(), err.msg().data());
}
return store->SetInt(setting_name, out);
}
Err SetList(const SetContext& context, const std::vector<std::string>& elements_to_set,
SettingStore* store, std::vector<std::string>* elements_changed) {
if (context.assign_type == AssignType::kAssign)
return store->SetList(context.setting.info.name, elements_to_set);
if (context.assign_type == AssignType::kAppend) {
auto list = store->GetList(context.setting.info.name);
list.insert(list.end(), elements_to_set.begin(), elements_to_set.end());
*elements_changed = elements_to_set;
return store->SetList(context.setting.info.name, list);
}
if (context.assign_type == AssignType::kRemove) {
// We search for the elements to remove.
auto list = store->GetList(context.setting.info.name);
std::vector<std::string> list_after_remove;
for (auto& elem : list) {
// If the element to change is within the list, means that we remove it.
auto it = std::find(elements_to_set.begin(), elements_to_set.end(), elem);
if (it == elements_to_set.end()) {
list_after_remove.push_back(elem);
} else {
elements_changed->push_back(elem);
}
}
// If none, were removed, we error so that the user can check why.
if (list.size() == list_after_remove.size())
return Err("Could not find any elements to remove.");
return store->SetList(context.setting.info.name, list_after_remove);
}
FXL_NOTREACHED();
return Err();
}
// Will run the sets against the correct SettingStore:
// |context| represents the required context needed to reason about the command.
// |elements_changed| are all the values that changed. This is used afterwards
// for user feedback.
// |out| is the resultant setting, which is used for user feedback.
Err SetSetting(const SetContext& context, const std::vector<std::string>& elements_to_set,
SettingStore* store, std::vector<std::string>* elements_changed, Setting* out) {
Err err;
if (context.assign_type != AssignType::kAssign && !context.setting.value.is_list())
return Err("Appending/removing only works for list options.");
switch (context.setting.value.type) {
case SettingType::kBoolean:
err = SetBool(store, context.setting.info.name, elements_to_set[0]);
break;
case SettingType::kInteger:
err = SetInt(store, context.setting.info.name, elements_to_set[0]);
break;
case SettingType::kString:
err = store->SetString(context.setting.info.name, elements_to_set[0]);
break;
case SettingType::kList:
err = SetList(context, elements_to_set, store, elements_changed);
break;
case SettingType::kNull:
return Err("Unknown type for setting %s. Please file a bug with repro.",
context.setting.info.name.data());
}
if (!err.ok())
return err;
*out = store->GetSetting(context.setting.info.name);
return Err();
}
OutputBuffer FormatSetFeedback(ConsoleContext* context, const SetContext& set_context) {
std::string verb;
switch (set_context.assign_type) {
case AssignType::kAssign:
verb = "Set value(s)";
break;
case AssignType::kAppend:
verb = "Added value(s)";
break;
case AssignType::kRemove:
verb = "Removed the following value(s)";
break;
}
FXL_DCHECK(!verb.empty());
std::string message;
switch (set_context.level) {
case SetContext::Level::kGlobal:
message = fxl::StringPrintf("%s system wide:\n", verb.data());
break;
case SetContext::Level::kJob: {
int job_id = context->IdForJobContext(set_context.job_context);
message = fxl::StringPrintf("%s for job %d:\n", verb.data(), job_id);
break;
}
case SetContext::Level::kTarget: {
int target_id = context->IdForTarget(set_context.target);
message = fxl::StringPrintf("%s for process %d:\n", verb.data(), target_id);
break;
}
case SetContext::Level::kThread: {
int target_id = context->IdForTarget(set_context.target);
int thread_id = context->IdForThread(set_context.thread);
message =
fxl::StringPrintf("%s for thread %d of process %d:\n", verb.data(), thread_id, target_id);
break;
}
default:
FXL_NOTREACHED() << "Should not receive a default setting.";
}
return OutputBuffer(std::move(message));
}
Err GetSetContext(const Command& cmd, const std::string& setting_name, SetContext* out) {
SettingStore* store = nullptr;
JobContext* job_context = cmd.job_context();
Target* target = cmd.target();
Thread* thread = cmd.thread();
if (!target)
return Err("No target found. Please file a bug with a repro.");
// If the user qualified the query, it means that we want to query *that*
// specific SettingStore, so we search for it. We search from more specific
// to less specific.
if (cmd.HasNoun(Noun::kThread)) {
store = thread ? &thread->settings() : nullptr;
out->level = SetContext::Level::kThread;
} else if (cmd.HasNoun(Noun::kProcess)) {
store = &target->settings();
out->level = SetContext::Level::kTarget;
} else if (cmd.HasNoun(Noun::kJob)) {
store = job_context ? &job_context->settings() : nullptr;
out->level = SetContext::Level::kJob;
}
if (!store) {
// We didn't found an explicit specified store, so we lookup in the current
// context. We look from less specific to more specific in terms of stores,
// so that the setting will be set for a biggest setting as it can.
// NOTE: Some settings are replicated upwards, even to the system level,
// as they make sense (eg. pause on attach). But others only make
// sense locally (eg. job filters).
System& system = target->session()->system();
if (system.settings().schema()->HasSetting(setting_name)) {
store = &system.settings();
out->level = SetContext::Level::kGlobal;
} else if (job_context && job_context->settings().schema()->HasSetting(setting_name)) {
store = &job_context->settings();
out->level = SetContext::Level::kJob;
} else if (target->settings().schema()->HasSetting(setting_name)) {
store = &target->settings();
out->level = SetContext::Level::kTarget;
} else if (thread && thread->settings().schema()->HasSetting(setting_name)) {
store = &thread->settings();
out->level = SetContext::Level::kThread;
}
}
if (!store || !store->schema()->HasSetting(setting_name))
return Err("Could not find setting \"%s\".", setting_name.data());
out->job_context = job_context;
out->target = target;
out->thread = thread;
out->store = store;
out->setting = store->schema()->GetSetting(setting_name).setting;
return Err();
}
Err DoSet(ConsoleContext* context, const Command& cmd) {
if (cmd.args().size() < 2)
return Err("Wrong amount of Arguments. See \"help set\".");
// Expected format is <option_name> [(=|+=|-=)] <value> [<value> ...]
Err err;
const std::string& setting_name = cmd.args()[0];
// Manually warn on this legacy setting name. This code can be removed after ~Aug 1, 2019.
if (setting_name == "filters") {
// Try to write the setting name the user typed. We don't bother handling all of the syntax if
// they did something more complex.
std::string setting_content = "<my_process>";
if (cmd.args().size() == 2)
setting_content = cmd.args()[1];
OutputBuffer out;
out.Append(Syntax::kError, "========================================\n");
out.Append(Syntax::kHeading, "The process filter interface has changed\n");
out.Append(Syntax::kError, "========================================\n");
out.Append(
fxl::StringPrintf("\n"
"The old way:\n"
"\n"
" set filters %s\n"
"\n"
"has now changed to\n"
"\n",
setting_content.c_str()));
out.Append(Syntax::kHeading, fxl::StringPrintf(" attach %s\n", setting_content.c_str()));
out.Append(
"\n"
"The semantics have not changed (it will attach to processes launched in the\n"
"future with that name). To see the current filters, type \"filter\" by itself.");
Console::get()->Output(out);
return Err();
}
// See where this setting would be stored.
SetContext set_context;
err = GetSetContext(cmd, setting_name, &set_context);
if (err.has_error())
return err;
// See what kind of assignment this is (whether it has =|+=|-=).
AssignType assign_type;
std::vector<std::string> elements_to_set;
err = SetElementsToAdd(cmd.args(), &assign_type, &elements_to_set);
if (err.has_error())
return err;
set_context.assign_type = assign_type;
// Validate that the operations makes sense.
if (assign_type != AssignType::kAssign && !set_context.setting.value.is_list())
return Err("List assignment (+=, -=) used on a non-list option.");
if (elements_to_set.size() > 1u && !set_context.setting.value.is_list())
return Err("Multiple values on a non-list option.");
Setting out; // Used for showing the new value.
err = SetSetting(set_context, elements_to_set, set_context.store, &set_context.elements_changed,
&out);
if (!err.ok())
return err;
// We output the new value.
Console::get()->Output(FormatSetFeedback(context, set_context));
// For removed values, we show which ones were removed.
if (set_context.assign_type != AssignType::kRemove) {
Console::get()->Output(FormatSettingShort(out));
} else {
Setting setting;
setting.value = SettingValue(set_context.elements_changed);
Console::get()->Output(FormatSettingShort(std::move(setting)));
}
return Err();
}
} // namespace
void AppendSettingsVerbs(std::map<Verb, VerbRecord>* verbs) {
// get.
SwitchRecord get_system(kGetSystemSwitch, false, "system", 's');
VerbRecord get(&DoGet, {"get"}, kGetShortHelp, kGetHelp, CommandGroup::kGeneral);
get.switches.push_back(std::move(get_system));
(*verbs)[Verb::kGet] = std::move(get);
// set.
SwitchRecord set_system(kSetSystemSwitch, false, "system", 's');
VerbRecord set(&DoSet, {"set"}, kSetShortHelp, kSetHelp, CommandGroup::kGeneral);
set.switches.push_back(std::move(set_system));
(*verbs)[Verb::kSet] = std::move(set);
}
} // namespace zxdb