From 19cb37389f4641d48803f0c5d72708749cbcf318 Mon Sep 17 00:00:00 2001 From: bellard Date: Sat, 19 Aug 2006 11:45:59 +0000 Subject: [PATCH] better support of host drives git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2124 c046a42c-6fe2-441c-8c8c-71466251a162 --- block-raw.c | 682 ++++++++++++++++++++++++++++++++++++++++++-------- block.c | 218 +++++++++++----- block_int.h | 12 +- qemu-doc.texi | 56 ++++- vl.h | 4 + 5 files changed, 808 insertions(+), 164 deletions(-) diff --git a/block-raw.c b/block-raw.c index a64564dc76..4dc0b9a749 100644 --- a/block-raw.c +++ b/block-raw.c @@ -46,100 +46,42 @@ #ifdef __sun__ #include #endif +#ifdef __linux__ +#include +#include +#include +#endif + +//#define DEBUG_FLOPPY + +#define FTYPE_FILE 0 +#define FTYPE_CD 1 +#define FTYPE_FD 2 + +/* if the FD is not accessed during that time (in ms), we try to + reopen it to see if the disk has been changed */ +#define FD_OPEN_TIMEOUT 1000 typedef struct BDRVRawState { int fd; + int type; +#if defined(__linux__) + /* linux floppy specific */ + int fd_open_flags; + int64_t fd_open_time; + int64_t fd_error_time; + int fd_got_error; + int fd_media_changed; +#endif } BDRVRawState; -#ifdef CONFIG_COCOA -static kern_return_t FindEjectableCDMedia( io_iterator_t *mediaIterator ); -static kern_return_t GetBSDPath( io_iterator_t mediaIterator, char *bsdPath, CFIndex maxPathSize ); - -kern_return_t FindEjectableCDMedia( io_iterator_t *mediaIterator ) -{ - kern_return_t kernResult; - mach_port_t masterPort; - CFMutableDictionaryRef classesToMatch; - - kernResult = IOMasterPort( MACH_PORT_NULL, &masterPort ); - if ( KERN_SUCCESS != kernResult ) { - printf( "IOMasterPort returned %d\n", kernResult ); - } - - classesToMatch = IOServiceMatching( kIOCDMediaClass ); - if ( classesToMatch == NULL ) { - printf( "IOServiceMatching returned a NULL dictionary.\n" ); - } else { - CFDictionarySetValue( classesToMatch, CFSTR( kIOMediaEjectableKey ), kCFBooleanTrue ); - } - kernResult = IOServiceGetMatchingServices( masterPort, classesToMatch, mediaIterator ); - if ( KERN_SUCCESS != kernResult ) - { - printf( "IOServiceGetMatchingServices returned %d\n", kernResult ); - } - - return kernResult; -} - -kern_return_t GetBSDPath( io_iterator_t mediaIterator, char *bsdPath, CFIndex maxPathSize ) -{ - io_object_t nextMedia; - kern_return_t kernResult = KERN_FAILURE; - *bsdPath = '\0'; - nextMedia = IOIteratorNext( mediaIterator ); - if ( nextMedia ) - { - CFTypeRef bsdPathAsCFString; - bsdPathAsCFString = IORegistryEntryCreateCFProperty( nextMedia, CFSTR( kIOBSDNameKey ), kCFAllocatorDefault, 0 ); - if ( bsdPathAsCFString ) { - size_t devPathLength; - strcpy( bsdPath, _PATH_DEV ); - strcat( bsdPath, "r" ); - devPathLength = strlen( bsdPath ); - if ( CFStringGetCString( bsdPathAsCFString, bsdPath + devPathLength, maxPathSize - devPathLength, kCFStringEncodingASCII ) ) { - kernResult = KERN_SUCCESS; - } - CFRelease( bsdPathAsCFString ); - } - IOObjectRelease( nextMedia ); - } - - return kernResult; -} - -#endif +static int fd_open(BlockDriverState *bs); static int raw_open(BlockDriverState *bs, const char *filename, int flags) { BDRVRawState *s = bs->opaque; - int fd, open_flags; + int fd, open_flags, ret; -#ifdef CONFIG_COCOA - if (strstart(filename, "/dev/cdrom", NULL)) { - kern_return_t kernResult; - io_iterator_t mediaIterator; - char bsdPath[ MAXPATHLEN ]; - int fd; - - kernResult = FindEjectableCDMedia( &mediaIterator ); - kernResult = GetBSDPath( mediaIterator, bsdPath, sizeof( bsdPath ) ); - - if ( bsdPath[ 0 ] != '\0' ) { - strcat(bsdPath,"s0"); - /* some CDs don't have a partition 0 */ - fd = open(bsdPath, O_RDONLY | O_BINARY | O_LARGEFILE); - if (fd < 0) { - bsdPath[strlen(bsdPath)-1] = '1'; - } else { - close(fd); - } - filename = bsdPath; - } - - if ( mediaIterator ) - IOObjectRelease( mediaIterator ); - } -#endif open_flags = O_BINARY; if ((flags & BDRV_O_ACCESS) == O_RDWR) { open_flags |= O_RDWR; @@ -150,9 +92,15 @@ static int raw_open(BlockDriverState *bs, const char *filename, int flags) if (flags & BDRV_O_CREAT) open_flags |= O_CREAT | O_TRUNC; + s->type = FTYPE_FILE; + fd = open(filename, open_flags, 0644); - if (fd < 0) - return -errno; + if (fd < 0) { + ret = -errno; + if (ret == -EROFS) + ret = -EACCES; + return ret; + } s->fd = fd; return 0; } @@ -180,6 +128,10 @@ static int raw_pread(BlockDriverState *bs, int64_t offset, BDRVRawState *s = bs->opaque; int ret; + ret = fd_open(bs); + if (ret < 0) + return ret; + lseek(s->fd, offset, SEEK_SET); ret = read(s->fd, buf, count); return ret; @@ -191,13 +143,17 @@ static int raw_pwrite(BlockDriverState *bs, int64_t offset, BDRVRawState *s = bs->opaque; int ret; + ret = fd_open(bs); + if (ret < 0) + return ret; + lseek(s->fd, offset, SEEK_SET); ret = write(s->fd, buf, count); return ret; } /***********************************************************/ -/* Unix AOP using POSIX AIO */ +/* Unix AIO using POSIX AIO */ typedef struct RawAIOCB { BlockDriverAIOCB common; @@ -236,15 +192,18 @@ void qemu_aio_init(void) act.sa_handler = aio_signal_handler; sigaction(aio_sig_num, &act, NULL); +#if defined(__GLIBC__) && defined(__linux__) { - /* XXX: aio thread exit seems to hang on RH 9 */ + /* XXX: aio thread exit seems to hang on RedHat 9 and this init + seems to fix the problem. */ struct aioinit ai; memset(&ai, 0, sizeof(ai)); - ai.aio_threads = 2; + ai.aio_threads = 1; ai.aio_num = 1; ai.aio_idle_time = 365 * 100000; aio_init(&ai); } +#endif } void qemu_aio_poll(void) @@ -270,7 +229,7 @@ void qemu_aio_poll(void) if (ret == acb->aiocb.aio_nbytes) ret = 0; else - ret = -1; + ret = -EINVAL; } else { ret = -ret; } @@ -329,6 +288,9 @@ static RawAIOCB *raw_aio_setup(BlockDriverState *bs, BDRVRawState *s = bs->opaque; RawAIOCB *acb; + if (fd_open(bs) < 0) + return NULL; + acb = qemu_aio_get(bs, cb, opaque); if (!acb) return NULL; @@ -405,12 +367,17 @@ static void raw_aio_cancel(BlockDriverAIOCB *blockacb) static void raw_close(BlockDriverState *bs) { BDRVRawState *s = bs->opaque; - close(s->fd); + if (s->fd >= 0) { + close(s->fd); + s->fd = -1; + } } static int raw_truncate(BlockDriverState *bs, int64_t offset) { BDRVRawState *s = bs->opaque; + if (s->type != FTYPE_FILE) + return -ENOTSUP; if (ftruncate(s->fd, offset) < 0) return -errno; return 0; @@ -428,6 +395,11 @@ static int64_t raw_getlength(BlockDriverState *bs) struct dk_minfo minfo; int rv; #endif + int ret; + + ret = fd_open(bs); + if (ret < 0) + return ret; #ifdef _BSD if (!fstat(fd, &sb) && (S_IFCHR & sb.st_mode)) { @@ -455,12 +427,6 @@ static int64_t raw_getlength(BlockDriverState *bs) { size = lseek(fd, 0, SEEK_END); } -#ifdef _WIN32 - /* On Windows hosts it can happen that we're unable to get file size - for CD-ROM raw device (it's inherent limitation of the CDFS driver). */ - if (size == -1) - size = LONG_LONG_MAX; -#endif return size; } @@ -509,13 +475,358 @@ BlockDriver bdrv_raw = { .bdrv_getlength = raw_getlength, }; +/***********************************************/ +/* host device */ + +#ifdef CONFIG_COCOA +static kern_return_t FindEjectableCDMedia( io_iterator_t *mediaIterator ); +static kern_return_t GetBSDPath( io_iterator_t mediaIterator, char *bsdPath, CFIndex maxPathSize ); + +kern_return_t FindEjectableCDMedia( io_iterator_t *mediaIterator ) +{ + kern_return_t kernResult; + mach_port_t masterPort; + CFMutableDictionaryRef classesToMatch; + + kernResult = IOMasterPort( MACH_PORT_NULL, &masterPort ); + if ( KERN_SUCCESS != kernResult ) { + printf( "IOMasterPort returned %d\n", kernResult ); + } + + classesToMatch = IOServiceMatching( kIOCDMediaClass ); + if ( classesToMatch == NULL ) { + printf( "IOServiceMatching returned a NULL dictionary.\n" ); + } else { + CFDictionarySetValue( classesToMatch, CFSTR( kIOMediaEjectableKey ), kCFBooleanTrue ); + } + kernResult = IOServiceGetMatchingServices( masterPort, classesToMatch, mediaIterator ); + if ( KERN_SUCCESS != kernResult ) + { + printf( "IOServiceGetMatchingServices returned %d\n", kernResult ); + } + + return kernResult; +} + +kern_return_t GetBSDPath( io_iterator_t mediaIterator, char *bsdPath, CFIndex maxPathSize ) +{ + io_object_t nextMedia; + kern_return_t kernResult = KERN_FAILURE; + *bsdPath = '\0'; + nextMedia = IOIteratorNext( mediaIterator ); + if ( nextMedia ) + { + CFTypeRef bsdPathAsCFString; + bsdPathAsCFString = IORegistryEntryCreateCFProperty( nextMedia, CFSTR( kIOBSDNameKey ), kCFAllocatorDefault, 0 ); + if ( bsdPathAsCFString ) { + size_t devPathLength; + strcpy( bsdPath, _PATH_DEV ); + strcat( bsdPath, "r" ); + devPathLength = strlen( bsdPath ); + if ( CFStringGetCString( bsdPathAsCFString, bsdPath + devPathLength, maxPathSize - devPathLength, kCFStringEncodingASCII ) ) { + kernResult = KERN_SUCCESS; + } + CFRelease( bsdPathAsCFString ); + } + IOObjectRelease( nextMedia ); + } + + return kernResult; +} + +#endif + +static int hdev_open(BlockDriverState *bs, const char *filename, int flags) +{ + BDRVRawState *s = bs->opaque; + int fd, open_flags, ret; + +#ifdef CONFIG_COCOA + if (strstart(filename, "/dev/cdrom", NULL)) { + kern_return_t kernResult; + io_iterator_t mediaIterator; + char bsdPath[ MAXPATHLEN ]; + int fd; + + kernResult = FindEjectableCDMedia( &mediaIterator ); + kernResult = GetBSDPath( mediaIterator, bsdPath, sizeof( bsdPath ) ); + + if ( bsdPath[ 0 ] != '\0' ) { + strcat(bsdPath,"s0"); + /* some CDs don't have a partition 0 */ + fd = open(bsdPath, O_RDONLY | O_BINARY | O_LARGEFILE); + if (fd < 0) { + bsdPath[strlen(bsdPath)-1] = '1'; + } else { + close(fd); + } + filename = bsdPath; + } + + if ( mediaIterator ) + IOObjectRelease( mediaIterator ); + } +#endif + open_flags = O_BINARY; + if ((flags & BDRV_O_ACCESS) == O_RDWR) { + open_flags |= O_RDWR; + } else { + open_flags |= O_RDONLY; + bs->read_only = 1; + } + + s->type = FTYPE_FILE; +#if defined(__linux__) + if (strstart(filename, "/dev/cd", NULL)) { + /* open will not fail even if no CD is inserted */ + open_flags |= O_NONBLOCK; + s->type = FTYPE_CD; + } else if (strstart(filename, "/dev/fd", NULL)) { + s->type = FTYPE_FD; + s->fd_open_flags = open_flags; + /* open will not fail even if no floppy is inserted */ + open_flags |= O_NONBLOCK; + } +#endif + fd = open(filename, open_flags, 0644); + if (fd < 0) { + ret = -errno; + if (ret == -EROFS) + ret = -EACCES; + return ret; + } + s->fd = fd; +#if defined(__linux__) + /* close fd so that we can reopen it as needed */ + if (s->type == FTYPE_FD) { + close(s->fd); + s->fd = -1; + s->fd_media_changed = 1; + } +#endif + return 0; +} + +#if defined(__linux__) && !defined(QEMU_TOOL) + +/* Note: we do not have a reliable method to detect if the floppy is + present. The current method is to try to open the floppy at every + I/O and to keep it opened during a few hundreds of ms. */ +static int fd_open(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + int last_media_present; + + if (s->type != FTYPE_FD) + return 0; + last_media_present = (s->fd >= 0); + if (s->fd >= 0 && + (qemu_get_clock(rt_clock) - s->fd_open_time) >= FD_OPEN_TIMEOUT) { + close(s->fd); + s->fd = -1; +#ifdef DEBUG_FLOPPY + printf("Floppy closed\n"); +#endif + } + if (s->fd < 0) { + if (s->fd_got_error && + (qemu_get_clock(rt_clock) - s->fd_error_time) < FD_OPEN_TIMEOUT) { +#ifdef DEBUG_FLOPPY + printf("No floppy (open delayed)\n"); +#endif + return -EIO; + } + s->fd = open(bs->filename, s->fd_open_flags); + if (s->fd < 0) { + s->fd_error_time = qemu_get_clock(rt_clock); + s->fd_got_error = 1; + if (last_media_present) + s->fd_media_changed = 1; +#ifdef DEBUG_FLOPPY + printf("No floppy\n"); +#endif + return -EIO; + } +#ifdef DEBUG_FLOPPY + printf("Floppy opened\n"); +#endif + } + if (!last_media_present) + s->fd_media_changed = 1; + s->fd_open_time = qemu_get_clock(rt_clock); + s->fd_got_error = 0; + return 0; +} +#else +static int fd_open(BlockDriverState *bs) +{ + return 0; +} +#endif + +#if defined(__linux__) + +static int raw_is_inserted(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + int ret; + + switch(s->type) { + case FTYPE_CD: + ret = ioctl(s->fd, CDROM_DRIVE_STATUS, CDSL_CURRENT); + if (ret == CDS_DISC_OK) + return 1; + else + return 0; + break; + case FTYPE_FD: + ret = fd_open(bs); + return (ret >= 0); + default: + return 1; + } +} + +/* currently only used by fdc.c, but a CD version would be good too */ +static int raw_media_changed(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + + switch(s->type) { + case FTYPE_FD: + { + int ret; + /* XXX: we do not have a true media changed indication. It + does not work if the floppy is changed without trying + to read it */ + fd_open(bs); + ret = s->fd_media_changed; + s->fd_media_changed = 0; +#ifdef DEBUG_FLOPPY + printf("Floppy changed=%d\n", ret); +#endif + return ret; + } + default: + return -ENOTSUP; + } +} + +static int raw_eject(BlockDriverState *bs, int eject_flag) +{ + BDRVRawState *s = bs->opaque; + + switch(s->type) { + case FTYPE_CD: + if (eject_flag) { + if (ioctl (s->fd, CDROMEJECT, NULL) < 0) + perror("CDROMEJECT"); + } else { + if (ioctl (s->fd, CDROMCLOSETRAY, NULL) < 0) + perror("CDROMEJECT"); + } + break; + case FTYPE_FD: + { + int fd; + if (s->fd >= 0) { + close(s->fd); + s->fd = -1; + } + fd = open(bs->filename, s->fd_open_flags | O_NONBLOCK); + if (fd >= 0) { + if (ioctl(fd, FDEJECT, 0) < 0) + perror("FDEJECT"); + close(fd); + } + } + break; + default: + return -ENOTSUP; + } + return 0; +} + +static int raw_set_locked(BlockDriverState *bs, int locked) +{ + BDRVRawState *s = bs->opaque; + + switch(s->type) { + case FTYPE_CD: + if (ioctl (s->fd, CDROM_LOCKDOOR, locked) < 0) { + /* Note: an error can happen if the distribution automatically + mounts the CD-ROM */ + // perror("CDROM_LOCKDOOR"); + } + break; + default: + return -ENOTSUP; + } + return 0; +} + +#else + +static int raw_is_inserted(BlockDriverState *bs) +{ + return 1; +} + +static int raw_media_changed(BlockDriverState *bs) +{ + return -ENOTSUP; +} + +static int raw_eject(BlockDriverState *bs, int eject_flag) +{ + return -ENOTSUP; +} + +static int raw_set_locked(BlockDriverState *bs, int locked) +{ + return -ENOTSUP; +} + +#endif /* !linux */ + +BlockDriver bdrv_host_device = { + "host_device", + sizeof(BDRVRawState), + NULL, /* no probe for protocols */ + hdev_open, + NULL, + NULL, + raw_close, + NULL, + raw_flush, + + .bdrv_aio_read = raw_aio_read, + .bdrv_aio_write = raw_aio_write, + .bdrv_aio_cancel = raw_aio_cancel, + .aiocb_size = sizeof(RawAIOCB), + .bdrv_pread = raw_pread, + .bdrv_pwrite = raw_pwrite, + .bdrv_getlength = raw_getlength, + + /* removable device support */ + .bdrv_is_inserted = raw_is_inserted, + .bdrv_media_changed = raw_media_changed, + .bdrv_eject = raw_eject, + .bdrv_set_locked = raw_set_locked, +}; + #else /* _WIN32 */ /* XXX: use another file ? */ #include +#define FTYPE_FILE 0 +#define FTYPE_CD 1 + typedef struct BDRVRawState { HANDLE hfile; + int type; + char drive_letter[2]; } BDRVRawState; typedef struct RawAIOCB { @@ -565,6 +876,23 @@ static int raw_open(BlockDriverState *bs, const char *filename, int flags) BDRVRawState *s = bs->opaque; int access_flags, create_flags; DWORD overlapped; + char device_name[64]; + const char *p; + + if (strstart(filename, "/dev/cdrom", NULL)) { + if (find_cdrom(device_name, sizeof(device_name)) < 0) + return -ENOENT; + filename = device_name; + } else { + /* transform drive letters into device name */ + if (((filename[0] >= 'a' && filename[0] <= 'z') || + (filename[0] >= 'A' && filename[0] <= 'Z')) && + filename[1] == ':' && filename[2] == '\0') { + snprintf(device_name, sizeof(device_name), "\\\\.\\%c:", filename[0]); + filename = device_name; + } + } + s->type = find_device_type(filename); if ((flags & BDRV_O_ACCESS) == O_RDWR) { access_flags = GENERIC_READ | GENERIC_WRITE; @@ -765,10 +1093,22 @@ static int64_t raw_getlength(BlockDriverState *bs) { BDRVRawState *s = bs->opaque; LARGE_INTEGER l; + ULARGE_INTEGER available, total, total_free; - l.LowPart = GetFileSize(s->hfile, &l.HighPart); - if (l.LowPart == 0xffffffffUL && GetLastError() != NO_ERROR) - return -EIO; + switch(s->ftype) { + case FTYPE_FILE: + l.LowPart = GetFileSize(s->hfile, &l.HighPart); + if (l.LowPart == 0xffffffffUL && GetLastError() != NO_ERROR) + return -EIO; + break; + case FTYPE_CD: + if (!GetDiskFreeSpaceEx(s->drive_letter, &available, &total, &total_free)) + return -EIO; + l = total; + break; + default: + return -EIO; + } return l.QuadPart; } @@ -833,4 +1173,146 @@ BlockDriver bdrv_raw = { .bdrv_truncate = raw_truncate, .bdrv_getlength = raw_getlength, }; + +/***********************************************/ +/* host device */ + +static int find_cdrom(char *cdrom_name, int cdrom_name_size) +{ + char drives[256], *pdrv = drives; + UINT type; + + memset(drives, 0, sizeof(drivers)); + GetLogicalDriveStrings(sizeof(drives), drives); + while(pdrv[0] != '\0') { + type = GetDriveType(pdrv); + switch(type) { + case DRIVE_CDROM: + snprintf(cdrom_name, cdrom_name_size, "\\\\.\\%c:", pdrv[0]); + return 0; + break; + } + pdrv += lstrlen(pdrv) + 1; + } + return -1; +} + +static int find_device_type(const char *filename) +{ + UINT type; + const char *p; + + if (strstart(filename, "\\\\.\\", &p) || + strstart(filename, "//./", &p)) { + s->drive_letter[0] = p[0]; + s->drive_letter[1] = '\0'; + type = GetDriveType(s->drive_letter); + if (type == DRIVE_CDROM) + return FTYPE_CD; + else + return FTYPE_FILE; + } else { + return FTYPE_FILE; + } +} + +static int hdev_open(BlockDriverState *bs, const char *filename, int flags) +{ + BDRVRawState *s = bs->opaque; + int access_flags, create_flags; + DWORD overlapped; + char device_name[64]; + const char *p; + + if (strstart(filename, "/dev/cdrom", NULL)) { + if (find_cdrom(device_name, sizeof(device_name)) < 0) + return -ENOENT; + filename = device_name; + } else { + /* transform drive letters into device name */ + if (((filename[0] >= 'a' && filename[0] <= 'z') || + (filename[0] >= 'A' && filename[0] <= 'Z')) && + filename[1] == ':' && filename[2] == '\0') { + snprintf(device_name, sizeof(device_name), "\\\\.\\%c:", filename[0]); + filename = device_name; + } + } + s->type = find_device_type(filename); + + if ((flags & BDRV_O_ACCESS) == O_RDWR) { + access_flags = GENERIC_READ | GENERIC_WRITE; + } else { + access_flags = GENERIC_READ; + } + create_flags = OPEN_EXISTING; + +#ifdef QEMU_TOOL + overlapped = 0; +#else + overlapped = FILE_FLAG_OVERLAPPED; +#endif + s->hfile = CreateFile(filename, access_flags, + FILE_SHARE_READ, NULL, + create_flags, overlapped, 0); + if (s->hfile == INVALID_HANDLE_VALUE) + return -1; + return 0; +} + +#if 0 +/***********************************************/ +/* removable device additionnal commands */ + +static int raw_is_inserted(BlockDriverState *bs) +{ + return 1; +} + +static int raw_media_changed(BlockDriverState *bs) +{ + return -ENOTSUP; +} + +static int raw_eject(BlockDriverState *bs, int eject_flag) +{ + DWORD ret_count; + + if (s->type == FTYPE_FILE) + return -ENOTSUP; + if (eject_flag) { + DeviceIoControl(s->hfile, IOCTL_STORAGE_EJECT_MEDIA, + NULL, 0, NULL, 0, &lpBytesReturned, NULL); + } else { + DeviceIoControl(s->hfile, IOCTL_STORAGE_LOAD_MEDIA, + NULL, 0, NULL, 0, &lpBytesReturned, NULL); + } +} + +static int raw_set_locked(BlockDriverState *bs, int locked) +{ + return -ENOTSUP; +} +#endif + +BlockDriver bdrv_host_device = { + "host_device", + sizeof(BDRVRawState), + NULL, /* no probe for protocols */ + hdev_open, + NULL, + NULL, + raw_close, + NULL, + raw_flush, + +#if 0 + .bdrv_aio_read = raw_aio_read, + .bdrv_aio_write = raw_aio_write, + .bdrv_aio_cancel = raw_aio_cancel, + .aiocb_size = sizeof(RawAIOCB); +#endif + .bdrv_pread = raw_pread, + .bdrv_pwrite = raw_pwrite, + .bdrv_getlength = raw_getlength, +}; #endif /* _WIN32 */ diff --git a/block.c b/block.c index d81451058e..885b700236 100644 --- a/block.c +++ b/block.c @@ -181,24 +181,37 @@ void get_tmp_filename(char *filename, int size) } #endif +#ifdef _WIN32 +static int is_windows_drive(const char *filename) +{ + if (((filename[0] >= 'a' && filename[0] <= 'z') || + (filename[0] >= 'A' && filename[0] <= 'Z')) && + filename[1] == ':' && 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)) + return &bdrv_raw; +#endif p = strchr(filename, ':'); if (!p) return &bdrv_raw; len = p - filename; if (len > sizeof(protocol) - 1) len = sizeof(protocol) - 1; -#ifdef _WIN32 - if (len == 1) { - /* specific win32 case for driver letters */ - return &bdrv_raw; - } -#endif memcpy(protocol, filename, len); protocol[len] = '\0'; for(drv1 = first_drv; drv1 != NULL; drv1 = drv1->next) { @@ -218,14 +231,28 @@ static BlockDriver *find_image_format(const char *filename) uint8_t buf[2048]; BlockDriverState *bs; + /* detect host devices. By convention, /dev/cdrom[N] is always + recognized as a host CDROM */ + if (strstart(filename, "/dev/cdrom", NULL)) + return &bdrv_host_device; +#ifdef _WIN32 + if (is_windows_drive(filename)) + return &bdrv_host_device; +#else + { + struct stat st; + if (stat(filename, &st) >= 0 && + (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode))) { + return &bdrv_host_device; + } + } +#endif + drv = find_protocol(filename); - /* no need to test disk image formats for vvfat or host specific - devices */ + /* no need to test disk image formats for vvfat */ if (drv == &bdrv_vvfat) return drv; - if (strstart(filename, "/dev/", NULL)) - return &bdrv_raw; - + ret = bdrv_file_open(&bs, filename, BDRV_O_RDONLY); if (ret < 0) return NULL; @@ -362,9 +389,8 @@ int bdrv_open2(BlockDriverState *bs, const char *filename, int flags, goto fail; } - bs->inserted = 1; - /* call the change callback */ + bs->media_changed = 1; if (bs->change_cb) bs->change_cb(bs->change_opaque); @@ -373,7 +399,7 @@ int bdrv_open2(BlockDriverState *bs, const char *filename, int flags, void bdrv_close(BlockDriverState *bs) { - if (bs->inserted) { + if (bs->drv) { if (bs->backing_hd) bdrv_delete(bs->backing_hd); bs->drv->bdrv_close(bs); @@ -385,9 +411,9 @@ void bdrv_close(BlockDriverState *bs) #endif bs->opaque = NULL; bs->drv = NULL; - bs->inserted = 0; /* call the change callback */ + bs->media_changed = 1; if (bs->change_cb) bs->change_cb(bs->change_opaque); } @@ -403,12 +429,13 @@ void bdrv_delete(BlockDriverState *bs) /* commit COW file into the raw image */ int bdrv_commit(BlockDriverState *bs) { + BlockDriver *drv = bs->drv; int64_t i, total_sectors; int n, j; unsigned char sector[512]; - if (!bs->inserted) - return -ENOENT; + if (!drv) + return -ENOMEDIUM; if (bs->read_only) { return -EACCES; @@ -420,7 +447,7 @@ int bdrv_commit(BlockDriverState *bs) total_sectors = bdrv_getlength(bs) >> SECTOR_BITS; for (i = 0; i < total_sectors;) { - if (bs->drv->bdrv_is_allocated(bs, i, 65536, &n)) { + if (drv->bdrv_is_allocated(bs, i, 65536, &n)) { for(j = 0; j < n; j++) { if (bdrv_read(bs, i, sector, 1) != 0) { return -EIO; @@ -436,20 +463,20 @@ int bdrv_commit(BlockDriverState *bs) } } - if (bs->drv->bdrv_make_empty) - return bs->drv->bdrv_make_empty(bs); + if (drv->bdrv_make_empty) + return drv->bdrv_make_empty(bs); return 0; } -/* return < 0 if error */ +/* 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 (!bs->inserted) - return -1; + if (!drv) + return -ENOMEDIUM; if (sector_num == 0 && bs->boot_sector_enabled && nb_sectors > 0) { memcpy(buf, bs->boot_sector_data, 512); @@ -466,7 +493,7 @@ int bdrv_read(BlockDriverState *bs, int64_t sector_num, if (ret < 0) return ret; else if (ret != len) - return -EIO; + return -EINVAL; else return 0; } else { @@ -474,15 +501,20 @@ int bdrv_read(BlockDriverState *bs, int64_t sector_num, } } -/* return < 0 if error */ +/* 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 +*/ int bdrv_write(BlockDriverState *bs, int64_t sector_num, const uint8_t *buf, int nb_sectors) { BlockDriver *drv = bs->drv; - if (!bs->inserted) - return -1; + if (!bs->drv) + return -ENOMEDIUM; if (bs->read_only) - return -1; + return -EACCES; if (sector_num == 0 && bs->boot_sector_enabled && nb_sectors > 0) { memcpy(bs->boot_sector_data, buf, 512); } @@ -501,7 +533,6 @@ int bdrv_write(BlockDriverState *bs, int64_t sector_num, } } -/* not necessary now */ static int bdrv_pread_em(BlockDriverState *bs, int64_t offset, uint8_t *buf, int count1) { @@ -603,7 +634,7 @@ int bdrv_pread(BlockDriverState *bs, int64_t offset, BlockDriver *drv = bs->drv; if (!drv) - return -ENOENT; + return -ENOMEDIUM; if (!drv->bdrv_pread) return bdrv_pread_em(bs, offset, buf1, count1); return drv->bdrv_pread(bs, offset, buf1, count1); @@ -618,7 +649,7 @@ int bdrv_pwrite(BlockDriverState *bs, int64_t offset, BlockDriver *drv = bs->drv; if (!drv) - return -ENOENT; + return -ENOMEDIUM; if (!drv->bdrv_pwrite) return bdrv_pwrite_em(bs, offset, buf1, count1); return drv->bdrv_pwrite(bs, offset, buf1, count1); @@ -631,7 +662,7 @@ int bdrv_truncate(BlockDriverState *bs, int64_t offset) { BlockDriver *drv = bs->drv; if (!drv) - return -ENOENT; + return -ENOMEDIUM; if (!drv->bdrv_truncate) return -ENOTSUP; return drv->bdrv_truncate(bs, offset); @@ -644,7 +675,7 @@ int64_t bdrv_getlength(BlockDriverState *bs) { BlockDriver *drv = bs->drv; if (!drv) - return -ENOENT; + return -ENOMEDIUM; if (!drv->bdrv_getlength) { /* legacy mode */ return bs->total_sectors * SECTOR_SIZE; @@ -652,9 +683,16 @@ int64_t bdrv_getlength(BlockDriverState *bs) return drv->bdrv_getlength(bs); } +/* return 0 as number of sectors if no device present or error */ void bdrv_get_geometry(BlockDriverState *bs, int64_t *nb_sectors_ptr) { - *nb_sectors_ptr = bs->total_sectors; + int64_t length; + length = bdrv_getlength(bs); + if (length < 0) + length = 0; + else + length = length >> SECTOR_BITS; + *nb_sectors_ptr = length; } /* force a given boot sector. */ @@ -715,21 +753,7 @@ int bdrv_is_read_only(BlockDriverState *bs) return bs->read_only; } -int bdrv_is_inserted(BlockDriverState *bs) -{ - return bs->inserted; -} - -int bdrv_is_locked(BlockDriverState *bs) -{ - return bs->locked; -} - -void bdrv_set_locked(BlockDriverState *bs, int locked) -{ - bs->locked = locked; -} - +/* XXX: no longer used */ void bdrv_set_change_cb(BlockDriverState *bs, void (*change_cb)(void *opaque), void *opaque) { @@ -761,7 +785,7 @@ int bdrv_set_key(BlockDriverState *bs, const char *key) void bdrv_get_format(BlockDriverState *bs, char *buf, int buf_size) { - if (!bs->inserted || !bs->drv) { + if (!bs->drv) { buf[0] = '\0'; } else { pstrcpy(buf, buf_size, bs->drv->format_name); @@ -833,7 +857,7 @@ void bdrv_info(void) if (bs->removable) { term_printf(" locked=%d", bs->locked); } - if (bs->inserted) { + if (bs->drv) { term_printf(" file=%s", bs->filename); if (bs->backing_file[0] != '\0') term_printf(" backing_file=%s", bs->backing_file); @@ -863,7 +887,7 @@ int bdrv_write_compressed(BlockDriverState *bs, int64_t sector_num, { BlockDriver *drv = bs->drv; if (!drv) - return -ENOENT; + return -ENOMEDIUM; if (!drv->bdrv_write_compressed) return -ENOTSUP; return drv->bdrv_write_compressed(bs, sector_num, buf, nb_sectors); @@ -873,7 +897,7 @@ int bdrv_get_info(BlockDriverState *bs, BlockDriverInfo *bdi) { BlockDriver *drv = bs->drv; if (!drv) - return -ENOENT; + return -ENOMEDIUM; if (!drv->bdrv_get_info) return -ENOTSUP; memset(bdi, 0, sizeof(*bdi)); @@ -888,7 +912,7 @@ int bdrv_snapshot_create(BlockDriverState *bs, { BlockDriver *drv = bs->drv; if (!drv) - return -ENOENT; + return -ENOMEDIUM; if (!drv->bdrv_snapshot_create) return -ENOTSUP; return drv->bdrv_snapshot_create(bs, sn_info); @@ -899,7 +923,7 @@ int bdrv_snapshot_goto(BlockDriverState *bs, { BlockDriver *drv = bs->drv; if (!drv) - return -ENOENT; + return -ENOMEDIUM; if (!drv->bdrv_snapshot_goto) return -ENOTSUP; return drv->bdrv_snapshot_goto(bs, snapshot_id); @@ -909,7 +933,7 @@ int bdrv_snapshot_delete(BlockDriverState *bs, const char *snapshot_id) { BlockDriver *drv = bs->drv; if (!drv) - return -ENOENT; + return -ENOMEDIUM; if (!drv->bdrv_snapshot_delete) return -ENOTSUP; return drv->bdrv_snapshot_delete(bs, snapshot_id); @@ -920,7 +944,7 @@ int bdrv_snapshot_list(BlockDriverState *bs, { BlockDriver *drv = bs->drv; if (!drv) - return -ENOENT; + return -ENOMEDIUM; if (!drv->bdrv_snapshot_list) return -ENOTSUP; return drv->bdrv_snapshot_list(bs, psn_info); @@ -1001,7 +1025,7 @@ BlockDriverAIOCB *bdrv_aio_read(BlockDriverState *bs, int64_t sector_num, { BlockDriver *drv = bs->drv; - if (!bs->inserted) + if (!drv) return NULL; /* XXX: we assume that nb_sectors == 0 is suppored by the async read */ @@ -1021,7 +1045,7 @@ BlockDriverAIOCB *bdrv_aio_write(BlockDriverState *bs, int64_t sector_num, { BlockDriver *drv = bs->drv; - if (!bs->inserted) + if (!drv) return NULL; if (bs->read_only) return NULL; @@ -1170,6 +1194,7 @@ static int bdrv_write_em(BlockDriverState *bs, int64_t sector_num, void bdrv_init(void) { bdrv_register(&bdrv_raw); + bdrv_register(&bdrv_host_device); #ifndef _WIN32 bdrv_register(&bdrv_cow); #endif @@ -1211,3 +1236,78 @@ void qemu_aio_release(void *p) acb->next = drv->free_aiocb; drv->free_aiocb = acb; } + +/**************************************************************/ +/* removable device support */ + +/** + * Return TRUE if the media is present + */ +int bdrv_is_inserted(BlockDriverState *bs) +{ + BlockDriver *drv = bs->drv; + int ret; + if (!drv) + return 0; + if (!drv->bdrv_is_inserted) + return 1; + ret = drv->bdrv_is_inserted(bs); + return ret; +} + +/** + * Return TRUE if the media changed since the last call to this + * function. It is currently only used for floppy disks + */ +int bdrv_media_changed(BlockDriverState *bs) +{ + BlockDriver *drv = bs->drv; + int ret; + + if (!drv || !drv->bdrv_media_changed) + ret = -ENOTSUP; + else + ret = drv->bdrv_media_changed(bs); + if (ret == -ENOTSUP) + ret = bs->media_changed; + bs->media_changed = 0; + return ret; +} + +/** + * If eject_flag is TRUE, eject the media. Otherwise, close the tray + */ +void bdrv_eject(BlockDriverState *bs, int eject_flag) +{ + BlockDriver *drv = bs->drv; + int ret; + + if (!drv || !drv->bdrv_eject) { + ret = -ENOTSUP; + } else { + ret = drv->bdrv_eject(bs, eject_flag); + } + if (ret == -ENOTSUP) { + if (eject_flag) + bdrv_close(bs); + } +} + +int bdrv_is_locked(BlockDriverState *bs) +{ + return bs->locked; +} + +/** + * Lock or unlock the media (if it is locked, the user won't be able + * to eject it manually). + */ +void bdrv_set_locked(BlockDriverState *bs, int locked) +{ + BlockDriver *drv = bs->drv; + + bs->locked = locked; + if (drv && drv->bdrv_set_locked) { + drv->bdrv_set_locked(bs, locked); + } +} diff --git a/block_int.h b/block_int.h index 8fd241f2a9..25f6717b5c 100644 --- a/block_int.h +++ b/block_int.h @@ -70,6 +70,12 @@ struct BlockDriver { QEMUSnapshotInfo **psn_info); int (*bdrv_get_info)(BlockDriverState *bs, BlockDriverInfo *bdi); + /* removable device specific */ + int (*bdrv_is_inserted)(BlockDriverState *bs); + int (*bdrv_media_changed)(BlockDriverState *bs); + int (*bdrv_eject)(BlockDriverState *bs, int eject_flag); + int (*bdrv_set_locked)(BlockDriverState *bs, int locked); + BlockDriverAIOCB *free_aiocb; struct BlockDriver *next; }; @@ -78,7 +84,6 @@ struct BlockDriverState { int64_t total_sectors; /* if we are reading a disk image, give its size in sectors */ int read_only; /* if true, the media is read only */ - int inserted; /* if true, the media is present */ int removable; /* if true, the media can be removed */ int locked; /* if true, the media cannot temporarily be ejected */ int encrypted; /* if true, the media is encrypted */ @@ -86,7 +91,7 @@ struct BlockDriverState { void (*change_cb)(void *opaque); void *change_opaque; - BlockDriver *drv; + BlockDriver *drv; /* NULL means no media */ void *opaque; int boot_sector_enabled; @@ -96,7 +101,8 @@ struct BlockDriverState { char backing_file[1024]; /* if non zero, the image is a diff of this file image */ int is_temporary; - + int media_changed; + BlockDriverState *backing_hd; /* async read/write emulation */ diff --git a/qemu-doc.texi b/qemu-doc.texi index a9d9747b18..40667e3f42 100644 --- a/qemu-doc.texi +++ b/qemu-doc.texi @@ -206,7 +206,7 @@ Select the emulated machine (@code{-M ?} for list) @item -fda file @item -fdb file Use @var{file} as floppy disk 0/1 image (@pxref{disk_images}). You can -use the host floppy by using @file{/dev/fd0} as filename. +use the host floppy by using @file{/dev/fd0} as filename (@pxref{host_drives}). @item -hda file @item -hdb file @@ -217,7 +217,7 @@ Use @var{file} as hard disk 0, 1, 2 or 3 image (@pxref{disk_images}). @item -cdrom file Use @var{file} as CD-ROM image (you cannot use @option{-hdc} and and @option{-cdrom} at the same time). You can use the host CD-ROM by -using @file{/dev/cdrom} as filename. +using @file{/dev/cdrom} as filename (@pxref{host_drives}). @item -boot [a|c|d] Boot on floppy (a), hard disk (c) or CD-ROM (d). Hard disk boot is @@ -916,6 +916,7 @@ snapshots. * disk_images_snapshot_mode:: Snapshot mode * vm_snapshots:: VM snapshots * qemu_img_invocation:: qemu-img Invocation +* host_drives:: Using host drives * disk_images_fat_images:: Virtual FAT disk images @end menu @@ -997,6 +998,57 @@ state is not saved or restored properly (in particular USB). @include qemu-img.texi +@node host_drives +@subsection Using host drives + +In addition to disk image files, QEMU can directly access host +devices. We describe here the usage for QEMU version >= 0.8.3. + +@subsubsection Linux + +On Linux, you can directly use the host device filename instead of a +disk image filename provided you have enough proviledge to access +it. For example, use @file{/dev/cdrom} to access to the CDROM or +@file{/dev/fd0} for the floppy. + +@table +@item CD +You can specify a CDROM device even if no CDROM is loaded. QEMU has +specific code to detect CDROM insertion or removal. CDROM ejection by +the guest OS is supported. Currently only data CDs are supported. +@item Floppy +You can specify a floppy device even if no floppy is loaded. Floppy +removal is currently not detected accurately (if you change floppy +without doing floppy access while the floppy is not loaded, the guest +OS will think that the same floppy is loaded). +@item Hard disks +Hard disks can be used. Normally you must specify the whole disk +(@file{/dev/hdb} instead of @file{/dev/hdb1}) so that the guest OS can +see it as a partitioned disk. WARNING: unless you know what you do, it +is better to only make READ-ONLY accesses to the hard disk otherwise +you may corrupt your host data (use the @option{-snapshot} command +line option or modify the device permissions accordingly). +@end table + +@subsubsection Windows + +On Windows you can use any host drives as QEMU drive. The prefered +syntax is the driver letter (e.g. @file{d:}). The alternate syntax +@file{\\.\d:} is supported. @file{/dev/cdrom} is supported as an alias +to the first CDROM drive. + +Currently there is no specific code to handle removable medias, so it +is better to use the @code{change} or @code{eject} monitor commands to +change or eject media. + +@subsubsection Mac OS X + +@file{/dev/cdrom} is an alias to the first CDROM. + +Currently there is no specific code to handle removable medias, so it +is better to use the @code{change} or @code{eject} monitor commands to +change or eject media. + @node disk_images_fat_images @subsection Virtual FAT disk images diff --git a/vl.h b/vl.h index 8a87a0a0dd..4bef80192c 100644 --- a/vl.h +++ b/vl.h @@ -50,6 +50,7 @@ #define fsync _commit #define lseek _lseeki64 #define ENOTSUP 4096 +#define ENOMEDIUM 4097 extern int qemu_ftruncate64(int, int64_t); #define ftruncate qemu_ftruncate64 @@ -502,6 +503,7 @@ typedef struct BlockDriverState BlockDriverState; typedef struct BlockDriver BlockDriver; extern BlockDriver bdrv_raw; +extern BlockDriver bdrv_host_device; extern BlockDriver bdrv_cow; extern BlockDriver bdrv_qcow; extern BlockDriver bdrv_vmdk; @@ -604,8 +606,10 @@ int bdrv_get_translation_hint(BlockDriverState *bs); int bdrv_is_removable(BlockDriverState *bs); int bdrv_is_read_only(BlockDriverState *bs); int bdrv_is_inserted(BlockDriverState *bs); +int bdrv_media_changed(BlockDriverState *bs); int bdrv_is_locked(BlockDriverState *bs); void bdrv_set_locked(BlockDriverState *bs, int locked); +void bdrv_eject(BlockDriverState *bs, int eject_flag); void bdrv_set_change_cb(BlockDriverState *bs, void (*change_cb)(void *opaque), void *opaque); void bdrv_get_format(BlockDriverState *bs, char *buf, int buf_size);