]> git.proxmox.com Git - proxmox-backup.git/blame - src/bin/pmt.rs
tape: implement locate_file without LOCATE(10)
[proxmox-backup.git] / src / bin / pmt.rs
CommitLineData
1e041082
DM
1/// Control magnetic tape drive operation
2///
a79082a0
DM
3/// This is a Rust implementation, using the Proxmox userspace tape
4/// driver. This is meant as replacement fot the 'mt' command line
5/// tool.
1e041082
DM
6///
7/// Features:
8///
9/// - written in Rust
a79082a0 10/// - use Proxmox userspace driver (using SG_IO)
1e041082
DM
11/// - optional json output format
12/// - support tape alert flags
13/// - support volume statistics
14/// - read cartridge memory
15
1e041082
DM
16use anyhow::{bail, Error};
17use serde_json::Value;
18
19use proxmox::{
20 api::{
21 api,
22 cli::*,
83b8949a
DM
23 schema::{
24 Schema,
25 IntegerSchema,
8937c659
DM
26 StringSchema,
27 ArraySchema,
83b8949a 28 },
1e041082
DM
29 RpcEnvironment,
30 },
31};
32
83b8949a
DM
33pub const FILE_MARK_COUNT_SCHEMA: Schema =
34 IntegerSchema::new("File mark count.")
35 .minimum(1)
e9959962 36 .maximum(i32::MAX as isize)
83b8949a
DM
37 .schema();
38
5d6379f8
DM
39pub const FILE_MARK_POSITION_SCHEMA: Schema =
40 IntegerSchema::new("File mark position (0 is BOT).")
41 .minimum(0)
42 .maximum(i32::MAX as isize)
43 .schema();
44
d690d145
DM
45pub const RECORD_COUNT_SCHEMA: Schema =
46 IntegerSchema::new("Record count.")
47 .minimum(1)
48 .maximum(i32::MAX as isize)
49 .schema();
50
8937c659 51pub const DRIVE_OPTION_SCHEMA: Schema = StringSchema::new(
a79082a0 52 "Lto Tape Driver Option, either numeric value or option name.")
8937c659
DM
53 .schema();
54
55pub const DRIVE_OPTION_LIST_SCHEMA: Schema =
56 ArraySchema::new("Drive Option List.", &DRIVE_OPTION_SCHEMA)
0d4e4cae 57 .min_length(1)
8937c659
DM
58 .schema();
59
1e041082 60use proxmox_backup::{
1e041082
DM
61 config::{
62 self,
63 drive::complete_drive_name,
64 },
1e041082 65 api2::types::{
a79082a0 66 LTO_DRIVE_PATH_SCHEMA,
1e041082 67 DRIVE_NAME_SCHEMA,
a79082a0 68 LtoTapeDrive,
1e041082
DM
69 },
70 tape::{
71 complete_drive_path,
a79082a0 72 lto_tape_device_list,
1e041082
DM
73 drive::{
74 TapeDriver,
a79082a0
DM
75 LtoTapeHandle,
76 open_lto_tape_device,
28f60e52 77 },
1e041082
DM
78 },
79};
80
a79082a0 81fn get_tape_handle(param: &Value) -> Result<LtoTapeHandle, Error> {
1e041082
DM
82
83 if let Some(name) = param["drive"].as_str() {
84 let (config, _digest) = config::drive::config()?;
a79082a0 85 let drive: LtoTapeDrive = config.lookup("lto", &name)?;
1e041082 86 eprintln!("using device {}", drive.path);
a79082a0 87 return LtoTapeHandle::new(open_lto_tape_device(&drive.path)?);
1e041082
DM
88 }
89
90 if let Some(device) = param["device"].as_str() {
91 eprintln!("using device {}", device);
a79082a0 92 return LtoTapeHandle::new(open_lto_tape_device(&device)?);
1e041082
DM
93 }
94
95 if let Ok(name) = std::env::var("PROXMOX_TAPE_DRIVE") {
96 let (config, _digest) = config::drive::config()?;
a79082a0 97 let drive: LtoTapeDrive = config.lookup("lto", &name)?;
1e041082 98 eprintln!("using device {}", drive.path);
a79082a0 99 return LtoTapeHandle::new(open_lto_tape_device(&drive.path)?);
1e041082
DM
100 }
101
102 if let Ok(device) = std::env::var("TAPE") {
103 eprintln!("using device {}", device);
a79082a0 104 return LtoTapeHandle::new(open_lto_tape_device(&device)?);
1e041082
DM
105 }
106
107 let (config, _digest) = config::drive::config()?;
108
109 let mut drive_names = Vec::new();
110 for (name, (section_type, _)) in config.sections.iter() {
a79082a0 111 if section_type != "lto" { continue; }
1e041082
DM
112 drive_names.push(name);
113 }
114
115 if drive_names.len() == 1 {
116 let name = drive_names[0];
a79082a0 117 let drive: LtoTapeDrive = config.lookup("lto", &name)?;
1e041082 118 eprintln!("using device {}", drive.path);
a79082a0 119 return LtoTapeHandle::new(open_lto_tape_device(&drive.path)?);
1e041082
DM
120 }
121
122 bail!("no drive/device specified");
123}
124
85ef6244
DM
125#[api(
126 input: {
127 properties: {
128 drive: {
129 schema: DRIVE_NAME_SCHEMA,
130 optional: true,
131 },
132 device: {
a79082a0 133 schema: LTO_DRIVE_PATH_SCHEMA,
85ef6244
DM
134 optional: true,
135 },
136 count: {
5d6379f8 137 schema: FILE_MARK_POSITION_SCHEMA,
85ef6244
DM
138 },
139 },
140 },
141)]
5d6379f8
DM
142/// Position the tape at the beginning of the count file (after
143/// filemark count)
144fn asf(count: u64, param: Value) -> Result<(), Error> {
85ef6244
DM
145
146 let mut handle = get_tape_handle(&param)?;
147
5d6379f8 148 handle.locate_file(count)?;
85ef6244
DM
149
150 Ok(())
151}
152
153
1f31d06f
DM
154#[api(
155 input: {
156 properties: {
157 drive: {
158 schema: DRIVE_NAME_SCHEMA,
159 optional: true,
160 },
161 device: {
a79082a0 162 schema: LTO_DRIVE_PATH_SCHEMA,
1f31d06f
DM
163 optional: true,
164 },
165 count: {
83b8949a 166 schema: FILE_MARK_COUNT_SCHEMA,
1f31d06f 167 },
83b8949a 168 },
1f31d06f
DM
169 },
170)]
171/// Backward space count files (position before file mark).
172///
173/// The tape is positioned on the last block of the previous file.
589c4dad 174fn bsf(count: usize, param: Value) -> Result<(), Error> {
1f31d06f
DM
175
176 let mut handle = get_tape_handle(&param)?;
177
178 handle.backward_space_count_files(count)?;
179
180 Ok(())
181}
182
8e6ad430
DM
183
184#[api(
185 input: {
186 properties: {
187 drive: {
188 schema: DRIVE_NAME_SCHEMA,
189 optional: true,
190 },
191 device: {
a79082a0 192 schema: LTO_DRIVE_PATH_SCHEMA,
8e6ad430
DM
193 optional: true,
194 },
195 count: {
196 schema: FILE_MARK_COUNT_SCHEMA,
197 },
198 },
199 },
200)]
201/// Backward space count files, then forward space one record (position after file mark).
202///
203/// This leaves the tape positioned at the first block of the file
204/// that is count - 1 files before the current file.
a79082a0 205fn bsfm(count: usize, param: Value) -> Result<(), Error> {
8e6ad430
DM
206
207 let mut handle = get_tape_handle(&param)?;
208
a79082a0
DM
209 handle.backward_space_count_files(count)?;
210 handle.forward_space_count_files(1)?;
8e6ad430
DM
211
212 Ok(())
213}
214
215
d690d145
DM
216#[api(
217 input: {
218 properties: {
219 drive: {
220 schema: DRIVE_NAME_SCHEMA,
221 optional: true,
222 },
223 device: {
a79082a0 224 schema: LTO_DRIVE_PATH_SCHEMA,
d690d145
DM
225 optional: true,
226 },
227 count: {
228 schema: RECORD_COUNT_SCHEMA,
229 },
230 },
231 },
232)]
233/// Backward space records.
7f745967 234fn bsr(count: usize, param: Value) -> Result<(), Error> {
d690d145
DM
235
236 let mut handle = get_tape_handle(&param)?;
237
7f745967 238 handle.backward_space_count_records(count)?;
d690d145
DM
239
240 Ok(())
241}
242
243
1e041082
DM
244#[api(
245 input: {
246 properties: {
247 drive: {
248 schema: DRIVE_NAME_SCHEMA,
249 optional: true,
250 },
251 device: {
a79082a0 252 schema: LTO_DRIVE_PATH_SCHEMA,
1e041082
DM
253 optional: true,
254 },
255 "output-format": {
256 schema: OUTPUT_FORMAT,
257 optional: true,
258 },
259 },
260 },
261)]
262/// Read Cartridge Memory
263fn cartridge_memory(param: Value) -> Result<(), Error> {
264
265 let output_format = get_output_format(&param);
266
267 let mut handle = get_tape_handle(&param)?;
268 let result = handle.cartridge_memory();
269
270 if output_format == "json-pretty" {
271 let result = result.map_err(|err: Error| err.to_string());
272 println!("{}", serde_json::to_string_pretty(&result)?);
273 return Ok(());
274 }
275
276 if output_format == "json" {
277 let result = result.map_err(|err: Error| err.to_string());
278 println!("{}", serde_json::to_string(&result)?);
279 return Ok(());
280 }
281
282 if output_format != "text" {
283 bail!("unknown output format '{}'", output_format);
284 }
285
286 let list = result?;
287
288 for item in list {
289 println!("{}|{}|{}", item.id, item.name, item.value);
290 }
291
292 Ok(())
293}
294
5ca5f8da
DM
295#[api(
296 input: {
297 properties: {
298 drive: {
299 schema: DRIVE_NAME_SCHEMA,
300 optional: true,
301 },
302 device: {
a79082a0 303 schema: LTO_DRIVE_PATH_SCHEMA,
5ca5f8da
DM
304 optional: true,
305 },
306 "output-format": {
307 schema: OUTPUT_FORMAT,
308 optional: true,
309 },
310 },
311 },
312)]
313/// Read Tape Alert Flags
314fn tape_alert_flags(param: Value) -> Result<(), Error> {
315
316 let output_format = get_output_format(&param);
317
318 let mut handle = get_tape_handle(&param)?;
319 let result = handle.tape_alert_flags()
320 .map(|flags| format!("{:?}", flags));
321
322 if output_format == "json-pretty" {
323 let result = result.map_err(|err: Error| err.to_string());
324 println!("{}", serde_json::to_string_pretty(&result)?);
325 return Ok(());
326 }
327
328 if output_format == "json" {
329 let result = result.map_err(|err: Error| err.to_string());
330 println!("{}", serde_json::to_string(&result)?);
331 return Ok(());
332 }
333
334 if output_format != "text" {
335 bail!("unknown output format '{}'", output_format);
336 }
337
338 let flags = result?;
339 println!("Tape Alert Flags: {}", flags);
340
341 Ok(())
342}
343
1e041082
DM
344#[api(
345 input: {
346 properties: {
347 drive: {
348 schema: DRIVE_NAME_SCHEMA,
349 optional: true,
350 },
351 device: {
a79082a0 352 schema: LTO_DRIVE_PATH_SCHEMA,
1e041082
DM
353 optional: true,
354 },
355 },
356 },
357)]
358/// Eject drive media
359fn eject(param: Value) -> Result<(), Error> {
360
361 let mut handle = get_tape_handle(&param)?;
362 handle.eject_media()?;
363
364 Ok(())
365}
366
367
368#[api(
369 input: {
370 properties: {
371 drive: {
372 schema: DRIVE_NAME_SCHEMA,
373 optional: true,
374 },
375 device: {
a79082a0 376 schema: LTO_DRIVE_PATH_SCHEMA,
1e041082
DM
377 optional: true,
378 },
379 },
380 },
381)]
382/// Move to end of media
383fn eod(param: Value) -> Result<(), Error> {
384
385 let mut handle = get_tape_handle(&param)?;
7b11a809 386 handle.move_to_eom(false)?;
1e041082
DM
387
388 Ok(())
389}
390
391
b22c6187
DM
392#[api(
393 input: {
394 properties: {
395 drive: {
396 schema: DRIVE_NAME_SCHEMA,
397 optional: true,
398 },
399 device: {
a79082a0 400 schema: LTO_DRIVE_PATH_SCHEMA,
b22c6187
DM
401 optional: true,
402 },
403 fast: {
404 description: "Use fast erase.",
405 type: bool,
406 optional: true,
407 default: true,
408 },
409 },
410 },
411)]
e29f456e 412/// Erase media (from current position)
b22c6187
DM
413fn erase(fast: Option<bool>, param: Value) -> Result<(), Error> {
414
415 let mut handle = get_tape_handle(&param)?;
416 handle.erase_media(fast.unwrap_or(true))?;
417
418 Ok(())
419}
420
e29f456e
DM
421#[api(
422 input: {
423 properties: {
424 drive: {
425 schema: DRIVE_NAME_SCHEMA,
426 optional: true,
427 },
428 device: {
429 schema: LTO_DRIVE_PATH_SCHEMA,
430 optional: true,
431 },
432 fast: {
433 description: "Use fast erase.",
434 type: bool,
435 optional: true,
436 default: true,
437 },
438 },
439 },
440)]
441/// Format media, single partition
442fn format(fast: Option<bool>, param: Value) -> Result<(), Error> {
443
444 let mut handle = get_tape_handle(&param)?;
445 handle.format_media(fast.unwrap_or(true))?;
446
447 Ok(())
448}
449
2f2e83c8
DM
450#[api(
451 input: {
452 properties: {
453 drive: {
454 schema: DRIVE_NAME_SCHEMA,
455 optional: true,
456 },
457 device: {
a79082a0 458 schema: LTO_DRIVE_PATH_SCHEMA,
2f2e83c8
DM
459 optional: true,
460 },
461 count: {
83b8949a 462 schema: FILE_MARK_COUNT_SCHEMA,
2f2e83c8
DM
463 },
464 },
465 },
466)]
467/// Forward space count files (position after file mark).
468///
469/// The tape is positioned on the first block of the next file.
589c4dad 470fn fsf(count: usize, param: Value) -> Result<(), Error> {
2f2e83c8
DM
471
472 let mut handle = get_tape_handle(&param)?;
473
474 handle.forward_space_count_files(count)?;
475
476 Ok(())
477}
478
8e6ad430
DM
479#[api(
480 input: {
481 properties: {
482 drive: {
483 schema: DRIVE_NAME_SCHEMA,
484 optional: true,
485 },
486 device: {
a79082a0 487 schema: LTO_DRIVE_PATH_SCHEMA,
8e6ad430
DM
488 optional: true,
489 },
490 count: {
491 schema: FILE_MARK_COUNT_SCHEMA,
492 },
493 },
494 },
495)]
496/// Forward space count files, then backward space one record (position before file mark).
497///
498/// This leaves the tape positioned at the last block of the file that
499/// is count - 1 files past the current file.
a79082a0 500fn fsfm(count: usize, param: Value) -> Result<(), Error> {
8e6ad430
DM
501
502 let mut handle = get_tape_handle(&param)?;
503
a79082a0
DM
504 handle.forward_space_count_files(count)?;
505 handle.backward_space_count_files(1)?;
8e6ad430
DM
506
507 Ok(())
508}
509
b22c6187 510
d690d145
DM
511#[api(
512 input: {
513 properties: {
514 drive: {
515 schema: DRIVE_NAME_SCHEMA,
516 optional: true,
517 },
518 device: {
a79082a0 519 schema: LTO_DRIVE_PATH_SCHEMA,
d690d145
DM
520 optional: true,
521 },
522 count: {
523 schema: RECORD_COUNT_SCHEMA,
524 },
525 },
526 },
527)]
528/// Forward space records.
7f745967 529fn fsr(count: usize, param: Value) -> Result<(), Error> {
d690d145
DM
530
531 let mut handle = get_tape_handle(&param)?;
532
7f745967 533 handle.forward_space_count_records(count)?;
d690d145
DM
534
535 Ok(())
536}
537
538
1e041082
DM
539#[api(
540 input: {
541 properties: {
542 drive: {
543 schema: DRIVE_NAME_SCHEMA,
544 optional: true,
545 },
546 device: {
a79082a0 547 schema: LTO_DRIVE_PATH_SCHEMA,
1e041082
DM
548 optional: true,
549 },
550 },
551 },
552)]
553/// Load media
554fn load(param: Value) -> Result<(), Error> {
555
556 let mut handle = get_tape_handle(&param)?;
a79082a0 557 handle.load()?;
1e041082
DM
558
559 Ok(())
560}
561
562
90769e56
DM
563#[api(
564 input: {
565 properties: {
566 drive: {
567 schema: DRIVE_NAME_SCHEMA,
568 optional: true,
569 },
570 device: {
a79082a0 571 schema: LTO_DRIVE_PATH_SCHEMA,
90769e56
DM
572 optional: true,
573 },
574 },
575 },
576)]
577/// Lock the tape drive door
578fn lock(param: Value) -> Result<(), Error> {
579
580 let mut handle = get_tape_handle(&param)?;
581
566b946f 582 handle.lock()?;
90769e56
DM
583
584 Ok(())
585}
586
587
1e041082
DM
588#[api(
589 input: {
590 properties: {
591 drive: {
592 schema: DRIVE_NAME_SCHEMA,
593 optional: true,
594 },
595 device: {
a79082a0 596 schema: LTO_DRIVE_PATH_SCHEMA,
1e041082
DM
597 optional: true,
598 },
599 },
600 },
601)]
602/// Rewind the tape
603fn rewind(param: Value) -> Result<(), Error> {
604
605 let mut handle = get_tape_handle(&param)?;
606 handle.rewind()?;
607
608 Ok(())
609}
610
611
612#[api(
613 input: {
614 properties: {
615 "output-format": {
616 schema: OUTPUT_FORMAT,
617 optional: true,
618 },
619 },
620 },
621)]
622/// Scan for existing tape changer devices
623fn scan(param: Value) -> Result<(), Error> {
624
625 let output_format = get_output_format(&param);
626
a79082a0 627 let list = lto_tape_device_list();
1e041082
DM
628
629 if output_format == "json-pretty" {
630 println!("{}", serde_json::to_string_pretty(&list)?);
631 return Ok(());
632 }
633
634 if output_format == "json" {
635 println!("{}", serde_json::to_string(&list)?);
636 return Ok(());
637 }
638
639 if output_format != "text" {
640 bail!("unknown output format '{}'", output_format);
641 }
642
643 for item in list.iter() {
644 println!("{} ({}/{}/{})", item.path, item.vendor, item.model, item.serial);
645 }
646
647 Ok(())
648}
649
650#[api(
651 input: {
652 properties: {
653 drive: {
654 schema: DRIVE_NAME_SCHEMA,
655 optional: true,
656 },
657 device: {
a79082a0 658 schema: LTO_DRIVE_PATH_SCHEMA,
1e041082
DM
659 optional: true,
660 },
661 "output-format": {
662 schema: OUTPUT_FORMAT,
663 optional: true,
664 },
665 },
666 },
667)]
668/// Drive Status
669fn status(param: Value) -> Result<(), Error> {
670
671 let output_format = get_output_format(&param);
672
673 let mut handle = get_tape_handle(&param)?;
674 let result = handle.get_drive_and_media_status();
675
676 if output_format == "json-pretty" {
677 let result = result.map_err(|err: Error| err.to_string());
678 println!("{}", serde_json::to_string_pretty(&result)?);
679 return Ok(());
680 }
681
682 if output_format == "json" {
683 let result = result.map_err(|err: Error| err.to_string());
684 println!("{}", serde_json::to_string(&result)?);
685 return Ok(());
686 }
687
688 if output_format != "text" {
689 bail!("unknown output format '{}'", output_format);
690 }
691
692 let status = result?;
693
694 println!("{}", serde_json::to_string_pretty(&status)?);
695
696 Ok(())
697}
698
90769e56
DM
699
700#[api(
701 input: {
702 properties: {
703 drive: {
704 schema: DRIVE_NAME_SCHEMA,
705 optional: true,
706 },
707 device: {
a79082a0 708 schema: LTO_DRIVE_PATH_SCHEMA,
90769e56
DM
709 optional: true,
710 },
711 },
712 },
713)]
714/// Unlock the tape drive door
715fn unlock(param: Value) -> Result<(), Error> {
716
717 let mut handle = get_tape_handle(&param)?;
718
566b946f 719 handle.unlock()?;
90769e56
DM
720
721 Ok(())
722}
723
724
1e041082
DM
725#[api(
726 input: {
727 properties: {
728 drive: {
729 schema: DRIVE_NAME_SCHEMA,
730 optional: true,
731 },
732 device: {
a79082a0 733 schema: LTO_DRIVE_PATH_SCHEMA,
1e041082
DM
734 optional: true,
735 },
736 "output-format": {
737 schema: OUTPUT_FORMAT,
738 optional: true,
739 },
740 },
741 },
742)]
743/// Volume Statistics
744fn volume_statistics(param: Value) -> Result<(), Error> {
745
746 let output_format = get_output_format(&param);
747
748 let mut handle = get_tape_handle(&param)?;
749 let result = handle.volume_statistics();
750
751 if output_format == "json-pretty" {
752 let result = result.map_err(|err: Error| err.to_string());
753 println!("{}", serde_json::to_string_pretty(&result)?);
754 return Ok(());
755 }
756
757 if output_format == "json" {
758 let result = result.map_err(|err: Error| err.to_string());
759 println!("{}", serde_json::to_string(&result)?);
760 return Ok(());
761 }
762
763 if output_format != "text" {
764 bail!("unknown output format '{}'", output_format);
765 }
766
767 let data = result?;
768
769 println!("{}", serde_json::to_string_pretty(&data)?);
770
771 Ok(())
772}
773
83b8949a
DM
774#[api(
775 input: {
776 properties: {
777 drive: {
778 schema: DRIVE_NAME_SCHEMA,
779 optional: true,
780 },
781 device: {
a79082a0 782 schema: LTO_DRIVE_PATH_SCHEMA,
83b8949a
DM
783 optional: true,
784 },
785 count: {
786 schema: FILE_MARK_COUNT_SCHEMA,
787 optional: true,
788 },
789 },
790 },
791)]
792/// Write count (default 1) EOF marks at current position.
a79082a0
DM
793fn weof(count: Option<usize>, param: Value) -> Result<(), Error> {
794
795 let count = count.unwrap_or(1);
83b8949a
DM
796
797 let mut handle = get_tape_handle(&param)?;
a79082a0
DM
798
799 handle.write_filemarks(count)?;
83b8949a
DM
800
801 Ok(())
802}
803
1e041082
DM
804fn main() -> Result<(), Error> {
805
806 let uid = nix::unistd::Uid::current();
807
808 let username = match nix::unistd::User::from_uid(uid)? {
809 Some(user) => user.name,
810 None => bail!("unable to get user name"),
811 };
812
813 let std_cmd = |method| {
814 CliCommand::new(method)
815 .completion_cb("drive", complete_drive_name)
816 .completion_cb("device", complete_drive_path)
817 };
818
819 let cmd_def = CliCommandMap::new()
4c209d6b 820 .usage_skip_options(&["device", "drive", "output-format"])
85ef6244 821 .insert("asf", std_cmd(&API_METHOD_ASF).arg_param(&["count"]))
8e6ad430
DM
822 .insert("bsf", std_cmd(&API_METHOD_BSF).arg_param(&["count"]))
823 .insert("bsfm", std_cmd(&API_METHOD_BSFM).arg_param(&["count"]))
d690d145 824 .insert("bsr", std_cmd(&API_METHOD_BSR).arg_param(&["count"]))
1e041082
DM
825 .insert("cartridge-memory", std_cmd(&API_METHOD_CARTRIDGE_MEMORY))
826 .insert("eject", std_cmd(&API_METHOD_EJECT))
827 .insert("eod", std_cmd(&API_METHOD_EOD))
b22c6187 828 .insert("erase", std_cmd(&API_METHOD_ERASE))
e29f456e 829 .insert("format", std_cmd(&API_METHOD_FORMAT))
8e6ad430
DM
830 .insert("fsf", std_cmd(&API_METHOD_FSF).arg_param(&["count"]))
831 .insert("fsfm", std_cmd(&API_METHOD_FSFM).arg_param(&["count"]))
d690d145 832 .insert("fsr", std_cmd(&API_METHOD_FSR).arg_param(&["count"]))
1e041082 833 .insert("load", std_cmd(&API_METHOD_LOAD))
90769e56 834 .insert("lock", std_cmd(&API_METHOD_LOCK))
1e041082
DM
835 .insert("rewind", std_cmd(&API_METHOD_REWIND))
836 .insert("scan", CliCommand::new(&API_METHOD_SCAN))
837 .insert("status", std_cmd(&API_METHOD_STATUS))
5ca5f8da 838 .insert("tape-alert-flags", std_cmd(&API_METHOD_TAPE_ALERT_FLAGS))
90769e56 839 .insert("unlock", std_cmd(&API_METHOD_UNLOCK))
1e041082 840 .insert("volume-statistics", std_cmd(&API_METHOD_VOLUME_STATISTICS))
8e6ad430 841 .insert("weof", std_cmd(&API_METHOD_WEOF).arg_param(&["count"]))
1e041082
DM
842 ;
843
844 let mut rpcenv = CliEnvironment::new();
845 rpcenv.set_auth_id(Some(format!("{}@pam", username)));
846
847 run_cli_command(cmd_def, rpcenv, None);
848
849 Ok(())
850}