]> git.proxmox.com Git - proxmox-backup.git/blame - src/tape/changer/mod.rs
sgutils2: add scsi_inquiry command
[proxmox-backup.git] / src / tape / changer / mod.rs
CommitLineData
37796ff7
DM
1//! Media changer implementation (SCSI media changer)
2
b107fdb9
DM
3mod email;
4pub use email::*;
5
6mod parse_mtx_status;
7pub use parse_mtx_status::*;
8
9mod mtx_wrapper;
10pub use mtx_wrapper::*;
11
e6217b8b
DM
12mod mtx;
13pub use mtx::*;
b107fdb9 14
37796ff7
DM
15mod online_status_map;
16pub use online_status_map::*;
17
04df41ce 18use anyhow::{bail, Error};
b107fdb9 19
04df41ce 20/// Interface to the media changer device for a single drive
b107fdb9
DM
21pub trait MediaChange {
22
04df41ce
DM
23 /// Drive number inside changer
24 fn drive_number(&self) -> u64;
25
26 /// Drive name (used for debug messages)
27 fn drive_name(&self) -> &str;
28
46a1863f
DM
29 /// Returns the changer status
30 fn status(&mut self) -> Result<MtxStatus, Error>;
31
0057f0e5
DM
32 /// Transfer media from on slot to another (storage or import export slots)
33 ///
34 /// Target slot needs to be empty
c92e3832 35 fn transfer_media(&mut self, from: u64, to: u64) -> Result<(), Error>;
0057f0e5 36
46a1863f
DM
37 /// Load media from storage slot into drive
38 fn load_media_from_slot(&mut self, slot: u64) -> Result<(), Error>;
39
8446fbca 40 /// Load media by label-text into drive
b107fdb9
DM
41 ///
42 /// This unloads first if the drive is already loaded with another media.
46a1863f 43 ///
04df41ce
DM
44 /// Note: This refuses to load media inside import/export
45 /// slots. Also, you cannot load cleaning units with this
46 /// interface.
8446fbca 47 fn load_media(&mut self, label_text: &str) -> Result<(), Error> {
04df41ce 48
8446fbca
DM
49 if label_text.starts_with("CLN") {
50 bail!("unable to load media '{}' (seems top be a a cleaning units)", label_text);
04df41ce
DM
51 }
52
53 let mut status = self.status()?;
54
55 let mut unload_drive = false;
56
57 // already loaded?
58 for (i, drive_status) in status.drives.iter().enumerate() {
59 if let ElementStatus::VolumeTag(ref tag) = drive_status.status {
8446fbca 60 if *tag == label_text {
04df41ce
DM
61 if i as u64 != self.drive_number() {
62 bail!("unable to load media '{}' - media in wrong drive ({} != {})",
8446fbca 63 label_text, i, self.drive_number());
04df41ce
DM
64 }
65 return Ok(()) // already loaded
66 }
67 }
68 if i as u64 == self.drive_number() {
69 match drive_status.status {
70 ElementStatus::Empty => { /* OK */ },
71 _ => unload_drive = true,
72 }
73 }
74 }
75
76 if unload_drive {
77 self.unload_to_free_slot(status)?;
78 status = self.status()?;
79 }
80
81 let mut slot = None;
82 for (i, (import_export, element_status)) in status.slots.iter().enumerate() {
83 if let ElementStatus::VolumeTag(tag) = element_status {
8446fbca 84 if *tag == label_text {
04df41ce 85 if *import_export {
8446fbca 86 bail!("unable to load media '{}' - inside import/export slot", label_text);
04df41ce
DM
87 }
88 slot = Some(i+1);
89 break;
90 }
91 }
92 }
93
94 let slot = match slot {
8446fbca 95 None => bail!("unable to find media '{}' (offline?)", label_text),
04df41ce
DM
96 Some(slot) => slot,
97 };
98
99 self.load_media_from_slot(slot as u64)
100 }
b107fdb9 101
6638c034 102 /// Unload media from drive (eject media if necessary)
46a1863f 103 fn unload_media(&mut self, target_slot: Option<u64>) -> Result<(), Error>;
b107fdb9 104
8446fbca 105 /// List online media labels (label_text/barcodes)
46a1863f 106 ///
8446fbca 107 /// List acessible (online) label texts. This does not include
46a1863f 108 /// media inside import-export slots or cleaning media.
8446fbca 109 fn online_media_label_texts(&mut self) -> Result<Vec<String>, Error> {
46a1863f
DM
110 let status = self.status()?;
111
112 let mut list = Vec::new();
113
114 for drive_status in status.drives.iter() {
115 if let ElementStatus::VolumeTag(ref tag) = drive_status.status {
116 list.push(tag.clone());
117 }
118 }
119
120 for (import_export, element_status) in status.slots.iter() {
121 if *import_export { continue; }
122 if let ElementStatus::VolumeTag(ref tag) = element_status {
25d39657 123 if tag.starts_with("CLN") { continue; }
46a1863f
DM
124 list.push(tag.clone());
125 }
126 }
127
128 Ok(list)
129 }
df69a4fc
DM
130
131 /// Load/Unload cleaning cartridge
132 ///
133 /// This fail if there is no cleaning cartridge online. Any media
134 /// inside the drive is automatically unloaded.
04df41ce
DM
135 fn clean_drive(&mut self) -> Result<(), Error> {
136 let status = self.status()?;
137
138 let mut cleaning_cartridge_slot = None;
139
140 for (i, (import_export, element_status)) in status.slots.iter().enumerate() {
141 if *import_export { continue; }
142 if let ElementStatus::VolumeTag(ref tag) = element_status {
143 if tag.starts_with("CLN") {
144 cleaning_cartridge_slot = Some(i + 1);
145 break;
146 }
147 }
148 }
149
150 let cleaning_cartridge_slot = match cleaning_cartridge_slot {
151 None => bail!("clean failed - unable to find cleaning cartridge"),
152 Some(cleaning_cartridge_slot) => cleaning_cartridge_slot as u64,
153 };
154
155 if let Some(drive_status) = status.drives.get(self.drive_number() as usize) {
156 match drive_status.status {
157 ElementStatus::Empty => { /* OK */ },
158 _ => self.unload_to_free_slot(status)?,
159 }
160 }
161
162 self.load_media_from_slot(cleaning_cartridge_slot)?;
163
164 self.unload_media(Some(cleaning_cartridge_slot))?;
165
166 Ok(())
167 }
0057f0e5
DM
168
169 /// Export media
170 ///
171 /// By moving the media to an empty import-export slot. Returns
172 /// Some(slot) if the media was exported. Returns None if the media is
173 /// not online (already exported).
8446fbca 174 fn export_media(&mut self, label_text: &str) -> Result<Option<u64>, Error> {
04df41ce
DM
175 let status = self.status()?;
176
177 let mut unload_from_drive = false;
178 if let Some(drive_status) = status.drives.get(self.drive_number() as usize) {
179 if let ElementStatus::VolumeTag(ref tag) = drive_status.status {
8446fbca 180 if tag == label_text {
04df41ce
DM
181 unload_from_drive = true;
182 }
183 }
184 }
185
186 let mut from = None;
187 let mut to = None;
188
189 for (i, (import_export, element_status)) in status.slots.iter().enumerate() {
190 if *import_export {
191 if to.is_some() { continue; }
192 if let ElementStatus::Empty = element_status {
193 to = Some(i as u64 + 1);
194 }
6334bdc1
FG
195 } else if let ElementStatus::VolumeTag(ref tag) = element_status {
196 if tag == label_text {
197 from = Some(i as u64 + 1);
04df41ce
DM
198 }
199 }
200 }
201
202 if unload_from_drive {
203 match to {
204 Some(to) => {
205 self.unload_media(Some(to))?;
206 Ok(Some(to))
207 }
208 None => bail!("unable to find free export slot"),
209 }
210 } else {
211 match (from, to) {
212 (Some(from), Some(to)) => {
213 self.transfer_media(from, to)?;
214 Ok(Some(to))
215 }
216 (Some(_from), None) => bail!("unable to find free export slot"),
217 (None, _) => Ok(None), // not online
218 }
219 }
220 }
221
222 /// Unload media to a free storage slot
223 ///
224 /// If posible to the slot it was previously loaded from.
225 ///
226 /// Note: This method consumes status - so please read again afterward.
227 fn unload_to_free_slot(&mut self, status: MtxStatus) -> Result<(), Error> {
228
229 let drive_status = &status.drives[self.drive_number() as usize];
230 if let Some(slot) = drive_status.loaded_slot {
231 // check if original slot is empty/usable
232 if let Some(info) = status.slots.get(slot as usize - 1) {
233 if let (_import_export, ElementStatus::Empty) = info {
234 return self.unload_media(Some(slot));
235 }
236 }
237 }
238
239 let mut free_slot = None;
240 for i in 0..status.slots.len() {
241 if status.slots[i].0 { continue; } // skip import/export slots
242 if let ElementStatus::Empty = status.slots[i].1 {
243 free_slot = Some((i+1) as u64);
244 break;
245 }
246 }
247 if let Some(slot) = free_slot {
248 self.unload_media(Some(slot))
249 } else {
250 bail!("drive '{}' unload failure - no free slot", self.drive_name());
251 }
252 }
b107fdb9 253}