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