]> git.proxmox.com Git - proxmox-backup.git/blob - src/bin/proxmox-tape.rs
thape: add read_tapedev_options, display driver options with status command
[proxmox-backup.git] / src / bin / proxmox-tape.rs
1 use anyhow::{format_err, Error};
2 use serde_json::{json, Value};
3
4 use proxmox::{
5 api::{
6 api,
7 cli::*,
8 RpcEnvironment,
9 section_config::SectionConfigData,
10 },
11 tools::{
12 time::strftime_local,
13 io::ReadExt,
14 },
15 };
16
17 use proxmox_backup::{
18 tools::format::{
19 HumanByte,
20 render_epoch,
21 render_bytes_human_readable,
22 },
23 client::{
24 connect_to_localhost,
25 view_task_result,
26 },
27 api2::{
28 self,
29 types::{
30 DATASTORE_SCHEMA,
31 DRIVE_NAME_SCHEMA,
32 MEDIA_LABEL_SCHEMA,
33 MEDIA_POOL_NAME_SCHEMA,
34 },
35 },
36 config::{
37 self,
38 datastore::complete_datastore_name,
39 drive::complete_drive_name,
40 media_pool::complete_pool_name,
41 },
42 tape::{
43 drive::open_drive,
44 complete_media_label_text,
45 complete_media_set_uuid,
46 file_formats::{
47 PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0,
48 PROXMOX_BACKUP_CONTENT_NAME,
49 MediaContentHeader,
50 },
51 },
52 };
53
54 mod proxmox_tape;
55 use proxmox_tape::*;
56
57 pub fn lookup_drive_name(
58 param: &Value,
59 config: &SectionConfigData,
60 ) -> Result<String, Error> {
61
62 let drive = param["drive"]
63 .as_str()
64 .map(String::from)
65 .or_else(|| std::env::var("PROXMOX_TAPE_DRIVE").ok())
66 .or_else(|| {
67
68 let mut drive_names = Vec::new();
69
70 for (name, (section_type, _)) in config.sections.iter() {
71
72 if !(section_type == "linux" || section_type == "virtual") { continue; }
73 drive_names.push(name);
74 }
75
76 if drive_names.len() == 1 {
77 Some(drive_names[0].to_owned())
78 } else {
79 None
80 }
81 })
82 .ok_or_else(|| format_err!("unable to get (default) drive name"))?;
83
84 Ok(drive)
85 }
86
87 #[api(
88 input: {
89 properties: {
90 drive: {
91 schema: DRIVE_NAME_SCHEMA,
92 optional: true,
93 },
94 fast: {
95 description: "Use fast erase.",
96 type: bool,
97 optional: true,
98 default: true,
99 },
100 "output-format": {
101 schema: OUTPUT_FORMAT,
102 optional: true,
103 },
104 },
105 },
106 )]
107 /// Erase media
108 async fn erase_media(param: Value) -> Result<(), Error> {
109
110 let output_format = get_output_format(&param);
111
112 let (config, _digest) = config::drive::config()?;
113
114 let drive = lookup_drive_name(&param, &config)?;
115
116 let mut client = connect_to_localhost()?;
117
118 let path = format!("api2/json/tape/drive/{}/erase-media", drive);
119 let result = client.post(&path, Some(param)).await?;
120
121 view_task_result(&mut client, result, &output_format).await?;
122
123 Ok(())
124 }
125
126 #[api(
127 input: {
128 properties: {
129 drive: {
130 schema: DRIVE_NAME_SCHEMA,
131 optional: true,
132 },
133 "output-format": {
134 schema: OUTPUT_FORMAT,
135 optional: true,
136 },
137 },
138 },
139 )]
140 /// Rewind tape
141 async fn rewind(param: Value) -> Result<(), Error> {
142
143 let output_format = get_output_format(&param);
144
145 let (config, _digest) = config::drive::config()?;
146
147 let drive = lookup_drive_name(&param, &config)?;
148
149 let mut client = connect_to_localhost()?;
150
151 let path = format!("api2/json/tape/drive/{}/rewind", drive);
152 let result = client.post(&path, Some(param)).await?;
153
154 view_task_result(&mut client, result, &output_format).await?;
155
156 Ok(())
157 }
158
159 #[api(
160 input: {
161 properties: {
162 drive: {
163 schema: DRIVE_NAME_SCHEMA,
164 optional: true,
165 },
166 "output-format": {
167 schema: OUTPUT_FORMAT,
168 optional: true,
169 },
170 },
171 },
172 )]
173 /// Eject/Unload drive media
174 async fn eject_media(param: Value) -> Result<(), Error> {
175
176 let output_format = get_output_format(&param);
177
178 let (config, _digest) = config::drive::config()?;
179
180 let drive = lookup_drive_name(&param, &config)?;
181
182 let mut client = connect_to_localhost()?;
183
184 let path = format!("api2/json/tape/drive/{}/eject-media", drive);
185 let result = client.post(&path, Some(param)).await?;
186
187 view_task_result(&mut client, result, &output_format).await?;
188
189 Ok(())
190 }
191
192 #[api(
193 input: {
194 properties: {
195 drive: {
196 schema: DRIVE_NAME_SCHEMA,
197 optional: true,
198 },
199 "label-text": {
200 schema: MEDIA_LABEL_SCHEMA,
201 },
202 },
203 },
204 )]
205 /// Load media with specified label
206 async fn load_media(param: Value) -> Result<(), Error> {
207
208 let (config, _digest) = config::drive::config()?;
209
210 let drive = lookup_drive_name(&param, &config)?;
211
212 let mut client = connect_to_localhost()?;
213
214 let path = format!("api2/json/tape/drive/{}/load-media", drive);
215 client.put(&path, Some(param)).await?;
216
217 Ok(())
218 }
219
220 #[api(
221 input: {
222 properties: {
223 drive: {
224 schema: DRIVE_NAME_SCHEMA,
225 optional: true,
226 },
227 "label-text": {
228 schema: MEDIA_LABEL_SCHEMA,
229 },
230 },
231 },
232 )]
233 /// Export media with specified label
234 async fn export_media(param: Value) -> Result<(), Error> {
235
236 let (config, _digest) = config::drive::config()?;
237
238 let drive = lookup_drive_name(&param, &config)?;
239
240 let mut client = connect_to_localhost()?;
241
242 let path = format!("api2/json/tape/drive/{}/export-media", drive);
243 client.put(&path, Some(param)).await?;
244
245 Ok(())
246 }
247
248 #[api(
249 input: {
250 properties: {
251 drive: {
252 schema: DRIVE_NAME_SCHEMA,
253 optional: true,
254 },
255 "source-slot": {
256 description: "Source slot number.",
257 type: u64,
258 minimum: 1,
259 },
260 },
261 },
262 )]
263 /// Load media from the specified slot
264 async fn load_media_from_slot(param: Value) -> Result<(), Error> {
265
266 let (config, _digest) = config::drive::config()?;
267
268 let drive = lookup_drive_name(&param, &config)?;
269
270 let mut client = connect_to_localhost()?;
271
272 let path = format!("api2/json/tape/drive/{}/load-slot", drive);
273 client.put(&path, Some(param)).await?;
274
275 Ok(())
276 }
277
278 #[api(
279 input: {
280 properties: {
281 drive: {
282 schema: DRIVE_NAME_SCHEMA,
283 optional: true,
284 },
285 "target-slot": {
286 description: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.",
287 type: u64,
288 minimum: 1,
289 optional: true,
290 },
291 },
292 },
293 )]
294 /// Unload media via changer
295 async fn unload_media(param: Value) -> Result<(), Error> {
296
297 let (config, _digest) = config::drive::config()?;
298
299 let drive = lookup_drive_name(&param, &config)?;
300
301 let mut client = connect_to_localhost()?;
302
303 let path = format!("api2/json/tape/drive/{}/unload", drive);
304 client.put(&path, Some(param)).await?;
305
306 Ok(())
307 }
308
309 #[api(
310 input: {
311 properties: {
312 pool: {
313 schema: MEDIA_POOL_NAME_SCHEMA,
314 optional: true,
315 },
316 drive: {
317 schema: DRIVE_NAME_SCHEMA,
318 optional: true,
319 },
320 "label-text": {
321 schema: MEDIA_LABEL_SCHEMA,
322 },
323 "output-format": {
324 schema: OUTPUT_FORMAT,
325 optional: true,
326 },
327 },
328 },
329 )]
330 /// Label media
331 async fn label_media(param: Value) -> Result<(), Error> {
332
333 let output_format = get_output_format(&param);
334
335 let (config, _digest) = config::drive::config()?;
336
337 let drive = lookup_drive_name(&param, &config)?;
338
339 let mut client = connect_to_localhost()?;
340
341 let path = format!("api2/json/tape/drive/{}/label-media", drive);
342 let result = client.post(&path, Some(param)).await?;
343
344 view_task_result(&mut client, result, &output_format).await?;
345
346 Ok(())
347 }
348
349 #[api(
350 input: {
351 properties: {
352 drive: {
353 schema: DRIVE_NAME_SCHEMA,
354 optional: true,
355 },
356 inventorize: {
357 description: "Inventorize media",
358 type: bool,
359 optional: true,
360 },
361 "output-format": {
362 schema: OUTPUT_FORMAT,
363 optional: true,
364 },
365 },
366 },
367 )]
368 /// Read media label
369 async fn read_label(param: Value) -> Result<(), Error> {
370
371 let output_format = get_output_format(&param);
372
373 let (config, _digest) = config::drive::config()?;
374
375 let drive = lookup_drive_name(&param, &config)?;
376
377 let client = connect_to_localhost()?;
378
379 let path = format!("api2/json/tape/drive/{}/read-label", drive);
380 let mut result = client.get(&path, Some(param)).await?;
381 let mut data = result["data"].take();
382
383 let info = &api2::tape::drive::API_METHOD_READ_LABEL;
384
385 let options = default_table_format_options()
386 .column(ColumnConfig::new("label-text"))
387 .column(ColumnConfig::new("uuid"))
388 .column(ColumnConfig::new("ctime").renderer(render_epoch))
389 .column(ColumnConfig::new("pool"))
390 .column(ColumnConfig::new("media-set-uuid"))
391 .column(ColumnConfig::new("media-set-ctime").renderer(render_epoch))
392 .column(ColumnConfig::new("encryption-key-fingerprint"))
393 ;
394
395 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
396
397 Ok(())
398 }
399
400 #[api(
401 input: {
402 properties: {
403 "output-format": {
404 schema: OUTPUT_FORMAT,
405 optional: true,
406 },
407 drive: {
408 schema: DRIVE_NAME_SCHEMA,
409 optional: true,
410 },
411 "read-labels": {
412 description: "Load unknown tapes and try read labels",
413 type: bool,
414 optional: true,
415 },
416 "read-all-labels": {
417 description: "Load all tapes and try read labels (even if already inventoried)",
418 type: bool,
419 optional: true,
420 },
421 },
422 },
423 )]
424 /// List (and update) media labels (Changer Inventory)
425 async fn inventory(
426 read_labels: Option<bool>,
427 read_all_labels: Option<bool>,
428 param: Value,
429 ) -> Result<(), Error> {
430
431 let output_format = get_output_format(&param);
432
433 let (config, _digest) = config::drive::config()?;
434 let drive = lookup_drive_name(&param, &config)?;
435
436 let do_read = read_labels.unwrap_or(false) || read_all_labels.unwrap_or(false);
437
438 let mut client = connect_to_localhost()?;
439
440 let path = format!("api2/json/tape/drive/{}/inventory", drive);
441
442 if do_read {
443
444 let mut param = json!({});
445 if let Some(true) = read_all_labels {
446 param["read-all-labels"] = true.into();
447 }
448
449 let result = client.put(&path, Some(param)).await?; // update inventory
450 view_task_result(&mut client, result, &output_format).await?;
451 }
452
453 let mut result = client.get(&path, None).await?;
454 let mut data = result["data"].take();
455
456 let info = &api2::tape::drive::API_METHOD_INVENTORY;
457
458 let options = default_table_format_options()
459 .column(ColumnConfig::new("label-text"))
460 .column(ColumnConfig::new("uuid"))
461 ;
462
463 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
464
465 Ok(())
466 }
467
468 #[api(
469 input: {
470 properties: {
471 pool: {
472 schema: MEDIA_POOL_NAME_SCHEMA,
473 optional: true,
474 },
475 drive: {
476 schema: DRIVE_NAME_SCHEMA,
477 optional: true,
478 },
479 "output-format": {
480 schema: OUTPUT_FORMAT,
481 optional: true,
482 },
483 },
484 },
485 )]
486 /// Label media with barcodes from changer device
487 async fn barcode_label_media(param: Value) -> Result<(), Error> {
488
489 let output_format = get_output_format(&param);
490
491 let (config, _digest) = config::drive::config()?;
492
493 let drive = lookup_drive_name(&param, &config)?;
494
495 let mut client = connect_to_localhost()?;
496
497 let path = format!("api2/json/tape/drive/{}/barcode-label-media", drive);
498 let result = client.post(&path, Some(param)).await?;
499
500 view_task_result(&mut client, result, &output_format).await?;
501
502 Ok(())
503 }
504
505 #[api(
506 input: {
507 properties: {
508 drive: {
509 schema: DRIVE_NAME_SCHEMA,
510 optional: true,
511 },
512 },
513 },
514 )]
515 /// Move to end of media (MTEOM, used to debug)
516 fn move_to_eom(param: Value) -> Result<(), Error> {
517
518 let (config, _digest) = config::drive::config()?;
519
520 let drive = lookup_drive_name(&param, &config)?;
521 let mut drive = open_drive(&config, &drive)?;
522
523 drive.move_to_eom()?;
524
525 Ok(())
526 }
527
528 #[api(
529 input: {
530 properties: {
531 drive: {
532 schema: DRIVE_NAME_SCHEMA,
533 optional: true,
534 },
535 },
536 },
537 )]
538 /// Rewind, then read media contents and print debug info
539 ///
540 /// Note: This reads unless the driver returns an IO Error, so this
541 /// method is expected to fails when we reach EOT.
542 fn debug_scan(param: Value) -> Result<(), Error> {
543
544 let (config, _digest) = config::drive::config()?;
545
546 let drive = lookup_drive_name(&param, &config)?;
547 let mut drive = open_drive(&config, &drive)?;
548
549 println!("rewinding tape");
550 drive.rewind()?;
551
552 loop {
553 let file_number = drive.current_file_number()?;
554
555 match drive.read_next_file()? {
556 None => {
557 println!("EOD");
558 continue;
559 },
560 Some(mut reader) => {
561 println!("got file number {}", file_number);
562
563 let header: Result<MediaContentHeader, _> = unsafe { reader.read_le_value() };
564 match header {
565 Ok(header) => {
566 if header.magic != PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0 {
567 println!("got MediaContentHeader with wrong magic: {:?}", header.magic);
568 } else if let Some(name) = PROXMOX_BACKUP_CONTENT_NAME.get(&header.content_magic) {
569 println!("got content header: {}", name);
570 println!(" uuid: {}", header.content_uuid());
571 println!(" ctime: {}", strftime_local("%c", header.ctime)?);
572 println!(" hsize: {}", HumanByte::from(header.size as usize));
573 println!(" part: {}", header.part_number);
574 } else {
575 println!("got unknown content header: {:?}", header.content_magic);
576 }
577 }
578 Err(err) => {
579 println!("unable to read content header - {}", err);
580 }
581 }
582 let bytes = reader.skip_to_end()?;
583 println!("skipped {}", HumanByte::from(bytes));
584 }
585 }
586 }
587 }
588
589 #[api(
590 input: {
591 properties: {
592 drive: {
593 schema: DRIVE_NAME_SCHEMA,
594 optional: true,
595 },
596 "output-format": {
597 schema: OUTPUT_FORMAT,
598 optional: true,
599 },
600 },
601 },
602 )]
603 /// Read Cartridge Memory (Medium auxiliary memory attributes)
604 async fn cartridge_memory(param: Value) -> Result<(), Error> {
605
606 let output_format = get_output_format(&param);
607
608 let (config, _digest) = config::drive::config()?;
609
610 let drive = lookup_drive_name(&param, &config)?;
611
612 let client = connect_to_localhost()?;
613
614 let path = format!("api2/json/tape/drive/{}/cartridge-memory", drive);
615 let mut result = client.get(&path, Some(param)).await?;
616 let mut data = result["data"].take();
617
618 let info = &api2::tape::drive::API_METHOD_CARTRIDGE_MEMORY;
619
620 let options = default_table_format_options()
621 .column(ColumnConfig::new("id"))
622 .column(ColumnConfig::new("name"))
623 .column(ColumnConfig::new("value"))
624 ;
625
626 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
627 Ok(())
628 }
629
630 #[api(
631 input: {
632 properties: {
633 drive: {
634 schema: DRIVE_NAME_SCHEMA,
635 optional: true,
636 },
637 "output-format": {
638 schema: OUTPUT_FORMAT,
639 optional: true,
640 },
641 },
642 },
643 )]
644 /// Read Volume Statistics (SCSI log page 17h)
645 async fn volume_statistics(param: Value) -> Result<(), Error> {
646
647 let output_format = get_output_format(&param);
648
649 let (config, _digest) = config::drive::config()?;
650
651 let drive = lookup_drive_name(&param, &config)?;
652
653 let client = connect_to_localhost()?;
654
655 let path = format!("api2/json/tape/drive/{}/volume-statistics", drive);
656 let mut result = client.get(&path, Some(param)).await?;
657 let mut data = result["data"].take();
658
659 let info = &api2::tape::drive::API_METHOD_VOLUME_STATISTICS;
660
661 let options = default_table_format_options();
662
663 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
664
665 Ok(())
666 }
667
668 #[api(
669 input: {
670 properties: {
671 drive: {
672 schema: DRIVE_NAME_SCHEMA,
673 optional: true,
674 },
675 "output-format": {
676 schema: OUTPUT_FORMAT,
677 optional: true,
678 },
679 },
680 },
681 )]
682 /// Get drive/media status
683 async fn status(param: Value) -> Result<(), Error> {
684
685 let output_format = get_output_format(&param);
686
687 let (config, _digest) = config::drive::config()?;
688
689 let drive = lookup_drive_name(&param, &config)?;
690
691 let client = connect_to_localhost()?;
692
693 let path = format!("api2/json/tape/drive/{}/status", drive);
694 let mut result = client.get(&path, Some(param)).await?;
695 let mut data = result["data"].take();
696
697 let info = &api2::tape::drive::API_METHOD_STATUS;
698
699 let render_percentage = |value: &Value, _record: &Value| {
700 match value.as_f64() {
701 Some(wearout) => Ok(format!("{:.2}%", wearout*100.0)),
702 None => Ok(String::from("ERROR")), // should never happen
703 }
704 };
705
706 let options = default_table_format_options()
707 .column(ColumnConfig::new("blocksize"))
708 .column(ColumnConfig::new("density"))
709 .column(ColumnConfig::new("status"))
710 .column(ColumnConfig::new("options"))
711 .column(ColumnConfig::new("alert-flags"))
712 .column(ColumnConfig::new("file-number"))
713 .column(ColumnConfig::new("block-number"))
714 .column(ColumnConfig::new("manufactured").renderer(render_epoch))
715 .column(ColumnConfig::new("bytes-written").renderer(render_bytes_human_readable))
716 .column(ColumnConfig::new("bytes-read").renderer(render_bytes_human_readable))
717 .column(ColumnConfig::new("medium-passes"))
718 .column(ColumnConfig::new("medium-wearout").renderer(render_percentage))
719 .column(ColumnConfig::new("volume-mounts"))
720 ;
721
722 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
723
724 Ok(())
725 }
726
727 #[api(
728 input: {
729 properties: {
730 drive: {
731 schema: DRIVE_NAME_SCHEMA,
732 optional: true,
733 },
734 "output-format": {
735 schema: OUTPUT_FORMAT,
736 optional: true,
737 },
738 },
739 },
740 )]
741 /// Clean drive
742 async fn clean_drive(param: Value) -> Result<(), Error> {
743
744 let output_format = get_output_format(&param);
745
746 let (config, _digest) = config::drive::config()?;
747
748 let drive = lookup_drive_name(&param, &config)?;
749
750 let mut client = connect_to_localhost()?;
751
752 let path = format!("api2/json/tape/drive/{}/clean-drive", drive);
753 let result = client.post(&path, Some(param)).await?;
754
755 view_task_result(&mut client, result, &output_format).await?;
756
757 Ok(())
758 }
759
760 #[api(
761 input: {
762 properties: {
763 store: {
764 schema: DATASTORE_SCHEMA,
765 },
766 pool: {
767 schema: MEDIA_POOL_NAME_SCHEMA,
768 },
769 drive: {
770 schema: DRIVE_NAME_SCHEMA,
771 optional: true,
772 },
773 "eject-media": {
774 description: "Eject media upon job completion.",
775 type: bool,
776 optional: true,
777 },
778 "export-media-set": {
779 description: "Export media set upon job completion.",
780 type: bool,
781 optional: true,
782 },
783 "output-format": {
784 schema: OUTPUT_FORMAT,
785 optional: true,
786 },
787 },
788 },
789 )]
790 /// Backup datastore to tape media pool
791 async fn backup(mut param: Value) -> Result<(), Error> {
792
793 let output_format = get_output_format(&param);
794
795 let (config, _digest) = config::drive::config()?;
796
797 param["drive"] = lookup_drive_name(&param, &config)?.into();
798
799 let mut client = connect_to_localhost()?;
800
801 let result = client.post("api2/json/tape/backup", Some(param)).await?;
802
803 view_task_result(&mut client, result, &output_format).await?;
804
805 Ok(())
806 }
807
808 #[api(
809 input: {
810 properties: {
811 store: {
812 schema: DATASTORE_SCHEMA,
813 },
814 drive: {
815 schema: DRIVE_NAME_SCHEMA,
816 optional: true,
817 },
818 "media-set": {
819 description: "Media set UUID.",
820 type: String,
821 },
822 "output-format": {
823 schema: OUTPUT_FORMAT,
824 optional: true,
825 },
826 },
827 },
828 )]
829 /// Restore data from media-set
830 async fn restore(mut param: Value) -> Result<(), Error> {
831
832 let output_format = get_output_format(&param);
833
834 let (config, _digest) = config::drive::config()?;
835
836 param["drive"] = lookup_drive_name(&param, &config)?.into();
837
838 let mut client = connect_to_localhost()?;
839
840 let result = client.post("api2/json/tape/restore", Some(param)).await?;
841
842 view_task_result(&mut client, result, &output_format).await?;
843
844 Ok(())
845 }
846
847 #[api(
848 input: {
849 properties: {
850 drive: {
851 schema: DRIVE_NAME_SCHEMA,
852 optional: true,
853 },
854 force: {
855 description: "Force overriding existing index.",
856 type: bool,
857 optional: true,
858 },
859 verbose: {
860 description: "Verbose mode - log all found chunks.",
861 type: bool,
862 optional: true,
863 },
864 "output-format": {
865 schema: OUTPUT_FORMAT,
866 optional: true,
867 },
868 },
869 },
870 )]
871 /// Scan media and record content
872 async fn catalog_media(param: Value) -> Result<(), Error> {
873
874 let output_format = get_output_format(&param);
875
876 let (config, _digest) = config::drive::config()?;
877
878 let drive = lookup_drive_name(&param, &config)?;
879
880 let mut client = connect_to_localhost()?;
881
882 let path = format!("api2/json/tape/drive/{}/catalog", drive);
883 let result = client.post(&path, Some(param)).await?;
884
885 view_task_result(&mut client, result, &output_format).await?;
886
887 Ok(())
888 }
889
890 fn main() {
891
892 let cmd_def = CliCommandMap::new()
893 .insert(
894 "backup",
895 CliCommand::new(&API_METHOD_BACKUP)
896 .arg_param(&["store", "pool"])
897 .completion_cb("store", complete_datastore_name)
898 .completion_cb("pool", complete_pool_name)
899 )
900 .insert(
901 "restore",
902 CliCommand::new(&API_METHOD_RESTORE)
903 .arg_param(&["media-set", "store"])
904 .completion_cb("store", complete_datastore_name)
905 .completion_cb("media-set", complete_media_set_uuid)
906 )
907 .insert(
908 "barcode-label",
909 CliCommand::new(&API_METHOD_BARCODE_LABEL_MEDIA)
910 .completion_cb("drive", complete_drive_name)
911 .completion_cb("pool", complete_pool_name)
912 )
913 .insert(
914 "rewind",
915 CliCommand::new(&API_METHOD_REWIND)
916 .completion_cb("drive", complete_drive_name)
917 )
918 .insert(
919 "scan",
920 CliCommand::new(&API_METHOD_DEBUG_SCAN)
921 .completion_cb("drive", complete_drive_name)
922 )
923 .insert(
924 "status",
925 CliCommand::new(&API_METHOD_STATUS)
926 .completion_cb("drive", complete_drive_name)
927 )
928 .insert(
929 "eod",
930 CliCommand::new(&API_METHOD_MOVE_TO_EOM)
931 .completion_cb("drive", complete_drive_name)
932 )
933 .insert(
934 "erase",
935 CliCommand::new(&API_METHOD_ERASE_MEDIA)
936 .completion_cb("drive", complete_drive_name)
937 )
938 .insert(
939 "eject",
940 CliCommand::new(&API_METHOD_EJECT_MEDIA)
941 .completion_cb("drive", complete_drive_name)
942 )
943 .insert(
944 "inventory",
945 CliCommand::new(&API_METHOD_INVENTORY)
946 .completion_cb("drive", complete_drive_name)
947 )
948 .insert(
949 "read-label",
950 CliCommand::new(&API_METHOD_READ_LABEL)
951 .completion_cb("drive", complete_drive_name)
952 )
953 .insert(
954 "catalog",
955 CliCommand::new(&API_METHOD_CATALOG_MEDIA)
956 .completion_cb("drive", complete_drive_name)
957 )
958 .insert(
959 "cartridge-memory",
960 CliCommand::new(&API_METHOD_CARTRIDGE_MEMORY)
961 .completion_cb("drive", complete_drive_name)
962 )
963 .insert(
964 "volume-statistics",
965 CliCommand::new(&API_METHOD_VOLUME_STATISTICS)
966 .completion_cb("drive", complete_drive_name)
967 )
968 .insert(
969 "clean",
970 CliCommand::new(&API_METHOD_CLEAN_DRIVE)
971 .completion_cb("drive", complete_drive_name)
972 )
973 .insert(
974 "label",
975 CliCommand::new(&API_METHOD_LABEL_MEDIA)
976 .completion_cb("drive", complete_drive_name)
977 .completion_cb("pool", complete_pool_name)
978
979 )
980 .insert("changer", changer_commands())
981 .insert("drive", drive_commands())
982 .insert("pool", pool_commands())
983 .insert("media", media_commands())
984 .insert("key", encryption_key_commands())
985 .insert(
986 "load-media",
987 CliCommand::new(&API_METHOD_LOAD_MEDIA)
988 .arg_param(&["label-text"])
989 .completion_cb("drive", complete_drive_name)
990 .completion_cb("label-text", complete_media_label_text)
991 )
992 .insert(
993 "load-media-from-slot",
994 CliCommand::new(&API_METHOD_LOAD_MEDIA_FROM_SLOT)
995 .arg_param(&["source-slot"])
996 .completion_cb("drive", complete_drive_name)
997 )
998 .insert(
999 "unload",
1000 CliCommand::new(&API_METHOD_UNLOAD_MEDIA)
1001 .completion_cb("drive", complete_drive_name)
1002 )
1003 .insert(
1004 "export-media",
1005 CliCommand::new(&API_METHOD_EXPORT_MEDIA)
1006 .arg_param(&["label-text"])
1007 .completion_cb("drive", complete_drive_name)
1008 .completion_cb("label-text", complete_media_label_text)
1009 )
1010 ;
1011
1012 let mut rpcenv = CliEnvironment::new();
1013 rpcenv.set_auth_id(Some(String::from("root@pam")));
1014
1015 proxmox_backup::tools::runtime::main(run_async_cli_command(cmd_def, rpcenv));
1016 }