block_devs/
lib.rs

1//! Block Devs provides safe wrappers for the ioctl calls for
2//! dealing with block devices (USB sticks, SSDs, hard drives etc).
3//!
4//! It aims to provide a consitent interface across all platforms for things like
5//! getting the number of bytes a disk has.
6//!
7//! So far Linux, macOS and Open BSD are supported
8//!
9//! It does this by a extention trait ([`BlckExt`]) on the standard [`File`] struct.
10//!
11//! ```rust,no_run
12//!     use block_devs::BlckExt;
13//!     use std::fs::File;
14//!
15//!     let path = "/dev/sda2";
16//!     let file = File::open(path).unwrap();
17//!     let count = file.get_block_count().unwrap();
18//!     let bytes = file.get_block_device_size().unwrap();
19//!     let gb = bytes >> 30;
20//!
21//!     println!("disk is {} blocks totaling {}gb", count, gb);
22//! ```
23//!
24//! [`File`]: std::fs::File
25//! [`BlckExt`]: BlckExt
26
27#[cfg(target_os = "linux")]
28mod linux;
29#[cfg(target_os = "linux")]
30pub use self::linux::*;
31
32#[cfg(target_os = "macos")]
33mod macos;
34#[cfg(target_os = "macos")]
35pub use self::macos::*;
36
37#[cfg(target_os = "freebsd")]
38mod freebsd;
39#[cfg(target_os = "freebsd")]
40pub use self::freebsd::*;
41
42use std::cmp::min;
43use std::io::{Read, Result, Seek, SeekFrom, Write};
44
45/// Block device specific extensions to [`File`].
46///
47/// [`File`]: std::fs::File
48pub trait BlckExt: Seek + Read + Write {
49    /// Test if the file is a block device
50    ///
51    /// This will return `true` for a block device e.g. `"/dev/sda1"` and `false` for other files
52    /// If it returns `false` using the other `BlckExt` methods on this file will almost certainly be an error.
53    fn is_block_device(&self) -> bool;
54
55    /// Get the total size of the block device in bytes.
56    fn get_block_device_size(&self) -> Result<u64>;
57
58    /// Get the size of one logical block in bytes.
59    fn get_size_of_block(&self) -> Result<u64>;
60
61    /// Get the number of blocks on the device.
62    fn get_block_count(&self) -> Result<u64>;
63
64    /// Ask the OS to re-read the partition table from the device.
65    ///
66    /// When writing an image to a block device the partions layout may change
67    /// this ask the OS to re-read the partion table
68    fn block_reread_paritions(&self) -> Result<()>;
69
70    /// Does this device support zeroing on discard.
71    ///
72    /// Some device (e.g. SSDs with TRIM support) have the ability to mark blocks as unused in a
73    /// way that means they will return zeros on future reads.
74    ///
75    /// If this returns `true` then all calls to [`block_discard`] will cause following reads to return zeros
76    ///
77    /// Some device only support zeroing on discard for certain sizes and alignements, in which case this
78    /// will return `false` but some calls to [`block_discard`] may still result in zeroing some or all of the discared range.
79    ///
80    /// Since this is a linux only feature other systems will always return false
81    ///
82    /// Your best bet for knowing if block discarding zeros is to discard some blocks and test that it worked using [`block_fast_zero_out`].
83    ///
84    /// [`block_fast_zero_out`]: #tymethod.block_fast_zero_out
85    /// [`block_discard`]: #tymethod.block_discard
86    fn block_discard_zeros(&self) -> Result<bool>;
87
88    /// Discard a section of the block device.
89    ///
90    /// Some device e.g. thinly provisioned arrays or SSDs with TRIM support have the ability to mark blocks as unused
91    /// to free them up for other use. This may or may not result in future reads to the discarded section to return
92    /// zeros, see [`block_discard_zeros`] for more detail.
93    ///
94    /// `offset` and `length` should be given in bytes.
95    ///
96    /// [`block_discard_zeros`]: #tymethod.block_discard_zeros
97    fn block_discard(&self, offset: u64, len: u64) -> Result<()>;
98
99    /// Zeros out a section of the block device.
100    ///
101    /// There is no guaranty that there special kernel support for this even [`block_discard_zeros`] returns `true`
102    /// so it is unlikely to be much faster that writing zeros the normal way, apart from saving a bunch of syscalls.
103    ///
104    /// If there is no system call on a platfrom it will be implement by writing zeros in the normal way
105    ///
106    /// `offset` and `length` should be given in bytes.
107    ///
108    /// [`block_discard_zeros`]: #tymethod.block_discard_zeros
109    fn block_zero_out(&mut self, offset: u64, len: u64) -> Result<()> {
110        const BUF_SIZE: usize = 1024;
111        let zeros = [0; BUF_SIZE];
112        let oldpos = self.seek(SeekFrom::Start(offset))?;
113        let mut remaining = len;
114        while remaining > BUF_SIZE as u64 {
115            self.write_all(&zeros)?;
116            remaining -= BUF_SIZE as u64;
117        }
118        self.write_all(&zeros[0..remaining as usize])?;
119        self.seek(SeekFrom::Start(oldpos))?;
120        Ok(())
121    }
122
123    /// Try to zero out a block using discard and return an error if the data is not zeroed.
124    ///
125    /// Some devices will (SSDs, thinly provisioned RAID arrays) will return zeros if a sufficiently
126    /// large area is discarded. This method writes some data to the start of the range to be zerod, disards the range
127    /// then reads the data back. It returns an error if the data was not zerod.
128    ///
129    /// `offset` and `length` should be given in bytes.
130    fn block_fast_zero_out(&mut self, offset: u64, len: u64) -> Result<()> {
131        const BUF_SIZE: usize = 1024;
132        let test_len = min(BUF_SIZE as u64, len) as usize;
133        let ones = [255; BUF_SIZE];
134        self.seek(SeekFrom::Start(offset))?;
135        self.write_all(&ones[0..test_len])?;
136        self.seek(SeekFrom::Start(offset))?;
137        self.sync_data()?;
138        self.block_discard(offset, len)?;
139        self.sync_data()?;
140
141        let mut buffer = [255; BUF_SIZE];
142        let read = self.read(&mut buffer)?;
143        self.seek(SeekFrom::Start(offset))?;
144        if read < test_len {
145            return Err(io_error("Fast Zero Block failed"));
146        }
147        if buffer[0..test_len].iter().any(|x| *x != 0) {
148            return Err(io_error("Fast Zero Block failed"));
149        }
150
151        Ok(())
152    }
153
154    fn sync_data(&self) -> Result<()>;
155}
156
157fn io_error(str: &str) -> std::io::Error {
158    std::io::Error::new(std::io::ErrorKind::Other, str)
159}
160
161fn to_io(err: nix::Error) -> std::io::Error {
162    match err {
163        nix::Error::Sys(errno) => errno.into(),
164        nix::Error::InvalidPath => io_error("InvalidPath"),
165        nix::Error::InvalidUtf8 => io_error("InvalidUtf8"),
166        nix::Error::UnsupportedOperation => io_error("UnsupportedOperation"),
167    }
168}