qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
To: Mike Maslenkin <mike.maslenkin@gmail.com>
Cc: qemu-devel@nongnu.org, qemu-block@nongnu.org, den@virtuozzo.com,
	stefanha@redhat.com, vsementsov@yandex-team.ru, kwolf@redhat.com,
	hreitz@redhat.com
Subject: Re: [PATCH 15/19] parallels: Remove unnecessary data_end field
Date: Sun, 8 Oct 2023 15:54:31 +0200	[thread overview]
Message-ID: <a678d4ce-b98b-4ea9-89e9-7c30ee1e2017@virtuozzo.com> (raw)
In-Reply-To: <CAL77WPC4ae+YzxBwNg9jLn93FUAuC8-b3vwzge7ktYBm62_1SQ@mail.gmail.com>



On 10/7/23 19:54, Mike Maslenkin wrote:
> On Sat, Oct 7, 2023 at 5:30 PM Alexander Ivanov
> <alexander.ivanov@virtuozzo.com> wrote:
>>
>>
>> On 10/7/23 13:21, Mike Maslenkin wrote:
>>> On Sat, Oct 7, 2023 at 1:18 PM Alexander Ivanov
>>> <alexander.ivanov@virtuozzo.com>  wrote:
>>>> On 10/6/23 21:43, Mike Maslenkin wrote:
>>>>> On Mon, Oct 2, 2023 at 12:01 PM Alexander Ivanov
>>>>> <alexander.ivanov@virtuozzo.com>  wrote:
>>>>>> Since we have used bitmap, field data_end in BDRVParallelsState is
>>>>>> redundant and can be removed.
>>>>>>
>>>>>> Add parallels_data_end() helper and remove data_end handling.
>>>>>>
>>>>>> Signed-off-by: Alexander Ivanov<alexander.ivanov@virtuozzo.com>
>>>>>> ---
>>>>>>     block/parallels.c | 33 +++++++++++++--------------------
>>>>>>     block/parallels.h |  1 -
>>>>>>     2 files changed, 13 insertions(+), 21 deletions(-)
>>>>>>
>>>>>> diff --git a/block/parallels.c b/block/parallels.c
>>>>>> index 48ea5b3f03..80a7171b84 100644
>>>>>> --- a/block/parallels.c
>>>>>> +++ b/block/parallels.c
>>>>>> @@ -265,6 +265,13 @@ static void parallels_free_used_bitmap(BlockDriverState *bs)
>>>>>>         g_free(s->used_bmap);
>>>>>>     }
>>>>>>
>>>>>> +static int64_t parallels_data_end(BDRVParallelsState *s)
>>>>>> +{
>>>>>> +    int64_t data_end = s->data_start * BDRV_SECTOR_SIZE;
>>>>>> +    data_end += s->used_bmap_size * s->cluster_size;
>>>>>> +    return data_end;
>>>>>> +}
>>>>>> +
>>>>>>     int64_t parallels_allocate_host_clusters(BlockDriverState *bs,
>>>>>>                                              int64_t *clusters)
>>>>>>     {
>>>>>> @@ -275,7 +282,7 @@ int64_t parallels_allocate_host_clusters(BlockDriverState *bs,
>>>>>>
>>>>>>         first_free = find_first_zero_bit(s->used_bmap, s->used_bmap_size);
>>>>>>         if (first_free == s->used_bmap_size) {
>>>>>> -        host_off = s->data_end * BDRV_SECTOR_SIZE;
>>>>>> +        host_off = parallels_data_end(s);
>>>>>>             prealloc_clusters = *clusters + s->prealloc_size / s->tracks;
>>>>>>             bytes = prealloc_clusters * s->cluster_size;
>>>>>>
>>>>>> @@ -297,9 +304,6 @@ int64_t parallels_allocate_host_clusters(BlockDriverState *bs,
>>>>>>             s->used_bmap = bitmap_zero_extend(s->used_bmap, s->used_bmap_size,
>>>>>>                                               new_usedsize);
>>>>>>             s->used_bmap_size = new_usedsize;
>>>>>> -        if (host_off + bytes > s->data_end * BDRV_SECTOR_SIZE) {
>>>>>> -            s->data_end = (host_off + bytes) / BDRV_SECTOR_SIZE;
>>>>>> -        }
>>>>>>         } else {
>>>>>>             next_used = find_next_bit(s->used_bmap, s->used_bmap_size, first_free);
>>>>>>
>>>>>> @@ -315,8 +319,7 @@ int64_t parallels_allocate_host_clusters(BlockDriverState *bs,
>>>>>>              * branch. In the other case we are likely re-using hole. Preallocate
>>>>>>              * the space if required by the prealloc_mode.
>>>>>>              */
>>>>>> -        if (s->prealloc_mode == PRL_PREALLOC_MODE_FALLOCATE &&
>>>>>> -                host_off < s->data_end * BDRV_SECTOR_SIZE) {
>>>>>> +        if (s->prealloc_mode == PRL_PREALLOC_MODE_FALLOCATE) {
>>>>>>                 ret = bdrv_pwrite_zeroes(bs->file, host_off, bytes, 0);
>>>>>>                 if (ret < 0) {
>>>>>>                     return ret;
>>>>>> @@ -757,13 +760,7 @@ parallels_check_outside_image(BlockDriverState *bs, BdrvCheckResult *res,
>>>>>>             }
>>>>>>         }
>>>>>>
>>>>>> -    if (high_off == 0) {
>>>>>> -        res->image_end_offset = s->data_end << BDRV_SECTOR_BITS;
>>>>>> -    } else {
>>>>>> -        res->image_end_offset = high_off + s->cluster_size;
>>>>>> -        s->data_end = res->image_end_offset >> BDRV_SECTOR_BITS;
>>>>>> -    }
>>>>>> -
>>>>>> +    res->image_end_offset = parallels_data_end(s);
>>>>>>         return 0;
>>>>>>     }
>>>>>>
>>>>>> @@ -806,7 +803,6 @@ parallels_check_leak(BlockDriverState *bs, BdrvCheckResult *res,
>>>>>>                 res->check_errors++;
>>>>>>                 return ret;
>>>>>>             }
>>>>>> -        s->data_end = res->image_end_offset >> BDRV_SECTOR_BITS;
>>>>>>
>>>>>>             parallels_free_used_bitmap(bs);
>>>>>>             ret = parallels_fill_used_bitmap(bs);
>>>>>> @@ -1361,8 +1357,7 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
>>>>>>         }
>>>>>>
>>>>>>         s->data_start = data_start;
>>>>>> -    s->data_end = s->data_start;
>>>>>> -    if (s->data_end < (s->header_size >> BDRV_SECTOR_BITS)) {
>>>>>> +    if (s->data_start < (s->header_size >> BDRV_SECTOR_BITS)) {
>>>>>>             /*
>>>>>>              * There is not enough unused space to fit to block align between BAT
>>>>>>              * and actual data. We can't avoid read-modify-write...
>>>>>> @@ -1403,11 +1398,10 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
>>>>>>
>>>>>>         for (i = 0; i < s->bat_size; i++) {
>>>>>>             sector = bat2sect(s, i);
>>>>>> -        if (sector + s->tracks > s->data_end) {
>>>>>> -            s->data_end = sector + s->tracks;
>>>>>> +        if (sector + s->tracks > file_nb_sectors) {
>>>>>> +            need_check = true;
>>>>>>             }
>>>>>>         }
>>>>>> -    need_check = need_check || s->data_end > file_nb_sectors;
>>>>>>
>>>>>>         ret = parallels_fill_used_bitmap(bs);
>>>>>>         if (ret == -ENOMEM) {
>>>>>> @@ -1461,7 +1455,6 @@ static int parallels_truncate_unused_clusters(BlockDriverState *bs)
>>>>>>             end_off = (end_off + 1) * s->cluster_size;
>>>>>>         }
>>>>>>         end_off += s->data_start * BDRV_SECTOR_SIZE;
>>>>>> -    s->data_end = end_off / BDRV_SECTOR_SIZE;
>>>>>>         return bdrv_truncate(bs->file, end_off, true, PREALLOC_MODE_OFF, 0, NULL);
>>>>>>     }
>>>>>>
>>>>>> diff --git a/block/parallels.h b/block/parallels.h
>>>>>> index 18b4f8068e..a6a048d890 100644
>>>>>> --- a/block/parallels.h
>>>>>> +++ b/block/parallels.h
>>>>>> @@ -79,7 +79,6 @@ typedef struct BDRVParallelsState {
>>>>>>         unsigned int bat_size;
>>>>>>
>>>>>>         int64_t  data_start;
>>>>>> -    int64_t  data_end;
>>>>>>         uint64_t prealloc_size;
>>>>>>         ParallelsPreallocMode prealloc_mode;
>>>>>>
>>>>>> --
>>>>>> 2.34.1
>>>>>>
>>>>> Is it intended behavior?
>>>>>
>>>>> Run:
>>>>> 1. ./qemu-img create -f parallels $TEST_IMG 1T
>>>>> 2. dd if=/dev/zero of=$TEST_IMG oseek=12  bs=1M count=128 conv=notrunc
>>>>> 3. ./qemu-img check  $TEST_IMG
>>>>>           No errors were found on the image.
>>>>>           Image end offset: 150994944
>>>>>
>>>>> Without this patch `qemu-img check` reports:
>>>>>           ERROR space leaked at the end of the image 145752064
>>>>>
>>>>>          139 leaked clusters were found on the image.
>>>>>          This means waste of disk space, but no harm to data.
>>>>>          Image end offset: 5242880
>>>> The original intention is do nothing at this point if an image is opened as
>>>> RO. In the next patch parallels_check_leak() was rewritten to detect
>>>> unused clusters at the image end.
>>>>
>>>> But there is a bug: (end_off == s->used_bmap_size) case is handled in an
>>>> incorrect way. Will fix it, thank you.
>>>>> Note: there is another issue caused by previous commits exists.
>>>>> g_free asserts from parallels_free_used_bitmap() because of
>>>>> s->used_bmap is NULL.
>>>> Maybe I don't understand your point, but if you meant that g_free() could be
>>>> called with NULL argument, it's not a problem. GLib Manual says:
>>>>
>>>>       void g_free (/|gpointer
>>>>       <https://www.manpagez.com/html/glib/glib-2.56.0/glib-Basic-Types.php#gpointer>
>>>>       mem|/);
>>>>
>>>>       If /|mem|/ is|NULL|
>>>>       <https://www.manpagez.com/html/glib/glib-2.56.0/glib-Standard-Macros.php#NULL:CAPS>
>>>>       it simply returns, so there is no need to check /|mem|/ against
>>>>       |NULL|
>>>>       <https://www.manpagez.com/html/glib/glib-2.56.0/glib-Standard-Macros.php#NULL:CAPS>
>>>>       before calling this function.
>>>>
>>>>> To reproduce this crash at revision before or without patch 15/19, run commands:
>>>>> 1. ./qemu-img create -f parallels $TEST_IMG 1T
>>>>> 2. dd if=/dev/zero of=$TEST_IMG oseek=12  bs=1M count=128 conv=notrunc
>>>>> 3. ./qemu-img check -r leaks $TEST_IMG
>>>> Sorry, but I couldn't reproduce it. Reset to 14/19, made the three steps
>>>> and had such output:
>>>>
>>>>       $ ./qemu-img create -f parallels $TEST_IMG 1T
>>>>       Formatting 'test.img', fmt=parallels size=1099511627776
>>>>       cluster_size=1048576
>>>>
>>>>       $ dd if=/dev/zero of=$TEST_IMG seek=12  bs=1M count=128 conv=notrunc
>>>>       128+0 records in
>>>>       128+0 records out
>>>>       134217728 bytes (134 MB, 128 MiB) copied, 0.0797576 s, 1.7 GB/s
>>>>
>>>>       $ ./qemu-img check -r leaks $TEST_IMG
>>>>       Repairing space leaked at the end of the image 141557760
>>>>       The following inconsistencies were found and repaired:
>>>>
>>>>       135 leaked clusters
>>>>       0 corruptions
>>>>
>>>>       Double checking the fixed image now...
>>>>       No errors were found on the image.
>>>>       Image end offset: 5242880
>>> My comment regarding patch 15 is about 'check' operation is not able
>>> to detect leaked data anymore.
>>> So, after this patch I see:
>>>
>>> $ ./qemu-img info   leak.bin
>>> image: leak.bin
>>> file format: parallels
>>> virtual size: 1 TiB (1099511627776 bytes)
>>> disk size: 145 MiB
>>> Child node '/file':
>>>       filename: leak.bin
>>>       protocol type: file
>>>       file length: 146 MiB (153092096 bytes)
>>>       disk size: 145 MiB
>>>
>>> $ ./qemu-img check -r leaks leak.bin
>>> No errors were found on the image.
>>> Image end offset: 153092096
>>>
>>> After reverting this patch  'check' reports about:
>>> ERROR space leaked at the end of the image 148897792
>>>
>>> So, after reverting patch 15 I tried to repair such image
>>> and got abort trap.
>> Yes, I understand this part. OK, I think, I could place 16 patch before
>> 15 and
>> leaks would handle in the correct way at any point of the patch sequence.
>>> I rechecked with downloaded patches, rebuild from scratch and can tell
>>> that there is no abort on master branch, but it appears after applying
>>> patches[1-9].
>> Maybe I do something wrong, but I reset to the top of mainstream, applied
>> 1-9 patches, rebuilt QEMU and didn't see any abort.
>>
>>> Obviously It can be debugged and the reason is that
>>> parallels_fill_used_bitmap() returns after
>>>
>>>    s->used_bmap_size = DIV_ROUND_UP(payload_bytes, s->cluster_size);
>>>       if (s->used_bmap_size == 0) {
>>>           return 0;
>>>       }
>>>
>>> Because DIV_ROUND_UP(payload_bytes, s->cluster_size); gives a 0;
>>>
>>> So subsequent parallels_free_used_bitmap() called from
>>> parallels_close() causes an assert.
>>>
>>> So, the first invocation of parallels_free_used_bitmap is:
>>>     * frame #0: 0x0000000100091830 qemu-img`parallels_check_leak
>>> [inlined] parallels_free_used_bitmap(bs=0x0000000101011600) at
>>> parallels.c:263:33 [opt]
>>>       frame #1: 0x0000000100091830
>>> qemu-img`parallels_check_leak(bs=0x0000000101011600,
>>> res=0x000000016fdff5d8, fix=BDRV_FIX_LEAKS, explicit=true) at
>>> parallels.c:811:9 [opt]
>>>       frame #2: 0x0000000100090d80
>>> qemu-img`parallels_co_check(bs=0x0000000101011600,
>>> res=0x000000016fdff5d8, fix=BDRV_FIX_LEAKS) at parallels.c:1014:15
>>> [opt]
>>>       frame #3: 0x0000000100014f6c
>>> qemu-img`bdrv_co_check_entry(opaque=0x000000016fdff560) at
>>> block-gen.c:556:14 [opt]
>>>
>>> And the second generates abort from there:
>>>     * frame #0: 0x000000010008fef8 qemu-img`parallels_close [inlined]
>>> parallels_free_used_bitmap(bs=<unavailable>) at parallels.c:263:33
>> In this line we have:
>>
>>      BDRVParallelsState *s = bs->opaque;
>>
>> So there is only one possibility to abort - incorrect bs. I don't know
>> how it
>> could be possible.
>>
>>> [opt]
>>>       frame #1: 0x000000010008fef8
>>> qemu-img`parallels_close(bs=0x0000000101011600) at parallels.c:1501:5
>>> [opt]
>>>       frame #2: 0x0000000100019d3c qemu-img`bdrv_unref [inlined]
>>> bdrv_close(bs=0x0000000101011600) at block.c:5164:13 [opt]
>>>
>>> After the first parallels_free_used_bitmap(), there is an actual image
>>> truncation happens, so there is no payload at the moment of the next
>>> parallels_fill_used_bitmap(),
>>>
>>> PS: there are a chances that some patches were not applied clearly,
>>> I'll recheck this later.
>> I just reset to the mainstream top and apply 1-9 patches:
>>
>>      $ git reset --hard 2f3913f4b2ad74baeb5a6f1d36efbd9ecdf1057d
>>      HEAD is now at 2f3913f4b2 Merge tag 'for_upstream' of
>>      https://git.kernel.org/pub/scm/virt/kvm/mst/qemu into staging
>>      $ git am *.eml
>>      Applying: parallels: Move inactivation code to a separate function
>>      Applying: parallels: Add mark_unused() helper
>>      Applying: parallels: Move host clusters allocation to a separate
>>      function
>>      Applying: parallels: Set data_end value in parallels_check_leak()
>>      Applying: parallels: Recreate used bitmap in parallels_check_leak()
>>      Applying: parallels: Add a note about used bitmap in
>>      parallels_check_duplicate()
>>      Applying: parallels: Create used bitmap even if checks needed
>>      Applying: parallels: Make mark_used() and mark_unused() global functions
>>      Applying: parallels: Add dirty bitmaps saving
>>
>>> It would be nice if it was possible to fetch changes from some repo,
>>> rather than extracting  it from gmail.
>> You can fetch it here (branch "parallels") -
>> https://github.com/AlexanderIvanov-Virtuozzo/qemu.git
>>> Regards,
>>> Mike.
>> --
>> Best regards,
>> Alexander Ivanov
>>
> Thanks for the link. I've fetched your repo and reverted "parallels:
> Remove unnecessary data_end field" as it hides reproduction,
> because it makes 'check' blind for the case we are discussing.
> So the situation is the same:
> 1. parallels_open calls parallels_fill_used_bitmap(). A this time file
> size is 145M (i.e leaked clusters are there) and s->used_bmap_size =
> 139.
> 2  Then parallels_co_check()->parallels_check_leak () is invoked.
>       At the first parallels_check_leak calls
> bdrv_co_truncate(offset=5242880), that is true as we have only empty
> BAT on the image.
>       At this step image truncated to 5M i.e. contains only empty BAT.
>       So, on line 809 s->data_end = 10240 i.e 5M (10240<<9)
>        809:         s->data_end = res->image_end_offset >> BDRV_SECTOR_BITS;
>
>        811:        parallels_free_used_bitmap(bs);
>        812:        ret = parallels_fill_used_bitmap(bs);
>
> Line 811 invalidates used_bmap and sets used_bmap_size to 0.
> parallels_fill_used_bitmap Invoked on line 812  returns 0, because
> payload_bytes = 0 (current file size 5M - s->data_start *
> BDRV_SECTOR_SIZE ),
> and s->used_bmap_size is NOT initialized.
>
> 3. parallels_close() invoked to finish work and exit process.
>     parallels_close() calls  parallels_free_used_bitmap() unconditionally.
>
> static void parallels_free_used_bitmap(BlockDriverState *bs)
> {
>      BDRVParallelsState *s = bs->opaque;
>      s->used_bmap_size = 0;
> ASSERT IS HERE >>>>  g_free(s->used_bmap);
Oh, now I see, s->used_bmap is not reset to NULL in 
parallels_free_used_bitmap().
That's why I could not reproduce the bug - on my system by a chance 
s->used_bmap
contained NULL.

Maybe it would be better to put NULL to s->used_bmap in
parallels_free_used_bitmap() and let g_free() handle NULL argument 
properly?
> }
>
> The fix is trivial...
>
> if (s->used_bmap_size) {
>     g_free(s->used_bmap);
>     s->used_bmap_size = 0;
> }
>
> PS: I retuned to your HEAD. Killed gdb thus made image marked is
> incorrectly closed.
> But 'qemu-img check' only removed  incorrectly closed flags and didn't
> remove leaked clusters.
Will work on it.
>
> Regards,
> Mike.

-- 
Best regards,
Alexander Ivanov



  reply	other threads:[~2023-10-08 13:55 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-10-02  8:57 [PATCH 00/19] parallels: Add full dirty bitmap support Alexander Ivanov
2023-10-02  8:57 ` [PATCH 01/19] parallels: Move inactivation code to a separate function Alexander Ivanov
2023-10-02  8:57 ` [PATCH 02/19] parallels: Add mark_unused() helper Alexander Ivanov
2023-10-02  8:57 ` [PATCH 03/19] parallels: Move host clusters allocation to a separate function Alexander Ivanov
2023-10-02  8:57 ` [PATCH 04/19] parallels: Set data_end value in parallels_check_leak() Alexander Ivanov
2023-10-02  8:57 ` [PATCH 05/19] parallels: Recreate used bitmap " Alexander Ivanov
2023-10-02  8:57 ` [PATCH 06/19] parallels: Add a note about used bitmap in parallels_check_duplicate() Alexander Ivanov
2023-10-02  8:57 ` [PATCH 07/19] parallels: Create used bitmap even if checks needed Alexander Ivanov
2023-10-02  8:57 ` [PATCH 08/19] parallels: Make mark_used() and mark_unused() global functions Alexander Ivanov
2023-10-02  8:57 ` [PATCH 09/19] parallels: Add dirty bitmaps saving Alexander Ivanov
2023-10-02  8:57 ` [PATCH 10/19] parallels: Let image extensions work in RW mode Alexander Ivanov
2023-10-02  8:57 ` [PATCH 11/19] parallels: Handle L1 entries equal to one Alexander Ivanov
2023-10-02  8:57 ` [PATCH 12/19] parallels: Make a loaded dirty bitmap persistent Alexander Ivanov
2023-10-02  8:57 ` [PATCH 13/19] parallels: Reverse a conditional in parallels_check_leak() to reduce indents Alexander Ivanov
2023-10-02  8:57 ` [PATCH 14/19] parallels: Truncate images on the last used cluster Alexander Ivanov
2023-10-02  8:57 ` [PATCH 15/19] parallels: Remove unnecessary data_end field Alexander Ivanov
2023-10-06 19:43   ` Mike Maslenkin
2023-10-07 10:18     ` Alexander Ivanov
2023-10-07 11:21       ` Mike Maslenkin
2023-10-07 14:30         ` Alexander Ivanov
2023-10-07 17:54           ` Mike Maslenkin
2023-10-08 13:54             ` Alexander Ivanov [this message]
2023-10-02  8:57 ` [PATCH 16/19] parallels: Check unused clusters in parallels_check_leak() Alexander Ivanov
2023-10-02  8:57 ` [PATCH 17/19] tests: Add parallels images support to test 165 Alexander Ivanov
2023-10-02  8:57 ` [PATCH 18/19] tests: Turned on 256, 299, 304 and block-status-cache for parallels format Alexander Ivanov
2023-10-02  8:57 ` [PATCH 19/19] tests: Add parallels format support to image-fleecing Alexander Ivanov

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=a678d4ce-b98b-4ea9-89e9-7c30ee1e2017@virtuozzo.com \
    --to=alexander.ivanov@virtuozzo.com \
    --cc=den@virtuozzo.com \
    --cc=hreitz@redhat.com \
    --cc=kwolf@redhat.com \
    --cc=mike.maslenkin@gmail.com \
    --cc=qemu-block@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    --cc=stefanha@redhat.com \
    --cc=vsementsov@yandex-team.ru \
    /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 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).