]> git.proxmox.com Git - proxmox-backup.git/blame - src/tape/drive/mod.rs
use new proxmox-sys crate
[proxmox-backup.git] / src / tape / drive / mod.rs
CommitLineData
37796ff7
DM
1//! Tape drivers
2
fa9c9be7 3mod virtual_tape;
83b8949a 4
a79082a0
DM
5mod lto;
6pub use lto::*;
fa9c9be7 7
cd44fb8d 8use std::path::PathBuf;
25aa55b5 9
fa9c9be7 10use anyhow::{bail, format_err, Error};
6ef1b649 11use serde::Deserialize;
feb1645f 12use serde_json::Value;
7526d864
DM
13use nix::fcntl::OFlag;
14use nix::sys::stat::Mode;
fa9c9be7 15
2b191385
DM
16use proxmox::{
17 tools::{
546d2653 18 fs::{
7526d864
DM
19 lock_file,
20 atomic_open_or_create_file,
546d2653
DC
21 file_read_optional_string,
22 replace_file,
23 CreateOptions,
24 }
2b191385 25 },
2b191385 26};
6ef1b649
WB
27use proxmox_io::ReadExt;
28use proxmox_section_config::SectionConfigData;
29use proxmox_uuid::Uuid;
d5790a9f 30use proxmox_sys::{task_log, worker_task_context::WorkerTaskContext};
fa9c9be7 31
bbdda58b
DM
32use pbs_api_types::{VirtualTapeDrive, LtoTapeDrive, Fingerprint};
33use pbs_config::key_config::KeyConfig;
c23192d3 34
048b43af
DM
35use pbs_tape::{
36 TapeWrite, TapeRead, BlockReadError, MediaContentHeader,
37 sg_tape::TapeAlertFlags,
38};
39
fa9c9be7 40use crate::{
b9700a9f 41 server::send_load_media_email,
fa9c9be7 42 tape::{
fe6c1938 43 MediaId,
1ce8e905
DM
44 drive::{
45 virtual_tape::open_virtual_tape_drive,
1ce8e905 46 },
fa9c9be7 47 file_formats::{
a78348ac 48 PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0,
fa9c9be7 49 PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0,
a78348ac 50 MediaLabel,
fa9c9be7 51 MediaSetLabel,
fa9c9be7
DM
52 },
53 changer::{
54 MediaChange,
37796ff7 55 MtxMediaChanger,
fa9c9be7
DM
56 },
57 },
58};
59
fa9c9be7
DM
60/// Tape driver interface
61pub trait TapeDriver {
62
63 /// Flush all data to the tape
64 fn sync(&mut self) -> Result<(), Error>;
65
66 /// Rewind the tape
67 fn rewind(&mut self) -> Result<(), Error>;
68
69 /// Move to end of recorded data
70 ///
7b11a809
DM
71 /// We assume this flushes the tape write buffer. if
72 /// write_missing_eof is true, we verify that there is a filemark
73 /// at the end. If not, we write one.
74 fn move_to_eom(&mut self, write_missing_eof: bool) -> Result<(), Error>;
fa9c9be7 75
20cc25d7 76 /// Move to last file
8b2c6f5d 77 fn move_to_last_file(&mut self) -> Result<(), Error>;
20cc25d7 78
56d36ca4
DC
79 /// Move to given file nr
80 fn move_to_file(&mut self, file: u64) -> Result<(), Error>;
81
fa9c9be7 82 /// Current file number
26aa9aca 83 fn current_file_number(&mut self) -> Result<u64, Error>;
fa9c9be7
DM
84
85 /// Completely erase the media
e29f456e 86 fn format_media(&mut self, fast: bool) -> Result<(), Error>;
fa9c9be7
DM
87
88 /// Read/Open the next file
318b3106 89 fn read_next_file<'a>(&'a mut self) -> Result<Box<dyn TapeRead + 'a>, BlockReadError>;
fa9c9be7
DM
90
91 /// Write/Append a new file
92 fn write_file<'a>(&'a mut self) -> Result<Box<dyn TapeWrite + 'a>, std::io::Error>;
93
94 /// Write label to tape (erase tape content)
fe6c1938 95 fn label_tape(&mut self, label: &MediaLabel) -> Result<(), Error> {
fa9c9be7 96
619554af
DM
97 self.set_encryption(None)?;
98
e29f456e 99 self.format_media(true)?; // this rewinds the tape
fa9c9be7
DM
100
101 let raw = serde_json::to_string_pretty(&serde_json::to_value(&label)?)?;
102
a78348ac 103 let header = MediaContentHeader::new(PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0, raw.len() as u32);
fa9c9be7
DM
104
105 {
106 let mut writer = self.write_file()?;
107 writer.write_header(&header, raw.as_bytes())?;
108 writer.finish(false)?;
109 }
110
111 self.sync()?; // sync data to tape
112
fe6c1938 113 Ok(())
fa9c9be7
DM
114 }
115
116 /// Write the media set label to tape
feb1645f
DM
117 ///
118 /// If the media-set is encrypted, we also store the encryption
119 /// key_config, so that it is possible to restore the key.
120 fn write_media_set_label(
121 &mut self,
122 media_set_label: &MediaSetLabel,
123 key_config: Option<&KeyConfig>,
124 ) -> Result<(), Error>;
fa9c9be7
DM
125
126 /// Read the media label
127 ///
feb1645f
DM
128 /// This tries to read both media labels (label and
129 /// media_set_label). Also returns the optional encryption key configuration.
130 fn read_label(&mut self) -> Result<(Option<MediaId>, Option<KeyConfig>), Error> {
fa9c9be7
DM
131
132 self.rewind()?;
133
fe6c1938 134 let label = {
318b3106
DM
135 let mut reader = match self.read_next_file() {
136 Err(BlockReadError::EndOfStream) => {
137 return Ok((None, None)); // tape is empty
138 }
139 Err(BlockReadError::EndOfFile) => {
140 bail!("got unexpected filemark at BOT");
141 }
142 Err(BlockReadError::Error(err)) => {
143 return Err(err.into());
144 }
145 Ok(reader) => reader,
fa9c9be7
DM
146 };
147
148 let header: MediaContentHeader = unsafe { reader.read_le_value()? };
a78348ac 149 header.check(PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0, 1, 64*1024)?;
fa9c9be7
DM
150 let data = reader.read_exact_allocated(header.size as usize)?;
151
a78348ac 152 let label: MediaLabel = serde_json::from_slice(&data)
fa9c9be7
DM
153 .map_err(|err| format_err!("unable to parse drive label - {}", err))?;
154
155 // make sure we read the EOF marker
156 if reader.skip_to_end()? != 0 {
157 bail!("got unexpected data after label");
158 }
159
fe6c1938 160 label
fa9c9be7
DM
161 };
162
fe6c1938 163 let mut media_id = MediaId { label, media_set_label: None };
fa9c9be7 164
fe6c1938 165 // try to read MediaSet label
318b3106
DM
166 let mut reader = match self.read_next_file() {
167 Err(BlockReadError::EndOfStream) => {
168 return Ok((Some(media_id), None));
169 }
170 Err(BlockReadError::EndOfFile) => {
171 bail!("got unexpected filemark after label");
172 }
173 Err(BlockReadError::Error(err)) => {
174 return Err(err.into());
175 }
176 Ok(reader) => reader,
fa9c9be7
DM
177 };
178
179 let header: MediaContentHeader = unsafe { reader.read_le_value()? };
180 header.check(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, 1, 64*1024)?;
181 let data = reader.read_exact_allocated(header.size as usize)?;
182
feb1645f
DM
183 let mut data: Value = serde_json::from_slice(&data)
184 .map_err(|err| format_err!("unable to parse media set label - {}", err))?;
185
186 let key_config_value = data["key-config"].take();
187 let key_config: Option<KeyConfig> = if !key_config_value.is_null() {
188 Some(serde_json::from_value(key_config_value)?)
189 } else {
190 None
191 };
192
193 let media_set_label: MediaSetLabel = serde_json::from_value(data)
fa9c9be7
DM
194 .map_err(|err| format_err!("unable to parse media set label - {}", err))?;
195
196 // make sure we read the EOF marker
197 if reader.skip_to_end()? != 0 {
198 bail!("got unexpected data after media set label");
199 }
200
fe6c1938 201 media_id.media_set_label = Some(media_set_label);
fa9c9be7 202
feb1645f 203 Ok((Some(media_id), key_config))
fa9c9be7
DM
204 }
205
206 /// Eject media
207 fn eject_media(&mut self) -> Result<(), Error>;
5843268c
DM
208
209 /// Read Tape Alert Flags
210 ///
211 /// This make only sense for real LTO drives. Virtual tape drives should
212 /// simply return empty flags (default).
213 fn tape_alert_flags(&mut self) -> Result<TapeAlertFlags, Error> {
214 Ok(TapeAlertFlags::empty())
215 }
d5a48b5c
DM
216
217 /// Set or clear encryption key
2b191385
DM
218 ///
219 /// We use the media_set_uuid to XOR the secret key with the
d1d74c43 220 /// uuid (first 16 bytes), so that each media set uses an unique
2b191385
DM
221 /// key for encryption.
222 fn set_encryption(
223 &mut self,
224 key_fingerprint: Option<(Fingerprint, Uuid)>,
225 ) -> Result<(), Error> {
d5a48b5c
DM
226 if key_fingerprint.is_some() {
227 bail!("drive does not support encryption");
228 }
229 Ok(())
230 }
fa9c9be7
DM
231}
232
284eb5da 233/// Get the media changer (MediaChange + name) associated with a tape drive.
fa9c9be7 234///
284eb5da 235/// Returns Ok(None) if the drive has no associated changer device.
75656a78
DM
236///
237/// Note: This may return the drive name as changer-name if the drive
238/// implements some kind of internal changer (which is true for our
239/// 'virtual' drive implementation).
fa9c9be7
DM
240pub fn media_changer(
241 config: &SectionConfigData,
242 drive: &str,
284eb5da 243) -> Result<Option<(Box<dyn MediaChange>, String)>, Error> {
fa9c9be7
DM
244
245 match config.sections.get(drive) {
246 Some((section_type_name, config)) => {
247 match section_type_name.as_ref() {
248 "virtual" => {
249 let tape = VirtualTapeDrive::deserialize(config)?;
284eb5da 250 Ok(Some((Box::new(tape), drive.to_string())))
fa9c9be7 251 }
a79082a0
DM
252 "lto" => {
253 let drive_config = LtoTapeDrive::deserialize(config)?;
6fe16039 254 match drive_config.changer {
fa9c9be7 255 Some(ref changer_name) => {
6fe16039 256 let changer = MtxMediaChanger::with_drive_config(&drive_config)?;
fa9c9be7 257 let changer_name = changer_name.to_string();
6fe16039 258 Ok(Some((Box::new(changer), changer_name)))
fa9c9be7 259 }
284eb5da 260 None => Ok(None),
fa9c9be7
DM
261 }
262 }
284eb5da 263 _ => bail!("unknown drive type '{}' - internal error"),
fa9c9be7
DM
264 }
265 }
266 None => {
267 bail!("no such drive '{}'", drive);
268 }
269 }
270}
271
284eb5da
DM
272/// Get the media changer (MediaChange + name) associated with a tape drive.
273///
274/// This fail if the drive has no associated changer device.
275pub fn required_media_changer(
276 config: &SectionConfigData,
277 drive: &str,
278) -> Result<(Box<dyn MediaChange>, String), Error> {
279 match media_changer(config, drive) {
280 Ok(Some(result)) => {
38556bf6 281 Ok(result)
284eb5da
DM
282 }
283 Ok(None) => {
284 bail!("drive '{}' has no associated changer device", drive);
285 },
286 Err(err) => {
38556bf6 287 Err(err)
284eb5da
DM
288 }
289 }
290}
291
edda5039 292/// Opens a tape drive (this fails if there is no media loaded)
fa9c9be7
DM
293pub fn open_drive(
294 config: &SectionConfigData,
295 drive: &str,
296) -> Result<Box<dyn TapeDriver>, Error> {
297
298 match config.sections.get(drive) {
299 Some((section_type_name, config)) => {
300 match section_type_name.as_ref() {
301 "virtual" => {
302 let tape = VirtualTapeDrive::deserialize(config)?;
1ce8e905 303 let handle = open_virtual_tape_drive(&tape)?;
d5a48b5c 304 Ok(Box::new(handle))
fa9c9be7 305 }
a79082a0
DM
306 "lto" => {
307 let tape = LtoTapeDrive::deserialize(config)?;
1ce8e905 308 let handle = open_lto_tape_drive(&tape)?;
fa9c9be7
DM
309 Ok(Box::new(handle))
310 }
284eb5da 311 _ => bail!("unknown drive type '{}' - internal error"),
fa9c9be7
DM
312 }
313 }
314 None => {
315 bail!("no such drive '{}'", drive);
316 }
317 }
318}
319
9ac8b73e
DC
320#[derive(PartialEq, Eq)]
321enum TapeRequestError {
322 None,
323 EmptyTape,
324 OpenFailed(String),
325 WrongLabel(String),
326 ReadFailed(String),
327}
328
329impl std::fmt::Display for TapeRequestError {
330 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331 match self {
332 TapeRequestError::None => {
333 write!(f, "no error")
334 },
335 TapeRequestError::OpenFailed(reason) => {
336 write!(f, "tape open failed - {}", reason)
337 }
338 TapeRequestError::WrongLabel(label) => {
339 write!(f, "wrong media label {}", label)
340 }
341 TapeRequestError::EmptyTape => {
342 write!(f, "found empty media without label (please label all tapes first)")
343 }
344 TapeRequestError::ReadFailed(reason) => {
345 write!(f, "tape read failed - {}", reason)
346 }
347 }
348 }
349}
350
fa9c9be7
DM
351/// Requests a specific 'media' to be inserted into 'drive'. Within a
352/// loop, this then tries to read the media label and waits until it
353/// finds the requested media.
354///
355/// Returns a handle to the opened drive and the media labels.
356pub fn request_and_load_media(
619cd5cb 357 worker: &dyn WorkerTaskContext,
fa9c9be7
DM
358 config: &SectionConfigData,
359 drive: &str,
a78348ac 360 label: &MediaLabel,
c9793d47 361 notify_email: &Option<String>,
fa9c9be7
DM
362) -> Result<(
363 Box<dyn TapeDriver>,
fe6c1938 364 MediaId,
fa9c9be7
DM
365), Error> {
366
6ef1b649 367 let check_label = |handle: &mut dyn TapeDriver, uuid: &proxmox_uuid::Uuid| {
feb1645f 368 if let Ok((Some(media_id), _)) = handle.read_label() {
271764de
DM
369 task_log!(
370 worker,
284eb5da 371 "found media label {} ({})",
8446fbca 372 media_id.label.label_text,
271764de
DM
373 media_id.label.uuid,
374 );
375
ff58c519
DM
376 if media_id.label.uuid == *uuid {
377 return Ok(media_id);
378 }
379 }
380 bail!("read label failed (please label all tapes first)");
381 };
382
fa9c9be7
DM
383 match config.sections.get(drive) {
384 Some((section_type_name, config)) => {
385 match section_type_name.as_ref() {
386 "virtual" => {
ff58c519 387 let mut tape = VirtualTapeDrive::deserialize(config)?;
fa9c9be7 388
8446fbca 389 let label_text = label.label_text.clone();
fa9c9be7 390
8446fbca 391 tape.load_media(&label_text)?;
fa9c9be7 392
1ce8e905 393 let mut handle: Box<dyn TapeDriver> = Box::new(open_virtual_tape_drive(&tape)?);
fa9c9be7 394
ff58c519
DM
395 let media_id = check_label(handle.as_mut(), &label.uuid)?;
396
38556bf6 397 Ok((handle, media_id))
fa9c9be7 398 }
a79082a0
DM
399 "lto" => {
400 let drive_config = LtoTapeDrive::deserialize(config)?;
ff58c519 401
8446fbca 402 let label_text = label.label_text.clone();
fa9c9be7 403
6fe16039 404 if drive_config.changer.is_some() {
fa9c9be7 405
3fbf2311
DM
406 task_log!(worker, "loading media '{}' into drive '{}'", label_text, drive);
407
6fe16039 408 let mut changer = MtxMediaChanger::with_drive_config(&drive_config)?;
8446fbca 409 changer.load_media(&label_text)?;
ff58c519 410
1ce8e905 411 let mut handle: Box<dyn TapeDriver> = Box::new(open_lto_tape_drive(&drive_config)?);
ff58c519
DM
412
413 let media_id = check_label(handle.as_mut(), &label.uuid)?;
414
415 return Ok((handle, media_id));
416 }
417
9ac8b73e
DC
418 let mut last_error = TapeRequestError::None;
419
420 let update_and_log_request_error =
421 |old: &mut TapeRequestError, new: TapeRequestError| -> Result<(), Error>
422 {
423 if new != *old {
424 task_log!(worker, "{}", new);
425 task_log!(
426 worker,
427 "Please insert media '{}' into drive '{}'",
428 label_text,
429 drive
430 );
431 if let Some(to) = notify_email {
432 send_load_media_email(
433 drive,
434 &label_text,
435 to,
436 Some(new.to_string()),
437 )?;
438 }
439 *old = new;
440 }
441 Ok(())
442 };
78593b5b 443
fa9c9be7 444 loop {
271764de
DM
445 worker.check_abort()?;
446
9ac8b73e 447 if last_error != TapeRequestError::None {
78593b5b
DC
448 for _ in 0..50 { // delay 5 seconds
449 worker.check_abort()?;
450 std::thread::sleep(std::time::Duration::from_millis(100));
451 }
9ac8b73e
DC
452 } else {
453 task_log!(
454 worker,
455 "Checking for media '{}' in drive '{}'",
456 label_text,
457 drive
458 );
78593b5b
DC
459 }
460
1ce8e905 461 let mut handle = match open_lto_tape_drive(&drive_config) {
fa9c9be7 462 Ok(handle) => handle,
81764111 463 Err(err) => {
9ac8b73e
DC
464 update_and_log_request_error(
465 &mut last_error,
466 TapeRequestError::OpenFailed(err.to_string()),
467 )?;
fa9c9be7
DM
468 continue;
469 }
470 };
471
9ac8b73e
DC
472 let request_error = match handle.read_label() {
473 Ok((Some(media_id), _)) if media_id.label.uuid == label.uuid => {
474 task_log!(
475 worker,
476 "found media label {} ({})",
477 media_id.label.label_text,
478 media_id.label.uuid.to_string(),
479 );
480 return Ok((Box::new(handle), media_id));
481 }
feb1645f 482 Ok((Some(media_id), _)) => {
9ac8b73e
DC
483 let label_string = format!(
484 "{} ({})",
485 media_id.label.label_text,
486 media_id.label.uuid.to_string(),
487 );
488 TapeRequestError::WrongLabel(label_string)
ff58c519 489 }
feb1645f 490 Ok((None, _)) => {
9ac8b73e 491 TapeRequestError::EmptyTape
fa9c9be7 492 }
81764111 493 Err(err) => {
9ac8b73e 494 TapeRequestError::ReadFailed(err.to_string())
81764111 495 }
9ac8b73e
DC
496 };
497
498 update_and_log_request_error(&mut last_error, request_error)?;
fa9c9be7
DM
499 }
500 }
501 _ => bail!("drive type '{}' not implemented!"),
502 }
503 }
504 None => {
505 bail!("no such drive '{}'", drive);
506 }
507 }
508}
25aa55b5 509
e5950360
DC
510#[derive(thiserror::Error, Debug)]
511pub enum TapeLockError {
512 #[error("timeout while trying to lock")]
513 TimeOut,
514 #[error("{0}")]
515 Other(#[from] Error),
516}
517
518impl From<std::io::Error> for TapeLockError {
519 fn from(error: std::io::Error) -> Self {
520 Self::Other(error.into())
521 }
522}
523
d1d74c43 524/// Acquires an exclusive lock for the tape device
25aa55b5
DM
525///
526/// Basically calls lock_device_path() using the configured drive path.
527pub fn lock_tape_device(
528 config: &SectionConfigData,
529 drive: &str,
e5950360 530) -> Result<DeviceLockGuard, TapeLockError> {
546d2653 531 let path = tape_device_path(config, drive)?;
e5950360
DC
532 lock_device_path(&path).map_err(|err| match err {
533 TapeLockError::Other(err) => {
534 TapeLockError::Other(format_err!("unable to lock drive '{}' - {}", drive, err))
535 }
536 other => other,
537 })
546d2653
DC
538}
539
540/// Writes the given state for the specified drive
541///
542/// This function does not lock, so make sure the drive is locked
543pub fn set_tape_device_state(
544 drive: &str,
545 state: &str,
546) -> Result<(), Error> {
cd44fb8d
DM
547
548 let mut path = PathBuf::from(crate::tape::DRIVE_STATE_DIR);
549 path.push(drive);
546d2653 550
21211748 551 let backup_user = pbs_config::backup_user()?;
546d2653
DC
552 let mode = nix::sys::stat::Mode::from_bits_truncate(0o0644);
553 let options = CreateOptions::new()
554 .perm(mode)
555 .owner(backup_user.uid)
556 .group(backup_user.gid);
557
e0a19d33 558 replace_file(path, state.as_bytes(), options, false)
546d2653 559}
25aa55b5 560
546d2653
DC
561/// Get the device state
562pub fn get_tape_device_state(
563 config: &SectionConfigData,
564 drive: &str,
565) -> Result<Option<String>, Error> {
a0cd0f9c 566 let path = format!("{}/{}", crate::tape::DRIVE_STATE_DIR, drive);
546d2653
DC
567 let state = file_read_optional_string(path)?;
568
569 let device_path = tape_device_path(config, drive)?;
570 if test_device_path_lock(&device_path)? {
571 Ok(state)
572 } else {
573 Ok(None)
574 }
575}
576
577fn tape_device_path(
578 config: &SectionConfigData,
579 drive: &str,
580) -> Result<String, Error> {
25aa55b5
DM
581 match config.sections.get(drive) {
582 Some((section_type_name, config)) => {
583 let path = match section_type_name.as_ref() {
584 "virtual" => {
585 VirtualTapeDrive::deserialize(config)?.path
586 }
a79082a0
DM
587 "lto" => {
588 LtoTapeDrive::deserialize(config)?.path
25aa55b5
DM
589 }
590 _ => bail!("unknown drive type '{}' - internal error"),
591 };
546d2653 592 Ok(path)
25aa55b5
DM
593 }
594 None => {
595 bail!("no such drive '{}'", drive);
596 }
597 }
598}
599
600pub struct DeviceLockGuard(std::fs::File);
601
25aa55b5
DM
602// Uses systemd escape_unit to compute a file name from `device_path`, the try
603// to lock `/var/lock/<name>`.
7526d864 604fn open_device_lock(device_path: &str) -> Result<std::fs::File, Error> {
81867f05 605 let lock_name = proxmox::tools::systemd::escape_unit(device_path, true);
25aa55b5 606
a0cd0f9c 607 let mut path = std::path::PathBuf::from(crate::tape::DRIVE_LOCK_DIR);
25aa55b5
DM
608 path.push(lock_name);
609
21211748 610 let user = pbs_config::backup_user()?;
7526d864
DM
611 let options = CreateOptions::new()
612 .perm(Mode::from_bits_truncate(0o660))
613 .owner(user.uid)
614 .group(user.gid);
615
616 atomic_open_or_create_file(
617 path,
618 OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_APPEND,
619 &[],
620 options,
e0a19d33 621 false,
7526d864
DM
622 )
623}
624
625// Acquires an exclusive lock on `device_path`
626//
627fn lock_device_path(device_path: &str) -> Result<DeviceLockGuard, TapeLockError> {
628 let mut file = open_device_lock(device_path)?;
25aa55b5 629 let timeout = std::time::Duration::new(10, 0);
7526d864 630 if let Err(err) = lock_file(&mut file, true, Some(timeout)) {
e5950360
DC
631 if err.kind() == std::io::ErrorKind::Interrupted {
632 return Err(TapeLockError::TimeOut);
633 } else {
634 return Err(err.into());
635 }
636 }
25aa55b5 637
25aa55b5
DM
638 Ok(DeviceLockGuard(file))
639}
33c06b33
DC
640
641// Same logic as lock_device_path, but uses a timeout of 0, making it
642// non-blocking, and returning if the file is locked or not
643fn test_device_path_lock(device_path: &str) -> Result<bool, Error> {
644
7526d864 645 let mut file = open_device_lock(device_path)?;
33c06b33
DC
646
647 let timeout = std::time::Duration::new(0, 0);
7526d864 648 match lock_file(&mut file, true, Some(timeout)) {
33c06b33
DC
649 // file was not locked, continue
650 Ok(()) => {},
651 // file was locked, return true
652 Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => return Ok(true),
653 Err(err) => bail!("{}", err),
654 }
655
33c06b33
DC
656 Ok(false)
657}