in src/mysqlsh/cmdline_shell.cc [949:1447]
shcore::Prompt_result Command_line_shell::do_prompt(bool is_password,
const char *text,
std::string *ret) {
char *tmp = nullptr;
const auto passwords_from_stdin = this->options().passwords_from_stdin;
char *(*get_response)(const char *) =
is_password ? (passwords_from_stdin ? shcore::mysh_get_stdin_password
: mysh_get_tty_password)
: Command_line_shell::readline;
shcore::on_leave_scope cleanup([&tmp, is_password]() {
if (!tmp) return;
if (is_password) {
// BUG#28915716: Cleans up the memory containing the password
shcore::clear_buffer(tmp, ::strlen(tmp));
}
free(std::exchange(tmp, nullptr));
});
shcore::Interrupt_handler second_handler([get_response]() {
#ifdef _WIN32
// this handler is not called if shell is running interactively
// this handler is called when shell is running interactively with
// --passwords-from-stdin option and a password prompt is canceled
// this handler is called when shell is running in non-interactive mode
// (i.e. launched by another process)
// cancel any pending I/O, if signal was sent using
// GenerateConsoleCtrlEvent() these are not going to be interrupted on its
// own
CancelIoEx(GetStdHandle(STD_INPUT_HANDLE), nullptr);
// mysh_get_tty_password() reads directly from the console, CancelIoEx()
// will not interrupt this, need to send CTRL-C
if (mysh_get_tty_password == get_response) {
#else // !_WIN32
// if signal is sent to us from another process and we're reading from
// terminal, we need to generate CTRL-C sequence to interrupt read loops
if (mysh_get_tty_password == get_response || is_stdin_a_tty()) {
#endif // !_WIN32
send_ctrl_c_to_terminal();
}
// always suppress further processing of the interrupt handlers, if a call
// to prompt*() throws in response to Prompt_result::Cancel, we want to
// make sure that this exception is properly propagated
return false;
});
shcore::Interrupt_handler first_handler([this]() {
// we need another Interrupt_handler, which is going to be executed first
// and returns true, to make sure that any interrupts generated by the
// handler registered first (and executed second) are suppressed
handle_interrupt();
return true;
});
_interrupted = false;
tmp = get_response(text);
#ifdef _WIN32
if (!tmp && !_interrupted) {
// If prompt returned no result but it was not interrupted, it means that
// either user pressed CTRL-D/CTRL-Z or there was a signal which
// interrupted read(), but was not yet delivered (signals are asynchronous
// on Windows) - this may happen i.e. if shell was started with
// --passwords-from-stdin and password prompt is interrupted with CTRL-C.
// Wait a bit here, if signal arrives, sleep is going to be interrupted.
// Worst case scenario: CTRL-D/CTRL-Z was pressed or signal arrived after
// flag was checked but before we went to sleep.
shcore::sleep_ms(100);
}
#endif // _WIN32
if (tmp && strcmp(tmp, CTRL_C_STR) == 0) _interrupted = true;
if (!tmp || _interrupted) {
*ret = "";
if (_interrupted)
return shcore::Prompt_result::Cancel;
else
return shcore::Prompt_result::CTRL_D;
}
*ret = tmp;
return shcore::Prompt_result::Ok;
}
std::string Command_line_shell::history_file(shcore::Shell_core::Mode mode) {
return shcore::path::join_path(
shcore::get_user_config_path(),
"history." + to_string(mode == shcore::Shell_core::Mode::None
? interactive_mode()
: mode));
}
void Command_line_shell::pre_command_loop() {
display_info(m_default_cluster, "cluster", "cluster");
display_info(m_default_clusterset, "clusterset", "clusterset");
// information about replicaset name is displayed by the dba
display_info(m_default_replicaset, "rs", "replicaset", false);
}
void Command_line_shell::command_loop() {
bool using_tty = is_stdin_a_tty();
if (_deferred_output && !_deferred_output->empty()) {
println(*_deferred_output);
_deferred_output.reset();
}
if (options().full_interactive && using_tty) {
std::string message;
auto session = _shell->get_dev_session();
message = " Use \\sql to switch to SQL mode and execute queries.";
switch (_shell->interactive_mode()) {
case shcore::Shell_core::Mode::SQL:
#ifdef HAVE_JS
message =
"Currently in SQL mode. Use \\js or \\py to switch the shell to "
"a scripting language.";
#else
message =
"Currently in SQL mode. Use \\py to switch the shell to python "
"scripting.";
#endif
break;
case shcore::Shell_core::Mode::JavaScript:
message = "Currently in JavaScript mode." + message;
break;
case shcore::Shell_core::Mode::Python:
message = "Currently in Python mode." + message;
break;
default:
break;
}
println(message);
}
m_current_session_uri = get_current_session_uri();
while (options().interactive) {
std::string cmd;
{
shcore::Interrupt_handler handler([this]() {
handle_interrupt();
return true;
});
if (using_tty) {
// Ensure prompt is in its own line, in case there was any output
// without a \n
if (m_prompt_requires_newline) {
mysqlsh::current_console()->raw_print(
"\n", mysqlsh::Output_stream::STDOUT, false);
}
m_set_pending_command = true;
char *tmp = Command_line_shell::readline(prompt().c_str());
const std::unique_ptr<char, decltype(&free)> tmp_deleter{tmp, free};
m_set_pending_command = false;
if (tmp && strcmp(tmp, CTRL_C_STR) != 0) {
cmd = tmp;
} else {
if (tmp) {
if (strcmp(tmp, CTRL_C_STR) == 0) _interrupted = true;
}
if (_interrupted) {
clear_input();
_interrupted = false;
continue;
}
break;
}
} else {
if (options().full_interactive) {
if (m_prompt_requires_newline) {
mysqlsh::current_console()->raw_print(
"\n", mysqlsh::Output_stream::STDOUT, false);
}
mysqlsh::current_console()->raw_print(
prompt(), mysqlsh::Output_stream::STDOUT, false);
}
if (!shcore::getline(std::cin, cmd)) {
if (_interrupted || !std::cin.eof()) {
_interrupted = false;
continue;
}
break;
}
}
if (options().full_interactive)
mysqlsh::current_console()->raw_print(
cmd + "\n", mysqlsh::Output_stream::STDOUT, false);
}
process_line(cmd);
reconnect_if_needed();
detect_session_change();
}
std::cout << "Bye!\n";
}
void Command_line_shell::print_banner() {
std::string welcome_msg("MySQL Shell ");
welcome_msg += MYSH_FULL_VERSION;
welcome_msg += "\n\n";
welcome_msg += "Copyright (c) 2016, " PACKAGE_YEAR
", Oracle and/or its affiliates.\n"
"Oracle is a registered trademark of Oracle Corporation "
"and/or its affiliates.\n"
"Other names may be trademarks of their respective owners.";
println(welcome_msg);
println();
println("Type '\\help' or '\\?' for help; '\\quit' to exit.");
}
void Command_line_shell::print_cmd_line_helper() {
// clang-format off
std::string help_msg("MySQL Shell ");
help_msg += MYSH_FULL_VERSION;
println(help_msg);
println("");
// Splitting line in two so the git hook does not complain
println("Copyright (c) 2016, " PACKAGE_YEAR ", "
"Oracle and/or its affiliates.");
println("Oracle is a registered trademark of Oracle Corporation and/or its affiliates.");
println("Other names may be trademarks of their respective owners.");
println("");
println("Usage: mysqlsh [OPTIONS] [URI]");
println(" mysqlsh [OPTIONS] [URI] -f <path> [<script-args>...]");
println(" mysqlsh [OPTIONS] [URI] --cluster|--replicaset");
println(" mysqlsh [OPTIONS] [URI] -- <object> <method> [<method-args>...]");
println(" mysqlsh [OPTIONS] [URI] --dba enableXProtocol");
println(" mysqlsh [OPTIONS] [URI] --import {<file>|-} [<collection>|<table> <column>]");
println("");
// clang-format on
std::vector<std::string> details = Shell_options(0, nullptr).get_details();
for (std::string line : details) println(" " + line);
// options handled by libmysqlclient (taken from my_default.cc)
println(
"\nThe following options may be given as the first argument:\n\
--print-defaults Print the program argument list and exit.\n\
--no-defaults Don't read default options from any option file,\n\
except for login file.\n\
--defaults-file=# Only read default options from the given file #.\n\
--defaults-extra-file=# Read this file after the global files are read.\n\
--defaults-group-suffix=#\n\
Also read groups with concat(group, suffix)\n\
--login-path=# Read this path from the login file.");
my_print_default_files("my");
{
std::string groups;
if (my_defaults_group_suffix) {
for (const auto *g : {"mysqlsh", "client"}) {
groups.append(" ").append(g).append(my_defaults_group_suffix);
}
}
println("The following groups are read: mysqlsh client" + groups);
}
println("");
println("Usage examples:");
println("$ mysqlsh --login-path=server1 --sql");
println("$ mysqlsh root@localhost/schema");
println("$ mysqlsh mysqlx://root@some.server:3307/world_x");
println("$ mysqlsh --uri root@localhost --py -f sample.py sample param");
println("$ mysqlsh root@targethost:33070 -s world_x -f sample.js");
println(
"$ mysqlsh -- util check-for-server-upgrade root@localhost "
"--output-format=JSON");
println("");
}
bool Command_line_shell::cmd_process_file(
const std::vector<std::string> ¶ms) {
pause_history(true);
shcore::on_leave_scope resume_history{[this]() { pause_history(false); }};
return Mysql_shell::cmd_process_file(params);
}
void Command_line_shell::handle_notification(
const std::string &name, const shcore::Object_bridge_ref & /* sender */,
shcore::Value::Map_type_ref data) {
if (name == "SN_STATEMENT_EXECUTED") {
const auto executed = shcore::str_strip(data->get_string("statement"));
auto mode = interactive_mode();
auto sql = executed;
if (shcore::str_beginswith(executed, "\\sql ") && executed.length() > 5) {
mode = shcore::Shell_core::Mode::SQL;
sql = executed.substr(5);
}
if (mode != shcore::Shell_core::Mode::SQL || sql_safe_for_logging(sql)) {
_history.add(executed);
if (shcore::Shell_core::Mode::SQL == mode) {
syslog(sql);
}
} else {
// add but delete after the next command and
// don't let it get saved to disk either
_history.add_temporary(executed);
}
if (m_previous_mode != shcore::Shell_core::Mode::None) {
save_state(m_previous_mode);
load_state();
m_previous_mode = shcore::Shell_core::Mode::None;
}
} else if (name == SN_SHELL_OPTION_CHANGED) {
const auto option = data->get_string("option");
if (SHCORE_HISTORY_MAX_SIZE == option) {
_history.set_limit(data->get_int("value"));
} else if (SHCORE_HISTIGNORE == option) {
set_sql_safe_for_logging(data->get_string("value"));
} else if (SHCORE_PAGER == option) {
const auto console = current_console();
console->print_info(get_pager_message(data->get_string("value")));
if (console->is_global_pager_enabled()) {
// if global pager is enabled, disable and re-enable it to use new
// pager
console->disable_global_pager();
console->enable_global_pager();
}
} else if (SHCORE_VERBOSE == option) {
const auto console = current_console();
console->set_verbose(data->get_int("value"));
} else if (SHCORE_HISTORY_SQL_SYSLOG == option) {
m_syslog.enable(data->get_bool("value"));
}
}
}
std::string Command_line_shell::get_current_session_uri() const {
if (const auto session = _shell->get_dev_session(); session) {
if (const auto core_session = session->get_core_session();
core_session && core_session->is_open())
return core_session->get_connection_options().as_uri();
}
return {};
}
void Command_line_shell::detect_session_change() {
const auto session_uri = get_current_session_uri();
if (session_uri != m_current_session_uri) {
m_current_session_uri = session_uri;
request_prompt_variables_update(true);
}
}
void Command_line_shell::set_next_input(const std::string &input) {
if (!input.empty()) {
auto lines = shcore::str_split(input, "\n");
// trim whitespace from the right side of the lines
std::transform(
lines.begin(), lines.end(), lines.begin(),
[](const std::string &line) { return shcore::str_rstrip(line); });
// remove consecutive empty lines at the end
while (lines.size() > 1 && lines.back().empty() &&
std::prev(lines.end(), 2)->empty()) {
lines.pop_back();
}
// if there are only two lines, and the last one is empty, remove it as
// well
if (lines.size() == 2 && lines.back().empty()) {
lines.pop_back();
}
// the last line goes to the linenoise and can be edited
const auto last_line = lines.back();
lines.pop_back();
const auto console = current_console();
// emulate user writing the input line by line
_input_buffer = "";
_input_mode = shcore::Input_state::Ok;
for (const auto &line : lines) {
console->raw_print(prompt() + line + "\n", mysqlsh::Output_stream::STDOUT,
false);
_input_buffer += line + "\n";
_input_mode = shcore::Input_state::ContinuedBlock;
}
// feed the last line to the linenoise
if (!last_line.empty()) {
linenoisePreloadBuffer(last_line.c_str());
}
}
}
bool Command_line_shell::set_pending_command(const Pending_command &command) {
if (m_set_pending_command) {
m_pending_command = command;
}
return m_set_pending_command;
}
void Command_line_shell::process_line(const std::string &line) {
if (m_pending_command) {
m_pending_command(line);
m_pending_command = nullptr;
} else {
auto mode = _shell->interactive_mode();
const char *mode_name = nullptr;
switch (mode) {
case shcore::IShell_core::Mode::None:
mode_name = "none";
break;
case shcore::IShell_core::Mode::SQL:
mode_name = "sql";
break;
case shcore::IShell_core::Mode::JavaScript:
mode_name = "js";
break;
case shcore::IShell_core::Mode::Python:
mode_name = "py";
break;
}
shcore::Log_sql_guard g(mode_name);
Mysql_shell::process_line(line);
}
}
void Command_line_shell::syslog(const std::string &statement) {
// log SQL statements and \source commands
if (m_syslog.active() &&
('\\' != statement[0] || shcore::str_beginswith(statement, "\\source ") ||
shcore::str_beginswith(statement, "\\. "))) {
m_syslog.log(shcore::syslog::Level::INFO, syslog_format(statement));
}
}
std::string Command_line_shell::syslog_format(const std::string &statement) {
const std::size_t max_log_length = 1024;
const auto variables = prompt_variables();
std::vector<std::string> data;
const auto add_data = [&data](const char *key, const std::string &value) {
data.emplace_back(shcore::make_kvp(key, value.empty() ? "--" : value));
};
const auto add_data_from_prompt =
[&variables, &add_data](const char *key, const std::string &var) {
const auto value = variables->find(var);
add_data(key, variables->end() != value ? value->second : "");
};
add_data_from_prompt("SYSTEM_USER", "system_user");
add_data_from_prompt("MYSQL_USER", "user");
add_data_from_prompt("CONNECTION_ID", "connection_id");
add_data_from_prompt("DB_SERVER", "host");
add_data_from_prompt("DB", "schema");
add_data("QUERY", statement);
return shcore::truncate(shcore::str_join(data, " "), max_log_length);
}
void Command_line_shell::pause_history(bool flag) {
_history.pause(flag);
m_syslog.pause(flag);
}
std::vector<std::string> Command_line_shell::auto_complete(
const std::string &line, size_t *completion_offset) {
return completer()->complete(shell_context()->interactive_mode(),
_input_buffer, line, completion_offset);
}
} // namespace mysqlsh