628 lines
16 KiB
C
628 lines
16 KiB
C
/* $Id: block.c,v 1.1 2008/12/11 12:18:17 ecd Exp $
|
|
*/
|
|
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "qemu-git/qemu-common.h"
|
|
#include "block.h"
|
|
#include "block_int.h"
|
|
|
|
#define SECTOR_BITS 9
|
|
#define SECTOR_SIZE ( 1 << SECTOR_BITS )
|
|
|
|
static BlockDriverState* bdrv_first;
|
|
static BlockDriver* first_drv;
|
|
|
|
#ifndef ENOMEDIUM
|
|
# define ENOMEDIUM ENODEV
|
|
#endif
|
|
|
|
static void bdrv_close( BlockDriverState* bs );
|
|
|
|
static int path_is_absolute( const char* path )
|
|
{
|
|
const char* p;
|
|
#ifdef _WIN32
|
|
/* specific case for names like: "\\.\d:" */
|
|
if ( *path == '/' || *path == '\\' )
|
|
return 1;
|
|
#endif
|
|
p = strchr( path, ':' );
|
|
if ( p )
|
|
p++;
|
|
else
|
|
p = path;
|
|
#ifdef _WIN32
|
|
return ( *p == '/' || *p == '\\' );
|
|
#else
|
|
return ( *p == '/' );
|
|
#endif
|
|
}
|
|
|
|
/* if filename is absolute, just copy it to dest. Otherwise, build a
|
|
path to it by considering it is relative to base_path. URL are
|
|
supported. */
|
|
static void path_combine( char* dest, int dest_size, const char* base_path, const char* filename )
|
|
{
|
|
const char *p, *p1;
|
|
int len;
|
|
|
|
if ( dest_size <= 0 )
|
|
return;
|
|
if ( path_is_absolute( filename ) ) {
|
|
pstrcpy( dest, dest_size, filename );
|
|
} else {
|
|
p = strchr( base_path, ':' );
|
|
if ( p )
|
|
p++;
|
|
else
|
|
p = base_path;
|
|
p1 = strrchr( base_path, '/' );
|
|
#ifdef _WIN32
|
|
{
|
|
const char* p2;
|
|
p2 = strrchr( base_path, '\\' );
|
|
if ( !p1 || p2 > p1 )
|
|
p1 = p2;
|
|
}
|
|
#endif
|
|
if ( p1 )
|
|
p1++;
|
|
else
|
|
p1 = base_path;
|
|
if ( p1 > p )
|
|
p = p1;
|
|
len = p - base_path;
|
|
if ( len > dest_size - 1 )
|
|
len = dest_size - 1;
|
|
memcpy( dest, base_path, len );
|
|
dest[ len ] = '\0';
|
|
pstrcat( dest, dest_size, filename );
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
void get_tmp_filename( char* filename, int size )
|
|
{
|
|
char temp_dir[ MAX_PATH ];
|
|
|
|
GetTempPath( MAX_PATH, temp_dir );
|
|
GetTempFileName( temp_dir, "x49", 0, filename );
|
|
}
|
|
#else
|
|
void get_tmp_filename( char* filename, int size )
|
|
{
|
|
int fd;
|
|
|
|
/* XXX: race condition possible */
|
|
pstrcpy( filename, size, "/tmp/x49gp.XXXXXX" );
|
|
fd = mkstemp( filename );
|
|
close( fd );
|
|
}
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
static int is_windows_drive_prefix( const char* filename )
|
|
{
|
|
return ( ( ( filename[ 0 ] >= 'a' && filename[ 0 ] <= 'z' ) || ( filename[ 0 ] >= 'A' && filename[ 0 ] <= 'Z' ) ) &&
|
|
filename[ 1 ] == ':' );
|
|
}
|
|
|
|
static int is_windows_drive( const char* filename )
|
|
{
|
|
if ( is_windows_drive_prefix( filename ) && filename[ 2 ] == '\0' )
|
|
return 1;
|
|
if ( strstart( filename, "\\\\.\\", NULL ) || strstart( filename, "//./", NULL ) )
|
|
return 1;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static BlockDriver* find_protocol( const char* filename )
|
|
{
|
|
BlockDriver* drv1;
|
|
char protocol[ 128 ];
|
|
int len;
|
|
const char* p;
|
|
|
|
#ifdef _WIN32
|
|
if ( is_windows_drive( filename ) || is_windows_drive_prefix( filename ) )
|
|
return &bdrv_raw;
|
|
#endif
|
|
p = strchr( filename, ':' );
|
|
if ( !p )
|
|
return &bdrv_raw;
|
|
len = p - filename;
|
|
if ( len > sizeof( protocol ) - 1 )
|
|
len = sizeof( protocol ) - 1;
|
|
memcpy( protocol, filename, len );
|
|
protocol[ len ] = '\0';
|
|
for ( drv1 = first_drv; drv1 != NULL; drv1 = drv1->next ) {
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: protocol '%s', drv->protocol_name '%s'\n", __FUNCTION__, __LINE__, protocol, drv1->protocol_name );
|
|
#endif
|
|
if ( drv1->protocol_name && !strcmp( drv1->protocol_name, protocol ) ) {
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: protocol '%s', drv %p\n", __FUNCTION__, __LINE__, protocol, drv1 );
|
|
#endif
|
|
return drv1;
|
|
}
|
|
}
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: protocol '%s', NULL\n", __FUNCTION__, __LINE__, protocol );
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
/* XXX: force raw format if block or character device ? It would
|
|
simplify the BSD case */
|
|
static BlockDriver* find_image_format( const char* filename )
|
|
{
|
|
int ret, score, score_max;
|
|
BlockDriver *drv1, *drv;
|
|
uint8_t buf[ 2048 ];
|
|
BlockDriverState* bs;
|
|
|
|
drv = find_protocol( filename );
|
|
/* no need to test disk image formats for vvfat */
|
|
if ( drv == &bdrv_vvfat )
|
|
return drv;
|
|
|
|
ret = bdrv_file_open( &bs, filename, BDRV_O_RDONLY );
|
|
if ( ret < 0 )
|
|
return NULL;
|
|
ret = bdrv_pread( bs, 0, buf, sizeof( buf ) );
|
|
bdrv_delete( bs );
|
|
if ( ret < 0 ) {
|
|
return NULL;
|
|
}
|
|
|
|
score_max = 0;
|
|
for ( drv1 = first_drv; drv1 != NULL; drv1 = drv1->next ) {
|
|
if ( drv1->bdrv_probe ) {
|
|
score = drv1->bdrv_probe( buf, ret, filename );
|
|
if ( score > score_max ) {
|
|
score_max = score;
|
|
drv = drv1;
|
|
}
|
|
}
|
|
}
|
|
return drv;
|
|
}
|
|
|
|
int bdrv_create( BlockDriver* drv, const char* filename, int64_t size_in_sectors, const char* backing_file, int flags )
|
|
{
|
|
if ( !drv->bdrv_create )
|
|
return -ENOTSUP;
|
|
return drv->bdrv_create( filename, size_in_sectors, backing_file, flags );
|
|
}
|
|
|
|
static void bdrv_register( BlockDriver* bdrv )
|
|
{
|
|
bdrv->next = first_drv;
|
|
first_drv = bdrv;
|
|
}
|
|
|
|
/* create a new block device (by default it is empty) */
|
|
BlockDriverState* bdrv_new( const char* device_name )
|
|
{
|
|
BlockDriverState **pbs, *bs;
|
|
|
|
bs = qemu_mallocz( sizeof( BlockDriverState ) );
|
|
if ( !bs )
|
|
return NULL;
|
|
pstrcpy( bs->device_name, sizeof( bs->device_name ), device_name );
|
|
if ( device_name[ 0 ] != '\0' ) {
|
|
/* insert at the end */
|
|
pbs = &bdrv_first;
|
|
while ( *pbs != NULL )
|
|
pbs = &( *pbs )->next;
|
|
*pbs = bs;
|
|
}
|
|
return bs;
|
|
}
|
|
|
|
/**
|
|
* Truncate file to 'offset' bytes (needed only for file protocols)
|
|
*/
|
|
int bdrv_truncate( BlockDriverState* bs, int64_t offset )
|
|
{
|
|
BlockDriver* drv = bs->drv;
|
|
if ( !drv )
|
|
return -ENOMEDIUM;
|
|
if ( !drv->bdrv_truncate )
|
|
return -ENOTSUP;
|
|
return drv->bdrv_truncate( bs, offset );
|
|
}
|
|
|
|
/**
|
|
* Length of a file in bytes. Return < 0 if error or unknown.
|
|
*/
|
|
int64_t bdrv_getlength( BlockDriverState* bs )
|
|
{
|
|
BlockDriver* drv = bs->drv;
|
|
if ( !drv )
|
|
return -ENOMEDIUM;
|
|
if ( !drv->bdrv_getlength ) {
|
|
/* legacy mode */
|
|
return bs->total_sectors * SECTOR_SIZE;
|
|
}
|
|
return drv->bdrv_getlength( bs );
|
|
}
|
|
|
|
int bdrv_file_open( BlockDriverState** pbs, const char* filename, int flags )
|
|
{
|
|
BlockDriverState* bs;
|
|
int ret;
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: filename '%s'\n", __FUNCTION__, __LINE__, filename );
|
|
#endif
|
|
|
|
bs = bdrv_new( "" );
|
|
if ( !bs )
|
|
return -ENOMEM;
|
|
ret = bdrv_open( bs, filename, flags | BDRV_O_FILE );
|
|
if ( ret < 0 ) {
|
|
bdrv_delete( bs );
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: '%s': %d\n", __FUNCTION__, __LINE__, filename, ret );
|
|
#endif
|
|
return ret;
|
|
}
|
|
*pbs = bs;
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: return 0\n", __FUNCTION__, __LINE__ );
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int bdrv_open( BlockDriverState* bs, const char* filename, int flags )
|
|
{
|
|
int ret, open_flags;
|
|
char backing_filename[ 1024 ];
|
|
BlockDriver* drv = NULL;
|
|
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: filename '%s'\n", __FUNCTION__, __LINE__, filename );
|
|
#endif
|
|
|
|
bs->read_only = 0;
|
|
bs->is_temporary = 0;
|
|
bs->encrypted = 0;
|
|
|
|
pstrcpy( bs->filename, sizeof( bs->filename ), filename );
|
|
if ( flags & BDRV_O_FILE ) {
|
|
drv = find_protocol( filename );
|
|
if ( !drv ) {
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: drv: %p\n", __FUNCTION__, __LINE__, drv );
|
|
#endif
|
|
return -ENOENT;
|
|
}
|
|
} else {
|
|
if ( !drv ) {
|
|
drv = find_image_format( filename );
|
|
if ( !drv ) {
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: drv: %p\n", __FUNCTION__, __LINE__, drv );
|
|
#endif
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: drv: %p\n", __FUNCTION__, __LINE__, drv );
|
|
#endif
|
|
bs->drv = drv;
|
|
bs->opaque = qemu_mallocz( drv->instance_size );
|
|
if ( bs->opaque == NULL && drv->instance_size > 0 ) {
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: no opaque\n", __FUNCTION__, __LINE__ );
|
|
#endif
|
|
return -1;
|
|
}
|
|
/* Note: for compatibility, we open disk image files as RDWR, and
|
|
RDONLY as fallback */
|
|
if ( !( flags & BDRV_O_FILE ) )
|
|
open_flags = BDRV_O_RDWR;
|
|
else
|
|
open_flags = flags & ~( BDRV_O_FILE );
|
|
ret = drv->bdrv_open( bs, filename, open_flags );
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: drv->bdrv_open: %d\n", __FUNCTION__, __LINE__, ret );
|
|
#endif
|
|
if ( ret == -EACCES && !( flags & BDRV_O_FILE ) ) {
|
|
ret = drv->bdrv_open( bs, filename, BDRV_O_RDONLY );
|
|
bs->read_only = 1;
|
|
}
|
|
if ( ret < 0 ) {
|
|
qemu_free( bs->opaque );
|
|
bs->opaque = NULL;
|
|
bs->drv = NULL;
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: return %d\n", __FUNCTION__, __LINE__, ret );
|
|
#endif
|
|
return ret;
|
|
}
|
|
if ( drv->bdrv_getlength ) {
|
|
bs->total_sectors = bdrv_getlength( bs ) >> SECTOR_BITS;
|
|
}
|
|
#ifndef _WIN32
|
|
if ( bs->is_temporary ) {
|
|
unlink( filename );
|
|
}
|
|
#endif
|
|
if ( bs->backing_file[ 0 ] != '\0' ) {
|
|
/* if there is a backing file, use it */
|
|
bs->backing_hd = bdrv_new( "" );
|
|
if ( !bs->backing_hd ) {
|
|
fail:
|
|
bdrv_close( bs );
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: return -ENOMEM\n", __FUNCTION__, __LINE__ );
|
|
#endif
|
|
return -ENOMEM;
|
|
}
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: combine '%s' '%s'\n", __FUNCTION__, __LINE__, filename, bs->backing_file );
|
|
#endif
|
|
path_combine( backing_filename, sizeof( backing_filename ), filename, bs->backing_file );
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: combine: '%s'\n", __FUNCTION__, __LINE__, backing_filename );
|
|
#endif
|
|
if ( bdrv_open( bs->backing_hd, backing_filename, 0 ) < 0 ) {
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: backing fail\n", __FUNCTION__, __LINE__ );
|
|
#endif
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
/* call the change callback */
|
|
bs->media_changed = 1;
|
|
if ( bs->change_cb )
|
|
bs->change_cb( bs->change_opaque );
|
|
|
|
#ifdef DEBUG_X49GP_BLOCK
|
|
fprintf( stderr, "%s:%u: return 0\n", __FUNCTION__, __LINE__ );
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static void bdrv_close( BlockDriverState* bs )
|
|
{
|
|
if ( NULL == bs->drv )
|
|
return;
|
|
|
|
/* call the change callback */
|
|
bs->media_changed = 1;
|
|
if ( bs->change_cb )
|
|
bs->change_cb( bs->change_opaque );
|
|
|
|
if ( bs->backing_hd )
|
|
bdrv_delete( bs->backing_hd );
|
|
|
|
bs->drv->bdrv_close( bs );
|
|
|
|
#ifdef _WIN32
|
|
if ( bs->is_temporary ) {
|
|
unlink( bs->filename );
|
|
}
|
|
#endif
|
|
|
|
qemu_free( bs->opaque );
|
|
bs->opaque = NULL;
|
|
bs->drv = NULL;
|
|
}
|
|
|
|
void bdrv_delete( BlockDriverState* bs )
|
|
{
|
|
/* XXX: remove the driver list */
|
|
bdrv_close( bs );
|
|
qemu_free( bs );
|
|
}
|
|
|
|
/* return < 0 if error. See bdrv_write() for the return codes */
|
|
int bdrv_read( BlockDriverState* bs, int64_t sector_num, uint8_t* buf, int nb_sectors )
|
|
{
|
|
BlockDriver* drv = bs->drv;
|
|
|
|
if ( !drv )
|
|
return -ENOMEDIUM;
|
|
|
|
if ( sector_num == 0 && bs->boot_sector_enabled && nb_sectors > 0 ) {
|
|
memcpy( buf, bs->boot_sector_data, 512 );
|
|
sector_num++;
|
|
nb_sectors--;
|
|
buf += 512;
|
|
if ( nb_sectors == 0 )
|
|
return 0;
|
|
}
|
|
if ( drv->bdrv_pread ) {
|
|
int ret, len;
|
|
|
|
len = nb_sectors * 512;
|
|
ret = drv->bdrv_pread( bs, sector_num * 512, buf, len );
|
|
if ( ret < 0 )
|
|
return ret;
|
|
else if ( ret != len )
|
|
return -EINVAL;
|
|
else
|
|
return 0;
|
|
} else {
|
|
return drv->bdrv_read( bs, sector_num, buf, nb_sectors );
|
|
}
|
|
}
|
|
|
|
/* Return < 0 if error. Important errors are:
|
|
-EIO generic I/O error (may happen for all errors)
|
|
-ENOMEDIUM No media inserted.
|
|
-EINVAL Invalid sector number or nb_sectors
|
|
-EACCES Trying to write a read-only device
|
|
*/
|
|
static int bdrv_write( BlockDriverState* bs, int64_t sector_num, const uint8_t* buf, int nb_sectors )
|
|
{
|
|
BlockDriver* drv = bs->drv;
|
|
|
|
if ( !bs->drv )
|
|
return -ENOMEDIUM;
|
|
if ( bs->read_only )
|
|
return -EACCES;
|
|
if ( sector_num == 0 && bs->boot_sector_enabled && nb_sectors > 0 ) {
|
|
memcpy( bs->boot_sector_data, buf, 512 );
|
|
}
|
|
if ( drv->bdrv_pwrite ) {
|
|
int ret, len;
|
|
|
|
len = nb_sectors * 512;
|
|
ret = drv->bdrv_pwrite( bs, sector_num * 512, buf, len );
|
|
if ( ret < 0 )
|
|
return ret;
|
|
else if ( ret != len )
|
|
return -EIO;
|
|
else
|
|
return 0;
|
|
} else {
|
|
return drv->bdrv_write( bs, sector_num, buf, nb_sectors );
|
|
}
|
|
}
|
|
|
|
static int bdrv_pread_em( BlockDriverState* bs, int64_t offset, uint8_t* buf, int count1 )
|
|
{
|
|
uint8_t tmp_buf[ SECTOR_SIZE ];
|
|
int len, nb_sectors, count;
|
|
int64_t sector_num;
|
|
|
|
count = count1;
|
|
/* first read to align to sector start */
|
|
len = ( SECTOR_SIZE - offset ) & ( SECTOR_SIZE - 1 );
|
|
if ( len > count )
|
|
len = count;
|
|
sector_num = offset >> SECTOR_BITS;
|
|
if ( len > 0 ) {
|
|
if ( bdrv_read( bs, sector_num, tmp_buf, 1 ) < 0 )
|
|
return -EIO;
|
|
memcpy( buf, tmp_buf + ( offset & ( SECTOR_SIZE - 1 ) ), len );
|
|
count -= len;
|
|
if ( count == 0 )
|
|
return count1;
|
|
sector_num++;
|
|
buf += len;
|
|
}
|
|
|
|
/* read the sectors "in place" */
|
|
nb_sectors = count >> SECTOR_BITS;
|
|
if ( nb_sectors > 0 ) {
|
|
if ( bdrv_read( bs, sector_num, buf, nb_sectors ) < 0 )
|
|
return -EIO;
|
|
sector_num += nb_sectors;
|
|
len = nb_sectors << SECTOR_BITS;
|
|
buf += len;
|
|
count -= len;
|
|
}
|
|
|
|
/* add data from the last sector */
|
|
if ( count > 0 ) {
|
|
if ( bdrv_read( bs, sector_num, tmp_buf, 1 ) < 0 )
|
|
return -EIO;
|
|
memcpy( buf, tmp_buf, count );
|
|
}
|
|
return count1;
|
|
}
|
|
|
|
static int bdrv_pwrite_em( BlockDriverState* bs, int64_t offset, const uint8_t* buf, int count1 )
|
|
{
|
|
uint8_t tmp_buf[ SECTOR_SIZE ];
|
|
int len, nb_sectors, count;
|
|
int64_t sector_num;
|
|
|
|
count = count1;
|
|
/* first write to align to sector start */
|
|
len = ( SECTOR_SIZE - offset ) & ( SECTOR_SIZE - 1 );
|
|
if ( len > count )
|
|
len = count;
|
|
sector_num = offset >> SECTOR_BITS;
|
|
if ( len > 0 ) {
|
|
if ( bdrv_read( bs, sector_num, tmp_buf, 1 ) < 0 )
|
|
return -EIO;
|
|
memcpy( tmp_buf + ( offset & ( SECTOR_SIZE - 1 ) ), buf, len );
|
|
if ( bdrv_write( bs, sector_num, tmp_buf, 1 ) < 0 )
|
|
return -EIO;
|
|
count -= len;
|
|
if ( count == 0 )
|
|
return count1;
|
|
sector_num++;
|
|
buf += len;
|
|
}
|
|
|
|
/* write the sectors "in place" */
|
|
nb_sectors = count >> SECTOR_BITS;
|
|
if ( nb_sectors > 0 ) {
|
|
if ( bdrv_write( bs, sector_num, buf, nb_sectors ) < 0 )
|
|
return -EIO;
|
|
sector_num += nb_sectors;
|
|
len = nb_sectors << SECTOR_BITS;
|
|
buf += len;
|
|
count -= len;
|
|
}
|
|
|
|
/* add data from the last sector */
|
|
if ( count > 0 ) {
|
|
if ( bdrv_read( bs, sector_num, tmp_buf, 1 ) < 0 )
|
|
return -EIO;
|
|
memcpy( tmp_buf, buf, count );
|
|
if ( bdrv_write( bs, sector_num, tmp_buf, 1 ) < 0 )
|
|
return -EIO;
|
|
}
|
|
return count1;
|
|
}
|
|
|
|
/**
|
|
* Read with byte offsets (needed only for file protocols)
|
|
*/
|
|
int bdrv_pread( BlockDriverState* bs, int64_t offset, void* buf1, int count1 )
|
|
{
|
|
BlockDriver* drv = bs->drv;
|
|
|
|
if ( !drv )
|
|
return -ENOMEDIUM;
|
|
if ( !drv->bdrv_pread )
|
|
return bdrv_pread_em( bs, offset, buf1, count1 );
|
|
return drv->bdrv_pread( bs, offset, buf1, count1 );
|
|
}
|
|
|
|
/**
|
|
* Write with byte offsets (needed only for file protocols)
|
|
*/
|
|
int bdrv_pwrite( BlockDriverState* bs, int64_t offset, const void* buf1, int count1 )
|
|
{
|
|
BlockDriver* drv = bs->drv;
|
|
|
|
if ( !drv )
|
|
return -ENOMEDIUM;
|
|
if ( !drv->bdrv_pwrite )
|
|
return bdrv_pwrite_em( bs, offset, buf1, count1 );
|
|
return drv->bdrv_pwrite( bs, offset, buf1, count1 );
|
|
}
|
|
|
|
void bdrv_flush( BlockDriverState* bs )
|
|
{
|
|
if ( bs->drv->bdrv_flush )
|
|
bs->drv->bdrv_flush( bs );
|
|
if ( bs->backing_hd )
|
|
bdrv_flush( bs->backing_hd );
|
|
}
|
|
|
|
void bdrv_init( void )
|
|
{
|
|
/* bdrv_register(&bdrv_raw); */
|
|
/* bdrv_register(&bdrv_host_device); */
|
|
bdrv_register( &bdrv_qcow );
|
|
bdrv_register( &bdrv_vvfat );
|
|
}
|