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