]> git.proxmox.com Git - proxmox-backup.git/blame - src/tape/drive/lto/mod.rs
tape: fix wrongly unloading encryption key
[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};
a79082a0
DM
16
17use anyhow::{bail, format_err, Error};
a79082a0 18
6ef1b649 19use proxmox_uuid::Uuid;
a79082a0 20
5839c469 21use pbs_api_types::{
4de1c42c 22 Fingerprint, Lp17VolumeStatistics, LtoDriveAndMediaStatus, LtoTapeDrive, MamAttribute,
5839c469 23};
1104d2a2 24use pbs_key_config::KeyConfig;
048b43af 25use pbs_tape::{
4de1c42c
TL
26 sg_tape::{SgTape, TapeAlertFlags},
27 BlockReadError, MediaContentHeader, TapeRead, TapeWrite,
048b43af 28};
4de1c42c 29use proxmox_sys::command::run_command;
b2065dc7 30
4de1c42c
TL
31use crate::tape::{
32 drive::TapeDriver,
33 file_formats::{MediaSetLabel, PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0},
a79082a0
DM
34};
35
1dc0df33
DC
36impl Drop for LtoTapeHandle {
37 fn drop(&mut self) {
38 // always unload the encryption key when the handle is dropped for security
39 // but only log an error if we set one in the first place
40 if let Err(err) = self.set_encryption(None) {
41 if self.encryption_key_loaded {
42 log::error!("could not unload encryption key from drive: {err}");
43 }
44 }
45 }
46}
47
a79082a0
DM
48/// Lto Tape device handle
49pub struct LtoTapeHandle {
50 sg_tape: SgTape,
1dc0df33 51 encryption_key_loaded: bool,
a79082a0
DM
52}
53
54impl LtoTapeHandle {
a79082a0
DM
55 /// Creates a new instance
56 pub fn new(file: File) -> Result<Self, Error> {
57 let sg_tape = SgTape::new(file)?;
1dc0df33
DC
58 Ok(Self {
59 sg_tape,
60 encryption_key_loaded: false,
61 })
a79082a0
DM
62 }
63
b5f86267
DC
64 /// Open a tape device
65 ///
66 /// since this calls [SgTape::open_lto_drive], it does some internal checks.
67 /// See [SgTape] docs for details.
68 pub fn open_lto_drive(config: &LtoTapeDrive) -> Result<Self, Error> {
69 let sg_tape = SgTape::open_lto_drive(config)?;
70
1dc0df33
DC
71 let handle = Self {
72 sg_tape,
73 encryption_key_loaded: false,
74 };
b5f86267
DC
75
76 Ok(handle)
77 }
78
a79082a0 79 /// Set all options we need/want
0892a512 80 pub fn set_default_options(&mut self) -> Result<(), Error> {
048b43af 81 self.sg_tape.set_default_options()?;
a79082a0
DM
82 Ok(())
83 }
84
80ea23e1
DM
85 /// Set driver options
86 pub fn set_drive_options(
87 &mut self,
88 compression: Option<bool>,
89 block_length: Option<u32>,
90 buffer_mode: Option<bool>,
91 ) -> Result<(), Error> {
4de1c42c
TL
92 self.sg_tape
93 .set_drive_options(compression, block_length, buffer_mode)
80ea23e1
DM
94 }
95
a79082a0
DM
96 /// Write a single EOF mark without flushing buffers
97 pub fn write_filemarks(&mut self, count: usize) -> Result<(), std::io::Error> {
98 self.sg_tape.write_filemarks(count, false)
99 }
100
101 /// Get Tape and Media status
4de1c42c 102 pub fn get_drive_and_media_status(&mut self) -> Result<LtoDriveAndMediaStatus, Error> {
048b43af 103 self.sg_tape.get_drive_and_media_status()
a79082a0
DM
104 }
105
8b2c6f5d 106 pub fn forward_space_count_files(&mut self, count: usize) -> Result<(), Error> {
5d6379f8 107 self.sg_tape.space_filemarks(count.try_into()?)
8b2c6f5d
DM
108 }
109
110 pub fn backward_space_count_files(&mut self, count: usize) -> Result<(), Error> {
5d6379f8 111 self.sg_tape.space_filemarks(-count.try_into()?)
8b2c6f5d
DM
112 }
113
7f745967 114 pub fn forward_space_count_records(&mut self, count: usize) -> Result<(), Error> {
5d6379f8 115 self.sg_tape.space_blocks(count.try_into()?)
7f745967
DM
116 }
117
118 pub fn backward_space_count_records(&mut self, count: usize) -> Result<(), Error> {
5d6379f8
DM
119 self.sg_tape.space_blocks(-count.try_into()?)
120 }
121
122 /// Position the tape after filemark count. Count 0 means BOT.
4de1c42c 123 pub fn locate_file(&mut self, position: u64) -> Result<(), Error> {
bbbf662d 124 self.sg_tape.locate_file(position)
7f745967
DM
125 }
126
e29f456e
DM
127 pub fn erase_media(&mut self, fast: bool) -> Result<(), Error> {
128 self.sg_tape.erase_media(fast)
129 }
130
4de1c42c 131 pub fn load(&mut self) -> Result<(), Error> {
a79082a0
DM
132 self.sg_tape.load()
133 }
134
135 /// Read Cartridge Memory (MAM Attributes)
136 pub fn cartridge_memory(&mut self) -> Result<Vec<MamAttribute>, Error> {
137 self.sg_tape.cartridge_memory()
4de1c42c 138 }
a79082a0
DM
139
140 /// Read Volume Statistics
141 pub fn volume_statistics(&mut self) -> Result<Lp17VolumeStatistics, Error> {
142 self.sg_tape.volume_statistics()
143 }
566b946f
DM
144
145 /// Lock the drive door
4de1c42c
TL
146 pub fn lock(&mut self) -> Result<(), Error> {
147 self.sg_tape
148 .set_medium_removal(false)
566b946f
DM
149 .map_err(|err| format_err!("lock door failed - {}", err))
150 }
151
152 /// Unlock the drive door
4de1c42c
TL
153 pub fn unlock(&mut self) -> Result<(), Error> {
154 self.sg_tape
155 .set_medium_removal(true)
566b946f
DM
156 .map_err(|err| format_err!("unlock door failed - {}", err))
157 }
66402cdc
DC
158
159 /// Returns if a medium is present
160 pub fn medium_present(&mut self) -> bool {
161 self.sg_tape.test_unit_ready().is_ok()
162 }
a79082a0
DM
163}
164
a79082a0 165impl TapeDriver for LtoTapeHandle {
a79082a0
DM
166 fn sync(&mut self) -> Result<(), Error> {
167 self.sg_tape.sync()?;
168 Ok(())
169 }
170
171 /// Go to the end of the recorded media (for appending files).
7b11a809
DM
172 fn move_to_eom(&mut self, write_missing_eof: bool) -> Result<(), Error> {
173 self.sg_tape.move_to_eom(write_missing_eof)
a79082a0
DM
174 }
175
8b2c6f5d 176 fn move_to_last_file(&mut self) -> Result<(), Error> {
7b11a809
DM
177 self.move_to_eom(false)?;
178
179 self.sg_tape.check_filemark()?;
8b2c6f5d
DM
180
181 let pos = self.current_file_number()?;
182
183 if pos == 0 {
184 bail!("move_to_last_file failed - media contains no data");
185 }
186
187 if pos == 1 {
188 self.rewind()?;
189 return Ok(());
190 }
191
192 self.backward_space_count_files(2)?;
193 self.forward_space_count_files(1)?;
194
195 Ok(())
a79082a0
DM
196 }
197
56d36ca4
DC
198 fn move_to_file(&mut self, file: u64) -> Result<(), Error> {
199 self.locate_file(file)
200 }
201
a79082a0
DM
202 fn rewind(&mut self) -> Result<(), Error> {
203 self.sg_tape.rewind()
204 }
205
206 fn current_file_number(&mut self) -> Result<u64, Error> {
207 self.sg_tape.current_file_number()
208 }
209
e29f456e
DM
210 fn format_media(&mut self, fast: bool) -> Result<(), Error> {
211 self.sg_tape.format_media(fast)
a79082a0
DM
212 }
213
318b3106 214 fn read_next_file<'a>(&'a mut self) -> Result<Box<dyn TapeRead + 'a>, BlockReadError> {
a79082a0 215 let reader = self.sg_tape.open_reader()?;
318b3106 216 let handle: Box<dyn TapeRead> = Box::new(reader);
a79082a0
DM
217 Ok(handle)
218 }
219
220 fn write_file<'a>(&'a mut self) -> Result<Box<dyn TapeWrite + 'a>, std::io::Error> {
221 let handle = self.sg_tape.open_writer();
222 Ok(Box::new(handle))
223 }
224
225 fn write_media_set_label(
226 &mut self,
227 media_set_label: &MediaSetLabel,
228 key_config: Option<&KeyConfig>,
229 ) -> Result<(), Error> {
a79082a0
DM
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 {
4de1c42c
TL
238 bail!(
239 "write_media_set_label failed - got wrong file number ({} != 1)",
240 file_number
241 );
a79082a0
DM
242 }
243
244 self.set_encryption(None)?;
245
4de1c42c
TL
246 {
247 // limit handle scope
a79082a0
DM
248 let mut handle = self.write_file()?;
249
250 let mut value = serde_json::to_value(media_set_label)?;
251 if media_set_label.encryption_key_fingerprint.is_some() {
252 match key_config {
253 Some(key_config) => {
254 value["key-config"] = serde_json::to_value(key_config)?;
255 }
256 None => {
257 bail!("missing encryption key config");
258 }
259 }
260 }
261
262 let raw = serde_json::to_string_pretty(&value)?;
263
4de1c42c
TL
264 let header =
265 MediaContentHeader::new(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, raw.len() as u32);
a79082a0
DM
266 handle.write_header(&header, raw.as_bytes())?;
267 handle.finish(false)?;
268 }
269
270 self.sync()?; // sync data to tape
271
272 Ok(())
273 }
274
275 /// Rewind and put the drive off line (Eject media).
276 fn eject_media(&mut self) -> Result<(), Error> {
277 self.sg_tape.eject()
278 }
279
280 /// Read Tape Alert Flags
281 fn tape_alert_flags(&mut self) -> Result<TapeAlertFlags, Error> {
282 self.sg_tape.tape_alert_flags()
283 }
284
285 /// Set or clear encryption key
286 ///
287 /// Note: Only 'root' can read secret encryption keys, so we need
288 /// to spawn setuid binary 'sg-tape-cmd'.
289 fn set_encryption(
290 &mut self,
291 key_fingerprint: Option<(Fingerprint, Uuid)>,
292 ) -> Result<(), Error> {
8ee5a5d3 293 if let Some((fingerprint, uuid)) = key_fingerprint {
ca6e66aa 294 let fingerprint = fingerprint.signature();
8ee5a5d3 295 let output = run_sg_tape_cmd(
4de1c42c
TL
296 "encryption",
297 &["--fingerprint", &fingerprint, "--uuid", &uuid.to_string()],
298 self.sg_tape.file_mut().as_raw_fd(),
8ee5a5d3 299 )?;
1dc0df33 300 self.encryption_key_loaded = true;
8ee5a5d3
DC
301 let result: Result<(), String> = serde_json::from_str(&output)?;
302 result.map_err(|err| format_err!("{}", err))
a79082a0 303 } else {
8ee5a5d3
DC
304 self.sg_tape.set_encryption(None)
305 }
a79082a0
DM
306 }
307}
308
a79082a0 309fn run_sg_tape_cmd(subcmd: &str, args: &[&str], fd: RawFd) -> Result<String, Error> {
4de1c42c
TL
310 let mut command =
311 std::process::Command::new("/usr/lib/x86_64-linux-gnu/proxmox-backup/sg-tape-cmd");
16f6766a
FG
312 command.args([subcmd]);
313 command.args(["--stdin"]);
a79082a0
DM
314 command.args(args);
315 let device_fd = nix::unistd::dup(fd)?;
4de1c42c 316 command.stdin(unsafe { std::process::Stdio::from_raw_fd(device_fd) });
a79082a0
DM
317 run_command(command, None)
318}