]>
Commit | Line | Data |
---|---|---|
697c41c5 | 1 | //! SCSI changer implementation using libsgutil2 |
697c41c5 | 2 | use std::collections::HashMap; |
b23adfd4 TL |
3 | use std::fs::{File, OpenOptions}; |
4 | use std::io::Read; | |
5 | use std::os::unix::prelude::AsRawFd; | |
697c41c5 | 6 | use std::path::Path; |
697c41c5 DM |
7 | |
8 | use anyhow::{bail, format_err, Error}; | |
9 | use endian_trait::Endian; | |
10 | ||
6ef1b649 | 11 | use proxmox_io::ReadExt; |
697c41c5 | 12 | |
6227654a DM |
13 | use pbs_api_types::ScsiTapeChanger; |
14 | ||
697c41c5 | 15 | use crate::{ |
b23adfd4 TL |
16 | sgutils2::{scsi_ascii_to_string, scsi_inquiry, ScsiError, SgRaw, SENSE_KEY_NOT_READY}, |
17 | DriveStatus, ElementStatus, MtxStatus, StorageElementStatus, TransportElementStatus, | |
697c41c5 DM |
18 | }; |
19 | ||
b23adfd4 | 20 | const SCSI_CHANGER_DEFAULT_TIMEOUT: usize = 60 * 5; // 5 minutes |
62a0e190 | 21 | const SCSI_VOLUME_TAG_LEN: usize = 36; |
697c41c5 DM |
22 | |
23 | /// Initialize element status (Inventory) | |
24 | pub fn initialize_element_status<F: AsRawFd>(file: &mut F) -> Result<(), Error> { | |
697c41c5 DM |
25 | let mut sg_raw = SgRaw::new(file, 64)?; |
26 | ||
27 | // like mtx(1), set a very long timeout (30 minutes) | |
b23adfd4 | 28 | sg_raw.set_timeout(30 * 60); |
697c41c5 | 29 | |
e1db0670 | 30 | let cmd = &[0x07, 0, 0, 0, 0, 0]; // INITIALIZE ELEMENT STATUS (07h) |
697c41c5 | 31 | |
b23adfd4 | 32 | sg_raw |
e1db0670 | 33 | .do_command(cmd) |
697c41c5 DM |
34 | .map_err(|err| format_err!("initializte element status (07h) failed - {}", err))?; |
35 | ||
36 | Ok(()) | |
37 | } | |
38 | ||
39 | #[repr(C, packed)] | |
40 | #[derive(Endian)] | |
41 | struct AddressAssignmentPage { | |
42 | data_len: u8, | |
43 | reserved1: u8, | |
44 | reserved2: u8, | |
45 | block_descriptor_len: u8, | |
46 | ||
47 | page_code: u8, | |
48 | additional_page_len: u8, | |
49 | first_transport_element_address: u16, | |
50 | transport_element_count: u16, | |
51 | first_storage_element_address: u16, | |
52 | storage_element_count: u16, | |
53 | first_import_export_element_address: u16, | |
54 | import_export_element_count: u16, | |
55 | first_tranfer_element_address: u16, | |
56 | transfer_element_count: u16, | |
57 | reserved22: u8, | |
58 | reserved23: u8, | |
59 | } | |
60 | ||
16b4d784 | 61 | /// Execute scsi commands, optionally repeat the command until |
9ee4c238 | 62 | /// successful or timeout (sleep 1 second between invovations) |
ec8d9c6b | 63 | /// |
9ee4c238 DM |
64 | /// Timeout is 5 seconds. If the device reports "Not Ready - becoming |
65 | /// ready", we wait up to 5 minutes. | |
ec8d9c6b DM |
66 | /// |
67 | /// Skipped errors are printed on stderr. | |
16b4d784 | 68 | fn execute_scsi_command<F: AsRawFd>( |
ec8d9c6b DM |
69 | sg_raw: &mut SgRaw<F>, |
70 | cmd: &[u8], | |
71 | error_prefix: &str, | |
16b4d784 | 72 | retry: bool, |
ec8d9c6b | 73 | ) -> Result<Vec<u8>, Error> { |
ec8d9c6b DM |
74 | let start = std::time::SystemTime::now(); |
75 | ||
76 | let mut last_msg: Option<String> = None; | |
77 | ||
78 | let mut timeout = std::time::Duration::new(5, 0); // short timeout by default | |
79 | ||
80 | loop { | |
9a37bd6c | 81 | match sg_raw.do_command(cmd) { |
ec8d9c6b | 82 | Ok(data) => return Ok(data.to_vec()), |
9ee4c238 | 83 | Err(err) if !retry => bail!("{} failed: {}", error_prefix, err), |
ec8d9c6b | 84 | Err(err) => { |
9ee4c238 DM |
85 | let msg = err.to_string(); |
86 | if let Some(ref last) = last_msg { | |
87 | if &msg != last { | |
e82c03a1 | 88 | log::error!("{}", err); |
9ee4c238 DM |
89 | last_msg = Some(msg); |
90 | } | |
91 | } else { | |
e82c03a1 | 92 | log::error!("{}", err); |
9ee4c238 | 93 | last_msg = Some(msg); |
16b4d784 | 94 | } |
ec8d9c6b | 95 | |
9ee4c238 DM |
96 | if let ScsiError::Sense(ref sense) = err { |
97 | // Not Ready - becoming ready | |
b23adfd4 TL |
98 | if sense.sense_key == SENSE_KEY_NOT_READY |
99 | && sense.asc == 0x04 | |
100 | && sense.ascq == 1 | |
101 | { | |
9ee4c238 | 102 | // wait up to 5 minutes, long enough to finish inventorize |
b23adfd4 | 103 | timeout = std::time::Duration::new(5 * 60, 0); |
ec8d9c6b DM |
104 | } |
105 | } | |
9ee4c238 DM |
106 | |
107 | if start.elapsed()? > timeout { | |
108 | bail!("{} failed: {}", error_prefix, err); | |
109 | } | |
110 | ||
111 | std::thread::sleep(std::time::Duration::new(1, 0)); | |
112 | continue; // try again | |
ec8d9c6b DM |
113 | } |
114 | } | |
b23adfd4 | 115 | } |
ec8d9c6b DM |
116 | } |
117 | ||
697c41c5 DM |
118 | fn read_element_address_assignment<F: AsRawFd>( |
119 | file: &mut F, | |
120 | ) -> Result<AddressAssignmentPage, Error> { | |
697c41c5 DM |
121 | let allocation_len: u8 = u8::MAX; |
122 | let mut sg_raw = SgRaw::new(file, allocation_len as usize)?; | |
123 | sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT); | |
124 | ||
e1db0670 FG |
125 | let cmd = &[ |
126 | 0x1A, // MODE SENSE6 (1Ah) | |
127 | 0x08, // DBD=1 (The Disable Block Descriptors) | |
128 | 0x1D, // Element Address Assignment Page | |
129 | 0, | |
130 | allocation_len, // allocation len | |
131 | 0, //control | |
132 | ]; | |
697c41c5 | 133 | |
e1db0670 | 134 | let data = execute_scsi_command(&mut sg_raw, cmd, "read element address assignment", true)?; |
697c41c5 | 135 | |
6ef1b649 | 136 | proxmox_lang::try_block!({ |
697c41c5 DM |
137 | let mut reader = &data[..]; |
138 | let page: AddressAssignmentPage = unsafe { reader.read_be_value()? }; | |
139 | ||
140 | if page.data_len != 23 { | |
141 | bail!("got unexpected page len ({} != 23)", page.data_len); | |
142 | } | |
143 | ||
144 | Ok(page) | |
b23adfd4 TL |
145 | }) |
146 | .map_err(|err: Error| format_err!("decode element address assignment page failed - {}", err)) | |
697c41c5 DM |
147 | } |
148 | ||
e1db0670 | 149 | #[allow(clippy::vec_init_then_push)] |
697c41c5 DM |
150 | fn scsi_move_medium_cdb( |
151 | medium_transport_address: u16, | |
152 | source_element_address: u16, | |
153 | destination_element_address: u16, | |
154 | ) -> Vec<u8> { | |
697c41c5 DM |
155 | let mut cmd = Vec::new(); |
156 | cmd.push(0xA5); // MOVE MEDIUM (A5h) | |
157 | cmd.push(0); // reserved | |
158 | cmd.extend(&medium_transport_address.to_be_bytes()); | |
159 | cmd.extend(&source_element_address.to_be_bytes()); | |
160 | cmd.extend(&destination_element_address.to_be_bytes()); | |
161 | cmd.push(0); // reserved | |
162 | cmd.push(0); // reserved | |
163 | cmd.push(0); // Invert=0 | |
164 | cmd.push(0); // control | |
165 | ||
166 | cmd | |
167 | } | |
168 | ||
169 | /// Load media from storage slot into drive | |
b23adfd4 | 170 | pub fn load_slot(file: &mut File, from_slot: u64, drivenum: u64) -> Result<(), Error> { |
697c41c5 DM |
171 | let status = read_element_status(file)?; |
172 | ||
173 | let transport_address = status.transport_address(); | |
174 | let source_element_address = status.slot_address(from_slot)?; | |
175 | let drive_element_address = status.drive_address(drivenum)?; | |
176 | ||
177 | let cmd = scsi_move_medium_cdb( | |
178 | transport_address, | |
179 | source_element_address, | |
180 | drive_element_address, | |
181 | ); | |
182 | ||
183 | let mut sg_raw = SgRaw::new(file, 64)?; | |
184 | sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT); | |
185 | ||
b23adfd4 TL |
186 | sg_raw |
187 | .do_command(&cmd) | |
697c41c5 DM |
188 | .map_err(|err| format_err!("load drive failed - {}", err))?; |
189 | ||
190 | Ok(()) | |
191 | } | |
192 | ||
193 | /// Unload media from drive into a storage slot | |
b23adfd4 | 194 | pub fn unload(file: &mut File, to_slot: u64, drivenum: u64) -> Result<(), Error> { |
697c41c5 DM |
195 | let status = read_element_status(file)?; |
196 | ||
197 | let transport_address = status.transport_address(); | |
198 | let target_element_address = status.slot_address(to_slot)?; | |
199 | let drive_element_address = status.drive_address(drivenum)?; | |
200 | ||
201 | let cmd = scsi_move_medium_cdb( | |
202 | transport_address, | |
203 | drive_element_address, | |
204 | target_element_address, | |
205 | ); | |
206 | ||
207 | let mut sg_raw = SgRaw::new(file, 64)?; | |
208 | sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT); | |
209 | ||
b23adfd4 TL |
210 | sg_raw |
211 | .do_command(&cmd) | |
697c41c5 DM |
212 | .map_err(|err| format_err!("unload drive failed - {}", err))?; |
213 | ||
214 | Ok(()) | |
215 | } | |
216 | ||
d1d74c43 | 217 | /// Transfer medium from one storage slot to another |
697c41c5 DM |
218 | pub fn transfer_medium<F: AsRawFd>( |
219 | file: &mut F, | |
220 | from_slot: u64, | |
221 | to_slot: u64, | |
222 | ) -> Result<(), Error> { | |
697c41c5 DM |
223 | let status = read_element_status(file)?; |
224 | ||
225 | let transport_address = status.transport_address(); | |
226 | let source_element_address = status.slot_address(from_slot)?; | |
227 | let target_element_address = status.slot_address(to_slot)?; | |
228 | ||
229 | let cmd = scsi_move_medium_cdb( | |
230 | transport_address, | |
231 | source_element_address, | |
232 | target_element_address, | |
233 | ); | |
234 | ||
235 | let mut sg_raw = SgRaw::new(file, 64)?; | |
236 | sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT); | |
237 | ||
b23adfd4 TL |
238 | sg_raw.do_command(&cmd).map_err(|err| { |
239 | format_err!( | |
240 | "transfer medium from slot {} to slot {} failed - {}", | |
241 | from_slot, | |
242 | to_slot, | |
243 | err | |
244 | ) | |
245 | })?; | |
697c41c5 DM |
246 | |
247 | Ok(()) | |
248 | } | |
249 | ||
0ed40b19 DC |
250 | #[derive(Clone, Copy)] |
251 | enum ElementType { | |
6c053ffc DC |
252 | MediumTransport, |
253 | Storage, | |
254 | ImportExport, | |
255 | DataTransfer, | |
256 | DataTransferWithDVCID, | |
257 | } | |
258 | ||
259 | impl ElementType { | |
260 | fn byte1(&self) -> u8 { | |
261 | let volume_tag_bit = 1u8 << 4; | |
262 | match *self { | |
263 | ElementType::MediumTransport => volume_tag_bit | 1, | |
264 | ElementType::Storage => volume_tag_bit | 2, | |
265 | ElementType::ImportExport => volume_tag_bit | 3, | |
266 | ElementType::DataTransfer => volume_tag_bit | 4, | |
267 | // some changers cannot get voltag + dvcid at the same time | |
268 | ElementType::DataTransferWithDVCID => 4, | |
269 | } | |
270 | } | |
271 | ||
272 | fn byte6(&self) -> u8 { | |
273 | match *self { | |
274 | ElementType::DataTransferWithDVCID => 0b001, // Mixed=0,CurData=0,DVCID=1 | |
b23adfd4 | 275 | _ => 0b000, // Mixed=0,CurData=0,DVCID=0 |
6c053ffc DC |
276 | } |
277 | } | |
0ed40b19 DC |
278 | } |
279 | ||
e1db0670 | 280 | #[allow(clippy::vec_init_then_push)] |
697c41c5 DM |
281 | fn scsi_read_element_status_cdb( |
282 | start_element_address: u16, | |
0ed40b19 DC |
283 | number_of_elements: u16, |
284 | element_type: ElementType, | |
697c41c5 DM |
285 | allocation_len: u32, |
286 | ) -> Vec<u8> { | |
697c41c5 DM |
287 | let mut cmd = Vec::new(); |
288 | cmd.push(0xB8); // READ ELEMENT STATUS (B8h) | |
6c053ffc | 289 | cmd.push(element_type.byte1()); |
697c41c5 DM |
290 | cmd.extend(&start_element_address.to_be_bytes()); |
291 | ||
697c41c5 | 292 | cmd.extend(&number_of_elements.to_be_bytes()); |
6c053ffc | 293 | cmd.push(element_type.byte6()); |
697c41c5 DM |
294 | cmd.extend(&allocation_len.to_be_bytes()[1..4]); |
295 | cmd.push(0); | |
296 | cmd.push(0); | |
297 | ||
298 | cmd | |
299 | } | |
300 | ||
0ed40b19 DC |
301 | // query a single element type from the changer |
302 | fn get_element<F: AsRawFd>( | |
0ed40b19 DC |
303 | sg_raw: &mut SgRaw<F>, |
304 | element_type: ElementType, | |
305 | allocation_len: u32, | |
306 | mut retry: bool, | |
307 | ) -> Result<DecodedStatusPage, Error> { | |
0ed40b19 DC |
308 | let mut start_element_address = 0; |
309 | let number_of_elements: u16 = 1000; // some changers limit the query | |
310 | ||
311 | let mut result = DecodedStatusPage { | |
312 | last_element_address: None, | |
313 | transports: Vec::new(), | |
314 | drives: Vec::new(), | |
315 | storage_slots: Vec::new(), | |
316 | import_export_slots: Vec::new(), | |
317 | }; | |
318 | ||
319 | loop { | |
b23adfd4 TL |
320 | let cmd = scsi_read_element_status_cdb( |
321 | start_element_address, | |
322 | number_of_elements, | |
323 | element_type, | |
324 | allocation_len, | |
325 | ); | |
0ed40b19 DC |
326 | |
327 | let data = execute_scsi_command(sg_raw, &cmd, "read element status (B8h)", retry)?; | |
328 | ||
3ab2432a | 329 | let page = decode_element_status_page(&data, start_element_address)?; |
0ed40b19 DC |
330 | |
331 | retry = false; // only retry the first command | |
332 | ||
333 | let returned_number_of_elements = page.transports.len() | |
334 | + page.drives.len() | |
335 | + page.storage_slots.len() | |
336 | + page.import_export_slots.len(); | |
337 | ||
338 | result.transports.extend(page.transports); | |
339 | result.drives.extend(page.drives); | |
340 | result.storage_slots.extend(page.storage_slots); | |
341 | result.import_export_slots.extend(page.import_export_slots); | |
342 | result.last_element_address = page.last_element_address; | |
343 | ||
344 | if let Some(last_element_address) = page.last_element_address { | |
345 | if last_element_address < start_element_address { | |
346 | bail!("got strange element address"); | |
347 | } | |
348 | if returned_number_of_elements >= (number_of_elements as usize) { | |
349 | start_element_address = last_element_address + 1; | |
350 | continue; // we possibly have to read additional elements | |
351 | } | |
352 | } | |
353 | break; | |
354 | } | |
355 | ||
356 | Ok(result) | |
357 | } | |
358 | ||
697c41c5 DM |
359 | /// Read element status. |
360 | pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error> { | |
697c41c5 DM |
361 | let inquiry = scsi_inquiry(file)?; |
362 | ||
363 | if inquiry.peripheral_type != 8 { | |
364 | bail!("wrong device type (not a scsi changer device)"); | |
365 | } | |
366 | ||
367 | // first, request address assignment (used for sanity checks) | |
368 | let setup = read_element_address_assignment(file)?; | |
369 | ||
370 | let allocation_len: u32 = 0x10000; | |
371 | ||
372 | let mut sg_raw = SgRaw::new(file, allocation_len as usize)?; | |
373 | sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT); | |
374 | ||
697c41c5 DM |
375 | let mut drives = Vec::new(); |
376 | let mut storage_slots = Vec::new(); | |
377 | let mut import_export_slots = Vec::new(); | |
378 | let mut transports = Vec::new(); | |
379 | ||
3ab2432a | 380 | let page = get_element(&mut sg_raw, ElementType::Storage, allocation_len, true)?; |
0ed40b19 | 381 | storage_slots.extend(page.storage_slots); |
16b4d784 | 382 | |
b23adfd4 TL |
383 | let page = get_element( |
384 | &mut sg_raw, | |
385 | ElementType::ImportExport, | |
386 | allocation_len, | |
387 | false, | |
388 | )?; | |
0ed40b19 | 389 | import_export_slots.extend(page.import_export_slots); |
697c41c5 | 390 | |
b23adfd4 TL |
391 | let page = get_element( |
392 | &mut sg_raw, | |
393 | ElementType::DataTransfer, | |
394 | allocation_len, | |
395 | false, | |
396 | )?; | |
0ed40b19 | 397 | drives.extend(page.drives); |
697c41c5 | 398 | |
6c053ffc DC |
399 | // get the serial + vendor + model, |
400 | // some changer require this to be an extra scsi command | |
b23adfd4 TL |
401 | let page = get_element( |
402 | &mut sg_raw, | |
403 | ElementType::DataTransferWithDVCID, | |
404 | allocation_len, | |
405 | false, | |
406 | )?; | |
6c053ffc DC |
407 | // should be in same order and same count, but be on the safe side. |
408 | // there should not be too many drives normally | |
409 | for drive in drives.iter_mut() { | |
410 | for drive2 in &page.drives { | |
411 | if drive2.element_address == drive.element_address { | |
412 | drive.vendor = drive2.vendor.clone(); | |
413 | drive.model = drive2.model.clone(); | |
414 | drive.drive_serial_number = drive2.drive_serial_number.clone(); | |
415 | } | |
416 | } | |
417 | } | |
418 | ||
b23adfd4 TL |
419 | let page = get_element( |
420 | &mut sg_raw, | |
421 | ElementType::MediumTransport, | |
422 | allocation_len, | |
423 | false, | |
424 | )?; | |
0ed40b19 | 425 | transports.extend(page.transports); |
697c41c5 | 426 | |
131d0f10 DC |
427 | let transport_count = setup.transport_element_count as usize; |
428 | let storage_count = setup.storage_element_count as usize; | |
429 | let import_export_count = setup.import_export_element_count as usize; | |
430 | let transfer_count = setup.transfer_element_count as usize; | |
431 | ||
432 | if transport_count != transports.len() { | |
433 | bail!( | |
434 | "got wrong number of transport elements: expoected {}, got{}", | |
435 | transport_count, | |
436 | transports.len() | |
437 | ); | |
697c41c5 | 438 | } |
131d0f10 DC |
439 | if storage_count != storage_slots.len() { |
440 | bail!( | |
441 | "got wrong number of storage elements: expected {}, got {}", | |
442 | storage_count, | |
443 | storage_slots.len(), | |
444 | ); | |
697c41c5 | 445 | } |
131d0f10 DC |
446 | if import_export_count != import_export_slots.len() { |
447 | bail!( | |
448 | "got wrong number of import/export elements: expected {}, got {}", | |
449 | import_export_count, | |
450 | import_export_slots.len(), | |
451 | ); | |
697c41c5 | 452 | } |
131d0f10 DC |
453 | if transfer_count != drives.len() { |
454 | bail!( | |
455 | "got wrong number of transfer elements: expected {}, got {}", | |
456 | transfer_count, | |
457 | drives.len(), | |
458 | ); | |
697c41c5 DM |
459 | } |
460 | ||
461 | // create same virtual slot order as mtx(1) | |
462 | // - storage slots first | |
463 | // - import export slots at the end | |
464 | let mut slots = storage_slots; | |
465 | slots.extend(import_export_slots); | |
466 | ||
b23adfd4 TL |
467 | let mut status = MtxStatus { |
468 | transports, | |
469 | drives, | |
470 | slots, | |
471 | }; | |
697c41c5 DM |
472 | |
473 | // sanity checks | |
474 | if status.drives.is_empty() { | |
475 | bail!("no data transfer elements reported"); | |
476 | } | |
477 | if status.slots.is_empty() { | |
b23adfd4 | 478 | bail!("no storage elements reported"); |
697c41c5 DM |
479 | } |
480 | ||
481 | // compute virtual storage slot to element_address map | |
482 | let mut slot_map = HashMap::new(); | |
483 | for (i, slot) in status.slots.iter().enumerate() { | |
484 | slot_map.insert(slot.element_address, (i + 1) as u64); | |
485 | } | |
486 | ||
487 | // translate element addresses in loaded_lot | |
488 | for drive in status.drives.iter_mut() { | |
489 | if let Some(source_address) = drive.loaded_slot { | |
490 | let source_address = source_address as u16; | |
dcf5a0f6 | 491 | drive.loaded_slot = slot_map.get(&source_address).copied(); |
697c41c5 DM |
492 | } |
493 | } | |
494 | ||
495 | Ok(status) | |
496 | } | |
497 | ||
4be47366 DC |
498 | /// Read status and map import-export slots from config |
499 | pub fn status(config: &ScsiTapeChanger) -> Result<MtxStatus, Error> { | |
500 | let path = &config.path; | |
501 | ||
b23adfd4 | 502 | let mut file = open(path).map_err(|err| format_err!("error opening '{}': {}", path, err))?; |
4be47366 DC |
503 | let mut status = read_element_status(&mut file) |
504 | .map_err(|err| format_err!("error reading element status: {}", err))?; | |
505 | ||
9a37bd6c | 506 | status.mark_import_export_slots(config)?; |
4be47366 DC |
507 | |
508 | Ok(status) | |
509 | } | |
510 | ||
697c41c5 DM |
511 | #[repr(C, packed)] |
512 | #[derive(Endian)] | |
513 | struct ElementStatusHeader { | |
514 | first_element_address_reported: u16, | |
515 | number_of_elements_available: u16, | |
516 | reserved: u8, | |
b23adfd4 | 517 | byte_count_of_report_available: [u8; 3], |
697c41c5 DM |
518 | } |
519 | ||
520 | #[repr(C, packed)] | |
521 | #[derive(Endian)] | |
522 | struct SubHeader { | |
523 | element_type_code: u8, | |
524 | flags: u8, | |
525 | descriptor_length: u16, | |
d1d74c43 | 526 | reserved: u8, |
b23adfd4 | 527 | byte_count_of_descriptor_data_available: [u8; 3], |
697c41c5 DM |
528 | } |
529 | ||
530 | impl SubHeader { | |
697c41c5 DM |
531 | fn parse_optional_volume_tag<R: Read>( |
532 | &self, | |
533 | reader: &mut R, | |
534 | full: bool, | |
535 | ) -> Result<Option<String>, Error> { | |
b23adfd4 TL |
536 | if (self.flags & 128) != 0 { |
537 | // has PVolTag | |
62a0e190 | 538 | let tmp = reader.read_exact_allocated(SCSI_VOLUME_TAG_LEN)?; |
697c41c5 DM |
539 | if full { |
540 | let volume_tag = scsi_ascii_to_string(&tmp); | |
541 | return Ok(Some(volume_tag)); | |
542 | } | |
543 | } | |
544 | Ok(None) | |
545 | } | |
546 | ||
547 | // AFAIK, tape changer do not use AlternateVolumeTag | |
548 | // but parse anyways, just to be sure | |
b23adfd4 TL |
549 | fn skip_alternate_volume_tag<R: Read>(&self, reader: &mut R) -> Result<Option<String>, Error> { |
550 | if (self.flags & 64) != 0 { | |
551 | // has AVolTag | |
62a0e190 | 552 | let _tmp = reader.read_exact_allocated(SCSI_VOLUME_TAG_LEN)?; |
697c41c5 DM |
553 | } |
554 | ||
555 | Ok(None) | |
556 | } | |
557 | } | |
558 | ||
559 | #[repr(C, packed)] | |
560 | #[derive(Endian)] | |
b23adfd4 TL |
561 | struct TransportDescriptor { |
562 | // Robot/Griper | |
697c41c5 DM |
563 | element_address: u16, |
564 | flags1: u8, | |
565 | reserved_3: u8, | |
566 | additional_sense_code: u8, | |
567 | additional_sense_code_qualifier: u8, | |
b23adfd4 | 568 | reserved_6: [u8; 3], |
697c41c5 DM |
569 | flags2: u8, |
570 | source_storage_element_address: u16, | |
571 | // volume tag and Mixed media descriptor follows (depends on flags) | |
572 | } | |
573 | ||
574 | #[repr(C, packed)] | |
575 | #[derive(Endian)] | |
b23adfd4 TL |
576 | struct TransferDescriptor { |
577 | // Tape drive | |
697c41c5 DM |
578 | element_address: u16, |
579 | flags1: u8, | |
580 | reserved_3: u8, | |
581 | additional_sense_code: u8, | |
582 | additional_sense_code_qualifier: u8, | |
583 | id_valid: u8, | |
584 | scsi_bus_address: u8, | |
585 | reserved_8: u8, | |
586 | flags2: u8, | |
587 | source_storage_element_address: u16, | |
588 | // volume tag, drive identifier and Mixed media descriptor follows | |
589 | // (depends on flags) | |
590 | } | |
591 | ||
592 | #[repr(C, packed)] | |
593 | #[derive(Endian)] | |
b23adfd4 TL |
594 | struct DvcidHead { |
595 | // Drive Identifier Header | |
697c41c5 DM |
596 | code_set: u8, |
597 | identifier_type: u8, | |
598 | reserved: u8, | |
599 | identifier_len: u8, | |
600 | // Identifier follows | |
601 | } | |
602 | ||
603 | #[repr(C, packed)] | |
604 | #[derive(Endian)] | |
b23adfd4 TL |
605 | struct StorageDescriptor { |
606 | // Mail Slot | |
697c41c5 DM |
607 | element_address: u16, |
608 | flags1: u8, | |
609 | reserved_3: u8, | |
610 | additional_sense_code: u8, | |
611 | additional_sense_code_qualifier: u8, | |
b23adfd4 | 612 | reserved_6: [u8; 3], |
697c41c5 DM |
613 | flags2: u8, |
614 | source_storage_element_address: u16, | |
615 | // volume tag and Mixed media descriptor follows (depends on flags) | |
616 | } | |
617 | ||
618 | struct DecodedStatusPage { | |
619 | last_element_address: Option<u16>, | |
620 | transports: Vec<TransportElementStatus>, | |
621 | drives: Vec<DriveStatus>, | |
622 | storage_slots: Vec<StorageElementStatus>, | |
623 | import_export_slots: Vec<StorageElementStatus>, | |
624 | } | |
625 | ||
626 | fn create_element_status(full: bool, volume_tag: Option<String>) -> ElementStatus { | |
627 | if full { | |
628 | if let Some(volume_tag) = volume_tag { | |
629 | ElementStatus::VolumeTag(volume_tag) | |
630 | } else { | |
631 | ElementStatus::Full | |
632 | } | |
633 | } else { | |
634 | ElementStatus::Empty | |
635 | } | |
636 | } | |
637 | ||
02631056 DC |
638 | struct DvcidInfo { |
639 | vendor: Option<String>, | |
640 | model: Option<String>, | |
641 | serial: Option<String>, | |
642 | } | |
643 | ||
644 | fn decode_dvcid_info<R: Read>(reader: &mut R) -> Result<DvcidInfo, Error> { | |
645 | let dvcid: DvcidHead = unsafe { reader.read_be_value()? }; | |
646 | ||
647 | let (serial, vendor, model) = match (dvcid.code_set, dvcid.identifier_type) { | |
b23adfd4 TL |
648 | (2, 0) => { |
649 | // Serial number only (Quantum Superloader3 uses this) | |
02631056 DC |
650 | let serial = reader.read_exact_allocated(dvcid.identifier_len as usize)?; |
651 | let serial = scsi_ascii_to_string(&serial); | |
652 | (Some(serial), None, None) | |
653 | } | |
654 | (2, 1) => { | |
655 | if dvcid.identifier_len != 34 { | |
656 | bail!("got wrong DVCID length"); | |
657 | } | |
658 | let vendor = reader.read_exact_allocated(8)?; | |
659 | let vendor = scsi_ascii_to_string(&vendor); | |
660 | let model = reader.read_exact_allocated(16)?; | |
661 | let model = scsi_ascii_to_string(&model); | |
662 | let serial = reader.read_exact_allocated(10)?; | |
663 | let serial = scsi_ascii_to_string(&serial); | |
664 | (Some(serial), Some(vendor), Some(model)) | |
665 | } | |
666 | _ => (None, None, None), | |
667 | }; | |
668 | ||
669 | Ok(DvcidInfo { | |
670 | vendor, | |
671 | model, | |
672 | serial, | |
673 | }) | |
674 | } | |
675 | ||
697c41c5 | 676 | fn decode_element_status_page( |
697c41c5 DM |
677 | data: &[u8], |
678 | start_element_address: u16, | |
679 | ) -> Result<DecodedStatusPage, Error> { | |
6ef1b649 | 680 | proxmox_lang::try_block!({ |
697c41c5 DM |
681 | let mut result = DecodedStatusPage { |
682 | last_element_address: None, | |
683 | transports: Vec::new(), | |
684 | drives: Vec::new(), | |
685 | storage_slots: Vec::new(), | |
686 | import_export_slots: Vec::new(), | |
687 | }; | |
688 | ||
dcf5a0f6 | 689 | let mut reader = data; |
697c41c5 DM |
690 | |
691 | let head: ElementStatusHeader = unsafe { reader.read_be_value()? }; | |
692 | ||
693 | if head.number_of_elements_available == 0 { | |
694 | return Ok(result); | |
695 | } | |
696 | ||
697 | if head.first_element_address_reported < start_element_address { | |
698 | bail!("got wrong first_element_address_reported"); // sanity check | |
699 | } | |
700 | ||
13e13d83 DC |
701 | let len = head.byte_count_of_report_available; |
702 | let len = ((len[0] as usize) << 16) + ((len[1] as usize) << 8) + (len[2] as usize); | |
703 | ||
704 | if len < reader.len() { | |
705 | reader = &reader[..len]; | |
706 | } else if len > reader.len() { | |
b23adfd4 TL |
707 | bail!( |
708 | "wrong amount of data: expected {}, got {}", | |
709 | len, | |
710 | reader.len() | |
711 | ); | |
13e13d83 DC |
712 | } |
713 | ||
697c41c5 DM |
714 | loop { |
715 | if reader.is_empty() { | |
716 | break; | |
717 | } | |
718 | ||
719 | let subhead: SubHeader = unsafe { reader.read_be_value()? }; | |
720 | ||
721 | let len = subhead.byte_count_of_descriptor_data_available; | |
722 | let mut len = ((len[0] as usize) << 16) + ((len[1] as usize) << 8) + (len[2] as usize); | |
723 | if len > reader.len() { | |
724 | len = reader.len(); | |
725 | } | |
726 | ||
727 | let descr_data = reader.read_exact_allocated(len)?; | |
697c41c5 | 728 | |
42b01017 DC |
729 | let descr_len = subhead.descriptor_length as usize; |
730 | ||
731 | if descr_len == 0 { | |
732 | bail!("got elements, but descriptor length 0"); | |
733 | } | |
734 | ||
735 | for descriptor in descr_data.chunks_exact(descr_len) { | |
dcf5a0f6 | 736 | let mut reader = descriptor; |
71e83e1b | 737 | |
697c41c5 DM |
738 | match subhead.element_type_code { |
739 | 1 => { | |
c1feb447 | 740 | let desc: TransportDescriptor = unsafe { reader.read_be_value()? }; |
697c41c5 DM |
741 | |
742 | let full = (desc.flags1 & 1) != 0; | |
743 | let volume_tag = subhead.parse_optional_volume_tag(&mut reader, full)?; | |
744 | ||
745 | subhead.skip_alternate_volume_tag(&mut reader)?; | |
746 | ||
697c41c5 DM |
747 | result.last_element_address = Some(desc.element_address); |
748 | ||
749 | let status = TransportElementStatus { | |
750 | status: create_element_status(full, volume_tag), | |
751 | element_address: desc.element_address, | |
752 | }; | |
753 | result.transports.push(status); | |
754 | } | |
755 | 2 | 3 => { | |
756 | let desc: StorageDescriptor = unsafe { reader.read_be_value()? }; | |
757 | ||
758 | let full = (desc.flags1 & 1) != 0; | |
759 | let volume_tag = subhead.parse_optional_volume_tag(&mut reader, full)?; | |
760 | ||
761 | subhead.skip_alternate_volume_tag(&mut reader)?; | |
762 | ||
697c41c5 DM |
763 | result.last_element_address = Some(desc.element_address); |
764 | ||
765 | if subhead.element_type_code == 3 { | |
766 | let status = StorageElementStatus { | |
767 | import_export: true, | |
768 | status: create_element_status(full, volume_tag), | |
769 | element_address: desc.element_address, | |
770 | }; | |
771 | result.import_export_slots.push(status); | |
772 | } else { | |
773 | let status = StorageElementStatus { | |
774 | import_export: false, | |
775 | status: create_element_status(full, volume_tag), | |
776 | element_address: desc.element_address, | |
777 | }; | |
778 | result.storage_slots.push(status); | |
779 | } | |
780 | } | |
781 | 4 => { | |
782 | let desc: TransferDescriptor = unsafe { reader.read_be_value()? }; | |
783 | ||
b23adfd4 TL |
784 | let loaded_slot = if (desc.flags2 & 128) != 0 { |
785 | // SValid | |
697c41c5 DM |
786 | Some(desc.source_storage_element_address as u64) |
787 | } else { | |
788 | None | |
789 | }; | |
790 | ||
791 | let full = (desc.flags1 & 1) != 0; | |
792 | let volume_tag = subhead.parse_optional_volume_tag(&mut reader, full)?; | |
793 | ||
794 | subhead.skip_alternate_volume_tag(&mut reader)?; | |
795 | ||
02631056 DC |
796 | let dvcid = decode_dvcid_info(&mut reader).unwrap_or(DvcidInfo { |
797 | vendor: None, | |
798 | model: None, | |
799 | serial: None, | |
800 | }); | |
697c41c5 DM |
801 | |
802 | result.last_element_address = Some(desc.element_address); | |
803 | ||
804 | let drive = DriveStatus { | |
805 | loaded_slot, | |
806 | status: create_element_status(full, volume_tag), | |
02631056 DC |
807 | drive_serial_number: dvcid.serial, |
808 | vendor: dvcid.vendor, | |
809 | model: dvcid.model, | |
697c41c5 DM |
810 | element_address: desc.element_address, |
811 | }; | |
812 | result.drives.push(drive); | |
813 | } | |
814 | code => bail!("got unknown element type code {}", code), | |
815 | } | |
816 | } | |
817 | } | |
818 | ||
819 | Ok(result) | |
b23adfd4 TL |
820 | }) |
821 | .map_err(|err: Error| format_err!("decode element status failed - {}", err)) | |
697c41c5 DM |
822 | } |
823 | ||
824 | /// Open the device for read/write, returns the file handle | |
825 | pub fn open<P: AsRef<Path>>(path: P) -> Result<File, Error> { | |
b23adfd4 | 826 | let file = OpenOptions::new().read(true).write(true).open(path)?; |
697c41c5 DM |
827 | |
828 | Ok(file) | |
829 | } | |
4f57f4ad DC |
830 | |
831 | #[cfg(test)] | |
832 | mod test { | |
4f57f4ad | 833 | use super::*; |
b23adfd4 | 834 | use anyhow::Error; |
4f57f4ad DC |
835 | |
836 | struct StorageDesc { | |
837 | address: u16, | |
838 | pvoltag: Option<String>, | |
839 | } | |
840 | ||
841 | fn build_element_status_page( | |
842 | descriptors: Vec<StorageDesc>, | |
843 | trailing: &[u8], | |
844 | element_type: u8, | |
845 | ) -> Vec<u8> { | |
b23adfd4 TL |
846 | let descs: Vec<Vec<u8>> = descriptors |
847 | .iter() | |
848 | .map(|desc| build_storage_descriptor(desc, trailing)) | |
849 | .collect(); | |
4f57f4ad DC |
850 | |
851 | let (desc_len, address) = if let Some(el) = descs.get(0) { | |
852 | (el.len() as u16, descriptors[0].address) | |
853 | } else { | |
854 | (0u16, 0u16) | |
855 | }; | |
856 | ||
857 | let descriptor_byte_count = desc_len * descs.len() as u16; | |
858 | let byte_count = 8 + descriptor_byte_count; | |
859 | ||
860 | let mut res = Vec::new(); | |
861 | ||
862 | res.extend_from_slice(&address.to_be_bytes()); | |
863 | res.extend_from_slice(&(descs.len() as u16).to_be_bytes()); | |
864 | res.push(0); | |
865 | let byte_count = byte_count as u32; | |
866 | res.extend_from_slice(&byte_count.to_be_bytes()[1..]); | |
867 | ||
868 | res.push(element_type); | |
869 | res.push(0x80); | |
870 | res.extend_from_slice(&desc_len.to_be_bytes()); | |
871 | res.push(0); | |
872 | let descriptor_byte_count = descriptor_byte_count as u32; | |
873 | res.extend_from_slice(&descriptor_byte_count.to_be_bytes()[1..]); | |
874 | ||
875 | for desc in descs { | |
876 | res.extend_from_slice(&desc); | |
877 | } | |
878 | ||
879 | res.extend_from_slice(trailing); | |
880 | ||
881 | res | |
882 | } | |
883 | ||
b23adfd4 | 884 | fn build_storage_descriptor(desc: &StorageDesc, trailing: &[u8]) -> Vec<u8> { |
4f57f4ad DC |
885 | let mut res = Vec::new(); |
886 | res.push(((desc.address >> 8) & 0xFF) as u8); | |
887 | res.push((desc.address & 0xFF) as u8); | |
888 | if desc.pvoltag.is_some() { | |
889 | res.push(0x01); // full | |
890 | } else { | |
891 | res.push(0x00); // full | |
892 | } | |
893 | ||
b23adfd4 | 894 | res.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0x80]); |
4f57f4ad DC |
895 | res.push(((desc.address >> 8) & 0xFF) as u8); |
896 | res.push((desc.address & 0xFF) as u8); | |
897 | ||
898 | if let Some(voltag) = &desc.pvoltag { | |
899 | res.extend_from_slice(voltag.as_bytes()); | |
900 | let rem = SCSI_VOLUME_TAG_LEN - voltag.as_bytes().len(); | |
901 | if rem > 0 { | |
902 | res.resize(res.len() + rem, 0); | |
903 | } | |
904 | } | |
905 | ||
906 | res.extend_from_slice(trailing); | |
907 | ||
908 | res | |
909 | } | |
910 | ||
911 | #[test] | |
912 | fn status_page_valid() -> Result<(), Error> { | |
913 | let descs = vec![ | |
914 | StorageDesc { | |
915 | address: 0, | |
916 | pvoltag: Some("0123456789".to_string()), | |
917 | }, | |
918 | StorageDesc { | |
919 | address: 1, | |
920 | pvoltag: Some("1234567890".to_string()), | |
921 | }, | |
922 | ]; | |
923 | let test_data = build_element_status_page(descs, &[], 0x2); | |
924 | let page = decode_element_status_page(&test_data, 0)?; | |
925 | assert_eq!(page.storage_slots.len(), 2); | |
926 | Ok(()) | |
927 | } | |
928 | ||
929 | #[test] | |
930 | fn status_page_too_short() -> Result<(), Error> { | |
931 | let descs = vec![ | |
932 | StorageDesc { | |
933 | address: 0, | |
934 | pvoltag: Some("0123456789".to_string()), | |
935 | }, | |
936 | StorageDesc { | |
937 | address: 1, | |
938 | pvoltag: Some("1234567890".to_string()), | |
939 | }, | |
940 | ]; | |
941 | let test_data = build_element_status_page(descs, &[], 0x2); | |
942 | let len = test_data.len(); | |
943 | let res = decode_element_status_page(&test_data[..(len - 10)], 0); | |
944 | assert!(res.is_err()); | |
945 | Ok(()) | |
946 | } | |
947 | ||
948 | #[test] | |
949 | fn status_page_too_large() -> Result<(), Error> { | |
950 | let descs = vec![ | |
951 | StorageDesc { | |
952 | address: 0, | |
953 | pvoltag: Some("0123456789".to_string()), | |
954 | }, | |
955 | StorageDesc { | |
956 | address: 1, | |
957 | pvoltag: Some("1234567890".to_string()), | |
958 | }, | |
959 | ]; | |
b23adfd4 | 960 | let test_data = build_element_status_page(descs, &[0, 0, 0, 0, 0], 0x2); |
4f57f4ad DC |
961 | let page = decode_element_status_page(&test_data, 0)?; |
962 | assert_eq!(page.storage_slots.len(), 2); | |
963 | Ok(()) | |
964 | } | |
965 | } |