1use std::io;
2use std::path::PathBuf;
3
4use clap::Subcommand;
5
6use crate::config::StorageConfiguration;
7use crate::{Error, Storage};
8
9pub mod admin;
11pub mod schema;
13
14#[derive(Subcommand, Debug)]
16pub enum StorageCommand {
17 #[clap(subcommand)]
19 Backup(Location),
20 #[clap(subcommand)]
22 Restore(Location),
23 #[clap(subcommand)]
25 Admin(admin::Command),
26 Schema(schema::Command),
28}
29
30#[derive(Subcommand, Debug)]
32pub enum Location {
33 Path {
35 path: PathBuf,
37 },
38}
39
40impl StorageCommand {
41 pub fn execute(self, config: StorageConfiguration) -> Result<(), Error> {
43 let storage = Storage::open(config)?;
44 self.execute_on(&storage)
45 }
46
47 pub fn execute_on(self, storage: &Storage) -> Result<(), Error> {
49 match self {
50 StorageCommand::Backup(location) => location.backup(storage),
51 StorageCommand::Restore(location) => location.restore(storage),
52 StorageCommand::Admin(admin) => admin.execute(storage),
53 StorageCommand::Schema(schema) => schema.execute(storage),
54 }
55 }
56
57 #[cfg(feature = "async")]
59 pub async fn execute_on_async(self, storage: &crate::AsyncStorage) -> Result<(), Error> {
60 match self {
61 StorageCommand::Backup(location) => location.backup_async(storage).await,
62 StorageCommand::Restore(location) => location.restore_async(storage).await,
63 StorageCommand::Admin(admin) => admin.execute_async(storage).await,
64 StorageCommand::Schema(schema) => schema.execute_async(storage).await,
65 }
66 }
67}
68
69impl Location {
70 pub fn backup(&self, storage: &Storage) -> Result<(), Error> {
72 match self {
73 Location::Path { path } => storage.backup(path),
74 }
75 }
76
77 pub fn restore(&self, storage: &Storage) -> Result<(), Error> {
79 match self {
80 Location::Path { path } => storage.restore(path),
81 }
82 }
83
84 #[cfg(feature = "async")]
86 pub async fn backup_async(&self, storage: &crate::AsyncStorage) -> Result<(), Error> {
87 match self {
88 Location::Path { path } => storage.backup(path.clone()).await,
89 }
90 }
91
92 #[cfg(feature = "async")]
94 pub async fn restore_async(&self, storage: &crate::AsyncStorage) -> Result<(), Error> {
95 match self {
96 Location::Path { path } => storage.restore(path.clone()).await,
97 }
98 }
99}
100
101#[cfg(feature = "password-hashing")]
107pub fn read_password_from_stdin(
108 confirm: bool,
109) -> Result<bonsaidb_core::connection::SensitiveString, ReadPasswordError> {
110 let password = read_sensitive_input_from_stdin("Enter Password:")?;
111 if confirm {
112 let confirmed = read_sensitive_input_from_stdin("Re-enter the same password:")?;
113 if password != confirmed {
114 return Err(ReadPasswordError::PasswordConfirmationFailed);
115 }
116 }
117 Ok(password)
118}
119
120#[cfg(feature = "password-hashing")]
122#[derive(thiserror::Error, Debug)]
123pub enum ReadPasswordError {
124 #[error("password input cancelled")]
126 Cancelled,
127 #[error("password confirmation did not match")]
129 PasswordConfirmationFailed,
130 #[error("io error: {0}")]
132 Io(#[from] io::Error),
133}
134
135#[cfg(feature = "password-hashing")]
136fn read_sensitive_input_from_stdin(
137 prompt: &str,
138) -> Result<bonsaidb_core::connection::SensitiveString, ReadPasswordError> {
139 use std::io::stdout;
140
141 use crossterm::cursor::MoveToColumn;
142 use crossterm::terminal::{Clear, ClearType};
143 use crossterm::ExecutableCommand;
144
145 println!("{prompt} (input Enter or Return when done, or Escape to cancel)");
146
147 crossterm::terminal::enable_raw_mode()?;
148 let password = read_password_loop();
149 drop(
150 stdout()
151 .execute(MoveToColumn(1))?
152 .execute(Clear(ClearType::CurrentLine)),
153 );
154 crossterm::terminal::disable_raw_mode()?;
155 if let Some(password) = password? {
156 println!("********");
157 Ok(password)
158 } else {
159 Err(ReadPasswordError::Cancelled)
160 }
161}
162
163#[cfg(feature = "password-hashing")]
164fn read_password_loop() -> io::Result<Option<bonsaidb_core::connection::SensitiveString>> {
165 const ESCAPE: u8 = 27;
166 const BACKSPACE: u8 = 127;
167 const CANCEL: u8 = 3;
168 const EOF: u8 = 4;
169 const CLEAR_BEFORE_CURSOR: u8 = 21;
170
171 use std::io::Read;
172
173 use crossterm::cursor::{MoveLeft, MoveToColumn};
174 use crossterm::style::Print;
175 use crossterm::terminal::{Clear, ClearType};
176 use crossterm::ExecutableCommand;
177 let mut stdin = std::io::stdin();
178 let mut stdout = std::io::stdout();
179 let mut buffer = [0; 1];
180 let mut password = bonsaidb_core::connection::SensitiveString::default();
181 loop {
182 if stdin.read(&mut buffer)? == 0 {
183 return Ok(Some(password));
184 }
185 match buffer[0] {
186 ESCAPE | CANCEL => return Ok(None),
187 BACKSPACE => {
188 password.pop();
189 stdout
190 .execute(MoveLeft(1))?
191 .execute(Clear(ClearType::UntilNewLine))?;
192 }
193 CLEAR_BEFORE_CURSOR => {
194 password.clear();
195 stdout
196 .execute(MoveToColumn(1))?
197 .execute(Clear(ClearType::CurrentLine))?;
198 }
199 b'\n' | b'\r' | EOF => {
200 return Ok(Some(password));
201 }
202 other if other.is_ascii_control() => {}
203 other => {
204 password.push(other as char);
205 stdout.execute(Print('*'))?;
206 }
207 }
208 }
209}