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, VmoBackedServerTestingExt as _};
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 let mut request_handler = pin!(fake_server.serve(stream).fuse());
194
195 select! {
196 _ = request_handler => unreachable!(),
197 format = detect_disk_format_res(&proxy).fuse() => format,
198 }
199 }
200
201 #[test]
203 fn to_from_str_test() {
204 for fmt in vec![
205 DiskFormat::Unknown,
206 DiskFormat::Gpt,
207 DiskFormat::Mbr,
208 DiskFormat::Minfs,
209 DiskFormat::Fat,
210 DiskFormat::Blobfs,
211 DiskFormat::Fvm,
212 DiskFormat::Zxcrypt,
213 DiskFormat::BlockVerity,
214 DiskFormat::VbMeta,
215 DiskFormat::Fxfs,
216 DiskFormat::F2fs,
217 DiskFormat::NandBroker,
218 ] {
219 assert_eq!(fmt, fmt.as_str().into());
220 }
221 }
222
223 #[fuchsia::test]
224 async fn detect_disk_format_too_small() {
225 assert_eq!(
226 get_detected_disk_format(&Vec::new(), 1, 512).await.unwrap(),
227 DiskFormat::Unknown
228 );
229 }
230
231 #[fuchsia::test]
232 async fn detect_format_fvm() {
233 let mut data = vec![0; 4096];
234 data[..constants::FVM_MAGIC.len()].copy_from_slice(&constants::FVM_MAGIC);
235 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fvm);
236 }
237
238 #[fuchsia::test]
239 async fn detect_format_zxcrypt() {
240 let mut data = vec![0; 4096];
241 data[0..constants::ZXCRYPT_MAGIC.len()].copy_from_slice(&constants::ZXCRYPT_MAGIC);
242 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Zxcrypt);
243 }
244
245 #[fuchsia::test]
246 async fn detect_format_block_verity() {
247 let mut data = vec![0; 4096];
248 data[0..constants::BLOCK_VERITY_MAGIC.len()]
249 .copy_from_slice(&constants::BLOCK_VERITY_MAGIC);
250 assert_eq!(
251 get_detected_disk_format(&data, 1000, 512).await.unwrap(),
252 DiskFormat::BlockVerity
253 );
254 }
255
256 #[fuchsia::test]
257 async fn detect_format_gpt() {
258 let mut data = vec![0; 4096];
259 data[512..512 + 16].copy_from_slice(&constants::GPT_MAGIC);
260 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Gpt);
261 }
262
263 #[fuchsia::test]
264 async fn detect_format_minfs() {
265 let mut data = vec![0; 4096];
266 data[0..constants::MINFS_MAGIC.len()].copy_from_slice(&constants::MINFS_MAGIC);
267 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Minfs);
268 }
269
270 #[fuchsia::test]
271 async fn detect_format_minfs_large_block_device() {
272 let mut data = vec![0; 32768];
273 data[0..constants::MINFS_MAGIC.len()].copy_from_slice(&constants::MINFS_MAGIC);
274 assert_eq!(
275 get_detected_disk_format(&data, 1250000, 16384).await.unwrap(),
276 DiskFormat::Minfs
277 );
278 }
279
280 #[fuchsia::test]
281 async fn detect_format_blobfs() {
282 let mut data = vec![0; 4096];
283 data[0..constants::BLOBFS_MAGIC.len()].copy_from_slice(&constants::BLOBFS_MAGIC);
284 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Blobfs);
285 }
286
287 #[fuchsia::test]
288 async fn detect_format_vb_meta() {
289 let mut data = vec![0; 4096];
290 data[0..constants::VB_META_MAGIC.len()].copy_from_slice(&constants::VB_META_MAGIC);
291 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::VbMeta);
292 }
293
294 #[fuchsia::test]
295 async fn detect_format_mbr() {
296 let mut data = vec![0; 4096];
297 data[510] = 0x55 as u8;
298 data[511] = 0xAA as u8;
299 data[38] = 0x30 as u8;
300 data[66] = 0x30 as u8;
301 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Mbr);
302 }
303
304 #[fuchsia::test]
305 async fn detect_format_fat_1() {
306 let mut data = vec![0; 4096];
307 data[510] = 0x55 as u8;
308 data[511] = 0xAA as u8;
309 data[38] = 0x29 as u8;
310 data[66] = 0x30 as u8;
311 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fat);
312 }
313
314 #[fuchsia::test]
315 async fn detect_format_fat_2() {
316 let mut data = vec![0; 4096];
317 data[510] = 0x55 as u8;
318 data[511] = 0xAA as u8;
319 data[38] = 0x30 as u8;
320 data[66] = 0x29 as u8;
321 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fat);
322 }
323
324 #[fuchsia::test]
325 async fn detect_format_f2fs() {
326 let mut data = vec![0; 4096];
327 data[1024..1024 + constants::F2FS_MAGIC.len()].copy_from_slice(&constants::F2FS_MAGIC);
328 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::F2fs);
329 }
330
331 #[fuchsia::test]
332 async fn detect_format_fxfs() {
333 let mut data = vec![0; 4096];
334 data[0..constants::FXFS_MAGIC.len()].copy_from_slice(&constants::FXFS_MAGIC);
335 assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fxfs);
336 }
337
338 #[fuchsia::test]
339 async fn detect_format_unknown() {
340 assert_eq!(
341 get_detected_disk_format(&vec![0; 4096], 1000, 512).await.unwrap(),
342 DiskFormat::Unknown
343 );
344 }
345}