blob: 00ffd99d0d9db5e199a5284502fb6fe6720602bb [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)
100 close(fd);
101 isOpen = 0;
102 openForWrite = 0;
103} // DiskIO::Close()
104
105// Returns block size of device pointed to by fd file descriptor. If the ioctl
106// returns an error condition, print a warning but return a value of SECTOR_SIZE
107// (512)..
108int DiskIO::GetBlockSize(void) {
109 int err = -1, blockSize = 0;
110
111 // If disk isn't open, try to open it....
112 if (!isOpen) {
113 OpenForRead();
114 } // if
115
116 if (isOpen) {
117#ifdef __APPLE__
118 err = ioctl(fd, DKIOCGETBLOCKSIZE, &blockSize);
119#endif
120#ifdef __FreeBSD__
121 err = ioctl(fd, DIOCGSECTORSIZE, &blockSize);
122#endif
123#ifdef __linux__
124 err = ioctl(fd, BLKSSZGET, &blockSize);
125#endif
126
127 if (err == -1) {
128 blockSize = SECTOR_SIZE;
129 // ENOTTY = inappropriate ioctl; probably being called on a disk image
130 // file, so don't display the warning message....
131 // 32-bit code returns EINVAL, I don't know why. I know I'm treading on
132 // thin ice here, but it should be OK in all but very weird cases....
133 if ((errno != ENOTTY) && (errno != EINVAL)) {
srs5694fed16d02010-01-27 23:03:40 -0500134 cerr << "\aError " << errno << " when determining sector size! Setting sector size to "
135 << SECTOR_SIZE << "\n";
srs5694add79a62010-01-26 15:59:58 -0500136 } // if
137 } // if (err == -1)
138 } // if (isOpen)
139
140 return (blockSize);
141} // DiskIO::GetBlockSize()
142
143// Resync disk caches so the OS uses the new partition table. This code varies
144// a lot from one OS to another.
145void DiskIO::DiskSync(void) {
146 int i, platformFound = 0;
147
148 // If disk isn't open, try to open it....
149 if (!isOpen) {
150 OpenForRead();
151 } // if
152
153 if (isOpen) {
154 sync();
155#ifdef __APPLE__
srs5694fed16d02010-01-27 23:03:40 -0500156 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
157 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500158 /* don't know if this helps
159 * it definitely will get things on disk though:
160 * http://topiks.org/mac-os-x/0321278542/ch12lev1sec8.html */
161 i = ioctl(fd, DKIOCSYNCHRONIZECACHE);
162 platformFound++;
163#endif
164#ifdef __FreeBSD__
165 sleep(2);
166 i = ioctl(fd, DIOCGFLUSH);
srs5694fed16d02010-01-27 23:03:40 -0500167 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
168 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500169 platformFound++;
170#endif
171#ifdef __linux__
172 sleep(2);
173 i = ioctl(fd, BLKRRPART);
174 if (i)
srs5694fed16d02010-01-27 23:03:40 -0500175 cout << "Warning: The kernel is still using the old partition table.\n"
176 << "The new table will be used at the next reboot.\n";
srs5694add79a62010-01-26 15:59:58 -0500177 platformFound++;
178#endif
179 if (platformFound == 0)
srs5694fed16d02010-01-27 23:03:40 -0500180 cerr << "Warning: Platform not recognized!\n";
srs5694add79a62010-01-26 15:59:58 -0500181 if (platformFound > 1)
srs5694fed16d02010-01-27 23:03:40 -0500182 cerr << "\nWarning: We seem to be running on multiple platforms!\n";
srs5694add79a62010-01-26 15:59:58 -0500183 } // if (isOpen)
184} // DiskIO::DiskSync()
185
186// Seek to the specified sector. Returns 1 on success, 0 on failure.
srs5694cb76c672010-02-11 22:22:22 -0500187// Note that seeking beyond the end of the file is NOT detected as a failure!
srs5694add79a62010-01-26 15:59:58 -0500188int DiskIO::Seek(uint64_t sector) {
189 int retval = 1;
190 off_t seekTo, sought;
191
192 // If disk isn't open, try to open it....
193 if (!isOpen) {
194 retval = OpenForRead();
195 } // if
196
197 if (isOpen) {
198 seekTo = sector * (uint64_t) GetBlockSize();
199 sought = lseek64(fd, seekTo, SEEK_SET);
200 if (sought != seekTo) {
201 retval = 0;
202 } // if
203 } // if
204 return retval;
205} // DiskIO::Seek()
206
207// A variant on the standard read() function. Done to work around
208// limitations in FreeBSD concerning the matching of the sector
209// size with the number of bytes read.
210// Returns the number of bytes read into buffer.
211int DiskIO::Read(void* buffer, int numBytes) {
srs5694cb76c672010-02-11 22:22:22 -0500212 int blockSize, numBlocks, retval = 0;
srs5694add79a62010-01-26 15:59:58 -0500213 char* tempSpace;
214
215 // If disk isn't open, try to open it....
216 if (!isOpen) {
217 OpenForRead();
218 } // if
219
220 if (isOpen) {
221 // Compute required space and allocate memory
222 blockSize = GetBlockSize();
223 if (numBytes <= blockSize) {
224 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500225 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500226 } else {
227 numBlocks = numBytes / blockSize;
srs5694cb76c672010-02-11 22:22:22 -0500228 if ((numBytes % blockSize) != 0)
229 numBlocks++;
230 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500231 } // if/else
232
233 // Read the data into temporary space, then copy it to buffer
234 retval = read(fd, tempSpace, numBlocks * blockSize);
235 memcpy(buffer, tempSpace, numBytes);
srs5694add79a62010-01-26 15:59:58 -0500236
237 // Adjust the return value, if necessary....
238 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
239 retval = numBytes;
240
srs5694cb76c672010-02-11 22:22:22 -0500241 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500242 } // if (isOpen)
243 return retval;
244} // DiskIO::Read()
245
246// A variant on the standard write() function. Done to work around
247// limitations in FreeBSD concerning the matching of the sector
248// size with the number of bytes read.
249// Returns the number of bytes written.
250int DiskIO::Write(void* buffer, int numBytes) {
251 int blockSize = 512, i, numBlocks, retval = 0;
252 char* tempSpace;
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;
srs5694cb76c672010-02-11 22:22:22 -0500264 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500265 } else {
266 numBlocks = numBytes / blockSize;
267 if ((numBytes % blockSize) != 0) numBlocks++;
srs5694cb76c672010-02-11 22:22:22 -0500268 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500269 } // if/else
270
271 // Copy the data to my own buffer, then write it
srs5694add79a62010-01-26 15:59:58 -0500272 memcpy(tempSpace, buffer, numBytes);
273 for (i = numBytes; i < numBlocks * blockSize; i++) {
274 tempSpace[i] = 0;
275 } // for
276 retval = write(fd, tempSpace, numBlocks * blockSize);
277
278 // Adjust the return value, if necessary....
279 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
280 retval = numBytes;
281
srs5694cb76c672010-02-11 22:22:22 -0500282 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500283 } // if (isOpen)
284 return retval;
285} // DiskIO:Write()
286
287/**************************************************************************************
288 * *
289 * Below functions are lifted from various sources, as documented in comments before *
290 * each one. *
291 * *
292 **************************************************************************************/
293
294// The disksize function is taken from the Linux fdisk code and modified
295// greatly since then to enable FreeBSD and MacOS support, as well as to
296// return correct values for disk image files.
297uint64_t DiskIO::DiskSize(int *err) {
298 long sz; // Do not delete; needed for Linux
299 long long b; // Do not delete; needed for Linux
300 uint64_t sectors = 0; // size in sectors
301 off_t bytes = 0; // size in bytes
302 struct stat64 st;
303 int platformFound = 0;
304
305 // If disk isn't open, try to open it....
306 if (!isOpen) {
307 OpenForRead();
308 } // if
309
310 if (isOpen) {
311 // Note to self: I recall testing a simplified version of
312 // this code, similar to what's in the __APPLE__ block,
313 // on Linux, but I had some problems. IIRC, it ran OK on 32-bit
314 // systems but not on 64-bit. Keep this in mind in case of
315 // 32/64-bit issues on MacOS....
316#ifdef __APPLE__
317 *err = ioctl(fd, DKIOCGETBLOCKCOUNT, &sectors);
318 platformFound++;
319#endif
320#ifdef __FreeBSD__
321 *err = ioctl(fd, DIOCGMEDIASIZE, &bytes);
322 b = GetBlockSize();
323 sectors = bytes / b;
324 platformFound++;
325#endif
326#ifdef __linux__
327 *err = ioctl(fd, BLKGETSIZE, &sz);
328 if (*err) {
329 sectors = sz = 0;
330 } // if
331 if ((errno == EFBIG) || (!*err)) {
332 *err = ioctl(fd, BLKGETSIZE64, &b);
333 if (*err || b == 0 || b == sz)
334 sectors = sz;
335 else
336 sectors = (b >> 9);
337 } // if
338 // Unintuitively, the above returns values in 512-byte blocks, no
339 // matter what the underlying device's block size. Correct for this....
340 sectors /= (GetBlockSize() / 512);
341 platformFound++;
342#endif
343 if (platformFound != 1)
srs5694fed16d02010-01-27 23:03:40 -0500344 cerr << "Warning! We seem to be running on no known platform!\n";
srs5694add79a62010-01-26 15:59:58 -0500345
346 // The above methods have failed, so let's assume it's a regular
347 // file (a QEMU image, dd backup, or what have you) and see what
348 // fstat() gives us....
349 if ((sectors == 0) || (*err == -1)) {
350 if (fstat64(fd, &st) == 0) {
351 bytes = (off_t) st.st_size;
352 if ((bytes % UINT64_C(512)) != 0)
srs5694fed16d02010-01-27 23:03:40 -0500353 cerr << "Warning: File size is not a multiple of 512 bytes!"
354 << " Misbehavior is likely!\n\a";
srs5694add79a62010-01-26 15:59:58 -0500355 sectors = bytes / UINT64_C(512);
356 } // if
357 } // if
358 } // if (isOpen)
359 return sectors;
360} // DiskIO::DiskSize()