]> git.proxmox.com Git - proxmox-backup.git/blob - pbs-tape/src/lib.rs
AuthId: derive Ord and PartialOrd
[proxmox-backup.git] / pbs-tape / src / lib.rs
1 use std::collections::HashSet;
2
3 use anyhow::{bail, Error};
4 use bitflags::bitflags;
5 use endian_trait::Endian;
6 use serde::{Deserialize, Serialize};
7 use serde_json::Value;
8
9 use proxmox_uuid::Uuid;
10
11 use pbs_api_types::{ScsiTapeChanger, SLOT_ARRAY_SCHEMA};
12
13 pub mod linux_list_drives;
14
15 pub mod sgutils2;
16
17 mod blocked_reader;
18 pub use blocked_reader::BlockedReader;
19
20 mod blocked_writer;
21 pub use blocked_writer::BlockedWriter;
22
23 mod tape_write;
24 pub use tape_write::*;
25
26 mod tape_read;
27 pub use tape_read::*;
28
29 mod emulate_tape_reader;
30 pub use emulate_tape_reader::EmulateTapeReader;
31
32 mod emulate_tape_writer;
33 pub use emulate_tape_writer::EmulateTapeWriter;
34
35 pub mod sg_tape;
36
37 pub mod sg_pt_changer;
38
39 /// We use 256KB blocksize (always)
40 pub const PROXMOX_TAPE_BLOCK_SIZE: usize = 256 * 1024;
41
42 // openssl::sha::sha256(b"Proxmox Tape Block Header v1.0")[0..8]
43 pub const PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0: [u8; 8] = [220, 189, 175, 202, 235, 160, 165, 40];
44
45 // openssl::sha::sha256(b"Proxmox Backup Content Header v1.0")[0..8];
46 pub const PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0: [u8; 8] = [99, 238, 20, 159, 205, 242, 155, 12];
47 // openssl::sha::sha256(b"Proxmox Backup Tape Label v1.0")[0..8];
48 pub const PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0: [u8; 8] = [42, 5, 191, 60, 176, 48, 170, 57];
49 // openssl::sha::sha256(b"Proxmox Backup MediaSet Label v1.0")
50 pub const PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0: [u8; 8] = [8, 96, 99, 249, 47, 151, 83, 216];
51
52 /// Tape Block Header with data payload
53 ///
54 /// All tape files are written as sequence of blocks.
55 ///
56 /// Note: this struct is large, never put this on the stack!
57 /// so we use an unsized type to avoid that.
58 ///
59 /// Tape data block are always read/written with a fixed size
60 /// (`PROXMOX_TAPE_BLOCK_SIZE`). But they may contain less data, so the
61 /// header has an additional size field. For streams of blocks, there
62 /// is a sequence number (`seq_nr`) which may be use for additional
63 /// error checking.
64 #[repr(C, packed)]
65 pub struct BlockHeader {
66 /// fixed value `PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0`
67 pub magic: [u8; 8],
68 pub flags: BlockHeaderFlags,
69 /// size as 3 bytes unsigned, little endian
70 pub size: [u8; 3],
71 /// block sequence number
72 pub seq_nr: u32,
73 pub payload: [u8],
74 }
75
76 bitflags! {
77 /// Header flags (e.g. `END_OF_STREAM` or `INCOMPLETE`)
78 pub struct BlockHeaderFlags: u8 {
79 /// Marks the last block in a stream.
80 const END_OF_STREAM = 0b00000001;
81 /// Mark multivolume streams (when set in the last block)
82 const INCOMPLETE = 0b00000010;
83 }
84 }
85
86 #[derive(Endian, Copy, Clone, Debug)]
87 #[repr(C, packed)]
88 /// Media Content Header
89 ///
90 /// All tape files start with this header. The header may contain some
91 /// informational data indicated by `size`.
92 ///
93 /// `| MediaContentHeader | header data (size) | stream data |`
94 ///
95 /// Note: The stream data following may be of any size.
96 pub struct MediaContentHeader {
97 /// fixed value `PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0`
98 pub magic: [u8; 8],
99 /// magic number for the content following
100 pub content_magic: [u8; 8],
101 /// unique ID to identify this data stream
102 pub uuid: [u8; 16],
103 /// stream creation time
104 pub ctime: i64,
105 /// Size of header data
106 pub size: u32,
107 /// Part number for multipart archives.
108 pub part_number: u8,
109 /// Reserved for future use
110 pub reserved_0: u8,
111 /// Reserved for future use
112 pub reserved_1: u8,
113 /// Reserved for future use
114 pub reserved_2: u8,
115 }
116
117 impl MediaContentHeader {
118 /// Create a new instance with autogenerated Uuid
119 pub fn new(content_magic: [u8; 8], size: u32) -> Self {
120 let uuid = *Uuid::generate().into_inner();
121 Self {
122 magic: PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0,
123 content_magic,
124 uuid,
125 ctime: proxmox_time::epoch_i64(),
126 size,
127 part_number: 0,
128 reserved_0: 0,
129 reserved_1: 0,
130 reserved_2: 0,
131 }
132 }
133
134 /// Helper to check magic numbers and size constraints
135 pub fn check(&self, content_magic: [u8; 8], min_size: u32, max_size: u32) -> Result<(), Error> {
136 if self.magic != PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0 {
137 bail!("MediaContentHeader: wrong magic");
138 }
139 if self.content_magic != content_magic {
140 bail!("MediaContentHeader: wrong content magic");
141 }
142 if self.size < min_size || self.size > max_size {
143 bail!("MediaContentHeader: got unexpected size");
144 }
145 Ok(())
146 }
147
148 /// Returns the content Uuid
149 pub fn content_uuid(&self) -> Uuid {
150 Uuid::from(self.uuid)
151 }
152 }
153
154 impl BlockHeader {
155 pub const SIZE: usize = PROXMOX_TAPE_BLOCK_SIZE;
156
157 /// Allocates a new instance on the heap
158 pub fn new() -> Box<Self> {
159 use std::alloc::{alloc_zeroed, Layout};
160
161 // align to PAGESIZE, so that we can use it with SG_IO
162 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
163
164 let mut buffer = unsafe {
165 let ptr = alloc_zeroed(Layout::from_size_align(Self::SIZE, page_size).unwrap());
166 Box::from_raw(
167 std::slice::from_raw_parts_mut(ptr, Self::SIZE - 16) as *mut [u8] as *mut Self,
168 )
169 };
170 buffer.magic = PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0;
171 buffer
172 }
173
174 /// Set the `size` field
175 pub fn set_size(&mut self, size: usize) {
176 let size = size.to_le_bytes();
177 self.size.copy_from_slice(&size[..3]);
178 }
179
180 /// Returns the `size` field
181 pub fn size(&self) -> usize {
182 (self.size[0] as usize) + ((self.size[1] as usize) << 8) + ((self.size[2] as usize) << 16)
183 }
184
185 /// Set the `seq_nr` field
186 pub fn set_seq_nr(&mut self, seq_nr: u32) {
187 self.seq_nr = seq_nr.to_le();
188 }
189
190 /// Returns the `seq_nr` field
191 pub fn seq_nr(&self) -> u32 {
192 u32::from_le(self.seq_nr)
193 }
194 }
195
196 /// Changer element status.
197 ///
198 /// Drive and slots may be `Empty`, or contain some media, either
199 /// with known volume tag `VolumeTag(String)`, or without (`Full`).
200 #[derive(Serialize, Deserialize, Debug)]
201 pub enum ElementStatus {
202 Empty,
203 Full,
204 VolumeTag(String),
205 }
206
207 /// Changer drive status.
208 #[derive(Serialize, Deserialize)]
209 pub struct DriveStatus {
210 /// The slot the element was loaded from (if known).
211 pub loaded_slot: Option<u64>,
212 /// The status.
213 pub status: ElementStatus,
214 /// Drive Identifier (Serial number)
215 pub drive_serial_number: Option<String>,
216 /// Drive Vendor
217 pub vendor: Option<String>,
218 /// Drive Model
219 pub model: Option<String>,
220 /// Element Address
221 pub element_address: u16,
222 }
223
224 /// Storage element status.
225 #[derive(Serialize, Deserialize)]
226 pub struct StorageElementStatus {
227 /// Flag for Import/Export slots
228 pub import_export: bool,
229 /// The status.
230 pub status: ElementStatus,
231 /// Element Address
232 pub element_address: u16,
233 }
234
235 /// Transport element status.
236 #[derive(Serialize, Deserialize)]
237 pub struct TransportElementStatus {
238 /// The status.
239 pub status: ElementStatus,
240 /// Element Address
241 pub element_address: u16,
242 }
243
244 /// Changer status - show drive/slot usage
245 #[derive(Serialize, Deserialize)]
246 pub struct MtxStatus {
247 /// List of known drives
248 pub drives: Vec<DriveStatus>,
249 /// List of known storage slots
250 pub slots: Vec<StorageElementStatus>,
251 /// Transport elements
252 ///
253 /// Note: Some libraries do not report transport elements.
254 pub transports: Vec<TransportElementStatus>,
255 }
256
257 impl MtxStatus {
258 pub fn slot_address(&self, slot: u64) -> Result<u16, Error> {
259 if slot == 0 {
260 bail!("invalid slot number '{}' (slots numbers starts at 1)", slot);
261 }
262 if slot > (self.slots.len() as u64) {
263 bail!(
264 "invalid slot number '{}' (max {} slots)",
265 slot,
266 self.slots.len()
267 );
268 }
269
270 Ok(self.slots[(slot - 1) as usize].element_address)
271 }
272
273 pub fn drive_address(&self, drivenum: u64) -> Result<u16, Error> {
274 if drivenum >= (self.drives.len() as u64) {
275 bail!("invalid drive number '{}'", drivenum);
276 }
277
278 Ok(self.drives[drivenum as usize].element_address)
279 }
280
281 pub fn transport_address(&self) -> u16 {
282 // simply use first transport
283 // (are there changers exposing more than one?)
284 // defaults to 0 for changer that do not report transports
285 self.transports
286 .get(0)
287 .map(|t| t.element_address)
288 .unwrap_or(0u16)
289 }
290
291 pub fn find_free_slot(&self, import_export: bool) -> Option<u64> {
292 let mut free_slot = None;
293 for (i, slot_info) in self.slots.iter().enumerate() {
294 if slot_info.import_export != import_export {
295 continue; // skip slots of wrong type
296 }
297 if let ElementStatus::Empty = slot_info.status {
298 free_slot = Some((i + 1) as u64);
299 break;
300 }
301 }
302 free_slot
303 }
304
305 pub fn mark_import_export_slots(&mut self, config: &ScsiTapeChanger) -> Result<(), Error> {
306 let mut export_slots: HashSet<u64> = HashSet::new();
307
308 if let Some(slots) = &config.export_slots {
309 let slots: Value = SLOT_ARRAY_SCHEMA.parse_property_string(slots)?;
310 export_slots = slots
311 .as_array()
312 .unwrap()
313 .iter()
314 .filter_map(|v| v.as_u64())
315 .collect();
316 }
317
318 for (i, entry) in self.slots.iter_mut().enumerate() {
319 let slot = i as u64 + 1;
320 if export_slots.contains(&slot) {
321 entry.import_export = true; // mark as IMPORT/EXPORT
322 }
323 }
324
325 Ok(())
326 }
327 }