mysqladm/client/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 client::{
9 commands::{EditDbPrivsArgs, edit_database_privileges, erroneous_server_response},
10 mysql_admutils_compatibility::{
11 common::trim_db_name_to_32_chars,
12 error_messages::{
13 format_show_database_error_message, handle_create_database_error,
14 handle_drop_database_error,
15 },
16 },
17 },
18 core::{
19 bootstrap::bootstrap_server_connection_and_drop_privileges,
20 database_privileges::DatabasePrivilegeRow,
21 protocol::{
22 ClientToServerMessageStream, GetDatabasesPrivilegeDataError, Request, Response,
23 create_client_to_server_message_stream,
24 },
25 types::MySQLDatabase,
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 = EditDbPrivsArgs {
191 name: Some(args.database),
192 privs: vec![],
193 json: false,
194 editor: None,
195 yes: false,
196 };
197
198 edit_database_privileges(edit_privileges_args, message_stream).await
199 }
200 }
201 })
202}
203
204async fn create_databases(
205 args: CreateArgs,
206 mut server_connection: ClientToServerMessageStream,
207) -> anyhow::Result<()> {
208 let database_names = args.name.iter().map(trim_db_name_to_32_chars).collect();
209
210 let message = Request::CreateDatabases(database_names);
211 server_connection.send(message).await?;
212
213 let result = match server_connection.next().await {
214 Some(Ok(Response::CreateDatabases(result))) => result,
215 response => return erroneous_server_response(response),
216 };
217
218 server_connection.send(Request::Exit).await?;
219
220 for (name, result) in result {
221 match result {
222 Ok(()) => println!("Database {} created.", name),
223 Err(err) => handle_create_database_error(err, &name),
224 }
225 }
226
227 Ok(())
228}
229
230async fn drop_databases(
231 args: DatabaseDropArgs,
232 mut server_connection: ClientToServerMessageStream,
233) -> anyhow::Result<()> {
234 let database_names = args.name.iter().map(trim_db_name_to_32_chars).collect();
235
236 let message = Request::DropDatabases(database_names);
237 server_connection.send(message).await?;
238
239 let result = match server_connection.next().await {
240 Some(Ok(Response::DropDatabases(result))) => result,
241 response => return erroneous_server_response(response),
242 };
243
244 server_connection.send(Request::Exit).await?;
245
246 for (name, result) in result {
247 match result {
248 Ok(()) => println!("Database {} dropped.", name),
249 Err(err) => handle_drop_database_error(err, &name),
250 }
251 }
252
253 Ok(())
254}
255
256async fn show_databases(
257 args: DatabaseShowArgs,
258 mut server_connection: ClientToServerMessageStream,
259) -> anyhow::Result<()> {
260 let database_names: Vec<MySQLDatabase> =
261 args.name.iter().map(trim_db_name_to_32_chars).collect();
262
263 let message = if database_names.is_empty() {
264 let message = Request::ListDatabases(None);
265 server_connection.send(message).await?;
266 let response = server_connection.next().await;
267 let databases = match response {
268 Some(Ok(Response::ListAllDatabases(databases))) => databases.unwrap_or(vec![]),
269 response => return erroneous_server_response(response),
270 };
271
272 let database_names = databases.into_iter().map(|db| db.database).collect();
273
274 Request::ListPrivileges(Some(database_names))
275 } else {
276 Request::ListPrivileges(Some(database_names))
277 };
278 server_connection.send(message).await?;
279
280 let response = server_connection.next().await;
281
282 server_connection.send(Request::Exit).await?;
283
284 let results: Vec<Result<(MySQLDatabase, Vec<DatabasePrivilegeRow>), String>> = match response {
287 Some(Ok(Response::ListPrivileges(result))) => result
288 .into_iter()
289 .map(
290 |(name, rows)| match rows.map(|rows| (name.to_owned(), rows)) {
291 Ok(rows) => Ok(rows),
292 Err(GetDatabasesPrivilegeDataError::DatabaseDoesNotExist) => Ok((name, vec![])),
293 Err(err) => Err(format_show_database_error_message(err, &name)),
294 },
295 )
296 .collect(),
297 response => return erroneous_server_response(response),
298 };
299
300 results.into_iter().try_for_each(|result| match result {
301 Ok((name, rows)) => print_db_privs(&name, rows),
302 Err(err) => {
303 eprintln!("{}", err);
304 Ok(())
305 }
306 })?;
307
308 Ok(())
309}
310
311#[inline]
312fn yn(value: bool) -> &'static str {
313 if value { "Y" } else { "N" }
314}
315
316fn print_db_privs(name: &str, rows: Vec<DatabasePrivilegeRow>) -> anyhow::Result<()> {
317 println!(
318 concat!(
319 "Database '{}':\n",
320 "# User Select Insert Update Delete Create Drop Alter Index Temp Lock References\n",
321 "# ---------------- ------ ------ ------ ------ ------ ---- ----- ----- ---- ---- ----------"
322 ),
323 name,
324 );
325 if rows.is_empty() {
326 println!("# (no permissions currently granted to any users)");
327 } else {
328 for privilege in rows {
329 println!(
330 " {:<16} {:<7} {:<7} {:<7} {:<7} {:<7} {:<7} {:<7} {:<7} {:<7} {:<7} {}",
331 privilege.user,
332 yn(privilege.select_priv),
333 yn(privilege.insert_priv),
334 yn(privilege.update_priv),
335 yn(privilege.delete_priv),
336 yn(privilege.create_priv),
337 yn(privilege.drop_priv),
338 yn(privilege.alter_priv),
339 yn(privilege.index_priv),
340 yn(privilege.create_tmp_table_priv),
341 yn(privilege.lock_tables_priv),
342 yn(privilege.references_priv)
343 );
344 }
345 }
346
347 Ok(())
348}