blob: b20f1614234d9279e8dcd2688a6532640bd1bcc1 [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;
srs56948f1b2d62010-05-23 13:07:19 -040042 struct stat64 st;
srs5694add79a62010-01-26 15:59:58 -050043
44 if (isOpen) { // file is already open
45 if (openForWrite) {
46 Close();
47 } else {
48 shouldOpen = 0;
49 } // if/else
50 } // if
51
52 if (shouldOpen) {
53 fd = open(realFilename.c_str(), O_RDONLY);
54 if (fd == -1) {
srs569455d92612010-03-07 22:16:07 -050055 cerr << "Problem opening " << realFilename << " for reading! Error is " << errno << ".\n";
56 if (errno == EACCES) // User is probably not running as root
srs5694fed16d02010-01-27 23:03:40 -050057 cerr << "You must run this program as root or use sudo!\n";
srs569455d92612010-03-07 22:16:07 -050058 if (errno == ENOENT)
59 cerr << "The specified file does not exist!\n";
srs5694add79a62010-01-26 15:59:58 -050060 realFilename = "";
61 userFilename = "";
62 isOpen = 0;
63 openForWrite = 0;
64 } else {
srs56948f1b2d62010-05-23 13:07:19 -040065 isOpen = 0;
srs5694add79a62010-01-26 15:59:58 -050066 openForWrite = 0;
srs56948f1b2d62010-05-23 13:07:19 -040067 if (fstat64(fd, &st) == 0) {
68 if (S_ISDIR(st.st_mode))
69 cerr << "The specified path is a directory!\n";
70#ifndef __FreeBSD__
71 else if (S_ISCHR(st.st_mode))
72 cerr << "The specified path is a character device!\n";
73#endif
74 else if (S_ISFIFO(st.st_mode))
75 cerr << "The specified path is a FIFO!\n";
76 else if (S_ISSOCK(st.st_mode))
77 cerr << "The specified path is a socket!\n";
78 else
79 isOpen = 1;
80 } // if (fstat64()...)
srs5694add79a62010-01-26 15:59:58 -050081 } // if/else
82 } // if
83
84 return isOpen;
85} // DiskIO::OpenForRead(void)
86
87// An extended file-open function. This includes some system-specific checks.
88// Returns 1 if the file is open, 0 otherwise....
89int DiskIO::OpenForWrite(void) {
90 if ((isOpen) && (openForWrite))
91 return 1;
92
93 // Close the disk, in case it's already open for reading only....
94 Close();
95
96 // try to open the device; may fail....
97 fd = open(realFilename.c_str(), O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
98#ifdef __APPLE__
99 // MacOS X requires a shared lock under some circumstances....
100 if (fd < 0) {
srs5694fed16d02010-01-27 23:03:40 -0500101 fd = open(realFilename.c_str(), O_WRONLY | O_SHLOCK);
srs5694add79a62010-01-26 15:59:58 -0500102 } // if
103#endif
104 if (fd >= 0) {
105 isOpen = 1;
106 openForWrite = 1;
107 } else {
108 isOpen = 0;
109 openForWrite = 0;
110 } // if/else
111 return isOpen;
112} // DiskIO::OpenForWrite(void)
113
114// Close the disk device. Note that this does NOT erase the stored filenames,
115// so the file can be re-opened without specifying the filename.
116void DiskIO::Close(void) {
117 if (isOpen)
srs569408bb0da2010-02-19 17:19:55 -0500118 if (close(fd) < 0)
119 cerr << "Warning! Problem closing file!\n";
srs5694add79a62010-01-26 15:59:58 -0500120 isOpen = 0;
121 openForWrite = 0;
122} // DiskIO::Close()
123
124// Returns block size of device pointed to by fd file descriptor. If the ioctl
125// returns an error condition, print a warning but return a value of SECTOR_SIZE
srs569455d92612010-03-07 22:16:07 -0500126// (512). If the disk can't be opened at all, return a value of 0.
srs5694add79a62010-01-26 15:59:58 -0500127int DiskIO::GetBlockSize(void) {
128 int err = -1, blockSize = 0;
129
130 // If disk isn't open, try to open it....
131 if (!isOpen) {
132 OpenForRead();
133 } // if
134
135 if (isOpen) {
136#ifdef __APPLE__
137 err = ioctl(fd, DKIOCGETBLOCKSIZE, &blockSize);
138#endif
srs569408bb0da2010-02-19 17:19:55 -0500139#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500140 err = ioctl(fd, DIOCGSECTORSIZE, &blockSize);
141#endif
142#ifdef __linux__
143 err = ioctl(fd, BLKSSZGET, &blockSize);
144#endif
145
146 if (err == -1) {
147 blockSize = SECTOR_SIZE;
148 // ENOTTY = inappropriate ioctl; probably being called on a disk image
149 // file, so don't display the warning message....
150 // 32-bit code returns EINVAL, I don't know why. I know I'm treading on
151 // thin ice here, but it should be OK in all but very weird cases....
152 if ((errno != ENOTTY) && (errno != EINVAL)) {
srs5694fed16d02010-01-27 23:03:40 -0500153 cerr << "\aError " << errno << " when determining sector size! Setting sector size to "
154 << SECTOR_SIZE << "\n";
srs569455d92612010-03-07 22:16:07 -0500155 cout << "Disk device is " << realFilename << "\n";
srs5694add79a62010-01-26 15:59:58 -0500156 } // if
157 } // if (err == -1)
158 } // if (isOpen)
159
160 return (blockSize);
161} // DiskIO::GetBlockSize()
162
163// Resync disk caches so the OS uses the new partition table. This code varies
164// a lot from one OS to another.
165void DiskIO::DiskSync(void) {
166 int i, platformFound = 0;
167
168 // If disk isn't open, try to open it....
169 if (!isOpen) {
170 OpenForRead();
171 } // if
172
173 if (isOpen) {
174 sync();
175#ifdef __APPLE__
srs5694fed16d02010-01-27 23:03:40 -0500176 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
177 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500178 /* don't know if this helps
179 * it definitely will get things on disk though:
180 * http://topiks.org/mac-os-x/0321278542/ch12lev1sec8.html */
181 i = ioctl(fd, DKIOCSYNCHRONIZECACHE);
182 platformFound++;
183#endif
srs569408bb0da2010-02-19 17:19:55 -0500184#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500185 sleep(2);
186 i = ioctl(fd, DIOCGFLUSH);
srs5694fed16d02010-01-27 23:03:40 -0500187 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
188 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500189 platformFound++;
190#endif
191#ifdef __linux__
192 sleep(2);
193 i = ioctl(fd, BLKRRPART);
194 if (i)
srs5694fed16d02010-01-27 23:03:40 -0500195 cout << "Warning: The kernel is still using the old partition table.\n"
196 << "The new table will be used at the next reboot.\n";
srs5694add79a62010-01-26 15:59:58 -0500197 platformFound++;
198#endif
199 if (platformFound == 0)
srs5694fed16d02010-01-27 23:03:40 -0500200 cerr << "Warning: Platform not recognized!\n";
srs5694add79a62010-01-26 15:59:58 -0500201 if (platformFound > 1)
srs5694fed16d02010-01-27 23:03:40 -0500202 cerr << "\nWarning: We seem to be running on multiple platforms!\n";
srs5694add79a62010-01-26 15:59:58 -0500203 } // if (isOpen)
204} // DiskIO::DiskSync()
205
206// Seek to the specified sector. Returns 1 on success, 0 on failure.
srs5694cb76c672010-02-11 22:22:22 -0500207// Note that seeking beyond the end of the file is NOT detected as a failure!
srs5694add79a62010-01-26 15:59:58 -0500208int DiskIO::Seek(uint64_t sector) {
209 int retval = 1;
210 off_t seekTo, sought;
211
212 // If disk isn't open, try to open it....
213 if (!isOpen) {
214 retval = OpenForRead();
215 } // if
216
217 if (isOpen) {
218 seekTo = sector * (uint64_t) GetBlockSize();
219 sought = lseek64(fd, seekTo, SEEK_SET);
220 if (sought != seekTo) {
221 retval = 0;
222 } // if
223 } // if
224 return retval;
225} // DiskIO::Seek()
226
227// A variant on the standard read() function. Done to work around
228// limitations in FreeBSD concerning the matching of the sector
229// size with the number of bytes read.
230// Returns the number of bytes read into buffer.
231int DiskIO::Read(void* buffer, int numBytes) {
srs5694cb76c672010-02-11 22:22:22 -0500232 int blockSize, numBlocks, retval = 0;
srs5694add79a62010-01-26 15:59:58 -0500233 char* tempSpace;
234
235 // If disk isn't open, try to open it....
236 if (!isOpen) {
237 OpenForRead();
238 } // if
239
240 if (isOpen) {
241 // Compute required space and allocate memory
242 blockSize = GetBlockSize();
243 if (numBytes <= blockSize) {
244 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500245 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500246 } else {
247 numBlocks = numBytes / blockSize;
srs5694cb76c672010-02-11 22:22:22 -0500248 if ((numBytes % blockSize) != 0)
249 numBlocks++;
250 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500251 } // if/else
252
253 // Read the data into temporary space, then copy it to buffer
254 retval = read(fd, tempSpace, numBlocks * blockSize);
255 memcpy(buffer, tempSpace, numBytes);
srs5694add79a62010-01-26 15:59:58 -0500256
257 // Adjust the return value, if necessary....
258 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
259 retval = numBytes;
260
srs5694cb76c672010-02-11 22:22:22 -0500261 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500262 } // if (isOpen)
263 return retval;
264} // DiskIO::Read()
265
266// A variant on the standard write() function. Done to work around
267// limitations in FreeBSD concerning the matching of the sector
268// size with the number of bytes read.
269// Returns the number of bytes written.
270int DiskIO::Write(void* buffer, int numBytes) {
271 int blockSize = 512, i, numBlocks, retval = 0;
272 char* tempSpace;
273
274 // If disk isn't open, try to open it....
275 if ((!isOpen) || (!openForWrite)) {
276 OpenForWrite();
277 } // if
278
279 if (isOpen) {
280 // Compute required space and allocate memory
281 blockSize = GetBlockSize();
282 if (numBytes <= blockSize) {
283 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500284 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500285 } else {
286 numBlocks = numBytes / blockSize;
287 if ((numBytes % blockSize) != 0) numBlocks++;
srs5694cb76c672010-02-11 22:22:22 -0500288 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500289 } // if/else
290
291 // Copy the data to my own buffer, then write it
srs5694add79a62010-01-26 15:59:58 -0500292 memcpy(tempSpace, buffer, numBytes);
293 for (i = numBytes; i < numBlocks * blockSize; i++) {
294 tempSpace[i] = 0;
295 } // for
296 retval = write(fd, tempSpace, numBlocks * blockSize);
297
298 // Adjust the return value, if necessary....
299 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
300 retval = numBytes;
301
srs5694cb76c672010-02-11 22:22:22 -0500302 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500303 } // if (isOpen)
304 return retval;
305} // DiskIO:Write()
306
307/**************************************************************************************
308 * *
309 * Below functions are lifted from various sources, as documented in comments before *
310 * each one. *
311 * *
312 **************************************************************************************/
313
314// The disksize function is taken from the Linux fdisk code and modified
315// greatly since then to enable FreeBSD and MacOS support, as well as to
316// return correct values for disk image files.
317uint64_t DiskIO::DiskSize(int *err) {
srs5694add79a62010-01-26 15:59:58 -0500318 uint64_t sectors = 0; // size in sectors
319 off_t bytes = 0; // size in bytes
320 struct stat64 st;
321 int platformFound = 0;
322
323 // If disk isn't open, try to open it....
324 if (!isOpen) {
325 OpenForRead();
326 } // if
327
328 if (isOpen) {
329 // Note to self: I recall testing a simplified version of
330 // this code, similar to what's in the __APPLE__ block,
331 // on Linux, but I had some problems. IIRC, it ran OK on 32-bit
332 // systems but not on 64-bit. Keep this in mind in case of
333 // 32/64-bit issues on MacOS....
334#ifdef __APPLE__
335 *err = ioctl(fd, DKIOCGETBLOCKCOUNT, &sectors);
336 platformFound++;
337#endif
srs569408bb0da2010-02-19 17:19:55 -0500338#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500339 *err = ioctl(fd, DIOCGMEDIASIZE, &bytes);
srs569408bb0da2010-02-19 17:19:55 -0500340 long long b = GetBlockSize();
srs5694add79a62010-01-26 15:59:58 -0500341 sectors = bytes / b;
342 platformFound++;
343#endif
344#ifdef __linux__
srs569408bb0da2010-02-19 17:19:55 -0500345 long sz;
346 long long b;
srs5694add79a62010-01-26 15:59:58 -0500347 *err = ioctl(fd, BLKGETSIZE, &sz);
348 if (*err) {
349 sectors = sz = 0;
350 } // if
351 if ((errno == EFBIG) || (!*err)) {
352 *err = ioctl(fd, BLKGETSIZE64, &b);
353 if (*err || b == 0 || b == sz)
354 sectors = sz;
355 else
356 sectors = (b >> 9);
357 } // if
358 // Unintuitively, the above returns values in 512-byte blocks, no
359 // matter what the underlying device's block size. Correct for this....
360 sectors /= (GetBlockSize() / 512);
361 platformFound++;
362#endif
363 if (platformFound != 1)
srs5694fed16d02010-01-27 23:03:40 -0500364 cerr << "Warning! We seem to be running on no known platform!\n";
srs5694add79a62010-01-26 15:59:58 -0500365
366 // The above methods have failed, so let's assume it's a regular
367 // file (a QEMU image, dd backup, or what have you) and see what
368 // fstat() gives us....
369 if ((sectors == 0) || (*err == -1)) {
370 if (fstat64(fd, &st) == 0) {
371 bytes = (off_t) st.st_size;
372 if ((bytes % UINT64_C(512)) != 0)
srs5694fed16d02010-01-27 23:03:40 -0500373 cerr << "Warning: File size is not a multiple of 512 bytes!"
374 << " Misbehavior is likely!\n\a";
srs5694add79a62010-01-26 15:59:58 -0500375 sectors = bytes / UINT64_C(512);
376 } // if
377 } // if
378 } // if (isOpen)
379 return sectors;
380} // DiskIO::DiskSize()