]> git.proxmox.com Git - proxmox-backup.git/blame - src/tape/drive/linux_tape.rs
clippy: remove unnecessary closures
[proxmox-backup.git] / src / tape / drive / linux_tape.rs
CommitLineData
fa9c9be7
DM
1use std::fs::{OpenOptions, File};
2use std::os::unix::fs::OpenOptionsExt;
b27c3282 3use std::os::unix::io::{AsRawFd, FromRawFd};
fa9c9be7
DM
4use std::convert::TryFrom;
5
6use anyhow::{bail, format_err, Error};
7use nix::fcntl::{fcntl, FcntlArg, OFlag};
8
9use proxmox::sys::error::SysResult;
fa9c9be7
DM
10
11use 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 52pub 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 60impl 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
67impl 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 121pub struct LinuxTapeHandle {
fa9c9be7
DM
122 file: File,
123 //_lock: File,
124}
125
126impl 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
371impl 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
572fn 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 585pub 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.
609pub 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
637pub struct TapeWriterHandle<'a> {
638 writer: BlockedWriter<&'a mut File>,
639}
640
641impl 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}