blob: 1bdb77b9204db8aa9b9ecc1bcc43e35833f4a513 [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>
srs569434882942012-03-23 12:49:15 -040025#include <unistd.h>
srs5694bf8950c2011-03-12 01:23:12 -050026
27#ifdef __linux__
28#include "linux/hdreg.h"
29#endif
30
srs5694add79a62010-01-26 15:59:58 -050031#include <iostream>
32
srs5694add79a62010-01-26 15:59:58 -050033#include "diskio.h"
34
35using namespace std;
36
37// Returns the official "real" name for a shortened version of same.
38// Trivial here; more important in Windows
39void DiskIO::MakeRealName(void) {
40 realFilename = userFilename;
41} // DiskIO::MakeRealName()
42
srs569455d92612010-03-07 22:16:07 -050043// Open the currently on-record file for reading. Returns 1 if the file is
44// already open or is opened by this call, 0 if opening the file doesn't
45// work.
srs5694add79a62010-01-26 15:59:58 -050046int DiskIO::OpenForRead(void) {
47 int shouldOpen = 1;
srs56948f1b2d62010-05-23 13:07:19 -040048 struct stat64 st;
srs5694add79a62010-01-26 15:59:58 -050049
50 if (isOpen) { // file is already open
51 if (openForWrite) {
52 Close();
53 } else {
54 shouldOpen = 0;
55 } // if/else
56 } // if
57
58 if (shouldOpen) {
59 fd = open(realFilename.c_str(), O_RDONLY);
60 if (fd == -1) {
srs569455d92612010-03-07 22:16:07 -050061 cerr << "Problem opening " << realFilename << " for reading! Error is " << errno << ".\n";
62 if (errno == EACCES) // User is probably not running as root
srs5694fed16d02010-01-27 23:03:40 -050063 cerr << "You must run this program as root or use sudo!\n";
srs569455d92612010-03-07 22:16:07 -050064 if (errno == ENOENT)
65 cerr << "The specified file does not exist!\n";
srs5694add79a62010-01-26 15:59:58 -050066 realFilename = "";
67 userFilename = "";
68 isOpen = 0;
69 openForWrite = 0;
70 } else {
srs56948f1b2d62010-05-23 13:07:19 -040071 isOpen = 0;
srs5694add79a62010-01-26 15:59:58 -050072 openForWrite = 0;
srs56948f1b2d62010-05-23 13:07:19 -040073 if (fstat64(fd, &st) == 0) {
74 if (S_ISDIR(st.st_mode))
75 cerr << "The specified path is a directory!\n";
Roderick W. Smithe09ef882013-07-08 22:56:00 -040076#if !defined(__FreeBSD__) && !defined(__APPLE__)
srs56948f1b2d62010-05-23 13:07:19 -040077 else if (S_ISCHR(st.st_mode))
78 cerr << "The specified path is a character device!\n";
79#endif
80 else if (S_ISFIFO(st.st_mode))
81 cerr << "The specified path is a FIFO!\n";
82 else if (S_ISSOCK(st.st_mode))
83 cerr << "The specified path is a socket!\n";
84 else
85 isOpen = 1;
86 } // if (fstat64()...)
srs5694add79a62010-01-26 15:59:58 -050087 } // if/else
88 } // if
89
90 return isOpen;
91} // DiskIO::OpenForRead(void)
92
93// An extended file-open function. This includes some system-specific checks.
94// Returns 1 if the file is open, 0 otherwise....
95int DiskIO::OpenForWrite(void) {
96 if ((isOpen) && (openForWrite))
97 return 1;
98
99 // Close the disk, in case it's already open for reading only....
100 Close();
101
102 // try to open the device; may fail....
103 fd = open(realFilename.c_str(), O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
104#ifdef __APPLE__
105 // MacOS X requires a shared lock under some circumstances....
106 if (fd < 0) {
Roderick W. Smithe09ef882013-07-08 22:56:00 -0400107 cerr << "Warning: Devices opened with shared lock will not have their\npartition table automatically reloaded!\n";
srs5694fed16d02010-01-27 23:03:40 -0500108 fd = open(realFilename.c_str(), O_WRONLY | O_SHLOCK);
srs5694add79a62010-01-26 15:59:58 -0500109 } // if
110#endif
111 if (fd >= 0) {
112 isOpen = 1;
113 openForWrite = 1;
114 } else {
115 isOpen = 0;
116 openForWrite = 0;
117 } // if/else
118 return isOpen;
119} // DiskIO::OpenForWrite(void)
120
121// Close the disk device. Note that this does NOT erase the stored filenames,
122// so the file can be re-opened without specifying the filename.
123void DiskIO::Close(void) {
124 if (isOpen)
srs569408bb0da2010-02-19 17:19:55 -0500125 if (close(fd) < 0)
126 cerr << "Warning! Problem closing file!\n";
srs5694add79a62010-01-26 15:59:58 -0500127 isOpen = 0;
128 openForWrite = 0;
129} // DiskIO::Close()
130
131// Returns block size of device pointed to by fd file descriptor. If the ioctl
132// returns an error condition, print a warning but return a value of SECTOR_SIZE
srs569455d92612010-03-07 22:16:07 -0500133// (512). If the disk can't be opened at all, return a value of 0.
srs5694add79a62010-01-26 15:59:58 -0500134int DiskIO::GetBlockSize(void) {
135 int err = -1, blockSize = 0;
srs56940741fa22013-01-09 12:55:40 -0500136#ifdef __sun__
137 struct dk_minfo minfo;
138#endif
srs5694add79a62010-01-26 15:59:58 -0500139
140 // If disk isn't open, try to open it....
141 if (!isOpen) {
142 OpenForRead();
143 } // if
144
145 if (isOpen) {
146#ifdef __APPLE__
147 err = ioctl(fd, DKIOCGETBLOCKSIZE, &blockSize);
148#endif
srs56940741fa22013-01-09 12:55:40 -0500149#ifdef __sun__
150 err = ioctl(fd, DKIOCGMEDIAINFO, &minfo);
151 if (err == 0)
152 blockSize = minfo.dki_lbsize;
153#endif
srs569408bb0da2010-02-19 17:19:55 -0500154#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500155 err = ioctl(fd, DIOCGSECTORSIZE, &blockSize);
156#endif
157#ifdef __linux__
158 err = ioctl(fd, BLKSSZGET, &blockSize);
159#endif
160
161 if (err == -1) {
162 blockSize = SECTOR_SIZE;
163 // ENOTTY = inappropriate ioctl; probably being called on a disk image
164 // file, so don't display the warning message....
165 // 32-bit code returns EINVAL, I don't know why. I know I'm treading on
166 // thin ice here, but it should be OK in all but very weird cases....
167 if ((errno != ENOTTY) && (errno != EINVAL)) {
srs5694fed16d02010-01-27 23:03:40 -0500168 cerr << "\aError " << errno << " when determining sector size! Setting sector size to "
169 << SECTOR_SIZE << "\n";
srs569455d92612010-03-07 22:16:07 -0500170 cout << "Disk device is " << realFilename << "\n";
srs5694add79a62010-01-26 15:59:58 -0500171 } // if
172 } // if (err == -1)
173 } // if (isOpen)
174
175 return (blockSize);
176} // DiskIO::GetBlockSize()
177
srs5694bf8950c2011-03-12 01:23:12 -0500178// Returns the number of heads, according to the kernel, or 255 if the
179// correct value can't be determined.
180uint32_t DiskIO::GetNumHeads(void) {
181 uint32_t numHeads = 255;
182
183#ifdef HDIO_GETGEO
184 struct hd_geometry geometry;
185
186 // If disk isn't open, try to open it....
187 if (!isOpen)
188 OpenForRead();
189
190 if (!ioctl(fd, HDIO_GETGEO, &geometry))
191 numHeads = (uint32_t) geometry.heads;
192#endif
193 return numHeads;
194} // DiskIO::GetNumHeads();
195
196// Returns the number of sectors per track, according to the kernel, or 63
197// if the correct value can't be determined.
198uint32_t DiskIO::GetNumSecsPerTrack(void) {
199 uint32_t numSecs = 63;
200
201 #ifdef HDIO_GETGEO
202 struct hd_geometry geometry;
203
204 // If disk isn't open, try to open it....
205 if (!isOpen)
206 OpenForRead();
207
208 if (!ioctl(fd, HDIO_GETGEO, &geometry))
209 numSecs = (uint32_t) geometry.sectors;
210 #endif
211 return numSecs;
212} // DiskIO::GetNumSecsPerTrack()
213
srs5694add79a62010-01-26 15:59:58 -0500214// Resync disk caches so the OS uses the new partition table. This code varies
215// a lot from one OS to another.
srs5694a17fe692011-09-10 20:30:20 -0400216// Returns 1 on success, 0 if the kernel continues to use the old partition table.
217// (Note that for most OSes, the default of 0 is returned because I've not yet
218// looked into how to test for success in the underlying system calls...)
219int DiskIO::DiskSync(void) {
220 int i, retval = 0, platformFound = 0;
srs5694add79a62010-01-26 15:59:58 -0500221
222 // If disk isn't open, try to open it....
223 if (!isOpen) {
224 OpenForRead();
225 } // if
226
227 if (isOpen) {
228 sync();
srs56940741fa22013-01-09 12:55:40 -0500229#if defined(__APPLE__) || defined(__sun__)
srs5694fed16d02010-01-27 23:03:40 -0500230 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
231 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500232 /* don't know if this helps
233 * it definitely will get things on disk though:
234 * http://topiks.org/mac-os-x/0321278542/ch12lev1sec8.html */
srs56940741fa22013-01-09 12:55:40 -0500235#ifdef __sun__
236 i = ioctl(fd, DKIOCFLUSHWRITECACHE);
237#else
srs5694add79a62010-01-26 15:59:58 -0500238 i = ioctl(fd, DKIOCSYNCHRONIZECACHE);
srs56940741fa22013-01-09 12:55:40 -0500239#endif
srs5694add79a62010-01-26 15:59:58 -0500240 platformFound++;
241#endif
srs569408bb0da2010-02-19 17:19:55 -0500242#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500243 sleep(2);
244 i = ioctl(fd, DIOCGFLUSH);
srs5694fed16d02010-01-27 23:03:40 -0500245 cout << "Warning: The kernel may continue to use old or deleted partitions.\n"
246 << "You should reboot or remove the drive.\n";
srs5694add79a62010-01-26 15:59:58 -0500247 platformFound++;
248#endif
249#ifdef __linux__
srs569400b6d7a2011-06-26 22:40:06 -0400250 sleep(1); // Theoretically unnecessary, but ioctl() fails sometimes if omitted....
251 fsync(fd);
srs5694add79a62010-01-26 15:59:58 -0500252 i = ioctl(fd, BLKRRPART);
srs5694a17fe692011-09-10 20:30:20 -0400253 if (i) {
srs5694fed16d02010-01-27 23:03:40 -0500254 cout << "Warning: The kernel is still using the old partition table.\n"
255 << "The new table will be used at the next reboot.\n";
srs5694a17fe692011-09-10 20:30:20 -0400256 } else {
257 retval = 1;
258 } // if/else
srs5694add79a62010-01-26 15:59:58 -0500259 platformFound++;
260#endif
261 if (platformFound == 0)
srs5694fed16d02010-01-27 23:03:40 -0500262 cerr << "Warning: Platform not recognized!\n";
srs5694add79a62010-01-26 15:59:58 -0500263 if (platformFound > 1)
srs5694fed16d02010-01-27 23:03:40 -0500264 cerr << "\nWarning: We seem to be running on multiple platforms!\n";
srs5694add79a62010-01-26 15:59:58 -0500265 } // if (isOpen)
srs5694a17fe692011-09-10 20:30:20 -0400266 return retval;
srs5694add79a62010-01-26 15:59:58 -0500267} // DiskIO::DiskSync()
268
269// Seek to the specified sector. Returns 1 on success, 0 on failure.
srs5694cb76c672010-02-11 22:22:22 -0500270// Note that seeking beyond the end of the file is NOT detected as a failure!
srs5694add79a62010-01-26 15:59:58 -0500271int DiskIO::Seek(uint64_t sector) {
272 int retval = 1;
273 off_t seekTo, sought;
274
275 // If disk isn't open, try to open it....
276 if (!isOpen) {
277 retval = OpenForRead();
278 } // if
279
280 if (isOpen) {
281 seekTo = sector * (uint64_t) GetBlockSize();
282 sought = lseek64(fd, seekTo, SEEK_SET);
283 if (sought != seekTo) {
284 retval = 0;
285 } // if
286 } // if
287 return retval;
288} // DiskIO::Seek()
289
290// A variant on the standard read() function. Done to work around
291// limitations in FreeBSD concerning the matching of the sector
292// size with the number of bytes read.
293// Returns the number of bytes read into buffer.
294int DiskIO::Read(void* buffer, int numBytes) {
srs5694cb76c672010-02-11 22:22:22 -0500295 int blockSize, numBlocks, retval = 0;
srs5694add79a62010-01-26 15:59:58 -0500296 char* tempSpace;
297
298 // If disk isn't open, try to open it....
299 if (!isOpen) {
300 OpenForRead();
301 } // if
302
303 if (isOpen) {
304 // Compute required space and allocate memory
305 blockSize = GetBlockSize();
306 if (numBytes <= blockSize) {
307 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500308 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500309 } else {
310 numBlocks = numBytes / blockSize;
srs5694cb76c672010-02-11 22:22:22 -0500311 if ((numBytes % blockSize) != 0)
312 numBlocks++;
313 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500314 } // if/else
srs56946aae2a92011-06-10 01:16:51 -0400315 if (tempSpace == NULL) {
316 cerr << "Unable to allocate memory in DiskIO::Read()! Terminating!\n";
317 exit(1);
318 } // if
srs5694add79a62010-01-26 15:59:58 -0500319
320 // Read the data into temporary space, then copy it to buffer
321 retval = read(fd, tempSpace, numBlocks * blockSize);
322 memcpy(buffer, tempSpace, numBytes);
srs5694add79a62010-01-26 15:59:58 -0500323
324 // Adjust the return value, if necessary....
325 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
326 retval = numBytes;
327
srs5694cb76c672010-02-11 22:22:22 -0500328 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500329 } // if (isOpen)
330 return retval;
331} // DiskIO::Read()
332
333// A variant on the standard write() function. Done to work around
334// limitations in FreeBSD concerning the matching of the sector
335// size with the number of bytes read.
336// Returns the number of bytes written.
337int DiskIO::Write(void* buffer, int numBytes) {
338 int blockSize = 512, i, numBlocks, retval = 0;
339 char* tempSpace;
340
341 // If disk isn't open, try to open it....
342 if ((!isOpen) || (!openForWrite)) {
343 OpenForWrite();
344 } // if
345
346 if (isOpen) {
347 // Compute required space and allocate memory
348 blockSize = GetBlockSize();
349 if (numBytes <= blockSize) {
350 numBlocks = 1;
srs5694cb76c672010-02-11 22:22:22 -0500351 tempSpace = new char [blockSize];
srs5694add79a62010-01-26 15:59:58 -0500352 } else {
353 numBlocks = numBytes / blockSize;
354 if ((numBytes % blockSize) != 0) numBlocks++;
srs5694cb76c672010-02-11 22:22:22 -0500355 tempSpace = new char [numBlocks * blockSize];
srs5694add79a62010-01-26 15:59:58 -0500356 } // if/else
srs56946aae2a92011-06-10 01:16:51 -0400357 if (tempSpace == NULL) {
358 cerr << "Unable to allocate memory in DiskIO::Write()! Terminating!\n";
359 exit(1);
360 } // if
361
srs5694add79a62010-01-26 15:59:58 -0500362 // Copy the data to my own buffer, then write it
srs5694add79a62010-01-26 15:59:58 -0500363 memcpy(tempSpace, buffer, numBytes);
364 for (i = numBytes; i < numBlocks * blockSize; i++) {
365 tempSpace[i] = 0;
366 } // for
367 retval = write(fd, tempSpace, numBlocks * blockSize);
368
369 // Adjust the return value, if necessary....
370 if (((numBlocks * blockSize) != numBytes) && (retval > 0))
371 retval = numBytes;
372
srs5694cb76c672010-02-11 22:22:22 -0500373 delete[] tempSpace;
srs5694add79a62010-01-26 15:59:58 -0500374 } // if (isOpen)
375 return retval;
376} // DiskIO:Write()
377
378/**************************************************************************************
379 * *
380 * Below functions are lifted from various sources, as documented in comments before *
381 * each one. *
382 * *
383 **************************************************************************************/
384
385// The disksize function is taken from the Linux fdisk code and modified
386// greatly since then to enable FreeBSD and MacOS support, as well as to
387// return correct values for disk image files.
388uint64_t DiskIO::DiskSize(int *err) {
srs5694add79a62010-01-26 15:59:58 -0500389 uint64_t sectors = 0; // size in sectors
390 off_t bytes = 0; // size in bytes
391 struct stat64 st;
392 int platformFound = 0;
srs56940741fa22013-01-09 12:55:40 -0500393#ifdef __sun__
394 struct dk_minfo minfo;
395#endif
srs5694add79a62010-01-26 15:59:58 -0500396
397 // If disk isn't open, try to open it....
398 if (!isOpen) {
399 OpenForRead();
400 } // if
401
402 if (isOpen) {
403 // Note to self: I recall testing a simplified version of
404 // this code, similar to what's in the __APPLE__ block,
405 // on Linux, but I had some problems. IIRC, it ran OK on 32-bit
406 // systems but not on 64-bit. Keep this in mind in case of
407 // 32/64-bit issues on MacOS....
408#ifdef __APPLE__
409 *err = ioctl(fd, DKIOCGETBLOCKCOUNT, &sectors);
410 platformFound++;
411#endif
srs56940741fa22013-01-09 12:55:40 -0500412#ifdef __sun__
413 *err = ioctl(fd, DKIOCGMEDIAINFO, &minfo);
414 if (*err == 0)
415 sectors = minfo.dki_capacity;
416 platformFound++;
417#endif
srs569408bb0da2010-02-19 17:19:55 -0500418#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__)
srs5694add79a62010-01-26 15:59:58 -0500419 *err = ioctl(fd, DIOCGMEDIASIZE, &bytes);
srs569408bb0da2010-02-19 17:19:55 -0500420 long long b = GetBlockSize();
srs5694add79a62010-01-26 15:59:58 -0500421 sectors = bytes / b;
422 platformFound++;
423#endif
424#ifdef __linux__
srs569408bb0da2010-02-19 17:19:55 -0500425 long sz;
426 long long b;
srs5694add79a62010-01-26 15:59:58 -0500427 *err = ioctl(fd, BLKGETSIZE, &sz);
428 if (*err) {
429 sectors = sz = 0;
430 } // if
srs569464cbd172011-03-01 22:03:54 -0500431 if ((!*err) || (errno == EFBIG)) {
srs5694add79a62010-01-26 15:59:58 -0500432 *err = ioctl(fd, BLKGETSIZE64, &b);
433 if (*err || b == 0 || b == sz)
434 sectors = sz;
435 else
436 sectors = (b >> 9);
437 } // if
438 // Unintuitively, the above returns values in 512-byte blocks, no
439 // matter what the underlying device's block size. Correct for this....
440 sectors /= (GetBlockSize() / 512);
441 platformFound++;
442#endif
443 if (platformFound != 1)
srs5694fed16d02010-01-27 23:03:40 -0500444 cerr << "Warning! We seem to be running on no known platform!\n";
srs5694add79a62010-01-26 15:59:58 -0500445
446 // The above methods have failed, so let's assume it's a regular
447 // file (a QEMU image, dd backup, or what have you) and see what
448 // fstat() gives us....
449 if ((sectors == 0) || (*err == -1)) {
450 if (fstat64(fd, &st) == 0) {
srs56949a46b042011-03-15 00:34:10 -0400451 bytes = st.st_size;
srs5694add79a62010-01-26 15:59:58 -0500452 if ((bytes % UINT64_C(512)) != 0)
srs5694fed16d02010-01-27 23:03:40 -0500453 cerr << "Warning: File size is not a multiple of 512 bytes!"
454 << " Misbehavior is likely!\n\a";
srs5694add79a62010-01-26 15:59:58 -0500455 sectors = bytes / UINT64_C(512);
456 } // if
457 } // if
458 } // if (isOpen)
459 return sectors;
460} // DiskIO::DiskSize()