blob: 573af5638f98c3520e3f182920e6aa21c9410c66 [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>
19#include <stdio.h>
20#include <string>
21#include <stdint.h>
22#include <errno.h>
23#include <fcntl.h>
24#include <sys/stat.h>
25#include <iostream>
26
27#include "support.h"
28#include "diskio.h"
29
30using namespace std;
31
32// Returns the official "real" name for a shortened version of same.
33// Trivial here; more important in Windows
34void DiskIO::MakeRealName(void) {
35 realFilename = userFilename;
36} // DiskIO::MakeRealName()
37
38// Open the currently on-record file for reading
39int DiskIO::OpenForRead(void) {
40 int shouldOpen = 1;
41
42 if (isOpen) { // file is already open
43 if (openForWrite) {
44 Close();
45 } else {
46 shouldOpen = 0;
47 } // if/else
48 } // if
49
50 if (shouldOpen) {
51 fd = open(realFilename.c_str(), O_RDONLY);
52 if (fd == -1) {
53 fprintf(stderr, "Problem opening %s for reading! Error is %d\n",
54 realFilename.c_str(), errno);
55 if (errno == EACCES) { // User is probably not running as root
56 fprintf(stderr, "You must run this program as root or use sudo!\n");
57 } // if
58 realFilename = "";
59 userFilename = "";
60 isOpen = 0;
61 openForWrite = 0;
62 } else {
63 isOpen = 1;
64 openForWrite = 0;
65 } // if/else
66 } // if
67
68 return isOpen;
69} // DiskIO::OpenForRead(void)
70
71// An extended file-open function. This includes some system-specific checks.
72// Returns 1 if the file is open, 0 otherwise....
73int DiskIO::OpenForWrite(void) {
74 if ((isOpen) && (openForWrite))
75 return 1;
76
77 // Close the disk, in case it's already open for reading only....
78 Close();
79
80 // try to open the device; may fail....
81 fd = open(realFilename.c_str(), O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
82#ifdef __APPLE__
83 // MacOS X requires a shared lock under some circumstances....
84 if (fd < 0) {
85 fd = open(realFilename.c_str(), O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH | O_SHLOCK);
86 } // if
87#endif
88 if (fd >= 0) {
89 isOpen = 1;
90 openForWrite = 1;
91 } else {
92 isOpen = 0;
93 openForWrite = 0;
94 } // if/else
95 return isOpen;
96} // DiskIO::OpenForWrite(void)
97
98// Close the disk device. Note that this does NOT erase the stored filenames,
99// so the file can be re-opened without specifying the filename.
100void DiskIO::Close(void) {
101 if (isOpen)
102 close(fd);
103 isOpen = 0;
104 openForWrite = 0;
105} // DiskIO::Close()
106
107// Returns block size of device pointed to by fd file descriptor. If the ioctl
108// returns an error condition, print a warning but return a value of SECTOR_SIZE
109// (512)..
110int DiskIO::GetBlockSize(void) {
111 int err = -1, blockSize = 0;
112
113 // If disk isn't open, try to open it....
114 if (!isOpen) {
115 OpenForRead();
116 } // if
117
118 if (isOpen) {
119#ifdef __APPLE__
120 err = ioctl(fd, DKIOCGETBLOCKSIZE, &blockSize);
121#endif
122#ifdef __FreeBSD__
123 err = ioctl(fd, DIOCGSECTORSIZE, &blockSize);
124#endif
125#ifdef __linux__
126 err = ioctl(fd, BLKSSZGET, &blockSize);
127#endif
128
129 if (err == -1) {
130 blockSize = SECTOR_SIZE;
131 // ENOTTY = inappropriate ioctl; probably being called on a disk image
132 // file, so don't display the warning message....
133 // 32-bit code returns EINVAL, I don't know why. I know I'm treading on
134 // thin ice here, but it should be OK in all but very weird cases....
135 if ((errno != ENOTTY) && (errno != EINVAL)) {
136 printf("\aError %d when determining sector size! Setting sector size to %d\n",
137 errno, SECTOR_SIZE);
138 } // if
139 } // if (err == -1)
140 } // if (isOpen)
141
142 return (blockSize);
143} // DiskIO::GetBlockSize()
144
145// Resync disk caches so the OS uses the new partition table. This code varies
146// a lot from one OS to another.
147void DiskIO::DiskSync(void) {
148 int i, platformFound = 0;
149
150 // If disk isn't open, try to open it....
151 if (!isOpen) {
152 OpenForRead();
153 } // if
154
155 if (isOpen) {
156 sync();
157#ifdef __APPLE__
158 printf("Warning: The kernel may continue to use old or deleted partitions.\n"
159 "You should reboot or remove the drive.\n");
160 /* don't know if this helps
161 * it definitely will get things on disk though:
162 * http://topiks.org/mac-os-x/0321278542/ch12lev1sec8.html */
163 i = ioctl(fd, DKIOCSYNCHRONIZECACHE);
164 platformFound++;
165#endif
166#ifdef __FreeBSD__
167 sleep(2);
168 i = ioctl(fd, DIOCGFLUSH);
169 printf("Warning: The kernel may continue to use old or deleted partitions.\n"
170 "You should reboot or remove the drive.\n");
171 platformFound++;
172#endif
173#ifdef __linux__
174 sleep(2);
175 i = ioctl(fd, BLKRRPART);
176 if (i)
177 printf("Warning: The kernel is still using the old partition table.\n"
178 "The new table will be used at the next reboot.\n");
179 platformFound++;
180#endif
181 if (platformFound == 0)
182 fprintf(stderr, "Warning: Platform not recognized!\n");
183 if (platformFound > 1)
184 fprintf(stderr, "\nWarning: We seem to be running on multiple platforms!\n");
185 } // if (isOpen)
186} // DiskIO::DiskSync()
187
188// Seek to the specified sector. Returns 1 on success, 0 on failure.
189int 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) {
213 int blockSize = 512, i, numBlocks, retval = 0;
214 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;
226 tempSpace = (char*) malloc(blockSize);
227 } else {
228 numBlocks = numBytes / blockSize;
229 if ((numBytes % blockSize) != 0) numBlocks++;
230 tempSpace = (char*) malloc(numBlocks * blockSize);
231 } // 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);
236/* for (i = 0; i < numBytes; i++) {
237 ((char*) buffer)[i] = tempSpace[i];
238 } // for */
239
240 // Adjust the return value, if necessary....
241 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
242 retval = numBytes;
243
244 free(tempSpace);
245 } // if (isOpen)
246 return retval;
247} // DiskIO::Read()
248
249// A variant on the standard write() function. Done to work around
250// limitations in FreeBSD concerning the matching of the sector
251// size with the number of bytes read.
252// Returns the number of bytes written.
253int DiskIO::Write(void* buffer, int numBytes) {
254 int blockSize = 512, i, numBlocks, retval = 0;
255 char* tempSpace;
256
257 // If disk isn't open, try to open it....
258 if ((!isOpen) || (!openForWrite)) {
259 OpenForWrite();
260 } // if
261
262 if (isOpen) {
263 // Compute required space and allocate memory
264 blockSize = GetBlockSize();
265 if (numBytes <= blockSize) {
266 numBlocks = 1;
267 tempSpace = (char*) malloc(blockSize);
268 } else {
269 numBlocks = numBytes / blockSize;
270 if ((numBytes % blockSize) != 0) numBlocks++;
271 tempSpace = (char*) malloc(numBlocks * blockSize);
272 } // if/else
273
274 // Copy the data to my own buffer, then write it
275/* for (i = 0; i < numBytes; i++) {
276 tempSpace[i] = ((char*) buffer)[i];
277 } // for */
278 memcpy(tempSpace, buffer, numBytes);
279 for (i = numBytes; i < numBlocks * blockSize; i++) {
280 tempSpace[i] = 0;
281 } // for
282 retval = write(fd, tempSpace, numBlocks * blockSize);
283
284 // Adjust the return value, if necessary....
285 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
286 retval = numBytes;
287
288 free(tempSpace);
289 } // if (isOpen)
290 return retval;
291} // DiskIO:Write()
292
293/**************************************************************************************
294 * *
295 * Below functions are lifted from various sources, as documented in comments before *
296 * each one. *
297 * *
298 **************************************************************************************/
299
300// The disksize function is taken from the Linux fdisk code and modified
301// greatly since then to enable FreeBSD and MacOS support, as well as to
302// return correct values for disk image files.
303uint64_t DiskIO::DiskSize(int *err) {
304 long sz; // Do not delete; needed for Linux
305 long long b; // Do not delete; needed for Linux
306 uint64_t sectors = 0; // size in sectors
307 off_t bytes = 0; // size in bytes
308 struct stat64 st;
309 int platformFound = 0;
310
311 // If disk isn't open, try to open it....
312 if (!isOpen) {
313 OpenForRead();
314 } // if
315
316 if (isOpen) {
317 // Note to self: I recall testing a simplified version of
318 // this code, similar to what's in the __APPLE__ block,
319 // on Linux, but I had some problems. IIRC, it ran OK on 32-bit
320 // systems but not on 64-bit. Keep this in mind in case of
321 // 32/64-bit issues on MacOS....
322#ifdef __APPLE__
323 *err = ioctl(fd, DKIOCGETBLOCKCOUNT, &sectors);
324 platformFound++;
325#endif
326#ifdef __FreeBSD__
327 *err = ioctl(fd, DIOCGMEDIASIZE, &bytes);
328 b = GetBlockSize();
329 sectors = bytes / b;
330 platformFound++;
331#endif
332#ifdef __linux__
333 *err = ioctl(fd, BLKGETSIZE, &sz);
334 if (*err) {
335 sectors = sz = 0;
336 } // if
337 if ((errno == EFBIG) || (!*err)) {
338 *err = ioctl(fd, BLKGETSIZE64, &b);
339 if (*err || b == 0 || b == sz)
340 sectors = sz;
341 else
342 sectors = (b >> 9);
343 } // if
344 // Unintuitively, the above returns values in 512-byte blocks, no
345 // matter what the underlying device's block size. Correct for this....
346 sectors /= (GetBlockSize() / 512);
347 platformFound++;
348#endif
349 if (platformFound != 1)
350 fprintf(stderr, "Warning! We seem to be running on no known platform!\n");
351
352 // The above methods have failed, so let's assume it's a regular
353 // file (a QEMU image, dd backup, or what have you) and see what
354 // fstat() gives us....
355 if ((sectors == 0) || (*err == -1)) {
356 if (fstat64(fd, &st) == 0) {
357 bytes = (off_t) st.st_size;
358 if ((bytes % UINT64_C(512)) != 0)
359 fprintf(stderr, "Warning: File size is not a multiple of 512 bytes!"
360 " Misbehavior is likely!\n\a");
361 sectors = bytes / UINT64_C(512);
362 } // if
363 } // if
364 } // if (isOpen)
365 return sectors;
366} // DiskIO::DiskSize()