/** * gekko_io.c - Gekko style disk io functions. * * Copyright (c) 2009 Rhys "Shareese" Koedijk * Copyright (c) 2010 Dimok * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program/include file is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_STDIO_H #include #endif #ifdef HAVE_MATH_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_LIMITS_H #include #endif #ifdef HAVE_LOCALE_H #include #endif #include "ntfs.h" #include "types.h" #include "logging.h" #include "device_io.h" #include "gekko_io.h" #include "cache.h" #include "device.h" #include "bootsect.h" #define DEV_FD(dev) ((gekko_fd *)dev->d_private) /* Prototypes */ static s64 ntfs_device_gekko_io_readbytes( struct ntfs_device *dev, s64 offset, s64 count, void *buf ); static bool ntfs_device_gekko_io_readsectors( struct ntfs_device *dev, sec_t sector, sec_t numSectors, void* buffer ); static s64 ntfs_device_gekko_io_writebytes( struct ntfs_device *dev, s64 offset, s64 count, const void *buf ); static bool ntfs_device_gekko_io_writesectors( struct ntfs_device *dev, sec_t sector, sec_t numSectors, const void* buffer ); /** * */ static int ntfs_device_gekko_io_open( struct ntfs_device *dev, int flags ) { ntfs_log_trace( "dev %p, flags %i\n", dev, flags ); // Get the device driver descriptor gekko_fd *fd = DEV_FD( dev ); if ( !fd ) { errno = EBADF; return -1; } // Get the device interface const DISC_INTERFACE* interface = fd->interface; if ( !interface ) { errno = ENODEV; return -1; } // Start the device interface and ensure that it is inserted if ( !interface->startup() ) { ntfs_log_perror( "device failed to start\n" ); errno = EIO; return -1; } if ( !interface->isInserted() ) { ntfs_log_perror( "device media is not inserted\n" ); errno = EIO; return -1; } // Check that the device isn't already open (used by another volume?) if ( NDevOpen( dev ) ) { ntfs_log_perror( "device is busy (already open)\n" ); errno = EBUSY; return -1; } // Check that there is a valid NTFS boot sector at the start of the device NTFS_BOOT_SECTOR boot; if ( interface->readSectors( fd->startSector, 1, &boot ) ) { if ( !ntfs_boot_sector_is_ntfs( &boot ) ) { errno = EINVALPART; return -1; } } else { ntfs_log_perror( "read failure @ sector %d\n", fd->startSector ); errno = EIO; return -1; } // Parse the boot sector fd->hiddenSectors = le32_to_cpu( boot.bpb.hidden_sectors ); fd->sectorSize = le16_to_cpu( boot.bpb.bytes_per_sector ); fd->sectorCount = sle64_to_cpu( boot.number_of_sectors ); fd->pos = 0; fd->len = ( fd->sectorCount * fd->sectorSize ); fd->ino = le64_to_cpu( boot.volume_serial_number ); // Mark the device as read-only (if required) if ( flags & O_RDONLY ) { NDevSetReadOnly( dev ); } // Create the cache fd->cache = _NTFS_cache_constructor( fd->cachePageCount, fd->cachePageSize, interface, fd->startSector + fd->sectorCount, fd->sectorSize ); // Mark the device as open NDevSetBlock( dev ); NDevSetOpen( dev ); return 0; } /** * */ static int ntfs_device_gekko_io_close( struct ntfs_device *dev ) { ntfs_log_trace( "dev %p\n", dev ); // Get the device driver descriptor gekko_fd *fd = DEV_FD( dev ); if ( !fd ) { errno = EBADF; return -1; } // Check that the device is actually open if ( !NDevOpen( dev ) ) { ntfs_log_perror( "device is not open\n" ); errno = EIO; return -1; } // Mark the device as closed NDevClearOpen( dev ); NDevClearBlock( dev ); // Flush the device (if dirty and not read-only) if ( NDevDirty( dev ) && !NDevReadOnly( dev ) ) { ntfs_log_debug( "device is dirty, will now sync\n" ); // ...? // Mark the device as clean NDevClearDirty( dev ); } // Flush and destroy the cache (if required) if ( fd->cache ) { _NTFS_cache_flush( fd->cache ); _NTFS_cache_destructor( fd->cache ); } // Shutdown the device interface /*const DISC_INTERFACE* interface = fd->interface; if (interface) { interface->shutdown(); }*/ // Free the device driver private data ntfs_free( dev->d_private ); dev->d_private = NULL; return 0; } /** * */ static s64 ntfs_device_gekko_io_seek( struct ntfs_device *dev, s64 offset, int whence ) { ntfs_log_trace( "dev %p, offset %Li, whence %i\n", dev, offset, whence ); // Get the device driver descriptor gekko_fd *fd = DEV_FD( dev ); if ( !fd ) { errno = EBADF; return -1; } // Set the current position on the device (in bytes) switch ( whence ) { case SEEK_SET: fd->pos = MIN( MAX( offset, 0 ), fd->len ); break; case SEEK_CUR: fd->pos = MIN( MAX( fd->pos + offset, 0 ), fd->len ); break; case SEEK_END: fd->pos = MIN( MAX( fd->len + offset, 0 ), fd->len ); break; } return 0; } /** * */ static s64 ntfs_device_gekko_io_read( struct ntfs_device *dev, void *buf, s64 count ) { return ntfs_device_gekko_io_readbytes( dev, DEV_FD( dev )->pos, count, buf ); } /** * */ static s64 ntfs_device_gekko_io_write( struct ntfs_device *dev, const void *buf, s64 count ) { return ntfs_device_gekko_io_writebytes( dev, DEV_FD( dev )->pos, count, buf ); } /** * */ static s64 ntfs_device_gekko_io_pread( struct ntfs_device *dev, void *buf, s64 count, s64 offset ) { return ntfs_device_gekko_io_readbytes( dev, offset, count, buf ); } /** * */ static s64 ntfs_device_gekko_io_pwrite( struct ntfs_device *dev, const void *buf, s64 count, s64 offset ) { return ntfs_device_gekko_io_writebytes( dev, offset, count, buf ); } /** * */ static s64 ntfs_device_gekko_io_readbytes( struct ntfs_device *dev, s64 offset, s64 count, void *buf ) { ntfs_log_trace( "dev %p, offset %Li, count %Li\n", dev, offset, count ); // Get the device driver descriptor gekko_fd *fd = DEV_FD( dev ); if ( !fd ) { errno = EBADF; return -1; } // Get the device interface const DISC_INTERFACE* interface = fd->interface; if ( !interface ) { errno = ENODEV; return -1; } if ( offset < 0 ) { errno = EROFS; return -1; } if ( !count ) return 0; sec_t sec_start = ( sec_t ) fd->startSector; sec_t sec_count = 1; u32 buffer_offset = ( u32 ) ( offset % fd->sectorSize ); u8 *buffer = NULL; // Determine the range of sectors required for this read if ( offset > 0 ) { sec_start += ( sec_t ) floor( ( f64 ) offset / ( f64 ) fd->sectorSize ); } if ( buffer_offset + count > fd->sectorSize ) { sec_count = ( sec_t ) ceil( ( f64 ) ( buffer_offset + count ) / ( f64 ) fd->sectorSize ); } // If this read happens to be on the sector boundaries then do the read straight into the destination buffer if ( ( buffer_offset == 0 ) && ( count % fd->sectorSize == 0 ) ) { // Read from the device ntfs_log_trace( "direct read from sector %d (%d sector(s) long)\n", sec_start, sec_count ); if ( !ntfs_device_gekko_io_readsectors( dev, sec_start, sec_count, buf ) ) { ntfs_log_perror( "direct read failure @ sector %d (%d sector(s) long)\n", sec_start, sec_count ); errno = EIO; return -1; } // Else read into a buffer and copy over only what was requested } else { // Allocate a buffer to hold the read data buffer = ( u8* )ntfs_alloc( sec_count * fd->sectorSize ); if ( !buffer ) { errno = ENOMEM; return -1; } // Read from the device ntfs_log_trace( "buffered read from sector %d (%d sector(s) long)\n", sec_start, sec_count ); ntfs_log_trace( "count: %d sec_count:%d fd->sectorSize: %d )\n", ( u32 )count, ( u32 )sec_count, ( u32 )fd->sectorSize ); if ( !ntfs_device_gekko_io_readsectors( dev, sec_start, sec_count, buffer ) ) { ntfs_log_perror( "buffered read failure @ sector %d (%d sector(s) long)\n", sec_start, sec_count ); ntfs_free( buffer ); errno = EIO; return -1; } // Copy what was requested to the destination buffer memcpy( buf, buffer + buffer_offset, count ); ntfs_free( buffer ); } return count; } /** * */ static s64 ntfs_device_gekko_io_writebytes( struct ntfs_device *dev, s64 offset, s64 count, const void *buf ) { ntfs_log_trace( "dev %p, offset %lli, count %lli\n", dev, offset, count ); // Get the device driver descriptor gekko_fd *fd = DEV_FD( dev ); if ( !fd ) { errno = EBADF; return -1; } // Get the device interface const DISC_INTERFACE* interface = fd->interface; if ( !interface ) { errno = ENODEV; return -1; } // Check that the device can be written to if ( NDevReadOnly( dev ) ) { errno = EROFS; return -1; } if ( count < 0 || offset < 0 ) { errno = EROFS; return -1; } if ( count == 0 ) return 0; sec_t sec_start = ( sec_t ) fd->startSector; sec_t sec_count = 1; u32 buffer_offset = ( u32 ) ( offset % fd->sectorSize ); u8 *buffer = NULL; // Determine the range of sectors required for this write if ( offset > 0 ) { sec_start += ( sec_t ) floor( ( f64 ) offset / ( f64 ) fd->sectorSize ); } if ( ( buffer_offset + count ) > fd->sectorSize ) { sec_count = ( sec_t ) ceil( ( f64 ) ( buffer_offset + count ) / ( f64 ) fd->sectorSize ); } // If this write happens to be on the sector boundaries then do the write straight to disc if ( ( buffer_offset == 0 ) && ( count % fd->sectorSize == 0 ) ) { // Write to the device ntfs_log_trace( "direct write to sector %d (%d sector(s) long)\n", sec_start, sec_count ); if ( !ntfs_device_gekko_io_writesectors( dev, sec_start, sec_count, buf ) ) { ntfs_log_perror( "direct write failure @ sector %d (%d sector(s) long)\n", sec_start, sec_count ); errno = EIO; return -1; } // Else write from a buffer aligned to the sector boundaries } else { // Allocate a buffer to hold the write data buffer = ( u8 * ) ntfs_alloc( sec_count * fd->sectorSize ); if ( !buffer ) { errno = ENOMEM; return -1; } // Read the first and last sectors of the buffer from disc (if required) // NOTE: This is done because the data does not line up with the sector boundaries, // we just read in the buffer edges where the data overlaps with the rest of the disc if ( buffer_offset != 0 ) { if ( !ntfs_device_gekko_io_readsectors( dev, sec_start, 1, buffer ) ) { ntfs_log_perror( "read failure @ sector %d\n", sec_start ); ntfs_free( buffer ); errno = EIO; return -1; } } if ( ( buffer_offset + count ) % fd->sectorSize != 0 ) { if ( !ntfs_device_gekko_io_readsectors( dev, sec_start + sec_count - 1, 1, buffer + ( ( sec_count - 1 ) * fd->sectorSize ) ) ) { ntfs_log_perror( "read failure @ sector %d\n", sec_start + sec_count - 1 ); ntfs_free( buffer ); errno = EIO; return -1; } } // Copy the data into the write buffer memcpy( buffer + buffer_offset, buf, count ); // Write to the device ntfs_log_trace( "buffered write to sector %d (%d sector(s) long)\n", sec_start, sec_count ); if ( !ntfs_device_gekko_io_writesectors( dev, sec_start, sec_count, buffer ) ) { ntfs_log_perror( "buffered write failure @ sector %d\n", sec_start ); ntfs_free( buffer ); errno = EIO; return -1; } // Free the buffer ntfs_free( buffer ); } // Mark the device as dirty (if we actually wrote anything) NDevSetDirty( dev ); return count; } static bool ntfs_device_gekko_io_readsectors( struct ntfs_device *dev, sec_t sector, sec_t numSectors, void* buffer ) { // Get the device driver descriptor gekko_fd *fd = DEV_FD( dev ); if ( !fd ) { errno = EBADF; return false; } // Read the sectors from disc (or cache, if enabled) if ( fd->cache ) return _NTFS_cache_readSectors( fd->cache, sector, numSectors, buffer ); else return fd->interface->readSectors( sector, numSectors, buffer ); return false; } static bool ntfs_device_gekko_io_writesectors( struct ntfs_device *dev, sec_t sector, sec_t numSectors, const void* buffer ) { // Get the device driver descriptor gekko_fd *fd = DEV_FD( dev ); if ( !fd ) { errno = EBADF; return false; } // Write the sectors to disc (or cache, if enabled) if ( fd->cache ) return _NTFS_cache_writeSectors( fd->cache, sector, numSectors, buffer ); else return fd->interface->writeSectors( sector, numSectors, buffer ); return false; } /** * */ static int ntfs_device_gekko_io_sync( struct ntfs_device *dev ) { gekko_fd *fd = DEV_FD( dev ); ntfs_log_trace( "dev %p\n", dev ); // Check that the device can be written to if ( NDevReadOnly( dev ) ) { errno = EROFS; return -1; } // Mark the device as clean NDevClearDirty( dev ); // Flush any sectors in the disc cache (if required) if ( fd->cache ) { if ( !_NTFS_cache_flush( fd->cache ) ) { errno = EIO; return -1; } } return 0; } /** * */ static int ntfs_device_gekko_io_stat( struct ntfs_device *dev, struct stat *buf ) { ntfs_log_trace( "dev %p, buf %p\n", dev, buf ); // Get the device driver descriptor gekko_fd *fd = DEV_FD( dev ); if ( !fd ) { errno = EBADF; return -1; } // Short circuit cases were we don't actually have to do anything if ( !buf ) return 0; // Build the device mode mode_t mode = ( S_IFBLK ) | ( S_IRUSR | S_IRGRP | S_IROTH ) | ( ( !NDevReadOnly( dev ) ) ? ( S_IWUSR | S_IWGRP | S_IWOTH ) : 0 ); // Zero out the stat buffer memset( buf, 0, sizeof( struct stat ) ); // Build the device stats buf->st_dev = fd->interface->ioType; buf->st_ino = fd->ino; buf->st_mode = mode; buf->st_rdev = fd->interface->ioType; buf->st_blksize = fd->sectorSize; buf->st_blocks = fd->sectorCount; return 0; } /** * */ static int ntfs_device_gekko_io_ioctl( struct ntfs_device *dev, int request, void *argp ) { ntfs_log_trace( "dev %p, request %i, argp %p\n", dev, request, argp ); // Get the device driver descriptor gekko_fd *fd = DEV_FD( dev ); if ( !fd ) { errno = EBADF; return -1; } // Figure out which i/o control was requested switch ( request ) { // Get block device size (sectors) #if defined(BLKGETSIZE) case BLKGETSIZE: { *( u32* )argp = fd->sectorCount; return 0; } #endif // Get block device size (bytes) #if defined(BLKGETSIZE64) case BLKGETSIZE64: { *( u64* )argp = ( fd->sectorCount * fd->sectorSize ); return 0; } #endif // Get hard drive geometry #if defined(HDIO_GETGEO) case HDIO_GETGEO: { struct hd_geometry *geo = ( struct hd_geometry* )argp; geo->sectors = 0; geo->heads = 0; geo->cylinders = 0; geo->start = fd->hiddenSectors; return -1; } #endif // Get block device sector size (bytes) #if defined(BLKSSZGET) case BLKSSZGET: { *( int* )argp = fd->sectorSize; return 0; } #endif // Set block device block size (bytes) #if defined(BLKBSZSET) case BLKBSZSET: { int sectorSize = *( int* )argp; fd->sectorSize = sectorSize; return 0; } #endif // Unimplemented ioctrl default: { ntfs_log_perror( "Unimplemented ioctrl %i\n", request ); errno = EOPNOTSUPP; return -1; } } return 0; } /** * Device operations for working with gekko style devices and files. */ struct ntfs_device_operations ntfs_device_gekko_io_ops = { .open = ntfs_device_gekko_io_open, .close = ntfs_device_gekko_io_close, .seek = ntfs_device_gekko_io_seek, .read = ntfs_device_gekko_io_read, .write = ntfs_device_gekko_io_write, .pread = ntfs_device_gekko_io_pread, .pwrite = ntfs_device_gekko_io_pwrite, .sync = ntfs_device_gekko_io_sync, .stat = ntfs_device_gekko_io_stat, .ioctl = ntfs_device_gekko_io_ioctl, };