1pub mod constants;
6
7use anyhow::{Context as _, Error, anyhow, ensure};
8use block_client::{BlockClient, MutableBufferSlice, RemoteBlockClient};
9use fidl_fuchsia_storage_block::BlockProxy;
10
11#[derive(Debug, PartialEq, Clone, Copy)]
12pub enum DiskFormat {
13 Unknown,
14 Gpt,
15 Mbr,
16 Minfs,
17 Fat,
18 Blobfs,
19 Fvm,
20 Zxcrypt,
21 BlockVerity,
22 VbMeta,
23 Fxfs,
24 F2fs,
25 NandBroker,
26}
27
28impl DiskFormat {
29 pub fn as_str(&self) -> &'static str {
32 match self {
33 Self::Unknown => "unknown!",
34 Self::Gpt => "gpt",
35 Self::Mbr => "mbr",
36 Self::Minfs => "minfs",
37 Self::Fat => "fat",
38 Self::Blobfs => "blobfs",
39 Self::Fvm => "fvm",
40 Self::Zxcrypt => "zxcrypt",
41 Self::BlockVerity => "block verity",
42 Self::VbMeta => "vbmeta",
43 Self::Fxfs => "fxfs",
44 Self::F2fs => "f2fs",
45 Self::NandBroker => "nand broker",
46 }
47 }
48}
49
50impl From<&str> for DiskFormat {
52 fn from(s: &str) -> Self {
53 match s {
54 "gpt" => Self::Gpt,
55 "mbr" => Self::Mbr,
56 "minfs" => Self::Minfs,
57 "fat" => Self::Fat,
58 "blobfs" => Self::Blobfs,
59 "fvm" => Self::Fvm,
60 "zxcrypt" => Self::Zxcrypt,
61 "block verity" => Self::BlockVerity,
62 "vbmeta" => Self::VbMeta,
63 "fxfs" => Self::Fxfs,
64 "f2fs" => Self::F2fs,
65 "nand broker" => Self::NandBroker,
66 _ => Self::Unknown,
67 }
68 }
69}
70
71pub fn round_up(val: u64, divisor: u64) -> Option<u64> {
72 ((val.checked_add(divisor.checked_sub(1)?)?).checked_div(divisor)?).checked_mul(divisor)
74}
75
76pub async fn detect_disk_format(block_proxy: &BlockProxy) -> DiskFormat {
77 match detect_disk_format_res(block_proxy).await {
78 Ok(format) => format,
79 Err(e) => {
80 log::warn!("detect_disk_format failed: {:?}", e);
81 return DiskFormat::Unknown;
82 }
83 }
84}
85
86async fn detect_disk_format_res(block_proxy: &BlockProxy) -> Result<DiskFormat, Error> {
87 let block_info = block_proxy
88 .get_info()
89 .await
90 .context("transport error on get_info call")?
91 .map_err(zx::Status::from_raw)
92 .context("get_info call failed")?;
93 ensure!(block_info.block_size > 0, "block size expected to be non-zero");
94
95 let double_block_size = (block_info.block_size)
96 .checked_mul(2)
97 .ok_or_else(|| anyhow!("overflow calculating double block size"))?;
98
99 let header_size = if constants::HEADER_SIZE > double_block_size {
100 constants::HEADER_SIZE
101 } else {
102 double_block_size
103 };
104
105 let device_size = (block_info.block_size as u64)
106 .checked_mul(block_info.block_count)
107 .ok_or_else(|| anyhow!("overflow calculating device size"))?;
108
109 if header_size as u64 > device_size {
110 return Ok(DiskFormat::Unknown);
113 }
114
115 let buffer_size = round_up(header_size as u64, block_info.block_size as u64)
116 .ok_or_else(|| anyhow!("overflow rounding header size up"))?;
117 let client =
118 RemoteBlockClient::new(block_proxy).await.context("RemoteBlockClient::new failed")?;
119 let mut data = vec![0; buffer_size as usize];
120 client.read_at(MutableBufferSlice::Memory(&mut data), 0).await.context("read_at failed")?;
121
122 if data.starts_with(&constants::FVM_MAGIC) {
123 return Ok(DiskFormat::Fvm);
124 }
125
126 if data.starts_with(&constants::ZXCRYPT_MAGIC) {
127 return Ok(DiskFormat::Zxcrypt);
128 }
129
130 if data.starts_with(&constants::BLOCK_VERITY_MAGIC) {
131 return Ok(DiskFormat::BlockVerity);
132 }
133
134 if &data[block_info.block_size as usize..block_info.block_size as usize + 16]
135 == &constants::GPT_MAGIC
136 {
137 return Ok(DiskFormat::Gpt);
138 }
139
140 if data.starts_with(&constants::MINFS_MAGIC) {
141 return Ok(DiskFormat::Minfs);
142 }
143
144 if data.starts_with(&constants::BLOBFS_MAGIC) {
145 return Ok(DiskFormat::Blobfs);
146 }
147
148 if data.starts_with(&constants::VB_META_MAGIC) {
149 return Ok(DiskFormat::VbMeta);
150 }
151
152 if &data[1024..1024 + constants::F2FS_MAGIC.len()] == &constants::F2FS_MAGIC {
153 return Ok(DiskFormat::F2fs);
154 }
155
156 if data.starts_with(&constants::FXFS_MAGIC) {
157 return Ok(DiskFormat::Fxfs);
158 }
159
160 if data[510] == 0x55 && data[511] == 0xAA {
163 if data[38] == 0x29 || data[66] == 0x29 {
164 return Ok(DiskFormat::Fat);
168 }
169 return Ok(DiskFormat::Mbr);
170 }
171
172 return Ok(DiskFormat::Unknown);
173}
174
175#[cfg(test)]
176mod tests {
177 use super::{DiskFormat, constants, detect_disk_format_res};
178 use anyhow::Error;
179 use fidl::endpoints::create_proxy_and_stream;
180 use fidl_fuchsia_storage_block::BlockMarker;
181 use futures::{FutureExt, select};
182 use std::pin::pin;
183 use vmo_backed_block_server::VmoBackedServer;
184
185 async fn get_detected_disk_format(
186 content: &[u8],
187 block_count: u64,
188 block_size: u32,
189 ) -> Result<DiskFormat, Error> {
190 let (proxy, stream) = create_proxy_and_stream::<BlockMarker>();
191
192 let fake_server = VmoBackedServer::new(block_count, block_size, content)
193 .expect("Failed to create VmoBackedServer");
194 let mut request_handler = pin!(fake_server.serve(stream).fuse());
195
196 select! {
197 _ = request_handler => unreachable!(),
198 format = detect_disk_format_res(&proxy).fuse() => format,
199 }
200 }
201
202 #[test]
204 fn to_from_str_test() {
205 for fmt in vec![
206 DiskFormat::Unknown,
207 DiskFormat::Gpt,
208 DiskFormat::Mbr,
209 DiskFormat::Minfs,
210 DiskFormat::Fat,
211 DiskFormat::Blobfs,
212 DiskFormat::Fvm,
213 DiskFormat::Zxcrypt,
214 DiskFormat::BlockVerity,
215 DiskFormat::VbMeta,
216 DiskFormat::Fxfs,
217 DiskFormat::F2fs,
218 DiskFormat::NandBroker,
219 ] {
220 assert_eq!(fmt, fmt.as_str().into());
221 }
222 }
223
224 #[fuchsia::test]
225 async fn detect_disk_format_too_small() {
226 assert_eq!(get_detected_disk_format(&[], 1, 512).await.unwrap(), DiskFormat::Unknown);
227 }
228
229 #[fuchsia::test]
230 async fn detect_format_fvm() {
231 let mut data = vec![0; 4096];
232 data[..constants::FVM_MAGIC.len()].copy_from_slice(&constants::FVM_MAGIC);
233 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fvm);
234 }
235
236 #[fuchsia::test]
237 async fn detect_format_zxcrypt() {
238 let mut data = vec![0; 4096];
239 data[0..constants::ZXCRYPT_MAGIC.len()].copy_from_slice(&constants::ZXCRYPT_MAGIC);
240 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Zxcrypt);
241 }
242
243 #[fuchsia::test]
244 async fn detect_format_block_verity() {
245 let mut data = vec![0; 4096];
246 data[0..constants::BLOCK_VERITY_MAGIC.len()]
247 .copy_from_slice(&constants::BLOCK_VERITY_MAGIC);
248 assert_eq!(
249 get_detected_disk_format(&data, 1000, 512).await.unwrap(),
250 DiskFormat::BlockVerity
251 );
252 }
253
254 #[fuchsia::test]
255 async fn detect_format_gpt() {
256 let mut data = vec![0; 4096];
257 data[512..512 + 16].copy_from_slice(&constants::GPT_MAGIC);
258 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Gpt);
259 }
260
261 #[fuchsia::test]
262 async fn detect_format_minfs() {
263 let mut data = vec![0; 4096];
264 data[0..constants::MINFS_MAGIC.len()].copy_from_slice(&constants::MINFS_MAGIC);
265 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Minfs);
266 }
267
268 #[fuchsia::test]
269 async fn detect_format_minfs_large_block_device() {
270 let mut data = vec![0; 32768];
271 data[0..constants::MINFS_MAGIC.len()].copy_from_slice(&constants::MINFS_MAGIC);
272 assert_eq!(
273 get_detected_disk_format(&data, 1250000, 16384).await.unwrap(),
274 DiskFormat::Minfs
275 );
276 }
277
278 #[fuchsia::test]
279 async fn detect_format_blobfs() {
280 let mut data = vec![0; 4096];
281 data[0..constants::BLOBFS_MAGIC.len()].copy_from_slice(&constants::BLOBFS_MAGIC);
282 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Blobfs);
283 }
284
285 #[fuchsia::test]
286 async fn detect_format_vb_meta() {
287 let mut data = vec![0; 4096];
288 data[0..constants::VB_META_MAGIC.len()].copy_from_slice(&constants::VB_META_MAGIC);
289 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::VbMeta);
290 }
291
292 #[fuchsia::test]
293 async fn detect_format_mbr() {
294 let mut data = vec![0; 4096];
295 data[510] = 0x55 as u8;
296 data[511] = 0xAA as u8;
297 data[38] = 0x30 as u8;
298 data[66] = 0x30 as u8;
299 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Mbr);
300 }
301
302 #[fuchsia::test]
303 async fn detect_format_fat_1() {
304 let mut data = vec![0; 4096];
305 data[510] = 0x55 as u8;
306 data[511] = 0xAA as u8;
307 data[38] = 0x29 as u8;
308 data[66] = 0x30 as u8;
309 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fat);
310 }
311
312 #[fuchsia::test]
313 async fn detect_format_fat_2() {
314 let mut data = vec![0; 4096];
315 data[510] = 0x55 as u8;
316 data[511] = 0xAA as u8;
317 data[38] = 0x30 as u8;
318 data[66] = 0x29 as u8;
319 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fat);
320 }
321
322 #[fuchsia::test]
323 async fn detect_format_f2fs() {
324 let mut data = vec![0; 4096];
325 data[1024..1024 + constants::F2FS_MAGIC.len()].copy_from_slice(&constants::F2FS_MAGIC);
326 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::F2fs);
327 }
328
329 #[fuchsia::test]
330 async fn detect_format_fxfs() {
331 let mut data = vec![0; 4096];
332 data[0..constants::FXFS_MAGIC.len()].copy_from_slice(&constants::FXFS_MAGIC);
333 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fxfs);
334 }
335
336 #[fuchsia::test]
337 async fn detect_format_unknown() {
338 assert_eq!(
339 get_detected_disk_format(&vec![0; 4096], 1000, 512).await.unwrap(),
340 DiskFormat::Unknown
341 );
342 }
343}