Benoît Canet <benoit.ca...@nodalink.com> writes: > On Tue, Sep 16, 2014 at 08:12:27PM +0200, Markus Armbruster wrote: >> Move device model attachment / detachment and the BlockDevOps device >> model callbacks and their wrappers from BlockDriverState to >> BlockBackend. >> >> Signed-off-by: Markus Armbruster <arm...@redhat.com> >> --- >> block.c | 126 ++++------------------------------ >> block/block-backend.c | 151 >> ++++++++++++++++++++++++++++++++++++++--- >> block/qapi.c | 8 +-- >> blockdev.c | 8 +-- >> include/block/block.h | 45 ------------ >> include/block/block_int.h | 12 ++-- >> include/sysemu/block-backend.h | 35 ++++++++++ >> 7 files changed, 203 insertions(+), 182 deletions(-) >> >> diff --git a/block.c b/block.c >> index 1d9a680..fac1211 100644 >> --- a/block.c >> +++ b/block.c >> @@ -58,9 +58,6 @@ struct BdrvDirtyBitmap { >> >> #define NOT_DONE 0x7fffffff /* used while emulated sync operation in >> progress */ >> >> -#define COROUTINE_POOL_RESERVATION 64 /* number of coroutines to reserve */ >> - >> -static void bdrv_dev_change_media_cb(BlockDriverState *bs, bool load); >> static BlockAIOCB *bdrv_aio_readv_em(BlockDriverState *bs, >> int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, >> BlockCompletionFunc *cb, void *opaque); >> @@ -1527,7 +1524,9 @@ int bdrv_open(BlockDriverState **pbs, const char >> *filename, >> } >> >> if (!bdrv_key_required(bs)) { >> - bdrv_dev_change_media_cb(bs, true); >> + if (bs->blk) { >> + blk_dev_change_media_cb(bs->blk, true); >> + } >> } else if (!runstate_check(RUN_STATE_PRELAUNCH) >> && !runstate_check(RUN_STATE_INMIGRATE) >> && !runstate_check(RUN_STATE_PAUSED)) { /* HACK */ >> @@ -1852,7 +1851,9 @@ void bdrv_close(BlockDriverState *bs) >> } >> } >> >> - bdrv_dev_change_media_cb(bs, false); >> + if (bs->blk) { >> + blk_dev_change_media_cb(bs->blk, false); >> + } >> >> /*throttling disk I/O limits*/ >> if (bs->io_limits_enabled) { >> @@ -1971,9 +1972,6 @@ static void bdrv_move_feature_fields(BlockDriverState >> *bs_dest, >> /* move some fields that need to stay attached to the device */ >> >> /* dev info */ >> - bs_dest->dev_ops = bs_src->dev_ops; >> - bs_dest->dev_opaque = bs_src->dev_opaque; >> - bs_dest->dev = bs_src->dev; >> bs_dest->guest_block_size = bs_src->guest_block_size; >> bs_dest->copy_on_read = bs_src->copy_on_read; >> >> @@ -2041,7 +2039,6 @@ void bdrv_swap(BlockDriverState *bs_new, >> BlockDriverState *bs_old) >> assert(!bs_new->blk); >> assert(QLIST_EMPTY(&bs_new->dirty_bitmaps)); >> assert(bs_new->job == NULL); >> - assert(bs_new->dev == NULL); >> assert(bs_new->io_limits_enabled == false); >> assert(!throttle_have_timer(&bs_new->throttle_state)); >> >> @@ -2058,7 +2055,6 @@ void bdrv_swap(BlockDriverState *bs_new, >> BlockDriverState *bs_old) >> assert(!bs_new->blk); >> >> /* Check a few fields that should remain attached to the device */ >> - assert(bs_new->dev == NULL); >> assert(bs_new->job == NULL); >> assert(bs_new->io_limits_enabled == false); >> assert(!throttle_have_timer(&bs_new->throttle_state)); >> @@ -2097,7 +2093,6 @@ void bdrv_append(BlockDriverState *bs_new, >> BlockDriverState *bs_top) >> >> static void bdrv_delete(BlockDriverState *bs) >> { >> - assert(!bs->dev); >> assert(!bs->job); >> assert(bdrv_op_blocker_is_empty(bs)); >> assert(!bs->refcnt); >> @@ -2111,105 +2106,6 @@ static void bdrv_delete(BlockDriverState *bs) >> g_free(bs); >> } >> >> -int bdrv_attach_dev(BlockDriverState *bs, void *dev) >> -/* TODO change to DeviceState *dev when all users are qdevified */ >> -{ >> - if (bs->dev) { >> - return -EBUSY; >> - } >> - bs->dev = dev; >> - bdrv_iostatus_reset(bs); >> - >> - /* We're expecting I/O from the device so bump up coroutine pool size */ >> - qemu_coroutine_adjust_pool_size(COROUTINE_POOL_RESERVATION); >> - return 0; >> -} >> - >> -/* TODO qdevified devices don't use this, remove when devices are qdevified >> */ >> -void bdrv_attach_dev_nofail(BlockDriverState *bs, void *dev) >> -{ >> - if (bdrv_attach_dev(bs, dev) < 0) { >> - abort(); >> - } >> -} >> - >> -void bdrv_detach_dev(BlockDriverState *bs, void *dev) >> -/* TODO change to DeviceState *dev when all users are qdevified */ >> -{ >> - assert(bs->dev == dev); >> - bs->dev = NULL; >> - bs->dev_ops = NULL; >> - bs->dev_opaque = NULL; >> - bs->guest_block_size = 512; >> - qemu_coroutine_adjust_pool_size(-COROUTINE_POOL_RESERVATION); >> -} >> - >> -/* TODO change to return DeviceState * when all users are qdevified */ >> -void *bdrv_get_attached_dev(BlockDriverState *bs) >> -{ >> - return bs->dev; >> -} >> - >> -void bdrv_set_dev_ops(BlockDriverState *bs, const BlockDevOps *ops, >> - void *opaque) >> -{ >> - bs->dev_ops = ops; >> - bs->dev_opaque = opaque; >> -} >> - >> -static void bdrv_dev_change_media_cb(BlockDriverState *bs, bool load) >> -{ >> - if (bs->dev_ops && bs->dev_ops->change_media_cb) { >> - bool tray_was_closed = !bdrv_dev_is_tray_open(bs); >> - bs->dev_ops->change_media_cb(bs->dev_opaque, load); >> - if (tray_was_closed) { >> - /* tray open */ >> - qapi_event_send_device_tray_moved(bdrv_get_device_name(bs), >> - true, &error_abort); >> - } >> - if (load) { >> - /* tray close */ >> - qapi_event_send_device_tray_moved(bdrv_get_device_name(bs), >> - false, &error_abort); >> - } >> - } >> -} >> - >> -bool bdrv_dev_has_removable_media(BlockDriverState *bs) >> -{ >> - return !bs->dev || (bs->dev_ops && bs->dev_ops->change_media_cb); >> -} >> - >> -void bdrv_dev_eject_request(BlockDriverState *bs, bool force) >> -{ >> - if (bs->dev_ops && bs->dev_ops->eject_request_cb) { >> - bs->dev_ops->eject_request_cb(bs->dev_opaque, force); >> - } >> -} >> - >> -bool bdrv_dev_is_tray_open(BlockDriverState *bs) >> -{ >> - if (bs->dev_ops && bs->dev_ops->is_tray_open) { >> - return bs->dev_ops->is_tray_open(bs->dev_opaque); >> - } >> - return false; >> -} >> - >> -static void bdrv_dev_resize_cb(BlockDriverState *bs) >> -{ >> - if (bs->dev_ops && bs->dev_ops->resize_cb) { >> - bs->dev_ops->resize_cb(bs->dev_opaque); >> - } >> -} >> - >> -bool bdrv_dev_is_medium_locked(BlockDriverState *bs) >> -{ >> - if (bs->dev_ops && bs->dev_ops->is_medium_locked) { >> - return bs->dev_ops->is_medium_locked(bs->dev_opaque); >> - } >> - return false; >> -} >> - >> /* >> * Run consistency checks on an image >> * >> @@ -3543,7 +3439,9 @@ int bdrv_truncate(BlockDriverState *bs, int64_t offset) >> ret = drv->bdrv_truncate(bs, offset); >> if (ret == 0) { >> ret = refresh_total_sectors(bs, offset >> BDRV_SECTOR_BITS); >> - bdrv_dev_resize_cb(bs); >> + if (bs->blk) { >> + blk_dev_resize_cb(bs->blk); >> + } >> } >> return ret; >> } >> @@ -3744,8 +3642,10 @@ int bdrv_set_key(BlockDriverState *bs, const char >> *key) >> bs->valid_key = 0; >> } else if (!bs->valid_key) { >> bs->valid_key = 1; >> - /* call the change callback now, we skipped it on open */ >> - bdrv_dev_change_media_cb(bs, true); >> + if (bs->blk) { >> + /* call the change callback now, we skipped it on open */ >> + blk_dev_change_media_cb(bs->blk, true); >> + } >> } >> return ret; >> } >> diff --git a/block/block-backend.c b/block/block-backend.c >> index b55f0b4..d49c988 100644 >> --- a/block/block-backend.c >> +++ b/block/block-backend.c >> @@ -13,6 +13,10 @@ >> #include "sysemu/block-backend.h" >> #include "block/block_int.h" >> #include "sysemu/blockdev.h" >> +#include "qapi-event.h" >> + >> +/* Number of coroutines to reserve per attached device model */ >> +#define COROUTINE_POOL_RESERVATION 64 >> >> struct BlockBackend { >> char *name; >> @@ -20,6 +24,11 @@ struct BlockBackend { >> BlockDriverState *bs; >> DriveInfo *legacy_dinfo; >> QTAILQ_ENTRY(BlockBackend) link; /* for blk_backends */ >> + >> + void *dev; /* attached device model, if any */ >> + /* TODO change to DeviceState when all users are qdevified */ >> + const BlockDevOps *dev_ops; >> + void *dev_opaque; >> }; >> >> static void drive_info_del(DriveInfo *dinfo); >> @@ -81,6 +90,7 @@ BlockBackend *blk_new_with_bs(const char *name, Error >> **errp) >> static void blk_delete(BlockBackend *blk) >> { >> assert(!blk->refcnt); >> + assert(!blk->dev); >> if (blk->bs) { >> assert(blk->bs->blk == blk); >> blk->bs->blk = NULL; >> @@ -233,34 +243,153 @@ void blk_hide_on_behalf_of_do_drive_del(BlockBackend >> *blk) >> } >> } >> >> -void blk_iostatus_enable(BlockBackend *blk) >> -{ >> - bdrv_iostatus_enable(blk->bs); >> -} >> - >> +/* >> + * Attach device model @dev to @blk. >> + * Return 0 on success, -EBUSY when a device model is attached already. >> + */ >> int blk_attach_dev(BlockBackend *blk, void *dev) >> +/* TODO change to DeviceState *dev when all users are qdevified */ >> { >> - return bdrv_attach_dev(blk->bs, dev); >> + if (blk->dev) { >> + return -EBUSY; >> + } >> + blk->dev = dev; >> + bdrv_iostatus_reset(blk->bs); >> + >> + /* We're expecting I/O from the device so bump up coroutine pool size */ >> + qemu_coroutine_adjust_pool_size(COROUTINE_POOL_RESERVATION); >> + return 0; >> } >> >> +/* >> + * Attach device model @dev to @blk. >> + * @blk must not have a device model attached already. >> + * TODO qdevified devices don't use this, remove when devices are qdevified >> + */ >> void blk_attach_dev_nofail(BlockBackend *blk, void *dev) >> { >> - bdrv_attach_dev_nofail(blk->bs, dev); >> + if (blk_attach_dev(blk, dev) < 0) { >> + abort(); >> + } >> } >> >> +/* >> + * Detach device model @dev from @blk. >> + * @dev must be currently attached to @blk. >> + */ >> void blk_detach_dev(BlockBackend *blk, void *dev) >> +/* TODO change to DeviceState *dev when all users are qdevified */ >> { >> - bdrv_detach_dev(blk->bs, dev); >> + assert(blk->dev == dev); >> + blk->dev = NULL; >> + blk->dev_ops = NULL; >> + blk->dev_opaque = NULL; >> + bdrv_set_guest_block_size(blk->bs, 512); >> + qemu_coroutine_adjust_pool_size(-COROUTINE_POOL_RESERVATION); >> } >> >> +/* >> + * Return the device model attached to @blk if any, else null. >> + */ >> void *blk_get_attached_dev(BlockBackend *blk) >> +/* TODO change to return DeviceState * when all users are qdevified */ >> { >> - return bdrv_get_attached_dev(blk->bs); >> + return blk->dev; >> } >> >> -void blk_set_dev_ops(BlockBackend *blk, const BlockDevOps *ops, void >> *opaque) >> +/* >> + * Set @blk's device model callbacks to @ops. >> + * @opaque is the opaque argument to pass to the callbacks. >> + * This is for use by device models. >> + */ >> +void blk_set_dev_ops(BlockBackend *blk, const BlockDevOps *ops, >> + void *opaque) >> { >> - bdrv_set_dev_ops(blk->bs, ops, opaque); >> + blk->dev_ops = ops; >> + blk->dev_opaque = opaque; >> +} >> + >> +/* >> + * Notify @blk's attached device model of media change. >> + * If @load is true, notify of media load. >> + * Else, notify of media eject. >> + * Also send DEVICE_TRAY_MOVED events as appropriate. >> + */ >> +void blk_dev_change_media_cb(BlockBackend *blk, bool load) >> +{ >> + if (blk->dev_ops && blk->dev_ops->change_media_cb) { >> + bool tray_was_closed = !blk_dev_is_tray_open(blk); >> + >> + blk->dev_ops->change_media_cb(blk->dev_opaque, load); >> + if (tray_was_closed) { >> + /* tray open */ >> + qapi_event_send_device_tray_moved(blk_name(blk), >> + true, &error_abort); >> + } >> + if (load) { >> + /* tray close */ >> + qapi_event_send_device_tray_moved(blk_name(blk), >> + false, &error_abort); >> + } >> + } >> +} >> + >> +/* >> + * Does @blk's attached device model have removable media? >> + * %true if no device model is attached. >> + */ >> +bool blk_dev_has_removable_media(BlockBackend *blk) >> +{ >> + return !blk->dev || (blk->dev_ops && blk->dev_ops->change_media_cb); >> +} >> + >> +/* >> + * Notify @blk's attached device model of a media eject request. >> + * If @force is true, the medium is about to be yanked out forcefully. >> + */ >> +void blk_dev_eject_request(BlockBackend *blk, bool force) >> +{ >> + if (blk->dev_ops && blk->dev_ops->eject_request_cb) { >> + blk->dev_ops->eject_request_cb(blk->dev_opaque, force); >> + } >> +} >> + >> +/* >> + * Does @blk's attached device model have a tray, and is it open? >> + */ >> +bool blk_dev_is_tray_open(BlockBackend *blk) >> +{ >> + if (blk->dev_ops && blk->dev_ops->is_tray_open) { >> + return blk->dev_ops->is_tray_open(blk->dev_opaque); >> + } >> + return false; >> +} >> + >> +/* >> + * Does @blk's attached device model have the medium locked? >> + * %false if the device model has no such lock. >> + */ >> +bool blk_dev_is_medium_locked(BlockBackend *blk) >> +{ >> + if (blk->dev_ops && blk->dev_ops->is_medium_locked) { >> + return blk->dev_ops->is_medium_locked(blk->dev_opaque); >> + } >> + return false; >> +} >> + >> +/* >> + * Notify @blk's attached device model of a backend size change. >> + */ >> +void blk_dev_resize_cb(BlockBackend *blk) >> +{ >> + if (blk->dev_ops && blk->dev_ops->resize_cb) { >> + blk->dev_ops->resize_cb(blk->dev_opaque); >> + } >> +} >> + >> +void blk_iostatus_enable(BlockBackend *blk) >> +{ >> + bdrv_iostatus_enable(blk->bs); >> } >> >> int blk_read(BlockBackend *blk, int64_t sector_num, uint8_t *buf, >> diff --git a/block/qapi.c b/block/qapi.c >> index fca981d..1301144 100644 >> --- a/block/qapi.c >> +++ b/block/qapi.c >> @@ -275,12 +275,12 @@ static void bdrv_query_info(BlockBackend *blk, >> BlockInfo **p_info, >> Error *local_err = NULL; >> info->device = g_strdup(blk_name(blk)); >> info->type = g_strdup("unknown"); >> - info->locked = bdrv_dev_is_medium_locked(bs); >> - info->removable = bdrv_dev_has_removable_media(bs); >> + info->locked = blk_dev_is_medium_locked(blk); >> + info->removable = blk_dev_has_removable_media(blk); >> >> - if (bdrv_dev_has_removable_media(bs)) { >> + if (blk_dev_has_removable_media(blk)) { >> info->has_tray_open = true; >> - info->tray_open = bdrv_dev_is_tray_open(bs); >> + info->tray_open = blk_dev_is_tray_open(blk); >> } >> >> if (bdrv_iostatus_is_enabled(bs)) { >> diff --git a/blockdev.c b/blockdev.c >> index e115bde..d3dccb9 100644 >> --- a/blockdev.c >> +++ b/blockdev.c >> @@ -1509,14 +1509,14 @@ static void eject_device(BlockBackend *blk, int >> force, Error **errp) >> if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_EJECT, errp)) { >> return; >> } >> - if (!bdrv_dev_has_removable_media(bs)) { >> + if (!blk_dev_has_removable_media(blk)) { >> error_setg(errp, "Device '%s' is not removable", >> bdrv_get_device_name(bs)); >> return; >> } >> >> - if (bdrv_dev_is_medium_locked(bs) && !bdrv_dev_is_tray_open(bs)) { >> - bdrv_dev_eject_request(bs, force); >> + if (blk_dev_is_medium_locked(blk) && !blk_dev_is_tray_open(blk)) { >> + blk_dev_eject_request(blk, force); >> if (!force) { >> error_setg(errp, "Device '%s' is locked", >> bdrv_get_device_name(bs)); >> @@ -1753,7 +1753,7 @@ int do_drive_del(Monitor *mon, const QDict *qdict, >> QObject **ret_data) >> * can be removed. If this is a drive with no device backing >> * then we can just get rid of the block driver state right here. >> */ >> - if (bdrv_get_attached_dev(bs)) { >> + if (blk_get_attached_dev(blk)) { >> blk_hide_on_behalf_of_do_drive_del(blk); >> /* Further I/O must not pause the guest */ >> bdrv_set_on_error(bs, BLOCKDEV_ON_ERROR_REPORT, >> diff --git a/include/block/block.h b/include/block/block.h >> index 5b45743..bc9ec50 100644 >> --- a/include/block/block.h >> +++ b/include/block/block.h >> @@ -48,41 +48,6 @@ typedef struct BlockFragInfo { >> uint64_t compressed_clusters; >> } BlockFragInfo; >> >> -/* Callbacks for block device models */ >> -typedef struct BlockDevOps { >> - /* >> - * Runs when virtual media changed (monitor commands eject, change) >> - * Argument load is true on load and false on eject. >> - * Beware: doesn't run when a host device's physical media >> - * changes. Sure would be useful if it did. >> - * Device models with removable media must implement this callback. >> - */ >> - void (*change_media_cb)(void *opaque, bool load); >> - /* >> - * Runs when an eject request is issued from the monitor, the tray >> - * is closed, and the medium is locked. >> - * Device models that do not implement is_medium_locked will not need >> - * this callback. Device models that can lock the medium or tray might >> - * want to implement the callback and unlock the tray when "force" is >> - * true, even if they do not support eject requests. >> - */ >> - void (*eject_request_cb)(void *opaque, bool force); >> - /* >> - * Is the virtual tray open? >> - * Device models implement this only when the device has a tray. >> - */ >> - bool (*is_tray_open)(void *opaque); >> - /* >> - * Is the virtual medium locked into the device? >> - * Device models implement this only when device has such a lock. >> - */ >> - bool (*is_medium_locked)(void *opaque); >> - /* >> - * Runs when the size changed (e.g. monitor command block_resize) >> - */ >> - void (*resize_cb)(void *opaque); >> -} BlockDevOps; >> - >> typedef enum { >> BDRV_REQ_COPY_ON_READ = 0x1, >> BDRV_REQ_ZERO_WRITE = 0x2, >> @@ -230,16 +195,6 @@ void bdrv_reopen_commit(BDRVReopenState *reopen_state); >> void bdrv_reopen_abort(BDRVReopenState *reopen_state); >> void bdrv_close(BlockDriverState *bs); >> void bdrv_add_close_notifier(BlockDriverState *bs, Notifier *notify); >> -int bdrv_attach_dev(BlockDriverState *bs, void *dev); >> -void bdrv_attach_dev_nofail(BlockDriverState *bs, void *dev); >> -void bdrv_detach_dev(BlockDriverState *bs, void *dev); >> -void *bdrv_get_attached_dev(BlockDriverState *bs); >> -void bdrv_set_dev_ops(BlockDriverState *bs, const BlockDevOps *ops, >> - void *opaque); >> -void bdrv_dev_eject_request(BlockDriverState *bs, bool force); >> -bool bdrv_dev_has_removable_media(BlockDriverState *bs); >> -bool bdrv_dev_is_tray_open(BlockDriverState *bs); >> -bool bdrv_dev_is_medium_locked(BlockDriverState *bs); >> int bdrv_read(BlockDriverState *bs, int64_t sector_num, >> uint8_t *buf, int nb_sectors); >> int bdrv_read_unthrottled(BlockDriverState *bs, int64_t sector_num, >> diff --git a/include/block/block_int.h b/include/block/block_int.h >> index e8e33a8..8898c6c 100644 >> --- a/include/block/block_int.h >> +++ b/include/block/block_int.h >> @@ -326,11 +326,6 @@ struct BlockDriverState { >> >> BlockBackend *blk; /* owning backend, if any */ >> >> - void *dev; /* attached device model, if any */ >> - /* TODO change to DeviceState when all users are qdevified */ >> - const BlockDevOps *dev_ops; >> - void *dev_opaque; >> - >> AioContext *aio_context; /* event loop used for fd handlers, timers, >> etc */ >> /* long-running tasks intended to always use the same AioContext as this >> * BDS may register themselves in this list to be notified of changes >> @@ -587,4 +582,11 @@ void backup_start(BlockDriverState *bs, >> BlockDriverState *target, >> BlockCompletionFunc *cb, void *opaque, >> Error **errp); >> >> +void blk_dev_change_media_cb(BlockBackend *blk, bool load); >> +bool blk_dev_has_removable_media(BlockBackend *blk); >> +void blk_dev_eject_request(BlockBackend *blk, bool force); >> +bool blk_dev_is_tray_open(BlockBackend *blk); >> +bool blk_dev_is_medium_locked(BlockBackend *blk); >> +void blk_dev_resize_cb(BlockBackend *blk); >> + >> #endif /* BLOCK_INT_H */ >> diff --git a/include/sysemu/block-backend.h b/include/sysemu/block-backend.h >> index e4bfea5..02305a1 100644 >> --- a/include/sysemu/block-backend.h >> +++ b/include/sysemu/block-backend.h >> @@ -25,6 +25,41 @@ >> */ >> #include "block/block.h" >> >> +/* Callbacks for block device models */ >> +typedef struct BlockDevOps { >> + /* >> + * Runs when virtual media changed (monitor commands eject, change) >> + * Argument load is true on load and false on eject. >> + * Beware: doesn't run when a host device's physical media >> + * changes. Sure would be useful if it did. >> + * Device models with removable media must implement this callback. >> + */ >> + void (*change_media_cb)(void *opaque, bool load); >> + /* >> + * Runs when an eject request is issued from the monitor, the tray >> + * is closed, and the medium is locked. >> + * Device models that do not implement is_medium_locked will not need >> + * this callback. Device models that can lock the medium or tray might >> + * want to implement the callback and unlock the tray when "force" is >> + * true, even if they do not support eject requests. >> + */ >> + void (*eject_request_cb)(void *opaque, bool force); >> + /* >> + * Is the virtual tray open? >> + * Device models implement this only when the device has a tray. >> + */ >> + bool (*is_tray_open)(void *opaque); >> + /* >> + * Is the virtual medium locked into the device? >> + * Device models implement this only when device has such a lock. >> + */ >> + bool (*is_medium_locked)(void *opaque); >> + /* >> + * Runs when the size changed (e.g. monitor command block_resize) >> + */ >> + void (*resize_cb)(void *opaque); >> +} BlockDevOps; >> + >> BlockBackend *blk_new(const char *name, Error **errp); >> BlockBackend *blk_new_with_bs(const char *name, Error **errp); >> void blk_ref(BlockBackend *blk); >> -- >> 1.9.3 >> > > This one is difficult to review. > Could it be splitted in multiple baby steps ?
Not sure, but I'll think about it. I certainly can try harder in the commit message.