mysqlshdk/libs/utils/utils_general.cc (992 lines of code) (raw):
/*
* Copyright (c) 2014, 2024, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
* as published by the Free Software Foundation.
*
* This program is designed to work with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms,
* as designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have either included with
* the program or referenced in the documentation.
*
* 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 General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "mysqlshdk/libs/utils/utils_general.h"
#include <string_view>
#include <utility>
#include "mysqlshdk/libs/textui/textui.h"
#include "include/mysh_config.h"
#include "my_config.h"
#ifdef WIN32
#include <Lmcons.h>
#else
#include <unistd.h>
#ifdef HAVE_GETPWUID_R
#include <pwd.h>
#endif
#if defined HAVE_EXPLICIT_BZERO
#if defined HAVE_BSD_STRING_H
#include <bsd/string.h>
#else
#include <strings.h>
#endif
#elif defined HAVE_MEMSET_S
#include <string.h>
extern "C" {
errno_t memset_s(void *__s, rsize_t __smax, int __c, rsize_t __n);
}
#endif
#endif
#include <cctype>
#include <cstdio>
#include <ctime>
#include <locale>
#include "mysqlshdk/include/shellcore/interrupt_handler.h"
#include "mysqlshdk/libs/db/connection_options.h"
#include "mysqlshdk/libs/db/uri_parser.h"
#include "mysqlshdk/libs/utils/utils_file.h"
#include "mysqlshdk/libs/utils/utils_lexing.h"
#include "mysqlshdk/libs/utils/utils_path.h"
#include "mysqlshdk/libs/utils/utils_sqlstring.h"
#include "mysqlshdk/libs/utils/utils_string.h"
#include "shellcore/utils_help.h"
#include <mysql_version.h>
namespace shcore {
bool is_valid_identifier(const std::string &name) {
bool ret_val = false;
if (!name.empty()) {
std::locale locale;
ret_val = std::isalpha(name[0], locale) || name[0] == '_';
size_t index = 1;
while (ret_val && index < name.size()) {
ret_val = std::isalnum(name[index], locale) || name[index] == '_';
index++;
}
}
return ret_val;
}
std::string strip_password(const std::string &connstring) {
std::string remaining = connstring;
std::string password;
std::string scheme;
std::string::size_type p;
p = remaining.find("://");
if (p != std::string::npos) {
scheme = remaining.substr(0, p + 3);
remaining = remaining.substr(p + 3);
}
std::string s = remaining;
p = remaining.find('/');
if (p != std::string::npos) {
s = remaining.substr(0, p);
}
p = s.rfind('@');
std::string user_part;
if (p != std::string::npos) {
user_part = s.substr(0, p);
}
if ((p = user_part.find(':')) != std::string::npos) {
password = user_part.substr(p + 1);
std::string uri_stripped = remaining;
std::string::size_type i = uri_stripped.find(":" + password);
if (i != std::string::npos) uri_stripped.erase(i, password.length() + 1);
return scheme + uri_stripped;
}
// no password to strip, return original one
return connstring;
}
/*std::string strip_ssl_args(const std::string &connstring) {
std::string result = connstring;
std::string::size_type pos;
if ((pos = result.find("ssl_ca=")) != std::string::npos) {
std::string::size_type pos2 = result.find("&");
result = result.replace(pos, (pos2 == std::string::npos) ? std::string::npos
: pos2 - pos + 1, "");
}
if ((pos = result.find("ssl_cert=")) != std::string::npos) {
std::string::size_type pos2 = result.find("&");
result = result.replace(pos, (pos2 == std::string::npos) ? std::string::npos
: pos2 - pos + 1, "");
}
if ((pos = result.find("ssl_key=")) != std::string::npos) {
std::string::size_type pos2 = result.find("&");
result = result.replace(pos, (pos2 == std::string::npos) ? std::string::npos
: pos2 - pos + 1, "");
}
if (result.at(result.size() - 1) == '?') {
result.resize(result.size() - 1);
}
return result;
}*/
char *mysh_get_stdin_password(const char *prompt) {
if (prompt) {
fputs(prompt, stdout);
fflush(stdout);
}
char buffer[128];
if (fgets(buffer, sizeof(buffer), stdin)) {
char *p = strchr(buffer, '\r');
if (p) *p = 0;
p = strchr(buffer, '\n');
if (p) *p = 0;
return strdup(buffer);
}
return NULL;
}
// Builds a connection data dictionary using the URI
mysqlshdk::db::Connection_options get_connection_options(const std::string &uri,
bool set_defaults) {
mysqlshdk::db::Connection_options connection_options(uri);
if (set_defaults) connection_options.set_default_data();
return connection_options;
}
mysqlshdk::ssh::Ssh_connection_options get_ssh_connection_options(
const std::string &uri, bool set_defaults, const std::string &config_path) {
mysqlshdk::ssh::Ssh_connection_options config(uri);
if (!config_path.empty()) {
config.set_config_file(config_path);
}
if (set_defaults) config.set_default_data();
return config;
}
std::string get_system_user() {
std::string ret_val{"UNKNOWN_USER"};
#ifdef _WIN32
wchar_t username[UNLEN + 1] = {};
DWORD username_len = UNLEN + 1;
if (GetUserNameW(username, &username_len) != 0) {
ret_val = shcore::wide_to_utf8(username, username_len - 1);
}
#else
if (geteuid() == 0) {
ret_val = "root"; /* allow use of surun */
} else {
#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETLOGIN_R)
auto name_size = sysconf(_SC_LOGIN_NAME_MAX);
char *name = reinterpret_cast<char *>(malloc(name_size));
if (!getlogin_r(name, name_size)) {
ret_val.assign(name);
} else {
auto buffer_size = sysconf(_SC_GETPW_R_SIZE_MAX);
char *buffer = reinterpret_cast<char *>(malloc(buffer_size));
struct passwd pwd;
struct passwd *res;
if (!getpwuid_r(geteuid(), &pwd, buffer, buffer_size, &res) && res) {
ret_val.assign(pwd.pw_name);
} else {
char *str;
if ((str = getenv("USER")) || (str = getenv("LOGNAME")) ||
(str = getenv("LOGIN"))) {
ret_val.assign(str);
} else {
ret_val = "UNKNOWN_USER";
}
}
free(buffer);
}
free(name);
#elif HAVE_CUSERID
char username[L_cuserid];
if (cuserid(username)) ret_val.assign(username);
#endif
}
#endif
return ret_val;
}
std::string errno_to_string(int err) {
if (!err) return std::string();
#ifdef _WIN32
#define strerror_r(E, B, S) strerror_s(B, S, E)
#endif
#if defined(_WIN32) || defined(__sun) || defined(__APPLE__) || \
((_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && \
!_GNU_SOURCE) // NOLINT
char buf[256];
if (!strerror_r(err, buf, sizeof(buf))) return std::string(buf);
return std::string();
#else
char buf[256];
return strerror_r(err, buf, sizeof(buf));
#endif
}
std::vector<std::string> split_string(std::string_view input,
std::string_view separator,
bool compress) {
std::vector<std::string> ret_val;
size_t index = 0, new_find = 0;
while (new_find != std::string_view::npos) {
new_find = input.find(separator, index);
if (new_find != std::string_view::npos) {
// When compress is enabled, consecutive separators
// do not generate new elements
if (new_find > index || !compress || new_find == 0)
ret_val.emplace_back(input.substr(index, new_find - index));
index = new_find + separator.length();
} else {
ret_val.emplace_back(input.substr(index));
}
}
return ret_val;
}
/**
* Splits string based on each of the individual characters of the separator
* string
*
* @param input The string to be split
* @param separator_chars String containing characters wherein the input string
* is split on any of the characters
* @param compress Boolean value which when true ensures consecutive separators
* do not generate new elements in the split
*
* @returns vector of splitted strings
*/
std::vector<std::string> split_string_chars(std::string_view input,
std::string_view separator_chars,
bool compress) {
std::vector<std::string> ret_val;
size_t index = 0, new_find = 0;
while (new_find != std::string_view::npos) {
new_find = input.find_first_of(separator_chars, index);
if (new_find != std::string_view::npos) {
// When compress is enabled, consecutive separators
// do not generate new elements
if (new_find > index || !compress || new_find == 0)
ret_val.emplace_back(input.substr(index, new_find - index));
index = new_find + 1;
} else {
ret_val.emplace_back(input.substr(index));
}
}
return ret_val;
}
// Retrieves a member name on a specific NamingStyle
// NOTE: Assumption is given that everything is created using a lowerUpperCase
// naming style
// Which is the default to be used on C++ and JS
std::string get_member_name(std::string_view name, shcore::NamingStyle style) {
switch (style) {
// This is the default style, input is returned without modifications
case shcore::LowerCamelCase:
return std::string{name};
case shcore::LowerCaseUnderscores: {
// Uppercase letters will be converted to underscore+lowercase letter
// except in two situations:
// - When it is the first letter
// - When an underscore is already before the uppercase letter
std::string new_name;
new_name.reserve(name.size());
bool skip_underscore = true;
for (auto character : name) {
if (character >= 65 && character <= 90) {
if (!skip_underscore)
new_name.append(1, '_');
else
skip_underscore = false;
new_name.append(1, character + 32);
} else {
// if character is '_'
skip_underscore = character == 95;
new_name.append(1, character);
}
}
return new_name;
}
case Constants: {
std::string new_name{name};
for (auto &character : new_name) {
if (character >= 97 && character <= 122) character -= 32;
}
return new_name;
}
}
return {};
}
/** Convert string from under_score/dash naming convention to camelCase
As a special case, if string is longer than 2 characters and
all characters are uppercase, conversion will be skipped.
*/
std::string to_camel_case(const std::string &name) {
std::string new_name;
bool upper_next = false;
size_t upper_count = 0;
for (auto ch : name) {
if (isupper(ch)) upper_count++;
if (ch == '_' || ch == '-') {
upper_count++;
upper_next = true;
} else if (upper_next) {
upper_next = false;
new_name.push_back(toupper(ch));
} else {
new_name.push_back(ch);
}
}
if (upper_count == name.length()) return name;
return new_name;
}
/** Convert string from camcelCase naming convention to under_score
As a special case, if string is longer than 2 characters and
all characters are uppercase, conversion will be skipped.
*/
std::string from_camel_case(const std::string &name) {
std::string new_name;
size_t upper_count = 0;
// Uppercase letters will be converted to underscore+lowercase letter
// except in two situations:
// - When it is the first letter
// - When an underscore or a dash is already before the uppercase letter
bool skip_underscore = true;
for (auto character : name) {
if (isupper(character)) {
upper_count++;
if (!skip_underscore)
new_name.append(1, '_');
else
skip_underscore = false;
new_name.append(1, tolower(character));
} else {
// if character is '_'
skip_underscore = character == '_' || character == '-';
if (skip_underscore) upper_count++;
new_name.append(1, character);
}
}
if (upper_count == name.length()) return name;
return new_name;
}
std::string from_camel_case_to_dashes(const std::string &name) {
return str_replace(from_camel_case(name), "_", "-");
}
static std::size_t span_quotable_identifier(const std::string &s, std::size_t p,
std::string *out_string,
bool allow_ansi_quotes = false) {
if (p >= s.length()) return p;
const auto copy_current_character = [&s, &p, out_string]() {
if (out_string) out_string->push_back(s[p]);
};
if ('`' == s[p] || (allow_ansi_quotes && '"' == s[p])) {
const char quote = s[p++];
bool esc = false;
bool done = false;
while (!done && p < s.size()) {
if (quote == s[p]) {
if (esc) {
copy_current_character();
esc = false;
} else {
esc = true;
}
} else {
if (esc) {
done = true;
break;
} else {
copy_current_character();
}
}
++p;
}
// was the last character a quote?
if (!done && esc) {
done = true;
}
if (!done) {
throw std::runtime_error("Invalid syntax in identifier");
}
} else {
const auto first = p;
bool seen_not_a_digit = false;
while (p < s.size()) {
if (!std::isalnum(s[p]) && s[p] != '_' && s[p] != '$') {
if (first == p) {
throw std::runtime_error("Invalid character in identifier");
} else {
break;
}
}
copy_current_character();
if (!seen_not_a_digit && !isdigit(s[p])) seen_not_a_digit = true;
++p;
}
if (!seen_not_a_digit) {
throw std::runtime_error(
"Invalid identifier: identifiers may begin with a digit but unless "
"quoted may not consist solely of digits.");
}
}
return p;
}
static std::size_t span_quotable_string_literal(
const std::string &s, std::size_t p, std::string *out_string,
bool allow_number_at_beginning = false) {
if (s.size() <= p) return p;
char quote = s[p];
if (quote != '\'' && quote != '"') {
// check if valid initial char
if (!std::isalpha(quote) &&
!(allow_number_at_beginning && std::isdigit(quote)) && quote != '_' &&
quote != '$' && quote != '%')
throw std::runtime_error("Invalid character in string literal");
quote = 0;
} else {
p++;
}
if (quote == 0) {
while (p < s.size()) {
if (!std::isalnum(s[p]) && s[p] != '_' && s[p] != '$' && s[p] != '.' &&
s[p] != '%')
break;
if (out_string) out_string->push_back(s[p]);
++p;
}
} else {
int esc = 0;
bool done = false;
while (p < s.size() && !done) {
if (esc == quote && s[p] != esc) {
done = true;
break;
}
switch (s[p]) {
case '"':
case '\'':
if (quote == s[p]) {
if (esc == quote || esc == '\\') {
if (out_string) out_string->push_back(s[p]);
esc = 0;
} else {
esc = s[p];
}
} else {
if (out_string) out_string->push_back(s[p]);
esc = 0;
}
break;
case '\\':
if (esc == '\\') {
if (out_string) out_string->push_back(s[p]);
esc = 0;
} else if (esc == 0) {
esc = '\\';
} else {
done = true;
}
break;
case 'n':
if (esc == '\\') {
if (out_string) out_string->push_back('\n');
esc = 0;
} else if (esc == 0) {
if (out_string) out_string->push_back(s[p]);
} else {
done = true;
}
break;
case 't':
if (esc == '\\') {
if (out_string) out_string->push_back('\t');
esc = 0;
} else if (esc == 0) {
if (out_string) out_string->push_back(s[p]);
} else {
done = true;
}
break;
case 'b':
if (esc == '\\') {
if (out_string) out_string->push_back('\b');
esc = 0;
} else if (esc == 0) {
if (out_string) out_string->push_back(s[p]);
} else {
done = true;
}
break;
case 'r':
if (esc == '\\') {
if (out_string) out_string->push_back('\r');
esc = 0;
} else if (esc == 0) {
if (out_string) out_string->push_back(s[p]);
} else {
done = true;
}
break;
case '0':
if (esc == '\\') {
if (out_string) out_string->push_back('\0');
esc = 0;
} else if (esc == 0) {
if (out_string) out_string->push_back(s[p]);
} else {
done = true;
}
break;
case 'Z':
if (esc == '\\') {
if (out_string) {
out_string->push_back(26);
}
esc = 0;
} else if (esc == 0) {
if (out_string) out_string->push_back(s[p]);
} else {
done = true;
}
break;
default:
if (esc == '\\') {
if (out_string) out_string->push_back(s[p]);
esc = 0;
} else if (esc == 0) {
if (out_string) out_string->push_back(s[p]);
} else {
done = true;
}
break;
}
++p;
}
if (!done && esc == quote) {
done = true;
} else if (!done) {
throw std::runtime_error("Invalid syntax in string literal");
}
}
return p;
}
static std::size_t span_account_hostname_relaxed(const std::string &s,
std::size_t p,
std::string *out_string,
bool auto_quote_hosts) {
if (s.size() <= p) return p;
// Use the span_quotable_identifier, if an error occurs, try to see if quotes
// would fix it, however first check for the existence of the '@' character
// which is not allowed in hostnames
std::size_t old_p = p, res = 0;
if (s.find('@', p) != std::string::npos) {
std::string err_msg = "Malformed hostname (illegal symbol: '@')";
throw std::runtime_error(err_msg);
}
// Check if hostname starts with string literal or identifier depending on the
// first character being a backtick or not.
if (s[p] == '`') {
res = span_quotable_identifier(s, p, out_string);
} else {
bool quoted = false;
// Do not allow quote characters unless they are surrounded by quotes
if (s[p] == s[s.size() - 1] && (s[p] == '\'' || s[p] == '"')) {
// hostname surrounded by quotes.
quoted = true;
} else {
if ((s.find('\'', p) != std::string::npos) ||
(s.find('"', p) != std::string::npos)) {
throw std::runtime_error(
"Malformed hostname. Cannot use \"'\" or '\"' "
"characters on the hostname without quotes");
}
}
bool try_quoting = false;
try {
res = span_quotable_string_literal(s, p, out_string, true);
// If the complete string was not consumed could be a hostname that
// requires quotes, they should be enabled only if not quoted already
if (res < s.size() && auto_quote_hosts) {
try_quoting = !quoted;
}
} catch (const std::runtime_error &) {
// In case of error parsing, tries quoting
try_quoting = auto_quote_hosts;
}
if (try_quoting) {
std::string quoted_s =
s.substr(0, old_p) + "'" + escape_backticks(s.substr(old_p)) + "'";
// reset out_string
if (out_string) *out_string = "";
res = span_quotable_string_literal(quoted_s, old_p, out_string, true);
}
}
return res;
}
/** Split a MySQL account string (in the form user@host) into its username and
* hostname components. The returned strings will be unquoted.
* The supported format is the <a
* href="https://dev.mysql.com/doc/refman/en/account-names.html">standard MySQL
* account name format</a>. This means it supports both identifiers and string
* literals for username and hostname.
*/
void split_account(const std::string &account, std::string *out_user,
std::string *out_host, Account::Auto_quote auto_quote) {
std::size_t pos = 0;
if (out_user) *out_user = "";
if (out_host) *out_host = "";
// Check if account starts with string literal or identifier depending on the
// first character being a backtick or not.
if (!account.empty()) {
if (account[0] == '`') {
pos = span_quotable_identifier(account, 0, out_user);
} else if (account[0] == '\'' || account[0] == '"') {
pos = span_quotable_string_literal(account, 0, out_user);
} else {
pos = account.rfind('@');
if (pos == 0) throw std::runtime_error("User name must not be empty.");
if (Account::Auto_quote::USER_AND_HOST != auto_quote) {
// don't allow @ on the username unless it is quoted
if (account.rfind('@', pos - 1) != std::string::npos) {
throw std::runtime_error("Invalid user name: " +
account.substr(0, pos));
}
}
if (out_user != nullptr) out_user->assign(account, 0, pos);
}
} else {
throw std::runtime_error("User name must not be empty.");
}
if (std::string::npos != pos && account[pos] == '@' &&
++pos < account.length()) {
if (account.compare(pos, std::string::npos, "skip-grants host") == 0) {
pos = account.length();
if (out_host != nullptr) *out_host = "skip-grants host";
} else {
pos = span_account_hostname_relaxed(
account, pos, out_host, Account::Auto_quote::NO != auto_quote);
}
}
if (pos < account.size())
throw std::runtime_error("Invalid syntax in account name '" + account +
"'");
}
Account split_account(const std::string &account,
Account::Auto_quote auto_quote) {
Account result;
split_account(account, &result.user, &result.host, auto_quote);
return result;
}
/** Join MySQL account components into a string suitable for use with GRANT
* and similar
*/
std::string make_account(const std::string &user, const std::string &host) {
return shcore::sqlstring("?@?", 0) << user << host;
}
std::string make_account(const Account &account) {
return make_account(account.user, account.host);
}
namespace {
void ensure_dot(const std::string &str, std::size_t pos) {
if (str[pos] != '.') {
throw std::runtime_error(
std::string("Invalid object name, expected '.', but got: '") +
str[pos] + "'.");
}
}
void ensure_eos(const std::string &str, std::size_t pos) {
if (pos < str.length()) {
throw std::runtime_error(
std::string("Invalid object name, expected end of name, but got: '") +
str[pos] + "'.");
}
}
} // namespace
void split_schema_and_table(const std::string &str, std::string *out_schema,
std::string *out_table, bool allow_ansi_quotes) {
std::string schema;
std::string table;
auto pos = span_quotable_identifier(str, 0, &schema, allow_ansi_quotes);
if (pos < str.length()) {
ensure_dot(str, pos);
pos = span_quotable_identifier(str, ++pos, &table, allow_ansi_quotes);
ensure_eos(str, pos);
} else {
// there's only table name
std::swap(schema, table);
}
if (table.empty()) {
throw std::runtime_error(
"Invalid object name, table name cannot be empty.");
}
if (out_schema) {
*out_schema = std::move(schema);
}
if (out_table) {
*out_table = std::move(table);
}
}
void split_schema_table_and_object(const std::string &str,
std::string *out_schema,
std::string *out_table,
std::string *out_object,
bool allow_ansi_quotes) {
std::string schema;
std::string table;
std::string object;
auto pos = span_quotable_identifier(str, 0, &schema, allow_ansi_quotes);
if (pos < str.length()) {
ensure_dot(str, pos);
pos = span_quotable_identifier(str, ++pos, &table, allow_ansi_quotes);
if (pos < str.length()) {
ensure_dot(str, pos);
pos = span_quotable_identifier(str, ++pos, &object, allow_ansi_quotes);
ensure_eos(str, pos);
} else {
// there are table and object names
std::swap(table, object);
std::swap(schema, table);
}
} else {
// there's only object name
std::swap(schema, object);
}
if (object.empty()) {
throw std::runtime_error(
"Invalid identifier, object name cannot be empty.");
}
if (out_schema) {
*out_schema = std::move(schema);
}
if (out_table) {
*out_table = std::move(table);
}
if (out_object) {
*out_object = std::move(object);
}
}
void split_priv_level(const std::string &str, std::string *out_schema,
std::string *out_object, size_t *out_leftover) {
assert(out_schema && out_object);
// *
// *.*
// schema.*
// *.object
// schema.object
std::string schema;
std::string object;
*out_schema = "";
*out_object = "";
if (str.empty()) return;
std::string::size_type pos;
if (str.front() == '*') {
*out_schema = "*";
pos = 1;
} else {
pos = span_quotable_identifier(str, 0, out_schema);
}
if (pos < str.length()) {
if (str[pos] != '.') {
throw std::runtime_error(std::string("Invalid object name '" + str +
"', expected '.', but got: '") +
str[pos] + "'.");
}
++pos;
if (pos < str.length()) {
if (str[pos] == '*') {
*out_object = "*";
pos++;
} else {
pos = span_quotable_identifier(str, pos, out_object);
}
} else {
throw std::runtime_error("Invalid object name '" + str +
"', expected object name after '.'");
}
if (pos < str.length()) {
if (out_leftover) {
*out_leftover = pos;
} else {
throw std::runtime_error(
std::string("Invalid object name '" + str +
"', expected end of name, but got: '") +
str[pos] + "'.");
}
}
}
}
std::string SHCORE_PUBLIC unquote_identifier(const std::string &str,
bool allow_ansi_quotes) {
std::string object;
const auto pos = span_quotable_identifier(str, 0, &object, allow_ansi_quotes);
if (pos < str.length()) {
throw std::runtime_error(
std::string("Invalid object name, expected end of name, but got: '") +
str[pos] + "'.");
}
if (object.empty()) {
throw std::runtime_error("Object name cannot be empty.");
}
return object;
}
std::string SHCORE_PUBLIC unquote_sql_string(const std::string &s) {
if (s.length() < 2 || s[0] != '\'' || s[0] != s.back())
throw std::invalid_argument("string is not properly quoted");
std::string result;
size_t offs;
size_t end = s.length();
result.reserve(end);
offs = 1; // skip opening quote
for (;;) {
auto p = s.find_first_of("'\\", offs);
if (p == std::string::npos)
throw std::invalid_argument("string is not properly quoted");
if (p > offs) {
result.append(s.c_str() + offs, p - offs);
}
if (s[p] == '\\') {
++p;
switch (s[p]) {
case 0:
// string too short?
throw std::invalid_argument("string is not properly quoted");
case '0':
result.push_back(0);
break;
case 'n':
result.push_back('\n');
break;
case 'r':
result.push_back('\r');
break;
case 'Z': /* This gives problems on Win32 */
result.push_back('\032');
break;
case '\\':
case '\'':
case '"':
default:
result.push_back(s[p]);
break;
}
} else {
++p;
if (s[p] == '\'')
result.push_back('\'');
else if (p == end)
break;
else
throw std::invalid_argument("string is not properly quoted");
}
offs = p + 1;
}
return result;
}
void sleep_ms(uint32_t ms) { shcore::current_interrupt()->wait(ms); }
void sleep(std::chrono::milliseconds duration) {
shcore::current_interrupt()->wait(duration.count());
}
/*
* Determines the current Operating System
*
* @return an enum representing the current operating system
* (shcore::OperatingSystem)
*/
OperatingSystem get_os_type() {
OperatingSystem os;
#ifdef WIN32
os = OperatingSystem::WINDOWS;
#elif __APPLE__
os = OperatingSystem::MACOS;
#elif __sun
os = OperatingSystem::SOLARIS;
#elif __linux__
os = OperatingSystem::LINUX;
// Detect the distribution
std::string distro_buffer, proc_version = "/proc/version";
if (is_file(proc_version)) {
// Read the proc_version file
std::ifstream s(proc_version.c_str());
if (!s.fail())
std::getline(s, distro_buffer);
else
log_warning("Failed to read file: %s", proc_version.c_str());
// Transform all to lowercase
std::transform(distro_buffer.begin(), distro_buffer.end(),
distro_buffer.begin(), ::tolower);
const std::vector<std::string> distros = {"ubuntu", "debian", "red hat"};
for (const auto &value : distros) {
if (distro_buffer.find(value) != std::string::npos) {
if (value == "ubuntu" || value == "debian") {
os = OperatingSystem::DEBIAN;
break;
} else if (value == "red hat") {
os = OperatingSystem::REDHAT;
break;
} else {
continue;
}
}
}
} else {
log_warning(
"Failed to detect the Linux distribution. '%s' "
"does not exist.",
proc_version.c_str());
}
#else
#error Unsupported platform
os = OperatingSystem::UNKNOWN;
#endif
return os;
}
std::string get_machine_type() {
{
constexpr std::string_view machine_type{MACHINE_TYPE};
static_assert(!machine_type.empty());
}
return MACHINE_TYPE;
}
std::string to_string(OperatingSystem os_type) {
switch (os_type) {
case OperatingSystem::UNKNOWN:
return "unknown";
case OperatingSystem::DEBIAN:
return "debian";
case OperatingSystem::REDHAT:
return "redhat";
case OperatingSystem::LINUX:
return "linux";
case OperatingSystem::WINDOWS:
return "windows";
case OperatingSystem::MACOS:
return "macos";
case OperatingSystem::SOLARIS:
return "solaris";
default:
assert(0);
return "unknown";
}
}
namespace {
/**
* https://research.swtch.com/glob
*/
bool _match_glob(const std::string_view pat, const std::string_view str) {
size_t pend = pat.length();
size_t send = str.length();
size_t px = 0;
size_t sx = 0;
size_t ppx = 0;
size_t psx = 0;
while (px < pend || sx < send) {
if (px < pend) {
char c = pat[px];
switch (c) {
case '?':
if (sx < send) {
++px;
++sx;
continue;
}
break;
case '*':
ppx = px;
psx = sx + 1;
++px;
continue;
case '\\':
// if '\' is followed by * or ?, it's an escape sequence, skip '\'
if ((px + 1) < pend && (pat[px + 1] == '*' || pat[px + 1] == '?')) {
++px;
c = pat[px];
}
// fall through
default:
if (sx < send && str[sx] == c) {
++px;
++sx;
continue;
}
break;
}
}
if (0 < psx && psx <= send) {
px = ppx + 1;
sx = psx;
++psx;
continue;
}
return false;
}
return true;
}
} // namespace
/**
* Match a string against a glob-like pattern.
*
* Allowed wildcard characters: '*', '?'.
* Supports escaping wildcards via '\\' character.
*
* Note: works with ASCII only, no UTF8 support
*/
bool match_glob(const std::string_view pattern, const std::string_view s,
bool case_sensitive) {
if (!case_sensitive) {
const std::string &str = str_lower(s);
const std::string &pat = str_lower(pattern);
return _match_glob(pat, str);
}
return _match_glob(pattern, s);
}
const char *get_long_version() {
return "Ver " MYSH_FULL_VERSION " for " SYSTEM_TYPE " on " MACHINE_TYPE
" - for MySQL " LIBMYSQL_VERSION " (" MYSQL_COMPILATION_COMMENT ")";
}
#ifdef _WIN32
std::string SHCORE_PUBLIC last_error_to_string(DWORD code) {
LPTSTR lpMsgBuf = nullptr;
std::string ret;
if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf, 0, nullptr) > 0) {
ret = lpMsgBuf;
LocalFree(lpMsgBuf);
} else {
ret = str_format("Unknown error code: %lu", code);
}
return ret;
}
#endif // _WIN32
std::istream &getline(std::istream &in, std::string &out) {
if (std::getline(in, out)) {
if (!out.empty() && out.back() == '\r') {
out.pop_back();
}
}
return in;
}
bool verify_status_code(int status, std::string *error) {
assert(error);
if (status < 0) {
const auto code = errno;
*error = "failed to execute: " + errno_to_string(code);
} else {
#ifdef _WIN32
const bool exited = true;
const auto exit_code = status;
#else // !_WIN32
const bool exited = WIFEXITED(status);
const auto exit_code = WEXITSTATUS(status);
#endif // !_WIN32
if (exited) {
if (0 != exit_code) {
*error = "returned exit code: " + std::to_string(exit_code);
} else {
return true;
}
}
#ifndef _WIN32
else if (WIFSIGNALED(status)) {
*error = "was terminated by signal: " + std::to_string(WTERMSIG(status));
} else if (WIFSTOPPED(status)) {
*error = "was stopped by signal: " + std::to_string(WSTOPSIG(status));
} else {
*error = "failed with unknown code: " + std::to_string(status);
}
#endif // !_WIN32
}
return false;
}
bool setenv(const char *name, const char *value) {
if (nullptr == value || '\0' == *value) {
return shcore::unsetenv(name);
}
const auto ret =
#ifdef _WIN32
_putenv_s(name, value);
#else // !_WIN32
::setenv(name, value, 1 /* overwrite */);
#endif // !_WIN32
return ret == 0;
}
bool setenv(const char *name, const std::string &value) {
return shcore::setenv(name, value.c_str());
}
bool setenv(const std::string &name, const std::string &value) {
return shcore::setenv(name.c_str(), value.c_str());
}
bool setenv(const std::string &name_value) {
const auto pos = name_value.find('=');
if (std::string::npos != pos) {
return shcore::setenv(name_value.substr(0, pos),
name_value.substr(pos + 1));
}
return false;
}
bool unsetenv(const char *name) {
const auto ret =
#ifdef _WIN32
_putenv_s(name, "");
#else // !_WIN32
::unsetenv(name);
#endif // !_WIN32
return ret == 0;
}
bool unsetenv(const std::string &name) {
return shcore::unsetenv(name.c_str());
}
} // namespace shcore