Skip to content

Commit bcfdf23

Browse files
committed
WL#15658: Make mysqldump dump logical ACL statements
Implemented --users, --add-drop-user, --include-user and --exclude-user for mysqldump. Tests added. Change-Id: I1749a4be436da4b7e44d0a142e4f56465e950266
1 parent 4df38bb commit bcfdf23

6 files changed

+730
-10
lines changed

client/include/client_priv.h

+2
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ enum options_client {
188188
OPT_INIT_COMMAND_ADD,
189189
OPT_OUTPUT_AS_VERSION,
190190
OPT_AUTHENTICATION_WEBAUTHN_CLIENT_PRESERVE_PRIVACY,
191+
OPT_MYSQLDUMP_EXCLUDE_USER,
192+
OPT_MYSQLDUMP_INCLUDE_USER,
191193
/* Add new option above this */
192194
OPT_MAX_CLIENT_OPTION
193195
};

client/mysqldump.cc

+193-10
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ static uint my_end_arg;
179179
static char *opt_mysql_unix_port = nullptr;
180180
static char *opt_bind_addr = nullptr;
181181
static int first_error = 0;
182+
static bool opt_dump_users = false;
183+
static bool opt_add_drop_user = false;
182184
#include "client/include/authentication_kerberos_clientopt-vars.h"
183185
#include "client/include/caching_sha2_passwordopt-vars.h"
184186
#include "client/include/multi_factor_passwordopt-vars.h"
@@ -250,6 +252,7 @@ const char *default_dbug_option = "d:t:o,/tmp/mysqldump.trace";
250252
bool seen_views = false;
251253

252254
collation_unordered_set<string> *ignore_table;
255+
collation_unordered_set<string> *exclude_user, *include_user;
253256

