blob: 3735b9fe181758cc46293de2a837461d8c29af89 [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>
srs5694bf8950c2011-03-12 01:23:12 -050025
26#ifdef __linux__
27#include "linux/hdreg.h"
28#endif
29
srs5694add79a62010-01-26 15:59:58 -050030#include <iostream>
31
srs5694add79a62010-01-26 15:59:58 -050032#include "diskio.h"
33
34using namespace std;
35
36// Returns the official "real" name for a shortened version of same.
37// Trivial here; more important in Windows
38void DiskIO::MakeRealName(void) {
39 realFilename = userFilename;
40} // DiskIO::MakeRealName()
41
srs569455d92612010-03-07 22:16:07 -050042// Open the currently on-record file for reading. Returns 1 if the file is
43// already open or is opened by this call, 0 if opening the file doesn't
44// work.
srs5694add79a62010-01-26 15:59:58 -050045int DiskIO::OpenForRead(void) {
46 int shouldOpen = 1;
srs56948f1b2d62010-05-23 13:07:19 -040047 struct stat64 st;
srs5694add79a62010-01-26 15:59:58 -050048
49 if (isOpen) { // file is already open
50 if (openForWrite) {
51 Close();
52 } else {
53 shouldOpen = 0;
54 } // if/else
55 } // if
56
57 if (shouldOpen) {
58 fd = open(realFilename.c_str(), O_RDONLY);
59 if (fd == -1) {
srs569455d92612010-03-07 22:16:07 -050060 cerr << "Problem opening " << realFilename << " for reading! Error is " << errno << ".\n";
61 if (errno == EACCES) // User is probably not running as root
srs5694fed16d02010-01-27 23:03:40 -050062 cerr << "You must run this program as root or use sudo!\n";
srs569455d92612010-03-07 22:16:07 -050063 if (errno == ENOENT)
64 cerr << "The specified file does not exist!\n";
srs5694add79a62010-01-26 15:59:58 -050065 realFilename = "";
66 userFilename = "";
67 isOpen = 0;
68 openForWrite = 0;
69 } else {
srs56948f1b2d62010-05-23 13:07:19 -040070 isOpen = 0;
srs5694add79a62010-01-26 15:59:58 -050071 openForWrite = 0;
srs56948f1b2d62010-05-23 13:07:19 -040072 if (fstat64(fd, &st) == 0) {
73 if (S_ISDIR(st.st_mode))
74 cerr << "The specified path is a directory!\n";
75#ifndef __FreeBSD__
76 else if (S_ISCHR(st.st_mode))
77 cerr << "The specified path is a character device!\n";
78#endif
79 else if (S_ISFIFO(st.st_mode))
80 cerr << "The specified path is a FIFO!\n";
81 else if (S_ISSOCK(st.st_mode))
82 cerr << "The specified path is a socket!\n";
83 else
84 isOpen = 1;
85 } // if (fstat64()...)
srs5694add79a62010-01-26 15:59:58 -050086 } // if/else
87 } // if
88
89 return isOpen;
90} // DiskIO::OpenForRead(void)
91
92// An extended file-open function. This includes some system-specific checks.
93// Returns 1 if the file is open, 0 otherwise....
94int DiskIO::OpenForWrite(void) {
95 if ((isOpen) && (openForWrite))
96 return 1;
97
98 // Close the disk, in case it's already open for reading only....
99 Close();
100
101 // try to open the device; may fail....
102 fd = open(realFilename.c_str(), O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
103#ifdef __APPLE__
104 // MacOS X requires a shared lock under some circumstances....
105 if (fd < 0) {
srs5694fed16d02010-01-27 23:03:40 -0500106 fd = open(realFilename.c_str(), O_WRONLY | O_SHLOCK);
srs5694add79a62010-01-26 15:59:58 -0500107 } // if
108#endif
109 if (fd >= 0) {
110 isOpen = 1;
111 openForWrite = 1;
112 } else {
113 isOpen = 0;
114 openForWrite = 0;
115 } // if/else
116 return isOpen;
117} // DiskIO::OpenForWrite(void)
118
119// Close the disk device. Note that this does NOT erase the stored filenames,
120// so the file can be re-opened without specifying the filename.
121void DiskIO::Close(void) {
122 if (isOpen)
srs569408bb0da2010-02-19 17:19:55 -0500123 if (close(fd) < 0)
124 cerr << "Warning! Problem closing file!\n";
srs5694add79a62010-01-26 15:59:58 -0500125 isOpen = 0;
126 openForWrite = 0;
127} // DiskIO::Close()
128
129// Returns block size of device pointed to by fd file descriptor. If the ioctl
130// returns an error condition, print a warning but return a value of SECTOR_SIZE
srs569455d92612010-03-07 22:16:07 -0500131// (512). If the disk can't be opened at all, return a value of 0.
srs5694add79a62010-01-26 15:59:58 -0500132int DiskIO::GetBlockSize(void) {
133 int err = -1, blockSize = 0;
134
135 // If disk isn't open, try to open it....
136 if (!isOpen) {
137 OpenForRead();
138 } // if
139
140 if (isOpen) {
141#ifdef __APPLE__
142 err = ioctl(fd, DKIOCGETBLOCKSIZE, &blockSize);
143#endif
srs569408bb0da2010-02-19 17:19:55 -0500144#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500145 err = ioctl(fd, DIOCGSECTORSIZE, &blockSize);
146#endif
147#ifdef __linux__
148 err = ioctl(fd, BLKSSZGET, &blockSize);
149#endif
150
151 if (err == -1) {
152 blockSize = SECTOR_SIZE;
153 // ENOTTY = inappropriate ioctl; probably being called on a disk image
154 // file, so don't display the warning message....
155 // 32-bit code returns EINVAL, I don't know why. I know I'm treading on
156 // thin ice here, but it should be OK in all but very weird cases....
157 if ((errno != ENOTTY) && (errno != EINVAL)) {
srs5694fed16d02010-01-27 23:03:40 -0500158 cerr << "\aError " << errno << " when determining sector size! Setting sector size to "
159 << SECTOR_SIZE << "\n";
srs569455d92612010-03-07 22:16:07 -0500160 cout << "Disk device is " << realFilename << "\n";
srs5694add79a62010-01-26 15:59:58 -0500161 } // if
162 } // if (err == -1)
163 } // if (isOpen)
164
165 return (blockSize);
166} // DiskIO::GetBlockSize()
167
srs5694bf8950c2011-03-12 01:23:12 -0500168// Returns the number of heads, according to the kernel, or 255 if the
169// correct value can't be determined.
170uint32_t DiskIO::GetNumHeads(void) {
171 uint32_t numHeads = 255;
172
173#ifdef HDIO_GETGEO
174 struct hd_geometry geometry;
175
176 // If disk isn't open, try to open it....
177 if (!isOpen)
178 OpenForRead();
179
180 if (!ioctl(fd, HDIO_GETGEO, &geometry))
181 numHeads = (uint32_t) geometry.heads;
182#endif
183 return numHeads;
184} // DiskIO::GetNumHeads();
185
186// Returns the number of sectors per track, according to the kernel, or 63
187// if the correct value can't be determined.
188uint32_t DiskIO::GetNumSecsPerTrack(void) {
189 uint32_t numSecs = 63;
190
191 #ifdef HDIO_GETGEO
192 struct hd_geometry geometry;
193
194 // If disk isn't open, try to open it....
195 if (!isOpen)
196 OpenForRead();
197
198 if (!ioctl(fd, HDIO_GETGEO, &geometry))
199 numSecs = (uint32_t) geometry.sectors;
200 #endif
201 return numSecs;
202} // DiskIO::GetNumSecsPerTrack()
203
srs5694add79a62010-01-26 15:59:58 -0500204// Resync disk caches so the OS uses the new partition table. This code varies
205// a lot from one OS to another.
srs5694a17fe692011-09-10 20:30:20 -0400206// Returns 1 on success, 0 if the kernel continues to use the old partition table.
207// (Note that for most OSes, the default of 0 is returned because I've not yet
208// looked into how to test for success in the underlying system calls...)
209int DiskIO::DiskSync(void) {
210 int i, retval = 0, platformFound = 0;
srs5694add79a62010-01-26 15:59:58 -0500211
212 // If disk isn't open, try to open it....
213 if (!isOpen) {
214 OpenForRead();
215 } // if
216
217 if (isOpen) {
218 sync();
219#ifdef __APPLE__
srs5694fed16d02010-01-27 23:03:40 -0500220 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
221 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500222 /* don't know if this helps
223 * it definitely will get things on disk though:
224 * http://topiks.org/mac-os-x/0321278542/ch12lev1sec8.html */
225 i = ioctl(fd, DKIOCSYNCHRONIZECACHE);
226 platformFound++;
227#endif
srs569408bb0da2010-02-19 17:19:55 -0500228#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500229 sleep(2);
230 i = ioctl(fd, DIOCGFLUSH);
srs5694fed16d02010-01-27 23:03:40 -0500231 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
232 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500233 platformFound++;
234#endif
235#ifdef __linux__
srs569400b6d7a2011-06-26 22:40:06 -0400236 sleep(1); // Theoretically unnecessary, but ioctl() fails sometimes if omitted....
237 fsync(fd);
srs5694add79a62010-01-26 15:59:58 -0500238 i = ioctl(fd, BLKRRPART);
srs5694a17fe692011-09-10 20:30:20 -0400239 if (i) {
srs5694fed16d02010-01-27 23:03:40 -0500240 cout << "Warning: The kernel is still using the old partition table.\n"
241 << "The new table will be used at the next reboot.\n";
srs5694a17fe692011-09-10 20:30:20 -0400242 } else {
243 retval = 1;
244 } // if/else
srs5694add79a62010-01-26 15:59:58 -0500245 platformFound++;
246#endif
247 if (platformFound == 0)
srs5694fed16d02010-01-27 23:03:40 -0500248 cerr << "Warning: Platform not recognized!\n";
srs5694add79a62010-01-26 15:59:58 -0500249 if (platformFound > 1)
srs5694fed16d02010-01-27 23:03:40 -0500250 cerr << "\nWarning: We seem to be running on multiple platforms!\n";
srs5694add79a62010-01-26 15:59:58 -0500251 } // if (isOpen)
srs5694a17fe692011-09-10 20:30:20 -0400252 return retval;
srs5694add79a62010-01-26 15:59:58 -0500253} // DiskIO::DiskSync()
254
255// Seek to the specified sector. Returns 1 on success, 0 on failure.
srs5694cb76c672010-02-11 22:22:22 -0500256// Note that seeking beyond the end of the file is NOT detected as a failure!
srs5694add79a62010-01-26 15:59:58 -0500257int DiskIO::Seek(uint64_t sector) {
258 int retval = 1;
259 off_t seekTo, sought;
260
261 // If disk isn't open, try to open it....
262 if (!isOpen) {
263 retval = OpenForRead();
264 } // if
265
266 if (isOpen) {
267 seekTo = sector * (uint64_t) GetBlockSize();
268 sought = lseek64(fd, seekTo, SEEK_SET);
269 if (sought != seekTo) {
270 retval = 0;
271 } // if
272 } // if
273 return retval;
274} // DiskIO::Seek()
275
276// A variant on the standard read() function. Done to work around
277// limitations in FreeBSD concerning the matching of the sector
278// size with the number of bytes read.
279// Returns the number of bytes read into buffer.
280int DiskIO::Read(void* buffer, int numBytes) {
srs5694cb76c672010-02-11 22:22:22 -0500281 int blockSize, numBlocks, retval = 0;
srs5694add79a62010-01-26 15:59:58 -0500282 char* tempSpace;
283
284 // If disk isn't open, try to open it....
285 if (!isOpen) {
286 OpenForRead();
287 } // if
288
289 if (isOpen) {
290 // Compute required space and allocate memory
291 blockSize = GetBlockSize();
292 if (numBytes <= blockSize) {
293 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500294 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500295 } else {
296 numBlocks = numBytes / blockSize;
srs5694cb76c672010-02-11 22:22:22 -0500297 if ((numBytes % blockSize) != 0)
298 numBlocks++;
299 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500300 } // if/else
srs56946aae2a92011-06-10 01:16:51 -0400301 if (tempSpace == NULL) {
302 cerr << "Unable to allocate memory in DiskIO::Read()! Terminating!\n";
303 exit(1);
304 } // if
srs5694add79a62010-01-26 15:59:58 -0500305
306 // Read the data into temporary space, then copy it to buffer
307 retval = read(fd, tempSpace, numBlocks * blockSize);
308 memcpy(buffer, tempSpace, numBytes);
srs5694add79a62010-01-26 15:59:58 -0500309
310 // Adjust the return value, if necessary....
311 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
312 retval = numBytes;
313
srs5694cb76c672010-02-11 22:22:22 -0500314 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500315 } // if (isOpen)
316 return retval;
317} // DiskIO::Read()
318
319// A variant on the standard write() function. Done to work around
320// limitations in FreeBSD concerning the matching of the sector
321// size with the number of bytes read.
322// Returns the number of bytes written.
323int DiskIO::Write(void* buffer, int numBytes) {
324 int blockSize = 512, i, numBlocks, retval = 0;
325 char* tempSpace;
326
327 // If disk isn't open, try to open it....
328 if ((!isOpen) || (!openForWrite)) {
329 OpenForWrite();
330 } // if
331
332 if (isOpen) {
333 // Compute required space and allocate memory
334 blockSize = GetBlockSize();
335 if (numBytes <= blockSize) {
336 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500337 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500338 } else {
339 numBlocks = numBytes / blockSize;
340 if ((numBytes % blockSize) != 0) numBlocks++;
srs5694cb76c672010-02-11 22:22:22 -0500341 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500342 } // if/else
srs56946aae2a92011-06-10 01:16:51 -0400343 if (tempSpace == NULL) {
344 cerr << "Unable to allocate memory in DiskIO::Write()! Terminating!\n";
345 exit(1);
346 } // if
347
srs5694add79a62010-01-26 15:59:58 -0500348 // Copy the data to my own buffer, then write it
srs5694add79a62010-01-26 15:59:58 -0500349 memcpy(tempSpace, buffer, numBytes);
350 for (i = numBytes; i < numBlocks * blockSize; i++) {
351 tempSpace[i] = 0;
352 } // for
353 retval = write(fd, tempSpace, numBlocks * blockSize);
354
355 // Adjust the return value, if necessary....
356 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
357 retval = numBytes;
358
srs5694cb76c672010-02-11 22:22:22 -0500359 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500360 } // if (isOpen)
361 return retval;
362} // DiskIO:Write()
363
364/**************************************************************************************
365 * *
366 * Below functions are lifted from various sources, as documented in comments before *
367 * each one. *
368 * *
369 **************************************************************************************/
370
371// The disksize function is taken from the Linux fdisk code and modified
372// greatly since then to enable FreeBSD and MacOS support, as well as to
373// return correct values for disk image files.
374uint64_t DiskIO::DiskSize(int *err) {
srs5694add79a62010-01-26 15:59:58 -0500375 uint64_t sectors = 0; // size in sectors
376 off_t bytes = 0; // size in bytes
377 struct stat64 st;
378 int platformFound = 0;
379
380 // If disk isn't open, try to open it....
381 if (!isOpen) {
382 OpenForRead();
383 } // if
384
385 if (isOpen) {
386 // Note to self: I recall testing a simplified version of
387 // this code, similar to what's in the __APPLE__ block,
388 // on Linux, but I had some problems. IIRC, it ran OK on 32-bit
389 // systems but not on 64-bit. Keep this in mind in case of
390 // 32/64-bit issues on MacOS....
391#ifdef __APPLE__
392 *err = ioctl(fd, DKIOCGETBLOCKCOUNT, &sectors);
393 platformFound++;
394#endif
srs569408bb0da2010-02-19 17:19:55 -0500395#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500396 *err = ioctl(fd, DIOCGMEDIASIZE, &bytes);
srs569408bb0da2010-02-19 17:19:55 -0500397 long long b = GetBlockSize();
srs5694add79a62010-01-26 15:59:58 -0500398 sectors = bytes / b;
399 platformFound++;
400#endif
401#ifdef __linux__
srs569408bb0da2010-02-19 17:19:55 -0500402 long sz;
403 long long b;
srs5694add79a62010-01-26 15:59:58 -0500404 *err = ioctl(fd, BLKGETSIZE, &sz);
405 if (*err) {
406 sectors = sz = 0;
407 } // if
srs569464cbd172011-03-01 22:03:54 -0500408 if ((!*err) || (errno == EFBIG)) {
srs5694add79a62010-01-26 15:59:58 -0500409 *err = ioctl(fd, BLKGETSIZE64, &b);
410 if (*err || b == 0 || b == sz)
411 sectors = sz;
412 else
413 sectors = (b >> 9);
414 } // if
415 // Unintuitively, the above returns values in 512-byte blocks, no
416 // matter what the underlying device's block size. Correct for this....
417 sectors /= (GetBlockSize() / 512);
418 platformFound++;
419#endif
420 if (platformFound != 1)
srs5694fed16d02010-01-27 23:03:40 -0500421 cerr << "Warning! We seem to be running on no known platform!\n";
srs5694add79a62010-01-26 15:59:58 -0500422
423 // The above methods have failed, so let's assume it's a regular
424 // file (a QEMU image, dd backup, or what have you) and see what
425 // fstat() gives us....
426 if ((sectors == 0) || (*err == -1)) {
427 if (fstat64(fd, &st) == 0) {
srs56949a46b042011-03-15 00:34:10 -0400428 bytes = st.st_size;
srs5694add79a62010-01-26 15:59:58 -0500429 if ((bytes % UINT64_C(512)) != 0)
srs5694fed16d02010-01-27 23:03:40 -0500430 cerr << "Warning: File size is not a multiple of 512 bytes!"
431 << " Misbehavior is likely!\n\a";
srs5694add79a62010-01-26 15:59:58 -0500432 sectors = bytes / UINT64_C(512);
433 } // if
434 } // if
435 } // if (isOpen)
436 return sectors;
437} // DiskIO::DiskSize()