1
0
Fork 0
forked from gwh/x49gp
x50ng/src/block.c
2024-10-23 16:17:52 +02:00

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 );
}