254257
static struct my_option my_long_options[] = {
255258
{"all-databases", 'A',
@@ -272,6 +275,9 @@ static struct my_option my_long_options[] = {
272275
{"add-drop-trigger", 0, "Add a DROP TRIGGER before each create.",
273276
&opt_drop_trigger, &opt_drop_trigger, nullptr, GET_BOOL, NO_ARG, 0, 0, 0,
274277
nullptr, 0, nullptr},
278+
{"add-drop-user", 0, "Add DROP USER when dumping the user definitions",
279+
&opt_add_drop_user, &opt_add_drop_user, nullptr, GET_BOOL, OPT_ARG, 0, 0,
280+
0, nullptr, 0, nullptr},
275281
{"add-locks", OPT_LOCKS, "Add locks around INSERT statements.", &opt_lock,
276282
&opt_lock, nullptr, GET_BOOL, NO_ARG, 1, 0, 0, nullptr, 0, nullptr},
277283
{"allow-keywords", OPT_KEYWORDS,
@@ -453,6 +459,21 @@ static struct my_option my_long_options[] = {
453459
"--ignore-table=database.table.",
454460
nullptr, nullptr, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0,
455461
nullptr},
462+
{"exclude-user", OPT_MYSQLDUMP_EXCLUDE_USER,
463+
"Do not dump the specified user account. To specify more than one user "
464+
"account to exclude, use the directive multiple times, once for each user "
465+
"account. Each user account must be specified with both user and host "
466+
"part, e.g., --exclude-user=foo@localhost.",
467+
nullptr, nullptr, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0,
468+
nullptr},
469+
{"include-user", OPT_MYSQLDUMP_INCLUDE_USER,
470+
"Dump the specified user account. If no --include-user is specified, dump "
471+
"all user accounts by default. To specify more than one user account to "
472+
"dump, use the directive multiple times, once for each user account. "
473+
"Each user account must be specified with both user and host part, e.g., "
474+
"--exclude-users=foo@localhost.",
475+
nullptr, nullptr, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0,
476+
nullptr},
456477
{"include-source-host-port", OPT_MYSQLDUMP_INCLUDE_SOURCE_HOST_PORT,
457478
"Adds 'SOURCE_HOST=<host>, SOURCE_PORT=<port>' to 'CHANGE REPLICATION "
458479
"SOURCE TO..' "
@@ -666,6 +687,11 @@ static struct my_option my_long_options[] = {
666687
{"user", 'u', "User for login if not current user.", &current_user,
667688
&current_user, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0,
668689
nullptr},
690+
{"users", 0,
691+
"Dump user accounts as logical definitions in the form of CREATE USER and "
692+
"GRANT statements. Not compatible with --flush-privileges!",
693+
&opt_dump_users, &opt_dump_users, nullptr, GET_BOOL, OPT_ARG, 0, 0, 0,
694+
nullptr, 0, nullptr},
669695
{"verbose", 'v', "Print info about the various stages.", &verbose, &verbose,
670696
nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
671697
{"version", 'V', "Output version information and exit.", nullptr, nullptr,
@@ -767,6 +793,9 @@ static void verbose_msg(const char *fmt, ...)
767793
MY_ATTRIBUTE((format(printf, 1, 2)));
768794
static char const *fix_identifier_with_newline(char const *object_name,
769795
bool *freemem);
796+
static bool dump_users(FILE *sql_file);
797+
static bool dump_grants(FILE *sql_file);
798+
static bool fetch_users_list_if_include_is_empty();
770799

771800
static std::unordered_map<string, string> compatibility_rpl_replica_commands = {
772801
{"SHOW REPLICA STATUS", "SHOW SLAVE STATUS"},
@@ -1095,7 +1124,23 @@ static bool get_one_option(int optid, const struct my_option *opt,
10951124
"Illegal use of option --ignore-table=<database>.<table>\n");
10961125
exit(1);
10971126
}
1098-
ignore_table->insert(argument);
1127+
ignore_table->emplace(argument);
1128+
break;
1129+
}
1130+
case (int)OPT_MYSQLDUMP_EXCLUDE_USER: {
1131+
if (!strchr(argument, '@')) {
1132+
fprintf(stderr, "Illegal use of option --exclude-user=<user>@<host>\n");
1133+
exit(1);
1134+
}
1135+
exclude_user->emplace(argument);
1136+
break;
1137+
}
1138+
case (int)OPT_MYSQLDUMP_INCLUDE_USER: {
1139+
if (!strchr(argument, '@')) {
1140+
fprintf(stderr, "Illegal use of option --include-user=<user>@<host>\n");
1141+
exit(1);
1142+
}
1143+
include_user->emplace(argument);
10991144
break;
11001145
}
11011146
case (int)OPT_COMPATIBLE: {
@@ -1177,6 +1222,10 @@ static int get_options(int *argc, char ***argv) {
11771222

11781223
ignore_table =
11791224
new collation_unordered_set<string>(charset_info, PSI_NOT_INSTRUMENTED);
1225+
exclude_user =
1226+
new collation_unordered_set<string>(charset_info, PSI_NOT_INSTRUMENTED);
1227+
include_user =
1228+
new collation_unordered_set<string>(charset_info, PSI_NOT_INSTRUMENTED);
11801229
/* Don't copy internal log tables */
11811230
ignore_table->insert("mysql.apply_status");
11821231
ignore_table->insert("mysql.schema");
@@ -1192,6 +1241,47 @@ static int get_options(int *argc, char ***argv) {
11921241
&opt_net_buffer_length)) {
11931242
exit(1);
11941243
}
1244+
if (opt_dump_users) {
1245+
if (flush_privileges) {
1246+
fprintf(
1247+
stderr,
1248+
"%s: The --users option is incompatible with --flush-privileges\n",
1249+
my_progname);
1250+
return (EX_USAGE);
1251+
}
1252+
if (opt_xml) {
1253+
fprintf(stderr, "%s: The --users option is incompatible with --xml\n",
1254+
my_progname);
1255+
return (EX_USAGE);
1256+
}
1257+
/* ignore the ACL tables if we are dumping logical ACL */
1258+
ignore_table->insert("mysql.user");
1259+
ignore_table->insert("mysql.global_grants");
1260+
ignore_table->insert("mysql.db");
1261+
ignore_table->insert("mysql.tables_priv");
1262+
ignore_table->insert("mysql.columns_priv");
1263+
ignore_table->insert("mysql.procs_priv");
1264+
ignore_table->insert("mysql.proxies_priv");
1265+
ignore_table->insert("mysql.default_roles");
1266+
ignore_table->insert("mysql.role_edges");
1267+
ignore_table->insert("mysql.password_history");
1268+
} else {
1269+
if (include_user->size() > 0) {
1270+
fprintf(stderr,
1271+
"%s: The --include-user option is a no-op without --users\n",
1272+
my_progname);
1273+
}
1274+
if (exclude_user->size() > 0) {
1275+
fprintf(stderr,
1276+
"%s: The --exclude-user option is a no-op without --users\n",
1277+
my_progname);
1278+
}
1279+
if (opt_add_drop_user) {
1280+
fprintf(stderr,
1281+
"%s: The --add-drop-user option is a no-op without --users\n",
1282+
my_progname);
1283+
}
1284+
}
11951285

11961286
if (debug_info_flag) my_end_arg = MY_CHECK_ERROR | MY_GIVE_INFO;
11971287
if (debug_check_flag) my_end_arg = MY_CHECK_ERROR;
@@ -1242,7 +1332,8 @@ static int get_options(int *argc, char ***argv) {
12421332
!(charset_info =
12431333
get_charset_by_csname(default_charset, MY_CS_PRIMARY, MYF(MY_WME))))
12441334
exit(1);
1245-
if ((*argc < 1 && !opt_alldbs) || (*argc > 0 && opt_alldbs)) {
1335+
if ((*argc < 1 && !opt_alldbs && !opt_dump_users) ||
1336+
(*argc > 0 && opt_alldbs)) {
12461337
short_usage();
12471338
return EX_USAGE;
12481339
}
@@ -1625,10 +1716,12 @@ static void free_resources() {
16251716
if (md_result_file && md_result_file != stdout)
16261717
my_fclose(md_result_file, MYF(0));
16271718
free_passwords();
1628-
if (ignore_table != nullptr) {
1629-
delete ignore_table;
1630-
ignore_table = nullptr;
1631-
}
1719+
delete ignore_table;
1720+
ignore_table = nullptr;
1721+
delete include_user;
1722+
include_user = nullptr;
1723+
delete exclude_user;
1724+
exclude_user = nullptr;
16321725
if (insert_pat_inited) dynstr_free(&insert_pat);
16331726
if (opt_ignore_error) my_free(opt_ignore_error);
16341727
opt_init_commands.free();
@@ -4564,6 +4657,8 @@ static int dump_tablespaces_for_databases(char **databases) {
45644657
int r;
45654658
int i;
45664659

4660+
if (databases[0] == nullptr) return 0;
4661+
45674662
init_dynamic_string_checked(&where,
45684663
" AND TABLESPACE_NAME IN ("
45694664
"SELECT DISTINCT TABLESPACE_NAME FROM"
@@ -5151,10 +5246,10 @@ static int dump_all_tables_in_db(char *database) {
51515246
"-- Warning: get_table_structure() failed with some internal "
51525247
"error for 'slow_log' table\n");
51535248
}
5154-
}
5155-
if (flush_privileges && using_mysql_db) {
5156-
fprintf(md_result_file, "\n--\n-- Flush Grant Tables \n--\n");
5157-
fprintf(md_result_file, "\n/*! FLUSH PRIVILEGES */;\n");
5249+
if (flush_privileges) {
5250+
fprintf(md_result_file, "\n--\n-- Flush Grant Tables \n--\n");
5251+
fprintf(md_result_file, "\n/*! FLUSH PRIVILEGES */;\n");
5252+
}
51585253
}
51595254
return 0;
51605255
} /* dump_all_tables_in_db */
@@ -6372,6 +6467,86 @@ static bool get_view_structure(char *table, char *db) {
63726467
return false;
63736468
}
63746469

6470+
static bool fetch_users_list_if_include_is_empty() {
6471+
if (include_user->size() != 0) return false;
6472+
6473+
const char *enum_users_query =
6474+
"SELECT CONCAT('\\'',user,'\\'@\\'',host,'\\''),"
6475+
" CONCAT(QUOTE(user),'@',QUOTE(host)), CONCAT(user,'@',host)"
6476+
"FROM mysql.user";
6477+
MYSQL_RES *enum_users_res;
6478+
MYSQL_ROW enum_users_row;
6479+
if (mysql_query_with_error_report(mysql, &enum_users_res, enum_users_query))
6480+
return true;
6481+
if (!enum_users_res) return true;
6482+
auto enum_users_res_guard =
6483+
create_scope_guard([&] { mysql_free_result(enum_users_res); });
6484+
6485+
while ((enum_users_row = mysql_fetch_row(enum_users_res)) != nullptr) {
6486+
const char *user_name;
6487+
if (strcmp(enum_users_row[0], enum_users_row[1]) ||
6488+
strpbrk(enum_users_row[0], ".%- "))
6489+
user_name = enum_users_row[1];
6490+
else
6491+
user_name = enum_users_row[2];
6492+
if (exclude_user->find(user_name) == exclude_user->end()) {
6493+
include_user->emplace(user_name);
6494+
}
6495+
}
6496+
return false;
6497+
}
6498+
6499+
static void retract_excluded_users() {
6500+
for (auto user : *exclude_user) include_user->erase(user);
6501+
}
6502+
6503+
/**
6504+
@brief Do a logical dump of all ACL data: users, roles, grants
6505+
6506+
@param sql_file The output SQL file to write to
6507+
@retval false success
6508+
@retval true failure
6509+
*/
6510+
static bool dump_users(FILE *sql_file) {
6511+
for (auto user : *include_user) {
6512+
if (opt_add_drop_user) fprintf(sql_file, "DROP USER %s;\n", user.c_str());
6513+
6514+
/* execute and dump SHOW CREATE USER */
6515+
MYSQL_RES *res;
6516+
MYSQL_ROW row;
6517+
std::string query = "SHOW CREATE USER " + user;
6518+
if (mysql_query_with_error_report(mysql, &res, query.c_str())) return true;
6519+
if (!res) return true;
6520+
auto res_guard = create_scope_guard([&] { mysql_free_result(res); });
6521+
if (nullptr == (row = mysql_fetch_row(res))) {
6522+
DB_error(mysql, "retrieving SHOW CREATE USER result");
6523+
return true;
6524+
}
6525+
fprintf(sql_file, "%s;\n", row[0]);
6526+
}
6527+
return false;
6528+
}
6529+
6530+
static bool dump_grants(FILE *sql_file) {
6531+
for (auto user : *include_user) {
6532+
/* execute and dump SHOW GRANTS */
6533+
MYSQL_RES *res;
6534+
MYSQL_ROW row;
6535+
std::string query = "SHOW GRANTS FOR " + user;
6536+
if (mysql_query_with_error_report(mysql, &res, query.c_str())) return true;
6537+
if (!res) return true;
6538+
auto res_guard = create_scope_guard([&] { mysql_free_result(res); });
6539+
while (nullptr != (row = mysql_fetch_row(res))) {
6540+
fprintf(sql_file, "%s;\n", row[0]);
6541+
}
6542+
if (mysql_errno(mysql) != 0) {
6543+
DB_error(mysql, "retrieving SHOW CREATE USER result");
6544+
return true;
6545+
}
6546+
}
6547+
return false;
6548+
}
6549+
63756550
/*
63766551
The following functions are wrappers for the dynamic string functions
63776552
and if they fail, the wrappers will terminate the current process.
@@ -6503,6 +6678,12 @@ int main(int argc, char **argv) {
65036678
*/
65046679
if (process_set_gtid_purged(mysql, server_has_gtid_enabled)) goto err;
65056680

6681+
if (opt_dump_users) {
6682+
retract_excluded_users();
6683+
fetch_users_list_if_include_is_empty();
6684+
dump_users(md_result_file);
6685+
}
6686+
65066687
if (opt_source_data && do_show_binary_log_status(mysql)) goto err;
65076688
if (opt_replica_data && do_show_replica_status(mysql)) goto err;
65086689
if (opt_single_transaction &&
@@ -6548,6 +6729,8 @@ int main(int argc, char **argv) {
65486729
}
65496730
}
65506731

6732+
if (opt_dump_users) dump_grants(md_result_file);
6733+
65516734
/* if --dump-replica , start the replica sql thread */
65526735
if (opt_replica_data && do_start_replica_sql(mysql)) goto err;
65536736

0 commit comments

Comments
 (0)