From nobody Sun Feb 8 21:28:46 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) client-ip=208.118.235.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Authentication-Results: mx.zoho.com; spf=pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; Return-Path: Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) by mx.zohomail.com with SMTPS id 1492531342203720.7756831930509; Tue, 18 Apr 2017 09:02:22 -0700 (PDT) Received: from localhost ([::1]:42714 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1d0Va7-0003KX-Uu for importer@patchew.org; Tue, 18 Apr 2017 12:02:20 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:37947) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1d0VWI-0000Sn-AU for qemu-devel@nongnu.org; Tue, 18 Apr 2017 11:58:24 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1d0VWG-0008Nb-9k for qemu-devel@nongnu.org; Tue, 18 Apr 2017 11:58:22 -0400 Received: from mx1.redhat.com ([209.132.183.28]:56686) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1d0VWB-0008Lq-Tz; Tue, 18 Apr 2017 11:58:16 -0400 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id AED4BC059742; Tue, 18 Apr 2017 15:58:14 +0000 (UTC) Received: from localhost (ovpn-204-72.brq.redhat.com [10.40.204.72]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 5233D71D62; Tue, 18 Apr 2017 15:58:10 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com AED4BC059742 Authentication-Results: ext-mx08.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com Authentication-Results: ext-mx08.extmail.prod.ext.phx2.redhat.com; spf=pass smtp.mailfrom=mreitz@redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 mx1.redhat.com AED4BC059742 From: Max Reitz To: qemu-block@nongnu.org Date: Sat, 1 Apr 2017 17:57:50 +0200 Message-Id: <20170401155751.14322-4-mreitz@redhat.com> In-Reply-To: <20170401155751.14322-1-mreitz@redhat.com> References: <20170401155751.14322-1-mreitz@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.32]); Tue, 18 Apr 2017 15:58:14 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [RFC for-3.0 3/4] block/qcow2-rust: Add partial write support X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , John Snow , qemu-devel@nongnu.org, Max Reitz Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail: RSF_0 Z_629925259 SPT_0 Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Some people may want to lead you to believe write support may destroy your data. These entirely BASELESS and FALSE accusations are COMPLETE and UTTER LIES because you actually cannot use this driver yet at all, as it does not register itself as a qemu block driver. This is a very modest approach, in contrast to some other LEGACY drivers that do just assume they are worthy of this honor (which of course is utterly wrong, they are INSECURE and NOT SUITED FOR THE JOB) and just want to bury the gem the QEMU project is under a pile of not-greatness. VERY SAD! Also, we do not allocate any new clusters because we do not TAKE from HONEST HARD-WORKING other programs that may want to use disk space, but we just make do with what we have and that is enough! Signed-off-by: Max Reitz --- block/rust/src/qcow2/allocation.rs | 162 +++++++++++++++++++++++++++++++++= ++++ block/rust/src/qcow2/io.rs | 117 +++++++++++++++++++++++++++ block/rust/src/qcow2/mod.rs | 17 +++- block/rust/src/qcow2/refcount.rs | 96 ++++++++++++++++++++++ 4 files changed, 390 insertions(+), 2 deletions(-) create mode 100644 block/rust/src/qcow2/allocation.rs create mode 100644 block/rust/src/qcow2/refcount.rs diff --git a/block/rust/src/qcow2/allocation.rs b/block/rust/src/qcow2/allo= cation.rs new file mode 100644 index 0000000000..9ed975d853 --- /dev/null +++ b/block/rust/src/qcow2/allocation.rs @@ -0,0 +1,162 @@ +use interface::*; +use qcow2::*; +use qcow2::io::*; + + +impl QCow2BDS { + fn allocate_cluster(cbds: &mut CBDS) -> Result + { + let (mut offset, cluster_size) =3D { + let_bds!(this, cbds); + (this.first_free_cluster_offset, this.cluster_size) + }; + + /* TODO: Optimize by scanning whole refblocks */ + while try!(Self::get_refcount(cbds, offset)) > 0 { + offset +=3D cluster_size as u64; + } + + try!(Self::change_refcount(cbds, offset, 1)); + + { + let_mut_bds!(this, cbds); + this.first_free_cluster_offset =3D offset + (cluster_size as u= 64); + } + + Ok(offset) + } + + + pub fn allocate_l2(cbds: &mut CBDS, mut hoi: HostOffsetInfo) + -> Result + { + let offset =3D try!(Self::allocate_cluster(cbds)); + + /* Zero the new table */ + { + let zero_data =3D qemu_blockalign(cbds, hoi.cluster_size as us= ize); + zero_byte_slice(zero_data); + + let res =3D hoi.file.bdrv_pwrite(offset, zero_data); + qemu_vfree(zero_data); + + if let Err(_) =3D res { + return Err(IOError::GenericError); + } + } + + hoi.l1_entry =3D L1Entry::Allocated(offset, true); + try!(Self::update_l1_entry(cbds, &hoi)); + + hoi.l2_entry =3D Some(L2Entry::Unallocated); + + Ok(hoi) + } + + + pub fn allocate_data_cluster(cbds: &mut CBDS, mut hoi: HostOffsetInfo) + -> Result + { + let offset =3D try!(Self::allocate_cluster(cbds)); + + hoi.l2_entry =3D Some(L2Entry::Normal(offset, true)); + hoi =3D try!(Self::update_l2_entry(cbds, hoi)); + + Ok(hoi) + } + + + pub fn free_cluster(cbds: &mut CBDS, l2e: L2Entry) -> Result<(), IOErr= or> + { + match l2e { + L2Entry::Unallocated =3D> Ok(()), + L2Entry::Zero(None, _) =3D> Ok(()), + + L2Entry::Normal(offset, _) + | L2Entry::Zero(Some(offset), _) + | L2Entry::Compressed(offset, _) =3D> { + { + let_mut_bds!(this, cbds); + if offset < this.first_free_cluster_offset { + this.first_free_cluster_offset =3D offset; + } + } + + Self::change_refcount(cbds, offset, -1) + }, + } + } + + + pub fn update_l1_entry(cbds: &mut CBDS, hoi: &HostOffsetInfo) + -> Result<(), IOError> + { + let (l1_offset, l1_size) =3D { + let_bds!(this, cbds); + (this.l1_offset, this.l1_table.len()) + }; + + assert!((hoi.l1_index as usize) < l1_size); + let entry_offset =3D l1_offset + (hoi.l1_index * 8) as u64; + let l1_entry_cpu =3D hoi.l1_entry.to_bits(); + let l1_entry =3D u64::to_be(l1_entry_cpu); + + if let Err(_) =3D hoi.file.bdrv_pwrite(entry_offset, + object_as_byte_slice(&l1_entr= y)) + { + return Err(IOError::GenericError); + } + + let_mut_bds!(this, cbds); + this.l1_table[hoi.l1_index as usize] =3D l1_entry_cpu; + + Ok(()) + } + + + /* hoi.l2_entry must be Some(_) */ + pub fn update_l2_entry(cbds: &mut CBDS, mut hoi: HostOffsetInfo) + -> Result + { + let (l2_offset, copied) =3D match hoi.l1_entry { + L1Entry::Unallocated =3D> panic!("L2 table must be allocated"), + L1Entry::Allocated(o, c) =3D> (o, c), + }; + + let l2_entry =3D u64::to_be(hoi.l2_entry.as_ref().unwrap() + .to_bits(hoi.compressed_shif= t)); + + if copied { + let entry_offset =3D l2_offset + (hoi.l2_index * 8) as u64; + if let Err(_) =3D hoi.file.bdrv_pwrite(entry_offset, + object_as_byte_slice(&l2_= entry)) + { + return Err(IOError::GenericError); + } + } else { + let table_data =3D qemu_blockalign(cbds, hoi.cluster_size as u= size); + + if let Err(_) =3D hoi.file.bdrv_pread(l2_offset, table_data) { + qemu_vfree(table_data); + return Err(IOError::GenericError); + } + + copy_into_byte_slice(table_data, (hoi.l2_index * 8) as usize, + object_as_byte_slice(&l2_entry)); + + let new_offset =3D try_vfree!(Self::allocate_cluster(cbds), + table_data); + let res =3D hoi.file.bdrv_pwrite(new_offset, table_data); + qemu_vfree(table_data); + + if let Err(_) =3D res { + return Err(IOError::GenericError); + } + + hoi.l1_entry =3D L1Entry::Allocated(new_offset, true); + try!(Self::update_l1_entry(cbds, &hoi)); + } + + Ok(hoi) + } +} diff --git a/block/rust/src/qcow2/io.rs b/block/rust/src/qcow2/io.rs index 069cc78303..36556282de 100644 --- a/block/rust/src/qcow2/io.rs +++ b/block/rust/src/qcow2/io.rs @@ -319,4 +319,121 @@ impl QCow2BDS { let hoi =3D try!(Self::find_host_offset(cbds, offset)); Self::do_read_cluster(cbds, &hoi, dest, flags) } + + + /* If necessary. copies a cluster somewhere else (freeing the original + * allocation), overlaying the data with the given slice (if given). */ + fn copy_and_overlay_cluster(cbds: &mut CBDS, mut hoi: HostOffsetInfo, + overlay_opt: Option<&[u8]>, flags: u32) + -> Result<(), IOError> + { + let free_original: L2Entry; + + let mut cluster_data =3D qemu_blockalign(cbds, hoi.cluster_size as= usize); + try_vfree!(Self::do_read_cluster(cbds, &hoi, cluster_data, 0), + cluster_data); + + match hoi.l2_entry { + None =3D> panic!("L2 entry expected"), + + Some(L2Entry::Zero(Some(offset), true)) =3D> { + /* We are going to use this cluster, so do not free it */ + free_original =3D L2Entry::Unallocated; + hoi.l2_entry =3D Some(L2Entry::Normal(offset, true)); + hoi =3D try_vfree!(Self::update_l2_entry(cbds, hoi), + cluster_data); + }, + + Some(unwrapped_entry) =3D> { + free_original =3D unwrapped_entry; + hoi.l2_entry =3D Some(L2Entry::Unallocated); + + hoi =3D try_vfree!(Self::allocate_data_cluster(cbds, hoi), + cluster_data); + } + } + + try_vfree!(Self::free_cluster(cbds, free_original), + cluster_data); + + if let Some(overlay) =3D overlay_opt { + copy_into_byte_slice(cluster_data, hoi.offset_in_cluster as us= ize, + overlay); + } + + /* TODO: Maybe on failure we should try pointing back to the origi= nal + * cluster? */ + hoi.offset_in_cluster =3D 0; + try_vfree!(Self::do_write_cluster(cbds, hoi, cluster_data, flags), + cluster_data); + + qemu_vfree(cluster_data); + + Ok(()) + } + + + fn do_write_cluster(cbds: &mut CBDS, mut hoi: HostOffsetInfo, src: &[u= 8], + flags: u32) + -> Result<(), IOError> + { + /* Try to set COPIED flag */ + match hoi.l2_entry { + Some(L2Entry::Normal(offset, false)) =3D> { + if try!(Self::get_refcount(cbds, offset)) =3D=3D 1 { + hoi.l2_entry =3D Some(L2Entry::Normal(offset, true)); + /* We are going to write directly into this cluster wi= thout + * updating the L2 table, so do it now */ + hoi =3D try!(Self::update_l2_entry(cbds, hoi)); + } + }, + + Some(L2Entry::Zero(Some(offset), false)) =3D> { + if try!(Self::get_refcount(cbds, offset)) =3D=3D 1 { + hoi.l2_entry =3D Some(L2Entry::Zero(Some(offset), true= )); + /* No need to update the L2 table entry now: We will c= all + * copy_and_overlay_cluster() and that will do the upd= ate */ + } + }, + + _ =3D> (), + } + + match hoi.l2_entry { + None =3D> { + hoi =3D try!(Self::allocate_l2(cbds, hoi)); + Self::do_write_cluster(cbds, hoi, src, flags) + }, + + Some(L2Entry::Normal(offset, true)) =3D> { + let full_offset =3D offset + (hoi.offset_in_cluster as u64= ); + if let Err(_) =3D hoi.file.bdrv_pwrite(full_offset, src) { + Err(IOError::GenericError) + } else { + Ok(()) + } + }, + + _ =3D> { + Self::copy_and_overlay_cluster(cbds, hoi, Some(src), flags) + }, + } + } + + + pub fn write_cluster(cbds: &mut CBDS, offset: u64, bytes: u32, + full_src_mnm: &mut MNMIOVSlice, flags: u32) + -> Result<(), IOError> + { + let src =3D match *full_src_mnm { + MNMIOVSlice::Mut(ref mut full_src) =3D> + full_src.split_at(bytes as usize).0, + + MNMIOVSlice::Const(ref mut full_src) =3D> + full_src.split_at(bytes as usize).0, + }; + + let hoi =3D try!(Self::find_host_offset(cbds, offset)); + Self::do_write_cluster(cbds, hoi, src, flags) + } } diff --git a/block/rust/src/qcow2/mod.rs b/block/rust/src/qcow2/mod.rs index 5fb523c93b..6cd413c5cb 100644 --- a/block/rust/src/qcow2/mod.rs +++ b/block/rust/src/qcow2/mod.rs @@ -1,4 +1,6 @@ +mod allocation; mod io; +mod refcount; mod on_disk_structures; =20 =20 @@ -264,8 +266,6 @@ impl BlockDriverOpen for QCow2BDS { this.common.set_file(Some(file)); } =20 - cbds.read_only =3D true; - QCow2BDS::do_open(cbds, options, flags) } } @@ -290,6 +290,18 @@ impl BlockDriverRead for QCow2BDS { } =20 =20 +impl BlockDriverWrite for QCow2BDS { + fn bdrv_co_pwritev(cbds: &mut CBDS, offset: u64, bytes: u64, + iov: Vec<&[u8]>, flags: u32) + -> Result<(), IOError> + { + /* TODO: Do not split */ + Self::split_io_to_clusters(cbds, offset, bytes, io::MNMIOV::Const(= iov), + flags, &Self::write_cluster) + } +} + + impl BlockDriverChildPerm for QCow2BDS { fn bdrv_child_perm(cbds: &mut CBDS, c: Option<&mut BdrvChild>, role: &c_structs::BdrvChildRole, perm: u64, shared:= u64) @@ -325,6 +337,7 @@ pub extern fn bdrv_qcow2_rust_init() bdrv.provides_open(); bdrv.provides_close(); bdrv.provides_read(); + bdrv.provides_write(); bdrv.provides_child_perm(); bdrv.provides_info(); =20 diff --git a/block/rust/src/qcow2/refcount.rs b/block/rust/src/qcow2/refcou= nt.rs new file mode 100644 index 0000000000..4f5e14a9f8 --- /dev/null +++ b/block/rust/src/qcow2/refcount.rs @@ -0,0 +1,96 @@ +use interface::*; +use qcow2::*; + + +impl QCow2BDS { + pub fn get_refcount(cbds: &mut CBDS, offset: u64) -> Result + { + let refblock_index; + let refcount_order; + let cluster_size; + let file; + + let reftable_entry =3D { + let_bds!(this, cbds); + + refcount_order =3D this.refcount_order; + cluster_size =3D this.cluster_size; + file =3D this.common.file(); + + let cluster_index =3D offset >> this.cluster_bits; + let reftable_index =3D offset >> this.reftable_bits; + + refblock_index =3D (cluster_index as u32) & (this.refblock_siz= e - 1); + + if reftable_index >=3D (this.reftable_size as u64) { + 0 + } else { + this.reftable[reftable_index as usize] + } + }; + + let refblock_offset =3D reftable_entry & REFT_OFFSET_MASK; + + if refblock_offset =3D=3D 0 { + return Ok(0); + } + + if (refblock_offset & ((cluster_size - 1) as u64)) !=3D 0 { + return Err(IOError::InvalidMetadata); + } + + assert!(refcount_order <=3D 6); + if refcount_order =3D=3D 6 { + let mut refcount: u64 =3D 0; + let byte_offset =3D (refblock_index * 8) as u64; + if let Err(_) =3D + file.bdrv_pread(refblock_offset + byte_offset, + object_as_mut_byte_slice(&mut refcount)) + { + return Err(IOError::GenericError); + } + Ok(u64::from_be(refcount)) + } else if refcount_order =3D=3D 5 { + let mut refcount: u32 =3D 0; + let byte_offset =3D (refblock_index * 4) as u64; + if let Err(_) =3D + file.bdrv_pread(refblock_offset + byte_offset, + object_as_mut_byte_slice(&mut refcount)) + { + return Err(IOError::GenericError); + } + Ok(u32::from_be(refcount) as u64) + } else if refcount_order =3D=3D 4 { + let mut refcount: u16 =3D 0; + let byte_offset =3D (refblock_index * 2) as u64; + if let Err(_) =3D + file.bdrv_pread(refblock_offset + byte_offset, + object_as_mut_byte_slice(&mut refcount)) + { + return Err(IOError::GenericError); + } + Ok(u16::from_be(refcount) as u64) + } else { + let mut refcount_byte: u8 =3D 0; + let byte_offset =3D (refblock_index >> (3 - refcount_order)) a= s u64; + if let Err(_) =3D + file.bdrv_pread(refblock_offset + byte_offset, + object_as_mut_byte_slice(&mut refcount_byt= e)) + { + return Err(IOError::GenericError); + } + + let mask =3D ((1u16 << (1u8 << refcount_order)) - 1) as u8; + let shift =3D (refblock_index << refcount_order) & 0x7; + + Ok(((refcount_byte >> shift) & mask) as u64) + } + } + + + pub fn change_refcount(_: &mut CBDS, _: u64, _: i8) + -> Result<(), IOError> + { + Err(IOError::UnsupportedImageFeature) + } +} --=20 2.12.2