]>
Commit | Line | Data |
---|---|---|
fa9c9be7 DM |
1 | use std::fs::{OpenOptions, File}; |
2 | use std::os::unix::fs::OpenOptionsExt; | |
b27c3282 | 3 | use std::os::unix::io::{AsRawFd, FromRawFd}; |
fa9c9be7 DM |
4 | use std::convert::TryFrom; |
5 | ||
6 | use anyhow::{bail, format_err, Error}; | |
7 | use nix::fcntl::{fcntl, FcntlArg, OFlag}; | |
8 | ||
9 | use proxmox::sys::error::SysResult; | |
fa9c9be7 DM |
10 | |
11 | use crate::{ | |
d5a48b5c | 12 | config, |
feb1645f DM |
13 | backup::{ |
14 | Fingerprint, | |
15 | KeyConfig, | |
16 | }, | |
b27c3282 | 17 | tools::run_command, |
cb80d900 DM |
18 | api2::types::{ |
19 | TapeDensity, | |
dbe7e556 | 20 | MamAttribute, |
0993923e | 21 | LinuxDriveAndMediaStatus, |
cb80d900 | 22 | }, |
fa9c9be7 DM |
23 | tape::{ |
24 | TapeRead, | |
25 | TapeWrite, | |
74595b88 | 26 | TapeAlertFlags, |
f8ccbfde | 27 | Lp17VolumeStatistics, |
dbe7e556 | 28 | read_mam_attributes, |
0993923e | 29 | mam_extract_media_usage, |
74595b88 | 30 | read_tape_alert_flags, |
f8ccbfde | 31 | read_volume_statistics, |
d5a48b5c | 32 | set_encryption, |
fa9c9be7 DM |
33 | drive::{ |
34 | LinuxTapeDrive, | |
35 | TapeDriver, | |
36 | linux_mtio::*, | |
37 | }, | |
38 | file_formats::{ | |
39 | PROXMOX_TAPE_BLOCK_SIZE, | |
40 | MediaSetLabel, | |
41 | MediaContentHeader, | |
42 | PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, | |
43 | }, | |
44 | helpers::{ | |
45 | BlockedReader, | |
46 | BlockedWriter, | |
47 | }, | |
48 | } | |
49 | }; | |
50 | ||
51 | #[derive(Debug)] | |
cb80d900 | 52 | pub struct LinuxDriveStatus { |
fa9c9be7 | 53 | pub blocksize: u32, |
fa9c9be7 | 54 | pub status: GMTStatusFlags, |
afb02206 DM |
55 | pub density: Option<TapeDensity>, |
56 | pub file_number: Option<u32>, | |
57 | pub block_number: Option<u32>, | |
fa9c9be7 DM |
58 | } |
59 | ||
cb80d900 | 60 | impl LinuxDriveStatus { |
fa9c9be7 DM |
61 | pub fn tape_is_ready(&self) -> bool { |
62 | self.status.contains(GMTStatusFlags::ONLINE) && | |
63 | !self.status.contains(GMTStatusFlags::DRIVE_OPEN) | |
64 | } | |
65 | } | |
66 | ||
67 | impl LinuxTapeDrive { | |
68 | ||
8ae9f4ef DM |
69 | /// Open a tape device |
70 | /// | |
71 | /// This does additional checks: | |
72 | /// | |
fc6ce983 | 73 | /// - check if it is a non-rewinding tape device |
8ae9f4ef DM |
74 | /// - check if drive is ready (tape loaded) |
75 | /// - check block size | |
a484c9cf | 76 | /// - for autoloader only, try to reload ejected tapes |
fa9c9be7 DM |
77 | pub fn open(&self) -> Result<LinuxTapeHandle, Error> { |
78 | ||
a08a1985 DM |
79 | proxmox::try_block!({ |
80 | let file = open_linux_tape_device(&self.path)?; | |
fa9c9be7 | 81 | |
a08a1985 | 82 | let mut handle = LinuxTapeHandle::new(file); |
fa9c9be7 | 83 | |
a08a1985 | 84 | let mut drive_status = handle.get_drive_status()?; |
a484c9cf | 85 | |
a08a1985 DM |
86 | if !drive_status.tape_is_ready() { |
87 | // for autoloader only, try to reload ejected tapes | |
88 | if self.changer.is_some() { | |
89 | let _ = handle.mtload(); // just try, ignore error | |
90 | drive_status = handle.get_drive_status()?; | |
91 | } | |
a484c9cf | 92 | } |
a08a1985 DM |
93 | |
94 | if !drive_status.tape_is_ready() { | |
95 | bail!("tape not ready (no tape loaded)"); | |
96 | } | |
97 | ||
98 | if drive_status.blocksize == 0 { | |
99 | // device is variable block size - OK | |
fa9c9be7 | 100 | } else { |
a08a1985 DM |
101 | if drive_status.blocksize != PROXMOX_TAPE_BLOCK_SIZE as u32 { |
102 | eprintln!("device is in fixed block size mode with wrong size ({} bytes)", drive_status.blocksize); | |
103 | eprintln!("trying to set variable block size mode..."); | |
104 | if handle.set_block_size(0).is_err() { | |
105 | bail!("set variable block size mod failed - device uses wrong blocksize."); | |
106 | } | |
107 | } else { | |
108 | // device is in fixed block size mode with correct block size | |
109 | } | |
fa9c9be7 | 110 | } |
fa9c9be7 | 111 | |
a08a1985 DM |
112 | // Only root can set driver options, so we cannot |
113 | // handle.set_default_options()?; | |
fa9c9be7 | 114 | |
a08a1985 DM |
115 | Ok(handle) |
116 | }).map_err(|err| format_err!("open drive '{}' ({}) failed - {}", self.name, self.path, err)) | |
fa9c9be7 DM |
117 | } |
118 | } | |
119 | ||
8ae9f4ef | 120 | /// Linux Tape device handle |
fa9c9be7 | 121 | pub struct LinuxTapeHandle { |
fa9c9be7 DM |
122 | file: File, |
123 | //_lock: File, | |
124 | } | |
125 | ||
126 | impl LinuxTapeHandle { | |
127 | ||
8ae9f4ef DM |
128 | /// Creates a new instance |
129 | pub fn new(file: File) -> Self { | |
130 | Self { file } | |
fa9c9be7 DM |
131 | } |
132 | ||
133 | /// Set all options we need/want | |
134 | pub fn set_default_options(&self) -> Result<(), Error> { | |
135 | ||
136 | let mut opts = SetDrvBufferOptions::empty(); | |
137 | ||
138 | // fixme: ? man st(4) claims we need to clear this for reliable multivolume | |
139 | opts.set(SetDrvBufferOptions::MT_ST_BUFFER_WRITES, true); | |
140 | ||
141 | // fixme: ?man st(4) claims we need to clear this for reliable multivolume | |
142 | opts.set(SetDrvBufferOptions::MT_ST_ASYNC_WRITES, true); | |
143 | ||
144 | opts.set(SetDrvBufferOptions::MT_ST_READ_AHEAD, true); | |
145 | ||
146 | self.set_drive_buffer_options(opts) | |
147 | } | |
148 | ||
149 | /// call MTSETDRVBUFFER to set boolean options | |
150 | /// | |
151 | /// Note: this uses MT_ST_BOOLEANS, so missing options are cleared! | |
152 | pub fn set_drive_buffer_options(&self, opts: SetDrvBufferOptions) -> Result<(), Error> { | |
153 | ||
154 | let cmd = mtop { | |
155 | mt_op: MTCmd::MTSETDRVBUFFER, | |
156 | mt_count: (SetDrvBufferCmd::MT_ST_BOOLEANS as i32) | opts.bits(), | |
157 | }; | |
158 | unsafe { | |
159 | mtioctop(self.file.as_raw_fd(), &cmd) | |
160 | }.map_err(|err| format_err!("MTSETDRVBUFFER options failed - {}", err))?; | |
161 | ||
162 | Ok(()) | |
163 | } | |
164 | ||
165 | /// This flushes the driver's buffer as a side effect. Should be | |
166 | /// used before reading status with MTIOCGET. | |
167 | fn mtnop(&self) -> Result<(), Error> { | |
168 | ||
169 | let cmd = mtop { mt_op: MTCmd::MTNOP, mt_count: 1, }; | |
170 | ||
171 | unsafe { | |
172 | mtioctop(self.file.as_raw_fd(), &cmd) | |
173 | }.map_err(|err| format_err!("MTNOP failed - {}", err))?; | |
174 | ||
175 | Ok(()) | |
176 | } | |
177 | ||
a484c9cf DM |
178 | fn mtload(&mut self) -> Result<(), Error> { |
179 | ||
180 | let cmd = mtop { mt_op: MTCmd::MTLOAD, mt_count: 1, }; | |
181 | ||
182 | unsafe { | |
183 | mtioctop(self.file.as_raw_fd(), &cmd) | |
184 | }.map_err(|err| format_err!("MTLOAD failed - {}", err))?; | |
185 | ||
186 | Ok(()) | |
187 | } | |
188 | ||
9aa58f01 | 189 | fn forward_space_count_files(&mut self, count: i32) -> Result<(), Error> { |
d108b610 DM |
190 | |
191 | let cmd = mtop { mt_op: MTCmd::MTFSF, mt_count: count, }; | |
192 | ||
193 | unsafe { | |
194 | mtioctop(self.file.as_raw_fd(), &cmd) | |
195 | }.map_err(|err| format_err!("tape fsf {} failed - {}", count, err))?; | |
196 | ||
197 | Ok(()) | |
198 | } | |
199 | ||
fa9c9be7 DM |
200 | /// Set tape compression feature |
201 | pub fn set_compression(&self, on: bool) -> Result<(), Error> { | |
202 | ||
203 | let cmd = mtop { mt_op: MTCmd::MTCOMPRESSION, mt_count: if on { 1 } else { 0 } }; | |
204 | ||
205 | unsafe { | |
206 | mtioctop(self.file.as_raw_fd(), &cmd) | |
207 | }.map_err(|err| format_err!("set compression to {} failed - {}", on, err))?; | |
208 | ||
209 | Ok(()) | |
210 | } | |
211 | ||
212 | /// Write a single EOF mark | |
213 | pub fn write_eof_mark(&self) -> Result<(), Error> { | |
214 | tape_write_eof_mark(&self.file)?; | |
215 | Ok(()) | |
216 | } | |
217 | ||
218 | /// Set the drive's block length to the value specified. | |
219 | /// | |
220 | /// A block length of zero sets the drive to variable block | |
221 | /// size mode. | |
222 | pub fn set_block_size(&self, block_length: usize) -> Result<(), Error> { | |
223 | ||
224 | if block_length > 256*1024*1024 { | |
225 | bail!("block_length too large (> max linux scsii block length)"); | |
226 | } | |
227 | ||
228 | let cmd = mtop { mt_op: MTCmd::MTSETBLK, mt_count: block_length as i32 }; | |
229 | ||
230 | unsafe { | |
231 | mtioctop(self.file.as_raw_fd(), &cmd) | |
232 | }.map_err(|err| format_err!("MTSETBLK failed - {}", err))?; | |
233 | ||
234 | Ok(()) | |
235 | } | |
236 | ||
0993923e DM |
237 | /// Get Tape and Media status |
238 | pub fn get_drive_and_media_status(&mut self) -> Result<LinuxDriveAndMediaStatus, Error> { | |
fa9c9be7 | 239 | |
0993923e DM |
240 | let drive_status = self.get_drive_status()?; |
241 | ||
470f1c79 DM |
242 | let alert_flags = self.tape_alert_flags() |
243 | .map(|flags| format!("{:?}", flags)) | |
244 | .ok(); | |
245 | ||
0993923e DM |
246 | let mut status = LinuxDriveAndMediaStatus { |
247 | blocksize: drive_status.blocksize, | |
248 | density: drive_status.density, | |
249 | status: format!("{:?}", drive_status.status), | |
470f1c79 | 250 | alert_flags, |
0993923e DM |
251 | file_number: drive_status.file_number, |
252 | block_number: drive_status.block_number, | |
253 | manufactured: None, | |
254 | bytes_read: None, | |
255 | bytes_written: None, | |
b40ab10d DM |
256 | medium_passes: None, |
257 | volume_mounts: None, | |
0993923e DM |
258 | }; |
259 | ||
260 | if drive_status.tape_is_ready() { | |
261 | ||
470f1c79 | 262 | if let Ok(mam) = self.cartridge_memory() { |
0993923e | 263 | |
470f1c79 | 264 | let usage = mam_extract_media_usage(&mam)?; |
0993923e | 265 | |
470f1c79 DM |
266 | status.manufactured = Some(usage.manufactured); |
267 | status.bytes_read = Some(usage.bytes_read); | |
268 | status.bytes_written = Some(usage.bytes_written); | |
b40ab10d | 269 | |
c4b2b9ab | 270 | if let Ok(volume_stats) = self.volume_statistics() { |
b40ab10d | 271 | |
c4b2b9ab DM |
272 | status.medium_passes = Some(std::cmp::max( |
273 | volume_stats.beginning_of_medium_passes, | |
274 | volume_stats.middle_of_tape_passes, | |
275 | )); | |
b40ab10d | 276 | |
c4b2b9ab DM |
277 | status.volume_mounts = Some(volume_stats.volume_mounts); |
278 | } | |
b40ab10d | 279 | } |
0993923e DM |
280 | } |
281 | ||
282 | Ok(status) | |
283 | } | |
284 | ||
285 | /// Get Tape status/configuration with MTIOCGET ioctl | |
286 | pub fn get_drive_status(&mut self) -> Result<LinuxDriveStatus, Error> { | |
287 | ||
288 | let _ = self.mtnop(); // ignore errors (i.e. no tape loaded) | |
fa9c9be7 DM |
289 | |
290 | let mut status = mtget::default(); | |
291 | ||
292 | if let Err(err) = unsafe { mtiocget(self.file.as_raw_fd(), &mut status) } { | |
293 | bail!("MTIOCGET failed - {}", err); | |
294 | } | |
295 | ||
fa9c9be7 DM |
296 | let gmt = GMTStatusFlags::from_bits_truncate(status.mt_gstat); |
297 | ||
298 | let blocksize; | |
299 | ||
300 | if status.mt_type == MT_TYPE_ISSCSI1 || status.mt_type == MT_TYPE_ISSCSI2 { | |
301 | blocksize = ((status.mt_dsreg & MT_ST_BLKSIZE_MASK) >> MT_ST_BLKSIZE_SHIFT) as u32; | |
302 | } else { | |
303 | bail!("got unsupported tape type {}", status.mt_type); | |
304 | } | |
305 | ||
306 | let density = ((status.mt_dsreg & MT_ST_DENSITY_MASK) >> MT_ST_DENSITY_SHIFT) as u8; | |
307 | ||
cb80d900 | 308 | Ok(LinuxDriveStatus { |
fa9c9be7 | 309 | blocksize, |
fa9c9be7 | 310 | status: gmt, |
afb02206 DM |
311 | density: if density != 0 { |
312 | Some(TapeDensity::try_from(density)?) | |
313 | } else { | |
314 | None | |
315 | }, | |
316 | file_number: if status.mt_fileno > 0 { | |
317 | Some(status.mt_fileno as u32) | |
318 | } else { | |
319 | None | |
320 | }, | |
321 | block_number: if status.mt_blkno > 0 { | |
322 | Some(status.mt_blkno as u32) | |
323 | } else { | |
324 | None | |
325 | }, | |
fa9c9be7 DM |
326 | }) |
327 | } | |
328 | ||
dbe7e556 | 329 | /// Read Cartridge Memory (MAM Attributes) |
b27c3282 DM |
330 | /// |
331 | /// Note: Only 'root' user may run RAW SG commands, so we need to | |
332 | /// spawn setuid binary 'sg-tape-cmd'. | |
dbe7e556 | 333 | pub fn cartridge_memory(&mut self) -> Result<Vec<MamAttribute>, Error> { |
b27c3282 DM |
334 | |
335 | if nix::unistd::Uid::effective().is_root() { | |
336 | return read_mam_attributes(&mut self.file); | |
337 | } | |
338 | ||
339 | let mut command = std::process::Command::new( | |
340 | "/usr/lib/x86_64-linux-gnu/proxmox-backup/sg-tape-cmd"); | |
341 | command.args(&["cartridge-memory"]); | |
2d50a619 | 342 | command.args(&["--stdin"]); |
b27c3282 DM |
343 | command.stdin(unsafe { std::process::Stdio::from_raw_fd(self.file.as_raw_fd())}); |
344 | let output = run_command(command, None)?; | |
345 | let result: Result<Vec<MamAttribute>, String> = serde_json::from_str(&output)?; | |
346 | result.map_err(|err| format_err!("{}", err)) | |
dbe7e556 | 347 | } |
74595b88 | 348 | |
f8ccbfde DM |
349 | /// Read Volume Statistics |
350 | /// | |
351 | /// Note: Only 'root' user may run RAW SG commands, so we need to | |
352 | /// spawn setuid binary 'sg-tape-cmd'. | |
353 | pub fn volume_statistics(&mut self) -> Result<Lp17VolumeStatistics, Error> { | |
354 | ||
355 | if nix::unistd::Uid::effective().is_root() { | |
356 | return read_volume_statistics(&mut self.file); | |
357 | } | |
358 | ||
359 | let mut command = std::process::Command::new( | |
360 | "/usr/lib/x86_64-linux-gnu/proxmox-backup/sg-tape-cmd"); | |
361 | command.args(&["volume-statistics"]); | |
2d50a619 | 362 | command.args(&["--stdin"]); |
f8ccbfde DM |
363 | command.stdin(unsafe { std::process::Stdio::from_raw_fd(self.file.as_raw_fd())}); |
364 | let output = run_command(command, None)?; | |
365 | let result: Result<Lp17VolumeStatistics, String> = serde_json::from_str(&output)?; | |
366 | result.map_err(|err| format_err!("{}", err)) | |
367 | } | |
fa9c9be7 DM |
368 | } |
369 | ||
370 | ||
371 | impl TapeDriver for LinuxTapeHandle { | |
372 | ||
373 | fn sync(&mut self) -> Result<(), Error> { | |
374 | ||
375 | println!("SYNC/FLUSH TAPE"); | |
376 | // MTWEOF with count 0 => flush | |
377 | let cmd = mtop { mt_op: MTCmd::MTWEOF, mt_count: 0 }; | |
378 | ||
379 | unsafe { | |
380 | mtioctop(self.file.as_raw_fd(), &cmd) | |
381 | }.map_err(|err| proxmox::io_format_err!("MT sync failed - {}", err))?; | |
382 | ||
383 | Ok(()) | |
384 | } | |
385 | ||
386 | /// Go to the end of the recorded media (for appending files). | |
387 | fn move_to_eom(&mut self) -> Result<(), Error> { | |
388 | ||
389 | let cmd = mtop { mt_op: MTCmd::MTEOM, mt_count: 1, }; | |
390 | ||
391 | unsafe { | |
392 | mtioctop(self.file.as_raw_fd(), &cmd) | |
393 | }.map_err(|err| format_err!("MTEOM failed - {}", err))?; | |
394 | ||
395 | ||
396 | Ok(()) | |
397 | } | |
398 | ||
399 | fn rewind(&mut self) -> Result<(), Error> { | |
400 | ||
401 | let cmd = mtop { mt_op: MTCmd::MTREW, mt_count: 1, }; | |
402 | ||
403 | unsafe { | |
404 | mtioctop(self.file.as_raw_fd(), &cmd) | |
405 | }.map_err(|err| format_err!("tape rewind failed - {}", err))?; | |
406 | ||
407 | Ok(()) | |
408 | } | |
409 | ||
26aa9aca | 410 | fn current_file_number(&mut self) -> Result<u64, Error> { |
fa9c9be7 DM |
411 | let mut status = mtget::default(); |
412 | ||
413 | self.mtnop()?; | |
414 | ||
415 | if let Err(err) = unsafe { mtiocget(self.file.as_raw_fd(), &mut status) } { | |
416 | bail!("current_file_number MTIOCGET failed - {}", err); | |
417 | } | |
418 | ||
419 | if status.mt_fileno < 0 { | |
420 | bail!("current_file_number failed (got {})", status.mt_fileno); | |
421 | } | |
26aa9aca | 422 | Ok(status.mt_fileno as u64) |
fa9c9be7 DM |
423 | } |
424 | ||
425 | fn erase_media(&mut self, fast: bool) -> Result<(), Error> { | |
426 | ||
427 | self.rewind()?; // important - erase from BOT | |
428 | ||
429 | let cmd = mtop { mt_op: MTCmd::MTERASE, mt_count: if fast { 0 } else { 1 } }; | |
430 | ||
431 | unsafe { | |
432 | mtioctop(self.file.as_raw_fd(), &cmd) | |
433 | }.map_err(|err| format_err!("MTERASE failed - {}", err))?; | |
434 | ||
435 | Ok(()) | |
436 | } | |
437 | ||
438 | fn read_next_file<'a>(&'a mut self) -> Result<Option<Box<dyn TapeRead + 'a>>, std::io::Error> { | |
439 | match BlockedReader::open(&mut self.file)? { | |
440 | Some(reader) => Ok(Some(Box::new(reader))), | |
441 | None => Ok(None), | |
442 | } | |
443 | } | |
444 | ||
445 | fn write_file<'a>(&'a mut self) -> Result<Box<dyn TapeWrite + 'a>, std::io::Error> { | |
446 | ||
447 | let handle = TapeWriterHandle { | |
448 | writer: BlockedWriter::new(&mut self.file), | |
449 | }; | |
450 | ||
451 | Ok(Box::new(handle)) | |
452 | } | |
453 | ||
feb1645f DM |
454 | fn write_media_set_label( |
455 | &mut self, | |
456 | media_set_label: &MediaSetLabel, | |
457 | key_config: Option<&KeyConfig>, | |
458 | ) -> Result<(), Error> { | |
fa9c9be7 | 459 | |
d108b610 DM |
460 | let file_number = self.current_file_number()?; |
461 | if file_number != 1 { | |
462 | self.rewind()?; | |
9aa58f01 | 463 | self.forward_space_count_files(1)?; // skip label |
d108b610 DM |
464 | } |
465 | ||
fa9c9be7 DM |
466 | let file_number = self.current_file_number()?; |
467 | if file_number != 1 { | |
468 | bail!("write_media_set_label failed - got wrong file number ({} != 1)", file_number); | |
469 | } | |
470 | ||
619554af DM |
471 | self.set_encryption(None)?; |
472 | ||
fa9c9be7 DM |
473 | let mut handle = TapeWriterHandle { |
474 | writer: BlockedWriter::new(&mut self.file), | |
475 | }; | |
feb1645f DM |
476 | |
477 | let mut value = serde_json::to_value(media_set_label)?; | |
478 | if media_set_label.encryption_key_fingerprint.is_some() { | |
479 | match key_config { | |
480 | Some(key_config) => { | |
481 | value["key-config"] = serde_json::to_value(key_config)?; | |
482 | } | |
483 | None => { | |
484 | bail!("missing encryption key config"); | |
485 | } | |
486 | } | |
487 | } | |
488 | ||
489 | let raw = serde_json::to_string_pretty(&value)?; | |
fa9c9be7 DM |
490 | |
491 | let header = MediaContentHeader::new(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, raw.len() as u32); | |
492 | handle.write_header(&header, raw.as_bytes())?; | |
493 | handle.finish(false)?; | |
494 | ||
495 | self.sync()?; // sync data to tape | |
496 | ||
fe6c1938 | 497 | Ok(()) |
fa9c9be7 DM |
498 | } |
499 | ||
500 | /// Rewind and put the drive off line (Eject media). | |
501 | fn eject_media(&mut self) -> Result<(), Error> { | |
502 | let cmd = mtop { mt_op: MTCmd::MTOFFL, mt_count: 1 }; | |
503 | ||
504 | unsafe { | |
505 | mtioctop(self.file.as_raw_fd(), &cmd) | |
506 | }.map_err(|err| format_err!("MTOFFL failed - {}", err))?; | |
507 | ||
508 | Ok(()) | |
509 | } | |
5843268c DM |
510 | |
511 | /// Read Tape Alert Flags | |
512 | /// | |
513 | /// Note: Only 'root' user may run RAW SG commands, so we need to | |
514 | /// spawn setuid binary 'sg-tape-cmd'. | |
515 | fn tape_alert_flags(&mut self) -> Result<TapeAlertFlags, Error> { | |
516 | ||
517 | if nix::unistd::Uid::effective().is_root() { | |
518 | return read_tape_alert_flags(&mut self.file); | |
519 | } | |
520 | ||
521 | let mut command = std::process::Command::new( | |
522 | "/usr/lib/x86_64-linux-gnu/proxmox-backup/sg-tape-cmd"); | |
523 | command.args(&["tape-alert-flags"]); | |
2d50a619 | 524 | command.args(&["--stdin"]); |
5843268c DM |
525 | command.stdin(unsafe { std::process::Stdio::from_raw_fd(self.file.as_raw_fd())}); |
526 | let output = run_command(command, None)?; | |
527 | let result: Result<u64, String> = serde_json::from_str(&output)?; | |
528 | result | |
529 | .map_err(|err| format_err!("{}", err)) | |
22a9189e | 530 | .map(TapeAlertFlags::from_bits_truncate) |
5843268c | 531 | } |
d5a48b5c DM |
532 | |
533 | /// Set or clear encryption key | |
534 | /// | |
535 | /// Note: Only 'root' user may run RAW SG commands, so we need to | |
536 | /// spawn setuid binary 'sg-tape-cmd'. Also, encryption key file | |
537 | /// is only readable by root. | |
538 | fn set_encryption(&mut self, key_fingerprint: Option<Fingerprint>) -> Result<(), Error> { | |
539 | ||
540 | if nix::unistd::Uid::effective().is_root() { | |
541 | ||
542 | if let Some(ref key_fingerprint) = key_fingerprint { | |
543 | ||
544 | let (key_map, _digest) = config::tape_encryption_keys::load_keys()?; | |
545 | match key_map.get(key_fingerprint) { | |
546 | Some(item) => { | |
547 | return set_encryption(&mut self.file, Some(item.key)); | |
548 | } | |
549 | None => bail!("unknown tape encryption key '{}'", key_fingerprint), | |
550 | } | |
551 | } else { | |
552 | return set_encryption(&mut self.file, None); | |
553 | } | |
554 | } | |
555 | ||
556 | let mut command = std::process::Command::new( | |
557 | "/usr/lib/x86_64-linux-gnu/proxmox-backup/sg-tape-cmd"); | |
558 | command.args(&["encryption"]); | |
559 | if let Some(fingerprint) = key_fingerprint { | |
560 | let fingerprint = crate::tools::format::as_fingerprint(fingerprint.bytes()); | |
561 | command.args(&["--fingerprint", &fingerprint]); | |
562 | } | |
563 | command.args(&["--stdin"]); | |
564 | command.stdin(unsafe { std::process::Stdio::from_raw_fd(self.file.as_raw_fd())}); | |
565 | let output = run_command(command, None)?; | |
566 | let result: Result<(), String> = serde_json::from_str(&output)?; | |
567 | result.map_err(|err| format_err!("{}", err)) | |
568 | } | |
fa9c9be7 DM |
569 | } |
570 | ||
571 | /// Write a single EOF mark without flushing buffers | |
572 | fn tape_write_eof_mark(file: &File) -> Result<(), std::io::Error> { | |
573 | ||
574 | println!("WRITE EOF MARK"); | |
575 | let cmd = mtop { mt_op: MTCmd::MTWEOFI, mt_count: 1 }; | |
576 | ||
577 | unsafe { | |
578 | mtioctop(file.as_raw_fd(), &cmd) | |
579 | }.map_err(|err| proxmox::io_format_err!("MTWEOFI failed - {}", err))?; | |
580 | ||
581 | Ok(()) | |
582 | } | |
583 | ||
8ae9f4ef | 584 | /// Check for correct Major/Minor numbers |
c9d13b0f | 585 | pub fn check_tape_is_linux_tape_device(file: &File) -> Result<(), Error> { |
fa9c9be7 | 586 | |
c9d13b0f DM |
587 | let stat = nix::sys::stat::fstat(file.as_raw_fd())?; |
588 | ||
589 | let devnum = stat.st_rdev; | |
fa9c9be7 DM |
590 | |
591 | let major = unsafe { libc::major(devnum) }; | |
592 | let minor = unsafe { libc::minor(devnum) }; | |
593 | ||
fc6ce983 | 594 | if major != 9 { |
c9d13b0f DM |
595 | bail!("not a tape device"); |
596 | } | |
fa9c9be7 | 597 | if (minor & 128) == 0 { |
c9d13b0f | 598 | bail!("Detected rewinding tape. Please use non-rewinding tape devices (/dev/nstX)."); |
fa9c9be7 DM |
599 | } |
600 | ||
c9d13b0f | 601 | Ok(()) |
fa9c9be7 DM |
602 | } |
603 | ||
bfacc1d8 DM |
604 | /// Opens a Linux tape device |
605 | /// | |
606 | /// The open call use O_NONBLOCK, but that flag is cleard after open | |
607 | /// succeeded. This also checks if the device is a non-rewinding tape | |
608 | /// device. | |
609 | pub fn open_linux_tape_device( | |
610 | path: &str, | |
611 | ) -> Result<File, Error> { | |
612 | ||
613 | let file = OpenOptions::new() | |
614 | .read(true) | |
615 | .write(true) | |
616 | .custom_flags(libc::O_NONBLOCK) | |
617 | .open(path)?; | |
618 | ||
619 | // clear O_NONBLOCK from now on. | |
620 | ||
621 | let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL) | |
622 | .into_io_result()?; | |
623 | ||
624 | let mut flags = OFlag::from_bits_truncate(flags); | |
625 | flags.remove(OFlag::O_NONBLOCK); | |
626 | ||
627 | fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags)) | |
628 | .into_io_result()?; | |
629 | ||
c9d13b0f DM |
630 | check_tape_is_linux_tape_device(&file) |
631 | .map_err(|err| format_err!("device type check {:?} failed - {}", path, err))?; | |
bfacc1d8 DM |
632 | |
633 | Ok(file) | |
634 | } | |
635 | ||
fa9c9be7 DM |
636 | /// like BlockedWriter, but writes EOF mark on finish |
637 | pub struct TapeWriterHandle<'a> { | |
638 | writer: BlockedWriter<&'a mut File>, | |
639 | } | |
640 | ||
641 | impl TapeWrite for TapeWriterHandle<'_> { | |
642 | ||
643 | fn write_all(&mut self, data: &[u8]) -> Result<bool, std::io::Error> { | |
644 | self.writer.write_all(data) | |
645 | } | |
646 | ||
647 | fn bytes_written(&self) -> usize { | |
648 | self.writer.bytes_written() | |
649 | } | |
650 | ||
651 | fn finish(&mut self, incomplete: bool) -> Result<bool, std::io::Error> { | |
652 | println!("FINISH TAPE HANDLE"); | |
653 | let leof = self.writer.finish(incomplete)?; | |
654 | tape_write_eof_mark(self.writer.writer_ref_mut())?; | |
655 | Ok(leof) | |
656 | } | |
657 | ||
658 | fn logical_end_of_media(&self) -> bool { | |
659 | self.writer.logical_end_of_media() | |
660 | } | |
661 | } |