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}