mysqladm/cli/mysql_admutils_compatibility/
mysql_dbadm.rs1use clap::Parser;
2use futures_util::{SinkExt, StreamExt};
3use std::os::unix::net::UnixStream as StdUnixStream;
4use std::path::PathBuf;
5use tokio::net::UnixStream as TokioUnixStream;
6
7use crate::{
8 cli::{
9 common::erroneous_server_response,
10 database_command,
11 mysql_admutils_compatibility::{
12 common::trim_db_name_to_32_chars,
13 error_messages::{
14 format_show_database_error_message, handle_create_database_error,
15 handle_drop_database_error,
16 },
17 },
18 },
19 core::{
20 bootstrap::bootstrap_server_connection_and_drop_privileges,
21 database_privileges::DatabasePrivilegeRow,
22 protocol::{
23 ClientToServerMessageStream, GetDatabasesPrivilegeDataError, MySQLDatabase, Request,
24 Response, create_client_to_server_message_stream,
25 },
26 },
27};
28
29const HELP_DB_PERM: &str = r#"
30Edit permissions for the DATABASE(s). Running this command will
31spawn the editor stored in the $EDITOR environment variable.
32(pico will be used if the variable is unset)
33
34The file should contain one line per user, starting with the
35username and followed by ten Y/N-values seperated by whitespace.
36Lines starting with # are ignored.
37
38The Y/N-values corresponds to the following mysql privileges:
39 Select - Enables use of SELECT
40 Insert - Enables use of INSERT
41 Update - Enables use of UPDATE
42 Delete - Enables use of DELETE
43 Create - Enables use of CREATE TABLE
44 Drop - Enables use of DROP TABLE
45 Alter - Enables use of ALTER TABLE
46 Index - Enables use of CREATE INDEX and DROP INDEX
47 Temp - Enables use of CREATE TEMPORARY TABLE
48 Lock - Enables use of LOCK TABLE
49 References - Enables use of REFERENCES
50"#;
51
52#[derive(Parser)]
58#[command(
59 bin_name = "mysql-dbadm",
60 version,
61 about,
62 disable_help_subcommand = true,
63 verbatim_doc_comment
64)]
65pub struct Args {
66 #[command(subcommand)]
67 pub command: Option<Command>,
68
69 #[arg(
71 short,
72 long,
73 value_name = "PATH",
74 global = true,
75 hide_short_help = true
76 )]
77 server_socket_path: Option<PathBuf>,
78
79 #[arg(
81 short,
82 long,
83 value_name = "PATH",
84 global = true,
85 hide_short_help = true
86 )]
87 config: Option<PathBuf>,
88
89 #[arg(long, global = true)]
91 pub help_editperm: bool,
92}
93
94#[derive(Parser)]
98pub enum Command {
99 Create(CreateArgs),
101
102 Drop(DatabaseDropArgs),
104
105 Show(DatabaseShowArgs),
108
109 Editperm(EditPermArgs),
117}
118
119#[derive(Parser)]
120pub struct CreateArgs {
121 #[arg(num_args = 1..)]
123 name: Vec<MySQLDatabase>,
124}
125
126#[derive(Parser)]
127pub struct DatabaseDropArgs {
128 #[arg(num_args = 1..)]
130 name: Vec<MySQLDatabase>,
131}
132
133#[derive(Parser)]
134pub struct DatabaseShowArgs {
135 #[arg(num_args = 0..)]
137 name: Vec<MySQLDatabase>,
138}
139
140#[derive(Parser)]
141pub struct EditPermArgs {
142 pub database: MySQLDatabase,
144}
145
146pub fn main() -> anyhow::Result<()> {
148 let args: Args = Args::parse();
149
150 if args.help_editperm {
151 println!("{}", HELP_DB_PERM);
152 return Ok(());
153 }
154
155 let server_connection = bootstrap_server_connection_and_drop_privileges(
156 args.server_socket_path,
157 args.config,
158 Default::default(),
159 )?;
160
161 let command = match args.command {
162 Some(command) => command,
163 None => {
164 println!(
165 "Try `{} --help' for more information.",
166 std::env::args().next().unwrap_or("mysql-dbadm".to_string())
167 );
168 return Ok(());
169 }
170 };
171
172 tokio_run_command(command, server_connection)?;
173
174 Ok(())
175}
176
177fn tokio_run_command(command: Command, server_connection: StdUnixStream) -> anyhow::Result<()> {
178 tokio::runtime::Builder::new_current_thread()
179 .enable_all()
180 .build()
181 .unwrap()
182 .block_on(async {
183 let tokio_socket = TokioUnixStream::from_std(server_connection)?;
184 let message_stream = create_client_to_server_message_stream(tokio_socket);
185 match command {
186 Command::Create(args) => create_databases(args, message_stream).await,
187 Command::Drop(args) => drop_databases(args, message_stream).await,
188 Command::Show(args) => show_databases(args, message_stream).await,
189 Command::Editperm(args) => {
190 let edit_privileges_args = database_command::DatabaseEditPrivsArgs {
191 name: Some(args.database),
192 privs: vec![],
193 json: false,
194 editor: None,
195 yes: false,
196 };
197
198 database_command::edit_database_privileges(edit_privileges_args, message_stream)
199 .await
200 }
201 }
202 })
203}
204
205async fn create_databases(
206 args: CreateArgs,
207 mut server_connection: ClientToServerMessageStream,
208) -> anyhow::Result<()> {
209 let database_names = args.name.iter().map(trim_db_name_to_32_chars).collect();
210
211 let message = Request::CreateDatabases(database_names);
212 server_connection.send(message).await?;
213
214 let result = match server_connection.next().await {
215 Some(Ok(Response::CreateDatabases(result))) => result,
216 response => return erroneous_server_response(response),
217 };
218
219 server_connection.send(Request::Exit).await?;
220
221 for (name, result) in result {
222 match result {
223 Ok(()) => println!("Database {} created.", name),
224 Err(err) => handle_create_database_error(err, &name),
225 }
226 }
227
228 Ok(())
229}
230
231async fn drop_databases(
232 args: DatabaseDropArgs,
233 mut server_connection: ClientToServerMessageStream,
234) -> anyhow::Result<()> {
235 let database_names = args.name.iter().map(trim_db_name_to_32_chars).collect();
236
237 let message = Request::DropDatabases(database_names);
238 server_connection.send(message).await?;
239
240 let result = match server_connection.next().await {
241 Some(Ok(Response::DropDatabases(result))) => result,
242 response => return erroneous_server_response(response),
243 };
244
245 server_connection.send(Request::Exit).await?;
246
247 for (name, result) in result {
248 match result {
249 Ok(()) => println!("Database {} dropped.", name),
250 Err(err) => handle_drop_database_error(err, &name),
251 }
252 }
253
254 Ok(())
255}
256
257async fn show_databases(
258 args: DatabaseShowArgs,
259 mut server_connection: ClientToServerMessageStream,
260) -> anyhow::Result<()> {
261 let database_names: Vec<MySQLDatabase> =
262 args.name.iter().map(trim_db_name_to_32_chars).collect();
263
264 let message = if database_names.is_empty() {
265 let message = Request::ListDatabases(None);
266 server_connection.send(message).await?;
267 let response = server_connection.next().await;
268 let databases = match response {
269 Some(Ok(Response::ListAllDatabases(databases))) => databases.unwrap_or(vec![]),
270 response => return erroneous_server_response(response),
271 };
272
273 let database_names = databases.into_iter().map(|db| db.database).collect();
274
275 Request::ListPrivileges(Some(database_names))
276 } else {
277 Request::ListPrivileges(Some(database_names))
278 };
279 server_connection.send(message).await?;
280
281 let response = server_connection.next().await;
282
283 server_connection.send(Request::Exit).await?;
284
285 let results: Vec<Result<(MySQLDatabase, Vec<DatabasePrivilegeRow>), String>> = match response {
288 Some(Ok(Response::ListPrivileges(result))) => result
289 .into_iter()
290 .map(
291 |(name, rows)| match rows.map(|rows| (name.to_owned(), rows)) {
292 Ok(rows) => Ok(rows),
293 Err(GetDatabasesPrivilegeDataError::DatabaseDoesNotExist) => Ok((name, vec![])),
294 Err(err) => Err(format_show_database_error_message(err, &name)),
295 },
296 )
297 .collect(),
298 response => return erroneous_server_response(response),
299 };
300
301 results.into_iter().try_for_each(|result| match result {
302 Ok((name, rows)) => print_db_privs(&name, rows),
303 Err(err) => {
304 eprintln!("{}", err);
305 Ok(())
306 }
307 })?;
308
309 Ok(())
310}
311
312#[inline]
313fn yn(value: bool) -> &'static str {
314 if value { "Y" } else { "N" }
315}
316
317fn print_db_privs(name: &str, rows: Vec<DatabasePrivilegeRow>) -> anyhow::Result<()> {
318 println!(
319 concat!(
320 "Database '{}':\n",
321 "# User Select Insert Update Delete Create Drop Alter Index Temp Lock References\n",
322 "# ---------------- ------ ------ ------ ------ ------ ---- ----- ----- ---- ---- ----------"
323 ),
324 name,
325 );
326 if rows.is_empty() {
327 println!("# (no permissions currently granted to any users)");
328 } else {
329 for privilege in rows {
330 println!(
331 " {:<16} {:<7} {:<7} {:<7} {:<7} {:<7} {:<7} {:<7} {:<7} {:<7} {:<7} {}",
332 privilege.user,
333 yn(privilege.select_priv),
334 yn(privilege.insert_priv),
335 yn(privilege.update_priv),
336 yn(privilege.delete_priv),
337 yn(privilege.create_priv),
338 yn(privilege.drop_priv),
339 yn(privilege.alter_priv),
340 yn(privilege.index_priv),
341 yn(privilege.create_tmp_table_priv),
342 yn(privilege.lock_tables_priv),
343 yn(privilege.references_priv)
344 );
345 }
346 }
347
348 Ok(())
349}