169a99ed0b2b2ef69d393c371b03db3a98aaf880eBen Murdoch//
2a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block// C++ Interface: diskio (Unix components [Linux, FreeBSD, Mac OS X])
3a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block//
4a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block// Description: Class to handle low-level disk I/O for GPT fdisk
5a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block//
6a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block//
7a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block// Author: Rod Smith <rodsmith@rodsbooks.com>, (C) 2009
8a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block//
9a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block// Copyright: See COPYING file that comes with this distribution
10a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block//
11a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block//
12a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block// This program is copyright (c) 2009 by Roderick W. Smith. It is distributed
13a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block// under the terms of the GNU GPL version 2, as detailed in the COPYING file.
14a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block
15a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block#define __STDC_LIMIT_MACROS
16a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block#define __STDC_CONSTANT_MACROS
17a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block
18a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block#include <sys/ioctl.h>
19a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block#include <string.h>
20a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block#include <string>
21a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block#include <stdint.h>
22a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block#include <unistd.h>
23a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block#include <errno.h>
24a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block#include <fcntl.h>
25a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block#include <sys/stat.h>
26a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block#include <unistd.h>
27a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block
2869a99ed0b2b2ef69d393c371b03db3a98aaf880eBen Murdoch#ifdef __linux__
2969a99ed0b2b2ef69d393c371b03db3a98aaf880eBen Murdoch#include "linux/hdreg.h"
30a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block#endif
3169a99ed0b2b2ef69d393c371b03db3a98aaf880eBen Murdoch
3269a99ed0b2b2ef69d393c371b03db3a98aaf880eBen Murdoch#include <iostream>
33a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block
3469a99ed0b2b2ef69d393c371b03db3a98aaf880eBen Murdoch#include "diskio.h"
3569a99ed0b2b2ef69d393c371b03db3a98aaf880eBen Murdoch
36a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Blockusing namespace std;
3769a99ed0b2b2ef69d393c371b03db3a98aaf880eBen Murdoch
38a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block// Returns the official "real" name for a shortened version of same.
3969a99ed0b2b2ef69d393c371b03db3a98aaf880eBen Murdoch// Trivial here; more important in Windows
4069a99ed0b2b2ef69d393c371b03db3a98aaf880eBen Murdochvoid DiskIO::MakeRealName(void) {
41a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block   realFilename = userFilename;
4269a99ed0b2b2ef69d393c371b03db3a98aaf880eBen Murdoch} // DiskIO::MakeRealName()
43a7e24c173cf37484693b9abb38e494fa7bd7baebSteve Block
4469a99ed0b2b2ef69d393c371b03db3a98aaf880eBen Murdoch// Open the currently on-record file for reading. Returns 1 if the file is
4569a99ed0b2b2ef69d393c371b03db3a98aaf880eBen Murdoch// already open or is opened by this call, 0 if opening the file doesn't
4669a99ed0b2b2ef69d393c371b03db3a98aaf880eBen Murdoch// work.
47014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdochint DiskIO::OpenForRead(void) {
48014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch   int shouldOpen = 1;
49014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch   struct stat64 st;
50014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch
51014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch   if (isOpen) { // file is already open
52014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch      if (openForWrite) {
53014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch         Close();
54014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch      } else {
55014dc512cdd3e367bee49a713fdc5ed92584a3e5Ben Murdoch         shouldOpen = 0;
56      } // if/else
57   } // if
58
59   if (shouldOpen) {
60      fd = open(realFilename.c_str(), O_RDONLY);
61      if (fd == -1) {
62         cerr << "Problem opening " << realFilename << " for reading! Error is " << errno << ".\n";
63         if (errno == EACCES) // User is probably not running as root
64            cerr << "You must run this program as root or use sudo!\n";
65         if (errno == ENOENT)
66            cerr << "The specified file does not exist!\n";
67         realFilename = "";
68         userFilename = "";
69         isOpen = 0;
70         openForWrite = 0;
71      } else {
72         isOpen = 0;
73         openForWrite = 0;
74         if (fstat64(fd, &st) == 0) {
75            if (S_ISDIR(st.st_mode))
76               cerr << "The specified path is a directory!\n";
77#if !defined(__FreeBSD__) && !defined(__APPLE__)
78            else if (S_ISCHR(st.st_mode))
79               cerr << "The specified path is a character device!\n";
80#endif
81            else if (S_ISFIFO(st.st_mode))
82               cerr << "The specified path is a FIFO!\n";
83            else if (S_ISSOCK(st.st_mode))
84               cerr << "The specified path is a socket!\n";
85            else
86               isOpen = 1;
87         } // if (fstat64()...)
88      } // if/else
89   } // if
90
91   return isOpen;
92} // DiskIO::OpenForRead(void)
93
94// An extended file-open function. This includes some system-specific checks.
95// Returns 1 if the file is open, 0 otherwise....
96int DiskIO::OpenForWrite(void) {
97   if ((isOpen) && (openForWrite))
98      return 1;
99
100   // Close the disk, in case it's already open for reading only....
101   Close();
102
103   // try to open the device; may fail....
104   fd = open(realFilename.c_str(), O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
105#ifdef __APPLE__
106   // MacOS X requires a shared lock under some circumstances....
107   if (fd < 0) {
108      cerr << "Warning: Devices opened with shared lock will not have their\npartition table automatically reloaded!\n";
109      fd = open(realFilename.c_str(), O_WRONLY | O_SHLOCK);
110   } // if
111#endif
112   if (fd >= 0) {
113      isOpen = 1;
114      openForWrite = 1;
115   } else {
116      isOpen = 0;
117      openForWrite = 0;
118   } // if/else
119   return isOpen;
120} // DiskIO::OpenForWrite(void)
121
122// Close the disk device. Note that this does NOT erase the stored filenames,
123// so the file can be re-opened without specifying the filename.
124void DiskIO::Close(void) {
125   if (isOpen)
126      if (close(fd) < 0)
127         cerr << "Warning! Problem closing file!\n";
128   isOpen = 0;
129   openForWrite = 0;
130} // DiskIO::Close()
131
132// Returns block size of device pointed to by fd file descriptor. If the ioctl
133// returns an error condition, print a warning but return a value of SECTOR_SIZE
134// (512). If the disk can't be opened at all, return a value of 0.
135int DiskIO::GetBlockSize(void) {
136   int err = -1, blockSize = 0;
137#ifdef __sun__
138   struct dk_minfo minfo;
139#endif
140
141   // If disk isn't open, try to open it....
142   if (!isOpen) {
143      OpenForRead();
144   } // if
145
146   if (isOpen) {
147#ifdef __APPLE__
148      err = ioctl(fd, DKIOCGETBLOCKSIZE, &blockSize);
149#endif
150#ifdef __sun__
151      err = ioctl(fd, DKIOCGMEDIAINFO, &minfo);
152      if (err == 0)
153          blockSize = minfo.dki_lbsize;
154#endif
155#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
156      err = ioctl(fd, DIOCGSECTORSIZE, &blockSize);
157#endif
158#ifdef __linux__
159      err = ioctl(fd, BLKSSZGET, &blockSize);
160#endif
161
162      if (err == -1) {
163         blockSize = SECTOR_SIZE;
164         // ENOTTY = inappropriate ioctl; probably being called on a disk image
165         // file, so don't display the warning message....
166         // 32-bit code returns EINVAL, I don't know why. I know I'm treading on
167         // thin ice here, but it should be OK in all but very weird cases....
168         if ((errno != ENOTTY) && (errno != EINVAL)) {
169            cerr << "\aError " << errno << " when determining sector size! Setting sector size to "
170                 << SECTOR_SIZE << "\n";
171            cout << "Disk device is " << realFilename << "\n";
172         } // if
173      } // if (err == -1)
174   } // if (isOpen)
175
176   return (blockSize);
177} // DiskIO::GetBlockSize()
178
179// Returns the number of heads, according to the kernel, or 255 if the
180// correct value can't be determined.
181uint32_t DiskIO::GetNumHeads(void) {
182   uint32_t numHeads = 255;
183
184#ifdef HDIO_GETGEO
185   struct hd_geometry geometry;
186
187   // If disk isn't open, try to open it....
188   if (!isOpen)
189      OpenForRead();
190
191   if (!ioctl(fd, HDIO_GETGEO, &geometry))
192      numHeads = (uint32_t) geometry.heads;
193#endif
194   return numHeads;
195} // DiskIO::GetNumHeads();
196
197// Returns the number of sectors per track, according to the kernel, or 63
198// if the correct value can't be determined.
199uint32_t DiskIO::GetNumSecsPerTrack(void) {
200   uint32_t numSecs = 63;
201
202   #ifdef HDIO_GETGEO
203   struct hd_geometry geometry;
204
205   // If disk isn't open, try to open it....
206   if (!isOpen)
207      OpenForRead();
208
209   if (!ioctl(fd, HDIO_GETGEO, &geometry))
210      numSecs = (uint32_t) geometry.sectors;
211   #endif
212   return numSecs;
213} // DiskIO::GetNumSecsPerTrack()
214
215// Resync disk caches so the OS uses the new partition table. This code varies
216// a lot from one OS to another.
217// Returns 1 on success, 0 if the kernel continues to use the old partition table.
218// (Note that for most OSes, the default of 0 is returned because I've not yet
219// looked into how to test for success in the underlying system calls...)
220int DiskIO::DiskSync(void) {
221   int i, retval = 0, platformFound = 0;
222
223   // If disk isn't open, try to open it....
224   if (!isOpen) {
225      OpenForRead();
226   } // if
227
228   if (isOpen) {
229      sync();
230#if defined(__APPLE__) || defined(__sun__)
231      cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
232           << "You should reboot or remove the drive.\n";
233               /* don't know if this helps
234               * it definitely will get things on disk though:
235               * http://topiks.org/mac-os-x/0321278542/ch12lev1sec8.html */
236#ifdef __sun__
237      i = ioctl(fd, DKIOCFLUSHWRITECACHE);
238#else
239      i = ioctl(fd, DKIOCSYNCHRONIZECACHE);
240#endif
241      platformFound++;
242#endif
243#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
244      sleep(2);
245      i = ioctl(fd, DIOCGFLUSH);
246      cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
247           << "You should reboot or remove the drive.\n";
248      platformFound++;
249#endif
250#ifdef __linux__
251      sleep(1); // Theoretically unnecessary, but ioctl() fails sometimes if omitted....
252      fsync(fd);
253      i = ioctl(fd, BLKRRPART);
254      if (i) {
255         cout << "Warning: The kernel is still using the old partition table.\n"
256              << "The new table will be used at the next reboot.\n";
257      } else {
258         retval = 1;
259      } // if/else
260      platformFound++;
261#endif
262      if (platformFound == 0)
263         cerr << "Warning: Platform not recognized!\n";
264      if (platformFound > 1)
265         cerr << "\nWarning: We seem to be running on multiple platforms!\n";
266   } // if (isOpen)
267   return retval;
268} // DiskIO::DiskSync()
269
270// Seek to the specified sector. Returns 1 on success, 0 on failure.
271// Note that seeking beyond the end of the file is NOT detected as a failure!
272int DiskIO::Seek(uint64_t sector) {
273   int retval = 1;
274   off64_t seekTo, sought;
275
276   // If disk isn't open, try to open it....
277   if (!isOpen) {
278      retval = OpenForRead();
279   } // if
280
281   if (isOpen) {
282      seekTo = sector * (uint64_t) GetBlockSize();
283      sought = lseek64(fd, seekTo, SEEK_SET);
284      if (sought != seekTo) {
285         retval = 0;
286      } // if
287   } // if
288   return retval;
289} // DiskIO::Seek()
290
291// A variant on the standard read() function. Done to work around
292// limitations in FreeBSD concerning the matching of the sector
293// size with the number of bytes read.
294// Returns the number of bytes read into buffer.
295int DiskIO::Read(void* buffer, int numBytes) {
296   int blockSize, numBlocks, retval = 0;
297   char* tempSpace;
298
299   // If disk isn't open, try to open it....
300   if (!isOpen) {
301      OpenForRead();
302   } // if
303
304   if (isOpen) {
305      // Compute required space and allocate memory
306      blockSize = GetBlockSize();
307      if (numBytes <= blockSize) {
308         numBlocks = 1;
309         tempSpace = new char [blockSize];
310      } else {
311         numBlocks = numBytes / blockSize;
312         if ((numBytes % blockSize) != 0)
313            numBlocks++;
314         tempSpace = new char [numBlocks * blockSize];
315      } // if/else
316      if (tempSpace == NULL) {
317         cerr << "Unable to allocate memory in DiskIO::Read()! Terminating!\n";
318         exit(1);
319      } // if
320
321      // Read the data into temporary space, then copy it to buffer
322      retval = read(fd, tempSpace, numBlocks * blockSize);
323      memcpy(buffer, tempSpace, numBytes);
324
325      // Adjust the return value, if necessary....
326      if (((numBlocks * blockSize) != numBytes) && (retval > 0))
327         retval = numBytes;
328
329      delete[] tempSpace;
330   } // if (isOpen)
331   return retval;
332} // DiskIO::Read()
333
334// A variant on the standard write() function. Done to work around
335// limitations in FreeBSD concerning the matching of the sector
336// size with the number of bytes read.
337// Returns the number of bytes written.
338int DiskIO::Write(void* buffer, int numBytes) {
339   int blockSize = 512, i, numBlocks, retval = 0;
340   char* tempSpace;
341
342   // If disk isn't open, try to open it....
343   if ((!isOpen) || (!openForWrite)) {
344      OpenForWrite();
345   } // if
346
347   if (isOpen) {
348      // Compute required space and allocate memory
349      blockSize = GetBlockSize();
350      if (numBytes <= blockSize) {
351         numBlocks = 1;
352         tempSpace = new char [blockSize];
353      } else {
354         numBlocks = numBytes / blockSize;
355         if ((numBytes % blockSize) != 0) numBlocks++;
356         tempSpace = new char [numBlocks * blockSize];
357      } // if/else
358      if (tempSpace == NULL) {
359         cerr << "Unable to allocate memory in DiskIO::Write()! Terminating!\n";
360         exit(1);
361      } // if
362
363      // Copy the data to my own buffer, then write it
364      memcpy(tempSpace, buffer, numBytes);
365      for (i = numBytes; i < numBlocks * blockSize; i++) {
366         tempSpace[i] = 0;
367      } // for
368      retval = write(fd, tempSpace, numBlocks * blockSize);
369
370      // Adjust the return value, if necessary....
371      if (((numBlocks * blockSize) != numBytes) && (retval > 0))
372         retval = numBytes;
373
374      delete[] tempSpace;
375   } // if (isOpen)
376   return retval;
377} // DiskIO:Write()
378
379/**************************************************************************************
380 *                                                                                    *
381 * Below functions are lifted from various sources, as documented in comments before  *
382 * each one.                                                                          *
383 *                                                                                    *
384 **************************************************************************************/
385
386// The disksize function is taken from the Linux fdisk code and modified
387// greatly since then to enable FreeBSD and MacOS support, as well as to
388// return correct values for disk image files.
389uint64_t DiskIO::DiskSize(int *err) {
390   uint64_t sectors = 0; // size in sectors
391   off_t bytes = 0; // size in bytes
392   struct stat64 st;
393   int platformFound = 0;
394#ifdef __sun__
395   struct dk_minfo minfo;
396#endif
397
398   // If disk isn't open, try to open it....
399   if (!isOpen) {
400      OpenForRead();
401   } // if
402
403   if (isOpen) {
404      // Note to self: I recall testing a simplified version of
405      // this code, similar to what's in the __APPLE__ block,
406      // on Linux, but I had some problems. IIRC, it ran OK on 32-bit
407      // systems but not on 64-bit. Keep this in mind in case of
408      // 32/64-bit issues on MacOS....
409#ifdef __APPLE__
410      *err = ioctl(fd, DKIOCGETBLOCKCOUNT, &sectors);
411      platformFound++;
412#endif
413#ifdef __sun__
414      *err = ioctl(fd, DKIOCGMEDIAINFO, &minfo);
415      if (*err == 0)
416          sectors = minfo.dki_capacity;
417      platformFound++;
418#endif
419#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
420      *err = ioctl(fd, DIOCGMEDIASIZE, &bytes);
421      long long b = GetBlockSize();
422      sectors = bytes / b;
423      platformFound++;
424#endif
425#ifdef __linux__
426      long sz;
427      long long b;
428      *err = ioctl(fd, BLKGETSIZE, &sz);
429      if (*err) {
430         sectors = sz = 0;
431      } // if
432      if ((!*err) || (errno == EFBIG)) {
433         *err = ioctl(fd, BLKGETSIZE64, &b);
434         if (*err || b == 0 || b == sz)
435            sectors = sz;
436         else
437            sectors = (b >> 9);
438      } // if
439      // Unintuitively, the above returns values in 512-byte blocks, no
440      // matter what the underlying device's block size. Correct for this....
441      sectors /= (GetBlockSize() / 512);
442      platformFound++;
443#endif
444      if (platformFound != 1)
445         cerr << "Warning! We seem to be running on no known platform!\n";
446
447      // The above methods have failed, so let's assume it's a regular
448      // file (a QEMU image, dd backup, or what have you) and see what
449      // fstat() gives us....
450      if ((sectors == 0) || (*err == -1)) {
451         if (fstat64(fd, &st) == 0) {
452            bytes = st.st_size;
453            if ((bytes % UINT64_C(512)) != 0)
454               cerr << "Warning: File size is not a multiple of 512 bytes!"
455                    << " Misbehavior is likely!\n\a";
456            sectors = bytes / UINT64_C(512);
457         } // if
458      } // if
459   } // if (isOpen)
460   return sectors;
461} // DiskIO::DiskSize()
462