]>
Commit | Line | Data |
---|---|---|
37796ff7 DM |
1 | //! Media changer implementation (SCSI media changer) |
2 | ||
b107fdb9 DM |
3 | mod email; |
4 | pub use email::*; | |
5 | ||
6 | mod parse_mtx_status; | |
7 | pub use parse_mtx_status::*; | |
8 | ||
9 | mod mtx_wrapper; | |
10 | pub use mtx_wrapper::*; | |
11 | ||
e6217b8b DM |
12 | mod mtx; |
13 | pub use mtx::*; | |
b107fdb9 | 14 | |
37796ff7 DM |
15 | mod online_status_map; |
16 | pub use online_status_map::*; | |
17 | ||
04df41ce | 18 | use anyhow::{bail, Error}; |
b107fdb9 | 19 | |
04df41ce | 20 | /// Interface to the media changer device for a single drive |
b107fdb9 DM |
21 | pub 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 | } |