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