diskio-windows.cc revision e321d444dcca514cf6b53459e388ddcbaab6176c
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   if (fd == INVALID_HANDLE_VALUE) {
95      CloseHandle(fd);
96      isOpen = 0;
97      openForWrite = 0;
98      errno = GetLastError();
99   } else {
100      isOpen = 1;
101      openForWrite = 1;
102   } // if/else
103   return isOpen;
104} // DiskIO::OpenForWrite(void)
105
106// Close the disk device. Note that this does NOT erase the stored filenames,
107// so the file can be re-opened without specifying the filename.
108void DiskIO::Close(void) {
109   if (isOpen)
110      CloseHandle(fd);
111   isOpen = 0;
112   openForWrite = 0;
113} // DiskIO::Close()
114
115// Returns block size of device pointed to by fd file descriptor. If the ioctl
116// returns an error condition, print a warning but return a value of SECTOR_SIZE
117// (512)..
118int DiskIO::GetBlockSize(void) {
119   int err;
120   DWORD blockSize, junk1, junk2, junk3;
121
122   // If disk isn't open, try to open it....
123   if (!isOpen) {
124      OpenForRead();
125   } // if
126
127   if (isOpen) {
128/*      BOOL WINAPI GetDiskFreeSpace(
129                                   __in   LPCTSTR lpRootPathName,
130                                   __out  LPDWORD lpSectorsPerCluster,
131                                   __out  LPDWORD lpBytesPerSector,
132                                   __out  LPDWORD lpNumberOfFreeClusters,
133                                   __out  LPDWORD lpTotalNumberOfClusters
134                                  ); */
135//      err = GetDiskFreeSpace(realFilename.c_str(), &junk1, &blockSize, &junk2, &junk3);
136      // Above call is fubared -- returns weird values for blockSize....
137      err = 1;
138      blockSize = 512;
139
140      if (err == 0) {
141         blockSize = SECTOR_SIZE;
142         // ENOTTY = inappropriate ioctl; probably being called on a disk image
143         // file, so don't display the warning message....
144         // 32-bit code returns EINVAL, I don't know why. I know I'm treading on
145         // thin ice here, but it should be OK in all but very weird cases....
146         if (errno != 267) { // 267 is returned on ordinary files
147            cerr << "\aError " << GetLastError() << " when determining sector size! "
148                 << "Setting sector size to " << SECTOR_SIZE << "\n";
149         } // if
150      } // if (err == -1)
151   } // if (isOpen)
152
153   return (blockSize);
154} // DiskIO::GetBlockSize()
155
156// Resync disk caches so the OS uses the new partition table. This code varies
157// a lot from one OS to another.
158void DiskIO::DiskSync(void) {
159   DWORD i;
160   GET_LENGTH_INFORMATION buf;
161
162   // If disk isn't open, try to open it....
163   if (!openForWrite) {
164      OpenForWrite();
165   } // if
166
167   if (isOpen) {
168      if (DeviceIoControl(fd, IOCTL_DISK_UPDATE_PROPERTIES, NULL, 0, &buf, sizeof(buf), &i, NULL) == 0) {
169         cout << "Disk synchronization failed! The computer may use the old partition table\n"
170              << "until you reboot or remove and re-insert the disk!\n";
171      } else {
172         cout << "Disk synchronization succeeded! The computer should now use the new\n"
173              << "partition table.\n";
174      } // if/else
175   } else {
176      cout << "Unable to open the disk for synchronization operation! The computer will\n"
177           << "continue to use the old partition table until you reboot or remove and\n"
178           << "re-insert the disk!\n";
179   } // if (isOpen)
180} // DiskIO::DiskSync()
181
182// Seek to the specified sector. Returns 1 on success, 0 on failure.
183int DiskIO::Seek(uint64_t sector) {
184   int retval = 1;
185   LARGE_INTEGER seekTo;
186
187   // If disk isn't open, try to open it....
188   if (!isOpen) {
189      retval = OpenForRead();
190   } // if
191
192   if (isOpen) {
193      seekTo.QuadPart = sector * (uint64_t) GetBlockSize();
194      retval = SetFilePointerEx(fd, seekTo, NULL, FILE_BEGIN);
195      if (retval == 0) {
196         errno = GetLastError();
197         cerr << "Error when seeking to " << seekTo.QuadPart << "! Error is " << errno << "\n";
198         retval = 0;
199      } // if
200   } // if
201   return retval;
202} // DiskIO::Seek()
203
204// A variant on the standard read() function. Done to work around
205// limitations in FreeBSD concerning the matching of the sector
206// size with the number of bytes read.
207// Returns the number of bytes read into buffer.
208int DiskIO::Read(void* buffer, int numBytes) {
209   int blockSize = 512, i, numBlocks;
210   char* tempSpace;
211   DWORD retval = 0;
212
213   // If disk isn't open, try to open it....
214   if (!isOpen) {
215      OpenForRead();
216   } // if
217
218   if (isOpen) {
219      // Compute required space and allocate memory
220      blockSize = GetBlockSize();
221      if (numBytes <= blockSize) {
222         numBlocks = 1;
223         tempSpace = (char*) malloc(blockSize);
224      } else {
225         numBlocks = numBytes / blockSize;
226         if ((numBytes % blockSize) != 0) numBlocks++;
227         tempSpace = (char*) malloc(numBlocks * blockSize);
228      } // if/else
229
230      // Read the data into temporary space, then copy it to buffer
231      ReadFile(fd, tempSpace, numBlocks * blockSize, &retval, NULL);
232      for (i = 0; i < numBytes; i++) {
233         ((char*) buffer)[i] = tempSpace[i];
234      } // for
235
236      // Adjust the return value, if necessary....
237      if (((numBlocks * blockSize) != numBytes) && (retval > 0))
238         retval = numBytes;
239
240      free(tempSpace);
241   } // if (isOpen)
242   return retval;
243} // DiskIO::Read()
244
245// A variant on the standard write() function. Done to work around
246// limitations in FreeBSD concerning the matching of the sector
247// size with the number of bytes read.
248// Returns the number of bytes written.
249int DiskIO::Write(void* buffer, int numBytes) {
250   int blockSize = 512, i, numBlocks, retval = 0;
251   char* tempSpace;
252   DWORD numWritten;
253
254   // If disk isn't open, try to open it....
255   if ((!isOpen) || (!openForWrite)) {
256      OpenForWrite();
257   } // if
258
259   if (isOpen) {
260      // Compute required space and allocate memory
261      blockSize = GetBlockSize();
262      if (numBytes <= blockSize) {
263         numBlocks = 1;
264         tempSpace = (char*) malloc(blockSize);
265      } else {
266         numBlocks = numBytes / blockSize;
267         if ((numBytes % blockSize) != 0) numBlocks++;
268         tempSpace = (char*) malloc(numBlocks * blockSize);
269      } // if/else
270
271      // Copy the data to my own buffer, then write it
272      for (i = 0; i < numBytes; i++) {
273         tempSpace[i] = ((char*) buffer)[i];
274      } // for
275      for (i = numBytes; i < numBlocks * blockSize; i++) {
276         tempSpace[i] = 0;
277      } // for
278      WriteFile(fd, tempSpace, numBlocks * blockSize, &numWritten, NULL);
279      retval = (int) numWritten;
280
281      // Adjust the return value, if necessary....
282      if (((numBlocks * blockSize) != numBytes) && (retval > 0))
283         retval = numBytes;
284
285      free(tempSpace);
286   } // if (isOpen)
287   return retval;
288} // DiskIO:Write()
289
290// Returns the size of the disk in blocks.
291uint64_t DiskIO::DiskSize(int *err) {
292   uint64_t sectors = 0; // size in sectors
293   DWORD bytes, moreBytes; // low- and high-order bytes of file size
294   GET_LENGTH_INFORMATION buf;
295   DWORD i;
296
297   // If disk isn't open, try to open it....
298   if (!isOpen) {
299      OpenForRead();
300   } // if
301
302   if (isOpen) {
303      // Note to self: I recall testing a simplified version of
304      // this code, similar to what's in the __APPLE__ block,
305      // on Linux, but I had some problems. IIRC, it ran OK on 32-bit
306      // systems but not on 64-bit. Keep this in mind in case of
307      // 32/64-bit issues on MacOS....
308      if (DeviceIoControl(fd, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &buf, sizeof(buf), &i, NULL)) {
309         sectors = (uint64_t) buf.Length.QuadPart / GetBlockSize();
310         *err = 0;
311      } else { // doesn't seem to be a disk device; assume it's an image file....
312         bytes = GetFileSize(fd, &moreBytes);
313         sectors = ((uint64_t) bytes + ((uint64_t) moreBytes) * UINT32_MAX) / GetBlockSize();
314         *err = 0;
315      } // if
316   } else {
317      *err = -1;
318      sectors = 0;
319   } // if/else (isOpen)
320
321   return sectors;
322} // DiskIO::DiskSize()
323