fs_management/
format.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5pub mod constants;
6
7use anyhow::{anyhow, ensure, Context as _, Error};
8use block_client::{AsBlockProxy, BlockClient, MutableBufferSlice, RemoteBlockClient};
9
10#[derive(Debug, PartialEq, Clone, Copy)]
11pub enum DiskFormat {
12    Unknown,
13    Gpt,
14    Mbr,
15    Minfs,
16    Fat,
17    Blobfs,
18    Fvm,
19    Zxcrypt,
20    BlockVerity,
21    VbMeta,
22    BootPart,
23    Fxfs,
24    F2fs,
25    NandBroker,
26}
27
28impl DiskFormat {
29    // These are copied verbatim from //src/storage/lib/fs_management/cpp/format.cc, and should be
30    // kept in sync.
31    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::BootPart => "bootpart",
44            Self::Fxfs => "fxfs",
45            Self::F2fs => "f2fs",
46            Self::NandBroker => "nand broker",
47        }
48    }
49}
50
51/// The inverse of DiskFormat::as_str().
52impl From<&str> for DiskFormat {
53    fn from(s: &str) -> Self {
54        match s {
55            "gpt" => Self::Gpt,
56            "mbr" => Self::Mbr,
57            "minfs" => Self::Minfs,
58            "fat" => Self::Fat,
59            "blobfs" => Self::Blobfs,
60            "fvm" => Self::Fvm,
61            "zxcrypt" => Self::Zxcrypt,
62            "block verity" => Self::BlockVerity,
63            "vbmeta" => Self::VbMeta,
64            "bootpart" => Self::BootPart,
65            "fxfs" => Self::Fxfs,
66            "f2fs" => Self::F2fs,
67            "nand broker" => Self::NandBroker,
68            _ => Self::Unknown,
69        }
70    }
71}
72
73pub fn round_up(val: u64, divisor: u64) -> Option<u64> {
74    // Checked version of ((val + (divisor - 1)) / divisor) * divisor
75    ((val.checked_add(divisor.checked_sub(1)?)?).checked_div(divisor)?).checked_mul(divisor)
76}
77
78pub async fn detect_disk_format(block_proxy: impl AsBlockProxy) -> DiskFormat {
79    match detect_disk_format_res(block_proxy).await {
80        Ok(format) => format,
81        Err(e) => {
82            log::warn!("detect_disk_format failed: {:?}", e);
83            return DiskFormat::Unknown;
84        }
85    }
86}
87
88async fn detect_disk_format_res(block_proxy: impl AsBlockProxy) -> Result<DiskFormat, Error> {
89    let block_info = block_proxy
90        .get_info()
91        .await
92        .context("transport error on get_info call")?
93        .map_err(zx::Status::from_raw)
94        .context("get_info call failed")?;
95    ensure!(block_info.block_size > 0, "block size expected to be non-zero");
96
97    let double_block_size = (block_info.block_size)
98        .checked_mul(2)
99        .ok_or_else(|| anyhow!("overflow calculating double block size"))?;
100
101    let header_size = if constants::HEADER_SIZE > double_block_size {
102        constants::HEADER_SIZE
103    } else {
104        double_block_size
105    };
106
107    let device_size = (block_info.block_size as u64)
108        .checked_mul(block_info.block_count)
109        .ok_or_else(|| anyhow!("overflow calculating device size"))?;
110
111    if header_size as u64 > device_size {
112        // The header we want to read is bigger than the actual device. This isn't necessarily an
113        // error, but it does mean we can't inspect the content.
114        return Ok(DiskFormat::Unknown);
115    }
116
117    let buffer_size = round_up(header_size as u64, block_info.block_size as u64)
118        .ok_or_else(|| anyhow!("overflow rounding header size up"))?;
119    let client =
120        RemoteBlockClient::new(block_proxy).await.context("RemoteBlockClient::new failed")?;
121    let mut data = vec![0; buffer_size as usize];
122    client.read_at(MutableBufferSlice::Memory(&mut data), 0).await.context("read_at failed")?;
123
124    if data.starts_with(&constants::FVM_MAGIC) {
125        return Ok(DiskFormat::Fvm);
126    }
127
128    if data.starts_with(&constants::ZXCRYPT_MAGIC) {
129        return Ok(DiskFormat::Zxcrypt);
130    }
131
132    if data.starts_with(&constants::BLOCK_VERITY_MAGIC) {
133        return Ok(DiskFormat::BlockVerity);
134    }
135
136    if &data[block_info.block_size as usize..block_info.block_size as usize + 16]
137        == &constants::GPT_MAGIC
138    {
139        return Ok(DiskFormat::Gpt);
140    }
141
142    if data.starts_with(&constants::MINFS_MAGIC) {
143        return Ok(DiskFormat::Minfs);
144    }
145
146    if data.starts_with(&constants::BLOBFS_MAGIC) {
147        return Ok(DiskFormat::Blobfs);
148    }
149
150    if data.starts_with(&constants::VB_META_MAGIC) {
151        return Ok(DiskFormat::VbMeta);
152    }
153
154    if &data[1024..1024 + constants::F2FS_MAGIC.len()] == &constants::F2FS_MAGIC {
155        return Ok(DiskFormat::F2fs);
156    }
157
158    if data.starts_with(&constants::FXFS_MAGIC) {
159        return Ok(DiskFormat::Fxfs);
160    }
161
162    // Check for Mbr and Fat last.  Since they only have two bytes of magic, it's fairly easy to
163    // randomly encounter this combination (this has happened multiple times in unit tests).
164    if data[510] == 0x55 && data[511] == 0xAA {
165        if data[38] == 0x29 || data[66] == 0x29 {
166            // 0x55AA are always placed at offset 510 and 511 for FAT filesystems.
167            // 0x29 is the Boot Signature, but it is placed at either offset 38 or
168            // 66 (depending on FAT type).
169            return Ok(DiskFormat::Fat);
170        }
171        return Ok(DiskFormat::Mbr);
172    }
173
174    return Ok(DiskFormat::Unknown);
175}
176
177#[cfg(test)]
178mod tests {
179    use super::{constants, detect_disk_format_res, DiskFormat};
180    use anyhow::Error;
181    use fake_block_server::FakeServer;
182    use fidl::endpoints::create_proxy_and_stream;
183    use fidl_fuchsia_hardware_block_volume::VolumeMarker;
184    use futures::{select, FutureExt};
185    use std::pin::pin;
186
187    async fn get_detected_disk_format(
188        content: &[u8],
189        block_count: u64,
190        block_size: u32,
191    ) -> Result<DiskFormat, Error> {
192        let (proxy, stream) = create_proxy_and_stream::<VolumeMarker>();
193
194        let fake_server = FakeServer::new(block_count, block_size, content);
195        let mut request_handler = pin!(fake_server.serve(stream).fuse());
196
197        select! {
198          _ = request_handler => unreachable!(),
199          format = detect_disk_format_res(&proxy).fuse() => format,
200        }
201    }
202
203    /// Confirm that we map to/from string consistently.
204    #[test]
205    fn to_from_str_test() {
206        for fmt in vec![
207            DiskFormat::Unknown,
208            DiskFormat::Gpt,
209            DiskFormat::Mbr,
210            DiskFormat::Minfs,
211            DiskFormat::Fat,
212            DiskFormat::Blobfs,
213            DiskFormat::Fvm,
214            DiskFormat::Zxcrypt,
215            DiskFormat::BlockVerity,
216            DiskFormat::VbMeta,
217            DiskFormat::BootPart,
218            DiskFormat::Fxfs,
219            DiskFormat::F2fs,
220            DiskFormat::NandBroker,
221        ] {
222            assert_eq!(fmt, fmt.as_str().into());
223        }
224    }
225
226    #[fuchsia::test]
227    async fn detect_disk_format_too_small() {
228        assert_eq!(
229            get_detected_disk_format(&Vec::new(), 1, 512).await.unwrap(),
230            DiskFormat::Unknown
231        );
232    }
233
234    #[fuchsia::test]
235    async fn detect_format_fvm() {
236        let mut data = vec![0; 4096];
237        data[..constants::FVM_MAGIC.len()].copy_from_slice(&constants::FVM_MAGIC);
238        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fvm);
239    }
240
241    #[fuchsia::test]
242    async fn detect_format_zxcrypt() {
243        let mut data = vec![0; 4096];
244        data[0..constants::ZXCRYPT_MAGIC.len()].copy_from_slice(&constants::ZXCRYPT_MAGIC);
245        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Zxcrypt);
246    }
247
248    #[fuchsia::test]
249    async fn detect_format_block_verity() {
250        let mut data = vec![0; 4096];
251        data[0..constants::BLOCK_VERITY_MAGIC.len()]
252            .copy_from_slice(&constants::BLOCK_VERITY_MAGIC);
253        assert_eq!(
254            get_detected_disk_format(&data, 1000, 512).await.unwrap(),
255            DiskFormat::BlockVerity
256        );
257    }
258
259    #[fuchsia::test]
260    async fn detect_format_gpt() {
261        let mut data = vec![0; 4096];
262        data[512..512 + 16].copy_from_slice(&constants::GPT_MAGIC);
263        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Gpt);
264    }
265
266    #[fuchsia::test]
267    async fn detect_format_minfs() {
268        let mut data = vec![0; 4096];
269        data[0..constants::MINFS_MAGIC.len()].copy_from_slice(&constants::MINFS_MAGIC);
270        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Minfs);
271    }
272
273    #[fuchsia::test]
274    async fn detect_format_minfs_large_block_device() {
275        let mut data = vec![0; 32768];
276        data[0..constants::MINFS_MAGIC.len()].copy_from_slice(&constants::MINFS_MAGIC);
277        assert_eq!(
278            get_detected_disk_format(&data, 1250000, 16384).await.unwrap(),
279            DiskFormat::Minfs
280        );
281    }
282
283    #[fuchsia::test]
284    async fn detect_format_blobfs() {
285        let mut data = vec![0; 4096];
286        data[0..constants::BLOBFS_MAGIC.len()].copy_from_slice(&constants::BLOBFS_MAGIC);
287        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Blobfs);
288    }
289
290    #[fuchsia::test]
291    async fn detect_format_vb_meta() {
292        let mut data = vec![0; 4096];
293        data[0..constants::VB_META_MAGIC.len()].copy_from_slice(&constants::VB_META_MAGIC);
294        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::VbMeta);
295    }
296
297    #[fuchsia::test]
298    async fn detect_format_mbr() {
299        let mut data = vec![0; 4096];
300        data[510] = 0x55 as u8;
301        data[511] = 0xAA as u8;
302        data[38] = 0x30 as u8;
303        data[66] = 0x30 as u8;
304        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Mbr);
305    }
306
307    #[fuchsia::test]
308    async fn detect_format_fat_1() {
309        let mut data = vec![0; 4096];
310        data[510] = 0x55 as u8;
311        data[511] = 0xAA as u8;
312        data[38] = 0x29 as u8;
313        data[66] = 0x30 as u8;
314        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fat);
315    }
316
317    #[fuchsia::test]
318    async fn detect_format_fat_2() {
319        let mut data = vec![0; 4096];
320        data[510] = 0x55 as u8;
321        data[511] = 0xAA as u8;
322        data[38] = 0x30 as u8;
323        data[66] = 0x29 as u8;
324        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fat);
325    }
326
327    #[fuchsia::test]
328    async fn detect_format_f2fs() {
329        let mut data = vec![0; 4096];
330        data[1024..1024 + constants::F2FS_MAGIC.len()].copy_from_slice(&constants::F2FS_MAGIC);
331        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::F2fs);
332    }
333
334    #[fuchsia::test]
335    async fn detect_format_fxfs() {
336        let mut data = vec![0; 4096];
337        data[0..constants::FXFS_MAGIC.len()].copy_from_slice(&constants::FXFS_MAGIC);
338        assert_eq!(get_detected_disk_format(&data, 1000, 512).await.unwrap(), DiskFormat::Fxfs);
339    }
340
341    #[fuchsia::test]
342    async fn detect_format_unknown() {
343        assert_eq!(
344            get_detected_disk_format(&vec![0; 4096], 1000, 512).await.unwrap(),
345            DiskFormat::Unknown
346        );
347    }
348}