blob: e95573976cd7266088cd2b0d3f8747d08c66f7d6 [file] [log] [blame]
srs5694add79a62010-01-26 15:59:58 -05001//
2// C++ Interface: diskio (Unix components [Linux, FreeBSD, Mac OS X])
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 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 <sys/ioctl.h>
srs5694fed16d02010-01-27 23:03:40 -050019#include <string.h>
srs5694add79a62010-01-26 15:59:58 -050020#include <string>
21#include <stdint.h>
22#include <errno.h>
23#include <fcntl.h>
24#include <sys/stat.h>
25#include <iostream>
26
srs5694add79a62010-01-26 15:59:58 -050027#include "diskio.h"
28
29using namespace std;
30
31// Returns the official "real" name for a shortened version of same.
32// Trivial here; more important in Windows
33void DiskIO::MakeRealName(void) {
34 realFilename = userFilename;
35} // DiskIO::MakeRealName()
36
37// Open the currently on-record file for reading
38int DiskIO::OpenForRead(void) {
39 int shouldOpen = 1;
40
41 if (isOpen) { // file is already open
42 if (openForWrite) {
43 Close();
44 } else {
45 shouldOpen = 0;
46 } // if/else
47 } // if
48
49 if (shouldOpen) {
50 fd = open(realFilename.c_str(), O_RDONLY);
51 if (fd == -1) {
srs5694fed16d02010-01-27 23:03:40 -050052 cerr << "Problem opening " << realFilename << " for reading! Error is " << errno << "\n";
srs5694add79a62010-01-26 15:59:58 -050053 if (errno == EACCES) { // User is probably not running as root
srs5694fed16d02010-01-27 23:03:40 -050054 cerr << "You must run this program as root or use sudo!\n";
srs5694add79a62010-01-26 15:59:58 -050055 } // if
56 realFilename = "";
57 userFilename = "";
58 isOpen = 0;
59 openForWrite = 0;
60 } else {
61 isOpen = 1;
62 openForWrite = 0;
63 } // if/else
64 } // if
65
66 return isOpen;
67} // DiskIO::OpenForRead(void)
68
69// An extended file-open function. This includes some system-specific checks.
70// Returns 1 if the file is open, 0 otherwise....
71int DiskIO::OpenForWrite(void) {
72 if ((isOpen) && (openForWrite))
73 return 1;
74
75 // Close the disk, in case it's already open for reading only....
76 Close();
77
78 // try to open the device; may fail....
79 fd = open(realFilename.c_str(), O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
80#ifdef __APPLE__
81 // MacOS X requires a shared lock under some circumstances....
82 if (fd < 0) {
srs5694fed16d02010-01-27 23:03:40 -050083 fd = open(realFilename.c_str(), O_WRONLY | O_SHLOCK);
srs5694add79a62010-01-26 15:59:58 -050084 } // if
85#endif
86 if (fd >= 0) {
87 isOpen = 1;
88 openForWrite = 1;
89 } else {
90 isOpen = 0;
91 openForWrite = 0;
92 } // if/else
93 return isOpen;
94} // DiskIO::OpenForWrite(void)
95
96// Close the disk device. Note that this does NOT erase the stored filenames,
97// so the file can be re-opened without specifying the filename.
98void DiskIO::Close(void) {
99 if (isOpen)
srs569408bb0da2010-02-19 17:19:55 -0500100 if (close(fd) < 0)
101 cerr << "Warning! Problem closing file!\n";
srs5694add79a62010-01-26 15:59:58 -0500102 isOpen = 0;
103 openForWrite = 0;
104} // DiskIO::Close()
105
106// Returns block size of device pointed to by fd file descriptor. If the ioctl
107// returns an error condition, print a warning but return a value of SECTOR_SIZE
108// (512)..
109int DiskIO::GetBlockSize(void) {
110 int err = -1, blockSize = 0;
111
112 // If disk isn't open, try to open it....
113 if (!isOpen) {
114 OpenForRead();
115 } // if
116
117 if (isOpen) {
118#ifdef __APPLE__
119 err = ioctl(fd, DKIOCGETBLOCKSIZE, &blockSize);
120#endif
srs569408bb0da2010-02-19 17:19:55 -0500121#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500122 err = ioctl(fd, DIOCGSECTORSIZE, &blockSize);
123#endif
124#ifdef __linux__
125 err = ioctl(fd, BLKSSZGET, &blockSize);
126#endif
127
128 if (err == -1) {
129 blockSize = SECTOR_SIZE;
130 // ENOTTY = inappropriate ioctl; probably being called on a disk image
131 // file, so don't display the warning message....
132 // 32-bit code returns EINVAL, I don't know why. I know I'm treading on
133 // thin ice here, but it should be OK in all but very weird cases....
134 if ((errno != ENOTTY) && (errno != EINVAL)) {
srs5694fed16d02010-01-27 23:03:40 -0500135 cerr << "\aError " << errno << " when determining sector size! Setting sector size to "
136 << SECTOR_SIZE << "\n";
srs5694add79a62010-01-26 15:59:58 -0500137 } // if
138 } // if (err == -1)
139 } // if (isOpen)
140
141 return (blockSize);
142} // DiskIO::GetBlockSize()
143
144// Resync disk caches so the OS uses the new partition table. This code varies
145// a lot from one OS to another.
146void DiskIO::DiskSync(void) {
147 int i, platformFound = 0;
148
149 // If disk isn't open, try to open it....
150 if (!isOpen) {
151 OpenForRead();
152 } // if
153
154 if (isOpen) {
155 sync();
156#ifdef __APPLE__
srs5694fed16d02010-01-27 23:03:40 -0500157 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
158 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500159 /* don't know if this helps
160 * it definitely will get things on disk though:
161 * http://topiks.org/mac-os-x/0321278542/ch12lev1sec8.html */
162 i = ioctl(fd, DKIOCSYNCHRONIZECACHE);
163 platformFound++;
164#endif
srs569408bb0da2010-02-19 17:19:55 -0500165#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500166 sleep(2);
167 i = ioctl(fd, DIOCGFLUSH);
srs5694fed16d02010-01-27 23:03:40 -0500168 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
169 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500170 platformFound++;
171#endif
172#ifdef __linux__
173 sleep(2);
174 i = ioctl(fd, BLKRRPART);
175 if (i)
srs5694fed16d02010-01-27 23:03:40 -0500176 cout << "Warning: The kernel is still using the old partition table.\n"
177 << "The new table will be used at the next reboot.\n";
srs5694add79a62010-01-26 15:59:58 -0500178 platformFound++;
179#endif
180 if (platformFound == 0)
srs5694fed16d02010-01-27 23:03:40 -0500181 cerr << "Warning: Platform not recognized!\n";
srs5694add79a62010-01-26 15:59:58 -0500182 if (platformFound > 1)
srs5694fed16d02010-01-27 23:03:40 -0500183 cerr << "\nWarning: We seem to be running on multiple platforms!\n";
srs5694add79a62010-01-26 15:59:58 -0500184 } // if (isOpen)
185} // DiskIO::DiskSync()
186
187// Seek to the specified sector. Returns 1 on success, 0 on failure.
srs5694cb76c672010-02-11 22:22:22 -0500188// Note that seeking beyond the end of the file is NOT detected as a failure!
srs5694add79a62010-01-26 15:59:58 -0500189int DiskIO::Seek(uint64_t sector) {
190 int retval = 1;
191 off_t seekTo, sought;
192
193 // If disk isn't open, try to open it....
194 if (!isOpen) {
195 retval = OpenForRead();
196 } // if
197
198 if (isOpen) {
199 seekTo = sector * (uint64_t) GetBlockSize();
200 sought = lseek64(fd, seekTo, SEEK_SET);
201 if (sought != seekTo) {
202 retval = 0;
203 } // if
204 } // if
205 return retval;
206} // DiskIO::Seek()
207
208// A variant on the standard read() function. Done to work around
209// limitations in FreeBSD concerning the matching of the sector
210// size with the number of bytes read.
211// Returns the number of bytes read into buffer.
212int DiskIO::Read(void* buffer, int numBytes) {
srs5694cb76c672010-02-11 22:22:22 -0500213 int blockSize, numBlocks, retval = 0;
srs5694add79a62010-01-26 15:59:58 -0500214 char* tempSpace;
215
216 // If disk isn't open, try to open it....
217 if (!isOpen) {
218 OpenForRead();
219 } // if
220
221 if (isOpen) {
222 // Compute required space and allocate memory
223 blockSize = GetBlockSize();
224 if (numBytes <= blockSize) {
225 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500226 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500227 } else {
228 numBlocks = numBytes / blockSize;
srs5694cb76c672010-02-11 22:22:22 -0500229 if ((numBytes % blockSize) != 0)
230 numBlocks++;
231 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500232 } // if/else
233
234 // Read the data into temporary space, then copy it to buffer
235 retval = read(fd, tempSpace, numBlocks * blockSize);
236 memcpy(buffer, tempSpace, numBytes);
srs5694add79a62010-01-26 15:59:58 -0500237
238 // Adjust the return value, if necessary....
239 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
240 retval = numBytes;
241
srs5694cb76c672010-02-11 22:22:22 -0500242 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500243 } // if (isOpen)
244 return retval;
245} // DiskIO::Read()
246
247// A variant on the standard write() function. Done to work around
248// limitations in FreeBSD concerning the matching of the sector
249// size with the number of bytes read.
250// Returns the number of bytes written.
251int DiskIO::Write(void* buffer, int numBytes) {
252 int blockSize = 512, i, numBlocks, retval = 0;
253 char* tempSpace;
254
255 // If disk isn't open, try to open it....
256 if ((!isOpen) || (!openForWrite)) {
257 OpenForWrite();
258 } // if
259
260 if (isOpen) {
261 // Compute required space and allocate memory
262 blockSize = GetBlockSize();
263 if (numBytes <= blockSize) {
264 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500265 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500266 } else {
267 numBlocks = numBytes / blockSize;
268 if ((numBytes % blockSize) != 0) numBlocks++;
srs5694cb76c672010-02-11 22:22:22 -0500269 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500270 } // if/else
271
272 // Copy the data to my own buffer, then write it
srs5694add79a62010-01-26 15:59:58 -0500273 memcpy(tempSpace, buffer, numBytes);
274 for (i = numBytes; i < numBlocks * blockSize; i++) {
275 tempSpace[i] = 0;
276 } // for
277 retval = write(fd, tempSpace, numBlocks * blockSize);
278
279 // Adjust the return value, if necessary....
280 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
281 retval = numBytes;
282
srs5694cb76c672010-02-11 22:22:22 -0500283 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500284 } // if (isOpen)
285 return retval;
286} // DiskIO:Write()
287
288/**************************************************************************************
289 * *
290 * Below functions are lifted from various sources, as documented in comments before *
291 * each one. *
292 * *
293 **************************************************************************************/
294
295// The disksize function is taken from the Linux fdisk code and modified
296// greatly since then to enable FreeBSD and MacOS support, as well as to
297// return correct values for disk image files.
298uint64_t DiskIO::DiskSize(int *err) {
srs5694add79a62010-01-26 15:59:58 -0500299 uint64_t sectors = 0; // size in sectors
300 off_t bytes = 0; // size in bytes
301 struct stat64 st;
302 int platformFound = 0;
303
304 // If disk isn't open, try to open it....
305 if (!isOpen) {
306 OpenForRead();
307 } // if
308
309 if (isOpen) {
310 // Note to self: I recall testing a simplified version of
311 // this code, similar to what's in the __APPLE__ block,
312 // on Linux, but I had some problems. IIRC, it ran OK on 32-bit
313 // systems but not on 64-bit. Keep this in mind in case of
314 // 32/64-bit issues on MacOS....
315#ifdef __APPLE__
316 *err = ioctl(fd, DKIOCGETBLOCKCOUNT, &sectors);
317 platformFound++;
318#endif
srs569408bb0da2010-02-19 17:19:55 -0500319#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500320 *err = ioctl(fd, DIOCGMEDIASIZE, &bytes);
srs569408bb0da2010-02-19 17:19:55 -0500321 long long b = GetBlockSize();
srs5694add79a62010-01-26 15:59:58 -0500322 sectors = bytes / b;
323 platformFound++;
324#endif
325#ifdef __linux__
srs569408bb0da2010-02-19 17:19:55 -0500326 long sz;
327 long long b;
srs5694add79a62010-01-26 15:59:58 -0500328 *err = ioctl(fd, BLKGETSIZE, &sz);
329 if (*err) {
330 sectors = sz = 0;
331 } // if
332 if ((errno == EFBIG) || (!*err)) {
333 *err = ioctl(fd, BLKGETSIZE64, &b);
334 if (*err || b == 0 || b == sz)
335 sectors = sz;
336 else
337 sectors = (b >> 9);
338 } // if
339 // Unintuitively, the above returns values in 512-byte blocks, no
340 // matter what the underlying device's block size. Correct for this....
341 sectors /= (GetBlockSize() / 512);
342 platformFound++;
343#endif
344 if (platformFound != 1)
srs5694fed16d02010-01-27 23:03:40 -0500345 cerr << "Warning! We seem to be running on no known platform!\n";
srs5694add79a62010-01-26 15:59:58 -0500346
347 // The above methods have failed, so let's assume it's a regular
348 // file (a QEMU image, dd backup, or what have you) and see what
349 // fstat() gives us....
350 if ((sectors == 0) || (*err == -1)) {
351 if (fstat64(fd, &st) == 0) {
352 bytes = (off_t) st.st_size;
353 if ((bytes % UINT64_C(512)) != 0)
srs5694fed16d02010-01-27 23:03:40 -0500354 cerr << "Warning: File size is not a multiple of 512 bytes!"
355 << " Misbehavior is likely!\n\a";
srs5694add79a62010-01-26 15:59:58 -0500356 sectors = bytes / UINT64_C(512);
357 } // if
358 } // if
359 } // if (isOpen)
360 return sectors;
361} // DiskIO::DiskSize()