blob: 7d24301e7b63696b6d3806ad478b0bd696785dc4 [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
srs569455d92612010-03-07 22:16:07 -050037// Open the currently on-record file for reading. Returns 1 if the file is
38// already open or is opened by this call, 0 if opening the file doesn't
39// work.
srs5694add79a62010-01-26 15:59:58 -050040int DiskIO::OpenForRead(void) {
41 int shouldOpen = 1;
42
43 if (isOpen) { // file is already open
44 if (openForWrite) {
45 Close();
46 } else {
47 shouldOpen = 0;
48 } // if/else
49 } // if
50
51 if (shouldOpen) {
52 fd = open(realFilename.c_str(), O_RDONLY);
53 if (fd == -1) {
srs569455d92612010-03-07 22:16:07 -050054 cerr << "Problem opening " << realFilename << " for reading! Error is " << errno << ".\n";
55 if (errno == EACCES) // User is probably not running as root
srs5694fed16d02010-01-27 23:03:40 -050056 cerr << "You must run this program as root or use sudo!\n";
srs569455d92612010-03-07 22:16:07 -050057 if (errno == ENOENT)
58 cerr << "The specified file does not exist!\n";
srs5694add79a62010-01-26 15:59:58 -050059 realFilename = "";
60 userFilename = "";
61 isOpen = 0;
62 openForWrite = 0;
63 } else {
64 isOpen = 1;
65 openForWrite = 0;
66 } // if/else
67 } // if
68
69 return isOpen;
70} // DiskIO::OpenForRead(void)
71
72// An extended file-open function. This includes some system-specific checks.
73// Returns 1 if the file is open, 0 otherwise....
74int DiskIO::OpenForWrite(void) {
75 if ((isOpen) && (openForWrite))
76 return 1;
77
78 // Close the disk, in case it's already open for reading only....
79 Close();
80
81 // try to open the device; may fail....
82 fd = open(realFilename.c_str(), O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
83#ifdef __APPLE__
84 // MacOS X requires a shared lock under some circumstances....
85 if (fd < 0) {
srs5694fed16d02010-01-27 23:03:40 -050086 fd = open(realFilename.c_str(), O_WRONLY | O_SHLOCK);
srs5694add79a62010-01-26 15:59:58 -050087 } // if
88#endif
89 if (fd >= 0) {
90 isOpen = 1;
91 openForWrite = 1;
92 } else {
93 isOpen = 0;
94 openForWrite = 0;
95 } // if/else
96 return isOpen;
97} // DiskIO::OpenForWrite(void)
98
99// Close the disk device. Note that this does NOT erase the stored filenames,
100// so the file can be re-opened without specifying the filename.
101void DiskIO::Close(void) {
102 if (isOpen)
srs569408bb0da2010-02-19 17:19:55 -0500103 if (close(fd) < 0)
104 cerr << "Warning! Problem closing file!\n";
srs5694add79a62010-01-26 15:59:58 -0500105 isOpen = 0;
106 openForWrite = 0;
107} // DiskIO::Close()
108
109// Returns block size of device pointed to by fd file descriptor. If the ioctl
110// returns an error condition, print a warning but return a value of SECTOR_SIZE
srs569455d92612010-03-07 22:16:07 -0500111// (512). If the disk can't be opened at all, return a value of 0.
srs5694add79a62010-01-26 15:59:58 -0500112int DiskIO::GetBlockSize(void) {
113 int err = -1, blockSize = 0;
114
115 // If disk isn't open, try to open it....
116 if (!isOpen) {
117 OpenForRead();
118 } // if
119
120 if (isOpen) {
121#ifdef __APPLE__
122 err = ioctl(fd, DKIOCGETBLOCKSIZE, &blockSize);
123#endif
srs569408bb0da2010-02-19 17:19:55 -0500124#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500125 err = ioctl(fd, DIOCGSECTORSIZE, &blockSize);
126#endif
127#ifdef __linux__
128 err = ioctl(fd, BLKSSZGET, &blockSize);
129#endif
130
131 if (err == -1) {
132 blockSize = SECTOR_SIZE;
133 // ENOTTY = inappropriate ioctl; probably being called on a disk image
134 // file, so don't display the warning message....
135 // 32-bit code returns EINVAL, I don't know why. I know I'm treading on
136 // thin ice here, but it should be OK in all but very weird cases....
137 if ((errno != ENOTTY) && (errno != EINVAL)) {
srs5694fed16d02010-01-27 23:03:40 -0500138 cerr << "\aError " << errno << " when determining sector size! Setting sector size to "
139 << SECTOR_SIZE << "\n";
srs569455d92612010-03-07 22:16:07 -0500140 cout << "Disk device is " << realFilename << "\n";
srs5694add79a62010-01-26 15:59:58 -0500141 } // if
142 } // if (err == -1)
143 } // if (isOpen)
144
145 return (blockSize);
146} // DiskIO::GetBlockSize()
147
148// Resync disk caches so the OS uses the new partition table. This code varies
149// a lot from one OS to another.
150void DiskIO::DiskSync(void) {
151 int i, platformFound = 0;
152
153 // If disk isn't open, try to open it....
154 if (!isOpen) {
155 OpenForRead();
156 } // if
157
158 if (isOpen) {
159 sync();
160#ifdef __APPLE__
srs5694fed16d02010-01-27 23:03:40 -0500161 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
162 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500163 /* don't know if this helps
164 * it definitely will get things on disk though:
165 * http://topiks.org/mac-os-x/0321278542/ch12lev1sec8.html */
166 i = ioctl(fd, DKIOCSYNCHRONIZECACHE);
167 platformFound++;
168#endif
srs569408bb0da2010-02-19 17:19:55 -0500169#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500170 sleep(2);
171 i = ioctl(fd, DIOCGFLUSH);
srs5694fed16d02010-01-27 23:03:40 -0500172 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
173 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500174 platformFound++;
175#endif
176#ifdef __linux__
177 sleep(2);
178 i = ioctl(fd, BLKRRPART);
179 if (i)
srs5694fed16d02010-01-27 23:03:40 -0500180 cout << "Warning: The kernel is still using the old partition table.\n"
181 << "The new table will be used at the next reboot.\n";
srs5694add79a62010-01-26 15:59:58 -0500182 platformFound++;
183#endif
184 if (platformFound == 0)
srs5694fed16d02010-01-27 23:03:40 -0500185 cerr << "Warning: Platform not recognized!\n";
srs5694add79a62010-01-26 15:59:58 -0500186 if (platformFound > 1)
srs5694fed16d02010-01-27 23:03:40 -0500187 cerr << "\nWarning: We seem to be running on multiple platforms!\n";
srs5694add79a62010-01-26 15:59:58 -0500188 } // if (isOpen)
189} // DiskIO::DiskSync()
190
191// Seek to the specified sector. Returns 1 on success, 0 on failure.
srs5694cb76c672010-02-11 22:22:22 -0500192// Note that seeking beyond the end of the file is NOT detected as a failure!
srs5694add79a62010-01-26 15:59:58 -0500193int DiskIO::Seek(uint64_t sector) {
194 int retval = 1;
195 off_t seekTo, sought;
196
197 // If disk isn't open, try to open it....
198 if (!isOpen) {
199 retval = OpenForRead();
200 } // if
201
202 if (isOpen) {
203 seekTo = sector * (uint64_t) GetBlockSize();
204 sought = lseek64(fd, seekTo, SEEK_SET);
205 if (sought != seekTo) {
206 retval = 0;
207 } // if
208 } // if
209 return retval;
210} // DiskIO::Seek()
211
212// A variant on the standard read() function. Done to work around
213// limitations in FreeBSD concerning the matching of the sector
214// size with the number of bytes read.
215// Returns the number of bytes read into buffer.
216int DiskIO::Read(void* buffer, int numBytes) {
srs5694cb76c672010-02-11 22:22:22 -0500217 int blockSize, numBlocks, retval = 0;
srs5694add79a62010-01-26 15:59:58 -0500218 char* tempSpace;
219
220 // If disk isn't open, try to open it....
221 if (!isOpen) {
222 OpenForRead();
223 } // if
224
225 if (isOpen) {
226 // Compute required space and allocate memory
227 blockSize = GetBlockSize();
228 if (numBytes <= blockSize) {
229 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500230 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500231 } else {
232 numBlocks = numBytes / blockSize;
srs5694cb76c672010-02-11 22:22:22 -0500233 if ((numBytes % blockSize) != 0)
234 numBlocks++;
235 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500236 } // if/else
237
238 // Read the data into temporary space, then copy it to buffer
239 retval = read(fd, tempSpace, numBlocks * blockSize);
240 memcpy(buffer, tempSpace, numBytes);
srs5694add79a62010-01-26 15:59:58 -0500241
242 // Adjust the return value, if necessary....
243 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
244 retval = numBytes;
245
srs5694cb76c672010-02-11 22:22:22 -0500246 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500247 } // if (isOpen)
248 return retval;
249} // DiskIO::Read()
250
251// A variant on the standard write() function. Done to work around
252// limitations in FreeBSD concerning the matching of the sector
253// size with the number of bytes read.
254// Returns the number of bytes written.
255int DiskIO::Write(void* buffer, int numBytes) {
256 int blockSize = 512, i, numBlocks, retval = 0;
257 char* tempSpace;
258
259 // If disk isn't open, try to open it....
260 if ((!isOpen) || (!openForWrite)) {
261 OpenForWrite();
262 } // if
263
264 if (isOpen) {
265 // Compute required space and allocate memory
266 blockSize = GetBlockSize();
267 if (numBytes <= blockSize) {
268 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500269 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500270 } else {
271 numBlocks = numBytes / blockSize;
272 if ((numBytes % blockSize) != 0) numBlocks++;
srs5694cb76c672010-02-11 22:22:22 -0500273 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500274 } // if/else
275
276 // Copy the data to my own buffer, then write it
srs5694add79a62010-01-26 15:59:58 -0500277 memcpy(tempSpace, buffer, numBytes);
278 for (i = numBytes; i < numBlocks * blockSize; i++) {
279 tempSpace[i] = 0;
280 } // for
281 retval = write(fd, tempSpace, numBlocks * blockSize);
282
283 // Adjust the return value, if necessary....
284 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
285 retval = numBytes;
286
srs5694cb76c672010-02-11 22:22:22 -0500287 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500288 } // if (isOpen)
289 return retval;
290} // DiskIO:Write()
291
292/**************************************************************************************
293 * *
294 * Below functions are lifted from various sources, as documented in comments before *
295 * each one. *
296 * *
297 **************************************************************************************/
298
299// The disksize function is taken from the Linux fdisk code and modified
300// greatly since then to enable FreeBSD and MacOS support, as well as to
301// return correct values for disk image files.
302uint64_t DiskIO::DiskSize(int *err) {
srs5694add79a62010-01-26 15:59:58 -0500303 uint64_t sectors = 0; // size in sectors
304 off_t bytes = 0; // size in bytes
305 struct stat64 st;
306 int platformFound = 0;
307
308 // If disk isn't open, try to open it....
309 if (!isOpen) {
310 OpenForRead();
311 } // if
312
313 if (isOpen) {
314 // Note to self: I recall testing a simplified version of
315 // this code, similar to what's in the __APPLE__ block,
316 // on Linux, but I had some problems. IIRC, it ran OK on 32-bit
317 // systems but not on 64-bit. Keep this in mind in case of
318 // 32/64-bit issues on MacOS....
319#ifdef __APPLE__
320 *err = ioctl(fd, DKIOCGETBLOCKCOUNT, &sectors);
321 platformFound++;
322#endif
srs569408bb0da2010-02-19 17:19:55 -0500323#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500324 *err = ioctl(fd, DIOCGMEDIASIZE, &bytes);
srs569408bb0da2010-02-19 17:19:55 -0500325 long long b = GetBlockSize();
srs5694add79a62010-01-26 15:59:58 -0500326 sectors = bytes / b;
327 platformFound++;
328#endif
329#ifdef __linux__
srs569408bb0da2010-02-19 17:19:55 -0500330 long sz;
331 long long b;
srs5694add79a62010-01-26 15:59:58 -0500332 *err = ioctl(fd, BLKGETSIZE, &sz);
333 if (*err) {
334 sectors = sz = 0;
335 } // if
336 if ((errno == EFBIG) || (!*err)) {
337 *err = ioctl(fd, BLKGETSIZE64, &b);
338 if (*err || b == 0 || b == sz)
339 sectors = sz;
340 else
341 sectors = (b >> 9);
342 } // if
343 // Unintuitively, the above returns values in 512-byte blocks, no
344 // matter what the underlying device's block size. Correct for this....
345 sectors /= (GetBlockSize() / 512);
346 platformFound++;
347#endif
348 if (platformFound != 1)
srs5694fed16d02010-01-27 23:03:40 -0500349 cerr << "Warning! We seem to be running on no known platform!\n";
srs5694add79a62010-01-26 15:59:58 -0500350
351 // The above methods have failed, so let's assume it's a regular
352 // file (a QEMU image, dd backup, or what have you) and see what
353 // fstat() gives us....
354 if ((sectors == 0) || (*err == -1)) {
355 if (fstat64(fd, &st) == 0) {
356 bytes = (off_t) st.st_size;
357 if ((bytes % UINT64_C(512)) != 0)
srs5694fed16d02010-01-27 23:03:40 -0500358 cerr << "Warning: File size is not a multiple of 512 bytes!"
359 << " Misbehavior is likely!\n\a";
srs5694add79a62010-01-26 15:59:58 -0500360 sectors = bytes / UINT64_C(512);
361 } // if
362 } // if
363 } // if (isOpen)
364 return sectors;
365} // DiskIO::DiskSize()