]> git.proxmox.com Git - proxmox-backup.git/blame - src/tape/drive/lto/mod.rs
update to first proxmox crate split
[proxmox-backup.git] / src / tape / drive / lto / mod.rs
CommitLineData
a79082a0
DM
1//! Driver for LTO SCSI tapes
2//!
3//! This is a userspace drive implementation using SG_IO.
4//!
5//! Why we do not use the Linux tape driver:
6//!
7//! - missing features (MAM, Encryption, ...)
8//!
9//! - strange permission handling - only root (or CAP_SYS_RAWIO) can
10//! do SG_IO (SYS_RAW_IO)
11//!
12//! - unability to detect EOT (you just get EIO)
13
048b43af 14use std::fs::File;
a79082a0 15use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
5d6379f8 16use std::convert::TryInto;
a79082a0
DM
17
18use anyhow::{bail, format_err, Error};
a79082a0 19
6ef1b649 20use proxmox_uuid::Uuid;
a79082a0 21
5839c469
DM
22use pbs_api_types::{
23 Fingerprint, MamAttribute, LtoDriveAndMediaStatus, LtoTapeDrive, Lp17VolumeStatistics,
24};
bbdda58b 25use pbs_config::key_config::KeyConfig;
4c1b7761 26use pbs_tools::run_command;
048b43af
DM
27use pbs_tape::{
28 TapeWrite, TapeRead, BlockReadError, MediaContentHeader,
29 sg_tape::{SgTape, TapeAlertFlags},
30 linux_list_drives::open_lto_tape_device,
31};
b2065dc7 32
a79082a0 33use crate::{
a79082a0 34 tape::{
048b43af
DM
35 drive::TapeDriver,
36 file_formats::{PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, MediaSetLabel},
a79082a0
DM
37 },
38};
39
1ce8e905
DM
40/// Open a tape device
41///
42/// This does additional checks:
43///
44/// - check if it is a non-rewinding tape device
45/// - check if drive is ready (tape loaded)
46/// - check block size
47/// - for autoloader only, try to reload ejected tapes
48pub fn open_lto_tape_drive(config: &LtoTapeDrive) -> Result<LtoTapeHandle, Error> {
49
6ef1b649 50 proxmox_lang::try_block!({
1ce8e905
DM
51 let file = open_lto_tape_device(&config.path)?;
52
53 let mut handle = LtoTapeHandle::new(file)?;
54
55 if !handle.sg_tape.test_unit_ready().is_ok() {
56 // for autoloader only, try to reload ejected tapes
57 if config.changer.is_some() {
58 let _ = handle.sg_tape.load(); // just try, ignore error
a79082a0 59 }
1ce8e905 60 }
a79082a0 61
1ce8e905 62 handle.sg_tape.wait_until_ready()?;
a79082a0 63
1ce8e905 64 handle.set_default_options()?;
a79082a0 65
1ce8e905
DM
66 Ok(handle)
67 }).map_err(|err: Error| format_err!("open drive '{}' ({}) failed - {}", config.name, config.path, err))
a79082a0
DM
68}
69
70/// Lto Tape device handle
71pub struct LtoTapeHandle {
72 sg_tape: SgTape,
73}
74
75impl LtoTapeHandle {
76
77 /// Creates a new instance
78 pub fn new(file: File) -> Result<Self, Error> {
79 let sg_tape = SgTape::new(file)?;
80 Ok(Self { sg_tape })
81 }
82
83 /// Set all options we need/want
0892a512 84 pub fn set_default_options(&mut self) -> Result<(), Error> {
048b43af 85 self.sg_tape.set_default_options()?;
a79082a0
DM
86 Ok(())
87 }
88
80ea23e1
DM
89 /// Set driver options
90 pub fn set_drive_options(
91 &mut self,
92 compression: Option<bool>,
93 block_length: Option<u32>,
94 buffer_mode: Option<bool>,
95 ) -> Result<(), Error> {
96 self.sg_tape.set_drive_options(compression, block_length, buffer_mode)
97 }
98
a79082a0
DM
99 /// Write a single EOF mark without flushing buffers
100 pub fn write_filemarks(&mut self, count: usize) -> Result<(), std::io::Error> {
101 self.sg_tape.write_filemarks(count, false)
102 }
103
104 /// Get Tape and Media status
105 pub fn get_drive_and_media_status(&mut self) -> Result<LtoDriveAndMediaStatus, Error> {
048b43af 106 self.sg_tape.get_drive_and_media_status()
a79082a0
DM
107 }
108
8b2c6f5d 109 pub fn forward_space_count_files(&mut self, count: usize) -> Result<(), Error> {
5d6379f8 110 self.sg_tape.space_filemarks(count.try_into()?)
8b2c6f5d
DM
111 }
112
113 pub fn backward_space_count_files(&mut self, count: usize) -> Result<(), Error> {
5d6379f8 114 self.sg_tape.space_filemarks(-count.try_into()?)
8b2c6f5d
DM
115 }
116
7f745967 117 pub fn forward_space_count_records(&mut self, count: usize) -> Result<(), Error> {
5d6379f8 118 self.sg_tape.space_blocks(count.try_into()?)
7f745967
DM
119 }
120
121 pub fn backward_space_count_records(&mut self, count: usize) -> Result<(), Error> {
5d6379f8
DM
122 self.sg_tape.space_blocks(-count.try_into()?)
123 }
124
125 /// Position the tape after filemark count. Count 0 means BOT.
5d6379f8 126 pub fn locate_file(&mut self, position: u64) -> Result<(), Error> {
bbbf662d 127 self.sg_tape.locate_file(position)
7f745967
DM
128 }
129
e29f456e
DM
130 pub fn erase_media(&mut self, fast: bool) -> Result<(), Error> {
131 self.sg_tape.erase_media(fast)
132 }
133
a79082a0
DM
134 pub fn load(&mut self) -> Result<(), Error> {
135 self.sg_tape.load()
136 }
137
138 /// Read Cartridge Memory (MAM Attributes)
139 pub fn cartridge_memory(&mut self) -> Result<Vec<MamAttribute>, Error> {
140 self.sg_tape.cartridge_memory()
141 }
142
143 /// Read Volume Statistics
144 pub fn volume_statistics(&mut self) -> Result<Lp17VolumeStatistics, Error> {
145 self.sg_tape.volume_statistics()
146 }
566b946f
DM
147
148 /// Lock the drive door
149 pub fn lock(&mut self) -> Result<(), Error> {
150 self.sg_tape.set_medium_removal(false)
151 .map_err(|err| format_err!("lock door failed - {}", err))
152 }
153
154 /// Unlock the drive door
155 pub fn unlock(&mut self) -> Result<(), Error> {
156 self.sg_tape.set_medium_removal(true)
157 .map_err(|err| format_err!("unlock door failed - {}", err))
158 }
a79082a0
DM
159}
160
161
162impl TapeDriver for LtoTapeHandle {
163
164 fn sync(&mut self) -> Result<(), Error> {
165 self.sg_tape.sync()?;
166 Ok(())
167 }
168
169 /// Go to the end of the recorded media (for appending files).
7b11a809
DM
170 fn move_to_eom(&mut self, write_missing_eof: bool) -> Result<(), Error> {
171 self.sg_tape.move_to_eom(write_missing_eof)
a79082a0
DM
172 }
173
8b2c6f5d 174 fn move_to_last_file(&mut self) -> Result<(), Error> {
a79082a0 175
7b11a809
DM
176 self.move_to_eom(false)?;
177
178 self.sg_tape.check_filemark()?;
8b2c6f5d
DM
179
180 let pos = self.current_file_number()?;
181
182 if pos == 0 {
183 bail!("move_to_last_file failed - media contains no data");
184 }
185
186 if pos == 1 {
187 self.rewind()?;
188 return Ok(());
189 }
190
191 self.backward_space_count_files(2)?;
192 self.forward_space_count_files(1)?;
193
194 Ok(())
a79082a0
DM
195 }
196
56d36ca4
DC
197 fn move_to_file(&mut self, file: u64) -> Result<(), Error> {
198 self.locate_file(file)
199 }
200
a79082a0
DM
201 fn rewind(&mut self) -> Result<(), Error> {
202 self.sg_tape.rewind()
203 }
204
205 fn current_file_number(&mut self) -> Result<u64, Error> {
206 self.sg_tape.current_file_number()
207 }
208
e29f456e
DM
209 fn format_media(&mut self, fast: bool) -> Result<(), Error> {
210 self.sg_tape.format_media(fast)
a79082a0
DM
211 }
212
318b3106 213 fn read_next_file<'a>(&'a mut self) -> Result<Box<dyn TapeRead + 'a>, BlockReadError> {
a79082a0 214 let reader = self.sg_tape.open_reader()?;
318b3106 215 let handle: Box<dyn TapeRead> = Box::new(reader);
a79082a0
DM
216 Ok(handle)
217 }
218
219 fn write_file<'a>(&'a mut self) -> Result<Box<dyn TapeWrite + 'a>, std::io::Error> {
220 let handle = self.sg_tape.open_writer();
221 Ok(Box::new(handle))
222 }
223
224 fn write_media_set_label(
225 &mut self,
226 media_set_label: &MediaSetLabel,
227 key_config: Option<&KeyConfig>,
228 ) -> Result<(), Error> {
229
230 let file_number = self.current_file_number()?;
231 if file_number != 1 {
232 self.rewind()?;
233 self.forward_space_count_files(1)?; // skip label
234 }
235
236 let file_number = self.current_file_number()?;
237 if file_number != 1 {
238 bail!("write_media_set_label failed - got wrong file number ({} != 1)", file_number);
239 }
240
241 self.set_encryption(None)?;
242
243 { // limit handle scope
244 let mut handle = self.write_file()?;
245
246 let mut value = serde_json::to_value(media_set_label)?;
247 if media_set_label.encryption_key_fingerprint.is_some() {
248 match key_config {
249 Some(key_config) => {
250 value["key-config"] = serde_json::to_value(key_config)?;
251 }
252 None => {
253 bail!("missing encryption key config");
254 }
255 }
256 }
257
258 let raw = serde_json::to_string_pretty(&value)?;
259
260 let header = MediaContentHeader::new(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, raw.len() as u32);
261 handle.write_header(&header, raw.as_bytes())?;
262 handle.finish(false)?;
263 }
264
265 self.sync()?; // sync data to tape
266
267 Ok(())
268 }
269
270 /// Rewind and put the drive off line (Eject media).
271 fn eject_media(&mut self) -> Result<(), Error> {
272 self.sg_tape.eject()
273 }
274
275 /// Read Tape Alert Flags
276 fn tape_alert_flags(&mut self) -> Result<TapeAlertFlags, Error> {
277 self.sg_tape.tape_alert_flags()
278 }
279
280 /// Set or clear encryption key
281 ///
282 /// Note: Only 'root' can read secret encryption keys, so we need
283 /// to spawn setuid binary 'sg-tape-cmd'.
284 fn set_encryption(
285 &mut self,
286 key_fingerprint: Option<(Fingerprint, Uuid)>,
287 ) -> Result<(), Error> {
288
289 if nix::unistd::Uid::effective().is_root() {
290
291 if let Some((ref key_fingerprint, ref uuid)) = key_fingerprint {
292
5839c469 293 let (key_map, _digest) = pbs_config::tape_encryption_keys::load_keys()?;
a79082a0
DM
294 match key_map.get(key_fingerprint) {
295 Some(item) => {
296
297 // derive specialized key for each media-set
298
299 let mut tape_key = [0u8; 32];
300
301 let uuid_bytes: [u8; 16] = uuid.as_bytes().clone();
302
303 openssl::pkcs5::pbkdf2_hmac(
304 &item.key,
305 &uuid_bytes,
306 10,
307 openssl::hash::MessageDigest::sha256(),
308 &mut tape_key)?;
309
310 return self.sg_tape.set_encryption(Some(tape_key));
311 }
312 None => bail!("unknown tape encryption key '{}'", key_fingerprint),
313 }
314 } else {
315 return self.sg_tape.set_encryption(None);
316 }
317 }
318
319 let output = if let Some((fingerprint, uuid)) = key_fingerprint {
770a36e5 320 let fingerprint = pbs_tools::format::as_fingerprint(fingerprint.bytes());
a79082a0
DM
321 run_sg_tape_cmd("encryption", &[
322 "--fingerprint", &fingerprint,
323 "--uuid", &uuid.to_string(),
324 ], self.sg_tape.file_mut().as_raw_fd())?
325 } else {
326 run_sg_tape_cmd("encryption", &[], self.sg_tape.file_mut().as_raw_fd())?
327 };
328 let result: Result<(), String> = serde_json::from_str(&output)?;
329 result.map_err(|err| format_err!("{}", err))
330 }
331}
332
a79082a0
DM
333fn run_sg_tape_cmd(subcmd: &str, args: &[&str], fd: RawFd) -> Result<String, Error> {
334 let mut command = std::process::Command::new(
335 "/usr/lib/x86_64-linux-gnu/proxmox-backup/sg-tape-cmd");
336 command.args(&[subcmd]);
337 command.args(&["--stdin"]);
338 command.args(args);
339 let device_fd = nix::unistd::dup(fd)?;
340 command.stdin(unsafe { std::process::Stdio::from_raw_fd(device_fd)});
341 run_command(command, None)
342}