All of lore.kernel.org
 help / color / mirror / Atom feed
From: Stefan Hajnoczi <stefanha@gmail.com>
To: Kevin Wolf <kwolf@redhat.com>
Cc: qemu-block@nongnu.org, hreitz@redhat.com, pbonzini@redhat.com,
	manos.pitsidianakis@linaro.org, philmd@linaro.org,
	qemu-devel@nongnu.org, qemu-rust@nongnu.org
Subject: Re: [PATCH v2 10/11] bochs-rs: Add bochs block driver reimplementation in Rust
Date: Wed, 5 Mar 2025 18:21:27 +0800	[thread overview]
Message-ID: <20250305102127.GE247800@fedora> (raw)
In-Reply-To: <20250218182019.111467-11-kwolf@redhat.com>

[-- Attachment #1: Type: text/plain, Size: 13988 bytes --]

On Tue, Feb 18, 2025 at 07:20:18PM +0100, Kevin Wolf wrote:
> This adds a separate block driver for the bochs image format called
> 'bochs-rs' so that for the moment both the C implementation and the Rust
> implementation can be present in the same build. The intention is to
> remove the C implementation eventually and rename this one into 'bochs'.
> This can only happen once Rust can be a hard build dependency for QEMU.
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  rust/block/Cargo.toml    |   2 +-
>  rust/block/src/bochs.rs  | 297 +++++++++++++++++++++++++++++++++++++++
>  rust/block/src/driver.rs |   5 -
>  rust/block/src/lib.rs    |   1 +
>  4 files changed, 299 insertions(+), 6 deletions(-)
>  create mode 100644 rust/block/src/bochs.rs
> 
> diff --git a/rust/block/Cargo.toml b/rust/block/Cargo.toml
> index fbc2f2d6ef..b91483aed1 100644
> --- a/rust/block/Cargo.toml
> +++ b/rust/block/Cargo.toml
> @@ -3,7 +3,7 @@ name = "block"
>  version = "0.1.0"
>  edition = "2021"
>  authors = ["Kevin Wolf <kwolf@redhat.com>"]
> -license = "GPL-2.0-or-later"
> +license = "GPL-2.0-or-later AND MIT"
>  readme = "README.md"
>  description = "Block backends for QEMU"
>  repository = "https://gitlab.com/qemu-project/qemu/"
> diff --git a/rust/block/src/bochs.rs b/rust/block/src/bochs.rs
> new file mode 100644
> index 0000000000..9dd84446e1
> --- /dev/null
> +++ b/rust/block/src/bochs.rs
> @@ -0,0 +1,297 @@
> +// SPDX-License-Identifier: MIT
> +/*
> + * Block driver for the various disk image formats used by Bochs
> + * Currently only for "growing" type in read-only mode
> + *
> + * Copyright (c) 2005 Alex Beregszaszi
> + * Copyright (c) 2024 Red Hat
> + *
> + * Authors:
> + *   Alex Beregszaszi
> + *   Kevin Wolf <kwolf@redhat.com>
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a copy
> + * of this software and associated documentation files (the "Software"), to deal
> + * in the Software without restriction, including without limitation the rights
> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> + * copies of the Software, and to permit persons to whom the Software is
> + * furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be included in
> + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> + * THE SOFTWARE.
> + */
> +
> +use crate::driver::{block_driver, BdrvChild, BlockDriver, Mapping, MappingTarget, Request};
> +use crate::SizedIoBuffer;
> +use qemu_api::bindings;
> +use qemu_api::errno::Errno;
> +use qemu_api::futures::qemu_run_future;
> +use std::cmp::min;
> +use std::io::{self, Error, ErrorKind};
> +use std::mem::MaybeUninit;
> +use std::ptr;
> +use std::sync::Arc;
> +
> +const BDRV_SECTOR_SIZE: u64 = 512;
> +
> +const HEADER_MAGIC: [u8; 32] = *b"Bochs Virtual HD Image\0\0\0\0\0\0\0\0\0\0";
> +const HEADER_VERSION: u32 = 0x00020000;
> +const HEADER_V1: u32 = 0x00010000;
> +const HEADER_SIZE: usize = 512;
> +
> +const HEADER_TYPE_REDOLOG: [u8; 16] = *b"Redolog\0\0\0\0\0\0\0\0\0";
> +const HEADER_SUBTYPE_GROWING: [u8; 16] = *b"Growing\0\0\0\0\0\0\0\0\0";
> +
> +// TODO Use u64.div_ceil() when MSRV is updated to at least 1.73.0
> +fn div_ceil(a: u64, b: u64) -> u64 {
> +    (a + b - 1) / b
> +}
> +
> +// TODO Use little endian enforcing type for integers

I just wanted to check if anyone knows whether the Rust standard library
has an endian enforcing type? It seems like an omission of a basic
standard library primitive. I've seen crates re-inventing this again and
again because the standard library only supports endianness conversion
but not a type that enforces safe conversion. Would be awesome if Rust
had this in the standard library and I may have missed recent
developments.

> +
> +#[repr(C, packed)]
> +struct BochsHeader {
> +    pub magic: [u8; 32],
> +    pub imgtype: [u8; 16],
> +    pub subtype: [u8; 16],
> +    pub version: u32,
> +    pub header_size: u32,
> +    pub catalog_entries: u32,
> +    pub bitmap_size: u32,
> +    pub extent_size: u32,
> +    pub extra: BochsHeaderExtra,
> +}
> +unsafe impl SizedIoBuffer for BochsHeader {}
> +
> +#[repr(C, packed)]
> +union BochsHeaderExtra {
> +    v2: BochsHeaderExtraRedolog,
> +    v1: BochsHeaderExtraRedologV1,
> +    padding: [u8; HEADER_SIZE - 84],

A compile-time check that BochsHeader == HEADER_SIZE would be nice. It
feels a little unsafe to have HEADER_SIZE and this explicit padding size
calculation without a check. And to be honest it took me a few moments
to verify the it's correct while reading the patch :).

> +}
> +
> +#[repr(C, packed)]
> +#[derive(Clone, Copy)]
> +struct BochsHeaderExtraRedolog {
> +    pub timestamp: u32,
> +    pub disk_size: u64,
> +}
> +
> +#[repr(C, packed)]
> +#[derive(Clone, Copy)]
> +struct BochsHeaderExtraRedologV1 {
> +    pub disk_size: u64,
> +}
> +
> +pub struct BochsImage {
> +    file: Arc<BdrvChild>,
> +    size: u64,
> +    data_offset: u64,
> +    bitmap_blocks: u64,
> +    extent_size: u64,
> +    extent_blocks: u64,
> +    catalog_bitmap: Vec<u32>, // TODO Rename
> +}
> +
> +impl BochsImage {
> +    pub async fn new(file: BdrvChild) -> io::Result<Self> {
> +        let header = file
> +            .read_uninit(0, Box::new(MaybeUninit::<BochsHeader>::uninit()))
> +            .await?;
> +
> +        if header.magic != HEADER_MAGIC
> +            || header.imgtype != HEADER_TYPE_REDOLOG
> +            || header.subtype != HEADER_SUBTYPE_GROWING
> +        {
> +            return Err(Error::new(
> +                ErrorKind::InvalidInput,
> +                "Image not in Bochs format",
> +            ));
> +        }
> +
> +        let size = match u32::from_le(header.version) {
> +            HEADER_VERSION => unsafe { header.extra.v2.disk_size.to_le() },
> +            HEADER_V1 => unsafe { header.extra.v1.disk_size.to_le() },

I don't understand why the names are HEADER_VERSION vs HEADER_V1. Does
it make sense to rename them to HEADER_V1 and HEADER_V2?

> +            _ => return Err(Error::new(ErrorKind::InvalidInput, "Version not supported")),
> +        };
> +
> +        let header_size: u64 = header.header_size.to_le().into();
> +        let extent_size: u64 = header.extent_size.to_le().into();
> +
> +        if extent_size < BDRV_SECTOR_SIZE {
> +            // bximage actually never creates extents smaller than 4k
> +            return Err(Error::new(
> +                ErrorKind::InvalidInput,
> +                "Extent size must be at least 512",
> +            ));
> +        } else if !extent_size.is_power_of_two() {
> +            return Err(Error::new(
> +                ErrorKind::InvalidInput,
> +                format!("Extent size {extent_size} is not a power of two"),
> +            ));
> +        } else if extent_size > 0x800000 {
> +            return Err(Error::new(
> +                ErrorKind::InvalidInput,
> +                format!("Extent size {extent_size} is too large"),
> +            ));
> +        }
> +
> +        // Limit to 1M entries to avoid unbounded allocation. This is what is
> +        // needed for the largest image that bximage can create (~8 TB).
> +        let catalog_entries: usize = header
> +            .catalog_entries
> +            .to_le()
> +            .try_into()
> +            .map_err(|e| Error::new(ErrorKind::InvalidInput, e))?;
> +        if catalog_entries > 0x100000 {
> +            return Err(Error::new(ErrorKind::Other, "Catalog size is too large"));
> +        } else if (catalog_entries as u64) < div_ceil(size, extent_size) {
> +            return Err(Error::new(
> +                ErrorKind::InvalidInput,
> +                "Catalog size is too small for this disk size",
> +            ));
> +        }
> +
> +        // FIXME This was g_try_malloc() in C
> +        let mut catalog_bitmap = vec![0u32; catalog_entries];
> +        file.read(header_size, catalog_bitmap.as_mut_slice())
> +            .await?;
> +
> +        for entry in &mut catalog_bitmap {
> +            *entry = entry.to_le();
> +        }
> +
> +        let data_offset = header_size + (catalog_entries as u64 * 4);
> +        let bitmap_blocks = (1 + (header.bitmap_size.to_le() - 1) / 512).into();
> +        let extent_blocks = 1 + (extent_size - 1) / 512;
> +
> +        Ok(Self {
> +            file: Arc::new(file),
> +            size,
> +            data_offset,
> +            bitmap_blocks,
> +            extent_size,
> +            extent_blocks,
> +            catalog_bitmap,
> +        })
> +    }
> +}
> +
> +impl BlockDriver for BochsImage {
> +    type Options = bindings::BlockdevOptionsGenericFormat;
> +
> +    unsafe fn parse_options(
> +        v: &mut bindings::Visitor,
> +        opts: &mut *mut Self::Options,
> +        errp: *mut *mut bindings::Error,
> +    ) {
> +        unsafe {
> +            bindings::visit_type_BlockdevOptionsGenericFormat(v, ptr::null(), opts as *mut _, errp);
> +        }
> +    }
> +
> +    unsafe fn free_options(opts: *mut Self::Options) {
> +        unsafe {
> +            bindings::qapi_free_BlockdevOptionsGenericFormat(opts);
> +        }
> +    }
> +
> +    unsafe fn open(
> +        bs: *mut bindings::BlockDriverState,
> +        opts: &Self::Options,
> +        errp: *mut *mut bindings::Error,
> +    ) -> std::os::raw::c_int {
> +        let file_child;
> +        unsafe {
> +            /* No write support yet */
> +            bindings::bdrv_graph_rdlock_main_loop();
> +            let ret = bindings::bdrv_apply_auto_read_only(bs, ptr::null(), errp);
> +            bindings::bdrv_graph_rdunlock_main_loop();
> +            if ret < 0 {
> +                return ret;
> +            }
> +
> +            file_child = match BdrvChild::new(bs, opts.file, errp) {
> +                Some(c) => c,
> +                None => return -libc::EINVAL,
> +            };
> +        }
> +
> +        qemu_run_future(async {
> +            match BochsImage::new(file_child).await {
> +                Ok(bdrv) => unsafe {
> +                    (*bs).total_sectors =
> +                        div_ceil(bdrv.size(), BDRV_SECTOR_SIZE).try_into().unwrap();
> +                    let state = (*bs).opaque as *mut BochsImage;
> +                    state.write(bdrv);
> +                    0
> +                },
> +                Err(e) => -i32::from(Errno::from(e).0),
> +            }
> +        })
> +    }
> +
> +    fn size(&self) -> u64 {
> +        self.size
> +    }
> +
> +    async fn map(&self, req: &Request) -> io::Result<Mapping> {
> +        let (offset, len) = match *req {
> +            Request::Read { offset, len } => (offset, len),
> +        };
> +
> +        let extent_index: usize = (offset / self.extent_size).try_into().unwrap();
> +        let extent_offset = (offset % self.extent_size) / 512;
> +
> +        if self.catalog_bitmap[extent_index] == 0xffffffff {
> +            return Ok(Mapping {
> +                offset: (extent_index as u64) * self.extent_size,
> +                len: self.extent_size,
> +                target: MappingTarget::Unmapped,
> +            });
> +        }
> +
> +        let bitmap_offset = self.data_offset
> +            + (512
> +                * (self.catalog_bitmap[extent_index] as u64)
> +                * (self.extent_blocks + self.bitmap_blocks));
> +
> +        // Read in bitmap for current extent
> +        // TODO This should be cached
> +        let mut bitmap_entry = 0x8;

Why is bitmap_entry initialized to 0x8?

> +        self.file
> +            .read(bitmap_offset + (extent_offset / 8), &mut bitmap_entry)
> +            .await?;
> +
> +        // We checked only a single sector
> +        let offset = offset & !511;
> +        let len = min(len, 512);
> +
> +        if (bitmap_entry >> (extent_offset % 8)) & 1 == 0 {
> +            return Ok(Mapping {
> +                offset,
> +                len,
> +                target: MappingTarget::Unmapped,
> +            });
> +        }
> +
> +        Ok(Mapping {
> +            offset,
> +            len,
> +            target: MappingTarget::Data {
> +                node: self.file.clone(),
> +                offset: bitmap_offset + (512 * (self.bitmap_blocks + extent_offset)),
> +            },
> +        })
> +    }
> +}
> +
> +block_driver!("bochs-rs", BochsImage);
> diff --git a/rust/block/src/driver.rs b/rust/block/src/driver.rs
> index 022d50ffbc..baeaf47eda 100644
> --- a/rust/block/src/driver.rs
> +++ b/rust/block/src/driver.rs
> @@ -2,11 +2,6 @@
>  // Author(s): Kevin Wolf <kwolf@redhat.com>
>  // SPDX-License-Identifier: GPL-2.0-or-later
>  
> -// All of this is unused until the first block driver is added
> -#![allow(dead_code)]
> -#![allow(unused_macros)]
> -#![allow(unused_imports)]
> -
>  use crate::{IoBuffer, SizedIoBuffer};
>  use qemu_api::bindings;
>  use qemu_api::errno::Errno;
> diff --git a/rust/block/src/lib.rs b/rust/block/src/lib.rs
> index 54ebd480ec..ff528609bc 100644
> --- a/rust/block/src/lib.rs
> +++ b/rust/block/src/lib.rs
> @@ -1,3 +1,4 @@
> +mod bochs;
>  mod driver;
>  mod iobuffer;
>  
> -- 
> 2.48.1
> 
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

  parent reply	other threads:[~2025-03-05 10:21 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-02-18 18:20 [PATCH v2 00/11] rust/block: Add minimal block driver bindings Kevin Wolf
2025-02-18 18:20 ` [PATCH v2 01/11] rust: Build separate qemu_api_tools and qemu_api_system Kevin Wolf
2025-02-20  7:10   ` Zhao Liu
2025-02-18 18:20 ` [PATCH v2 02/11] meson: Add rust_block_ss and link tools with it Kevin Wolf
2025-02-18 18:20 ` [PATCH v2 03/11] rust: Add some block layer bindings Kevin Wolf
2025-02-18 18:20 ` [PATCH v2 04/11] rust/qemu-api: Add wrappers to run futures in QEMU Kevin Wolf
2025-02-20  6:35   ` Zhao Liu
2025-02-20 14:58     ` Kevin Wolf
2025-03-05  2:15   ` Stefan Hajnoczi
2025-02-18 18:20 ` [PATCH v2 05/11] rust/block: Add empty crate Kevin Wolf
2025-02-19  6:46   ` Zhao Liu
2025-02-18 18:20 ` [PATCH v2 06/11] rust/block: Add I/O buffer traits Kevin Wolf
2025-02-18 18:20 ` [PATCH v2 07/11] block: Add bdrv_open_blockdev_ref_file() Kevin Wolf
2025-02-18 18:20 ` [PATCH v2 08/11] rust/block: Add driver module Kevin Wolf
2025-02-20  6:52   ` Zhao Liu
2025-03-05  2:43   ` Stefan Hajnoczi
2025-02-18 18:20 ` [PATCH v2 09/11] rust/block: Add read support for block drivers Kevin Wolf
2025-02-19  6:11   ` Paolo Bonzini
2025-02-19 13:02     ` Kevin Wolf
2025-02-19 22:42       ` Paolo Bonzini
2025-03-05  3:04   ` Stefan Hajnoczi
2025-03-05  9:56   ` Stefan Hajnoczi
2025-02-18 18:20 ` [PATCH v2 10/11] bochs-rs: Add bochs block driver reimplementation in Rust Kevin Wolf
2025-02-20  7:02   ` Zhao Liu
2025-03-05 10:21   ` Stefan Hajnoczi [this message]
2025-02-18 18:20 ` [PATCH v2 11/11] rust/block: Add format probing Kevin Wolf
2025-03-05 10:23 ` [PATCH v2 00/11] rust/block: Add minimal block driver bindings Stefan Hajnoczi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250305102127.GE247800@fedora \
    --to=stefanha@gmail.com \
    --cc=hreitz@redhat.com \
    --cc=kwolf@redhat.com \
    --cc=manos.pitsidianakis@linaro.org \
    --cc=pbonzini@redhat.com \
    --cc=philmd@linaro.org \
    --cc=qemu-block@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    --cc=qemu-rust@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.