@ -156,6 +156,11 @@ static void QEMU_NORETURN help(void)
" kinds of errors, with a higher risk of choosing the wrong fix or \n "
" hiding corruption that has already occurred. \n "
" \n "
" Parameters to convert subcommand: \n "
" '-m' specifies how many coroutines work in parallel during the convert \n "
" process (defaults to 8) \n "
" '-W' allow to write to the target out of order rather than sequential \n "
" \n "
" Parameters to snapshot subcommand: \n "
" 'snapshot' is the name of the snapshot to create, apply or delete \n "
" '-a' applies a snapshot (revert disk to saved state) \n "
@ -1462,48 +1467,61 @@ enum ImgConvertBlockStatus {
BLK_BACKING_FILE ,
} ;
# define MAX_COROUTINES 16
typedef struct ImgConvertState {
BlockBackend * * src ;
int64_t * src_sectors ;
int src_cur , src_num ;
int64_t src_cur_offset ;
int src_num ;
int64_t total_sectors ;
int64_t allocated_sectors ;
int64_t allocated_done ;
int64_t sector_num ;
int64_t wr_offs ;
enum ImgConvertBlockStatus status ;
int64_t sector_next_status ;
BlockBackend * target ;
bool has_zero_init ;
bool compressed ;
bool target_has_backing ;
bool wr_in_order ;
int min_sparse ;
size_t cluster_sectors ;
size_t buf_sectors ;
int num_coroutines ;
int running_coroutines ;
Coroutine * co [ MAX_COROUTINES ] ;
int64_t wait_sector_num [ MAX_COROUTINES ] ;
CoMutex lock ;
int ret ;
} ImgConvertState ;
static void convert_select_part ( ImgConvertState * s , int64_t sector_num )
static void convert_select_part ( ImgConvertState * s , int64_t sector_num ,
int * src_cur , int64_t * src_cur_offset )
{
assert ( sector_num > = s - > src_cur_offset ) ;
while ( sector_num - s - > src_cur_offset > = s - > src_sectors [ s - > src_cur ] ) {
s - > src_cur_offset + = s - > src_sectors [ s - > src_cur ] ;
s - > src_cur + + ;
assert ( s - > src_cur < s - > src_num ) ;
* src_cur = 0 ;
* src_cur_offset = 0 ;
while ( sector_num - * src_cur_offset > = s - > src_sectors [ * src_cur ] ) {
* src_cur_offset + = s - > src_sectors [ * src_cur ] ;
( * src_cur ) + + ;
assert ( * src_cur < s - > src_num ) ;
}
}
static int convert_iteration_sectors ( ImgConvertState * s , int64_t sector_num )
{
int64_t ret ;
int n ;
int64_t ret , src_cur_offset ;
int n , src_cur ;
convert_select_part ( s , sector_num ) ;
convert_select_part ( s , sector_num , & src_cur , & src_cur_offset ) ;
assert ( s - > total_sectors > sector_num ) ;
n = MIN ( s - > total_sectors - sector_num , BDRV_REQUEST_MAX_SECTORS ) ;
if ( s - > sector_next_status < = sector_num ) {
BlockDriverState * file ;
ret = bdrv_get_block_status ( blk_bs ( s - > src [ s - > s rc_cur] ) ,
sector_num - s - > s rc_cur_offset,
ret = bdrv_get_block_status ( blk_bs ( s - > src [ src_cur ] ) ,
sector_num - src_cur_offset ,
n , & n , & file ) ;
if ( ret < 0 ) {
return ret ;
@ -1519,8 +1537,8 @@ static int convert_iteration_sectors(ImgConvertState *s, int64_t sector_num)
/* Check block status of the backing file chain to avoid
* needlessly reading zeroes and limiting the iteration to the
* buffer size */
ret = bdrv_get_block_status_above ( blk_bs ( s - > src [ s - > s rc_cur] ) , NULL ,
sector_num - s - > s rc_cur_offset,
ret = bdrv_get_block_status_above ( blk_bs ( s - > src [ src_cur ] ) , NULL ,
sector_num - src_cur_offset ,
n , & n , & file ) ;
if ( ret < 0 ) {
return ret ;
@ -1558,28 +1576,34 @@ static int convert_iteration_sectors(ImgConvertState *s, int64_t sector_num)
return n ;
}
static int convert_read ( ImgConvertState * s , int64_t sector_num , int nb_sectors ,
uint8_t * buf )
static int coroutine_fn co nvert_co _read ( ImgConvertState * s , int64_t sector_num ,
int nb_sectors , uint8_t * buf )
{
int n ;
int ret ;
int n , ret ;
QEMUIOVector qiov ;
struct iovec iov ;
assert ( nb_sectors < = s - > buf_sectors ) ;
while ( nb_sectors > 0 ) {
BlockBackend * blk ;
int64_t bs_sectors ;
int src_cur ;
int64_t bs_sectors , src_cur_offset ;
/* In the case of compression with multiple source files, we can get a
* nb_sectors that spreads into the next part . So we must be able to
* read across multiple BDSes for one convert_read ( ) call . */
convert_select_part ( s , sector_num ) ;
blk = s - > src [ s - > src_cur ] ;
bs_sectors = s - > src_sectors [ s - > src_cur ] ;
n = MIN ( nb_sectors , bs_sectors - ( sector_num - s - > src_cur_offset ) ) ;
ret = blk_pread ( blk ,
( sector_num - s - > src_cur_offset ) < < BDRV_SECTOR_BITS ,
buf , n < < BDRV_SECTOR_BITS ) ;
convert_select_part ( s , sector_num , & src_cur , & src_cur_offset ) ;
blk = s - > src [ src_cur ] ;
bs_sectors = s - > src_sectors [ src_cur ] ;
n = MIN ( nb_sectors , bs_sectors - ( sector_num - src_cur_offset ) ) ;
iov . iov_base = buf ;
iov . iov_len = n < < BDRV_SECTOR_BITS ;
qemu_iovec_init_external ( & qiov , & iov , 1 ) ;
ret = blk_co_preadv (
blk , ( sector_num - src_cur_offset ) < < BDRV_SECTOR_BITS ,
n < < BDRV_SECTOR_BITS , & qiov , 0 ) ;
if ( ret < 0 ) {
return ret ;
}
@ -1592,15 +1616,18 @@ static int convert_read(ImgConvertState *s, int64_t sector_num, int nb_sectors,
return 0 ;
}
static int convert_write ( ImgConvertState * s , int64_t sector_num , int nb_sectors ,
const uint8_t * buf )
static int coroutine_fn convert_co_write ( ImgConvertState * s , int64_t sector_num ,
int nb_sectors , uint8_t * buf ,
enum ImgConvertBlockStatus status )
{
int ret ;
QEMUIOVector qiov ;
struct iovec iov ;
while ( nb_sectors > 0 ) {
int n = nb_sectors ;
switch ( s - > status ) {
switch ( status ) {
case BLK_BACKING_FILE :
/* If we have a backing file, leave clusters unallocated that are
* unallocated in the source image , so that the backing file is
@ -1621,9 +1648,13 @@ static int convert_write(ImgConvertState *s, int64_t sector_num, int nb_sectors,
break ;
}
ret = blk_pwrite_compressed ( s - > target ,
sector_num < < BDRV_SECTOR_BITS ,
buf , n < < BDRV_SECTOR_BITS ) ;
iov . iov_base = buf ;
iov . iov_len = n < < BDRV_SECTOR_BITS ;
qemu_iovec_init_external ( & qiov , & iov , 1 ) ;
ret = blk_co_pwritev ( s - > target , sector_num < < BDRV_SECTOR_BITS ,
n < < BDRV_SECTOR_BITS , & qiov ,
BDRV_REQ_WRITE_COMPRESSED ) ;
if ( ret < 0 ) {
return ret ;
}
@ -1636,8 +1667,12 @@ static int convert_write(ImgConvertState *s, int64_t sector_num, int nb_sectors,
if ( ! s - > min_sparse | |
is_allocated_sectors_min ( buf , n , & n , s - > min_sparse ) )
{
ret = blk_pwrite ( s - > target , sector_num < < BDRV_SECTOR_BITS ,
buf , n < < BDRV_SECTOR_BITS , 0 ) ;
iov . iov_base = buf ;
iov . iov_len = n < < BDRV_SECTOR_BITS ;
qemu_iovec_init_external ( & qiov , & iov , 1 ) ;
ret = blk_co_pwritev ( s - > target , sector_num < < BDRV_SECTOR_BITS ,
n < < BDRV_SECTOR_BITS , & qiov , 0 ) ;
if ( ret < 0 ) {
return ret ;
}
@ -1649,8 +1684,9 @@ static int convert_write(ImgConvertState *s, int64_t sector_num, int nb_sectors,
if ( s - > has_zero_init ) {
break ;
}
ret = blk_pwrite_zeroes ( s - > target , sector_num < < BDRV_SECTOR_BITS ,
n < < BDRV_SECTOR_BITS , 0 ) ;
ret = blk_co_pwrite_zeroes ( s - > target ,
sector_num < < BDRV_SECTOR_BITS ,
n < < BDRV_SECTOR_BITS , 0 ) ;
if ( ret < 0 ) {
return ret ;
}
@ -1665,12 +1701,122 @@ static int convert_write(ImgConvertState *s, int64_t sector_num, int nb_sectors,
return 0 ;
}
static in t convert_do_copy ( ImgConvertState * s )
static vo id corou tine_fn convert_co _do_copy ( void * opaque )
{
ImgConvertState * s = opaque ;
uint8_t * buf = NULL ;
int64_t sector_num , allocated_done ;
int ret ;
int n ;
int ret , i ;
int index = - 1 ;
for ( i = 0 ; i < s - > num_coroutines ; i + + ) {
if ( s - > co [ i ] = = qemu_coroutine_self ( ) ) {
index = i ;
break ;
}
}
assert ( index > = 0 ) ;
s - > running_coroutines + + ;
buf = blk_blockalign ( s - > target , s - > buf_sectors * BDRV_SECTOR_SIZE ) ;
while ( 1 ) {
int n ;
int64_t sector_num ;
enum ImgConvertBlockStatus status ;
qemu_co_mutex_lock ( & s - > lock ) ;
if ( s - > ret ! = - EINPROGRESS | | s - > sector_num > = s - > total_sectors ) {
qemu_co_mutex_unlock ( & s - > lock ) ;
goto out ;
}
n = convert_iteration_sectors ( s , s - > sector_num ) ;
if ( n < 0 ) {
qemu_co_mutex_unlock ( & s - > lock ) ;
s - > ret = n ;
goto out ;
}
/* save current sector and allocation status to local variables */
sector_num = s - > sector_num ;
status = s - > status ;
if ( ! s - > min_sparse & & s - > status = = BLK_ZERO ) {
n = MIN ( n , s - > buf_sectors ) ;
}
/* increment global sector counter so that other coroutines can
* already continue reading beyond this request */
s - > sector_num + = n ;
qemu_co_mutex_unlock ( & s - > lock ) ;
if ( status = = BLK_DATA | | ( ! s - > min_sparse & & status = = BLK_ZERO ) ) {
s - > allocated_done + = n ;
qemu_progress_print ( 100.0 * s - > allocated_done /
s - > allocated_sectors , 0 ) ;
}
if ( status = = BLK_DATA ) {
ret = convert_co_read ( s , sector_num , n , buf ) ;
if ( ret < 0 ) {
error_report ( " error while reading sector % " PRId64
" : %s " , sector_num , strerror ( - ret ) ) ;
s - > ret = ret ;
goto out ;
}
} else if ( ! s - > min_sparse & & status = = BLK_ZERO ) {
status = BLK_DATA ;
memset ( buf , 0x00 , n * BDRV_SECTOR_SIZE ) ;
}
if ( s - > wr_in_order ) {
/* keep writes in order */
while ( s - > wr_offs ! = sector_num ) {
if ( s - > ret ! = - EINPROGRESS ) {
goto out ;
}
s - > wait_sector_num [ index ] = sector_num ;
qemu_coroutine_yield ( ) ;
}
s - > wait_sector_num [ index ] = - 1 ;
}
ret = convert_co_write ( s , sector_num , n , buf , status ) ;
if ( ret < 0 ) {
error_report ( " error while writing sector % " PRId64
" : %s " , sector_num , strerror ( - ret ) ) ;
s - > ret = ret ;
goto out ;
}
if ( s - > wr_in_order ) {
/* reenter the coroutine that might have waited
* for this write to complete */
s - > wr_offs = sector_num + n ;
for ( i = 0 ; i < s - > num_coroutines ; i + + ) {
if ( s - > co [ i ] & & s - > wait_sector_num [ i ] = = s - > wr_offs ) {
/*
* A - > B - > A cannot occur because A has
* s - > wait_sector_num [ i ] = = - 1 during A - > B . Therefore
* B will never enter A during this time window .
*/
qemu_coroutine_enter ( s - > co [ i ] ) ;
break ;
}
}
}
}
out :
qemu_vfree ( buf ) ;
s - > co [ index ] = NULL ;
s - > running_coroutines - - ;
if ( ! s - > running_coroutines & & s - > ret = = - EINPROGRESS ) {
/* the convert job finished successfully */
s - > ret = 0 ;
}
}
static int convert_do_copy ( ImgConvertState * s )
{
int ret , i , n ;
int64_t sector_num = 0 ;
/* Check whether we have zero initialisation or can get it efficiently */
s - > has_zero_init = s - > min_sparse & & ! s - > target_has_backing
@ -1691,21 +1837,15 @@ static int convert_do_copy(ImgConvertState *s)
if ( s - > compressed ) {
if ( s - > cluster_sectors < = 0 | | s - > cluster_sectors > s - > buf_sectors ) {
error_report ( " invalid cluster size " ) ;
ret = - EINVAL ;
goto fail ;
return - EINVAL ;
}
s - > buf_sectors = s - > cluster_sectors ;
}
buf = blk_blockalign ( s - > target , s - > buf_sectors * BDRV_SECTOR_SIZE ) ;
/* Calculate allocated sectors for progress */
s - > allocated_sectors = 0 ;
sector_num = 0 ;
while ( sector_num < s - > total_sectors ) {
n = convert_iteration_sectors ( s , sector_num ) ;
if ( n < 0 ) {
ret = n ;
goto fail ;
return n ;
}
if ( s - > status = = BLK_DATA | | ( ! s - > min_sparse & & s - > status = = BLK_ZERO ) )
{
@ -1715,61 +1855,29 @@ static int convert_do_copy(ImgConvertState *s)
}
/* Do the copy */
s - > src_cur = 0 ;
s - > src_cur_offset = 0 ;
s - > sector_next_status = 0 ;
s - > ret = - EINPROGRESS ;
sector_num = 0 ;
allocated_done = 0 ;
while ( sector_num < s - > total_sectors ) {
n = convert_iteration_sectors ( s , sector_num ) ;
if ( n < 0 ) {
ret = n ;
goto fail ;
}
if ( s - > status = = BLK_DATA | | ( ! s - > min_sparse & & s - > status = = BLK_ZERO ) )
{
allocated_done + = n ;
qemu_progress_print ( 100.0 * allocated_done / s - > allocated_sectors ,
0 ) ;
}
if ( s - > status = = BLK_DATA ) {
ret = convert_read ( s , sector_num , n , buf ) ;
if ( ret < 0 ) {
error_report ( " error while reading sector % " PRId64
" : %s " , sector_num , strerror ( - ret ) ) ;
goto fail ;
}
} else if ( ! s - > min_sparse & & s - > status = = BLK_ZERO ) {
n = MIN ( n , s - > buf_sectors ) ;
memset ( buf , 0 , n * BDRV_SECTOR_SIZE ) ;
s - > status = BLK_DATA ;
}
ret = convert_write ( s , sector_num , n , buf ) ;
if ( ret < 0 ) {
error_report ( " error while writing sector % " PRId64
" : %s " , sector_num , strerror ( - ret ) ) ;
goto fail ;
}
qemu_co_mutex_init ( & s - > lock ) ;
for ( i = 0 ; i < s - > num_coroutines ; i + + ) {
s - > co [ i ] = qemu_coroutine_create ( convert_co_do_copy , s ) ;
s - > wait_sector_num [ i ] = - 1 ;
qemu_coroutine_enter ( s - > co [ i ] ) ;
}
sector_num + = n ;
while ( s - > ret = = - EINPROGRESS ) {
main_loop_wait ( false ) ;
}
if ( s - > compressed ) {
if ( s - > compressed & & ! s - > ret ) {
/* signal EOF to align */
ret = blk_pwrite_compressed ( s - > target , 0 , NULL , 0 ) ;
if ( ret < 0 ) {
goto fail ;
return ret ;
}
}
ret = 0 ;
fail :
qemu_vfree ( buf ) ;
return ret ;
return s - > ret ;
}
static int img_convert ( int argc , char * * argv )
@ -1797,6 +1905,8 @@ static int img_convert(int argc, char **argv)
QemuOpts * sn_opts = NULL ;
ImgConvertState state ;
bool image_opts = false ;
bool wr_in_order = true ;
long num_coroutines = 8 ;
fmt = NULL ;
out_fmt = " raw " ;
@ -1812,7 +1922,7 @@ static int img_convert(int argc, char **argv)
{ " image-opts " , no_argument , 0 , OPTION_IMAGE_OPTS } ,
{ 0 , 0 , 0 , 0 }
} ;
c = getopt_long ( argc , argv , " hf:O:B:ce6o:s:l:S:pt:T:qn " ,
c = getopt_long ( argc , argv , " hf:O:B:ce6o:s:l:S:pt:T:qnm:W " ,
long_options , NULL ) ;
if ( c = = - 1 ) {
break ;
@ -1904,6 +2014,18 @@ static int img_convert(int argc, char **argv)
case ' n ' :
skip_create = 1 ;
break ;
case ' m ' :
if ( qemu_strtol ( optarg , NULL , 0 , & num_coroutines ) | |
num_coroutines < 1 | | num_coroutines > MAX_COROUTINES ) {
error_report ( " Invalid number of coroutines. Allowed number of "
" coroutines is between 1 and %d " , MAX_COROUTINES ) ;
ret = - 1 ;
goto fail_getopt ;
}
break ;
case ' W ' :
wr_in_order = false ;
break ;
case OPTION_OBJECT :
opts = qemu_opts_parse_noisily ( & qemu_object_opts ,
optarg , true ) ;
@ -1923,6 +2045,12 @@ static int img_convert(int argc, char **argv)
goto fail_getopt ;
}
if ( ! wr_in_order & & compress ) {
error_report ( " Out of order write and compress are mutually exclusive " ) ;
ret = - 1 ;
goto fail_getopt ;
}
/* Initialize before goto out */
if ( quiet ) {
progress = 0 ;
@ -2163,6 +2291,8 @@ static int img_convert(int argc, char **argv)
. min_sparse = min_sparse ,
. cluster_sectors = cluster_sectors ,
. buf_sectors = bufsectors ,
. wr_in_order = wr_in_order ,
. num_coroutines = num_coroutines ,
} ;
ret = convert_do_copy ( & state ) ;