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