/* Phyvhd.c Physical I/O for VHD format disks */ /* Timothe Litt March 2016 * Copyright (C) 2016 Timothe litt * litt at acm dot org * * Free for use with the ODS2 package. All other rights reserved. */ /* * This is distributed as part of ODS2, originally written by * Paul Nankervis, email address: Paulnank@au1.ibm.com * * ODS2 is distributed freely for all members of the * VMS community to use. However all derived works * must maintain comments in their source to acknowledge * the contributions of the original author and * subsequent contributors. This is free software; no * warranty is offered, and while we believe it to be useful, * you use it at your own risk. */ #if !defined( DEBUG ) && defined( DEBUG_PHYVHD ) #define DEBUG DEBUG_PHYVHD #else #ifndef DEBUG #define DEBUG 0 #endif #endif #ifndef phyvhd_BUFSIZE #define phyvhd_BUFSIZE (10 * 512) #endif #ifndef phyvhd_MINSIZE #define phyvhd_MINSIZE (0) #endif #include #include #include #include #include #include #ifdef _WIN32 #include #define close _close #else #include #define _aligned_free free #endif #include "compat.h" #include "device.h" #include "ods2.h" #include "phyvirt.h" #include "ssdef.h" #include "stsdef.h" #include "sysmsg.h" #include "libvhd.h" #include "phyvhd.h" typedef vhd_context_t *vhd_contextp; typedef vhd_context_t vhd_context; #ifdef USE_LIBVHD #include #include #include #include #include typedef int (*vhd_open_p)( vhd_contextp ctx, const char *filename, int flags ); typedef void (*vhd_close_p)( vhd_contextp ctx ); typedef int (*vhd_create_p)(const char *file, uint64_t size, int type, uint32_t flags ); typedef int vhd_header_decode_parent(vhd_contextp ctx, vhd_header_t *header, char **buf); typedef int (*vhd_snapshot_p)( const char *filename, const char *parent, uint64_t size, const char *parentfile, uint32_t flags ); typedef int (*vhd_io_read_p)(vhd_contextp ctx, void *buf, uint64_t pbn, uint32_t count ); typedef int (*vhd_io_write_p)(vhd_contextp ctx, void *buf, uint64_t pbn, uint32_t count); typedef void (*libvhd_set_log_level_p)(int); typedef uint32_t (*vhd_chs_p)(uint64_t size); /* *** */ static void *libvhd = NULL; #define vhddeclare(s) s ## _p s ## _fcn = NULL vhddeclare(vhd_open); vhddeclare(vhd_close); vhddeclare(vhd_create); vhddeclare(libvhd_set_log_level); vhddeclare(vhd_header_decode_parent); vhddeclare(vhd_snapshot); #if 0 vhddeclare(vhd_chs); #endif vhddeclare(vhd_io_read); vhddeclare(vhd_io_write); #define vhdresolve(s) do { \ if( libvhd != NULL && \ (s ## _fcn = (s ## _p)dlsym( libvhd, #s )) == NULL ) { \ printmsg( IO_VHDNOSYM, 0, #s, LIBVHDSO(USE_LIBVHD) ); \ dlclose( libvhd ); \ libvhd = NULL; \ } \ } while( 0 ) #else #define vhd_open_fcn vhd_open #define vhd_close_fcn vhd_close #define vhd_create_fcn vhd_create #define libvhd_set_log_level_fcn libvhd_set_log_level #define vhd_header_decode_parent_fcn vhd_header_decode_parent #define vhd_snapshot_fcn vhd_snapshot #if 0 #define vhd_chs_fcn vhd_chs #endif #define vhd_io_read_fcn vhd_io_read #define vhd_io_write_fcn vhd_io_write #endif static vmscond_t phyvhd_read( struct DEV *dev, uint32_t lbn, uint32_t length, char *buffer ); static vmscond_t phyvhd_write( struct DEV *dev, uint32_t lbn, uint32_t length, const char *buffer ); /*********************************************************** phyvhd_available() */ int phyvhd_available( int query ) { #ifdef USE_LIBVHD #define LIBVHDSOx(x) #x #define LIBVHDSO(n) LIBVHDSOx(n) if( libvhd == NULL ) { if( (libvhd = dlopen( LIBVHDSO(USE_LIBVHD), RTLD_LAZY )) == NULL ) { const char *err; if( query ) printmsg( IO_VHDNOTAVAIL, MSG_TEXT ); printmsg( IO_VHDNOLIB, 0, LIBVHDSO(USE_LIBVHD) ); if( (err = dlerror()) != NULL ) printmsg( ODS2_OSERROR, MSG_CONTINUE, O_VHDNOLIB, err ); return FALSE; } vhdresolve( vhd_open ); vhdresolve( vhd_close ); vhdresolve( vhd_create ); vhdresolve( vhd_snapshot ); vhdresolve( libvhd_set_log_level ); vhdresolve( vhd_header_decode_parent ); vhdresolve( vhd_io_read ); vhdresolve( vhd_io_write ); if( libvhd == NULL ) { printmsg( IO_VHDNOTVALID, MSG_CONTINUE, IO_VHDNOSYM, LIBVHDSO(USE_LIBVHD) ); return FALSE; } } #endif if( query ) printmsg( IO_VHDAVAIL, MSG_TEXT ); return TRUE; } #ifdef _WIN32 #pragma warning(push) #pragma warning(disable: 4996) /* MS complains about _open, which is open() */ #endif /*********************************************************** phyvhd_snapshot() */ vmscond_t phyvhd_snapshot( const char *filename, const char *parent ) { int fd, err; char *p; if( !( (p = strrchr( filename, '.')) != NULL && ( !strcmp( p, ".vhd" ) || !strcmp( p, ".VHD" ) ) ) ) { return printmsg( IO_VHDONLY, 0, filename ); } #ifdef USE_LIBVHD if( libvhd == NULL && !phyvhd_available( FALSE ) ) return IO_VHDNOTVALID | STS$M_INHIB_MSG; #endif #ifdef _WIN32 fd = _open( filename, _O_BINARY | _O_RDWR | _O_CREAT | _O_EXCL, 0644 ); #else fd = open( filename, O_RDWR | O_CREAT | O_EXCL, 0644 ); #endif if( fd == -1 ) { int err; if( errno == EEXIST ) { printmsg( IO_EXISTS, 0, filename ); return SS$_DUPFILENAME | STS$M_INHIB_MSG; } err = errno; printmsg( IO_OPENDEV, 0, filename ); return printmsg( ODS2_OSERROR, MSG_CONTINUE, IO_OPENDEV, strerror( err ) ); } err = vhd_snapshot_fcn( filename, 0, parent, 0 ); close( fd ); if( err == 0 ) return SS$_NORMAL; printmsg( IO_VHDSNAPFAIL, 0, parent ); return printmsg( ODS2_OSERROR, MSG_CONTINUE, IO_VHDSNAPFAIL, strerror( -err ) ); } /*********************************************************** phyvhd_init() */ vmscond_t phyvhd_init( struct DEV *dev ) { int err; char *fname; unsigned status; long align; #ifdef _WIN32 SYSTEM_INFO inf; GetSystemInfo( &inf ); align = inf.dwPageSize; #elif defined( _SC_PAGESIZE) align = sysconf( _SC_PAGESIZE ); #else align = getpagesize(); #endif #ifdef USE_LIBVHD if( libvhd == NULL && !phyvhd_available( FALSE ) ) return IO_VHDNOTVALID | STS$M_INHIB_MSG; #endif libvhd_set_log_level_fcn( -1 ); fname = virt_lookup( dev->devnam ); if( fname == NULL ) return SS$_NOSUCHDEV; dev->devread = phyvhd_read; dev->devwrite = phyvhd_write; #ifdef _WIN32 if( (dev->IoBuffer = _aligned_malloc( phyvhd_BUFSIZE, align )) == NULL ) { return printmsg( IO_VHDMEM, 0 ); } if( (dev->context = _aligned_malloc( sizeof( vhd_context ), align )) == NULL ) { _aligned_free(dev->IoBuffer); dev->IoBuffer = NULL; return printmsg( IO_VHDMEM, 0 ); } #else if( posix_memalign( (void**)&dev->IoBuffer, align, phyvhd_BUFSIZE ) != 0 ) { return printmsg( IO_VHDMEM, 0 ); } if( posix_memalign( (void**)&dev->context, align, sizeof( vhd_context ) ) != 0 ) { free(dev->IoBuffer); dev->IoBuffer = NULL; return printmsg( IO_VHDMEM, 0 ); } #endif status = SS$_NORMAL; do { if( dev->access & PHY_CREATE ) { disktypep_t dp; uint64_t size; int fd; #ifdef _WIN32 fd = _open( fname, _O_BINARY | _O_RDWR | _O_CREAT | _O_EXCL, 0644 ); #else fd = open( fname, O_RDWR | O_CREAT | O_EXCL, 0644 ); #endif if( fd == -1 ) { status = SS$_DUPFILENAME; break; } if( dev->access & MOU_LOG ) printmsg( IO_VHDFMTSTART, 0, fname ); dp = dev->disktype; size = ((uint64_t)dp->sectors) * dp->tracks * dp->cylinders * dp->sectorsize; size = (size + 511) / 512; size *= 512; #if phyvhd_MINSIZE == 0 err = vhd_create_fcn( fname, size, HD_TYPE_DYNAMIC, 0 ); #else err = vhd_create_fcn( fname, size, (size >= phyvhd_MINSIZE? HD_TYPE_DYNAMIC: HD_TYPE_FIXED), 0 ); #endif close( fd ); if( err != 0 ) { printmsg( IO_VHDCREATEERR, 0, fname ); status = printmsg( ODS2_OSERROR, MSG_CONTINUE, IO_VHDCREATEERR, strerror( -err ) ); break; } if( dev->access & MOU_LOG ) printmsg( IO_VHDFMTEND, 0 ); } err = vhd_open_fcn( dev->context, fname, (dev->access & MOU_WRITE)? VHD_OPEN_RDWR | VHD_OPEN_STRICT : VHD_OPEN_RDONLY | VHD_OPEN_STRICT ); if( err != 0 && (dev->access & (MOU_WRITE|PHY_CREATE)) == MOU_WRITE ) { dev->access &= ~MOU_WRITE; err = vhd_open_fcn( dev->context, fname, VHD_OPEN_RDONLY | VHD_OPEN_STRICT ); } if( err != 0 ) { printmsg( IO_OPENDEV, 0, fname ); status = printmsg( ODS2_OSERROR, MSG_CONTINUE, IO_OPENDEV, strerror( -err ) ); break; } if( (dev->access & MOU_WRITE) && ((vhd_contextp)dev->context)->footer.saved ) { printmsg( IO_VHDSAVED, 0, fname ); } dev->eofptr = (off_t)(((vhd_contextp)dev->context)->footer.curr_size); } while( 0 ); if( $FAILED(status) ) { _aligned_free( dev->context ); _aligned_free( dev->IoBuffer ); dev->context = dev->IoBuffer = NULL; } return status; } #ifdef _WIN32 #pragma warning(pop) #endif /*********************************************************** phyvhd_show() */ void phyvhd_show( struct DEV *dev, size_t column ) { vhd_contextp ctx; const char *type, *p; size_t i, n; if( (ctx = dev->context) == NULL ) return; i = ctx->footer.type; if( i > HD_TYPE_MAX ) type = "Unknown"; else type = HD_TYPE_STR[i]; n = strlen( type ); if( (p = strchr( type, ' ' )) == NULL ) p = type + n; n = (size_t) (p -type); printf( " VHD %*.*s disk", (int)n, (int)n, type ); if( i == HD_TYPE_DIFF ) { char *name; #define PLABEL "parent: " if( vhd_header_decode_parent_fcn( ctx, &ctx->header, &name ) == 0 ) { printf( "\n%*sparent: %s", (int)(column < sizeof( PLABEL ) -1? (sizeof( PLABEL )-1)-column : column - (sizeof( PLABEL ) -1)), "", name ); free( name ); } } return; } /*********************************************************** phyvhd_read() */ static vmscond_t phyvhd_read( struct DEV *dev, uint32_t lbn, uint32_t length, char *buffer ) { vmscond_t status; int err; status = SS$_NORMAL; while( length > 0 ) { uint32_t rdsize; rdsize = (length + 511) / 512; rdsize = ( (rdsize > phyvhd_BUFSIZE/512)? phyvhd_BUFSIZE/512 : rdsize ); if( (err = vhd_io_read_fcn(dev->context, dev->IoBuffer, lbn, rdsize)) != 0 ) { if( err == -ERANGE ) return printmsg( IO_RDBLK2BIG, 0, lbn ); printmsg( IO_READERR, 0, lbn ); return printmsg( ODS2_OSERROR, MSG_CONTINUE, IO_READERR, strerror( -err ) ); } lbn += rdsize; rdsize *= 512; if( rdsize > length ) rdsize = length; memcpy( buffer, dev->IoBuffer, rdsize ); buffer += rdsize; length -= rdsize; } return status; } /*********************************************************** phyvhd_write() */ static vmscond_t phyvhd_write( struct DEV *dev, uint32_t lbn, uint32_t length, const char *buffer ) { vmscond_t status; int err; status = SS$_NORMAL; while( length > 0 ) { uint32_t wrsize, iosize; iosize = (length + 511) / 512; iosize = ( (iosize > phyvhd_BUFSIZE/512)? phyvhd_BUFSIZE/512 : iosize ); wrsize = iosize * 512; if( wrsize > length ) wrsize = length; memcpy( dev->IoBuffer, buffer, wrsize ); buffer += wrsize; length -= wrsize; if( (err = vhd_io_write_fcn(dev->context, dev->IoBuffer, lbn, iosize)) != 0 ) { if( err == -ERANGE ) return printmsg( IO_WRBLK2BIG, 0, lbn ); printmsg( IO_WRITEERR, 0, lbn ); return printmsg( ODS2_OSERROR, MSG_CONTINUE, IO_WRITEERR, strerror( -err ) ); } lbn += iosize; } return status; } /*********************************************************** phyvhd_close() */ vmscond_t phyvhd_close( struct DEV *dev ) { if( dev->context != NULL ) { vhd_close_fcn( dev->context ); _aligned_free( dev->context ); dev->context = NULL; } _aligned_free( dev->IoBuffer ); dev->IoBuffer = NULL; return SS$_NORMAL; }