2 use anyhow
::{bail, Error}
;
8 list_subdirs_api_method
,
23 MEDIA_POOL_NAME_SCHEMA
,
39 linux_tape_device_list
,
42 update_changer_online_status
,
54 schema
: DRIVE_ID_SCHEMA
,
57 description
: "Source slot number",
63 /// Load media via changer from slot
68 ) -> Result
<(), Error
> {
70 let (config
, _digest
) = config
::drive
::config()?
;
72 let drive_config
: LinuxTapeDrive
= config
.lookup("linux", &drive
)?
;
74 let changer
: ScsiTapeChanger
= match drive_config
.changer
{
75 Some(ref changer
) => config
.lookup("changer", changer
)?
,
76 None
=> bail
!("drive '{}' has no associated changer", drive
),
79 let drivenum
= drive_config
.changer_drive_id
.unwrap_or(0);
81 mtx_load(&changer
.path
, slot
, drivenum
)
88 schema
: DRIVE_ID_SCHEMA
,
91 schema
: MEDIA_LABEL_SCHEMA
,
96 /// Load media with specified label
98 /// Issue a media load request to the associated changer device.
99 pub fn load_media(drive
: String
, changer_id
: String
) -> Result
<(), Error
> {
101 let (config
, _digest
) = config
::drive
::config()?
;
103 let (mut changer
, _
) = media_changer(&config
, &drive
, false)?
;
105 changer
.load_media(&changer_id
)?
;
114 schema
: DRIVE_ID_SCHEMA
,
117 description
: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.",
124 /// Unload media via changer
129 ) -> Result
<(), Error
> {
131 let (config
, _digest
) = config
::drive
::config()?
;
133 let mut drive_config
: LinuxTapeDrive
= config
.lookup("linux", &drive
)?
;
135 let changer
: ScsiTapeChanger
= match drive_config
.changer
{
136 Some(ref changer
) => config
.lookup("changer", changer
)?
,
137 None
=> bail
!("drive '{}' has no associated changer", drive
),
140 let drivenum
: u64 = 0;
142 if let Some(slot
) = slot
{
143 mtx_unload(&changer
.path
, slot
, drivenum
)
145 drive_config
.unload_media()
154 description
: "The list of autodetected tape drives.",
157 type: TapeDeviceInfo
,
162 pub fn scan_drives(_param
: Value
) -> Result
<Vec
<TapeDeviceInfo
>, Error
> {
164 let list
= linux_tape_device_list();
173 schema
: DRIVE_ID_SCHEMA
,
176 description
: "Use fast erase.",
185 pub fn erase_media(drive
: String
, fast
: Option
<bool
>) -> Result
<(), Error
> {
187 let (config
, _digest
) = config
::drive
::config()?
;
189 let mut drive
= open_drive(&config
, &drive
)?
;
191 drive
.erase_media(fast
.unwrap_or(true))?
;
200 schema
: DRIVE_ID_SCHEMA
,
206 pub fn rewind(drive
: String
) -> Result
<(), Error
> {
208 let (config
, _digest
) = config
::drive
::config()?
;
210 let mut drive
= open_drive(&config
, &drive
)?
;
221 schema
: DRIVE_ID_SCHEMA
,
226 /// Eject/Unload drive media
227 pub fn eject_media(drive
: String
) -> Result
<(), Error
> {
229 let (config
, _digest
) = config
::drive
::config()?
;
231 let (mut changer
, _
) = media_changer(&config
, &drive
, false)?
;
233 if !changer
.eject_on_unload() {
234 let mut drive
= open_drive(&config
, &drive
)?
;
235 drive
.eject_media()?
;
238 changer
.unload_media()?
;
247 schema
: DRIVE_ID_SCHEMA
,
250 schema
: MEDIA_LABEL_SCHEMA
,
253 schema
: MEDIA_POOL_NAME_SCHEMA
,
261 /// Write a new media label to the media in 'drive'. The media is
262 /// assigned to the specified 'pool', or else to the free media pool.
264 /// Note: The media need to be empty (you may want to erase it first).
267 pool
: Option
<String
>,
269 ) -> Result
<(), Error
> {
271 if let Some(ref pool
) = pool
{
272 let (pool_config
, _digest
) = config
::media_pool
::config()?
;
274 if pool_config
.sections
.get(pool
).is_none() {
275 bail
!("no such pool ('{}')", pool
);
279 let (config
, _digest
) = config
::drive
::config()?
;
281 let mut drive
= open_drive(&config
, &drive
)?
;
285 match drive
.read_next_file() {
286 Ok(Some(_file
)) => bail
!("media is not empty (erase first)"),
287 Ok(None
) => { /* EOF mark at BOT, assume tape is empty */ }
,
289 if err
.is_errno(nix
::errno
::Errno
::ENOSPC
) || err
.is_errno(nix
::errno
::Errno
::EIO
) {
290 /* assume tape is empty */
292 bail
!("media read error - {}", err
);
297 let ctime
= proxmox
::tools
::time
::epoch_i64();
298 let label
= DriveLabel
{
299 changer_id
: changer_id
.to_string(),
300 uuid
: Uuid
::generate(),
304 write_media_label(&mut drive
, label
, pool
)
307 fn write_media_label(
308 drive
: &mut Box
<dyn TapeDriver
>,
310 pool
: Option
<String
>,
311 ) -> Result
<(), Error
> {
313 drive
.label_tape(&label
)?
;
315 let mut media_set_label
= None
;
317 if let Some(ref pool
) = pool
{
318 // assign media to pool by writing special media set label
319 println
!("Label media '{}' for pool '{}'", label
.changer_id
, pool
);
320 let set
= MediaSetLabel
::with_data(&pool
, [0u8; 16].into(), 0, label
.ctime
);
322 drive
.write_media_set_label(&set
)?
;
323 media_set_label
= Some(set
);
325 println
!("Label media '{}' (no pool assignment)", label
.changer_id
);
328 let media_id
= MediaId { label, media_set_label }
;
330 let mut inventory
= Inventory
::load(Path
::new(TAPE_STATUS_DIR
))?
;
331 inventory
.store(media_id
.clone())?
;
335 match drive
.read_label() {
337 if info
.label
.uuid
!= media_id
.label
.uuid
{
338 bail
!("verify label failed - got wrong label uuid");
340 if let Some(ref pool
) = pool
{
341 match info
.media_set_label
{
343 if set
.uuid
!= [0u8; 16].into() {
344 bail
!("verify media set label failed - got wrong set uuid");
346 if &set
.pool
!= pool
{
347 bail
!("verify media set label failed - got wrong pool");
351 bail
!("verify media set label failed (missing set label)");
356 Ok(None
) => bail
!("verify label failed (got empty media)"),
357 Err(err
) => bail
!("verify label failed - {}", err
),
369 schema
: DRIVE_ID_SCHEMA
,
374 type: MediaLabelInfoFlat
,
378 pub fn read_label(drive
: String
) -> Result
<MediaLabelInfoFlat
, Error
> {
380 let (config
, _digest
) = config
::drive
::config()?
;
382 let mut drive
= open_drive(&config
, &drive
)?
;
384 let info
= drive
.read_label()?
;
386 let info
= match info
{
388 let mut flat
= MediaLabelInfoFlat
{
389 uuid
: info
.label
.uuid
.to_string(),
390 changer_id
: info
.label
.changer_id
.clone(),
391 ctime
: info
.label
.ctime
,
392 media_set_ctime
: None
,
393 media_set_uuid
: None
,
397 if let Some((set
, _
)) = info
.media_set_label
{
398 flat
.pool
= Some(set
.pool
.clone());
399 flat
.seq_nr
= Some(set
.seq_nr
);
400 flat
.media_set_uuid
= Some(set
.uuid
.to_string());
401 flat
.media_set_ctime
= Some(set
.ctime
);
406 bail
!("Media is empty (no label).");
417 schema
: DRIVE_ID_SCHEMA
,
420 description
: "Load unknown tapes and try read labels",
425 description
: "Load all tapes and try read labels (even if already inventoried)",
432 description
: "The list of media labels with associated media Uuid (if any).",
439 /// List (and update) media labels (Changer Inventory)
441 /// Note: Only useful for drives with associated changer device.
443 /// This method queries the changer to get a list of media labels. It
444 /// 'read-labels' is set, it then loads any unknown media into the
445 /// drive, reads the label, and store the result to the media
449 read_labels
: Option
<bool
>,
450 read_all_labels
: Option
<bool
>,
451 ) -> Result
<Vec
<LabelUuidMap
>, Error
> {
453 let (config
, _digest
) = config
::drive
::config()?
;
455 let (mut changer
, changer_name
) = media_changer(&config
, &drive
, false)?
;
457 let changer_id_list
= changer
.list_media_changer_ids()?
;
459 let state_path
= Path
::new(TAPE_STATUS_DIR
);
461 let mut inventory
= Inventory
::load(state_path
)?
;
462 let mut state_db
= MediaStateDatabase
::load(state_path
)?
;
464 update_changer_online_status(&config
, &mut inventory
, &mut state_db
, &changer_name
, &changer_id_list
)?
;
466 let mut list
= Vec
::new();
468 let do_read
= read_labels
.unwrap_or(false) || read_all_labels
.unwrap_or(false);
470 for changer_id
in changer_id_list
.iter() {
471 if changer_id
.starts_with("CLN") {
472 // skip cleaning unit
476 let changer_id
= changer_id
.to_string();
478 if !read_all_labels
.unwrap_or(false) {
479 if let Some(media_id
) = inventory
.find_media_by_changer_id(&changer_id
) {
480 list
.push(LabelUuidMap { changer_id, uuid: Some(media_id.label.uuid.to_string()) }
);
486 list
.push(LabelUuidMap { changer_id, uuid: None }
);
490 if let Err(err
) = changer
.load_media(&changer_id
) {
491 eprintln
!("unable to load media '{}' - {}", changer_id
, err
);
492 list
.push(LabelUuidMap { changer_id, uuid: None }
);
496 let mut drive
= open_drive(&config
, &drive
)?
;
497 match drive
.read_label() {
499 eprintln
!("unable to read label form media '{}' - {}", changer_id
, err
);
500 list
.push(LabelUuidMap { changer_id, uuid: None }
);
504 // no label on media (empty)
505 list
.push(LabelUuidMap { changer_id, uuid: None }
);
509 if changer_id
!= info
.label
.changer_id
{
510 eprintln
!("label changer ID missmatch ({} != {})", changer_id
, info
.label
.changer_id
);
511 list
.push(LabelUuidMap { changer_id, uuid: None }
);
514 let uuid
= info
.label
.uuid
.to_string();
515 inventory
.store(info
.into())?
;
516 list
.push(LabelUuidMap { changer_id, uuid: Some(uuid) }
);
526 pub const SUBDIRS
: SubdirMap
= &sorted
!([
530 .put(&API_METHOD_EJECT_MEDIA
)
535 .put(&API_METHOD_ERASE_MEDIA
)
540 .get(&API_METHOD_INVENTORY
)
545 .put(&API_METHOD_LABEL_MEDIA
)
550 .put(&API_METHOD_LOAD_SLOT
)
555 .get(&API_METHOD_READ_LABEL
)
560 .put(&API_METHOD_REWIND
)
565 .get(&API_METHOD_SCAN_DRIVES
)
570 .put(&API_METHOD_UNLOAD
)
574 pub const ROUTER
: Router
= Router
::new()
575 .get(&list_subdirs_api_method
!(SUBDIRS
))