blob: a72c7ce96318e754a5e21685d220830fd5658233 [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.
187int DiskIO::Seek(uint64_t sector) {
188 int retval = 1;
189 off_t seekTo, sought;
190
191 // If disk isn't open, try to open it....
192 if (!isOpen) {
193 retval = OpenForRead();
194 } // if
195
196 if (isOpen) {
197 seekTo = sector * (uint64_t) GetBlockSize();
198 sought = lseek64(fd, seekTo, SEEK_SET);
199 if (sought != seekTo) {
200 retval = 0;
201 } // if
202 } // if
203 return retval;
204} // DiskIO::Seek()
205
206// A variant on the standard read() function. Done to work around
207// limitations in FreeBSD concerning the matching of the sector
208// size with the number of bytes read.
209// Returns the number of bytes read into buffer.
210int DiskIO::Read(void* buffer, int numBytes) {
211 int blockSize = 512, i, numBlocks, retval = 0;
212 char* tempSpace;
213
214 // If disk isn't open, try to open it....
215 if (!isOpen) {
216 OpenForRead();
217 } // if
218
219 if (isOpen) {
220 // Compute required space and allocate memory
221 blockSize = GetBlockSize();
222 if (numBytes <= blockSize) {
223 numBlocks = 1;
224 tempSpace = (char*) malloc(blockSize);
225 } else {
226 numBlocks = numBytes / blockSize;
227 if ((numBytes % blockSize) != 0) numBlocks++;
228 tempSpace = (char*) malloc(numBlocks * blockSize);
229 } // if/else
230
231 // Read the data into temporary space, then copy it to buffer
232 retval = read(fd, tempSpace, numBlocks * blockSize);
233 memcpy(buffer, tempSpace, numBytes);
srs5694add79a62010-01-26 15:59:58 -0500234
235 // Adjust the return value, if necessary....
236 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
237 retval = numBytes;
238
239 free(tempSpace);
240 } // if (isOpen)
241 return retval;
242} // DiskIO::Read()
243
244// A variant on the standard write() function. Done to work around
245// limitations in FreeBSD concerning the matching of the sector
246// size with the number of bytes read.
247// Returns the number of bytes written.
248int DiskIO::Write(void* buffer, int numBytes) {
249 int blockSize = 512, i, numBlocks, retval = 0;
250 char* tempSpace;
251
252 // If disk isn't open, try to open it....
253 if ((!isOpen) || (!openForWrite)) {
254 OpenForWrite();
255 } // if
256
257 if (isOpen) {
258 // Compute required space and allocate memory
259 blockSize = GetBlockSize();
260 if (numBytes <= blockSize) {
261 numBlocks = 1;
262 tempSpace = (char*) malloc(blockSize);
263 } else {
264 numBlocks = numBytes / blockSize;
265 if ((numBytes % blockSize) != 0) numBlocks++;
266 tempSpace = (char*) malloc(numBlocks * blockSize);
267 } // if/else
268
269 // Copy the data to my own buffer, then write it
270/* for (i = 0; i < numBytes; i++) {
271 tempSpace[i] = ((char*) buffer)[i];
272 } // for */
273 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
283 free(tempSpace);
284 } // 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) {
299 long sz; // Do not delete; needed for Linux
300 long long b; // Do not delete; needed for Linux
301 uint64_t sectors = 0; // size in sectors
302 off_t bytes = 0; // size in bytes
303 struct stat64 st;
304 int platformFound = 0;
305
306 // If disk isn't open, try to open it....
307 if (!isOpen) {
308 OpenForRead();
309 } // if
310
311 if (isOpen) {
312 // Note to self: I recall testing a simplified version of
313 // this code, similar to what's in the __APPLE__ block,
314 // on Linux, but I had some problems. IIRC, it ran OK on 32-bit
315 // systems but not on 64-bit. Keep this in mind in case of
316 // 32/64-bit issues on MacOS....
317#ifdef __APPLE__
318 *err = ioctl(fd, DKIOCGETBLOCKCOUNT, &sectors);
319 platformFound++;
320#endif
321#ifdef __FreeBSD__
322 *err = ioctl(fd, DIOCGMEDIASIZE, &bytes);
323 b = GetBlockSize();
324 sectors = bytes / b;
325 platformFound++;
326#endif
327#ifdef __linux__
328 *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()