diskio-windows.cc revision 55d926192adc984462509b2966e23bc0d1129bbd
1// 2// C++ Interface: diskio (Windows-specific components) 3// 4// Description: Class to handle low-level disk I/O for GPT fdisk 5// 6// 7// Author: Rod Smith <rodsmith@rodsbooks.com>, (C) 2009 8// 9// Copyright: See COPYING file that comes with this distribution 10// 11// 12// This program is copyright (c) 2009, 2010 by Roderick W. Smith. It is distributed 13// under the terms of the GNU GPL version 2, as detailed in the COPYING file. 14 15#define __STDC_LIMIT_MACROS 16#define __STDC_CONSTANT_MACROS 17 18#include <windows.h> 19#include <winioctl.h> 20#define fstat64 fstat 21#define stat64 stat 22#define S_IRGRP 0 23#define S_IROTH 0 24#include <stdio.h> 25#include <string> 26#include <stdint.h> 27#include <errno.h> 28#include <fcntl.h> 29#include <sys/stat.h> 30#include <iostream> 31 32#include "support.h" 33#include "diskio.h" 34 35using namespace std; 36 37// Returns the official Windows name for a shortened version of same. 38void DiskIO::MakeRealName(void) { 39 size_t colonPos; 40 41 colonPos = userFilename.find(':', 0); 42 if ((colonPos != string::npos) && (colonPos <= 3)) { 43 realFilename = "\\\\.\\physicaldrive"; 44 realFilename += userFilename.substr(0, colonPos); 45 } else { 46 realFilename = userFilename; 47 } // if/else 48} // DiskIO::MakeRealName() 49 50// Open the currently on-record file for reading 51int DiskIO::OpenForRead(void) { 52 int shouldOpen = 1; 53 54 if (isOpen) { // file is already open 55 if (openForWrite) { 56 Close(); 57 } else { 58 shouldOpen = 0; 59 } // if/else 60 } // if 61 62 if (shouldOpen) { 63 fd = CreateFile(realFilename.c_str(),GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 64 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 65 if (fd == INVALID_HANDLE_VALUE) { 66 CloseHandle(fd); 67 cerr << "Problem opening " << realFilename << " for reading!\n"; 68 realFilename = ""; 69 userFilename = ""; 70 isOpen = 0; 71 openForWrite = 0; 72 } else { 73 isOpen = 1; 74 openForWrite = 0; 75 } // if/else 76 } // if 77 78 return isOpen; 79} // DiskIO::OpenForRead(void) 80 81// An extended file-open function. This includes some system-specific checks. 82// Returns 1 if the file is open, 0 otherwise.... 83int DiskIO::OpenForWrite(void) { 84 if ((isOpen) && (openForWrite)) 85 return 1; 86 87 // Close the disk, in case it's already open for reading only.... 88 Close(); 89 90 // try to open the device; may fail.... 91 fd = CreateFile(realFilename.c_str(), GENERIC_READ | GENERIC_WRITE, 92 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 93 FILE_ATTRIBUTE_NORMAL, NULL); 94 // Preceding call can fail when creating backup files; if so, try 95 // again with different option... 96 if (fd == INVALID_HANDLE_VALUE) { 97 CloseHandle(fd); 98 fd = CreateFile(realFilename.c_str(), GENERIC_READ | GENERIC_WRITE, 99 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 100 FILE_ATTRIBUTE_NORMAL, NULL); 101 } // if 102 if (fd == INVALID_HANDLE_VALUE) { 103 CloseHandle(fd); 104 isOpen = 0; 105 openForWrite = 0; 106 errno = GetLastError(); 107 } else { 108 isOpen = 1; 109 openForWrite = 1; 110 } // if/else 111 return isOpen; 112} // DiskIO::OpenForWrite(void) 113 114// Close the disk device. Note that this does NOT erase the stored filenames, 115// so the file can be re-opened without specifying the filename. 116void DiskIO::Close(void) { 117 if (isOpen) 118 CloseHandle(fd); 119 isOpen = 0; 120 openForWrite = 0; 121} // DiskIO::Close() 122 123// Returns block size of device pointed to by fd file descriptor. If the ioctl 124// returns an error condition, assume it's a disk file and return a value of 125// SECTOR_SIZE (512). If the disk can't be opened at all, return a value of 0. 126int DiskIO::GetBlockSize(void) { 127 DWORD blockSize = 0, retBytes; 128 DISK_GEOMETRY_EX geom; 129 130 // If disk isn't open, try to open it.... 131 if (!isOpen) { 132 OpenForRead(); 133 } // if 134 135 if (isOpen) { 136 if (DeviceIoControl(fd, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, NULL, 0, &geom, sizeof(geom), &retBytes, NULL)) { 137 blockSize = geom.Geometry.BytesPerSector; 138 } else { // was probably an ordinary file; set default value.... 139 blockSize = SECTOR_SIZE; 140 } // if/else 141 } // if (isOpen) 142 143 return (blockSize); 144} // DiskIO::GetBlockSize() 145 146// Resync disk caches so the OS uses the new partition table. This code varies 147// a lot from one OS to another. 148void DiskIO::DiskSync(void) { 149 DWORD i; 150 GET_LENGTH_INFORMATION buf; 151 152 // If disk isn't open, try to open it.... 153 if (!openForWrite) { 154 OpenForWrite(); 155 } // if 156 157 if (isOpen) { 158 if (DeviceIoControl(fd, IOCTL_DISK_UPDATE_PROPERTIES, NULL, 0, &buf, sizeof(buf), &i, NULL) == 0) { 159 cout << "Disk synchronization failed! The computer may use the old partition table\n" 160 << "until you reboot or remove and re-insert the disk!\n"; 161 } else { 162 cout << "Disk synchronization succeeded! The computer should now use the new\n" 163 << "partition table.\n"; 164 } // if/else 165 } else { 166 cout << "Unable to open the disk for synchronization operation! The computer will\n" 167 << "continue to use the old partition table until you reboot or remove and\n" 168 << "re-insert the disk!\n"; 169 } // if (isOpen) 170} // DiskIO::DiskSync() 171 172// Seek to the specified sector. Returns 1 on success, 0 on failure. 173int DiskIO::Seek(uint64_t sector) { 174 int retval = 1; 175 LARGE_INTEGER seekTo; 176 177 // If disk isn't open, try to open it.... 178 if (!isOpen) { 179 retval = OpenForRead(); 180 } // if 181 182 if (isOpen) { 183 seekTo.QuadPart = sector * (uint64_t) GetBlockSize(); 184 retval = SetFilePointerEx(fd, seekTo, NULL, FILE_BEGIN); 185 if (retval == 0) { 186 errno = GetLastError(); 187 cerr << "Error when seeking to " << seekTo.QuadPart << "! Error is " << errno << "\n"; 188 retval = 0; 189 } // if 190 } // if 191 return retval; 192} // DiskIO::Seek() 193 194// A variant on the standard read() function. Done to work around 195// limitations in FreeBSD concerning the matching of the sector 196// size with the number of bytes read. 197// Returns the number of bytes read into buffer. 198int DiskIO::Read(void* buffer, int numBytes) { 199 int blockSize = 512, i, numBlocks; 200 char* tempSpace; 201 DWORD retval = 0; 202 203 // If disk isn't open, try to open it.... 204 if (!isOpen) { 205 OpenForRead(); 206 } // if 207 208 if (isOpen) { 209 // Compute required space and allocate memory 210 blockSize = GetBlockSize(); 211 if (numBytes <= blockSize) { 212 numBlocks = 1; 213 tempSpace = new char [blockSize]; 214 } else { 215 numBlocks = numBytes / blockSize; 216 if ((numBytes % blockSize) != 0) 217 numBlocks++; 218 tempSpace = new char [numBlocks * blockSize]; 219 } // if/else 220 221 // Read the data into temporary space, then copy it to buffer 222 ReadFile(fd, tempSpace, numBlocks * blockSize, &retval, NULL); 223 for (i = 0; i < numBytes; i++) { 224 ((char*) buffer)[i] = tempSpace[i]; 225 } // for 226 227 // Adjust the return value, if necessary.... 228 if (((numBlocks * blockSize) != numBytes) && (retval > 0)) 229 retval = numBytes; 230 231 delete[] tempSpace; 232 } // if (isOpen) 233 return retval; 234} // DiskIO::Read() 235 236// A variant on the standard write() function. Done to work around 237// limitations in FreeBSD concerning the matching of the sector 238// size with the number of bytes read. 239// Returns the number of bytes written. 240int DiskIO::Write(void* buffer, int numBytes) { 241 int blockSize = 512, i, numBlocks, retval = 0; 242 char* tempSpace; 243 DWORD numWritten; 244 245 // If disk isn't open, try to open it.... 246 if ((!isOpen) || (!openForWrite)) { 247 OpenForWrite(); 248 } // if 249 250 if (isOpen) { 251 // Compute required space and allocate memory 252 blockSize = GetBlockSize(); 253 if (numBytes <= blockSize) { 254 numBlocks = 1; 255 tempSpace = new char [blockSize]; 256 } else { 257 numBlocks = numBytes / blockSize; 258 if ((numBytes % blockSize) != 0) numBlocks++; 259 tempSpace = new char [numBlocks * blockSize]; 260 } // if/else 261 262 // Copy the data to my own buffer, then write it 263 for (i = 0; i < numBytes; i++) { 264 tempSpace[i] = ((char*) buffer)[i]; 265 } // for 266 for (i = numBytes; i < numBlocks * blockSize; i++) { 267 tempSpace[i] = 0; 268 } // for 269 WriteFile(fd, tempSpace, numBlocks * blockSize, &numWritten, NULL); 270 retval = (int) numWritten; 271 272 // Adjust the return value, if necessary.... 273 if (((numBlocks * blockSize) != numBytes) && (retval > 0)) 274 retval = numBytes; 275 276 delete[] tempSpace; 277 } // if (isOpen) 278 return retval; 279} // DiskIO:Write() 280 281// Returns the size of the disk in blocks. 282uint64_t DiskIO::DiskSize(int *err) { 283 uint64_t sectors = 0; // size in sectors 284 DWORD bytes, moreBytes; // low- and high-order bytes of file size 285 GET_LENGTH_INFORMATION buf; 286 DWORD i; 287 288 // If disk isn't open, try to open it.... 289 if (!isOpen) { 290 OpenForRead(); 291 } // if 292 293 if (isOpen) { 294 // Note to self: I recall testing a simplified version of 295 // this code, similar to what's in the __APPLE__ block, 296 // on Linux, but I had some problems. IIRC, it ran OK on 32-bit 297 // systems but not on 64-bit. Keep this in mind in case of 298 // 32/64-bit issues on MacOS.... 299 if (DeviceIoControl(fd, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &buf, sizeof(buf), &i, NULL)) { 300 sectors = (uint64_t) buf.Length.QuadPart / GetBlockSize(); 301 *err = 0; 302 } else { // doesn't seem to be a disk device; assume it's an image file.... 303 bytes = GetFileSize(fd, &moreBytes); 304 sectors = ((uint64_t) bytes + ((uint64_t) moreBytes) * UINT32_MAX) / GetBlockSize(); 305 *err = 0; 306 } // if 307 } else { 308 *err = -1; 309 sectors = 0; 310 } // if/else (isOpen) 311 312 return sectors; 313} // DiskIO::DiskSize() 314