]> git.proxmox.com Git - proxmox-backup.git/blame - src/bin/pmt.rs
tape: add pmt fsfm/bsfm, pass count as arg_param
[proxmox-backup.git] / src / bin / pmt.rs
CommitLineData
1e041082
DM
1/// Control magnetic tape drive operation
2///
3/// This is a Rust implementation, meant to replace the 'mt' command
4/// line tool.
5///
6/// Features:
7///
8/// - written in Rust
9/// - optional json output format
10/// - support tape alert flags
11/// - support volume statistics
12/// - read cartridge memory
13
1e041082
DM
14use anyhow::{bail, Error};
15use serde_json::Value;
16
17use proxmox::{
18 api::{
19 api,
20 cli::*,
83b8949a
DM
21 schema::{
22 Schema,
23 IntegerSchema,
24 },
1e041082
DM
25 RpcEnvironment,
26 },
27};
28
83b8949a
DM
29pub const FILE_MARK_COUNT_SCHEMA: Schema =
30 IntegerSchema::new("File mark count.")
31 .minimum(1)
32 .minimum(i32::MAX as isize)
33 .schema();
34
1e041082 35use proxmox_backup::{
1e041082
DM
36 config::{
37 self,
38 drive::complete_drive_name,
39 },
1e041082
DM
40 api2::types::{
41 LINUX_DRIVE_PATH_SCHEMA,
42 DRIVE_NAME_SCHEMA,
1e041082
DM
43 LinuxTapeDrive,
44 },
45 tape::{
46 complete_drive_path,
47 linux_tape_device_list,
48 drive::{
83b8949a 49 linux_mtio::MTCmd,
1e041082
DM
50 TapeDriver,
51 LinuxTapeHandle,
52 open_linux_tape_device,
28f60e52 53 },
1e041082
DM
54 },
55};
56
57fn get_tape_handle(param: &Value) -> Result<LinuxTapeHandle, Error> {
58
59 if let Some(name) = param["drive"].as_str() {
60 let (config, _digest) = config::drive::config()?;
61 let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
62 eprintln!("using device {}", drive.path);
63 return drive.open();
64 }
65
66 if let Some(device) = param["device"].as_str() {
67 eprintln!("using device {}", device);
68 return Ok(LinuxTapeHandle::new(open_linux_tape_device(&device)?))
69 }
70
71 if let Ok(name) = std::env::var("PROXMOX_TAPE_DRIVE") {
72 let (config, _digest) = config::drive::config()?;
73 let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
74 eprintln!("using device {}", drive.path);
75 return drive.open();
76 }
77
78 if let Ok(device) = std::env::var("TAPE") {
79 eprintln!("using device {}", device);
80 return Ok(LinuxTapeHandle::new(open_linux_tape_device(&device)?))
81 }
82
83 let (config, _digest) = config::drive::config()?;
84
85 let mut drive_names = Vec::new();
86 for (name, (section_type, _)) in config.sections.iter() {
87 if section_type != "linux" { continue; }
88 drive_names.push(name);
89 }
90
91 if drive_names.len() == 1 {
92 let name = drive_names[0];
93 let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
94 eprintln!("using device {}", drive.path);
95 return drive.open();
96 }
97
98 bail!("no drive/device specified");
99}
100
1f31d06f
DM
101#[api(
102 input: {
103 properties: {
104 drive: {
105 schema: DRIVE_NAME_SCHEMA,
106 optional: true,
107 },
108 device: {
109 schema: LINUX_DRIVE_PATH_SCHEMA,
110 optional: true,
111 },
112 count: {
83b8949a 113 schema: FILE_MARK_COUNT_SCHEMA,
1f31d06f 114 },
83b8949a 115 },
1f31d06f
DM
116 },
117)]
118/// Backward space count files (position before file mark).
119///
120/// The tape is positioned on the last block of the previous file.
121fn bsf(count: i32, param: Value) -> Result<(), Error> {
122
123 let mut handle = get_tape_handle(&param)?;
124
125 handle.backward_space_count_files(count)?;
126
127 Ok(())
128}
129
8e6ad430
DM
130
131#[api(
132 input: {
133 properties: {
134 drive: {
135 schema: DRIVE_NAME_SCHEMA,
136 optional: true,
137 },
138 device: {
139 schema: LINUX_DRIVE_PATH_SCHEMA,
140 optional: true,
141 },
142 count: {
143 schema: FILE_MARK_COUNT_SCHEMA,
144 },
145 },
146 },
147)]
148/// Backward space count files, then forward space one record (position after file mark).
149///
150/// This leaves the tape positioned at the first block of the file
151/// that is count - 1 files before the current file.
152fn bsfm(count: i32, param: Value) -> Result<(), Error> {
153
154 let mut handle = get_tape_handle(&param)?;
155
156 handle.mtop(MTCmd::MTBSFM, count, "bsfm")?;
157
158 Ok(())
159}
160
161
1e041082
DM
162#[api(
163 input: {
164 properties: {
165 drive: {
166 schema: DRIVE_NAME_SCHEMA,
167 optional: true,
168 },
169 device: {
170 schema: LINUX_DRIVE_PATH_SCHEMA,
171 optional: true,
172 },
173 "output-format": {
174 schema: OUTPUT_FORMAT,
175 optional: true,
176 },
177 },
178 },
179)]
180/// Read Cartridge Memory
181fn cartridge_memory(param: Value) -> Result<(), Error> {
182
183 let output_format = get_output_format(&param);
184
185 let mut handle = get_tape_handle(&param)?;
186 let result = handle.cartridge_memory();
187
188 if output_format == "json-pretty" {
189 let result = result.map_err(|err: Error| err.to_string());
190 println!("{}", serde_json::to_string_pretty(&result)?);
191 return Ok(());
192 }
193
194 if output_format == "json" {
195 let result = result.map_err(|err: Error| err.to_string());
196 println!("{}", serde_json::to_string(&result)?);
197 return Ok(());
198 }
199
200 if output_format != "text" {
201 bail!("unknown output format '{}'", output_format);
202 }
203
204 let list = result?;
205
206 for item in list {
207 println!("{}|{}|{}", item.id, item.name, item.value);
208 }
209
210 Ok(())
211}
212
213#[api(
214 input: {
215 properties: {
216 drive: {
217 schema: DRIVE_NAME_SCHEMA,
218 optional: true,
219 },
220 device: {
221 schema: LINUX_DRIVE_PATH_SCHEMA,
222 optional: true,
223 },
224 },
225 },
226)]
227/// Eject drive media
228fn eject(param: Value) -> Result<(), Error> {
229
230 let mut handle = get_tape_handle(&param)?;
231 handle.eject_media()?;
232
233 Ok(())
234}
235
236
237#[api(
238 input: {
239 properties: {
240 drive: {
241 schema: DRIVE_NAME_SCHEMA,
242 optional: true,
243 },
244 device: {
245 schema: LINUX_DRIVE_PATH_SCHEMA,
246 optional: true,
247 },
248 },
249 },
250)]
251/// Move to end of media
252fn eod(param: Value) -> Result<(), Error> {
253
254 let mut handle = get_tape_handle(&param)?;
255 handle.move_to_eom()?;
256
257 Ok(())
258}
259
260
b22c6187
DM
261#[api(
262 input: {
263 properties: {
264 drive: {
265 schema: DRIVE_NAME_SCHEMA,
266 optional: true,
267 },
268 device: {
269 schema: LINUX_DRIVE_PATH_SCHEMA,
270 optional: true,
271 },
272 fast: {
273 description: "Use fast erase.",
274 type: bool,
275 optional: true,
276 default: true,
277 },
278 },
279 },
280)]
281/// Erase media
282fn erase(fast: Option<bool>, param: Value) -> Result<(), Error> {
283
284 let mut handle = get_tape_handle(&param)?;
285 handle.erase_media(fast.unwrap_or(true))?;
286
287 Ok(())
288}
289
2f2e83c8
DM
290#[api(
291 input: {
292 properties: {
293 drive: {
294 schema: DRIVE_NAME_SCHEMA,
295 optional: true,
296 },
297 device: {
298 schema: LINUX_DRIVE_PATH_SCHEMA,
299 optional: true,
300 },
301 count: {
83b8949a 302 schema: FILE_MARK_COUNT_SCHEMA,
2f2e83c8
DM
303 },
304 },
305 },
306)]
307/// Forward space count files (position after file mark).
308///
309/// The tape is positioned on the first block of the next file.
310fn fsf(count: i32, param: Value) -> Result<(), Error> {
311
312 let mut handle = get_tape_handle(&param)?;
313
314 handle.forward_space_count_files(count)?;
315
316 Ok(())
317}
318
8e6ad430
DM
319#[api(
320 input: {
321 properties: {
322 drive: {
323 schema: DRIVE_NAME_SCHEMA,
324 optional: true,
325 },
326 device: {
327 schema: LINUX_DRIVE_PATH_SCHEMA,
328 optional: true,
329 },
330 count: {
331 schema: FILE_MARK_COUNT_SCHEMA,
332 },
333 },
334 },
335)]
336/// Forward space count files, then backward space one record (position before file mark).
337///
338/// This leaves the tape positioned at the last block of the file that
339/// is count - 1 files past the current file.
340fn fsfm(count: i32, param: Value) -> Result<(), Error> {
341
342 let mut handle = get_tape_handle(&param)?;
343
344 handle.mtop(MTCmd::MTFSFM, count, "fsfm")?;
345
346 Ok(())
347}
348
b22c6187 349
1e041082
DM
350#[api(
351 input: {
352 properties: {
353 drive: {
354 schema: DRIVE_NAME_SCHEMA,
355 optional: true,
356 },
357 device: {
358 schema: LINUX_DRIVE_PATH_SCHEMA,
359 optional: true,
360 },
361 },
362 },
363)]
364/// Load media
365fn load(param: Value) -> Result<(), Error> {
366
367 let mut handle = get_tape_handle(&param)?;
368 handle.mtload()?;
369
370 Ok(())
371}
372
373
374#[api(
375 input: {
376 properties: {
377 drive: {
378 schema: DRIVE_NAME_SCHEMA,
379 optional: true,
380 },
381 device: {
382 schema: LINUX_DRIVE_PATH_SCHEMA,
383 optional: true,
384 },
385 },
386 },
387)]
388/// Rewind the tape
389fn rewind(param: Value) -> Result<(), Error> {
390
391 let mut handle = get_tape_handle(&param)?;
392 handle.rewind()?;
393
394 Ok(())
395}
396
397
398#[api(
399 input: {
400 properties: {
401 "output-format": {
402 schema: OUTPUT_FORMAT,
403 optional: true,
404 },
405 },
406 },
407)]
408/// Scan for existing tape changer devices
409fn scan(param: Value) -> Result<(), Error> {
410
411 let output_format = get_output_format(&param);
412
413 let list = linux_tape_device_list();
414
415 if output_format == "json-pretty" {
416 println!("{}", serde_json::to_string_pretty(&list)?);
417 return Ok(());
418 }
419
420 if output_format == "json" {
421 println!("{}", serde_json::to_string(&list)?);
422 return Ok(());
423 }
424
425 if output_format != "text" {
426 bail!("unknown output format '{}'", output_format);
427 }
428
429 for item in list.iter() {
430 println!("{} ({}/{}/{})", item.path, item.vendor, item.model, item.serial);
431 }
432
433 Ok(())
434}
435
436#[api(
437 input: {
438 properties: {
439 drive: {
440 schema: DRIVE_NAME_SCHEMA,
441 optional: true,
442 },
443 device: {
444 schema: LINUX_DRIVE_PATH_SCHEMA,
445 optional: true,
446 },
447 "output-format": {
448 schema: OUTPUT_FORMAT,
449 optional: true,
450 },
451 },
452 },
453)]
454/// Drive Status
455fn status(param: Value) -> Result<(), Error> {
456
457 let output_format = get_output_format(&param);
458
459 let mut handle = get_tape_handle(&param)?;
460 let result = handle.get_drive_and_media_status();
461
462 if output_format == "json-pretty" {
463 let result = result.map_err(|err: Error| err.to_string());
464 println!("{}", serde_json::to_string_pretty(&result)?);
465 return Ok(());
466 }
467
468 if output_format == "json" {
469 let result = result.map_err(|err: Error| err.to_string());
470 println!("{}", serde_json::to_string(&result)?);
471 return Ok(());
472 }
473
474 if output_format != "text" {
475 bail!("unknown output format '{}'", output_format);
476 }
477
478 let status = result?;
479
480 println!("{}", serde_json::to_string_pretty(&status)?);
481
482 Ok(())
483}
484
485#[api(
486 input: {
487 properties: {
488 drive: {
489 schema: DRIVE_NAME_SCHEMA,
490 optional: true,
491 },
492 device: {
493 schema: LINUX_DRIVE_PATH_SCHEMA,
494 optional: true,
495 },
496 "output-format": {
497 schema: OUTPUT_FORMAT,
498 optional: true,
499 },
500 },
501 },
502)]
503/// Volume Statistics
504fn volume_statistics(param: Value) -> Result<(), Error> {
505
506 let output_format = get_output_format(&param);
507
508 let mut handle = get_tape_handle(&param)?;
509 let result = handle.volume_statistics();
510
511 if output_format == "json-pretty" {
512 let result = result.map_err(|err: Error| err.to_string());
513 println!("{}", serde_json::to_string_pretty(&result)?);
514 return Ok(());
515 }
516
517 if output_format == "json" {
518 let result = result.map_err(|err: Error| err.to_string());
519 println!("{}", serde_json::to_string(&result)?);
520 return Ok(());
521 }
522
523 if output_format != "text" {
524 bail!("unknown output format '{}'", output_format);
525 }
526
527 let data = result?;
528
529 println!("{}", serde_json::to_string_pretty(&data)?);
530
531 Ok(())
532}
533
83b8949a
DM
534#[api(
535 input: {
536 properties: {
537 drive: {
538 schema: DRIVE_NAME_SCHEMA,
539 optional: true,
540 },
541 device: {
542 schema: LINUX_DRIVE_PATH_SCHEMA,
543 optional: true,
544 },
545 count: {
546 schema: FILE_MARK_COUNT_SCHEMA,
547 optional: true,
548 },
549 },
550 },
551)]
552/// Write count (default 1) EOF marks at current position.
553fn weof(count: Option<i32>, param: Value) -> Result<(), Error> {
554
555 let mut handle = get_tape_handle(&param)?;
556 handle.mtop(MTCmd::MTWEOF, count.unwrap_or(1), "write EOF mark")?;
557
558 Ok(())
559}
560
1e041082
DM
561fn main() -> Result<(), Error> {
562
563 let uid = nix::unistd::Uid::current();
564
565 let username = match nix::unistd::User::from_uid(uid)? {
566 Some(user) => user.name,
567 None => bail!("unable to get user name"),
568 };
569
570 let std_cmd = |method| {
571 CliCommand::new(method)
572 .completion_cb("drive", complete_drive_name)
573 .completion_cb("device", complete_drive_path)
574 };
575
576 let cmd_def = CliCommandMap::new()
8e6ad430
DM
577 .insert("bsf", std_cmd(&API_METHOD_BSF).arg_param(&["count"]))
578 .insert("bsfm", std_cmd(&API_METHOD_BSFM).arg_param(&["count"]))
1e041082
DM
579 .insert("cartridge-memory", std_cmd(&API_METHOD_CARTRIDGE_MEMORY))
580 .insert("eject", std_cmd(&API_METHOD_EJECT))
581 .insert("eod", std_cmd(&API_METHOD_EOD))
b22c6187 582 .insert("erase", std_cmd(&API_METHOD_ERASE))
8e6ad430
DM
583 .insert("fsf", std_cmd(&API_METHOD_FSF).arg_param(&["count"]))
584 .insert("fsfm", std_cmd(&API_METHOD_FSFM).arg_param(&["count"]))
1e041082
DM
585 .insert("load", std_cmd(&API_METHOD_LOAD))
586 .insert("rewind", std_cmd(&API_METHOD_REWIND))
587 .insert("scan", CliCommand::new(&API_METHOD_SCAN))
588 .insert("status", std_cmd(&API_METHOD_STATUS))
589 .insert("volume-statistics", std_cmd(&API_METHOD_VOLUME_STATISTICS))
8e6ad430 590 .insert("weof", std_cmd(&API_METHOD_WEOF).arg_param(&["count"]))
1e041082
DM
591 ;
592
593 let mut rpcenv = CliEnvironment::new();
594 rpcenv.set_auth_id(Some(format!("{}@pam", username)));
595
596 run_cli_command(cmd_def, rpcenv, None);
597
598 Ok(())
599}