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