]> git.proxmox.com Git - proxmox-backup.git/blob - src/bin/proxmox_tape/changer.rs
fix #4904: tape changer: add option to eject before unload
[proxmox-backup.git] / src / bin / proxmox_tape / changer.rs
1 use anyhow::{bail, Error};
2 use serde_json::Value;
3
4 use proxmox_router::{cli::*, ApiHandler, RpcEnvironment};
5 use proxmox_schema::api;
6 use proxmox_section_config::SectionConfigData;
7
8 use pbs_config::drive::{complete_changer_name, complete_drive_name};
9
10 use pbs_api_types::CHANGER_NAME_SCHEMA;
11
12 use pbs_tape::linux_list_drives::complete_changer_path;
13
14 use proxmox_backup::{api2, tape::drive::media_changer};
15
16 pub fn lookup_changer_name(param: &Value, config: &SectionConfigData) -> Result<String, Error> {
17 if let Some(name) = param["name"].as_str() {
18 return Ok(String::from(name));
19 }
20
21 let mut empty = Value::Null;
22
23 if let Ok(drive) = crate::extract_drive_name(&mut empty, config) {
24 if let Ok(Some((_, name))) = media_changer(config, &drive) {
25 return Ok(name);
26 }
27 }
28
29 bail!("unable to get (default) changer name");
30 }
31
32 pub fn changer_commands() -> CommandLineInterface {
33 let cmd_def = CliCommandMap::new()
34 .insert("scan", CliCommand::new(&API_METHOD_SCAN_FOR_CHANGERS))
35 .insert("list", CliCommand::new(&API_METHOD_LIST_CHANGERS))
36 .insert(
37 "config",
38 CliCommand::new(&API_METHOD_GET_CONFIG)
39 .arg_param(&["name"])
40 .completion_cb("name", complete_changer_name),
41 )
42 .insert(
43 "remove",
44 CliCommand::new(&api2::config::changer::API_METHOD_DELETE_CHANGER)
45 .arg_param(&["name"])
46 .completion_cb("name", complete_changer_name),
47 )
48 .insert(
49 "create",
50 CliCommand::new(&api2::config::changer::API_METHOD_CREATE_CHANGER)
51 .arg_param(&["name"])
52 .completion_cb("name", complete_drive_name)
53 .completion_cb("path", complete_changer_path),
54 )
55 .insert(
56 "update",
57 CliCommand::new(&api2::config::changer::API_METHOD_UPDATE_CHANGER)
58 .arg_param(&["name"])
59 .completion_cb("name", complete_changer_name)
60 .completion_cb("path", complete_changer_path),
61 )
62 .insert(
63 "status",
64 CliCommand::new(&API_METHOD_GET_STATUS)
65 .arg_param(&["name"])
66 .completion_cb("name", complete_changer_name),
67 )
68 .insert(
69 "transfer",
70 CliCommand::new(&API_METHOD_TRANSFER)
71 .arg_param(&["name"])
72 .completion_cb("name", complete_changer_name),
73 );
74
75 cmd_def.into()
76 }
77
78 #[api(
79 input: {
80 properties: {
81 "output-format": {
82 schema: OUTPUT_FORMAT,
83 optional: true,
84 },
85 },
86 },
87 )]
88 /// List changers
89 fn list_changers(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
90 let output_format = get_output_format(&param);
91 let info = &api2::tape::changer::API_METHOD_LIST_CHANGERS;
92 let mut data = match info.handler {
93 ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
94 _ => unreachable!(),
95 };
96
97 let options = default_table_format_options()
98 .column(ColumnConfig::new("name"))
99 .column(ColumnConfig::new("path"))
100 .column(ColumnConfig::new("vendor"))
101 .column(ColumnConfig::new("model"))
102 .column(ColumnConfig::new("serial"));
103
104 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
105
106 Ok(())
107 }
108
109 #[api(
110 input: {
111 properties: {
112 "output-format": {
113 schema: OUTPUT_FORMAT,
114 optional: true,
115 },
116 },
117 },
118 )]
119 /// Scan for SCSI tape changers
120 fn scan_for_changers(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
121 let output_format = get_output_format(&param);
122 let info = &api2::tape::API_METHOD_SCAN_CHANGERS;
123 let mut data = match info.handler {
124 ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
125 _ => unreachable!(),
126 };
127
128 let options = default_table_format_options()
129 .column(ColumnConfig::new("path"))
130 .column(ColumnConfig::new("vendor"))
131 .column(ColumnConfig::new("model"))
132 .column(ColumnConfig::new("serial"));
133
134 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
135
136 Ok(())
137 }
138
139 #[api(
140 input: {
141 properties: {
142 "output-format": {
143 schema: OUTPUT_FORMAT,
144 optional: true,
145 },
146 name: {
147 schema: CHANGER_NAME_SCHEMA,
148 },
149 },
150 },
151 )]
152 /// Get tape changer configuration
153 fn get_config(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
154 let output_format = get_output_format(&param);
155 let info = &api2::config::changer::API_METHOD_GET_CONFIG;
156 let mut data = match info.handler {
157 ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
158 _ => unreachable!(),
159 };
160
161 let options = default_table_format_options()
162 .column(ColumnConfig::new("name"))
163 .column(ColumnConfig::new("path"))
164 .column(ColumnConfig::new("options"))
165 .column(ColumnConfig::new("export-slots"));
166
167 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
168
169 Ok(())
170 }
171
172 #[api(
173 input: {
174 properties: {
175 "output-format": {
176 schema: OUTPUT_FORMAT,
177 optional: true,
178 },
179 name: {
180 schema: CHANGER_NAME_SCHEMA,
181 optional: true,
182 },
183 cache: {
184 description: "Use cached value.",
185 type: bool,
186 optional: true,
187 default: true,
188 },
189 },
190 },
191 )]
192 /// Get tape changer status
193 async fn get_status(mut param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
194 let (config, _digest) = pbs_config::drive::config()?;
195
196 param["name"] = lookup_changer_name(&param, &config)?.into();
197
198 let output_format = get_output_format(&param);
199 let info = &api2::tape::changer::API_METHOD_GET_STATUS;
200 let mut data = match info.handler {
201 ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
202 _ => unreachable!(),
203 };
204
205 let render_label_text = |value: &Value, _record: &Value| -> Result<String, Error> {
206 if value.is_null() {
207 return Ok(String::new());
208 }
209 let text = value.as_str().unwrap().to_string();
210 if text.is_empty() {
211 Ok(String::from("--FULL--"))
212 } else {
213 Ok(text)
214 }
215 };
216
217 let options = default_table_format_options()
218 .sortby("entry-kind", false)
219 .sortby("entry-id", false)
220 .column(ColumnConfig::new("entry-kind"))
221 .column(ColumnConfig::new("entry-id"))
222 .column(ColumnConfig::new("label-text").renderer(render_label_text))
223 .column(ColumnConfig::new("loaded-slot"));
224
225 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
226
227 Ok(())
228 }
229
230 #[api(
231 input: {
232 properties: {
233 name: {
234 schema: CHANGER_NAME_SCHEMA,
235 optional: true,
236 },
237 from: {
238 description: "Source slot number",
239 type: u64,
240 minimum: 1,
241 },
242 to: {
243 description: "Destination slot number",
244 type: u64,
245 minimum: 1,
246 },
247 },
248 },
249 )]
250 /// Transfers media from one slot to another
251 pub async fn transfer(mut param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
252 let (config, _digest) = pbs_config::drive::config()?;
253
254 param["name"] = lookup_changer_name(&param, &config)?.into();
255
256 let info = &api2::tape::changer::API_METHOD_TRANSFER;
257 match info.handler {
258 ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
259 _ => unreachable!(),
260 };
261
262 Ok(())
263 }