qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH v3] qcow2: Rewrite qcow2_alloc_bytes()
@ 2015-02-06 14:39 Max Reitz
  2015-02-06 15:01 ` Kevin Wolf
  2015-02-06 15:27 ` Eric Blake
  0 siblings, 2 replies; 5+ messages in thread
From: Max Reitz @ 2015-02-06 14:39 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Stefan Hajnoczi, Max Reitz

qcow2_alloc_bytes() is a function with insufficient error handling and
an unnecessary goto. This patch rewrites it.

Signed-off-by: Max Reitz <mreitz@redhat.com>
---
v3:
- Use alloc_clusters_noref() and update_refcount() [Kevin]
- Only modify s->free_byte_offset if the function is successful; this is
  now necessary because update_refcount() is called unconditionally and
  thus, if it failed and alloc_clusters_noref() had been called and
  had returned a non-contiguous offset, s->free_byte_offset would point
  to an unallocated cluster's head, which is both wrong in itself and
  would also violate the assertion at the beginning of the function
---
 block/qcow2-refcount.c | 78 +++++++++++++++++++++++++-------------------------
 1 file changed, 39 insertions(+), 39 deletions(-)

diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c
index 9afdb40..9b80ca7 100644
--- a/block/qcow2-refcount.c
+++ b/block/qcow2-refcount.c
@@ -759,54 +759,54 @@ int qcow2_alloc_clusters_at(BlockDriverState *bs, uint64_t offset,
 int64_t qcow2_alloc_bytes(BlockDriverState *bs, int size)
 {
     BDRVQcowState *s = bs->opaque;
-    int64_t offset, cluster_offset;
-    int free_in_cluster;
+    int64_t offset;
+    size_t free_in_cluster;
+    int ret;
 
     BLKDBG_EVENT(bs->file, BLKDBG_CLUSTER_ALLOC_BYTES);
     assert(size > 0 && size <= s->cluster_size);
-    if (s->free_byte_offset == 0) {
-        offset = qcow2_alloc_clusters(bs, s->cluster_size);
-        if (offset < 0) {
-            return offset;
+    assert(!s->free_byte_offset || offset_into_cluster(s, s->free_byte_offset));
+
+    offset = s->free_byte_offset;
+
+    if (offset) {
+        int refcount = qcow2_get_refcount(bs, offset >> s->cluster_bits);
+        if (refcount < 0) {
+            return refcount;
         }
-        s->free_byte_offset = offset;
-    }
- redo:
-    free_in_cluster = s->cluster_size -
-        offset_into_cluster(s, s->free_byte_offset);
-    if (size <= free_in_cluster) {
-        /* enough space in current cluster */
-        offset = s->free_byte_offset;
-        s->free_byte_offset += size;
-        free_in_cluster -= size;
-        if (free_in_cluster == 0)
-            s->free_byte_offset = 0;
-        if (offset_into_cluster(s, offset) != 0)
-            qcow2_update_cluster_refcount(bs, offset >> s->cluster_bits, 1,
-                                          QCOW2_DISCARD_NEVER);
-    } else {
-        offset = qcow2_alloc_clusters(bs, s->cluster_size);
-        if (offset < 0) {
-            return offset;
+
+        if (refcount == 0xffff) {
+            offset = 0;
         }
-        cluster_offset = start_of_cluster(s, s->free_byte_offset);
-        if ((cluster_offset + s->cluster_size) == offset) {
-            /* we are lucky: contiguous data */
-            offset = s->free_byte_offset;
-            qcow2_update_cluster_refcount(bs, offset >> s->cluster_bits, 1,
-                                          QCOW2_DISCARD_NEVER);
-            s->free_byte_offset += size;
-        } else {
-            s->free_byte_offset = offset;
-            goto redo;
+    }
+
+    free_in_cluster = s->cluster_size - offset_into_cluster(s, offset);
+    if (!offset || free_in_cluster < size) {
+        int64_t new_cluster = alloc_clusters_noref(bs, s->cluster_size);
+        if (new_cluster < 0) {
+            return new_cluster;
+        }
+
+        if (!offset || ROUND_UP(offset, s->cluster_size) != new_cluster) {
+            offset = new_cluster;
         }
     }
 
-    /* The cluster refcount was incremented, either by qcow2_alloc_clusters()
-     * or explicitly by qcow2_update_cluster_refcount().  Refcount blocks must
-     * be flushed before the caller's L2 table updates.
-     */
+    assert(offset);
+    ret = update_refcount(bs, offset, size, 1, QCOW2_DISCARD_NEVER);
+    if (ret < 0) {
+        return ret;
+    }
+
+    /* The cluster refcount was incremented; refcount blocks must be flushed
+     * before the caller's L2 table updates. */
     qcow2_cache_set_dependency(bs, s->l2_table_cache, s->refcount_block_cache);
+
+    s->free_byte_offset = offset + size;
+    if (!offset_into_cluster(s, s->free_byte_offset)) {
+        s->free_byte_offset = 0;
+    }
+
     return offset;
 }
 
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [Qemu-devel] [PATCH v3] qcow2: Rewrite qcow2_alloc_bytes()
  2015-02-06 14:39 [Qemu-devel] [PATCH v3] qcow2: Rewrite qcow2_alloc_bytes() Max Reitz
@ 2015-02-06 15:01 ` Kevin Wolf
  2015-02-06 15:27 ` Eric Blake
  1 sibling, 0 replies; 5+ messages in thread
From: Kevin Wolf @ 2015-02-06 15:01 UTC (permalink / raw)
  To: Max Reitz; +Cc: qemu-devel, Stefan Hajnoczi

Am 06.02.2015 um 15:39 hat Max Reitz geschrieben:
> qcow2_alloc_bytes() is a function with insufficient error handling and
> an unnecessary goto. This patch rewrites it.
> 
> Signed-off-by: Max Reitz <mreitz@redhat.com>
> ---
> v3:
> - Use alloc_clusters_noref() and update_refcount() [Kevin]
> - Only modify s->free_byte_offset if the function is successful; this is
>   now necessary because update_refcount() is called unconditionally and
>   thus, if it failed and alloc_clusters_noref() had been called and
>   had returned a non-contiguous offset, s->free_byte_offset would point
>   to an unallocated cluster's head, which is both wrong in itself and
>   would also violate the assertion at the beginning of the function

Looks much nicer, I think. :-)

Thanks, applied to the block branch.

Kevin

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [Qemu-devel] [PATCH v3] qcow2: Rewrite qcow2_alloc_bytes()
  2015-02-06 14:39 [Qemu-devel] [PATCH v3] qcow2: Rewrite qcow2_alloc_bytes() Max Reitz
  2015-02-06 15:01 ` Kevin Wolf
@ 2015-02-06 15:27 ` Eric Blake
  2015-02-06 15:31   ` Eric Blake
  1 sibling, 1 reply; 5+ messages in thread
From: Eric Blake @ 2015-02-06 15:27 UTC (permalink / raw)
  To: Max Reitz, qemu-devel; +Cc: Kevin Wolf, Stefan Hajnoczi

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

On 02/06/2015 07:39 AM, Max Reitz wrote:
> qcow2_alloc_bytes() is a function with insufficient error handling and
> an unnecessary goto. This patch rewrites it.
> 
> Signed-off-by: Max Reitz <mreitz@redhat.com>
> ---
> v3:
> - Use alloc_clusters_noref() and update_refcount() [Kevin]

Ouch.  Not done quite right.  Kevin, you may want to remove this from
your staging area, while Max works on a v4.


> +
> +    free_in_cluster = s->cluster_size - offset_into_cluster(s, offset);
> +    if (!offset || free_in_cluster < size) {

Let's consider all four possibilities:

Case 1: !offset
  we won't be modifying any existing clusters. When we are done, the new
cluster needs a refcount of 1

Case 2: free_in_cluster >= size
  we will only be modifying an existing cluster, assume it starts with
refcount of 1. When we are done, it needs a refcount of 2

Case 3: free_in_cluster < size, allocation is not contiguous
  we won't be modifying any existing clusters. When we are done, the new
cluster needs a refcount of 1

Case 4: free_in_cluster < size, allocation is contiguous
  we will be placing data into both an existing cluster and a new one;
assume the existing cluster starts with a refcount of 1. When we are
done, the old cluster needs a refcount of 2, and the new cluster needs a
refcount of 1

> +        int64_t new_cluster = alloc_clusters_noref(bs, s->cluster_size);

This says the new cluster has a refcount of 0.

> +        if (new_cluster < 0) {
> +            return new_cluster;
> +        }
> +
> +        if (!offset || ROUND_UP(offset, s->cluster_size) != new_cluster) {
> +            offset = new_cluster;
>          }
>      }
>  
> -    /* The cluster refcount was incremented, either by qcow2_alloc_clusters()
> -     * or explicitly by qcow2_update_cluster_refcount().  Refcount blocks must
> -     * be flushed before the caller's L2 table updates.
> -     */
> +    assert(offset);
> +    ret = update_refcount(bs, offset, size, 1, QCOW2_DISCARD_NEVER);

Case 1: This incremented the new cluster. Good
Case 2: This incremented the old cluster. Good
Case 3: This incremented the new cluster. Good
Case 4: This incremented the old cluster. But the new cluster remains at
refcount 0.  BAD.

v2 got it right, because it always put the NEW cluster at refcount 1 (if
there was a new cluster), then incremented the old cluster when needed.

> +    if (ret < 0) {
> +        return ret;
> +    }
> +
> +    /* The cluster refcount was incremented; refcount blocks must be flushed
> +     * before the caller's L2 table updates. */
>      qcow2_cache_set_dependency(bs, s->l2_table_cache, s->refcount_block_cache);
> +
> +    s->free_byte_offset = offset + size;
> +    if (!offset_into_cluster(s, s->free_byte_offset)) {
> +        s->free_byte_offset = 0;
> +    }
> +
>      return offset;
>  }
>  
> 

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [Qemu-devel] [PATCH v3] qcow2: Rewrite qcow2_alloc_bytes()
  2015-02-06 15:27 ` Eric Blake
@ 2015-02-06 15:31   ` Eric Blake
  2015-02-06 16:22     ` Kevin Wolf
  0 siblings, 1 reply; 5+ messages in thread
From: Eric Blake @ 2015-02-06 15:31 UTC (permalink / raw)
  To: Max Reitz, qemu-devel; +Cc: Kevin Wolf, Stefan Hajnoczi

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

On 02/06/2015 08:27 AM, Eric Blake wrote:

>>  
>> -    /* The cluster refcount was incremented, either by qcow2_alloc_clusters()
>> -     * or explicitly by qcow2_update_cluster_refcount().  Refcount blocks must
>> -     * be flushed before the caller's L2 table updates.
>> -     */
>> +    assert(offset);
>> +    ret = update_refcount(bs, offset, size, 1, QCOW2_DISCARD_NEVER);
> 
> Case 1: This incremented the new cluster. Good
> Case 2: This incremented the old cluster. Good
> Case 3: This incremented the new cluster. Good
> Case 4: This incremented the old cluster. But the new cluster remains at
> refcount 0.  BAD.

Wait. Maybe I'm confused.  You are requesting an update_refcount()
across size bytes, and given the offset, that means that the code will
round up to cover BOTH clusters in one call.  Does update_refcount()
properly increment from  [ 1, 0 ] to [ 2, 1 ] when given a 2-cluster
size (when offset, size is rounded up to cluster boundaries)?  If so,
then there is no bug after all.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [Qemu-devel] [PATCH v3] qcow2: Rewrite qcow2_alloc_bytes()
  2015-02-06 15:31   ` Eric Blake
@ 2015-02-06 16:22     ` Kevin Wolf
  0 siblings, 0 replies; 5+ messages in thread
From: Kevin Wolf @ 2015-02-06 16:22 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, Stefan Hajnoczi, Max Reitz

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

Am 06.02.2015 um 16:31 hat Eric Blake geschrieben:
> On 02/06/2015 08:27 AM, Eric Blake wrote:
> 
> >>  
> >> -    /* The cluster refcount was incremented, either by qcow2_alloc_clusters()
> >> -     * or explicitly by qcow2_update_cluster_refcount().  Refcount blocks must
> >> -     * be flushed before the caller's L2 table updates.
> >> -     */
> >> +    assert(offset);
> >> +    ret = update_refcount(bs, offset, size, 1, QCOW2_DISCARD_NEVER);
> > 
> > Case 1: This incremented the new cluster. Good
> > Case 2: This incremented the old cluster. Good
> > Case 3: This incremented the new cluster. Good
> > Case 4: This incremented the old cluster. But the new cluster remains at
> > refcount 0.  BAD.
> 
> Wait. Maybe I'm confused.  You are requesting an update_refcount()
> across size bytes, and given the offset, that means that the code will
> round up to cover BOTH clusters in one call.  Does update_refcount()
> properly increment from  [ 1, 0 ] to [ 2, 1 ] when given a 2-cluster
> size (when offset, size is rounded up to cluster boundaries)?  If so,
> then there is no bug after all.

Yes, this is what it should be doing. Letting one update_refcount() call
(more or less) atomically increment both refcounts was the whole point
of my suggestion anyway because that is what enables the simplification.

Kevin

[-- Attachment #2: Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2015-02-06 16:22 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-02-06 14:39 [Qemu-devel] [PATCH v3] qcow2: Rewrite qcow2_alloc_bytes() Max Reitz
2015-02-06 15:01 ` Kevin Wolf
2015-02-06 15:27 ` Eric Blake
2015-02-06 15:31   ` Eric Blake
2015-02-06 16:22     ` Kevin Wolf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).